一些类型推导相关的功能(C++)

目录

auto关键的新用法(C++11起)

背景介绍

用法示例

注意事项

typeid运算符

type_info类

typeid的用法

decltype运算符

用法阐述

用法示例

用法建议

function类模板

用法阐述

用法示例

function较函数指针的优势

std::function和decltype的区别


auto关键的新用法(C++11起)

背景介绍

在早期的C/C++中,`auto`关键字用于声明具有自动存储期的局部变量,但它并不是必需的,因为在C语言中,默认情况下所有在函数内部声明的局部变量都是自动存储器的。因此,使用`auto`关键字并没有提供任何额外的功能或改进。

(在我刚开始学习C语言的时候也是提了一嘴这种早期用法的,不过由于早期的auto关键字并不怎么使用,所以也就仅限于提了一嘴:C语言基础 <超详细>_小白麋鹿的博客-CSDN博客)

在这种用法下,auto关键字并不是必须的,因为在C语言中,默认情况下,所有在函数内部声明的局部变量都是自动存储器的,也就是具有自动存储期的。所以,如果你在早期的C代码中看到了类似这样的声明:

auto int x;

实际上,早期C/C++的程序员很少使用`auto`关键字的原因主要有两个:

1. 不必要的冗余:在早期C语言标准中,使用`auto`关键字声明局部变量并没有任何额外的好处,反而增加了冗余。程序员可以简单地省略`auto`关键字,直接声明变量,代码更加简洁清晰。

2. 可移植性:在早期的C标准中,`auto`关键字是可选的,并且默认行为与使用`auto`关键字完全相同。因此,如果代码中使用了`auto`关键字,这些代码在不支持`auto`关键字的编译器上也能正常编译执行。这样,代码的可移植性更好,可以在不同的编译器上无需修改直接使用。

所以,C++11对auto关键字进行了重定义,并引入了一种新的用法,用于类型推断。也就是说,在C++11标准之后,是将auto作为一个新的关键字引入,并舍弃了之前的用法。例如在C++11的环境下如下代码就会报错:

用法示例

在C++中,auto是一个关键字,用于自动推导变量的类型。它可以让编译器根据变量的初始化表达式推断出变量的类型,从而简化代码并提高可读性。

使用auto关键字声明变量时,编译器会根据等号右侧的表达式来确定变量的类型。例如:

auto x = 10; // x被推断为int类型
auto name = "ChatAI"; // name被推断为const char*类型
auto pi = 3.14159; // pi被推断为double类型

auto关键字的使用可以方便地定义复杂类型的变量,特别是在涉及泛型编程时。例如:

std::vector<int> numbers = {1, 2, 3, 4, 5};
for (auto num : numbers) {
    // 在这里,num的类型被推断为int
    std::cout << num << " ";
}

需要注意的是,auto关键字在C++11中引入,并且它只能用于函数内的局部变量声明,不能用于函数参数、类成员变量或全局变量的声明。

注意事项

(1)auto不能作为函数的参数

(2)auto不能直接用来声明数组

(3)当用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&。即用auto推导的类型分成两种,一种是引用,要写作auto&,另一种就是其它类型,统一写作auto就可以了。例如:

int main()
{
    int x = 10;
    auto a = &x;
    auto* b = &x;
    auto& c = x;
    cout << typeid(a).name() << endl;
    cout << typeid(b).name() << endl;
    cout << typeid(c).name() << endl;
    *a = 20;
    *b = 30;
    c = 40;
    return 0;
}

(4)当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。例如:

void TestAuto()
{
    auto a = 1, b = 2;
    auto c = 3, d = 4.0; // 该行代码会编译失败,因为c和d的初始化表达式类型不同
}

typeid运算符

type_info类

在C++中,`std::type_info`类是用于处理类型信息的类,定义在头文件 `<typeinfo>` 中。`std::type_info`类提供了一些有用的成员函数来获取有关类型的信息,比如最常用的name成员函数。(type_info类中具体有哪些信息可以到参考这个网页的内容:std::type_info - cppreference.com )

