Skip to content

Commit 1caccb2

Browse files
committed
feat: change respo to Reading'
0 parents  commit 1caccb2

File tree

728 files changed

+52936
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

728 files changed

+52936
-0
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.DS_Store
2+
~$*.pptx
3+
blogFiles/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
## 条款1:理解模版型别推导
2+
3+
函数模版形如:
4+
5+
```cpp
6+
template<typename T>
7+
void f(ParamType param);
8+
```
9+
10+
一次调用形如:
11+
12+
```cpp
13+
f(expr);
14+
```
15+
16+
编译器会通过expre推导两个型别:一个是T的型别,一个是ParamType的型别,这两个型别通常不一样,因为ParamType通常包含了一些饰词,如const或引用符号等限定词。
17+
18+
T的型别推导结果,不仅仅以来expr的型别,还依赖ParamType的形式。具体分三种情况讨论。
19+
20+
### 情况1: *ParamType*是个指针或者引用,但不是个万能引用
21+
22+
这种情形下,型别推导会这样运作:
23+
24+
1. 若expr具有引用型别,现将引用部分忽略
25+
2. 对expr的型别和ParamType的型别执行模式匹配,来决定T的型别
26+
27+
例如:
28+
29+
```cpp
30+
template<typename T>
31+
void f(T& param); // param是个引用
32+
33+
声明变量:
34+
35+
int x = 27; // x的类型是int
36+
const int cx = x; // cx的类型是const int
37+
const int& rx = x; // rx是x的型别为const int的引用
38+
39+
f(x); // T的类型是int,param的类型是int&
40+
f(cx); // T的类型是const int,param的类型是const int&
41+
f(rx); // T的类型是const int,param的类型是const int&
42+
```
43+
44+
由于cx和rx的值都被指明为const ,所以T的型别被推到为const int,从而形参的型别就成了const int&。
45+
46+
当人们向引用型别传入const 对象时,他们期望对该对象保持不可修改的属性,即期望该形参成为const的引用型别。
47+
48+
向持有T&型别的模版传入const对象是安全的:该对象的常量性(constness)会成为T的型别推导结果的组成部分。
49+
50+
而引用性(reference-ness)会在型别推导过程中被忽略。
51+
52+
```cpp
53+
template<typename T>
54+
void f(const T& param); // param是个引用
55+
56+
声明变量:
57+
58+
int x = 27; // 同前
59+
const int cx = x; // 同前
60+
const int& rx = x; // 同前
61+
62+
f(x); // T的类型是int,param的类型是const int&
63+
f(cx); // T的类型是int,param的类型是const int&
64+
f(rx); // T的类型是int,param的类型是const int&
65+
```
66+
67+
rx的引用性在型别推导过程中是被忽略的。
68+
69+
如果param是个指针,而非引用,运作方式上本质上没有不同。
70+
71+
```cpp
72+
template<typename T>
73+
void f(T* param); // param是个指针
74+
75+
int x = 27;
76+
const int *px = &x; // px是指到x的指针,类型为 const int
77+
f(&x); // T的类型是int,param的类型是int*
78+
f(px); // T的类型是const int,param的类型是const int*
79+
```
80+
81+
### 情况2: *ParamType*是个万能引用
82+
83+
万能引用的声明型别写作:T&&
84+
85+
- 如果expr是个左值,T和ParamType都会被推导为左值引用。这个结果具有双重奇特之处:首先,这是在模版类型推导中,T被推导为引用型的唯一情形。其次,尽管在声明时使用的是右值引用语法,它的型别推导结果却是左值引用。
86+
- 如果expre是个右值,则应用“常规”的规则。
87+
88+
```cpp
89+
template<typename T>
90+
void f(T&& param); // param是个万能引用
91+
92+
声明变量:
93+
94+
int x = 27; // 同前
95+
const int cx = x; // 同前
96+
const int& rx = x; // 同前
97+
98+
f(x); // x是个左值,所以T的型别是int&,param的型别也是int&
99+
f(cx); // cx是个左值,所以T的型别是const int&
100+
// param的型别也是const int&
101+
f(rx); // rx是个左值,所以T的型别是const int&
102+
// param的型别也是const int&
103+
f(27); // 27是个右值,所以T的型别是int
104+
// param就成了int&&
105+
```
106+
107+
万能引用的推导规则不同于左值引用和右值引用形参。当遇到万能引用时,型别推导类型规则会区分实参是左值还是右值。而非万能引用时从来不会作这样的区分的。
108+
109+
### 情况3: *ParamType*既非指针也非引用
110+
111+
既非指针,也非引用,就是按值传递了。
112+
113+
```cpp
114+
template<typename T>
115+
void f(T param); // param是按值传递
116+
```
117+
118+
无论传入的是什么,param都会是它的一个副本,也即是一个全新的对象。
119+
120+
- exper具有引用型别,则忽略其引用部分
121+
- 忽略引用部分之后,若expre是个const对象,也忽略。如果是一个volatile对象,也忽略。
122+
123+
所以
124+
125+
```cpp
126+
int x = 27; // 同前
127+
const int cx = x; // 同前
128+
const int& rx = x; // 同前
129+
130+
f(x); // T和prama都是int
131+
f(cx); // T和prama都是int
132+
f(rx); // T和prama都是int
133+
```
134+
135+
cx和rx代表const值,param仍然不具有const型别,这是合理的。
136+
137+
param是个完全独立于cx和rx存在的对象——是cx和rx的一个**副本**,cx和rx不能修改不能说明param不能修改。expre的常量性和挥发性(volatileness,若有)可以在推导param的型别时忽略。
138+
139+
重点说明的是,const(和volatile)仅会在按值形参处被忽略。若形参是const的引用或者指针,expre的常量性会在型别推导过程中加以保留。
140+
141+
但是这种情况,expr是一个指到const对象的const指针,且expr按值传递给param:
142+
143+
```cpp
144+
template<typename T>
145+
void f(T param);
146+
147+
const char* const ptr = "xxxx";
148+
f(ptr); // 传递类型为 const char * const
149+
```
150+
151+
ptr这个指针自己会按值传递,按照按值传递的规则,ptr的常量性会被忽略,param的型别会被推导为const char*。
152+
153+
即ptr的指向对象的常量性会被保留,但是其自身的常量性会在以复制方式创建新指针param时被忽略。
154+
155+
### 数组实参
156+
157+
数组型别有别于指针型别,很多语境下,数组会退化成指到其首元素的指针。
158+
159+
```cpp
160+
const char name[] = "xxx";
161+
162+
template<typename T>
163+
void f(T param);
164+
f(name);
165+
```
166+
167+
由于数组形参声明会按照他们好像是指针形参那样处理,按值传递给函数模版的数组型别会被推导成指针型别。
168+
169+
在模版f调用中,其型别类型T会被推导成const char*
170+
171+
难点来了,尽管函数无法声明真正的数组类型形参,它们却能够将形参声明成数组的引用。如果修改模版f,制定按照引用的方式传递实参。
172+
173+
```cpp
174+
template<typename T>
175+
void f(T& param);
176+
f(name); // 向f传递一个数组
177+
```
178+
179+
这种情况下,T的型别会推导成实际数组的型别,这个型别包含数组的尺寸。
180+
181+
本例中,T的型别推导类型结果为 const char [4],而f的形参被推导为 const char (&)[4]。
182+
183+
可以利用数组引用这一能力创造一个模版,用来推导数组含有的元素个数。
184+
185+
```cpp
186+
// 以编译期常量的形式返回数组尺寸
187+
template<typename T, std::size_t N>
188+
constexpr std::size_t arraySize( T(&)[N]) noexcept
189+
{
190+
return N;
191+
}
192+
```
193+
194+
将该函数声明为constexpr,能够使得其返回值在编译期间可用。从而就可以指定一个数组时,制定其尺寸和另一个数组相同。
195+
196+
```cpp
197+
int keyVals = {1,2,3,4};
198+
int mappedVals[arraySize(keyVals)];
199+
```
200+
201+
### 函数实参
202+
203+
数组并非c++唯一可以退化为指针之物。函数型别也同样可以退化为函数指针,针对数组型别的推导的一切导论适用于函数向其指针的退化。
204+
205+
```cpp
206+
void someFunc(int, double);
207+
208+
template<typename T>
209+
void f1(T param); // param按值传递
210+
211+
template<typename T>
212+
void f2(T param); // param按引用传递
213+
214+
f1(someFunc); // param被推导为函数指针
215+
// 具体类型为 void(*)(int, double)
216+
f2(someFunc); // param被推导为函数引用
217+
// 具体类型为 void(&)(int, double)
218+
```
219+
220+
221+
222+
## 总结
223+
224+
- 在模版类型推导过程中,具有引用类型的实参会被当成非引用类型来处理。即其引用性会被忽略
225+
- 对万能引用形参进行推导时,左值实参会进行特殊处理
226+
- 对按值传递的形参进行推导时,若实参中带有const 或 volatile饰词,则它们还是会被当作不带const或volatile饰词的型别来处理
227+
- 在模版型别推导过程中,数组或函数类型的实参会退化成对应的指针,除非它们被用来初始化引用。
228+
229+
230+
231+
232+
233+
234+
235+
236+
237+
238+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
## 条款2: 理解auto 型别推导
2+
3+
在模板型别推导和auto型别推导可以建立起一一映射,它们之间也确实存在着双向的算法变换。
4+
5+
```cpp
6+
template<typename T>
7+
void f(ParamType param);
8+
9+
f(expr); // 以某表达式调用f
10+
```
11+
12+
在f的调用语句中,编译器会利用expr来推导T和ParamType的型别。
13+
14+
当某变量采用auto来声明的时候,auto就扮演了模板中T这个角色,而变量的饰词则扮演的是ParamType的角色。
15+
16+
- 情况1: *ParamType*是个指针或者引用,但不是个万能引用
17+
- 情况2: *ParamType*是个万能引用
18+
- 情况3: *ParamType*既非指针也非引用
19+
20+
```cpp
21+
// 情况1 或 情况3
22+
auto x = 27; // 情况3(x既非指针也非引用)
23+
24+
const auto cx = x; // 情况3(cx既非指针也非引用)
25+
26+
const auto& rx = x; // 情况1(rx是个引用,但不是个万能引用)
27+
28+
// 情况2
29+
auto&& uref1 = x; // x的型别是int,且是左值
30+
// 所以uref1的型别是int&
31+
32+
auto&& uref2 = x; // cx的型别是const int,且是左值
33+
// 所以uref2的型别是const int&
34+
35+
auto&& uref3 = 27; // 27的型别是int,且是右值
36+
// 所以uref2的型别是int&&
37+
```
38+
39+
数组和函数的情况也适用于auto型别的推导
40+
41+
```cpp
42+
const char name[] = "xxxxxx" // name的型别是 const char [7]
43+
44+
auto arr1 = name; // arr1的型别是const char *
45+
46+
auto arr2 = name; // arr1的型别是const char (&)[7]
47+
48+
void someFunc(int, double) // someFunc是个函数,型别是void(int, double)
49+
50+
auto func1 = someFunc; // fun1的型别是 void(*)(int, double)
51+
52+
auto& func2 = someFunc; // func2的型别是void(&)(int, double)
53+
```
54+
55+
56+
57+
### auto和模板类型的不同
58+
59+
声明一个int并初始化,C++98中有两种语法
60+
61+
```cpp
62+
int x1 = 27;
63+
int x2(27);
64+
```
65+
66+
C++11为了支持统一初始化(uniform initialization),增加了下面的语法选项:
67+
68+
```cpp
69+
int x3 = {27};
70+
int x4{27};
71+
```
72+
73+
如果用auto:
74+
75+
```cpp
76+
auto x1 = 27; // 型别是int,值是27
77+
auto x2(27); // 同上
78+
auto x3 = {27}; // 型别是 std::initializer_list<int>,值是{27}
79+
auto x4{27}; // 同上
80+
```
81+
82+
后面两个语句,声明了这么一个变量,其型别类型为std::initializer_list<int>,且含有单个值为27的元素。
83+
84+
当用于auto声明变量的初始表达式是大括号起时,推导所得的型别就属于std::initizlizer_list。
85+
86+
对于大括号初始表达式的处理方式,是auto型别推导和模板型别推导的唯一不同之处。当采用auto声明的变量使用大括号初始化表达式进行初始化时,推导所得的型别是std::initializer_list的一个实例型别。
87+
88+
但是如果向对应模板传入一个同样的初始化表达式,型别类型推导将会失败,代码将不同通过编译:
89+
90+
```cpp
91+
auto x = {1,2,3}; // x的型别是 std::initializer_list<int>
92+
93+
template<typename T>
94+
void f(ParamType param);
95+
96+
f({1,2,3}) //错误,无法推导T的型别
97+
```
98+
99+
auto和模板类型推导的唯一区别是,auto会假定用大括号起的初始化表达式是一个std::initialize_list,但是模板型别推导不会。
100+
101+
102+
103+
C++14允许使用auto来说明函数返回值需要推导,而且C++14中的lamba表达式也会在形参中用到auto。然而,这些auto的用法是在使用模板型别推导,而非auto型别推导,所以,带有auto返回值的函数如果需要返回一个大括号起的初始化表达式,是通不过编译的。
104+
105+
```cpp
106+
auto createInitList()
107+
{
108+
return {1,2,3}; // 错误,无法为{1,2,3}完成型别类型推导
109+
}
110+
```
111+
112+
用auto来指定C++14中lambda式的形参型别时,也不能使用大括号起的初始化表达式
113+
114+
```cpp
115+
std::vector<int> v;
116+
117+
118+
auto resetV =
119+
[&v](const auto& newValue) { v = newValue; }; // C++14
120+
121+
122+
123+
resetV({ 1, 2, 3 }); // error! can't deduce type
124+
// for { 1, 2, 3 }
125+
```
126+
127+
128+
129+
### 总结
130+
131+
- 在一般情况下,auto型别推导和模板型别推导时一模一样的,但是auto型别推导会假定用大括号括起的初始化表达式代表一个std::intializer_list,但是模板型别推导不会
132+
- 在函数返回值或lambda式的形参中使用auto,意思是使用模板型别推导而非auto型别推导。
133+

0 commit comments

Comments
 (0)