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

news2024/11/15 13:56:13

目录

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

苍穹外卖day10——订单状态定时处理(Spring Task)、来单提醒和客户催单(WebSocket)

预期效果 对于超时没处理的需要定时程序处理。基于SpringTask实现。 来单提醒和客户催单。基于WebSocket实现。 Spring Task 介绍 Cron表达式 周几通常不能和日一起指定。 cron表达式在线生成器 在线Cron表达式生成器 入门案例 创建定时任务类 /*** 定义定时任务类*/ Slf4j…

OBS推流工具介绍及桌面录屏推流功能实现

OBS推流工具介绍及桌面录屏推流功能实现 文章目录 OBS推流工具介绍及桌面录屏推流功能实现1 OBS工具介绍2 OBS工具安装及简单使用2.1 安装步骤2.2 简单使用介绍 3 OBS实现桌面录屏推流工具实现4 总结 流媒体开发工程中&#xff0c;我们除了使用ffmpeg等工具辅助调试外&#xff…

《工具箱-数据库相关》Dbeaver数据导入“CSV格式“、“Txt格式“导入配置

《工具箱-数据库相关》DBeaver线下数据导入数据库表 Dbeaver数据导入"CSV格式"、"Txt格式"导入配置 使用CSV、Txt导入的时候&#xff0c;数据格式不同&#xff0c;在导入数据的时候&#xff0c;要根据数据编码样式设置不同的配置。 一、Txt格式导入 1.1 …

汇编语言基础知识

目录 前言&#xff1a; 汇编语言的产生 汇编语言的组成 内存 指令和数据 cpu对内存的读写操作 地址总线 数据总线 控制总线 内存地址空间 前言&#xff1a; 汇编语言是直接在硬件之上工作的 编程语言&#xff0c;我们首先了解硬件系统的机构&#xff0c;才能有效地应用…

CHI中的resp type

Rsp分为4大类&#xff1b; Completion response □ 除了PCrdReturn&#xff0c;PrefetchTgt&#xff0c;其他所有的trans都需要comp resp; □ 通常是一个trans的最后一个发送的message, 来自completer; 这个响应保证trans到达了POS/POC; □ 通常RN还会发送一个compack;Read an…

pytorch深度学习快速入门

放弃个人素质 享受缺德人生 拒绝精神内耗 有事直接发疯 一、安装Anaconda 官网下载地址 选择适合的系统版本进行安装即可 安装完之后&#xff0c;可以看到下面的内容 二、使用Anaconda创建开发环境 这也是为什么要使用Anaconda的原因&#xff0c;可以创建不同的开发环境&am…

剑指offer46.把数字翻译成字符串

一开始我的想法是从后面向前面不断对100取余&#xff0c;如果这个余数大于等于10并且小于等于25&#xff0c;说明这两位既可以做一个大的字母&#xff0c;也可以做两个小的字母。所以对于前面的n-2个数字来说&#xff0c;后面的连个数字使得前面的n-2个数字的结果数翻了一倍&am…

Statefulset 实战 1

上一部分与大家分享到 Statefulset 与 RplicaSet 的区别&#xff0c;以及 Statefulset 的特点&#xff0c;能做的一些事情及一些注意事项 现在我们来尝试使用 Statefulset 来部署我们的应用&#xff0c;我们可以需要有应用程序&#xff0c;然后有持久化卷 开始使用 Statefuls…

28.利用fminsearch、fminunc 求解最大利润问题(matlab程序)

1.简述 1.无约束&#xff08;无条件&#xff09;的最优化 fminunc函数 : - 可用于任意函数求最小值 - 统一求最小值问题 - 如求最大值问题&#xff1a; >对函数取相反数而变成求最小值问题&#xff0c;最后把函数值取反即为函数的最大值。 使用格式如下 1.必须预先把函数存…

【机器学习】Gradient Descent for Logistic Regression

Gradient Descent for Logistic Regression 1. 数据集&#xff08;多变量&#xff09;2. 逻辑梯度下降3. 梯度下降的实现及代码描述3.1 计算梯度3.2 梯度下降 4. 数据集&#xff08;单变量&#xff09;附录 导入所需的库 import copy, math import numpy as np %matplotlib wi…