不过需要注意的是,`std::type_info`的成员函数在不同的编译器和平台上可能会略有差异。还有一点就是,`std::type_info`类没有公开的构造函数,因此无法手动创建`std::type_info`的对象。类型信息通常由编译器在运行时自动生成并绑定到`typeid`运算符返回的对象。因此,不能手动创建`std::type_info`对象,只能通过`typeid`运算符获取。

typeid的用法

在C++中,`typeid`是一个运算符,用于获取表达式的类型信息。它的一般形式是:

typeid(expression)

注意,typeid是个运算符,并不是函数,这一点与sizeof类似。

`typeid`的作用是返回一个常量`std::type_info`对象,该对象表示`expression`的类型信息。

使用`typeid`有两个主要用途:

1. 检查对象的类型:可以将一个对象或表达式传递给`typeid`,然后通过比较`std::type_info`对象来确定表达式的类型。这对于动态类型检查非常有用。

2. 处理异常:`typeid`也在异常处理中使用,特别是在`catch`块中。通过使用`typeid`可以捕获特定类型的异常,并根据不同的异常类型进行处理。

下面是一个简单代码示例:

#include <iostream>
#include <typeinfo>

int main() {
    int num = 42;
    double pi = 3.14159;

    const std::type_info& numTypeInfo = typeid(num);
    const std::type_info& piTypeInfo = typeid(pi);

    std::cout << "Type of num: " << numTypeInfo.name() << std::endl;
    std::cout << "Type of pi: " << piTypeInfo.name() << std::endl;

    if (numTypeInfo == typeid(int)) {
        std::cout << "num is an integer." << std::endl;
    }

    if (piTypeInfo == typeid(double)) {
        std::cout << "pi is a double." << std::endl;
    }

    try {
        throw num; // Throwing an integer
    } catch (const int& caughtNum) {
        std::cout << "Caught an integer: " << caughtNum << std::endl;
    } catch (...) {
        std::cout << "Caught an unknown exception." << std::endl;
    }

    return 0;
}

不过请注意,`typeid`返回的`std::type_info`对象在不同的编译器和平台上具有不同的名称表示,因此在实际应用中,可能需要进行更复杂的类型比较。此外,对于多态类型(通过继承和虚函数实现),`typeid`的行为可能会有所不同,因为它可能会返回动态类型的信息而不是静态类型。在这种情况下,通常会与`dynamic_cast`结合使用。

decltype运算符

用法阐述

在C++中,decltype是一个运算符,用于推导(获取)表达式的类型。它允许在编译时获取表达式的类型,而无需实际执行该表达式。`decltype`特别适用于模板编程和泛型代码,因为它可以在不知道具体类型的情况下操作表达式的结果类型。

`decltype`的语法如下:

decltype(expression)

其中,expression是一个有效的C++表达式,可以是变量、函数调用、算术表达式等。

用法示例

当使用`decltype`推导类型之后,我们可以将其用于几种不同的场景。下面将展示几个示例,以更详细地说明`decltype`的用法。

示例 1:推导变量类型并声明新变量

#include <iostream>

int main() {
    int x = 42;
    decltype(x) y; // 推导x的类型,并声明新变量y
    y = 10;

    std::cout << "x: " << x << std::endl; // 输出:x: 42
    std::cout << "y: " << y << std::endl; // 输出:y: 10

    return 0;
}

示例 2:推导表达式类型并声明新变量

#include <iostream>

int main() {
    int a = 5, b = 10;
    decltype(a + b) result; // 推导a + b的类型,并声明新变量result
    result = a + b;

    std::cout << "result: " << result << std::endl; // 输出:result: 15

    return 0;
}


示例 3:推导函数返回值类型并定义函数

#include <iostream>

decltype(auto) add(int x, int y) { // 使用decltype(auto)推导函数返回值类型
    return x + y;
}

