@@ -14,15 +14,15 @@ order: 2
1414
1515### nullptr
1616
17- ` nullptr ` 出现的目的是为了替代 ` NULL ` 。在某种意义上来说,传统 C++ 会把 ` NULL ` 、` 0 ` 视为同一种东西,这取决于编译器如何定义 NULL,有些编译器会将 NULL 定义为 ` ((void*)0) ` ,有些则会直接将其定义为 ` 0 ` 。
17+ ` nullptr ` 出现的目的是为了替代 ` NULL ` 。在某种意义上来说,传统 C++ 会把 ` NULL ` 、` 0 ` 视为同一种东西,这取决于编译器如何定义 ` NULL ` ,有些编译器会将 ` NULL ` 定义为 ` ((void*)0) ` ,有些则会直接将其定义为 ` 0 ` 。
1818
1919C++ ** 不允许** 直接将 ` void * ` 隐式转换到其他类型。但如果编译器尝试把 ` NULL ` 定义为 ` ((void*)0) ` ,那么在下面这句代码中:
2020
2121``` cpp
2222char *ch = NULL ;
2323```
2424
25- 没有了 ` void * ` 隐式转换的 C++ 只好将` NULL ` 定义为 ` 0 ` 。而这依然会产生新的问题,将 ` NULL ` 定义成 0 将导致 ` C++ ` 中重载特性发生混乱。考虑下面这两个 ` foo ` 函数:
25+ 没有了 ` void * ` 隐式转换的 C++ 只好将 ` NULL ` 定义为 ` 0 ` 。而这依然会产生新的问题,将 ` NULL ` 定义成 ` 0 ` 将导致 ` C++ ` 中重载特性发生混乱。考虑下面这两个 ` foo ` 函数:
2626
2727``` cpp
2828void foo (char* );
@@ -31,9 +31,9 @@ void foo(int);
3131
3232那么 `foo(NULL);` 这个语句将会去调用 `foo(int)`,从而导致代码违反直觉。
3333
34- 为了解决这个问题,C++11 引入了 `nullptr` 关键字,专门用来区分空指针、0 。而 `nullptr` 的类型为 `nullptr_t`,能够隐式的转换为任何指针或成员指针的类型,也能和他们进行相等或者不等的比较。
34+ 为了解决这个问题,C++11 引入了 `nullptr` 关键字,专门用来区分空指针、`0` 。而 `nullptr` 的类型为 `nullptr_t`,能够隐式的转换为任何指针或成员指针的类型,也能和他们进行相等或者不等的比较。
3535
36- 你可以尝试使用 clang++ 编译下面的代码:
36+ 你可以尝试使用 ` clang++` 编译下面的代码:
3737
3838```cpp
3939#include <iostream>
@@ -73,11 +73,11 @@ foo(char*) is called
7373
7474从输出中我们可以看出,` NULL ` 不同于 ` 0 ` 与 ` nullptr ` 。所以,请养成直接使用 ` nullptr ` 的习惯。
7575
76- 此外,在上面的代码中,我们使用了 ` decltype ` 和 ` std::is_same ` 这两个属于现代 C++ 的语法,简单来说,` decltype ` 用于类型推导,而 ` std::is_same ` 用于比较两个类型是否相等 ,我们会在后面 [ decltype] ( #decltype ) 一节中详细讨论。
76+ 此外,在上面的代码中,我们使用了 ` decltype ` 和 ` std::is_same ` 这两个属于现代 C++ 的语法,简单来说,` decltype ` 用于类型推导,而 ` std::is_same ` 用于比较两个类型是否相同 ,我们会在后面 [ decltype] ( #decltype ) 一节中详细讨论。
7777
7878### constexpr
7979
80- C++ 本身已经具备了常量表达式的概念,比如 1+2, 3* 4 这种表达式总是会产生相同的结果并且没有任何副作用。如果编译器能够在编译时就把这些表达式直接优化并植入到程序运行时,将能增加程序的性能。一个非常明显的例子就是在数组的定义阶段:
80+ C++ 本身已经具备了常量表达式的概念,比如 ` 1+2 ` , ` 3*4 ` 这种表达式总是会产生相同的结果并且没有任何副作用。如果编译器能够在编译时就把这些表达式直接优化并植入到程序运行时,将能增加程序的性能。一个非常明显的例子就是在数组的定义阶段:
8181
8282``` cpp
8383#include < iostream>
@@ -123,15 +123,15 @@ int main() {
123123
124124C++11 提供了 `constexpr` 让用户显式的声明函数或对象构造函数在编译期会成为常量表达式,这个关键字明确的告诉编译器应该去验证 `len_foo` 在编译期就应该是一个常量表达式。
125125
126- 此外,`constexpr` 的函数可以使用递归 :
126+ 此外,`constexpr` 修饰的函数可以使用递归 :
127127
128128```cpp
129129constexpr int fibonacci(const int n) {
130130 return n == 1 || n == 2 ? 1 : fibonacci(n-1)+fibonacci(n-2);
131131}
132132```
133133
134- 从 C++14 开始,constexpr 函数可以在内部使用局部变量、循环和分支等简单语句,例如下面的代码在 C++11 的标准下是不能够通过编译的:
134+ 从 C++14 开始,` constexpr ` 函数可以在内部使用局部变量、循环和分支等简单语句,例如下面的代码在 C++11 的标准下是不能够通过编译的:
135135
136136``` cpp
137137constexpr int fibonacci (const int n) {
@@ -181,7 +181,7 @@ int main() {
181181}
182182```
183183
184- 在上面的代码中,我们可以看到 ` itr ` 这一变量是定义在整个 ` main() ` 的作用域内的,这导致当我们需要再次遍历整个 ` std::vectors ` 时,需要重新命名另一个变量。C++17 消除了这一限制,使得我们可以在 if (或 switch)中完成这一操作:
184+ 在上面的代码中,我们可以看到 ` itr ` 这一变量是定义在整个 ` main() ` 的作用域内的,这导致当我们需要再次遍历整个 ` std::vectors ` 时,需要重新命名另一个变量。C++17 消除了这一限制,使得我们可以在 ` if ` (或 ` switch ` )中完成这一操作:
185185
186186``` cpp
187187// 将临时变量放到 if 语句内
@@ -295,7 +295,7 @@ int main() {
295295
296296## 2.3 类型推导
297297
298- 在传统 C 和 C++中,参数的类型都必须明确定义,这其实对我们快速进行编码没有任何帮助,尤其是当我们面对一大堆复杂的模板类型时,必须明确的指出变量的类型才能进行后续的编码,这不仅拖慢我们的开发效率,也让代码变得又臭又长。
298+ 在传统 C 和 C++ 中,参数的类型都必须明确定义,这其实对我们快速进行编码没有任何帮助,尤其是当我们面对一大堆复杂的模板类型时,必须明确的指出变量的类型才能进行后续的编码,这不仅拖慢我们的开发效率,也让代码变得又臭又长。
299299
300300C++11 引入了 ` auto ` 和 ` decltype ` 这两个关键字实现了类型推导,让编译器来操心变量的类型。这使得 C++ 也具有了和其他现代编程语言一样,某种意义上提供了无需操心变量类型的使用习惯。
301301
@@ -369,7 +369,7 @@ auto arr = new auto(10); // arr 被推导为 int *
369369
370370### decltype
371371
372- `decltype` 关键字是为了解决 auto 关键字只能对变量进行类型推导的缺陷而出现的。它的用法和 `typeof` 很相似:
372+ `decltype` 关键字是为了解决 ` auto` 关键字只能对变量进行类型推导的缺陷而出现的。它的用法和 `typeof` 很相似:
373373
374374```cpp
375375decltype(表达式)
@@ -403,7 +403,7 @@ type z == type x
403403
404404### 尾返回类型推导
405405
406- 你可能会思考,在介绍 ` auto ` 时,我们已经提过 ` auto ` 不能用于函数形参进行类型推导,那么 ` auto ` 能不能用于推导函数的返回类型呢?还是考虑一个加法函数的例子,在传统 C++ 中我们必须这么写:
406+ 你可能会思考,在介绍 ` auto ` 时,我们已经提过 ` auto ` 不能用于函数形参进行类型推导,那么 ` auto ` 能不能用于推导函数的返回类型呢?还是考虑一个加法函数的例子,在传统 C++ 中我们必须这么写:
407407
408408``` cpp
409409template <typename R, typename T, typename U>
@@ -423,7 +423,7 @@ R add(T x, U y) {
423423decltype(x+y) add(T x, U y)
424424```
425425
426- 但事实上这样的写法并不能通过编译。这是因为在编译器读到 decltype(x+y) 时,` x ` 和 ` y ` 尚未被定义。为了解决这个问题,C++11 还引入了一个叫做尾返回类型(trailing return type),利用 auto 关键字将返回类型后置:
426+ 但事实上这样的写法并不能通过编译。这是因为在编译器读到 decltype(x+y) 时,` x ` 和 ` y ` 尚未被定义。为了解决这个问题,C++11 还引入了一个叫做尾返回类型(trailing return type),利用 ` auto ` 关键字将返回类型后置:
427427
428428``` cpp
429429template <typename T, typename U>
@@ -575,7 +575,7 @@ extern template class std::vector<double>; // 不在该当前编译文件中实
575575std::vector<std::vector<int>> matrix;
576576```
577577
578- 这在传统C++ 编译器下是不能够被编译的,而 C++11 开始,连续的右尖括号将变得合法,并且能够顺利通过编译。甚至于像下面这种写法都能够通过编译:
578+ 这在传统 C++ 编译器下是不能够被编译的,而 C++11 开始,连续的右尖括号将变得合法,并且能够顺利通过编译。甚至于像下面这种写法都能够通过编译:
579579
580580``` cpp
581581template <bool T>
@@ -624,13 +624,17 @@ int main() {
624624我们可能定义了一个加法函数:
625625
626626```cpp
627+ // c++11 version
627628template<typename T, typename U>
628629auto add(T x, U y) -> decltype(x+y) {
629630 return x+y;
630631}
632+
633+ // Call add function
634+ auto ret = add<int, int>(1,3);
631635```
632636
633- 但在使用时发现,要使用 add,就必须每次都指定其模板参数的类型。
637+ 但在使用时发现,要使用 ` add ` ,就必须每次都指定其模板参数的类型。
634638
635639在 C++11 中提供了一种便利,可以指定模板的默认参数:
636640
@@ -639,6 +643,9 @@ template<typename T = int, typename U = int>
639643auto add (T x, U y) -> decltype(x+y) {
640644 return x+y;
641645}
646+
647+ // Call add function
648+ auto ret = add(1,3);
642649```
643650
644651### 变长参数模板
@@ -661,9 +668,9 @@ class Magic<int,
661668 std::vector<int >>> darkMagic;
662669```
663670
664- 既然是任意形式,所以个数为 0 的模板参数也是可以的:`class Magic<> nothing;`。
671+ 既然是任意形式,所以个数为 `0` 的模板参数也是可以的:`class Magic<> nothing;`。
665672
666- 如果不希望产生的模板参数个数为0 ,可以手动的定义至少一个模板参数:
673+ 如果不希望产生的模板参数个数为 `0` ,可以手动的定义至少一个模板参数:
667674
668675```cpp
669676template<typename Require, typename... Args> class Magic;
@@ -672,7 +679,7 @@ template<typename Require, typename... Args> class Magic;
672679变长参数模板也能被直接调整到到模板函数上。传统 C 中的 ` printf ` 函数,
673680虽然也能达成不定个数的形参的调用,但其并非类别安全。
674681而 C++11 除了能定义类别安全的变长参数函数外,
675- 还可以使类似 printf 的函数能自然地处理非自带类别的对象。
682+ 还可以使类似 ` printf ` 的函数能自然地处理非自带类别的对象。
676683除了在模板参数中能使用 ` ... ` 表示不定长模板参数外,
677684函数参数也使用同样的表示法代表不定长参数,
678685这也就为我们简单编写变长参数函数提供了便捷的手段,例如:
@@ -847,7 +854,7 @@ int main() {
847854
848855### 继承构造
849856
850- 在传统 C++ 中,构造函数如果需要继承是需要将参数一一传递的,这将导致效率低下。C++11 利用关键字 using 引入了继承构造函数的概念:
857+ 在传统 C++ 中,构造函数如果需要继承是需要将参数一一传递的,这将导致效率低下。C++11 利用关键字 ` using` 引入了继承构造函数的概念:
851858
852859```cpp
853860#include <iostream>
@@ -875,7 +882,7 @@ int main() {
875882
876883### 显式虚函数重载
877884
878- 在传统 C++中,经常容易发生意外重载虚函数的事情。例如:
885+ 在传统 C++ 中,经常容易发生意外重载虚函数的事情。例如:
879886
880887``` cpp
881888struct Base {
@@ -975,7 +982,7 @@ if (new_enum::value3 == new_enum::value4) {
975982}
976983```
977984
978- 在这个语法中,枚举类型后面使用了冒号及类型关键字来指定枚举中枚举值的类型,这使得我们能够为枚举赋值(未指定时将默认使用 int)。
985+ 在这个语法中,枚举类型后面使用了冒号及类型关键字来指定枚举中枚举值的类型,这使得我们能够为枚举赋值(未指定时将默认使用 ` int ` )。
979986
980987而我们希望获得枚举值的值时,将必须显式的进行类型转换,不过我们可以通过重载 ` << ` 这个算符来进行输出,可以收藏下面这个代码段:
981988
@@ -998,8 +1005,8 @@ std::cout << new_enum::value3 << std::endl
9981005
9991006本节介绍了现代 C++ 中对语言可用性的增强,其中笔者认为最为重要的几个特性是几乎所有人都需要了解并熟练使用的:
10001007
1001- 1 . auto 类型推导
1002- 2 . 范围 for 迭代
1008+ 1 . ` auto ` 类型推导
1009+ 2 . 范围 ` for ` 迭代
100310103 . 初始化列表
100410114 . 变参模板
10051012
0 commit comments