Django的生命周期流程图(补充)、路由层urls.py文件、无名分组和有名分组、反向解析(无名反向解析、有名反向解析)、路由分发、伪静态

一、orm的增删改查方法&#xff08;补充&#xff09; 1. 查询resmodels.表名(类名).objects.all()[0]resmodels.表名(类名).objects.filter(usernameusername, passwordpassword).all()res models.表名(类名).objects.first() # 判断&#xff0c;判断数据是否有# res如果查询…

如何通过github学生包认证(远程不在学校或在校但位置报错均可用)

如何通过github学生包认证 在经历11次被拒绝&#xff0c;查阅多方博客后&#xff0c;终于成功通过了github的学生认证&#xff0c;材料每次重复的话github又会让你提交不同的材料&#xff0c;博主最后都已经要没有材料可以证明自己是学生了&#xff0c;不得不说这个认证是真的…

git使用(由浅到深)

目录流程图 1. 分布式版本控制与集中式版本控制 1.1 集中式版本控制 集中式版本控制系统有:CVS和SVN它们的主要特点是单一的集中管理的服务器&#xff0c;保存所有文件的修订版本&#xff1b;协同开发人员通过客户端连接到这台服务器&#xff0c;取出最新的文件或者提交更新…

【C++】通讯录管理系统

1.系统功能介绍与展示 2.创建项目 3.菜单功能 代码&#xff1a; //-封装函数显示该界面 如 void showMenu() //-在main函数中调用封装好的函数 #include <iostream> using namespace std;//-菜单界面 void showMenu() {cout << "***************************…

Azure pipeline自动化打包发布

pipeline自动化&#xff0c;提交代码后&#xff0c;就自动打包&#xff0c;打包成功后自动发布 第一步 pipeline提交代码后&#xff0c;自动打包。 1 在Repos,分支里选择要触发的分支&#xff0c;这里选择cn_china,对该分支设置分支策略 2 在生产验证中增加新的策略 3 在分支安…

【Linux】yum工具的认识及使用

【Linux】yum工具的认识及使用 1.知识点补充2.yum是什么3.yum常用指令3.1查看软件安装包3.1.1关于rzsz 3.2安装软件3.3卸载软件 4.yum扩展4.1扩展14.2扩展24.3扩展3 什么是工具&#xff1f; 本质上也是指令 1.知识点补充 1.我们一般安装软件&#xff0c;是不是需要把软件安装…

LabVIEW FPGA开发实时滑动摩擦系统

LabVIEW FPGA开发实时滑动摩擦系统 由于非线性摩擦效应的建模和补偿的固有困难&#xff0c;摩擦系统的运动控制已被广泛研究。最近&#xff0c;人们更加关注滑动动力学和滑动定位&#xff0c;作为传统机器人定位的低成本和更灵活的驱动替代方案。摩擦控制器设计和适当选择基础…

JVM类加载器的作用和层次结构

类加载器的作用 1)通过一个类的全限定名来获取定义此类的二进制字节流。 2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。 3)在内存中生成一个代表这个类的java.lang.Class对象,这个对象存放在方法区中。这个对象将作为程序访问方法区中的这些数据的外部接…

【嵌入式学习笔记】嵌入式入门2——中断(外部中断)

1.什么是中断 打断CPU执行正常的程序&#xff0c;转而处理紧急程序&#xff0c;然后返回原暂停的程序继续运行&#xff0c;就叫中断 1.1.中断的作用与意义 作用1&#xff1a;实时控制在确定时间内对相应事件作出响应——定时器中断作用2&#xff1a;故障处理检测到故障&…

Linux内核的I2C驱动框架详解------这应该是我目前600多篇博客中耗时最长的一篇博客

目录 1 I2C驱动整体框架图 2 I2C控制器 2.1 I2C控制器设备--I2C控制器在内核中也被看做一个设备 2.2 i2c控制器驱动程序 2.3 platform_driver结构体中的probe函数做了什么 2.3.1 疑问&#xff1a; i2cdev_notifier_call函数哪里来的 2.3.2 疑问&#xff1a;为什么有两…