int main() {
    int a = 3, b = 5;
    auto sum = add(a, b); // 使用auto推导sum的类型
    std::cout << "sum: " << sum << std::endl; // 输出:sum: 8

    return 0;
}


示例 4:推导模板参数类型

#include <iostream>

template <typename T, typename U>
//在函数返回类型后置声明中,->操作符用于指定函数的返回类型。
//这种写法允许您在函数参数列表之后,
//使用->来指示函数的返回类型将由后续表达式的推导结果决定。
auto multiply(T t, U u) -> decltype(t * u) {
    return t * u;
}

int main() {
    double a = 2.5;
    int b = 4;
    // 使用decltype(auto)推导result的类型
    decltype(auto) result = multiply(a, b); 
    std::cout << "result: " << result << std::endl; // 输出:result: 10.0

    return 0;
}


示例 5:推导Lambda表达式的类型

Lambda表达式是一个匿名函数对象,必须通过类型推导的方式得到它的类型。因此,Lambda表达式不能直接作为模板参数,需要使用`decltype`确定比较函数对象的类型,然后将其作为模板参数。例如,用`std::priority_queue`实现一个小根堆:

#include <iostream>
#include <queue>

int main() 
{
    //这里虽然用auto推导出了compare的类型,
    //但compare是一个实体化的匿名函数对象,
    //而priority_queue第三个参数要求的是类型,
    //所以这里只能通过decltype运算符来获取compare的类型。
    auto compare = [](int* a, int* b) { return *a > *b; };
    std::priority_queue<int*, std::vector<int*>, decltype(compare)> 
        min_heap(compare);
    return 0;
}

用法建议

decltype 是一个强大的工具,它允许我们在不实际计算表达式的情况下获得表达式的类型。在用法上,它与auto有一定的相似性。decltype 在模板编程和泛型编程中特别有用,可以帮助我们编写更灵活、通用的代码。然而,在普通的C++代码中,auto关键字通常更简洁和易于理解。

function类模板

用法阐述

        当使用C++编写复杂程序时,经常需要处理不同类型的可调用对象,例如普通函数、函数指针、成员函数指针、lambda表达式等。这些可调用对象可能具有不同的参数类型和返回类型,因此需要一种机制来以通用的方式处理它们。std::function就是C++标准库中提供的用于处理这种情况的工具。
        在C++中,std::function是一个类模板,它是C++11标准库中的一部分。std::function提供了一种通用的方式来封装函数或可调用对象,使得您可以像使用函数指针一样使用它,但更加灵活和类型安全。它为函数的签名提供了一种类型擦除的机制,使得可以在运行时存储和调用具有不同参数和返回类型的可调用对象。
        std::function的灵活性使其在很多情况下非常有用,比如在回调函数、函数参数传递等场景下,可以动态地在运行时指定具体的函数或可调用对象。

注释:

类型擦除的机制:

        std::function 提供的类型擦除机制是指它能够在运行时存储和调用具有不同函数签名的可调用对象,而在编译时不需要知道这些对象的具体类型。这意味着您可以使用同一个 std::function 对象来存储和调用不同函数签名的函数,而无需在编译时为每个不同的函数签名创建不同的 std::function 对象。

std::function是一个函数模板类:

        std::function在C++中既不是函数也不是运算符,而是一个模板类,它是C++标准库中的一部分。它用于封装和存储可调用对象,如函数、函数指针、lambda表达式、成员函数指针等,并且允许您在运行时动态地调用这些可调用对象。

  • 用法格式:
#include <functional> //所属的头文件

// 定义一个可调用对象的类型为std::function,这里以函数为例
std::function<返回类型(参数类型1, 参数类型2, ...)> myFunction;

// 将可调用对象赋值给std::function
myFunction = someFunction; // 可以是普通函数、函数指针、lambda表达式等

// 调用可调用对象
返回类型 result = myFunction(参数1, 参数2, ...);

