Skip to content

Commit 8119e85

Browse files
authored
Update auto.md
1 parent c6bbf5f commit 8119e85

File tree

1 file changed

+178
-2
lines changed

1 file changed

+178
-2
lines changed

docs/auto.md

+178-2
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,187 @@ TODO
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+
9185
TODO
10186

11187
## 返回类型 `auto`
12188

13-
C++11 引入的 `auto` 关键字可以用作函数的返回类型,但它只是一个“占位”,让我们得以后置返回类型,并没有多大作用,非常残废。
189+
C++11 引入的 `auto` 关键字可以用作函数的返回类型,但它只是一个“占位”,让我们得以后置返回类型,并没有多大作用,所以 C++11 这版的 `auto` 非常残废。
14190

15191
```cpp
16192
auto 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
28204
auto f(int x) {

0 commit comments

Comments
 (0)