下面来看一个问题:
假设swap函数代码有十行,1000个调用swap的地方
swap不是inline时:
swap+调用swap指令,合计是多少行指令?
直接调用:10+1000。
swap是inline时:
swap+调用swap指令,合计是多少行指令?
inline展开:10*1000。
- C++11引入了auto关键字,它可以用于自动推导变量的类型。auto关键字可以让编译器根据变量的初始化表达式来推导变量的类型,从而简化代码。
- auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。
下面这段代码演示了C++11中引入的auto关键字的用法。类型很长时,可以考虑auto关键字自动推导变量的类型,从而简化代码。
- #include
- #include
- #include
- using namespace std;
- int main()
- {
-
- // auto 实际价值 简化代码,类型很长时,可以考虑自动推导
- map
dict; - typedef map
::iterator DictIt; - //map
::iterator dit = dict.begin(); - //DictIt dit = dict.begin();
- auto dit = dict.begin();
-
- return 0;
- }
1、下面这个例子是auto常规操作,使用
auto关键字来声明b和c变量的类型。
- int main()
- {
- int a = 0;
- auto b = a;
- auto c = &a;
- cout << typeid(b).name() << endl;
- cout << typeid(c).name() << endl;
- return 0;
- }
使用 typeid 运算符来输出变量 b 和 c 的类型信息,VS中输出结果如下,左为64位环境,右为32位环境 。


使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为变量实际的类型。
2、auto与指针和引用结合起来使用 ,auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&。
- int main()
- {
- int a = 0;
- auto b = &a;
- auto* c = &a;
- auto& d = a;
- return 0;
- }
auto 关键字来声明 b、c 和 d 变量的类型。auto 关键字可以让编译器自动推断变量的类型,根据变量的初始化表达式来确定类型。b 的类型被推断为 int*,因为它被初始化为 &a,即 a 的地址。c 的类型被显式声明为 int*,与 b 的类型相同。d 的类型被推断为 int&,因为它被初始化为 a,即 a 的引用。b 和 c 的类型虽然都是指针类型,但它们的声明方式不同。b 的类型是 auto 推断出来的,而 c 的类型是显式声明的指针类型。这两种方式在语义上是等价的,但在代码风格上有所不同。d 的类型是引用类型,它可以看作是 a 的别名,d 可以直接访问 a 的值。3、当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
- void TAuto()
- {
- auto a = 1, b = 2;
- auto c = 3, d = 4.0; // 该行代码会编译失败,因为c和d的初始化表达式类型不同
- }
- void TAuto(auto a)
- {}
- void TestAuto()
- {
- int a[] = {1,2,3};
- auto b[] = {4,5,6};
- }
我们来对比一下输出数组元素的两种方式。
- int main()
- {
- int a[] = { 1,2,3,4,5 };
- for (int i = 0; i < sizeof(a) / sizeof(int); i++)
- {
- cout << a[i] << " ";
- }
- cout << endl;
- for (auto& e : a)
- {
- cout << e << " ";
- }
- cout << endl;
- return 0;
- }
二者输出结果相同,其中第二种方式就叫范围for。

C++11 引入了一种新的循环语句——范围
for循环(Range-based for loop),它可以用于遍历容器类对象或数组。
范围 for 循环的语法如下:
- for (auto& var : container) {
- statement
- }
var 是一个变量名,用于存储容器中的元素;container 是一个容器类对象或数组,用于存储要遍历的元素;statement 是要执行的语句块,可以包含多条语句。auto& 或 const auto& 声明一个引用变量或常量引用变量,它会依次引用容器或数组中的每个元素。在循环体中,直接使用该变量访问容器或数组元素,并执行相应的操作。for 循环的优点在于语法简洁明了,可以避免下标越界等问题,同时也可以提高代码的可读性和可维护性。它适用于遍历容器或数组中的所有元素,但不适用于需要跳过或删除元素的情况。来看一下下面例子中有什么问题?
- void TestFor(int array[])
- {
- for (auto& e : array)
- cout << e << endl;
- }
数组作为函数参数:在C++中,当数组作为函数参数时,它会被解析为指向其首元素的指针。因此,你无法在函数内部获取数组的大小。这就导致了你无法在范围for循环中使用数组,因为范围for循环需要知道数组的开始和结束。
使用auto&:在这个上下文中,auto&表示引用类型,但是由于数组被解析为指针,所以这里的e实际上是一个指针,而不是数组中的元素。
在C++编程中,为变量赋予一个初始值是一种良好的编程习惯,否则可能会引发不可预知的错误,尤其是在处理未初始化的指针时。
如果一个指针没有有效的指向,我们通常会按照以下方式进行初始化:
- void TestPtr()
- {
- int* p1 = NULL;
- int* p2 = 0;
- // ……
- }
在这里,NULL实际上是一个宏,在传统的C头文件(stddef.h)中可以看到如下代码:
- #ifndef NULL
- #ifdef __cplusplus
- #define NULL 0
- #else
- #define NULL ((void *)0)
- #endif
- #endif
具体来看,NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量。这两种定义方式都可能在使用空指针时引发一些问题。
例如:
- #include
- using namespace std;
- void f(int)
- {
- cout << "f(int)" << endl;
- }
- void f(int*)
- {
- cout << "f(int*)" << endl;
- }
- int main()
- {
- f(0);
- f(NULL);
- f((int*)NULL);
- return 0;
- }
在这个例子中,我们希望通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL被定义为0,所以实际上调用的是f(int)函数,这与我们的初衷相反。
在C++98中,字面常量0既可以被视为一个整型数字,也可以被视为无类型的指针(void*)常量。然而,编译器默认将其视为一个整型常量。如果我们希望将其作为指针使用,必须进行强制类型转换,即(void *)0。
需要注意的是: