• C++入门(3)—内联函数、auto、范围for、nullptr


    目录

    一、内联函数

    1、定义

    2、特性

    二、auto

    1、定义

    2、使用场景

    3、不能使用场景 

    三、范围for(C++11)

    1、定义 

     2、使用条件

    四、nullptr


    一、内联函数

    1、定义

    inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率。
    如果在上述函数前增加inline关键字将其改成内联函数,在编译期间编译器会用函数体替换函数的
    调用。
    查看方式:
    1. 在release模式下,查看编译器生成的汇编代码中是否存在call Add。
    2. 在debug模式下,需要对编译器进行设置,否则不会展开(因为debug模式下,编译器默认不会对代码进行优化)
     

    2、特性

    • inline是一种以空间换时间的做法(编译出的可执行程序会变慢),如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运行效率。
    • inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性。
    • inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。

    下面来看一个问题:

    假设swap函数代码有十行,1000个调用swap的地方
    swap不是inline时:

    swap+调用swap指令,合计是多少行指令?

    直接调用:10+1000。

    swap是inline时:

    swap+调用swap指令,合计是多少行指令?

    inline展开:10*1000。

    二、auto

    1、定义

    •  C++11引入了auto关键字,它可以用于自动推导变量的类型。auto关键字可以让编译器根据变量的初始化表达式来推导变量的类型,从而简化代码。
    • auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。

    下面这段代码演示了C++11中引入的auto关键字的用法。类型很长时,可以考虑auto关键字自动推导变量的类型,从而简化代码。 

    1. #include
    2. #include
    3. #include
    4. using namespace std;
    5. int main()
    6. {
    7. // auto 实际价值 简化代码,类型很长时,可以考虑自动推导
    8. map dict;
    9. typedef map::iterator DictIt;
    10. //map::iterator dit = dict.begin();
    11. //DictIt dit = dict.begin();
    12. auto dit = dict.begin();
    13. return 0;
    14. }
    • 在这段代码中,我们定义了一个名为 dict 的 map 容器,它将字符串映射到字符串。我们还定义了一个名为 DictIt 的类型别名,它是一个指向map容器中元素的迭代器类型。
    • 接下来,我们使用 auto 关键字来自动推导一个名为 dit 的变量的类型,它是一个指向map容器中元素的迭代器类型。由于我们已经定义了 DictIt 类型别名,我们可以使用它来指定迭代器的类型,也可以直接使用 map::iterator 来指定迭代器的类型。
    • 但是,使用auto关键字可以让我们更简洁地表达迭代器的类型,而不需要显式地指定类型。
    • 最后,我们在main函数中返回 0,表示程序正常结束。

    2、使用场景

     1、下面这个例子是auto常规操作,使用 auto 关键字来声明 b 和 c 变量的类型。

    1. int main()
    2. {
    3. int a = 0;
    4. auto b = a;
    5. auto c = &a;
    6. cout << typeid(b).name() << endl;
    7. cout << typeid(c).name() << endl;
    8. return 0;
    9. }

    使用 typeid 运算符来输出变量 b 和 c 的类型信息,VS中输出结果如下,左为64位环境,右为32位环境 。

    使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为变量实际的类型。

     2、auto与指针和引用结合起来使用 ,auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&。

    1. int main()
    2. {
    3. int a = 0;
    4. auto b = &a;
    5. auto* c = &a;
    6. auto& d = a;
    7. return 0;
    8. }
    • 使用 auto 关键字来声明 bc 和 d 变量的类型。auto 关键字可以让编译器自动推断变量的类型,根据变量的初始化表达式来确定类型。
    • b 的类型被推断为 int*,因为它被初始化为 &a,即 a 的地址。c 的类型被显式声明为 int*,与 b 的类型相同。d 的类型被推断为 int&,因为它被初始化为 a,即 a 的引用。
    • 需要注意的是,b 和 c 的类型虽然都是指针类型,但它们的声明方式不同。b 的类型是 auto 推断出来的,而 c 的类型是显式声明的指针类型。这两种方式在语义上是等价的,但在代码风格上有所不同。
    • d 的类型是引用类型,它可以看作是 a 的别名,d 可以直接访问 a 的值。

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

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

    3、不能使用场景 

    1. auto不能直接用来声明数组。此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导
      1. void TAuto(auto a)
      2. {}
    2. auto不能作为函数的参数
      1. void TestAuto()
      2. {
      3.    int a[] = {1,2,3};
      4.    auto b[] = {456};
      5. }
    3. 为了避免与C++98中的auto发生混淆,C++11只保留了auto作为类型指示符的用法
    4. auto在实际中最常见的优势用法就是跟以后会讲到的C++11提供的新式for循环,还有lambda表达式等进行配合使用。

    三、范围for(C++11)

    我们来对比一下输出数组元素的两种方式。

    1. int main()
    2. {
    3. int a[] = { 1,2,3,4,5 };
    4. for (int i = 0; i < sizeof(a) / sizeof(int); i++)
    5. {
    6. cout << a[i] << " ";
    7. }
    8. cout << endl;
    9. for (auto& e : a)
    10. {
    11. cout << e << " ";
    12. }
    13. cout << endl;
    14. return 0;
    15. }

     二者输出结果相同,其中第二种方式就叫范围for。

    1、定义 

    C++11 引入了一种新的循环语句——范围 for 循环(Range-based for loop),它可以用于遍历容器类对象或数组。

    范围 for 循环的语法如下:

    1. for (auto& var : container) {
    2. statement
    3. }
    • 其中,var 是一个变量名,用于存储容器中的元素;container 是一个容器类对象或数组,用于存储要遍历的元素;statement 是要执行的语句块,可以包含多条语句。
    • 在循环头中,使用 auto& 或 const auto& 声明一个引用变量或常量引用变量,它会依次引用容器或数组中的每个元素。在循环体中,直接使用该变量访问容器或数组元素,并执行相应的操作。
    • 范围 for 循环的优点在于语法简洁明了,可以避免下标越界等问题,同时也可以提高代码的可读性和可维护性。它适用于遍历容器或数组中的所有元素,但不适用于需要跳过或删除元素的情况。
    • 注意:与普通循环类似,可以用continue来结束本次循环,也可以用break来跳出整个循环

     2、使用条件

    •  1、for循环迭代的范围必须是确定的
      • 对于数组而言,就是数组中第一个元素和最后一个元素的范围;对于类而言,应该提供for循环迭代的范围。
    • 2、迭代的对象要实现++和==的操作。 

    来看一下下面例子中有什么问题?

    1. void TestFor(int array[])
    2. {
    3. for (auto& e : array)
    4. cout << e << endl;
    5. }
    • 数组作为函数参数:在C++中,当数组作为函数参数时,它会被解析为指向其首元素的指针。因此,你无法在函数内部获取数组的大小。这就导致了你无法在范围for循环中使用数组,因为范围for循环需要知道数组的开始和结束。

    • 使用auto&:在这个上下文中,auto&表示引用类型,但是由于数组被解析为指针,所以这里的e实际上是一个指针,而不是数组中的元素。

    四、nullptr

    在C++编程中,为变量赋予一个初始值是一种良好的编程习惯,否则可能会引发不可预知的错误,尤其是在处理未初始化的指针时。

    如果一个指针没有有效的指向,我们通常会按照以下方式进行初始化:

    1. void TestPtr()
    2. {
    3.     int* p1 = NULL;
    4.     int* p2 = 0;
    5.     // ……
    6. }

    在这里,NULL实际上是一个宏,在传统的C头文件(stddef.h)中可以看到如下代码:

    1. #ifndef NULL
    2. #ifdef __cplusplus
    3. #define NULL   0
    4. #else
    5. #define NULL   ((void *)0)
    6. #endif
    7. #endif

    具体来看,NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量。这两种定义方式都可能在使用空指针时引发一些问题。

    例如:

    1. #include
    2. using namespace std;
    3. void f(int)
    4. {
    5.     cout << "f(int)" << endl;
    6. }
    7. void f(int*)
    8. {
    9.     cout << "f(int*)" << endl;
    10. }
    11. int main()
    12. {
    13.     f(0);
    14.     f(NULL);
    15.     f((int*)NULL);
    16.     return 0;
    17. }

    在这个例子中,我们希望通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL被定义为0,所以实际上调用的是f(int)函数,这与我们的初衷相反。

    在C++98中,字面常量0既可以被视为一个整型数字,也可以被视为无类型的指针(void*)常量。然而,编译器默认将其视为一个整型常量。如果我们希望将其作为指针使用,必须进行强制类型转换,即(void *)0。

    需要注意的是:

    • 使用nullptr表示空指针时,不需要包含任何头文件,因为nullptr是在C++11中作为新关键字引入的。
    • 在C++11中,sizeof(nullptr)和sizeof((void*)0)所占的字节数是相同的。
    • 为了提高代码的健壮性,建议在表示空指针时使用nullptr

  • 相关阅读:
    JavaWeb开发之Servlet&Request&Response
    一文速学-时间序列分析算法之一次移动平均法和二次移动平均法详解+实例代码
    性能提升 25 倍:Rust 有望取代 C 和 C++,成为机器学习首选 Python 后端
    uniapp实战项目 (仿知识星球App) - - 配置开发工具和全局css样式
    stm32 中字与字节的关系
    用idea工具scala 和 Java开发 spark案例:WordCount
    特殊矩阵:零矩阵(Zero)幺矩阵(Ones)单位矩阵(Identity)随机矩阵(Random)#matlab
    编程(代码、软件)规范(适用嵌入式、单片机、上位机等)
    前端面试练习24.3.16
    AI工人操作行为流程规范识别算法
  • 原文地址:https://blog.csdn.net/m0_73800602/article/details/134438849