这里的返回类型参数类型是具体的函数签名。根据具体的实际需求来定义。

用法示例

下面我们通过一些示例来详细理解std::function的用法:

示例1:存储普通函数

#include <iostream>
#include <functional>

int add(int a, int b) {
    return a + b;
}

int main() {
    std::function<int(int, int)> myFunction = add;
    std::cout << "Result: " << myFunction(3, 4) << std::endl;
    return 0;
}

示例2:存储lambda表达式

#include <iostream>
#include <functional>

int main() {
    std::function<int(int, int)> myFunction = 
        [](int a, int b) { return a * b; };
    std::cout << "Result: " << myFunction(3, 4) << std::endl;
    return 0;
}

示例3:存储成员函数

#include <iostream>
#include <functional>

class Calculator {
public:
    int add(int a, int b) {
        return a + b;
    }
};

int main() {
    Calculator calc;
    std::function<int(Calculator&, int, int)> 
        myFunction = &Calculator::add;
    std::cout << "Result: " << myFunction(calc, 3, 4) << std::endl;
    return 0;
}

示例4:存储仿函数

#include <iostream>
#include <functional>

class Multiply {
public:
    int operator()(int a, int b) {
        return a * b;
    }
};

int main() {
    Multiply multiply;
    std::function<int(int, int)> myFunction = multiply;
    std::cout << "Result: " << myFunction(3, 4) << std::endl;
    return 0;
}

function较函数指针的优势

std::function 相较于函数指针具有多个优势,大致可以概括为下面几点:

  1. 安全性: 由于在编译时会进行类型匹配检查,而std::function 提供了类型擦除的机制,允许在运行时存储和调用不同类型的可调用对象,所以就不需要在编译时知道它们的具体类型,这就使得function的用法更加安全。而函数指针可能会因为类型不匹配而引发未定义行为。

  2. 灵活性: std::function 可以存储普通函数、函数指针、lambda 表达式、成员函数指针以及仿函数等。而函数指针只能用于存储普通函数和静态成员函数。

  3. 不受限于同一函数签名: 可以使用不同的 std::function 对象存储具有不同函数签名的可调用对象。这使得编写通用的函数接口或回调机制变得更加方便,而函数指针通常需要针对不同的函数签名使用不同的函数指针类型。

  4. 复制和赋值: std::function 可以进行复制和赋值操作,这使得在复杂的数据结构中传递和存储函数变得更加容易。

尽管 std::function 有很多优势,但std::function 的灵活性和类型安全性会带来一些运行时开销,相比直接调用函数指针,会有一些性能上的损失。但在大多数应用中,这种损失通常可以忽略不计,特别是对于非常复杂的函数调用场景而言。

std::function和decltype的区别

std::functiondecltype 具有不同的用途,它们有着不同的作用和应用场景:

  • std::function:

        std::function是一个类模板,用于封装和存储可调用对象,并提供一种通用的方式来调用这些对象。它的优势在于可以存储不同函数签名的可调用对象,并在运行时动态选择和调用它们。std::function 主要用于处理运行时多态(runtime polymorphism),即在运行时确定要调用的具体函数或可调用对象。代码示例:

#include <iostream>
#include <functional>

int add(int a, int b) {
    return a + b;
}

int main() {
    std::function<int(int, int)> myFunction = add;
    std::cout << "Result: " << myFunction(3, 4) << std::endl;
    return 0;
}
  • decltype

        decltype是一个关键字,用于在编译时获取表达式的类型而不实际执行该表达式。它的主要用途是在编译时推导出表达式的类型,通常用于函数返回类型的推导、模板编程、以及其他需要根据表达式类型进行编译时决策的场景。代码示例:

#include <iostream>

int add(int a, int b) {
    return a + b;
}

int main() {
    decltype(add(3, 4)) result; // 推导出 add(3, 4) 表达式的类型,即 int
    result = add(3, 4);
    std::cout << "Result: " << result << std::endl;
    return 0;
}

