66
77## 变量声明为 ` auto `
88
9+ ``` cpp
10+ int i = 0 ;
11+ ```
12+
13+ ### 用 ` auto ` 声明万物的好处
14+
15+ #### 避免复读类型
16+
17+ > {{ icon.fun }} 人类的本质是复读机。
18+
19+ ``` cpp
20+ QSlider *slider = new QSlider();
21+ ```
22+
23+ ``` cpp
24+ auto slider = new QSlider();
25+ ```
26+
27+ TODO
28+
29+ #### 模板编程产生的超长类型名喧宾夺主
30+
31+ 在 C++98 时代,仅仅只是保存个迭代器作为变量,就得写一长串:
32+
33+ ``` cpp
34+ std::map<std::string, int > tab;
35+ std::map<std::string, int >::iterator it = tab.find(" key" );
36+ ```
37+
38+ 这踏码的类型名比右侧的表达式都长了!
39+
40+ > {{ icon.fun }} 哮点解析:张心欣的第三条腿比另外两条腿都长。
41+
42+ 有了 ` auto ` 以后,无需复读类型名和繁琐的 ` ::iterator ` 废话,自动从右侧 ` find ` 函数的返回值推导出正确的类型。
43+
44+ ``` cpp
45+ std::map<std::string, int > tab;
46+ auto it = tab.find(" key" );
47+ ```
48+
49+ #### 避免未初始化
50+
51+ 因为 ` auto ` 规定必须右侧有赋初始值(否则无法推导类型)。
52+
53+ 所以只要你的代码规范能一直使用 ` auto ` 的话,就可以避免未初始化。
54+
55+ 众所周知,读取一个未初始化的变量是未定义行为,C/C++ 程序员饱受其苦,小彭老师也好几次因为忘记初始化成员指针。
56+
57+ 例如,你平时可能一不小心写:
58+
59+ ``` cpp
60+ int i;
61+ cout << i; // 未定义行为!此时 i 还没有初始化
62+ ```
63+
64+ 但是如果你用了 ` auto ` ,那么 ` auto i ` 就会直接报错,提醒你没有赋初始值:
65+
66+ ``` cpp
67+ auto i; // 编译出错,强制提醒你必须赋初始值!
68+ cout << i;
69+ ```
70+
71+ 你意识到自己漏写了 ` = 0 ` !于是你写上了初始值,编译才能通过。
72+
73+ ``` cpp
74+ auto i = 0 ;
75+ cout << i;
76+ ```
77+
78+ 可见,只要你养成“总是 ` auto ` ”的好习惯,就绝对不会忘记变量未初始化,因为 ` auto ` 会强制要求有初始值。
79+
80+ #### 自动适配类型,避免类型隐式转换
81+
82+ 假设你有一个能返回 ` int ` 的函数:
83+
84+ ``` cpp
85+ int getNum ();
86+ ```
87+
88+ 有多处使用了这个函数:
89+
90+ ``` cpp
91+ int a = getNum();
92+ ...
93+ int b = getNum() + 1 ;
94+ ...
95+ ```
96+
97+ 假如你哪天遇到牢板需求改变,它说现在我们的 ` Num ` 需要是浮点数了!
98+
99+ ``` cpp
100+ float getNum ();
101+ ```
102+
103+ 哎呀,你需要把之前那些“多处使用”里写的 ` int ` 全部一个个改成 ` float ` !
104+
105+ ``` cpp
106+ float a = getNum();
107+ ...
108+ float b = getNum() + 1 ;
109+ ...
110+ ```
111+
112+ 如果漏改一个的话,就会发生隐式转换,并且只是警告,不会报错,你根本注意不到,精度就丢失了!
113+
114+ 现在“马后炮”一下,如果当时你的“多处使用”用的是 ` auto ` ,那该多好!自动适应!
115+
116+ ``` cpp
117+ auto a = getNum();
118+ ...
119+ auto b = getNum() + 1 ;
120+ ...
121+ ```
122+
123+ 无论你今天 ` getNum ` 想返回 ` float ` 还是 ` double ` ,只需要修改 ` getNum ` 的返回值一处,所有调用了 ` getNum ` 的地方都会自动适配!
124+
125+ > {{ icon.fun }} 专治张心欣这种小计级扒皮牢板骚动反复跳脚的情况,无需你一个个去狼狈的改来改回,一处修改,处处生效。
126+
127+ #### 统一写法,更可读
128+
129+ ``` cpp
130+ std::vector<int > aVeryLongName (5);
131+ ```
132+
133+ ```cpp
134+ auto aVeryLongName = std::vector<int>(5);
135+ ```
136+
137+ TODO
138+
139+ #### 强制写明字面量类型,避免隐式转换
140+
141+ 有同学反映,他想要创建一个 ` size_t ` 类型的整数,初始化为 3。
142+
143+ ``` cpp
144+ size_t i = 3 ; // 3 是 int 类型,这里初始化时发生了隐式转换,int 转为了 size_t
145+ i = 0xffffffffff ; // OK,在 size_t 范围内(64 位编译器)
146+ ```
147+
148+ 如果直接改用 ` auto ` 的话,因为 ` 3 ` 这个字面量是 ` int ` 类型的,所以初始化出来的 ` auto i ` 也会被推导成 ` int i ` !
149+
150+ 虽然目前初始只用到了 ` 3 ` ,然而这位同学后面可能会用到 ` size_t ` 范围的更大整数存入,就存不下了。
151+
152+ ``` cpp
153+ auto i = 3 ; // 错误!auto 会推导为 int 了!
154+ i = 0xffffffffff ; // 超出 int 范围!
155+ ```
156+
157+ 由于 C++ 是静态编译,变量类型一旦确定就无法更改,我们必须在定义时就指定号范围更大的 ` size_t ` 。
158+
159+ 为了让 ` auto ` 推导出这位同学想要的 ` size_t ` 类型,我们可以在 ` 3 ` 这个字面量周围显式写出类型转换,将其转换为 ` size_t ` 。
160+
161+ > {{ icon.tip }} 显式类型转换总比隐式的要好!
162+
163+ ```
164+ auto i = (size_t)3; // 正确
165+ ```
166+
167+ 这里的类型转换用的是 C 语言的强制类型转换语法 ` (size_t)3 ` ,更好的写法是用括号包裹的 C++ 构造函数风格的强制类型转换语法:
168+
169+ ```
170+ auto i = size_t(3); // 正确
171+ ```
172+
173+ 看起来就和调用了 ` size_t ` 的“构造函数”一样。这也符合我们前面说的统一写法,类型统一和值写在一起,以括号结合,更可读。
174+
175+ > {{ icon.detail }} 顺便一提,` 0xffffffffff ` 会是 ` long ` (Linux) 或 ` long long ` (Windows) 类型字面量,因为它已经超出了 ` int ` 范围,所以实际上 ` auto i = 0xffffffffff ` 会推导为 ` long i ` 。字面量类型的规则是,如果还在 ` int ` 范围内(0x7fffffff 以内),那这个字面量就是 ` int ` ;如果超过了 0x7fffffff 但不超过 0xffffffff,就会变成 ` unsigned int ` ;如果超过了 0xffffffff 就会自动变成 ` long ` (Linux) 或 ` long long ` (Windows) ;超过 0x7fffffffffffffff 则变成 ` unsigned long ` (Linux) 或 ` unsigned long long ` (Windows) ——这时和手动加 ` ULL ` 等后缀等价,无后缀时默认 ` int ` ,如果超过了 ` int ` 编译器会自动推测一个最合适的。
176+
177+ 如果需要其他类型的变量,改用 ` short(3) ` ,` uint8_t(3) ` 配合 ` auto ` 不就行了,根本没必要把类型前置。
178+
179+ #### 避免语法歧义
180+
181+ TODO
182+
183+ ### ` auto ` 的小插曲:初始化列表
184+
9185TODO
10186
11187## 返回类型 ` auto `
12188
13- C++11 引入的 ` auto ` 关键字可以用作函数的返回类型,但它只是一个“占位”,让我们得以后置返回类型,并没有多大作用,非常残废。
189+ C++11 引入的 ` auto ` 关键字可以用作函数的返回类型,但它只是一个“占位”,让我们得以后置返回类型,并没有多大作用,所以 C++11 这版的 ` auto ` 非常残废。
14190
15191``` cpp
16192auto f () -> int;
@@ -22,7 +198,7 @@ int f();
22198
23199> {{ icon.detail }} 当初引入后置返回类型实际的用途是 ` auto f(int x) -> decltype(x * x) { return x * x; } ` 这种情况,但很容易被接下来 C++14 引入的真正 ` auto ` 返回类型推导平替了。
24200
25- 但是 C++14 引入了函数** 返回类型推导** ,` auto ` 才算真正意义上能用做函数返回类型, 它会自动根据函数中的 ` return ` 表达式推导出函数的返回类型。
201+ 终于, C++14 引入了函数** 返回类型推导** ,` auto ` 才算真正意义上能用做函数返回类型! 它会自动根据函数中的 ` return ` 表达式推导出函数的返回类型。
26202
27203``` cpp
28204auto f (int x) {
0 commit comments