@@ -14,15 +14,15 @@ order: 2
14
14
15
15
### nullptr
16
16
17
- ` nullptr ` 出现的目的是为了替代 ` NULL ` 。在某种意义上来说,传统 C++ 会把 ` NULL ` 、` 0 ` 视为同一种东西,这取决于编译器如何定义 NULL,有些编译器会将 NULL 定义为 ` ((void*)0) ` ,有些则会直接将其定义为 ` 0 ` 。
17
+ ` nullptr ` 出现的目的是为了替代 ` NULL ` 。在某种意义上来说,传统 C++ 会把 ` NULL ` 、` 0 ` 视为同一种东西,这取决于编译器如何定义 ` NULL ` ,有些编译器会将 ` NULL ` 定义为 ` ((void*)0) ` ,有些则会直接将其定义为 ` 0 ` 。
18
18
19
19
C++ ** 不允许** 直接将 ` void * ` 隐式转换到其他类型。但如果编译器尝试把 ` NULL ` 定义为 ` ((void*)0) ` ,那么在下面这句代码中:
20
20
21
21
``` cpp
22
22
char *ch = NULL ;
23
23
```
24
24
25
- 没有了 ` void * ` 隐式转换的 C++ 只好将` NULL ` 定义为 ` 0 ` 。而这依然会产生新的问题,将 ` NULL ` 定义成 0 将导致 ` C++ ` 中重载特性发生混乱。考虑下面这两个 ` foo ` 函数:
25
+ 没有了 ` void * ` 隐式转换的 C++ 只好将 ` NULL ` 定义为 ` 0 ` 。而这依然会产生新的问题,将 ` NULL ` 定义成 ` 0 ` 将导致 ` C++ ` 中重载特性发生混乱。考虑下面这两个 ` foo ` 函数:
26
26
27
27
``` cpp
28
28
void foo (char* );
@@ -31,9 +31,9 @@ void foo(int);
31
31
32
32
那么 `foo(NULL);` 这个语句将会去调用 `foo(int)`,从而导致代码违反直觉。
33
33
34
- 为了解决这个问题,C++11 引入了 `nullptr` 关键字,专门用来区分空指针、0 。而 `nullptr` 的类型为 `nullptr_t`,能够隐式的转换为任何指针或成员指针的类型,也能和他们进行相等或者不等的比较。
34
+ 为了解决这个问题,C++11 引入了 `nullptr` 关键字,专门用来区分空指针、`0` 。而 `nullptr` 的类型为 `nullptr_t`,能够隐式的转换为任何指针或成员指针的类型,也能和他们进行相等或者不等的比较。
35
35
36
- 你可以尝试使用 clang++ 编译下面的代码:
36
+ 你可以尝试使用 ` clang++` 编译下面的代码:
37
37
38
38
```cpp
39
39
#include <iostream>
@@ -73,11 +73,11 @@ foo(char*) is called
73
73
74
74
从输出中我们可以看出,` NULL ` 不同于 ` 0 ` 与 ` nullptr ` 。所以,请养成直接使用 ` nullptr ` 的习惯。
75
75
76
- 此外,在上面的代码中,我们使用了 ` decltype ` 和 ` std::is_same ` 这两个属于现代 C++ 的语法,简单来说,` decltype ` 用于类型推导,而 ` std::is_same ` 用于比较两个类型是否相等 ,我们会在后面 [ decltype] ( #decltype ) 一节中详细讨论。
76
+ 此外,在上面的代码中,我们使用了 ` decltype ` 和 ` std::is_same ` 这两个属于现代 C++ 的语法,简单来说,` decltype ` 用于类型推导,而 ` std::is_same ` 用于比较两个类型是否相同 ,我们会在后面 [ decltype] ( #decltype ) 一节中详细讨论。
77
77
78
78
### constexpr
79
79
80
- C++ 本身已经具备了常量表达式的概念,比如 1+2, 3* 4 这种表达式总是会产生相同的结果并且没有任何副作用。如果编译器能够在编译时就把这些表达式直接优化并植入到程序运行时,将能增加程序的性能。一个非常明显的例子就是在数组的定义阶段:
80
+ C++ 本身已经具备了常量表达式的概念,比如 ` 1+2 ` , ` 3*4 ` 这种表达式总是会产生相同的结果并且没有任何副作用。如果编译器能够在编译时就把这些表达式直接优化并植入到程序运行时,将能增加程序的性能。一个非常明显的例子就是在数组的定义阶段:
81
81
82
82
``` cpp
83
83
#include < iostream>
@@ -123,15 +123,15 @@ int main() {
123
123
124
124
C++11 提供了 `constexpr` 让用户显式的声明函数或对象构造函数在编译期会成为常量表达式,这个关键字明确的告诉编译器应该去验证 `len_foo` 在编译期就应该是一个常量表达式。
125
125
126
- 此外,`constexpr` 的函数可以使用递归 :
126
+ 此外,`constexpr` 修饰的函数可以使用递归 :
127
127
128
128
```cpp
129
129
constexpr int fibonacci(const int n) {
130
130
return n == 1 || n == 2 ? 1 : fibonacci(n-1)+fibonacci(n-2);
131
131
}
132
132
```
133
133
134
- 从 C++14 开始,constexpr 函数可以在内部使用局部变量、循环和分支等简单语句,例如下面的代码在 C++11 的标准下是不能够通过编译的:
134
+ 从 C++14 开始,` constexpr ` 函数可以在内部使用局部变量、循环和分支等简单语句,例如下面的代码在 C++11 的标准下是不能够通过编译的:
135
135
136
136
``` cpp
137
137
constexpr int fibonacci (const int n) {
@@ -181,7 +181,7 @@ int main() {
181
181
}
182
182
```
183
183
184
- 在上面的代码中,我们可以看到 ` itr ` 这一变量是定义在整个 ` main() ` 的作用域内的,这导致当我们需要再次遍历整个 ` std::vectors ` 时,需要重新命名另一个变量。C++17 消除了这一限制,使得我们可以在 if (或 switch)中完成这一操作:
184
+ 在上面的代码中,我们可以看到 ` itr ` 这一变量是定义在整个 ` main() ` 的作用域内的,这导致当我们需要再次遍历整个 ` std::vectors ` 时,需要重新命名另一个变量。C++17 消除了这一限制,使得我们可以在 ` if ` (或 ` switch ` )中完成这一操作:
185
185
186
186
``` cpp
187
187
// 将临时变量放到 if 语句内
@@ -295,7 +295,7 @@ int main() {
295
295
296
296
## 2.3 类型推导
297
297
298
- 在传统 C 和 C++中,参数的类型都必须明确定义,这其实对我们快速进行编码没有任何帮助,尤其是当我们面对一大堆复杂的模板类型时,必须明确的指出变量的类型才能进行后续的编码,这不仅拖慢我们的开发效率,也让代码变得又臭又长。
298
+ 在传统 C 和 C++ 中,参数的类型都必须明确定义,这其实对我们快速进行编码没有任何帮助,尤其是当我们面对一大堆复杂的模板类型时,必须明确的指出变量的类型才能进行后续的编码,这不仅拖慢我们的开发效率,也让代码变得又臭又长。
299
299
300
300
C++11 引入了 ` auto ` 和 ` decltype ` 这两个关键字实现了类型推导,让编译器来操心变量的类型。这使得 C++ 也具有了和其他现代编程语言一样,某种意义上提供了无需操心变量类型的使用习惯。
301
301
@@ -369,7 +369,7 @@ auto arr = new auto(10); // arr 被推导为 int *
369
369
370
370
### decltype
371
371
372
- `decltype` 关键字是为了解决 auto 关键字只能对变量进行类型推导的缺陷而出现的。它的用法和 `typeof` 很相似:
372
+ `decltype` 关键字是为了解决 ` auto` 关键字只能对变量进行类型推导的缺陷而出现的。它的用法和 `typeof` 很相似:
373
373
374
374
```cpp
375
375
decltype(表达式)
@@ -403,7 +403,7 @@ type z == type x
403
403
404
404
### 尾返回类型推导
405
405
406
- 你可能会思考,在介绍 ` auto ` 时,我们已经提过 ` auto ` 不能用于函数形参进行类型推导,那么 ` auto ` 能不能用于推导函数的返回类型呢?还是考虑一个加法函数的例子,在传统 C++ 中我们必须这么写:
406
+ 你可能会思考,在介绍 ` auto ` 时,我们已经提过 ` auto ` 不能用于函数形参进行类型推导,那么 ` auto ` 能不能用于推导函数的返回类型呢?还是考虑一个加法函数的例子,在传统 C++ 中我们必须这么写:
407
407
408
408
``` cpp
409
409
template <typename R, typename T, typename U>
@@ -423,7 +423,7 @@ R add(T x, U y) {
423
423
decltype(x+y) add(T x, U y)
424
424
```
425
425
426
- 但事实上这样的写法并不能通过编译。这是因为在编译器读到 decltype(x+y) 时,` x ` 和 ` y ` 尚未被定义。为了解决这个问题,C++11 还引入了一个叫做尾返回类型(trailing return type),利用 auto 关键字将返回类型后置:
426
+ 但事实上这样的写法并不能通过编译。这是因为在编译器读到 decltype(x+y) 时,` x ` 和 ` y ` 尚未被定义。为了解决这个问题,C++11 还引入了一个叫做尾返回类型(trailing return type),利用 ` auto ` 关键字将返回类型后置:
427
427
428
428
``` cpp
429
429
template <typename T, typename U>
@@ -575,7 +575,7 @@ extern template class std::vector<double>; // 不在该当前编译文件中实
575
575
std::vector<std::vector<int>> matrix;
576
576
```
577
577
578
- 这在传统C++ 编译器下是不能够被编译的,而 C++11 开始,连续的右尖括号将变得合法,并且能够顺利通过编译。甚至于像下面这种写法都能够通过编译:
578
+ 这在传统 C++ 编译器下是不能够被编译的,而 C++11 开始,连续的右尖括号将变得合法,并且能够顺利通过编译。甚至于像下面这种写法都能够通过编译:
579
579
580
580
``` cpp
581
581
template <bool T>
@@ -624,13 +624,17 @@ int main() {
624
624
我们可能定义了一个加法函数:
625
625
626
626
```cpp
627
+ // c++11 version
627
628
template<typename T, typename U>
628
629
auto add(T x, U y) -> decltype(x+y) {
629
630
return x+y;
630
631
}
632
+
633
+ // Call add function
634
+ auto ret = add<int, int>(1,3);
631
635
```
632
636
633
- 但在使用时发现,要使用 add,就必须每次都指定其模板参数的类型。
637
+ 但在使用时发现,要使用 ` add ` ,就必须每次都指定其模板参数的类型。
634
638
635
639
在 C++11 中提供了一种便利,可以指定模板的默认参数:
636
640
@@ -639,6 +643,9 @@ template<typename T = int, typename U = int>
639
643
auto add (T x, U y) -> decltype(x+y) {
640
644
return x+y;
641
645
}
646
+
647
+ // Call add function
648
+ auto ret = add(1,3);
642
649
```
643
650
644
651
### 变长参数模板
@@ -661,9 +668,9 @@ class Magic<int,
661
668
std::vector<int >>> darkMagic;
662
669
```
663
670
664
- 既然是任意形式,所以个数为 0 的模板参数也是可以的:`class Magic<> nothing;`。
671
+ 既然是任意形式,所以个数为 `0` 的模板参数也是可以的:`class Magic<> nothing;`。
665
672
666
- 如果不希望产生的模板参数个数为0 ,可以手动的定义至少一个模板参数:
673
+ 如果不希望产生的模板参数个数为 `0` ,可以手动的定义至少一个模板参数:
667
674
668
675
```cpp
669
676
template<typename Require, typename... Args> class Magic;
@@ -672,7 +679,7 @@ template<typename Require, typename... Args> class Magic;
672
679
变长参数模板也能被直接调整到到模板函数上。传统 C 中的 ` printf ` 函数,
673
680
虽然也能达成不定个数的形参的调用,但其并非类别安全。
674
681
而 C++11 除了能定义类别安全的变长参数函数外,
675
- 还可以使类似 printf 的函数能自然地处理非自带类别的对象。
682
+ 还可以使类似 ` printf ` 的函数能自然地处理非自带类别的对象。
676
683
除了在模板参数中能使用 ` ... ` 表示不定长模板参数外,
677
684
函数参数也使用同样的表示法代表不定长参数,
678
685
这也就为我们简单编写变长参数函数提供了便捷的手段,例如:
@@ -847,7 +854,7 @@ int main() {
847
854
848
855
### 继承构造
849
856
850
- 在传统 C++ 中,构造函数如果需要继承是需要将参数一一传递的,这将导致效率低下。C++11 利用关键字 using 引入了继承构造函数的概念:
857
+ 在传统 C++ 中,构造函数如果需要继承是需要将参数一一传递的,这将导致效率低下。C++11 利用关键字 ` using` 引入了继承构造函数的概念:
851
858
852
859
```cpp
853
860
#include <iostream>
@@ -875,7 +882,7 @@ int main() {
875
882
876
883
### 显式虚函数重载
877
884
878
- 在传统 C++中,经常容易发生意外重载虚函数的事情。例如:
885
+ 在传统 C++ 中,经常容易发生意外重载虚函数的事情。例如:
879
886
880
887
``` cpp
881
888
struct Base {
@@ -975,7 +982,7 @@ if (new_enum::value3 == new_enum::value4) {
975
982
}
976
983
```
977
984
978
- 在这个语法中,枚举类型后面使用了冒号及类型关键字来指定枚举中枚举值的类型,这使得我们能够为枚举赋值(未指定时将默认使用 int)。
985
+ 在这个语法中,枚举类型后面使用了冒号及类型关键字来指定枚举中枚举值的类型,这使得我们能够为枚举赋值(未指定时将默认使用 ` int ` )。
979
986
980
987
而我们希望获得枚举值的值时,将必须显式的进行类型转换,不过我们可以通过重载 ` << ` 这个算符来进行输出,可以收藏下面这个代码段:
981
988
@@ -998,8 +1005,8 @@ std::cout << new_enum::value3 << std::endl
998
1005
999
1006
本节介绍了现代 C++ 中对语言可用性的增强,其中笔者认为最为重要的几个特性是几乎所有人都需要了解并熟练使用的:
1000
1007
1001
- 1 . auto 类型推导
1002
- 2 . 范围 for 迭代
1008
+ 1 . ` auto ` 类型推导
1009
+ 2 . 范围 ` for ` 迭代
1003
1010
3 . 初始化列表
1004
1011
4 . 变参模板
1005
1012
0 commit comments