综上所述,std::functiondecltype 在 C++ 中有着不同的应用场景和用途。std::function 用于封装和调用可调用对象,特别适用于运行时动态选择函数的情况。而 decltype 则用于在编译时获取表达式的类型,对于模板编程、函数返回类型推导以及其他需要根据表达式类型进行编译时决策的情况非常有用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/53808.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

DID以及社交网络中的ZKP

1. 引言 本文关键术语为&#xff1a; Decentralized Identity (DID&#xff0c;去中心化身份) or self-sovereign identity (SSI&#xff0c;自治身份) &#xff1a;是一个基于开放标准的框架&#xff0c;使用自主、独立的标识符和可验证证书&#xff0c;实现可信的数据交换。…

Vue2 第六节 key的作用与原理

&#xff08;1&#xff09;虚拟DOM &#xff08;2&#xff09;v-for中的key的作用 一.虚拟DOM 1.虚拟DOM就是内存中的数据 2.原生的JS没有虚拟DOM: 如果新的数据和原来的数据有重复数据&#xff0c;不会在原来的基础上新加数据&#xff0c;而是重新生成一份 3. Vue会有虚拟…

驶向专业:嵌入式开发在自动驾驶中的学习之道

导语: 自动驾驶技术在汽车行业中的快速发展为嵌入式开发领域带来了巨大的机遇。作为自动驾驶的核心组成部分&#xff0c;嵌入式开发在驱动汽车的智能化和自主性方面发挥着至关重要的作用。本文将探讨嵌入式开发的学习方向、途径以及未来在自动驾驶领域中的展望。 一、学习方向:…

3ds MAX绘制茶壶

综合一下之前的内容画个茶壶 长方形&#xff0c;然后转化为可编辑多边形&#xff0c;添加节点并设置圆角&#xff0c;如下图 车削生成一个圆环&#xff0c;其实这一步也可以用一个圆柱体和两个圆角圆柱体解决 效果如下&#xff1a; 茶壶的底座绘制好了 接下来是茶壶的上半边 …

python测试开发面试常考题:装饰器

目录 简介 应用 第一类对象 装饰器 描述器descriptor 资料获取方法 简介 Python 装饰器是一个可调用的(函数、方法或类)&#xff0c;它获得一个函数对象 func_in 作为输入&#xff0c;并返回另一函数对象 func_out。它用于扩展函数、方法或类的行为。 装饰器模式通常用…

Hudi Flink SQL源码调试学习(1)

前言 本着学习hudi-flink源码的目的&#xff0c;利用之前总结的文章Hudi Flink SQL代码示例及本地调试中的代码进行调试,记录调试学习过程中主要的步骤及对应源码片段。 版本 Flink 1.15.4Hudi 0.13.0 目标 在文章Hudi Flink SQL代码示例及本地调试中提到&#xff1a;我们…

【LLM系列之指令微调】长话短说大模型指令微调的“Prompt”

1 指令微调数据集形式“花样”太多 大家有没有分析过 prompt对模型训练或者推理的影响&#xff1f;之前推理的时候&#xff0c;发现不加训练的时候prompt&#xff0c;直接输入模型性能会变差的&#xff0c;这个倒是可以理解。假如不加prompt直接训练&#xff0c;是不是测试的时…

基于高通QCC5171的对讲机音频数据传输系统设计

一 研发资料准备 二 设计方法 蓝牙连接与配对&#xff1a;使用QCC5171的蓝牙功能&#xff0c;实现设备之间的蓝牙连接和配对。确保设备能够相互识别并建立起稳定的蓝牙连接。 音频采集与处理&#xff1a;将麦克风采集到的音频数据通过QCC5171的ADC&#xff08;模数转换器&…

linux系统编程重点复习--线程同步

