一些类型推导相关的功能(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代码中看到了类似这样的声明:

cpp 复制代码
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关键字声明变量时,编译器会根据等号右侧的表达式来确定变量的类型。++例如:

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

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

cpp 复制代码
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就可以了。例如:

cpp 复制代码
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)当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。例如:

cpp 复制代码
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的对象,只能通过typeid运算符获取。

typeid的用法

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

cpp 复制代码
typeid(expression)

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

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

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

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

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

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

cpp 复制代码
#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`特别适用于模板编程和泛型代码,因为它可以在不知道具体类型的情况下操作表达式的结果类型。

语法格式如下:

cpp 复制代码
decltype(expression)

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

用法示例

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

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

cpp 复制代码
#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:**推导表达式类型并声明新变量

cpp 复制代码
#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:**推导函数返回值类型并定义函数

cpp 复制代码
#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:**推导模板参数类型

cpp 复制代码
#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`实现一个小根堆:

cpp 复制代码
#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表达式、成员函数指针等,并且允许您在运行时动态地调用这些可调用对象。

  • 用法格式:
cpp 复制代码
#include <functional> //所属的头文件

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

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

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

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

用法示例

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

示例1:存储普通函数

cpp 复制代码
#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表达式

cpp 复制代码
#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:存储成员函数

cpp 复制代码
#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:存储仿函数

cpp 复制代码
#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),即在运行时确定要调用的具体函数或可调用对象。代码示例:

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

cpp 复制代码
#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 则用于在编译时获取表达式的类型,对于模板编程、函数返回类型推导以及其他需要根据表达式类型进行编译时决策的情况非常有用。

相关推荐
此生只爱蛋9 分钟前
【手撕排序2】快速排序
c语言·c++·算法·排序算法
blammmp17 分钟前
Java:数据结构-枚举
java·开发语言·数据结构
何曾参静谧29 分钟前
「C/C++」C/C++ 指针篇 之 指针运算
c语言·开发语言·c++
暗黑起源喵35 分钟前
设计模式-工厂设计模式
java·开发语言·设计模式
WaaTong40 分钟前
Java反射
java·开发语言·反射
Troc_wangpeng41 分钟前
R language 关于二维平面直角坐标系的制作
开发语言·机器学习
努力的家伙是不讨厌的43 分钟前
解析json导出csv或者直接入库
开发语言·python·json
Envyᥫᩣ1 小时前
C#语言:从入门到精通
开发语言·c#
童先生1 小时前
Go 项目中实现类似 Java Shiro 的权限控制中间件?
开发语言·go
lulu_gh_yu1 小时前
数据结构之排序补充
c语言·开发语言·数据结构·c++·学习·算法·排序算法