目录 复习目标&#xff1a; 1 互斥锁 1.1互斥锁的使用步骤 1.2 练习 1.3 死锁 2 读写锁 3 条件变量 4 信号量 复习目标&#xff1a; 熟练掌握互斥量的使用说出什么叫死锁以及解决方案熟练掌握读写锁的使用熟练掌握条件变量的使用理解条件变量实现的生产消费者模型理解…

python简单的病毒编程代码,如何用python写一个病毒

大家好&#xff0c;本文将围绕python简单的病毒编程代码展开说明&#xff0c;如何用python做恶搞病毒是一个很多人都想弄明白的事情&#xff0c;想搞清楚如何用python写一个病毒需要先了解以下几个事情。 1、Python能不能写病毒 国家计算机病毒应急处理中心通过对互联网的监测…

算法leetcode|64. 最小路径和(rust重拳出击)

文章目录 64. 最小路径和&#xff1a;样例 1&#xff1a;样例 2&#xff1a;提示&#xff1a; 分析&#xff1a;题解&#xff1a;rust&#xff1a;go&#xff1a;c&#xff1a;python&#xff1a;java&#xff1a; 64. 最小路径和&#xff1a; 给定一个包含非负整数的 m x n 网…

windows 安装 mongodb 数据库

软件下载 访问官方的下载地址&#xff1a; https://www.mongodb.com/try/download/community &#xff0c;然后选择对应的版本进行下载 下载好了之后双击进行安装 软件安装 1、点击 next 点击下一步 2、勾选接受协议&#xff0c;点击 next 3、第三页有两个选项&#x…

redisson分布式锁学习

什么是分布式锁? 当有多个线程并发访问同一共享数据时,如果多个线程同时都去修改这个共享数据,且修改操作不是原子操作,就很有可能出现线程安全问题&#xff0c;而产生线程安全问题的根本原因是缺乏对共享数据访问的同步和互斥。 为了解决这个问题&#xff0c;通常我们的做法…

P2P网络NAT穿透原理(打洞方案)

1.关于NAT NAT技术&#xff08;Network Address Translation&#xff0c;网络地址转换&#xff09;是一种把内部网络&#xff08;简称为内网&#xff09;私有IP地址转换为外部网络&#xff08;简称为外网&#xff09;公共IP地址的技术&#xff0c;它使得一定范围内的多台主机只…

SpringBoot超级详解

1.父工程的父工程 在父工程的父工程中的核心依赖&#xff0c;专门用来版本管理的 版本管理。 2.父工程 资源过滤问题&#xff0c;都帮解决了&#xff0c;什么配置文件&#xff0c;都已经配置好了&#xff0c;资源过滤问题是帮助&#xff0c;过滤解决让静态资源文件能够过滤到…

别再分库分表了,来试试它吧

什么是NewSQL传统SQL的问题 升级服务器硬件数据分片NoSQL 的问题 优点缺点NewSQL 特性NewSQL 的主要特性三种SQL的对比TiDB怎么来的TiDB社区版和企业版TIDB核心特性 水平弹性扩展分布式事务支持金融级高可用实时 HTAP云原生的分布式数据库高度兼容 MySQLOLTP&OLAP&#xff…

openssl/bn.h: No such file or directory

报错截图 解决方法 ubuntu apt install libssl-dev -y centos yum install openssl-devel -y

第六章 支持向量机

文章目录 支持向量机间隔和支持向量对偶问题问题推导SMO 核函数实验 支持向量机 ⽀持向量机&#xff08;Support Vector Machines&#xff0c;SVM&#xff09; 优点&#xff1a;泛化错误率低&#xff0c;计算开销不⼤&#xff0c;结果易解释。缺点&#xff1a;对参数调节和核…

Python 教程之标准库概览

概要 Python 标准库非常庞大&#xff0c;所提供的组件涉及范围十分广泛&#xff0c;使用标准库我们可以让您轻松地完成各种任务。 以下是一些 Python3 标准库中的模块&#xff1a; 「os 模块」 os 模块提供了许多与操作系统交互的函数&#xff0c;例如创建、移动和删除文件和…