diff --git a/Chapter-1/README.md b/Chapter-1 Getting Started/README.md similarity index 100% rename from Chapter-1/README.md rename to Chapter-1 Getting Started/README.md diff --git a/Chapter-10/Image/10-1.png b/Chapter-10 Generic Algorithms/Image/10-1.png similarity index 100% rename from Chapter-10/Image/10-1.png rename to Chapter-10 Generic Algorithms/Image/10-1.png diff --git a/Chapter-10/Image/10-10.png b/Chapter-10 Generic Algorithms/Image/10-10.png similarity index 100% rename from Chapter-10/Image/10-10.png rename to Chapter-10 Generic Algorithms/Image/10-10.png diff --git a/Chapter-10/Image/10-2.png b/Chapter-10 Generic Algorithms/Image/10-2.png similarity index 100% rename from Chapter-10/Image/10-2.png rename to Chapter-10 Generic Algorithms/Image/10-2.png diff --git a/Chapter-10/Image/10-3.png b/Chapter-10 Generic Algorithms/Image/10-3.png similarity index 100% rename from Chapter-10/Image/10-3.png rename to Chapter-10 Generic Algorithms/Image/10-3.png diff --git a/Chapter-10/Image/10-4.png b/Chapter-10 Generic Algorithms/Image/10-4.png similarity index 100% rename from Chapter-10/Image/10-4.png rename to Chapter-10 Generic Algorithms/Image/10-4.png diff --git a/Chapter-10/Image/10-5.png b/Chapter-10 Generic Algorithms/Image/10-5.png similarity index 100% rename from Chapter-10/Image/10-5.png rename to Chapter-10 Generic Algorithms/Image/10-5.png diff --git a/Chapter-10/Image/10-6.png b/Chapter-10 Generic Algorithms/Image/10-6.png similarity index 100% rename from Chapter-10/Image/10-6.png rename to Chapter-10 Generic Algorithms/Image/10-6.png diff --git a/Chapter-10/Image/10-7.png b/Chapter-10 Generic Algorithms/Image/10-7.png similarity index 100% rename from Chapter-10/Image/10-7.png rename to Chapter-10 Generic Algorithms/Image/10-7.png diff --git a/Chapter-10/Image/10-8.png b/Chapter-10 Generic Algorithms/Image/10-8.png similarity index 100% rename from Chapter-10/Image/10-8.png rename to Chapter-10 Generic Algorithms/Image/10-8.png diff --git a/Chapter-10/Image/10-9.png b/Chapter-10 Generic Algorithms/Image/10-9.png similarity index 100% rename from Chapter-10/Image/10-9.png rename to Chapter-10 Generic Algorithms/Image/10-9.png diff --git a/Chapter-10/README.md b/Chapter-10 Generic Algorithms/README.md similarity index 80% rename from Chapter-10/README.md rename to Chapter-10 Generic Algorithms/README.md index f23045f..6eef8a1 100644 --- a/Chapter-10/README.md +++ b/Chapter-10 Generic Algorithms/README.md @@ -33,9 +33,9 @@ string sum = accumulate(v.cbegin(), v.cend(), string("")); string sum = accumulate(v.cbegin(), v.cend(), ""); ``` -建议在只读算法中使用cbegin和cend函数。 +建议在只读算法中使用`cbegin`和`cend`函数。 -`equal`函数用于确定两个序列是否保存相同的值。它接受三个迭代器参数,前两个参数指定第一个序列范围,第三个参数指定第二个序列的首元素。equal函数假定第二个序列至少与第一个序列一样长。 +`equal`函数用于确定两个序列是否保存相同的值。它接受三个迭代器参数,前两个参数指定第一个序列范围,第三个参数指定第二个序列的首元素。`equal`函数假定第二个序列至少与第一个序列一样长。 ```c++ // roster2 should have at least as many elements as roster1 @@ -64,7 +64,7 @@ fill_n(vec.begin(), vec.size(), 0); 插入迭代器(insert iterator)是一种向容器内添加元素的迭代器。通过插入迭代器赋值时,一个与赋值号右侧值相等的元素会被添加到容器中。 -`back_inserter`函数(定义在头文件*iterator*中)接受一个指向容器的引用,返回与该容器绑定的插入迭代器。通过此迭代器赋值时,赋值运算符会调用push_back将一个具有给定值的元素添加到容器中。 +`back_inserter`函数(定义在头文件*iterator*中)接受一个指向容器的引用,返回与该容器绑定的插入迭代器。通过此迭代器赋值时,赋值运算符会调用`push_back`将一个具有给定值的元素添加到容器中。 ```c++ vector vec; // empty vector @@ -90,7 +90,7 @@ auto ret = copy(begin(a1), end(a1), a2); // copy a1 into a2 replace(ilst.begin(), ilst.end(), 0, 42); ``` -相对于replace,`replace_copy`函数可以保留原序列不变。它接受第三个迭代器参数,指定调整后序列的保存位置。 +相对于`replace`,`replace_copy`函数可以保留原序列不变。它接受第三个迭代器参数,指定调整后序列的保存位置。 ```c++ // use back_inserter to grow destination as needed @@ -147,22 +147,22 @@ sort(words.begin(), words.end(), isShorter); 对于一个对象或表达式,如果可以对其使用调用运算符`()`,则称它为可调用对象(callable object)。可以向算法传递任何类别的可调用对象。 -一个lambda表达式表示一个可调用的代码单元,类似未命名的内联函数,但可以定义在函数内部。其形式如下: +一个`lambda`表达式表示一个可调用的代码单元,类似未命名的内联函数,但可以定义在函数内部。其形式如下: ```c++ [capture list] (parameter list) -> return type { function body } ``` -其中,*capture list*(捕获列表)是一个由lambda所在函数定义的局部变量的列表(通常为空)。*return type*、*parameter list*和*function body*与普通函数一样,分别表示返回类型、参数列表和函数体。但与普通函数不同,lambda必须使用尾置返回类型,且不能有默认实参。 +其中,*capture list*(捕获列表)是一个由`lambda`所在函数定义的局部变量的列表(通常为空)。*return type*、*parameter list*和*function body*与普通函数一样,分别表示返回类型、参数列表和函数体。但与普通函数不同,`lambda`必须使用尾置返回类型,且不能有默认实参。 -定义lambda时可以省略参数列表和返回类型,但必须包含捕获列表和函数体。省略参数列表等价于指定空参数列表。省略返回类型时,若函数体只是一个return语句,则返回类型由返回表达式的类型推断而来。否则返回类型为void。 +定义`lambda`时可以省略参数列表和返回类型,但必须包含捕获列表和函数体。省略参数列表等价于指定空参数列表。省略返回类型时,若函数体只是一个`return`语句,则返回类型由返回表达式的类型推断而来。否则返回类型为`void`。 ```c++ auto f = [] { return 42; }; cout << f() << endl; // prints 42 ``` -lambda可以使用其所在函数的局部变量,但必须先将其包含在捕获列表中。捕获列表只能用于局部非static变量,lambda可以直接使用局部static变量和其所在函数之外声明的名字。 +`lambda`可以使用其所在函数的局部变量,但必须先将其包含在捕获列表中。捕获列表只能用于局部非`static`变量,`lambda`可以直接使用局部`static`变量和其所在函数之外声明的名字。 ```c++ // get an iterator to the first element whose size() is >= sz @@ -180,7 +180,7 @@ for_each(wc, words.end(), ### lambda捕获和返回(Lambda Captures and Returns) -被lambda捕获的变量的值是在lambda创建时拷贝,而不是调用时拷贝。在lambda创建后修改局部变量不会影响lambda内对应的值。 +被`lambda`捕获的变量的值是在`lambda`创建时拷贝,而不是调用时拷贝。在`lambda`创建后修改局部变量不会影响`lambda`内对应的值。 ```c++ size_t v1 = 42; // local variable @@ -190,7 +190,7 @@ v1 = 0; auto j = f(); // j is 42; f stored a copy of v1 when we created it ``` -lambda可以以引用方式捕获变量,但必须保证lambda执行时变量存在。 +`lambda`可以以引用方式捕获变量,但必须保证`lambda`执行时变量存在。 ```c++ size_t v1 = 42; // local variable @@ -200,7 +200,7 @@ v1 = 0; auto j = f2(); // j is 0; f2 refers to v1; it doesn't store it ``` -可以让编译器根据lambda代码隐式捕获函数变量,方法是在捕获列表中写一个`&`或`=`符号。`&`为引用捕获,`=`为值捕获。 +可以让编译器根据`lambda`代码隐式捕获函数变量,方法是在捕获列表中写一个`&`或`=`符号。`&`为引用捕获,`=`为值捕获。 可以混合使用显式捕获和隐式捕获。混合使用时,捕获列表中的第一个元素必须是`&`或`=`符号,用于指定默认捕获方式。显式捕获的变量必须使用与隐式捕获不同的方式。 @@ -213,11 +213,11 @@ for_each(words.begin(), words.end(), [=, &os] (const string &s) { os << s << c; }); ``` -lambda捕获列表形式: +`lambda`捕获列表形式: ![10-2](Image/10-2.png) -默认情况下,对于值方式捕获的变量,lambda不能修改其值。如果希望修改,就必须在参数列表后添加关键字`mutable`。 +默认情况下,对于值方式捕获的变量,`lambda`不能修改其值。如果希望修改,就必须在参数列表后添加关键字`mutable`。 ```c++ size_t v1 = 42; // local variable @@ -227,7 +227,7 @@ v1 = 0; auto j = f(); // j is 43 ``` -对于引用方式捕获的变量,lambda是否可以修改依赖于此引用指向的是否是const类型。 +对于引用方式捕获的变量,`lambda`是否可以修改依赖于此引用指向的是否是`const`类型。 `transform`函数接受三个迭代器参数和一个可调用对象。前两个迭代器参数指定输入序列,第三个迭代器参数表示目的位置。它对输入序列中的每个元素调用可调用对象,并将结果写入目的位置。 @@ -236,7 +236,7 @@ transform(vi.begin(), vi.end(), vi.begin(), [](int i) -> int { if (i < 0) return -i; else return i; }); ``` -为lambda定义返回类型时,必须使用尾置返回类型。 +为`lambda`定义返回类型时,必须使用尾置返回类型。 ### 参数绑定(Binding Arguments) @@ -260,7 +260,7 @@ string s = "hello"; bool b1 = check6(s); // check6(s) calls check_size(s, 6) ``` -bind函数可以调整给定可调用对象中的参数顺序。 +`bind`函数可以调整给定可调用对象中的参数顺序。 ```c++ // sort on word length, shortest to longest @@ -269,9 +269,9 @@ sort(words.begin(), words.end(), isShorter); sort(words.begin(), words.end(), bind(isShorter, _2, _1)); ``` -默认情况下,bind函数的非占位符参数被拷贝到bind返回的可调用对象中。但有些类型不支持拷贝操作。 +默认情况下,`bind`函数的非占位符参数被拷贝到`bind`返回的可调用对象中。但有些类型不支持拷贝操作。 -如果希望传递给bind一个对象而又不拷贝它,则必须使用标准库的`ref`函数。ref函数返回一个对象,包含给定的引用,此对象是可以拷贝的。`cref`函数生成保存const引用的类。 +如果希望传递给`bind`一个对象而又不拷贝它,则必须使用标准库的`ref`函数。`ref`函数返回一个对象,包含给定的引用,此对象是可以拷贝的。`cref`函数生成保存`const`引用的类。 ```c++ ostream &print(ostream &os, const string &s, char c); @@ -284,7 +284,7 @@ for_each(words.begin(), words.end(), bind(print, ref(os), _1, ' ')); - 插入迭代器(insert iterator):该类型迭代器被绑定到容器对象上,可用来向容器中插入元素。 - 流迭代器(stream iterator):该类型迭代器被绑定到输入或输出流上,可用来遍历所关联的IO流。 -- 反向迭代器(reverse iterator):该类型迭代器向后而不是向前移动。除了forward_list之外的标准库容器都有反向迭代器。 +- 反向迭代器(reverse iterator):该类型迭代器向后而不是向前移动。除了`forward_list`之外的标准库容器都有反向迭代器。 - 移动迭代器(move iterator):该类型迭代器用来移动容器元素。 ### 插入迭代器(Insert Iterators) @@ -297,9 +297,9 @@ for_each(words.begin(), words.end(), bind(print, ref(os), _1, ' ')); 插入器有三种类型,区别在于元素插入的位置: -- `back_inserter`:创建一个调用push_back操作的迭代器。 -- `front_inserter`:创建一个调用push_front操作的迭代器。 -- `inserter`:创建一个调用insert操作的迭代器。此函数接受第二个参数,该参数必须是一个指向给定容器的迭代器,元素会被插入到该参数指向的元素之前。 +- `back_inserter`:创建一个调用`push_back`操作的迭代器。 +- `front_inserter`:创建一个调用`push_front`操作的迭代器。 +- `inserter`:创建一个调用`insert`操作的迭代器。此函数接受第二个参数,该参数必须是一个指向给定容器的迭代器,元素会被插入到该参数指向的元素之前。 ```c++ list 1st = { 1,2,3,4 }; @@ -314,7 +314,7 @@ copy(1st.cbegin(), lst.cend(), inserter(lst3, lst3.begin())); `istream_iterator`从输入流读取数据,`ostream_iterator`向输出流写入数据。这些迭代器将流当作特定类型的元素序列处理。 -创建流迭代器时,必须指定迭代器读写的对象类型。istream_iterator使用~来读取流,因此istream_iterator要读取的类型必须定义了`>>`运算符。创建istream_iterator时,可以将其绑定到一个流。如果默认初始化,则创建的是尾后迭代器。 +创建流迭代器时,必须指定迭代器读写的对象类型。`istream_iterator`使用`>>`来读取流,因此`istream_iterator`要读取的类型必须定义了`>>`运算符。创建`istream_iterator`时,可以将其绑定到一个流。如果默认初始化,则创建的是尾后迭代器。 ```c++ istream_iterator int_it(cin); // reads ints from cin @@ -341,19 +341,19 @@ istream_iterator in_iter(cin), eof; // read ints from cin vector vec(in_iter, eof); // construct vec from an iterator range ``` -istream_iterator操作: +`istream_iterator`操作: ![10-4](Image/10-4.png) -将istream_iterator绑定到一个流时,标准库并不保证迭代器立即从流读取数据。但可以保证在第一次解引用迭代器之前,从流中读取数据的操作已经完成了。 +将`istream_iterator`绑定到一个流时,标准库并不保证迭代器立即从流读取数据。但可以保证在第一次解引用迭代器之前,从流中读取数据的操作已经完成了。 -定义ostream_iterator对象时,必须将其绑定到一个指定的流。不允许定义空的或者表示尾后位置的ostream_iterator。 +定义`ostream_iterator`对象时,必须将其绑定到一个指定的流。不允许定义空的或者表示尾后位置的`ostream_iterator`。 -ostream_iterator操作: +`ostream_iterator`操作: ![10-5](Image/10-5.png) -`*`和`++`运算符实际上不会对ostream_iterator对象做任何操作。但是建议代码写法与其他迭代器保持一致。 +`*`和`++`运算符实际上不会对`ostream_iterator`对象做任何操作。但是建议代码写法与其他迭代器保持一致。 ```c++ ostream_iterator out_iter(cout, " "); @@ -362,7 +362,7 @@ for (auto e : vec) cout << endl; ``` -可以为任何定义了`<<`运算符的类型创建istream_iterator对象,为定义了`>>`运算符的类型创建ostream_iterator对象。 +可以为任何定义了`<<`运算符的类型创建`istream_iterator`对象,为定义了`>>`运算符的类型创建`ostream_iterator`对象。 ### 反向迭代器(Reverse Iterators) @@ -376,7 +376,7 @@ sort(vec.rbegin(), vec.rend()); ![10-6](Image/10-6.png) -不能从forward_list或流迭代器创建反向迭代器。 +不能从`forward_list`或流迭代器创建反向迭代器。 调用反向迭代器的`base`函数可以获得其对应的普通迭代器。 @@ -419,7 +419,7 @@ C++标准指定了泛型和数值算法的每个迭代器参数的最小类别 - 前向迭代器(forward iterator):可以读写序列中的元素。只能在序列中沿一个方向移动。支持所有输入和输出迭代器的操作,而且可以多次读写同一个元素。因此可以使用前向迭代器对序列进行多遍扫描。 -- 双向迭代器(bidirectional iterator):可以正向/反向读写序列中的元素。除了支持所有前向迭代器的操作之外,还支持前置和后置递减运算符`--`。除forward_list之外的其他标准库容器都提供符合双向迭代器要求的迭代器。 +- 双向迭代器(bidirectional iterator):可以正向/反向读写序列中的元素。除了支持所有前向迭代器的操作之外,还支持前置和后置递减运算符`--`。除`forward_list`之外的其他标准库容器都提供符合双向迭代器要求的迭代器。 - 随机访问迭代器(random-access iterator):可以在常量时间内访问序列中的任何元素。除了支持所有双向迭代器的操作之外,还必须支持以下操作: @@ -465,13 +465,13 @@ reverse_copy(beg, end, dest); // copy elements in reverse order into dest ## 特定容器算法(Container-Specific Algorithms) -对于list和forward_list类型,应该优先使用成员函数版本的算法,而非通用算法。 +对于`list`和`forward_list`类型,应该优先使用成员函数版本的算法,而非通用算法。 -list和forward_list成员函数版本的算法: +`list`和`forward_list`成员函数版本的算法: ![10-9](Image/10-9.png) -list和forward_list的`splice`函数可以进行容器合并,其参数如下: +`list`和`forward_list`的`splice`函数可以进行容器合并,其参数如下: ![10-10](Image/10-10.png) diff --git a/Chapter-11/Image/11-1.png b/Chapter-11 Associative Containers/Image/11-1.png similarity index 100% rename from Chapter-11/Image/11-1.png rename to Chapter-11 Associative Containers/Image/11-1.png diff --git a/Chapter-11/Image/11-2.png b/Chapter-11 Associative Containers/Image/11-2.png similarity index 100% rename from Chapter-11/Image/11-2.png rename to Chapter-11 Associative Containers/Image/11-2.png diff --git a/Chapter-11/Image/11-3.png b/Chapter-11 Associative Containers/Image/11-3.png similarity index 100% rename from Chapter-11/Image/11-3.png rename to Chapter-11 Associative Containers/Image/11-3.png diff --git a/Chapter-11/Image/11-4.png b/Chapter-11 Associative Containers/Image/11-4.png similarity index 100% rename from Chapter-11/Image/11-4.png rename to Chapter-11 Associative Containers/Image/11-4.png diff --git a/Chapter-11/Image/11-5.png b/Chapter-11 Associative Containers/Image/11-5.png similarity index 100% rename from Chapter-11/Image/11-5.png rename to Chapter-11 Associative Containers/Image/11-5.png diff --git a/Chapter-11/Image/11-6.png b/Chapter-11 Associative Containers/Image/11-6.png similarity index 100% rename from Chapter-11/Image/11-6.png rename to Chapter-11 Associative Containers/Image/11-6.png diff --git a/Chapter-11/Image/11-7.png b/Chapter-11 Associative Containers/Image/11-7.png similarity index 100% rename from Chapter-11/Image/11-7.png rename to Chapter-11 Associative Containers/Image/11-7.png diff --git a/Chapter-11/Image/11-8.png b/Chapter-11 Associative Containers/Image/11-8.png similarity index 100% rename from Chapter-11/Image/11-8.png rename to Chapter-11 Associative Containers/Image/11-8.png diff --git a/Chapter-11/README.md b/Chapter-11 Associative Containers/README.md similarity index 58% rename from Chapter-11/README.md rename to Chapter-11 Associative Containers/README.md index 38ff984..3023474 100644 --- a/Chapter-11/README.md +++ b/Chapter-11 Associative Containers/README.md @@ -2,12 +2,12 @@ 关联容器支持高效的关键字查找和访问操作。2个主要的关联容器(associative-container)类型是`map`和`set`。 -- map中的元素是一些键值对(key-value):关键字起索引作用,值表示与索引相关联的数据。 -- set中每个元素只包含一个关键字,支持高效的关键字查询操作:检查一个给定关键字是否在set中。 +- `map`中的元素是一些键值对(key-value):关键字起索引作用,值表示与索引相关联的数据。 +- `set`中每个元素只包含一个关键字,支持高效的关键字查询操作:检查一个给定关键字是否在`set`中。 标准库提供了8个关联容器,它们之间的不同体现在三个方面: -- 是map还是set类型。 +- 是`map`还是`set`类型。 - 是否允许保存重复的关键字。 - 是否按顺序保存元素。 @@ -15,13 +15,13 @@ ![11-1](Image/11-1.png) -map和`multimap`类型定义在头文件*map*中;set和`multiset`类型定义在头文件*set*中;无序容器定义在头文件*unordered_map*和*unordered_set*中。 +`map`和`multimap`类型定义在头文件*map*中;`set`和`multiset`类型定义在头文件*set*中;无序容器定义在头文件*unordered_map*和*unordered_set*中。 ## 使用关联容器(Using an Associative Container) -map类型通常被称为关联数组(associative array)。 +`map`类型通常被称为关联数组(associative array)。 -从map中提取一个元素时,会得到一个`pair`类型的对象。pair是一个模板类型,保存两个名为`first`和`second`的公有数据成员。map所使用的pair用first成员保存关键字,用second成员保存对应的值。 +从`map`中提取一个元素时,会得到一个`pair`类型的对象。`pair`是一个模板类型,保存两个名为`first`和`second`的公有数据成员。`map`所使用的`pair`用`first`成员保存关键字,用`second`成员保存对应的值。 ```c++ // count the number of times each word occurs in the input @@ -35,15 +35,15 @@ for (const auto &w : word_count) // for each element in the map << ((w.second > 1) ? " times" : " time") << endl; ``` -set类型的`find`成员返回一个迭代器。如果给定关键字在set中,则迭代器指向该关键字,否则返回的是尾后迭代器。 +`set`类型的`find`成员返回一个迭代器。如果给定关键字在`set`中,则迭代器指向该关键字,否则返回的是尾后迭代器。 ## 关联容器概述(Overview of the Associative Containers) ### 定义关联容器(Defining an Associative Container) -定义map时,必须指定关键字类型和值类型;定义set时,只需指定关键字类型。 +定义`map`时,必须指定关键字类型和值类型;定义`set`时,只需指定关键字类型。 -初始化map时,提供的每个键值对用花括号`{}`包围。 +初始化`map`时,提供的每个键值对用花括号`{}`包围。 ```C++ map word_count; // empty @@ -58,11 +58,11 @@ map authors = }; ``` -map和set中的关键字必须唯一,multimap和multiset没有此限制。 +`map`和`set`中的关键字必须唯一,`multimap`和`multiset`没有此限制。 ### 关键字类型的要求(Requirements on Key Type) -对于有序容器——map、multimap、set和multiset,关键字类型必须定义元素比较的方法。默认情况下,标准库使用关键字类型的`<`运算符来进行比较操作。 +对于有序容器——`map`、`multimap`、`set`和`multiset`,关键字类型必须定义元素比较的方法。默认情况下,标准库使用关键字类型的`<`运算符来进行比较操作。 用来组织容器元素的操作的类型也是该容器类型的一部分。如果需要使用自定义的比较操作,则必须在定义关联容器类型时提供此操作的类型。操作类型在尖括号中紧跟着元素类型给出。 @@ -79,7 +79,7 @@ multiset bookstore(compareIsbn); ### pair类型(The pair Type) -pair定义在头文件*utility*中。一个pair可以保存两个数据成员,分别命名为first和second。 +`pair`定义在头文件*utility*中。一个`pair`可以保存两个数据成员,分别命名为`first`和`second`。 ```c++ pair anon; // holds two strings @@ -87,13 +87,13 @@ pair word_count; // holds a string and an size_t pair> line; // holds string and vector ``` -pair的默认构造函数对数据成员进行值初始化。 +`pair`的默认构造函数对数据成员进行值初始化。 -pair支持的操作: +`pair`支持的操作: ![11-2](Image/11-2.png) -在C++11中,如果函数需要返回pair,可以对返回值进行列表初始化。早期C++版本中必须显式构造返回值。 +在C++11中,如果函数需要返回`pair`,可以对返回值进行列表初始化。早期C++版本中必须显式构造返回值。 ```c++ pair process(vector &v) @@ -114,7 +114,7 @@ pair process(vector &v) ![11-3](Image/11-3.png) -对于set类型,`key_type`和`value_type`是一样的。set中保存的值就是关键字。对于map类型,元素是关键字-值对。即每个元素是一个pair对象,包含一个关键字和一个关联的值。由于元素关键字不能改变,因此pair的关键字部分是const的。另外,只有map类型(unordered_map、unordered_multimap、multimap、map)才定义了`mapped_type`。 +对于`set`类型,`key_type`和`value_type`是一样的。`set`中保存的值就是关键字。对于`map`类型,元素是关键字-值对。即每个元素是一个`pair`对象,包含一个关键字和一个关联的值。由于元素关键字不能改变,因此`pair`的关键字部分是`const`的。另外,只有`map`类型(`unordered_map`、`unordered_multimap`、`multimap`、`map`)才定义了`mapped_type`。 ```c++ set::value_type v1; // v1 is a string @@ -126,7 +126,7 @@ map::mapped_type v5; // v5 is an int ### 关联容器迭代器(Associative Container Iterators) -解引用关联容器迭代器时,会得到一个类型为容器的value_type的引用。对map而言,value_type是pair类型,其first成员保存const的关键字,second成员保存值。 +解引用关联容器迭代器时,会得到一个类型为容器的`value_type`的引用。对`map`而言,`value_type`是`pair`类型,其`first`成员保存`const`的关键字,`second`成员保存值。 ```c++ // get an iterator to an element in word_count @@ -138,7 +138,7 @@ map_it->first = "new key"; // error: key is const ++map_it->second; // ok: we can change the value through an iterator ``` -虽然set同时定义了iterator和const_iterator类型,但两种迭代器都只允许只读访问set中的元素。类似map,set中的关键字也是const的。 +虽然`set`同时定义了`iterator`和`const_iterator`类型,但两种迭代器都只允许只读访问`set`中的元素。类似`map`,`set`中的关键字也是`const`的。 ```c++ set iset = {0,1,2,3,4,5,6,7,8,9}; @@ -150,15 +150,15 @@ if (set_it != iset.end()) } ``` -map和set都支持begin和end操作。使用迭代器遍历map、multimap、set或multiset时,迭代器按关键字升序遍历元素。 +`map`和`set`都支持`begin`和`end`操作。使用迭代器遍历`map`、`multimap`、`set`或`multiset`时,迭代器按关键字升序遍历元素。 通常不对关联容器使用泛型算法。 ### 添加元素(Adding Elements) -使用`insert`成员可以向关联容器中添加元素。向map和set中添加已存在的元素对容器没有影响。 +使用`insert`成员可以向关联容器中添加元素。向`map`和`set`中添加已存在的元素对容器没有影响。 -通常情况下,对于想要添加到map中的数据,并没有现成的pair对象。可以直接在insert的参数列表中创建pair。 +通常情况下,对于想要添加到`map`中的数据,并没有现成的`pair`对象。可以直接在`insert`的参数列表中创建`pair`。 ```c++ // four ways to add word to word_count @@ -168,14 +168,14 @@ word_count.insert(pair(word, 1)); word_count.insert(map::value_type(word, 1)); ``` -关联容器的insert操作: +关联容器的`insert`操作: ![11-4](Image/11-4.png) -insert或`emplace`的返回值依赖于容器类型和参数: +`insert`或`emplace`的返回值依赖于容器类型和参数: -- 对于不包含重复关键字的容器,添加单一元素的insert和emplace版本返回一个pair,表示操作是否成功。pair的first成员是一个迭代器,指向具有给定关键字的元素;second成员是一个bool值。如果关键字已在容器中,则insert直接返回,bool值为false。如果关键字不存在,元素会被添加至容器中,bool值为true。 -- 对于允许包含重复关键字的容器,添加单一元素的insert和emplace版本返回指向新元素的迭代器。 +- 对于不包含重复关键字的容器,添加单一元素的`insert`和`emplace`版本返回一个`pair`,表示操作是否成功。`pair`的`first`成员是一个迭代器,指向具有给定关键字的元素;`second`成员是一个`bool`值。如果关键字已在容器中,则`insert`直接返回,`bool`值为`false`。如果关键字不存在,元素会被添加至容器中,`bool`值为`true`。 +- 对于允许包含重复关键字的容器,添加单一元素的`insert`和`emplace`版本返回指向新元素的迭代器。 ### 删除元素(Erasing Elements) @@ -183,19 +183,19 @@ insert或`emplace`的返回值依赖于容器类型和参数: ![11-5](Image/11-5.png) -与顺序容器不同,关联容器提供了一个额外的`erase`操作。它接受一个key_type参数,删除所有匹配给定关键字的元素(如果存在),返回实际删除的元素数量。对于不包含重复关键字的容器,erase的返回值总是1或0。若返回值为0,则表示想要删除的元素并不在容器中。 +与顺序容器不同,关联容器提供了一个额外的`erase`操作。它接受一个`key_type`参数,删除所有匹配给定关键字的元素(如果存在),返回实际删除的元素数量。对于不包含重复关键字的容器,`erase`的返回值总是1或0。若返回值为0,则表示想要删除的元素并不在容器中。 ### map的下标操作(Subscripting a map) -map下标运算符接受一个关键字,获取与此关键字相关联的值。如果关键字不在容器中,下标运算符会向容器中添加该关键字,并值初始化关联值。 +`map`下标运算符接受一个关键字,获取与此关键字相关联的值。如果关键字不在容器中,下标运算符会向容器中添加该关键字,并值初始化关联值。 -由于下标运算符可能向容器中添加元素,所以只能对非const的map使用下标操作。 +由于下标运算符可能向容器中添加元素,所以只能对非`const`的`map`使用下标操作。 -map和unordered_map的下标操作: +`map`和`unordered_map`的下标操作: ![11-6](Image/11-6.png) -对map进行下标操作时,返回的是mapped_type类型的对象;解引用map迭代器时,返回的是value_type类型的对象。 +对`map`进行下标操作时,返回的是`mapped_type`类型的对象;解引用`map`迭代器时,返回的是`value_type`类型的对象。 ### 访问元素(Accessing Elements) @@ -203,7 +203,7 @@ map和unordered_map的下标操作: ![11-7](Image/11-7.png) -如果multimap或multiset中有多个元素具有相同关键字,则这些元素在容器中会相邻存储。 +如果`multimap`或`multiset`中有多个元素具有相同关键字,则这些元素在容器中会相邻存储。 ```c++ multimap authors; @@ -224,7 +224,7 @@ while(entries) } ``` -`lower_bound`和`upper_bound`操作都接受一个关键字,返回一个迭代器。如果关键字在容器中,lower_bound返回的迭代器会指向第一个匹配给定关键字的元素,而upper_bound返回的迭代器则指向最后一个匹配元素之后的位置。如果关键字不在multimap中,则lower_bound和upper_bound会返回相等的迭代器,指向一个不影响排序的关键字插入位置。因此用相同的关键字调用lower_bound和upper_bound会得到一个迭代器范围,表示所有具有该关键字的元素范围。 +`lower_bound`和`upper_bound`操作都接受一个关键字,返回一个迭代器。如果关键字在容器中,`lower_bound`返回的迭代器会指向第一个匹配给定关键字的元素,而`upper_bound`返回的迭代器则指向最后一个匹配元素之后的位置。如果关键字不在`multimap`中,则`lower_bound`和`upper_bound`会返回相等的迭代器,指向一个不影响排序的关键字插入位置。因此用相同的关键字调用`lower_bound`和`upper_bound`会得到一个迭代器范围,表示所有具有该关键字的元素范围。 ```c++ // definitions of authors and search_item as above @@ -235,9 +235,9 @@ for (auto beg = authors.lower_bound(search_item), cout << beg->second << endl; // print each title ``` -lower_bound和upper_bound有可能返回尾后迭代器。如果查找的元素具有容器中最大的关键字,则upper_bound返回尾后迭代器。如果关键字不存在,且大于容器中任何关键字,则lower_bound也返回尾后迭代器。 +`lower_bound`和`upper_bound`有可能返回尾后迭代器。如果查找的元素具有容器中最大的关键字,则`upper_bound`返回尾后迭代器。如果关键字不存在,且大于容器中任何关键字,则`lower_bound`也返回尾后迭代器。 -`equal_range`操作接受一个关键字,返回一个迭代器pair。若关键字存在,则第一个迭代器指向第一个匹配关键字的元素,第二个迭代器指向最后一个匹配元素之后的位置。若关键字不存在,则两个迭代器都指向一个不影响排序的关键字插入位置。 +`equal_range`操作接受一个关键字,返回一个迭代器`pair`。若关键字存在,则第一个迭代器指向第一个匹配关键字的元素,第二个迭代器指向最后一个匹配元素之后的位置。若关键字不存在,则两个迭代器都指向一个不影响排序的关键字插入位置。 ```c++ // definitions of authors and search_item as above diff --git a/Chapter-12/Image/12-1.png b/Chapter-12 Dynamic Memory/Image/12-1.png similarity index 100% rename from Chapter-12/Image/12-1.png rename to Chapter-12 Dynamic Memory/Image/12-1.png diff --git a/Chapter-12/Image/12-2.png b/Chapter-12 Dynamic Memory/Image/12-2.png similarity index 100% rename from Chapter-12/Image/12-2.png rename to Chapter-12 Dynamic Memory/Image/12-2.png diff --git a/Chapter-12/Image/12-3.png b/Chapter-12 Dynamic Memory/Image/12-3.png similarity index 100% rename from Chapter-12/Image/12-3.png rename to Chapter-12 Dynamic Memory/Image/12-3.png diff --git a/Chapter-12/Image/12-4.png b/Chapter-12 Dynamic Memory/Image/12-4.png similarity index 100% rename from Chapter-12/Image/12-4.png rename to Chapter-12 Dynamic Memory/Image/12-4.png diff --git a/Chapter-12/Image/12-5.png b/Chapter-12 Dynamic Memory/Image/12-5.png similarity index 100% rename from Chapter-12/Image/12-5.png rename to Chapter-12 Dynamic Memory/Image/12-5.png diff --git a/Chapter-12/Image/12-6.png b/Chapter-12 Dynamic Memory/Image/12-6.png similarity index 100% rename from Chapter-12/Image/12-6.png rename to Chapter-12 Dynamic Memory/Image/12-6.png diff --git a/Chapter-12/Image/12-7.png b/Chapter-12 Dynamic Memory/Image/12-7.png similarity index 100% rename from Chapter-12/Image/12-7.png rename to Chapter-12 Dynamic Memory/Image/12-7.png diff --git a/Chapter-12/Image/12-8.png b/Chapter-12 Dynamic Memory/Image/12-8.png similarity index 100% rename from Chapter-12/Image/12-8.png rename to Chapter-12 Dynamic Memory/Image/12-8.png diff --git a/Chapter-12/README.md b/Chapter-12 Dynamic Memory/README.md similarity index 58% rename from Chapter-12/README.md rename to Chapter-12 Dynamic Memory/README.md index 27dfa91..b8167a4 100644 --- a/Chapter-12/README.md +++ b/Chapter-12 Dynamic Memory/README.md @@ -6,7 +6,7 @@ C++中的动态内存管理通过一对运算符完成:`new`在动态内存中为对象分配空间并返回指向该对象的指针,可以选择对对象进行初始化;`delete`接受一个动态对象的指针,销毁该对象并释放与之关联的内存。 -新标准库提供了两种智能指针(smart pointer)类型来管理动态对象。智能指针的行为类似常规指针,但它自动释放所指向的对象。这两种智能指针的区别在于管理底层指针的方式:`shared_ptr`允许多个指针指向同一个对象;`unique_ptr`独占所指向的对象。标准库还定义了一个名为`weak_ptr`的伴随类,它是一种弱引用,指向shared_ptr所管理的对象。这三种类型都定义在头文件*memory*中。 +新标准库提供了两种智能指针(smart pointer)类型来管理动态对象。智能指针的行为类似常规指针,但它自动释放所指向的对象。这两种智能指针的区别在于管理底层指针的方式:`shared_ptr`允许多个指针指向同一个对象;`unique_ptr`独占所指向的对象。标准库还定义了一个名为`weak_ptr`的伴随类,它是一种弱引用,指向`shared_ptr`所管理的对象。这三种类型都定义在头文件*memory*中。 ### shared_ptr类(The shared_ptr Class) @@ -17,15 +17,15 @@ shared_ptr p1; // shared_ptr that can point at a string shared_ptr> p2; // shared_ptr that can point at a list of ints ``` -shared_ptr和unique_ptr都支持的操作: +`shared_ptr`和`unique_ptr`都支持的操作: ![12-1](Image/12-1.png) -shared_ptr独有的操作: +`shared_ptr`独有的操作: ![12-2](Image/12-2.png) -`make_shared`函数(定义在头文件*memory*中)在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr。 +`make_shared`函数(定义在头文件*memory*中)在动态内存中分配一个对象并初始化它,返回指向此对象的`shared_ptr`。 ```c++ // shared_ptr that points to an int with value 42 @@ -36,7 +36,7 @@ shared_ptr p4 = make_shared(10, '9'); shared_ptr p5 = make_shared(); ``` -进行拷贝或赋值操作时,每个shared_ptr会记录有多少个其他shared_ptr与其指向相同的对象。 +进行拷贝或赋值操作时,每个`shared_ptr`会记录有多少个其他`shared_ptr`与其指向相同的对象。 ```c++ auto p = make_shared(42); // object to which p points has one user @@ -44,7 +44,7 @@ auto q(p); // p and q point to the same object // object to which p and q point has two users ``` -每个shared_ptr都有一个与之关联的计数器,通常称为引用计数(reference count)。拷贝shared_ptr时引用计数会递增。例如使用一个shared_ptr初始化另一个shared_ptr,或将它作为参数传递给函数以及作为函数的返回值返回。给shared_ptr赋予新值或shared_ptr被销毁时引用计数会递减。例如一个局部shared_ptr离开其作用域。一旦一个shared_ptr的引用计数变为0,它就会自动释放其所管理的对象。 +每个`shared_ptr`都有一个与之关联的计数器,通常称为引用计数(reference count)。拷贝`shared_ptr`时引用计数会递增。例如使用一个`shared_ptr`初始化另一个`shared_ptr`,或将它作为参数传递给函数以及作为函数的返回值返回。给`shared_ptr`赋予新值或`shared_ptr`被销毁时引用计数会递减。例如一个局部`shared_ptr`离开其作用域。一旦一个`shared_ptr`的引用计数变为0,它就会自动释放其所管理的对象。 ```c++ auto r = make_shared(42); // int to which r points has one user @@ -54,9 +54,9 @@ r = q; // assign to r, making it point to a different address // the object r had pointed to has no users; that object is automatically freed ``` -shared_ptr的析构函数会递减它所指向对象的引用计数。如果引用计数变为0,shared_ptr的析构函数会销毁对象并释放空间。 +`shared_ptr`的析构函数会递减它所指向对象的引用计数。如果引用计数变为0,`shared_ptr`的析构函数会销毁对象并释放空间。 -如果将shared_ptr存放于容器中,而后不再需要全部元素,而只使用其中一部分,应该用erase删除不再需要的元素。 +如果将`shared_ptr`存放于容器中,而后不再需要全部元素,而只使用其中一部分,应该用`erase`删除不再需要的元素。 程序使用动态内存通常出于以下三种原因之一: @@ -66,7 +66,7 @@ shared_ptr的析构函数会递减它所指向对象的引用计数。如果引 ### 直接管理内存(Managing Memory Directly) -相对于智能指针,使用new和delete管理内存很容易出错。 +相对于智能指针,使用`new`和`delete`管理内存很容易出错。 默认情况下,动态分配的对象是默认初始化的。所以内置类型或组合类型的对象的值将是未定义的,而类类型对象将用默认构造函数进行初始化。 @@ -88,7 +88,7 @@ int *pi1 = new int; // default initialized; *pi1 is undefined int *pi2 = new int(); // value initialized to 0; *pi2 is 0 ``` -只有当初始化的括号中仅有单一初始化器时才可以使用auto。 +只有当初始化的括号中仅有单一初始化器时才可以使用`auto`。 ```c++ auto p1 = new auto(obj); // p points to an object of the type of obj @@ -96,9 +96,9 @@ auto p1 = new auto(obj); // p points to an object of the type of obj auto p2 = new auto{a,b,c}; // error: must use parentheses for the initializer ``` -可以用new分配const对象,返回指向const类型的指针。动态分配的const对象必须初始化。 +可以用`new`分配`const`对象,返回指向`const`类型的指针。动态分配的`const`对象必须初始化。 -默认情况下,如果new不能分配所要求的内存空间,会抛出bad_alloc异常。使用定位new(placement new)可以阻止其抛出异常。定位new表达式允许程序向new传递额外参数。如果将`nothrow`传递给new,则new在分配失败后会返回空指针。bad_alloc和nothrow都定义在头文件*new*中。 +默认情况下,如果`new`不能分配所要求的内存空间,会抛出`bad_alloc`异常。使用定位`new`(placement new)可以阻止其抛出异常。定位`new`表达式允许程序向`new`传递额外参数。如果将`nothrow`传递给`new`,则`new`在分配失败后会返回空指针。`bad_alloc`和`nothrow`都定义在头文件*new*中。 ```c++ // if allocation fails, new returns a null pointer @@ -106,26 +106,26 @@ int *p1 = new int; // if allocation fails, new throws std::bad_alloc int *p2 = new (nothrow) int; // if allocation fails, new returns a null pointer ``` -使用delete释放一块并非new分配的内存,或者将相同的指针值释放多次的行为是未定义的。 +使用`delete`释放一块并非`new`分配的内存,或者将相同的指针值释放多次的行为是未定义的。 由内置指针管理的动态对象在被显式释放前一直存在。 -delete一个指针后,指针值就无效了(空悬指针,dangling pointer)。为了防止后续的错误访问,应该在delete之后将指针值置空。 +`delete`一个指针后,指针值就无效了(空悬指针,dangling pointer)。为了防止后续的错误访问,应该在`delete`之后将指针值置空。 ### shared_ptr和new结合使用(Using shared_ptrs with new) -可以用new返回的指针初始化智能指针。该构造函数是explicit的,因此必须使用直接初始化形式。 +可以用`new`返回的指针初始化智能指针。该构造函数是`explicit`的,因此必须使用直接初始化形式。 ```c++ shared_ptr p1 = new int(1024); // error: must use direct initialization shared_ptr p2(new int(1024)); // ok: uses direct initialization ``` -默认情况下,用来初始化智能指针的内置指针必须指向动态内存,因为智能指针默认使用delete释放它所管理的对象。如果要将智能指针绑定到一个指向其他类型资源的指针上,就必须提供自定义操作来代替delete。 +默认情况下,用来初始化智能指针的内置指针必须指向动态内存,因为智能指针默认使用`delete`释放它所管理的对象。如果要将智能指针绑定到一个指向其他类型资源的指针上,就必须提供自定义操作来代替`delete`。 ![12-3](Image/12-3.png) -不要混合使用内置指针和智能指针。当将shared_ptr绑定到内置指针后,资源管理就应该交由shared_ptr负责。不应该再使用内置指针访问shared_ptr指向的内存。 +不要混合使用内置指针和智能指针。当将`shared_ptr`绑定到内置指针后,资源管理就应该交由`shared_ptr`负责。不应该再使用内置指针访问`shared_ptr`指向的内存。 ```c++ // ptr is created and initialized when process is called @@ -144,9 +144,9 @@ process(p); // copying p increments its count; in process the reference coun int i = *p; // ok: reference count is 1 ``` -智能指针的`get`函数返回一个内置指针,指向智能指针管理的对象。主要用于向不能使用智能指针的代码传递内置指针。使用get返回指针的代码不能delete此指针。 +智能指针的`get`函数返回一个内置指针,指向智能指针管理的对象。主要用于向不能使用智能指针的代码传递内置指针。使用`get`返回指针的代码不能`delete`此指针。 -不要使用get初始化另一个智能指针或为智能指针赋值。 +不要使用`get`初始化另一个智能指针或为智能指针赋值。 ```c++ shared_ptr p(new int(42)); // reference count is 1 @@ -158,7 +158,7 @@ int *q = p.get(); // ok: but don't use q in any way that might delete its poin int foo = *p; // undefined; the memory to which p points was freed ``` -可以用`reset`函数将新的指针赋予shared_ptr。与赋值类似,reset会更新引用计数,如果需要的话,还会释放内存空间。reset经常与`unique`一起使用,来控制多个shared_ptr共享的对象。 +可以用`reset`函数将新的指针赋予`shared_ptr`。与赋值类似,`reset`会更新引用计数,如果需要的话,还会释放内存空间。`reset`经常与`unique`一起使用,来控制多个`shared_ptr`共享的对象。 ```c++ if (!p.unique()) @@ -185,7 +185,7 @@ void f() } // shared_ptr freed automatically when the function ends ``` -默认情况下shared_ptr假定其指向动态内存,使用delete释放对象。创建shared_ptr时可以传递一个(可选)指向删除函数的指针参数,用来代替delete。 +默认情况下`shared_ptr`假定其指向动态内存,使用`delete`释放对象。创建`shared_ptr`时可以传递一个(可选)指向删除函数的指针参数,用来代替`delete`。 ```c++ struct destination; // represents what we are connecting to @@ -208,17 +208,17 @@ void f(destination &d /* other parameters */) 智能指针规范: -- 不使用相同的内置指针值初始化或reset多个智能指针。 -- 不释放get返回的指针。 -- 不使用get初始化或reset另一个智能指针。 -- 使用get返回的指针时,如果最后一个对应的智能指针被销毁,指针就无效了。 -- 使用shared_ptr管理并非new分配的资源时,应该传递删除函数。 +- 不使用相同的内置指针值初始化或`reset`多个智能指针。 +- 不释放`get`返回的指针。 +- 不使用`get`初始化或`reset`另一个智能指针。 +- 使用`get`返回的指针时,如果最后一个对应的智能指针被销毁,指针就无效了。 +- 使用`shared_ptr`管理并非`new`分配的资源时,应该传递删除函数。 ### unique_ptr(unique_ptr) -与shared_ptr不同,同一时刻只能有一个unique_ptr指向给定的对象。当unique_ptr被销毁时,它指向的对象也会被销毁。 +与`shared_ptr`不同,同一时刻只能有一个`unique_ptr`指向给定的对象。当`unique_ptr`被销毁时,它指向的对象也会被销毁。 -`make_unique`函数(C++14新增,定义在头文件*memory*中)在动态内存中分配一个对象并初始化它,返回指向此对象的unique_ptr。 +`make_unique`函数(C++14新增,定义在头文件*memory*中)在动态内存中分配一个对象并初始化它,返回指向此对象的`unique_ptr`。 ```c++ unique_ptr p1(new int(42)); @@ -226,15 +226,15 @@ unique_ptr p1(new int(42)); unique_ptr p2 = make_unique(42); ``` -由于unique_ptr独占其指向的对象,因此unique_ptr不支持普通的拷贝或赋值操作。 +由于`unique_ptr`独占其指向的对象,因此`unique_ptr`不支持普通的拷贝或赋值操作。 -unique_ptr操作: +`unique_ptr`操作: ![12-4](Image/12-4.png) -`release`函数返回unique_ptr当前保存的指针并将其置为空。 +`release`函数返回`unique_ptr`当前保存的指针并将其置为空。 -`reset`函数成员接受一个可选的指针参数,重新设置unique_ptr保存的指针。如果unique_ptr不为空,则它原来指向的对象会被释放。 +`reset`函数成员接受一个可选的指针参数,重新设置`unique_ptr`保存的指针。如果`unique_ptr`不为空,则它原来指向的对象会被释放。 ```c++ // transfers ownership from p1 (which points to the string Stegosaurus) to p2 @@ -244,14 +244,14 @@ unique_ptr p3(new string("Trex")); p2.reset(p3.release()); // reset deletes the memory to which p2 had pointed ``` -调用release会切断unique_ptr和它原来管理的对象之间的联系。release返回的指针通常被用来初始化另一个智能指针或给智能指针赋值。如果没有用另一个智能指针保存release返回的指针,程序就要负责资源的释放。 +调用`release`会切断`unique_ptr`和它原来管理的对象之间的联系。`release`返回的指针通常被用来初始化另一个智能指针或给智能指针赋值。如果没有用另一个智能指针保存`release`返回的指针,程序就要负责资源的释放。 ```c++ p2.release(); // WRONG: p2 won't free the memory and we've lost the pointer auto p = p2.release(); // ok, but we must remember to delete(p) ``` -不能拷贝unique_ptr的规则有一个例外:可以拷贝或赋值一个即将被销毁的unique_ptr(移动构造、移动赋值)。 +不能拷贝`unique_ptr`的规则有一个例外:可以拷贝或赋值一个即将被销毁的`unique_ptr`(移动构造、移动赋值)。 ```c++ unique_ptr clone(int p) @@ -264,7 +264,7 @@ unique_ptr clone(int p) 老版本的标准库包含了一个名为`auto_ptr`的类, -类似shared_ptr,默认情况下unique_ptr用delete释放其指向的对象。unique_ptr的删除器同样可以重载,但unique_ptr管理删除器的方式与shared_ptr不同。定义unique_ptr时必须在尖括号中提供删除器类型。创建或reset这种unique_ptr类型的对象时,必须提供一个指定类型的可调用对象(删除器)。 +类似`shared_ptr`,默认情况下`unique_ptr`用`delete`释放其指向的对象。`unique_ptr`的删除器同样可以重载,但`unique_ptr`管理删除器的方式与`shared_ptr`不同。定义`unique_ptr`时必须在尖括号中提供删除器类型。创建或`reset`这种`unique_ptr`类型的对象时,必须提供一个指定类型的可调用对象(删除器)。 ```c++ // p points to an object of type objT and uses an object of type delT to free that object @@ -283,18 +283,18 @@ void f(destination &d /* other needed parameters */) ### weak_ptr(weak_ptr) -weak_ptr是一种不控制所指向对象生存期的智能指针,它指向一个由shared_ptr管理的对象。将weak_ptr绑定到shared_ptr不会改变shared_ptr的引用计数。如果shared_ptr被销毁,即使有weak_ptr指向对象,对象仍然有可能被释放。 +`weak_ptr`是一种不控制所指向对象生存期的智能指针,它指向一个由`shared_ptr`管理的对象。将`weak_ptr`绑定到`shared_ptr`不会改变`shared_ptr`的引用计数。如果`shared_ptr`被销毁,即使有`weak_ptr`指向对象,对象仍然有可能被释放。 ![12-5](Image/12-5.png) -创建一个weak_ptr时,需要使用shared_ptr来初始化它。 +创建一个`weak_ptr`时,需要使用`shared_ptr`来初始化它。 ```c++ auto p = make_shared(42); weak_ptr wp(p); // wp weakly shares with p; use count in p is unchanged ``` -使用weak_ptr访问对象时,必须先调用`lock`函数。该函数检查weak_ptr指向的对象是否仍然存在。如果存在,则返回指向共享对象的shared_ptr,否则返回空指针。 +使用`weak_ptr`访问对象时,必须先调用`lock`函数。该函数检查`weak_ptr`指向的对象是否仍然存在。如果存在,则返回指向共享对象的`shared_ptr`,否则返回空指针。 ```c++ if (shared_ptr np = wp.lock()) @@ -310,16 +310,16 @@ if (shared_ptr np = wp.lock()) ### new和数组(new and Arrays) -使用new分配对象数组时需要在类型名之后跟一对方括号,在其中指明要分配的对象数量(必须是整型,但不必是常量)。new返回指向第一个对象的指针(元素类型)。 +使用`new`分配对象数组时需要在类型名之后跟一对方括号,在其中指明要分配的对象数量(必须是整型,但不必是常量)。`new`返回指向第一个对象的指针(元素类型)。 ```c++ // call get_size to determine how many ints to allocate int *pia = new int[get_size()]; // pia points to the first of these ints ``` -由于new分配的内存并不是数组类型,因此不能对动态数组调用begin和end,也不能用范围for语句处理其中的元素。 +由于`new`分配的内存并不是数组类型,因此不能对动态数组调用`begin`和`end`,也不能用范围`for`语句处理其中的元素。 -默认情况下,new分配的对象是默认初始化的。可以对数组中的元素进行值初始化,方法是在大小后面跟一对空括号`()`。在新标准中,还可以提供一个元素初始化器的花括号列表。如果初始化器数量大于元素数量,则new表达式失败,不会分配任何内存,并抛出bad_array_new_length异常。 +默认情况下,`new`分配的对象是默认初始化的。可以对数组中的元素进行值初始化,方法是在大小后面跟一对空括号`()`。在新标准中,还可以提供一个元素初始化器的花括号列表。如果初始化器数量大于元素数量,则`new`表达式失败,不会分配任何内存,并抛出`bad_array_new_length`异常。 ```c++ int *pia = new int[10]; // block of ten uninitialized ints @@ -333,9 +333,9 @@ int *pia3 = new int[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; string *psa3 = new string[10] { "a", "an", "the", string(3,'x') }; ``` -虽然可以使用空括号对new分配的数组元素进行值初始化,但不能在括号中指定初始化器。这意味着不能用auto分配数组。 +虽然可以使用空括号对`new`分配的数组元素进行值初始化,但不能在括号中指定初始化器。这意味着不能用`auto`分配数组。 -动态分配一个空数组是合法的,此时new会返回一个合法的非空指针。对于零长度的数组来说,该指针类似尾后指针,不能解引用。 +动态分配一个空数组是合法的,此时`new`会返回一个合法的非空指针。对于零长度的数组来说,该指针类似尾后指针,不能解引用。 使用`delete[]`释放动态数组。 @@ -344,9 +344,9 @@ delete p; // p must point to a dynamically allocated object or be null delete [] pa; // pa must point to a dynamically allocated array or be null ``` -如果在delete数组指针时忘记添加方括号,或者在delete单一对象时使用了方括号,编译器很可能不会给出任何警告,程序可能会在执行过程中行为异常。 +如果在`delete`数组指针时忘记添加方括号,或者在`delete`单一对象时使用了方括号,编译器很可能不会给出任何警告,程序可能会在执行过程中行为异常。 -unique_ptr可以直接管理动态数组,定义时需要在对象类型后添加一对空方括号`[]`。 +`unique_ptr`可以直接管理动态数组,定义时需要在对象类型后添加一对空方括号`[]`。 ```c++ // up points to an array of ten uninitialized ints @@ -354,11 +354,11 @@ unique_ptr up(new int[10]); up.release(); // automatically uses delete[] to destroy its pointer ``` -指向数组的unique_ptr: +指向数组的`unique_ptr`: ![12-6](Image/12-6.png) -与unique_ptr不同,shared_ptr不直接支持动态数组管理。如果想用shared_ptr管理动态数组,必须提供自定义的删除器。 +与`unique_ptr`不同,`shared_ptr`不直接支持动态数组管理。如果想用`shared_ptr`管理动态数组,必须提供自定义的删除器。 ```c++ // to use a shared_ptr we must supply a deleter @@ -366,7 +366,7 @@ shared_ptr sp(new int[10], [](int *p) { delete[] p; }); sp.reset(); // uses the lambda we supplied that uses delete[] to free the array ``` -shared_ptr未定义下标运算符,智能指针类型也不支持指针算术运算。因此如果想访问shared_ptr管理的数组元素,必须先用get获取内置指针,再用内置指针进行访问。 +`shared_ptr`未定义下标运算符,智能指针类型也不支持指针算术运算。因此如果想访问`shared_ptr`管理的数组元素,必须先用`get`获取内置指针,再用内置指针进行访问。 ```c++ // shared_ptrs don't have subscript operator and don't support pointer arithmetic @@ -376,18 +376,18 @@ for (size_t i = 0; i != 10; ++i) ### allocator类(The allocator Class) -allocator类是一个模板,定义时必须指定其可以分配的对象类型。 +`allocator`类是一个模板,定义时必须指定其可以分配的对象类型。 ```c++ allocator alloc; // object that can allocate strings auto const p = alloc.allocate(n); // allocate n unconstructed strings ``` -标准库allocator类及其算法: +标准库`allocator`类及其算法: ![12-7](Image/12-7.png) -allocator分配的内存是未构造的,程序需要在此内存中构造对象。新标准库的`construct`函数接受一个指针和零或多个额外参数,在给定位置构造一个元素。额外参数用来初始化构造的对象,必须与对象类型相匹配。 +`allocator`分配的内存是未构造的,程序需要在此内存中构造对象。新标准库的`construct`函数接受一个指针和零或多个额外参数,在给定位置构造一个元素。额外参数用来初始化构造的对象,必须与对象类型相匹配。 ```c++ auto q = p; // q will point to one past the last constructed element @@ -396,22 +396,22 @@ alloc.construct(q++, 10, 'c'); // *q is cccccccccc alloc.construct(q++, "hi"); // *q is hi! ``` -直接使用allocator返回的未构造内存是错误行为,其结果是未定义的。 +直接使用`allocator`返回的未构造内存是错误行为,其结果是未定义的。 -对象使用完后,必须对每个构造的元素调用`destroy`进行销毁。destroy函数接受一个指针,对指向的对象执行析构函数。 +对象使用完后,必须对每个构造的元素调用`destroy`进行销毁。`destroy`函数接受一个指针,对指向的对象执行析构函数。 ```c++ while (q != p) alloc.destroy(--q); // free the strings we actually allocated ``` -`deallocate`函数用于释放allocator分配的内存空间。传递给deallocate的指针不能为空,它必须指向由allocator分配的内存。而且传递给deallocate的大小参数必须与调用allocator分配内存时提供的大小参数相一致。 +`deallocate`函数用于释放`allocator`分配的内存空间。传递给`deallocate`的指针不能为空,它必须指向由`allocator`分配的内存。而且传递给`deallocate`的大小参数必须与调用`allocator`分配内存时提供的大小参数相一致。 ```c++ alloc.deallocate(p, n); ``` -allocator算法: +`allocator`算法: ![12-8](Image/12-8.png) diff --git a/Chapter-13/README.md b/Chapter-13 Copy Control/README.md similarity index 82% rename from Chapter-13/README.md rename to Chapter-13 Copy Control/README.md index 893a14c..c194db7 100644 --- a/Chapter-13/README.md +++ b/Chapter-13 Copy Control/README.md @@ -16,7 +16,7 @@ ### 拷贝构造函数(The Copy Constructor) -如果一个构造函数的第一个参数是自身类类型的引用(几乎总是const引用),且任何额外参数都有默认值,则此构造函数是拷贝构造函数。 +如果一个构造函数的第一个参数是自身类类型的引用(几乎总是`const`引用),且任何额外参数都有默认值,则此构造函数是拷贝构造函数。 ```c++ class Foo @@ -28,9 +28,9 @@ public: }; ``` -由于拷贝构造函数在一些情况下会被隐式使用,因此通常不会声明为explicit的。 +由于拷贝构造函数在一些情况下会被隐式使用,因此通常不会声明为`explicit`的。 -如果类未定义自己的拷贝构造函数,编译器会为类合成一个。一般情况下,合成拷贝构造函数(synthesized copy constructor)会将其参数的非static成员逐个拷贝到正在创建的对象中。 +如果类未定义自己的拷贝构造函数,编译器会为类合成一个。一般情况下,合成拷贝构造函数(synthesized copy constructor)会将其参数的非`static`成员逐个拷贝到正在创建的对象中。 ```c++ class Sales_data @@ -72,7 +72,7 @@ string nines = string(100, '9'); // copy initialization - 从返回类型为非引用类型的函数返回对象。 - 用花括号列表初始化数组中的元素或聚合类中的成员。 -当传递一个实参或者从函数返回一个值时,不能隐式使用explicit构造函数。 +当传递一个实参或者从函数返回一个值时,不能隐式使用`explicit`构造函数。 ```c++ vector v1(10); // ok: direct initialization @@ -86,7 +86,7 @@ f(vector(10)); // ok: directly construct a temporary vector from an int 重载运算符(overloaded operator)的参数表示运算符的运算对象。 -如果一个运算符是成员函数,则其左侧运算对象会绑定到隐式的this参数上。 +如果一个运算符是成员函数,则其左侧运算对象会绑定到隐式的`this`参数上。 赋值运算符通常应该返回一个指向其左侧运算对象的引用。 @@ -101,7 +101,7 @@ public: 标准库通常要求保存在容器中的类型要具有赋值运算符,且其返回值是左侧运算对象的引用。 -如果类未定义自己的拷贝赋值运算符,编译器会为类合成一个。一般情况下,合成拷贝赋值运算符(synthesized copy-assignment operator)会将其右侧运算对象的非static成员逐个赋值给左侧运算对象的对应成员,之后返回左侧运算对象的引用。 +如果类未定义自己的拷贝赋值运算符,编译器会为类合成一个。一般情况下,合成拷贝赋值运算符(synthesized copy-assignment operator)会将其右侧运算对象的非`static`成员逐个赋值给左侧运算对象的对应成员,之后返回左侧运算对象的引用。 ```c++ // equivalent to the synthesized copy-assignment operator @@ -117,7 +117,7 @@ Sales_data& Sales_data::operator=(const Sales_data &rhs) ### 析构函数(The Destructor) -析构函数负责释放对象使用的资源,并销毁对象的非static数据成员。 +析构函数负责释放对象使用的资源,并销毁对象的非`static`数据成员。 析构函数的名字由波浪号`~`接类名构成,它没有返回值,也不接受参数。 @@ -136,13 +136,13 @@ public: 析构函数首先执行函数体,然后再销毁数据成员。在整个对象销毁过程中,析构函数体是作为成员销毁步骤之外的另一部分而进行的。成员按照初始化顺序的逆序销毁。 -隐式销毁一个内置指针类型的成员不会delete它所指向的对象。 +隐式销毁一个内置指针类型的成员不会`delete`它所指向的对象。 无论何时一个对象被销毁,都会自动调用其析构函数。 当指向一个对象的引用或指针离开作用域时,该对象的析构函数不会执行。 -### 三/五法则(The Rule of Three/Five) +### 三/五法则(The Rule of Three/Five) 需要析构函数的类一般也需要拷贝和赋值操作。 @@ -164,9 +164,9 @@ public: }; ``` -在类内使用=default修饰成员声明时,合成的函数是隐式内联的。如果不希望合成的是内联函数,应该只对成员的类外定义使用=default。 +在类内使用`=default`修饰成员声明时,合成的函数是隐式内联的。如果不希望合成的是内联函数,应该只对成员的类外定义使用`=default`。 -只能对具有合成版本的成员函数使用=default。 +只能对具有合成版本的成员函数使用`=default`。 ### 阻止拷贝(Preventing Copies) @@ -185,16 +185,16 @@ struct NoCopy }; ``` -=delete和=default有两点不同: +`=delete`和`=default`有两点不同: -- =delete可以对任何函数使用;=default只能对具有合成版本的函数使用。 -- =delete必须出现在函数第一次声明的地方;=default既能出现在类内,也能出现在类外。 +- `=delete`可以对任何函数使用;`=default`只能对具有合成版本的函数使用。 +- `=delete`必须出现在函数第一次声明的地方;`=default`既能出现在类内,也能出现在类外。 析构函数不能是删除的函数。对于析构函数被删除的类型,不能定义该类型的变量或者释放指向该类型动态分配对象的指针。 如果一个类中有数据成员不能默认构造、拷贝或销毁,则对应的合成拷贝控制成员将被定义为删除的。 -在旧版本的C++标准中,类通过将拷贝构造函数和拷贝赋值运算符声明为private成员来阻止类对象的拷贝。在新标准中建议使用=delete而非private。 +在旧版本的C++标准中,类通过将拷贝构造函数和拷贝赋值运算符声明为`private`成员来阻止类对象的拷贝。在新标准中建议使用`=delete`而非`private`。 ## 拷贝控制和资源管理(Copy Control and Resource Management) @@ -302,7 +302,7 @@ HasPtr& HasPtr::operator=(const HasPtr &rhs) ## 交换操作(Swap) -通常,管理类外资源的类会定义swap函数。如果一个类定义了自己的swap函数,算法将使用自定义版本,否则将使用标准库定义的`swap`。 +通常,管理类外资源的类会定义`swap`函数。如果一个类定义了自己的`swap`函数,算法将使用自定义版本,否则将使用标准库定义的`swap`。 ```c++ class HasPtr @@ -319,7 +319,7 @@ inline void swap(HasPtr &lhs, HasPtr &rhs) } ``` -一些算法在交换两个元素时会调用swap函数,其中每个swap调用都应该是未加限定的。如果存在类型特定的swap版本,其匹配程度会优于*std*中定义的版本(假定作用域中有using声明)。 +一些算法在交换两个元素时会调用`swap`函数,其中每个`swap`调用都应该是未加限定的。如果存在类型特定的`swap`版本,其匹配程度会优于*std*中定义的版本(假定作用域中有`using`声明)。 ```c++ void swap(Foo &lhs, Foo &rhs) @@ -337,11 +337,11 @@ void swap(Foo &lhs, Foo &rhs) } ``` -与拷贝控制成员不同,swap函数并不是必要的。但是对于分配了资源的类,定义swap可能是一种重要的优化手段。 +与拷贝控制成员不同,`swap`函数并不是必要的。但是对于分配了资源的类,定义`swap`可能是一种重要的优化手段。 -由于swap函数的存在就是为了优化代码,所以一般将其声明为内联函数。 +由于`swap`函数的存在就是为了优化代码,所以一般将其声明为内联函数。 -定义了swap的类通常用swap来实现赋值运算符。在这种版本的赋值运算符中,右侧运算对象以值方式传递,然后将左侧运算对象与右侧运算对象的副本进行交换(拷贝并交换,copy and swap)。这种方式可以正确处理自赋值情况。 +定义了`swap`的类通常用`swap`来实现赋值运算符。在这种版本的赋值运算符中,右侧运算对象以值方式传递,然后将左侧运算对象与右侧运算对象的副本进行交换(拷贝并交换,copy and swap)。这种方式可以正确处理自赋值情况。 ```c++ // note rhs is passed by value, which means the HasPtr copy constructor @@ -356,7 +356,7 @@ HasPtr& HasPtr::operator=(HasPtr rhs) ## 拷贝控制示例(A Copy-Control Example) -拷贝赋值运算符通常结合了拷贝构造函数和析构函数的工作。在这种情况下,公共部分应该放在private的工具函数中完成。 +拷贝赋值运算符通常结合了拷贝构造函数和析构函数的工作。在这种情况下,公共部分应该放在`private`的工具函数中完成。 ## 动态内存管理类(Classes That Manage Dynamic Memory) @@ -368,7 +368,7 @@ HasPtr& HasPtr::operator=(HasPtr rhs) 在旧版本的标准库中,容器所能保存的类型必须是可拷贝的。但在新标准中,可以用容器保存不可拷贝,但可移动的类型。 -标准库容器、string和shared_ptr类既支持移动也支持拷贝。IO类和unique_ptr类可以移动但不能拷贝。 +标准库容器、`string`和`shared_ptr`类既支持移动也支持拷贝。IO类和`unique_ptr`类可以移动但不能拷贝。 ### 右值引用(Rvalue Reference) @@ -398,7 +398,7 @@ int &&rr2 = rr1; // error: the expression rr1 is an lvalue! int &&rr3 = std::move(rr1); ``` -调用move函数的代码应该使用std::move而非move,这样做可以避免潜在的名字冲突。 +调用`move`函数的代码应该使用`std::move`而非`move`,这样做可以避免潜在的名字冲突。 ### 移动构造函数和移动赋值运算符(Move Constructor and Move Assignment) @@ -408,7 +408,7 @@ int &&rr3 = std::move(rr1); 在函数的形参列表后面添加关键字`noexcept`可以指明该函数不会抛出任何异常。 -对于构造函数,noexcept位于形参列表和初始化列表开头的冒号之间。在类的头文件声明和定义中(如果定义在类外)都应该指定noexcept。 +对于构造函数,`noexcept`位于形参列表和初始化列表开头的冒号之间。在类的头文件声明和定义中(如果定义在类外)都应该指定`noexcept`。 ```c++ class StrVec @@ -424,7 +424,7 @@ StrVec::StrVec(StrVec &&s) noexcept : /* member initializers */ 标准库容器能对异常发生时其自身的行为提供保障。虽然移动操作通常不抛出异常,但抛出异常也是允许的。为了安全起见,除非容器确定元素类型的移动操作不会抛出异常,否则在重新分配内存的过程中,它就必须使用拷贝而非移动操作。 -不抛出异常的移动构造函数和移动赋值运算符必须标记为noexcept。 +不抛出异常的移动构造函数和移动赋值运算符必须标记为`noexcept`。 在移动操作之后,移后源对象必须保持有效的、可销毁的状态,但是用户不能使用它的值。 @@ -445,7 +445,7 @@ StrVec &StrVec::operator=(StrVec &&rhs) noexcept } ``` -只有当一个类没有定义任何拷贝控制成员,且类的每个非static数据成员都可以移动时,编译器才会为类合成移动构造函数和移动赋值运算符。编译器可以移动内置类型的成员。如果一个成员是类类型,且该类有对应的移动操作,则编译器也能移动该成员。 +只有当一个类没有定义任何拷贝控制成员,且类的每个非`static`数据成员都可以移动时,编译器才会为类合成移动构造函数和移动赋值运算符。编译器可以移动内置类型的成员。如果一个成员是类类型,且该类有对应的移动操作,则编译器也能移动该成员。 ```c++ // the compiler will synthesize the move operations for X and hasX @@ -464,11 +464,11 @@ X x, x2 = std::move(x); // uses the synthesized move constructor hasX hx, hx2 = std::move(hx); // uses the synthesized move constructor ``` -与拷贝操作不同,移动操作永远不会被隐式定义为删除的函数。但如果显式地要求编译器生成=default的移动操作,且编译器不能移动全部成员,则移动操作会被定义为删除的函数。 +与拷贝操作不同,移动操作永远不会被隐式定义为删除的函数。但如果显式地要求编译器生成`=default`的移动操作,且编译器不能移动全部成员,则移动操作会被定义为删除的函数。 定义了移动构造函数或移动赋值运算符的类必须也定义自己的拷贝操作,否则这些成员会被默认地定义为删除的函数。 -如果一个类有可用的拷贝构造函数而没有移动构造函数,则其对象是通过拷贝构造函数来“移动”的,即使调用move函数时也是如此。拷贝赋值运算符和移动赋值运算符的情况类似。 +如果一个类有可用的拷贝构造函数而没有移动构造函数,则其对象是通过拷贝构造函数来“移动”的,即使调用`move`函数时也是如此。拷贝赋值运算符和移动赋值运算符的情况类似。 ```c++ class Foo @@ -506,11 +506,11 @@ C++11标准库定义了移动迭代器(move iterator)适配器。一个移 调用`make_move_iterator`函数能将一个普通迭代器转换成移动迭代器。原迭代器的所有其他操作在移动迭代器中都照常工作。 -最好不要在移动构造函数和移动赋值运算符这些类实现代码之外的地方随意使用move操作。 +最好不要在移动构造函数和移动赋值运算符这些类实现代码之外的地方随意使用`move`操作。 ### 右值引用和成员函数(Rvalue References and Member Functions) -区分移动和拷贝的重载函数通常有一个版本接受一个const *T*&参数,另一个版本接受一个*T*&&参数(*T*为类型)。 +区分移动和拷贝的重载函数通常有一个版本接受一个`const T&`参数,另一个版本接受一个`T&&`参数(*T*为类型)。 ```c++ void push_back(const X&); // copy: binds to any kind of X @@ -524,9 +524,9 @@ string s1, s2; s1 + s2 = "wow!"; ``` -在旧标准中,没有办法阻止这种使用方式。为了维持向下兼容性,新标准库仍然允许向右值赋值。但是可以在自己的类中阻止这种行为,规定左侧运算对象(即this指向的对象)必须是一个左值。 +在旧标准中,没有办法阻止这种使用方式。为了维持向下兼容性,新标准库仍然允许向右值赋值。但是可以在自己的类中阻止这种行为,规定左侧运算对象(即`this`指向的对象)必须是一个左值。 -在非static成员函数的形参列表后面添加引用限定符(reference qualifier)可以指定this的左值/右值属性。引用限定符可以是`&`或者`&&`,分别表示this可以指向一个左值或右值对象。引用限定符必须同时出现在函数的声明和定义中。 +在非`static`成员函数的形参列表后面添加引用限定符(reference qualifier)可以指定`this`的左值/右值属性。引用限定符可以是`&`或者`&&`,分别表示`this`可以指向一个左值或右值对象。引用限定符必须同时出现在函数的声明和定义中。 ```c++ class Foo @@ -543,7 +543,7 @@ Foo &Foo::operator=(const Foo &rhs) & } ``` -一个非static成员函数可以同时使用const和引用限定符,此时引用限定符跟在const限定符之后。 +一个非`static`成员函数可以同时使用`const`和引用限定符,此时引用限定符跟在`const`限定符之后。 ```c++ class Foo diff --git a/Chapter-14/Image/14-1.png b/Chapter-14 Overloaded Operations and Conversions/Image/14-1.png similarity index 100% rename from Chapter-14/Image/14-1.png rename to Chapter-14 Overloaded Operations and Conversions/Image/14-1.png diff --git a/Chapter-14/Image/14-2.png b/Chapter-14 Overloaded Operations and Conversions/Image/14-2.png similarity index 100% rename from Chapter-14/Image/14-2.png rename to Chapter-14 Overloaded Operations and Conversions/Image/14-2.png diff --git a/Chapter-14/Image/14-3.png b/Chapter-14 Overloaded Operations and Conversions/Image/14-3.png similarity index 100% rename from Chapter-14/Image/14-3.png rename to Chapter-14 Overloaded Operations and Conversions/Image/14-3.png diff --git a/Chapter-14/README.md b/Chapter-14 Overloaded Operations and Conversions/README.md similarity index 84% rename from Chapter-14/README.md rename to Chapter-14 Overloaded Operations and Conversions/README.md index baf39dc..592ed38 100644 --- a/Chapter-14/README.md +++ b/Chapter-14 Overloaded Operations and Conversions/README.md @@ -6,7 +6,7 @@ 重载运算符函数的参数数量和该运算符作用的运算对象数量一样多。对于二元运算符来说,左侧运算对象传递给第一个参数,右侧运算对象传递给第二个参数。除了重载的函数调用运算符`operator()`之外,其他重载运算符不能含有默认实参。 -如果一个运算符函数是类的成员函数,则它的第一个运算对象会绑定到隐式的this指针上。因此成员运算符函数的显式参数数量比运算对象的数量少一个。 +如果一个运算符函数是类的成员函数,则它的第一个运算对象会绑定到隐式的`this`指针上。因此成员运算符函数的显式参数数量比运算对象的数量少一个。 当运算符作用于内置类型的运算对象时,无法改变该运算符的含义。 @@ -51,7 +51,7 @@ string u = "hi" + s; // would be an error if + were a member of string ### 重载输出运算符<<(Overloading the Output Operator <<) -通常情况下,输出运算符的第一个形参是ostream类型的普通引用,第二个形参是要打印类型的常量引用,返回值是它的ostream形参。 +通常情况下,输出运算符的第一个形参是`ostream`类型的普通引用,第二个形参是要打印类型的常量引用,返回值是它的`ostream`形参。 ```c++ ostream &operator<<(ostream &os, const Sales_data &item) @@ -93,13 +93,13 @@ istream &operator>>(istream &is, Sales_data &item) 当读取操作发生错误时,输入操作符应该负责从错误状态中恢复。 -如果输入的数据不符合规定的格式,即使从技术上看IO操作是成功的,输入运算符也应该设置流的条件状态以标示出失败信息。通常情况下,输入运算符只设置failbit状态。eofbit、badbit等错误最好由IO标准库自己标示。 +如果输入的数据不符合规定的格式,即使从技术上看IO操作是成功的,输入运算符也应该设置流的条件状态以标示出失败信息。通常情况下,输入运算符只设置`failbit`状态。`eofbit`、`badbit`等错误最好由IO标准库自己标示。 ## 算术和关系运算符(Arithmetic and Relational Operators) 通常情况下,算术和关系运算符应该定义为非成员函数,以便两侧的运算对象进行转换。其次,由于这些运算符一般不会改变运算对象的状态,所以形参都是常量引用。 -算术运算符通常会计算它的两个运算对象并得到一个新值,这个值通常存储在一个局部变量内,操作完成后返回该局部变量的副本作为结果(返回类型建议设置为原对象的const类型)。 +算术运算符通常会计算它的两个运算对象并得到一个新值,这个值通常存储在一个局部变量内,操作完成后返回该局部变量的副本作为结果(返回类型建议设置为原对象的`const`类型)。 ```c++ // assumes that both objects refer to the same book @@ -119,11 +119,11 @@ Sales_data operator+(const Sales_data &lhs, const Sales_data &rhs) - 如果类在逻辑上有相等性的含义,则应该定义`operator==`而非一个普通的命名函数。这样做便于使用标准库容器和算法,也更容易记忆。 -- 通常情况下,operator==应该具有传递性。 +- 通常情况下,`operator==`应该具有传递性。 -- 如果类定义了operator==,则也应该定义`operator!=`。 +- 如果类定义了`operator==`,则也应该定义`operator!=`。 -- operator==和operator!=中的一个应该把具体工作委托给另一个。 +- `operator==`和`operator!=`中的一个应该把具体工作委托给另一个。 ```c++ bool operator==(const Sales_data &lhs, const Sales_data &rhs) @@ -146,8 +146,8 @@ Sales_data operator+(const Sales_data &lhs, const Sales_data &rhs) 关系运算符设计准则: - 定义顺序关系,令其与关联容器中对关键字的要求保持一致。 -- 如果类定义了operator==,则关系运算符的定义应该与operator==保持一致。特别是,如果两个对象是不相等的,那么其中一个对象应该小于另一个对象。 -- 只有存在唯一一种逻辑可靠的小于关系时,才应该考虑为类定义operator<。 +- 如果类定义了`operator==`,则关系运算符的定义应该与`operator==`保持一致。特别是,如果两个对象是不相等的,那么其中一个对象应该小于另一个对象。 +- 只有存在唯一一种逻辑可靠的小于关系时,才应该考虑为类定义`operator<`。 ## 赋值运算符(Assignment Operators) @@ -210,7 +210,7 @@ StrBlobPtr& StrBlobPtr::operator++() } ``` -后置递增或递减运算符接受一个额外的(不被使用)int类型形参,该形参的唯一作用就是区分运算符的前置和后置版本。 +后置递增或递减运算符接受一个额外的(不被使用)`int`类型形参,该形参的唯一作用就是区分运算符的前置和后置版本。 ```c++ class StrBlobPtr @@ -224,7 +224,7 @@ public: }; ``` -为了与内置操作保持一致,后置递增或递减运算符应该返回运算前对象的原值(返回类型建议设置为原对象的const类型)。 +为了与内置操作保持一致,后置递增或递减运算符应该返回运算前对象的原值(返回类型建议设置为原对象的`const`类型)。 ```c++ StrBlobPtr StrBlobPtr::operator++(int) @@ -264,10 +264,10 @@ public: }; ``` -对于形如*point->mem*的表达式来说,*point*必须是指向类对象的指针或者是一个重载了`operator->`的类的对象。*point*类型不同,*point->mem*的含义也不同。 +对于形如`point->mem`的表达式来说,*point*必须是指向类对象的指针或者是一个重载了`operator->`的类的对象。*point*类型不同,`point->mem`的含义也不同。 -- 如果*point*是指针,则调用内置箭头运算符,表达式等价于*(\*point).mem*。 -- 如果*point*是重载了operator->的类的对象,则使用*point.operator->()*的结果来获取*mem*,表达式等价于*(point.operator->())->mem*。其中,如果该结果是一个指针,则执行内置操作,否则重复调用当前操作。 +- 如果*point*是指针,则调用内置箭头运算符,表达式等价于`(*point).mem`。 +- 如果*point*是重载了`operator->`的类的对象,则使用`point.operator->()`的结果来获取*mem*,表达式等价于`(point.operator->())->mem`。其中,如果该结果是一个指针,则执行内置操作,否则重复调用当前操作。 ## 函数调用运算符(Function-Call Operator) @@ -301,7 +301,7 @@ for_each(vs.begin(), vs.end(), PrintString(cerr, '\n')); ### lambda是函数对象(Lambdas Are Function Objects) -编写一个lambda后,编译器会将该表达式转换成一个未命名类的未命名对象,类中含有一个重载的函数调用运算符。 +编写一个`lambda`后,编译器会将该表达式转换成一个未命名类的未命名对象,类中含有一个重载的函数调用运算符。 ```c++ // sort words by size, but maintain alphabetical order for words of the same size @@ -319,9 +319,9 @@ public: }; ``` -lambda默认不能改变它捕获的变量。因此在默认情况下,由lambda产生的类中的函数调用运算符是一个const成员函数。如果lambda被声明为可变的,则调用运算符就不再是const函数了。 +`lambda`默认不能改变它捕获的变量。因此在默认情况下,由`lambda`产生的类中的函数调用运算符是一个`const`成员函数。如果`lambda`被声明为可变的,则调用运算符就不再是`const`函数了。 -lambda通过引用捕获变量时,由程序负责确保lambda执行时该引用所绑定的对象确实存在。因此编译器可以直接使用该引用而无须在lambda产生的类中将其存储为数据成员。相反,通过值捕获的变量被拷贝到lambda中,此时lambda产生的类必须为每个值捕获的变量建立对应的数据成员,并创建构造函数,用捕获变量的值来初始化数据成员。 +`lambda`通过引用捕获变量时,由程序负责确保`lambda`执行时该引用所绑定的对象确实存在。因此编译器可以直接使用该引用而无须在`lambda`产生的类中将其存储为数据成员。相反,通过值捕获的变量被拷贝到`lambda`中,此时`lambda`产生的类必须为每个值捕获的变量建立对应的数据成员,并创建构造函数,用捕获变量的值来初始化数据成员。 ```c++ // get an iterator to the first element whose size() is >= sz @@ -344,7 +344,7 @@ private: }; ``` -lambda产生的类不包含默认构造函数、赋值运算符和默认析构函数,它是否包含默认拷贝/移动构造函数则通常要视捕获的变量类型而定。 +`lambda`产生的类不包含默认构造函数、赋值运算符和默认析构函数,它是否包含默认拷贝/移动构造函数则通常要视捕获的变量类型而定。 ### 标准库定义的函数对象(Library-Defined Function Objects) @@ -371,7 +371,7 @@ sort(nameTable.begin(), nameTable.end(), less()); ![14-3](Image/14-3.png) -创建一个具体的function类型时必须提供其所表示的对象的调用形式。 +创建一个具体的`function`类型时必须提供其所表示的对象的调用形式。 ```c++ // ordinary function @@ -394,9 +394,9 @@ cout << f2(4,2) << endl; // prints 2 cout << f3(4,2) << endl; // prints 8 ``` -不能直接将重载函数的名字存入function类型的对象中,这样做会产生二义性错误。消除二义性的方法是使用lambda或者存储函数指针而非函数名字。 +不能直接将重载函数的名字存入`function`类型的对象中,这样做会产生二义性错误。消除二义性的方法是使用`lambda`或者存储函数指针而非函数名字。 -C++11新标准库中的function类与旧版本中的`unary_function`和`binary_function`没有关系,后两个类已经被bind函数代替。 +C++11新标准库中的`function`类与旧版本中的`unary_function`和`binary_function`没有关系,后两个类已经被`bind`函数代替。 ## 重载、类型转换与运算符(Overloading,Conversions,and Operators) @@ -410,7 +410,7 @@ C++11新标准库中的function类与旧版本中的`unary_function`和`binary_f operator type() const; ``` -类型转换运算符可以面向除了void以外的任意类型(该类型要能作为函数的返回类型)进行定义。 +类型转换运算符可以面向除了`void`以外的任意类型(该类型要能作为函数的返回类型)进行定义。 ```c++ class SmallInt @@ -457,18 +457,18 @@ static_cast(si) + 3; // ok: explicitly request the conversion 如果表达式被用作条件,则编译器会隐式地执行显式类型转换。 -- if、while、do语句的条件部分。 -- for语句头的条件表达式。 +- `if`、`while`、`do-while`语句的条件部分。 +- `for`语句头的条件表达式。 - 条件运算符`? :`的条件表达式。 - 逻辑非运算符`!`、逻辑或运算符`||`、逻辑与运算符`&&`的运算对象。 -类类型向bool的类型转换通常用在条件部分,因此`operator bool`一般被定义为显式的。 +类类型向`bool`的类型转换通常用在条件部分,因此`operator bool`一般被定义为显式的。 ### 避免有二义性的类型转换(Avoiding Ambiguous Conversions) 在两种情况下可能产生多重转换路径: -- A类定义了一个接受B类对象的转换构造函数,同时B类定义了一个转换目标是A类的类型转换运算符。 +- *A*类定义了一个接受*B*类对象的转换构造函数,同时*B*类定义了一个转换目标是*A*类的类型转换运算符。 ```c++ // usually a bad idea to have mutual conversions between two class types diff --git a/Chapter-15/Image/15-1.png b/Chapter-15 Object-Oriented Programming/Image/15-1.png similarity index 100% rename from Chapter-15/Image/15-1.png rename to Chapter-15 Object-Oriented Programming/Image/15-1.png diff --git a/Chapter-15/README.md b/Chapter-15 Object-Oriented Programming/README.md similarity index 87% rename from Chapter-15/README.md rename to Chapter-15 Object-Oriented Programming/README.md index 0be1455..a99dba2 100644 --- a/Chapter-15/README.md +++ b/Chapter-15 Object-Oriented Programming/README.md @@ -37,7 +37,7 @@ public: 基类通常都应该定义一个虚析构函数,即使该函数不执行任何实际操作也是如此。 -除构造函数之外的任何非静态函数都能定义为虚函数。virtual关键字只能出现在类内部的声明语句之前而不能用于类外部的函数定义。如果基类把一个函数声明为虚函数,则该函数在派生类中隐式地也是虚函数。 +除构造函数之外的任何非静态函数都能定义为虚函数。`virtual`关键字只能出现在类内部的声明语句之前而不能用于类外部的函数定义。如果基类把一个函数声明为虚函数,则该函数在派生类中隐式地也是虚函数。 成员函数如果没有被声明为虚函数,则其解析过程发生在编译阶段而非运行阶段。 @@ -114,9 +114,9 @@ Bulk_quote* bulkP = &base; // error: can't convert base to derived Bulk_quote& bulkRef = base; // error: can't convert base to derived ``` -如果在基类中含有一个或多个虚函数,可以使用dynamic_cast运算符,用于将基类的指针或引用安全地转换成派生类的指针或引用,该转换的安全检查将在运行期间执行。 +如果在基类中含有一个或多个虚函数,可以使用`dynamic_cast`运算符,用于将基类的指针或引用安全地转换成派生类的指针或引用,该转换的安全检查将在运行期间执行。 -如果已知某个基类到派生类的转换是安全的,可以使用static_cast强制覆盖掉编译器的检查工作。 +如果已知某个基类到派生类的转换是安全的,可以使用`static_cast`强制覆盖掉编译器的检查工作。 派生类到基类的自动类型转换只对指针或引用有效,在派生类类型和基类类型之间不存在这种转换。 @@ -134,13 +134,13 @@ item = bulk; // calls Quote::operator=(const Quote&) 当且仅当通过指针或引用调用虚函数时,才会在运行过程解析该调用,也只有在这种情况下对象的动态类型有可能与静态类型不同。 -在派生类中覆盖某个虚函数时,可以再次使用virtual关键字说明函数性质,但这并非强制要求。因为一旦某个函数被声明为虚函数,则在所有派生类中它都是虚函数。 +在派生类中覆盖某个虚函数时,可以再次使用`virtual`关键字说明函数性质,但这并非强制要求。因为一旦某个函数被声明为虚函数,则在所有派生类中它都是虚函数。 在派生类中覆盖某个虚函数时,该函数在基类中的形参必须与派生类中的形参严格匹配。 派生类可以定义一个与基类中的虚函数名字相同但形参列表不同的函数,但编译器会认为该函数与基类中原有的函数是相互独立的,此时派生类的函数并没有覆盖掉基类中的版本。 -C++11允许派生类使用`override`关键字显式地注明虚函数。如果override标记了某个函数,但该函数并没有覆盖已存在的虚函数,编译器将报告错误。override位于函数参数列表之后。 +C++11允许派生类使用`override`关键字显式地注明虚函数。如果`override`标记了某个函数,但该函数并没有覆盖已存在的虚函数,编译器将报告错误。`override`位于函数参数列表之后。 ```c++ struct B @@ -159,7 +159,7 @@ struct D1 : B } ``` -与禁止类继承类似,函数也可以通过添加final关键字来禁止覆盖操作。 +与禁止类继承类似,函数也可以通过添加`final`关键字来禁止覆盖操作。 ```c++ struct D2 : B @@ -169,7 +169,7 @@ struct D2 : B }; ``` -final和override关键字出现在形参列表(包括任何const或引用修饰符)以及尾置返回类型之后。 +`final`和`override`关键字出现在形参列表(包括任何`const`或引用修饰符)以及尾置返回类型之后。 虚函数也可以有默认实参,每次函数调用的默认实参值由本次调用的静态类型决定。如果通过基类的指针或引用调用函数,则使用基类中定义的默认实参,即使实际运行的是派生类中的函数版本也是如此。 @@ -208,7 +208,7 @@ double net_price(std::size_t) const = 0; 一个类可以使用`protected`关键字来声明外部代码无法访问,但是派生类对象可以访问的成员。 -派生类的成员或友元只能通过派生类对象来访问基类的protected成员。派生类对于一个基类对象中的protected成员没有任何访问权限。 +派生类的成员或友元只能通过派生类对象来访问基类的`protected`成员。派生类对于一个基类对象中的`protected`成员没有任何访问权限。 ```c++ class Base @@ -269,7 +269,7 @@ public: 友元关系不能继承,每个类负责控制各自成员的访问权限。 -使用using声明可以改变派生类继承的某个名字的访问级别。新的访问级别由该using声明之前的访问说明符决定。 +使用`using`声明可以改变派生类继承的某个名字的访问级别。新的访问级别由该`using`声明之前的访问说明符决定。 ```c++ class Base @@ -290,9 +290,9 @@ protected: }; ``` -派生类只能为那些它可以访问的名字提供using声明。 +派生类只能为那些它可以访问的名字提供`using`声明。 -默认情况下,使用class关键字定义的派生类是私有继承的,而使用struct关键字定义的派生类是公有继承的。 +默认情况下,使用`class`关键字定义的派生类是私有继承的,而使用`struct`关键字定义的派生类是公有继承的。 建议显式地声明派生类的继承方式,不要仅仅依赖于默认设置。 @@ -335,7 +335,7 @@ struct Derived : Base 派生类可以覆盖重载函数的0个或多个实例。如果派生类希望所有的重载版本对它来说都是可见的,那么它就需要覆盖所有版本,或者一个也不覆盖。 -有时一个类仅需覆盖重载集合中的一些而非全部函数,此时如果我们不得不覆盖基类中的每一个版本的话,操作会极其繁琐。为了简化操作,可以为重载成员提供using声明。using声明指定了一个函数名字但不指定形参列表,所以一条基类成员函数的using声明语句就可以把该函数的所有重载实例添加到派生类作用域中。 +有时一个类仅需覆盖重载集合中的一些而非全部函数,此时如果我们不得不覆盖基类中的每一个版本的话,操作会极其繁琐。为了简化操作,可以为重载成员提供`using`声明。`using`声明指定了一个函数名字但不指定形参列表,所以一条基类成员函数的`using`声明语句就可以把该函数的所有重载实例添加到派生类作用域中。 ```c++ class Base @@ -366,7 +366,7 @@ public: ![15-1](Image/15-1.png) -类内使用using声明改变访问级别的规则同样适用于重载函数的名字。 +类内使用`using`声明改变访问级别的规则同样适用于重载函数的名字。 ## 构造函数与拷贝控制(Constructors and Copy Control) @@ -385,7 +385,7 @@ public: }; ``` -如果基类的析构函数不是虚函数,则delete一个指向派生类对象的基类指针会产生未定义的结果。 +如果基类的析构函数不是虚函数,则`delete`一个指向派生类对象的基类指针会产生未定义的结果。 ```c++ Quote *itemP = new Quote; // same static and dynamic type @@ -404,7 +404,7 @@ delete itemP; // destructor for Bulk_quote called - 如果基类中的默认构造函数、拷贝构造函数、拷贝赋值运算符或析构函数是被删除的或者不可访问的函数,则派生类中对应的成员也会是被删除的。因为编译器不能使用基类成员来执行派生类对象中基类部分的构造、赋值或销毁操作。 - 如果基类的析构函数是被删除的或者不可访问的,则派生类中合成的默认和拷贝构造函数也会是被删除的。因为编译器无法销毁派生类对象中的基类部分。 -- 编译器不会合成一个被删除的移动操作。当我们使用=default请求一个移动操作时,如果基类中对应的操作是被删除的或者不可访问的,则派生类中的操作也会是被删除的。因为派生类对象中的基类部分不能移动。同样,如果基类的析构函数是被删除的或者不可访问的,则派生类的移动构造函数也会是被删除的。 +- 编译器不会合成一个被删除的移动操作。当我们使用`=default`请求一个移动操作时,如果基类中对应的操作是被删除的或者不可访问的,则派生类中的操作也会是被删除的。因为派生类对象中的基类部分不能移动。同样,如果基类的析构函数是被删除的或者不可访问的,则派生类的移动构造函数也会是被删除的。 在实际编程中,如果基类没有默认、拷贝或移动构造函数,则一般情况下派生类也不会定义相应的操作。 @@ -466,7 +466,7 @@ public: ### 继承的构造函数(Inherited Constructors) -C++11新标准允许派生类重用(非常规方式继承)其直接基类定义的构造函数。继承方式是提供一条注明了直接基类名的using声明语句。 +C++11新标准允许派生类重用(非常规方式继承)其直接基类定义的构造函数。继承方式是提供一条注明了直接基类名的`using`声明语句。 ```c++ class Bulk_quote : public Disc_quote @@ -477,9 +477,9 @@ public: }; ``` -通常情况下,using声明语句只是令某个名字在当前作用域内可见。而作用于构造函数时,using声明将令编译器产生代码。对于基类的每个构造函数,编译器都会生成一个与其形参列表完全相同的派生类构造函数。如果派生类含有自己的数据成员,则这些成员会被默认初始化。 +通常情况下,`using`声明语句只是令某个名字在当前作用域内可见。而作用于构造函数时,`using`声明将令编译器产生代码。对于基类的每个构造函数,编译器都会生成一个与其形参列表完全相同的派生类构造函数。如果派生类含有自己的数据成员,则这些成员会被默认初始化。 -构造函数的using声明不会改变该函数的访问级别,不能指定explicit或constexpr属性。 +构造函数的`using`声明不会改变该函数的访问级别,不能指定`explicit`或`constexpr`属性。 定义在派生类中的构造函数会替换继承而来的具有相同形参列表的构造函数。 diff --git a/Chapter-16/Image/16-1.png b/Chapter-16 Templates and Generic Programming/Image/16-1.png similarity index 100% rename from Chapter-16/Image/16-1.png rename to Chapter-16 Templates and Generic Programming/Image/16-1.png diff --git a/Chapter-16/README.md b/Chapter-16 Templates and Generic Programming/README.md similarity index 87% rename from Chapter-16/README.md rename to Chapter-16 Templates and Generic Programming/README.md index 21c2b07..389e75d 100644 --- a/Chapter-16/README.md +++ b/Chapter-16 Templates and Generic Programming/README.md @@ -50,7 +50,7 @@ template T calc(const T&, const U&); template calc (const T&, const U&); ``` -建议使用typename而不是class来指定模板类型参数,这样更加直观。 +建议使用`typename`而不是`class`来指定模板类型参数,这样更加直观。 模板非类型参数(nontype parameter)需要用特定的类型名来指定,表示一个值而非一个类型。非类型参数可以是整型、指向对象或函数类型的指针或左值引用。 @@ -66,7 +66,7 @@ int compare(const char (&p1)[3], const char (&p2)[4]); 绑定到整型非类型参数的实参必须是一个常量表达式。绑定到指针或引用非类型参数的实参必须具有静态的生存期,不能用普通局部变量或动态对象作为指针或引用非类型参数的实参。 -函数模板也可以声明为inline或constexpr的,说明符放在模板参数列表之后,返回类型之前。 +函数模板也可以声明为`inline`或`constexpr`的,说明符放在模板参数列表之后,返回类型之前。 ```c++ // ok: inline specifier follows the template parameter list @@ -129,7 +129,7 @@ Blob prices; // different element type 如果一个类模板中的代码使用了另一个模板,通常不会将一个实际类型(或值)的名字用作其模板实参,而是将模板自己的参数用作被使用模板的实参。 -类模板的成员函数具有和类模板相同的模板参数,因此定义在类模板外的成员函数必须以关键字template开始,后跟类模板参数列表。 +类模板的成员函数具有和类模板相同的模板参数,因此定义在类模板外的成员函数必须以关键字`template`开始,后跟类模板参数列表。 ```c++ template @@ -221,14 +221,14 @@ class Bar }; ``` -C++11允许使用using为类模板定义类型别名。 +C++11允许使用`using`为类模板定义类型别名。 ```c++ template using twin = pair; twin authors; // authors is a pair ``` -类模板可以声明static成员。 +类模板可以声明`static`成员。 ```c++ template @@ -247,7 +247,7 @@ Foo fs; Foo fi, fi2, fi3; ``` -类模板的每个实例都有一个独有的static对象,而每个static成员必须有且只有一个定义。因此与定义模板的成员函数类似,static成员也应该定义成模板。 +类模板的每个实例都有一个独有的`static`对象,而每个`static`成员必须有且只有一个定义。因此与定义模板的成员函数类似,`static`成员也应该定义成模板。 ```c++ template @@ -274,9 +274,9 @@ void f(A a, B b) 一个特定文件所需要的所有模板声明通常一起放置在文件开始位置,出现在任何使用这些模板的代码之前。 -模板中的代码使用作用域运算符`::`时,编译器无法确定其访问的名字是类型还是static成员。 +模板中的代码使用作用域运算符`::`时,编译器无法确定其访问的名字是类型还是`static`成员。 -默认情况下,C++假定模板中通过作用域运算符访问的名字是static成员。因此,如果需要使用一个模板类型参数的类型成员,就必须使用关键字`typename`显式地告知编译器该名字是一个类型。 +默认情况下,C++假定模板中通过作用域运算符访问的名字是`static`成员。因此,如果需要使用一个模板类型参数的类型成员,就必须使用关键字`typename`显式地告知编译器该名字是一个类型。 ```c++ template @@ -374,7 +374,7 @@ extern template declaration; // instantiation declaration template declaration; // instantiation definition ``` -*declaration*是一个类或函数声明,其中所有模板参数已被替换为模板实参。当编译器遇到extern模板声明时,它不会在本文件中生成实例化代码。对于一个给定的实例化版本,可能有多个extern声明,但必须只有一个定义。 +*declaration*是一个类或函数声明,其中所有模板参数已被替换为模板实参。当编译器遇到`extern`模板声明时,它不会在本文件中生成实例化代码。对于一个给定的实例化版本,可能有多个`extern`声明,但必须只有一个定义。 ```c++ // templateBuild.cc @@ -398,7 +398,7 @@ int i = compare(a1[0], a2[0]); // instantiation will appear elsewhere ### 效率与灵活性(Efficiency and Flexibility) -unique_ptr在编译时绑定删除器,避免了间接调用删除器的运行时开销。shared_ptr在运行时绑定删除器,使用户重载删除器的操作更加简便。 +`unique_ptr`在编译时绑定删除器,避免了间接调用删除器的运行时开销。`shared_ptr`在运行时绑定删除器,使用户重载删除器的操作更加简便。 ## 模板实参推断(Template Argument Deduction) @@ -410,8 +410,8 @@ unique_ptr在编译时绑定删除器,避免了间接调用删除器的运行 有3种类型转换可以在调用中应用于函数模板: -- 顶层const会被忽略。 -- 可以将一个非const对象的引用或指针传递给一个const引用或指针形参。 +- 顶层`const`会被忽略。 +- 可以将一个非`const`对象的引用或指针传递给一个`const`引用或指针形参。 - 如果函数形参不是引用类型,则可以对数组或函数类型的实参应用正常的指针转换。数组实参可以转换为指向其首元素的指针。函数实参可以转换为该函数类型的指针。 其他的类型转换,如算术转换、派生类向基类的转换以及用户定义的转换,都不能应用于函数模板。 @@ -505,11 +505,11 @@ auto fcn(It beg, It end) -> decltype(*beg) } ``` -标准库在头文件*type_traits*中定义了类型转换模板,这些模板常用于模板元程序设计。其中每个模板都有一个名为`type`的公有类型成员,表示一个类型。此类型与模板自身的模板类型参数相关。如果不可能(或不必要)转换模板参数,则type成员就是模板参数类型本身。 +标准库在头文件*type_traits*中定义了类型转换模板,这些模板常用于模板元程序设计。其中每个模板都有一个名为`type`的公有类型成员,表示一个类型。此类型与模板自身的模板类型参数相关。如果不可能(或不必要)转换模板参数,则`type`成员就是模板参数类型本身。 ![16-1](Image/16-1.png) -使用`remove_reference`可以获得引用对象的元素类型,如果用一个引用类型实例化remove_reference,则type表示被引用的类型。因为type是一个类的类型成员,所以在模板中必须使用关键字typename来告知编译器其表示一个类型。 +使用`remove_reference`可以获得引用对象的元素类型,如果用一个引用类型实例化`remove_reference`,则`type`表示被引用的类型。因为`type`是一个类的类型成员,所以在模板中必须使用关键字`typename`来告知编译器其表示一个类型。 ```c++ // must use typename to use a type member of a template parameter @@ -544,7 +544,7 @@ func(compare); // passing compare(const int&, const int&) ### 模板实参推断和引用(Template Argument Deduction and References) -当一个函数参数是模板类型参数的普通(左值)引用(形如*T&*)时,只能传递给它一个左值(如一个变量或一个返回引用类型的表达式)。*T*被推断为实参所引用的类型,如果实参是const的,则*T*也为const类型。 +当一个函数参数是模板类型参数的普通(左值)引用(形如`T&`)时,只能传递给它一个左值(如一个变量或一个返回引用类型的表达式)。*T*被推断为实参所引用的类型,如果实参是`const`的,则*T*也为`const`类型。 ```c++ template void f1(T&); // argument must be an lvalue @@ -554,7 +554,7 @@ f1(ci); // ci is a const int; template parameter T is const int f1(5); // error: argument to a & parameter must be an lvalue ``` -当一个函数参数是模板类型参数的常量引用(形如*const T&*)时,可以传递给它任何类型的实参。函数参数本身是const时,*T*的类型推断结果不会是const类型。const已经是函数参数类型的一部分了,因此不会再是模板参数类型的一部分。 +当一个函数参数是模板类型参数的常量引用(形如`const T&`)时,可以传递给它任何类型的实参。函数参数本身是`const`时,*T*的类型推断结果不会是`const`类型。`const`已经是函数参数类型的一部分了,因此不会再是模板参数类型的一部分。 ```c++ template void f2(const T&); // can take an rvalue @@ -565,7 +565,7 @@ f2(ci); // ci is a const int, but template parameter T is int f2(5); // a const & parameter can be bound to an rvalue; T is int ``` -当一个函数参数是模板类型参数的右值引用(形如*T&&*)时,如果传递给它一个右值,类型推断过程类似普通左值引用函数参数的推断过程,推断出的*T*类型是该右值实参的类型。 +当一个函数参数是模板类型参数的右值引用(形如`T&&`)时,如果传递给它一个右值,类型推断过程类似普通左值引用函数参数的推断过程,推断出的*T*类型是该右值实参的类型。 ```c++ template void f3(T&&); @@ -580,8 +580,8 @@ f3(42); // argument is an rvalue of type int; template parameter T is int | 折叠前 | 折叠后 | | :----------------------: | :----: | - | *T& &*、*T& &&*、*T&& &* | *T&* | - | *T&& &&* | *T&&* | + | `T& &`、`T& &&`、`T&& &` | `T&` | + | `T&& &&` | `T&&` | ```c++ f3(i); // argument is an lvalue; template parameter T is int& @@ -618,7 +618,7 @@ template void f(const T&); // lvalues and const rvalues ### 理解std::move(Understanding std::move) -std::move的定义如下: +`std::move`的定义如下: ```c++ template @@ -628,7 +628,7 @@ typename remove_reference::type&& move(T&& t) } ``` -std::move的工作过程: +`std::move`的工作过程: ```c++ string s1("hi!"), s2; @@ -638,25 +638,25 @@ s2 = std::move(s1); // ok: but after the assigment s1 has indeterminate valu - 在`std::move(string("bye!"))`中传递的是右值。 -- - 推断出的*T*类型为string。 - - remove_reference用string进行实例化。 - - remove_reference\的type成员是string。 - - move的返回类型是string&&。 - - move的函数参数t的类型为string&&。 +- - 推断出的*T*类型为`string`。 + - `remove_reference`用`string`进行实例化。 + - `remove_reference`的`type`成员是`string`。 + - `move`的返回类型是`string&&`。 + - `move`的函数参数*t*的类型为`string&&`。 - 在`std::move(s1)`中传递的是左值。 -- - 推断出的*T*类型为string&。 - - remove_reference用string&进行实例化。 - - remove_reference\的type成员是string。 - - move的返回类型是string&&。 - - move的函数参数t的类型为string& &&,会折叠成string&。 +- - 推断出的*T*类型为`string&`。 + - `remove_reference`用`string&`进行实例化。 + - `remove_reference`的`type`成员是`string`。 + - `move`的返回类型是`string&&`。 + - `move`的函数参数t的类型为`string& &&`,会折叠成`string&`。 -可以使用static_cast显式地将一个左值转换为一个右值引用。 +可以使用`static_cast`显式地将一个左值转换为一个右值引用。 ### 转发(Forwarding) -某些函数需要将其一个或多个实参连同类型不变地转发给其他函数。在这种情况下,需要保持被转发实参的所有性质,包括实参的const属性以及左值/右值属性。 +某些函数需要将其一个或多个实参连同类型不变地转发给其他函数。在这种情况下,需要保持被转发实参的所有性质,包括实参的`const`属性以及左值/右值属性。 ```c++ template @@ -674,7 +674,7 @@ f(42, i); // f changes its argument i flip1(f, j, 42); // f called through flip1 leaves j unchanged ``` -将函数参数定义为指向模板类型参数的右值引用(形如*T&&*),通过引用折叠,可以保持翻转实参的左值/右值属性。并且引用参数(无论是左值还是右值)可以保持实参的const属性,因为在引用类型中的const是底层的。 +将函数参数定义为指向模板类型参数的右值引用(形如`T&&`),通过引用折叠,可以保持翻转实参的左值/右值属性。并且引用参数(无论是左值还是右值)可以保持实参的`const`属性,因为在引用类型中的`const`是底层的。 ```c++ template @@ -696,9 +696,9 @@ void g(int &&i, int& j) flip2(g, i, 42); ``` -C++11在头文件*utility*中定义了`forward`。与move不同,forward必须通过显式模板实参调用,返回该显式实参类型的右值引用。即forward\<*T*\>返回类型*T&&*。 +C++11在头文件*utility*中定义了`forward`。与`move`不同,`forward`必须通过显式模板实参调用,返回该显式实参类型的右值引用。即`forward`返回类型`T&&`。 -通常情况下,可以使用forward传递定义为指向模板类型参数的右值引用函数参数。通过其返回类型上的引用折叠,forward可以保持给定实参的左值/右值属性。 +通常情况下,可以使用`forward`传递定义为指向模板类型参数的右值引用函数参数。通过其返回类型上的引用折叠,`forward`可以保持给定实参的左值/右值属性。 ```c++ template @@ -715,7 +715,7 @@ void flip(F f, T1 &&t1, T2 &&t2) } ``` -与std::move一样,对std::forward也不应该使用using声明。 +与`std::move`一样,对`std::forward`也不应该使用`using`声明。 ## 重载与模板(Overloading and Templates) @@ -724,14 +724,10 @@ void flip(F f, T1 &&t1, T2 &&t2) 如果重载涉及函数模板,则函数匹配规则会受到一些影响: - 对于一个调用,其候选函数包括所有模板实参推断成功的函数模板实例。 - - 候选的函数模板都是可行的,因为模板实参推断会排除任何不可行的模板。 - - 和往常一样,可行函数(模板与非模板)按照类型转换(如果需要的话)来排序。但是可以用于函数模板调用的类型转换非常有限。 - - 和往常一样,如果恰有一个函数提供比其他任何函数都更好的匹配,则选择此函数。但是如果多个函数都提供相同级别的匹配,则: - -- - 如果同级别的函数中只有一个是非模板函数,则选择此函数。 + - 如果同级别的函数中只有一个是非模板函数,则选择此函数。 - 如果同级别的函数中没有非模板函数,而有多个函数模板,且其中一个模板比其他模板更特例化,则选择此模板。 - 否则该调用有歧义。 @@ -772,7 +768,7 @@ void foo(const T &t, const Args& ... rest); 对于一个可变参数模板,编译器会推断模板参数类型和参数数量。 -可以使用`sizeof…`运算符获取参数包中的元素数量。类似sizeof,sizeof…也返回一个常量表达式,而且不会对其实参求值。 +可以使用`sizeof…`运算符获取参数包中的元素数量。类似`sizeof`,`sizeof…`也返回一个常量表达式,而且不会对其实参求值。 ```c++ template @@ -805,11 +801,11 @@ ostream &print(ostream &os, const T &t, const Args&... rest) } ``` -| Call | t | rest... | -| :-------------------: | :---------: | :---------: | -| print(cout, i, s, 42) | i | s, 42 | -| print(cout, s, 42) | s | 42 | -| print(cout, 42) | nonvariadic | nonvariadic | +| Call | t | rest... | +| :---------------------: | :--: | :-----: | +| `print(cout, i, s, 42)` | i | s, 42 | +| `print(cout, s, 42)` | s | 42 | +| `print(cout, 42)` | | | ### 包扩展(Pack Expansion) @@ -826,7 +822,7 @@ ostream& print(ostream &os, const T &t, const Args&... rest) // expand Args } ``` -- 第一个扩展操作扩展模板参数包,为print生成函数参数列表。编译器将模式*const Args&*应用到模板参数包*Args*中的每个元素上。因此该模式的扩展结果是一个以逗号分隔的零个或多个类型的列表,每个类型都形如*const type&*。 +- 第一个扩展操作扩展模板参数包,为`print`生成函数参数列表。编译器将模式`const Args&`应用到模板参数包*Args*中的每个元素上。因此该模式的扩展结果是一个以逗号分隔的零个或多个类型的列表,每个类型都形如`const type&`。 ```c++ print(cout, i, s, 42); // two parameters in the pack @@ -856,7 +852,7 @@ print(os, debug_rep(rest...)); // error: no matching function to call ### 转发参数包(Forwarding Parameter Packs) -在C++11中,可以组合使用可变参数模板和forward机制来编写函数,实现将其实参不变地传递给其他函数。 +在C++11中,可以组合使用可变参数模板和`forward`机制来编写函数,实现将其实参不变地传递给其他函数。 ```c++ // fun has zero or more parameters each of which is @@ -892,7 +888,7 @@ int compare(const char* const &p1, const char* const &p2) } ``` -特例化一个函数模板时,必须为模板中的每个模板参数都提供实参。为了指明我们正在实例化一个模板,应该在关键字template后面添加一个空尖括号对`<>`。 +特例化一个函数模板时,必须为模板中的每个模板参数都提供实参。为了指明我们正在实例化一个模板,应该在关键字`template`后面添加一个空尖括号对`<>`。 特例化版本的参数类型必须与一个先前声明的模板中对应的类型相匹配。 diff --git a/Chapter-17/Image/17-1.png b/Chapter-17 Specialized Library Facilities/Image/17-1.png similarity index 100% rename from Chapter-17/Image/17-1.png rename to Chapter-17 Specialized Library Facilities/Image/17-1.png diff --git a/Chapter-17/Image/17-10.png b/Chapter-17 Specialized Library Facilities/Image/17-10.png similarity index 100% rename from Chapter-17/Image/17-10.png rename to Chapter-17 Specialized Library Facilities/Image/17-10.png diff --git a/Chapter-17/Image/17-11.png b/Chapter-17 Specialized Library Facilities/Image/17-11.png similarity index 100% rename from Chapter-17/Image/17-11.png rename to Chapter-17 Specialized Library Facilities/Image/17-11.png diff --git a/Chapter-17/Image/17-12.png b/Chapter-17 Specialized Library Facilities/Image/17-12.png similarity index 100% rename from Chapter-17/Image/17-12.png rename to Chapter-17 Specialized Library Facilities/Image/17-12.png diff --git a/Chapter-17/Image/17-13.png b/Chapter-17 Specialized Library Facilities/Image/17-13.png similarity index 100% rename from Chapter-17/Image/17-13.png rename to Chapter-17 Specialized Library Facilities/Image/17-13.png diff --git a/Chapter-17/Image/17-14.png b/Chapter-17 Specialized Library Facilities/Image/17-14.png similarity index 100% rename from Chapter-17/Image/17-14.png rename to Chapter-17 Specialized Library Facilities/Image/17-14.png diff --git a/Chapter-17/Image/17-15.png b/Chapter-17 Specialized Library Facilities/Image/17-15.png similarity index 100% rename from Chapter-17/Image/17-15.png rename to Chapter-17 Specialized Library Facilities/Image/17-15.png diff --git a/Chapter-17/Image/17-16.png b/Chapter-17 Specialized Library Facilities/Image/17-16.png similarity index 100% rename from Chapter-17/Image/17-16.png rename to Chapter-17 Specialized Library Facilities/Image/17-16.png diff --git a/Chapter-17/Image/17-17.png b/Chapter-17 Specialized Library Facilities/Image/17-17.png similarity index 100% rename from Chapter-17/Image/17-17.png rename to Chapter-17 Specialized Library Facilities/Image/17-17.png diff --git a/Chapter-17/Image/17-18.png b/Chapter-17 Specialized Library Facilities/Image/17-18.png similarity index 100% rename from Chapter-17/Image/17-18.png rename to Chapter-17 Specialized Library Facilities/Image/17-18.png diff --git a/Chapter-17/Image/17-19.png b/Chapter-17 Specialized Library Facilities/Image/17-19.png similarity index 100% rename from Chapter-17/Image/17-19.png rename to Chapter-17 Specialized Library Facilities/Image/17-19.png diff --git a/Chapter-17/Image/17-2.png b/Chapter-17 Specialized Library Facilities/Image/17-2.png similarity index 100% rename from Chapter-17/Image/17-2.png rename to Chapter-17 Specialized Library Facilities/Image/17-2.png diff --git a/Chapter-17/Image/17-20.png b/Chapter-17 Specialized Library Facilities/Image/17-20.png similarity index 100% rename from Chapter-17/Image/17-20.png rename to Chapter-17 Specialized Library Facilities/Image/17-20.png diff --git a/Chapter-17/Image/17-21.png b/Chapter-17 Specialized Library Facilities/Image/17-21.png similarity index 100% rename from Chapter-17/Image/17-21.png rename to Chapter-17 Specialized Library Facilities/Image/17-21.png diff --git a/Chapter-17/Image/17-22.png b/Chapter-17 Specialized Library Facilities/Image/17-22.png similarity index 100% rename from Chapter-17/Image/17-22.png rename to Chapter-17 Specialized Library Facilities/Image/17-22.png diff --git a/Chapter-17/Image/17-23.png b/Chapter-17 Specialized Library Facilities/Image/17-23.png similarity index 100% rename from Chapter-17/Image/17-23.png rename to Chapter-17 Specialized Library Facilities/Image/17-23.png diff --git a/Chapter-17/Image/17-24.png b/Chapter-17 Specialized Library Facilities/Image/17-24.png similarity index 100% rename from Chapter-17/Image/17-24.png rename to Chapter-17 Specialized Library Facilities/Image/17-24.png diff --git a/Chapter-17/Image/17-3.png b/Chapter-17 Specialized Library Facilities/Image/17-3.png similarity index 100% rename from Chapter-17/Image/17-3.png rename to Chapter-17 Specialized Library Facilities/Image/17-3.png diff --git a/Chapter-17/Image/17-4.png b/Chapter-17 Specialized Library Facilities/Image/17-4.png similarity index 100% rename from Chapter-17/Image/17-4.png rename to Chapter-17 Specialized Library Facilities/Image/17-4.png diff --git a/Chapter-17/Image/17-5.png b/Chapter-17 Specialized Library Facilities/Image/17-5.png similarity index 100% rename from Chapter-17/Image/17-5.png rename to Chapter-17 Specialized Library Facilities/Image/17-5.png diff --git a/Chapter-17/Image/17-6.png b/Chapter-17 Specialized Library Facilities/Image/17-6.png similarity index 100% rename from Chapter-17/Image/17-6.png rename to Chapter-17 Specialized Library Facilities/Image/17-6.png diff --git a/Chapter-17/Image/17-7.png b/Chapter-17 Specialized Library Facilities/Image/17-7.png similarity index 100% rename from Chapter-17/Image/17-7.png rename to Chapter-17 Specialized Library Facilities/Image/17-7.png diff --git a/Chapter-17/Image/17-8.png b/Chapter-17 Specialized Library Facilities/Image/17-8.png similarity index 100% rename from Chapter-17/Image/17-8.png rename to Chapter-17 Specialized Library Facilities/Image/17-8.png diff --git a/Chapter-17/Image/17-9.png b/Chapter-17 Specialized Library Facilities/Image/17-9.png similarity index 100% rename from Chapter-17/Image/17-9.png rename to Chapter-17 Specialized Library Facilities/Image/17-9.png diff --git a/Chapter-17/README.md b/Chapter-17 Specialized Library Facilities/README.md similarity index 62% rename from Chapter-17/README.md rename to Chapter-17 Specialized Library Facilities/README.md index e1a756b..0ab594c 100644 --- a/Chapter-17/README.md +++ b/Chapter-17 Specialized Library Facilities/README.md @@ -2,27 +2,27 @@ ## tuple类型(The tuple Type) -`tuple`是类似pair的模板,定义在头文件*tuple*中。与pair不同,tuple可以有任意数量的成员。如果希望将一些数据组合成单一对象,但又不想定义新数据结构时,可以使用tuple(“快速而随意”的数据结构)。 +`tuple`是类似`pair`的模板,定义在头文件*tuple*中。与`pair`不同,`tuple`可以有任意数量的成员。如果希望将一些数据组合成单一对象,但又不想定义新数据结构时,可以使用`tuple`(“快速而随意”的数据结构)。 ![17-1](Image/17-1.png) ### 定义和初始化tuple(Defining and Initializing tuples) -定义tuple时需要指定每个成员的类型。创建tuple对象时,可以使用tuple的默认构造函数,它会对每个成员进行值初始化。或者给每个成员提供初始值。包含初始值的构造函数是explicit的,因此必须使用直接初始化语法。 +定义`tuple`时需要指定每个成员的类型。创建`tuple`对象时,可以使用`tuple`的默认构造函数,它会对每个成员进行值初始化。或者给每个成员提供初始值。包含初始值的构造函数是`explicit`的,因此必须使用直接初始化语法。 ```c++ tuple threeD = { 1, 2, 3 }; // error tuple threeD{ 1, 2, 3 }; // ok ``` -类似make_pair,`make_tuple`函数可以生成tuple对象。tuple的类型由初始值决定。 +类似`make_pair`,`make_tuple`函数可以生成`tuple`对象。`tuple`的类型由初始值决定。 ```c++ // tuple that represents a bookstore transaction: ISBN, count, price per book auto item = make_tuple("0-999-78345-X", 3, 20.00); ``` -可以使用`get`访问tuple的成员。get是一个函数模板,使用时必须指定一个显式模板实参,表示要访问的成员索引。传递给get一个tuple实参后,会返回其指定成员的引用。 +可以使用`get`访问`tuple`的成员。`get`是一个函数模板,使用时必须指定一个显式模板实参,表示要访问的成员索引。传递给`get`一个`tuple`实参后,会返回其指定成员的引用。 ```c++ auto book = get<0>(item); // returns the first member of item @@ -31,12 +31,12 @@ auto price = get<2>(item)/cnt; // returns the last member of item get<2>(item) *= 0.8; // apply 20% discount ``` -可以使用`tuple_size`和`tuple_element`这两个辅助类模板查询tuple成员的数量和类型。 +可以使用`tuple_size`和`tuple_element`这两个辅助类模板查询`tuple`成员的数量和类型。 -- tuple_size通过一个tuple类型来初始化,它有一个名为value的静态公有数据成员,类型为size_t,表示给定tuple中成员的数量。 -- tuple_element通过一个索引值(整型常量)和一个tuple类型来初始化,它有一个名为type的公有数据成员,表示给定tuple中指定成员的类型。 +- `tuple_size`通过一个`tuple`类型来初始化,它有一个名为`value`的静态公有数据成员,类型为`size_t`,表示给定`tuple`中成员的数量。 +- `tuple_element`通过一个索引值(整型常量)和一个`tuple`类型来初始化,它有一个名为`type`的公有数据成员,表示给定`tuple`中指定成员的类型。 -使用decltype可以确定一个对象的类型。 +使用`decltype`可以确定一个对象的类型。 ```c++ typedef decltype(item) trans; // trans is the type of item @@ -46,25 +46,25 @@ size_t sz = tuple_size::value; // returns 3 tuple_element<1, trans>::type cnt = get<1>(item); // cnt is an int ``` -tuple的关系和相等运算符逐对比较两个tuple对象的成员。只有当两个tuple的成员数量相等时才可以进行比较。使用tuple的相等或不等运算符时,每对成员必须支持`==`运算符;使用tuple的关系运算符时,每对成员必须支持`<`运算符。 +`tuple`的关系和相等运算符逐对比较两个`tuple`对象的成员。只有当两个`tuple`的成员数量相等时才可以进行比较。使用`tuple`的相等或不等运算符时,每对成员必须支持`==`运算符;使用`tuple`的关系运算符时,每对成员必须支持`<`运算符。 -由于tuple定义了`<`和`==`运算符,因此tuple序列可以被传递给算法,无序容器的关键字也可以使用tuple类型。 +由于`tuple`定义了`<`和`==`运算符,因此`tuple`序列可以被传递给算法,无序容器的关键字也可以使用`tuple`类型。 ### 使用tuple返回多个值(Using a tuple to Return Multiple Values) -tuple的一个常见用途是从一个函数返回多个值。 +`tuple`的一个常见用途是从一个函数返回多个值。 ## bitset类型(The bitset Type) -标准库在头文件*bitset*中定义了`bitset`类,用于处理二进制位。bitset可以处理超过最长整型类型大小的位集合。 +标准库在头文件*bitset*中定义了`bitset`类,用于处理二进制位。`bitset`可以处理超过最长整型类型大小的位集合。 ### 定义和初始化bitset(Defining and Initializing bitsets) -bitset类是一个模板,类似array,具有固定的大小。定义一个bitset时需要指明它包含的二进制位数。 +`bitset`类是一个模板,类似`array`,具有固定的大小。定义一个`bitset`时需要指明它包含的二进制位数。 ![17-2](Image/17-2.png) -使用一个整型值初始化bitset时,此值会被转换为unsigned long long类型并被当作位模式处理。bitset中的二进制位就是此模式的副本。如果bitset的大小大于unsigned long long中的二进制位数,剩余的高位会被置为0。如果bitset的大小小于unsigned long long中的二进制位数,则只使用给定值的低位部分。 +使用一个整型值初始化`bitset`时,此值会被转换为`unsigned long long`类型并被当作位模式处理。`bitset`中的二进制位就是此模式的副本。如果`bitset`的大小大于`unsigned long long`中的二进制位数,剩余的高位会被置为0。如果`bitset`的大小小于`unsigned long long`中的二进制位数,则只使用给定值的低位部分。 ```c++ // bitvec1 is smaller than the initializer; high-order bits from the initializer are discarded @@ -75,7 +75,7 @@ bitset<20> bitvec2(0xbeef); // bits are 00001011111011101111 bitset<128> bitvec3(~0ULL); // bits 0 ... 63 are one; 63 ... 127 are zero ``` -可以使用string或字符数组指针来初始化bitset,字符直接表示位模式。使用字符串表示数时,字符串中下标最小的字符对应bitset的高位。如果string包含的字符数比bitset少,则bitset的高位被置为0。 +可以使用`string`或字符数组指针来初始化`bitset`,字符直接表示位模式。使用字符串表示数时,字符串中下标最小的字符对应`bitset`的高位。如果`string`包含的字符数比`bitset`少,则`bitset`的高位被置为0。 ```c++ bitset<32> bitvec4("1100"); // bits 2 and 3 are 1, all others are 0 @@ -88,27 +88,27 @@ bitset<32> bitvec6(str, str.size()-4); // use last four characters ### bitset操作(Operations on bitsets) -bitset操作: +`bitset`操作: ![17-4](Image/17-4.png) -bitset的下标运算符对const属性进行了重载。const版本的下标运算符在指定位置置位时返回true,否则返回false。非const版本返回bitset定义的一个特殊类型,用来控制指定位置的值。 +`bitset`的下标运算符对`const`属性进行了重载。`const`版本的下标运算符在指定位置置位时返回`true`,否则返回`false`。非`const`版本返回`bitset`定义的一个特殊类型,用来控制指定位置的值。 -`to_ulong`和`to_ullong`操作用来提取bitset的值。只有当bitset的大小不大于对应操作的返回值大(to_ulong为unsigned long,to_ullong为unsigned long long)时,才能使用这两个操作。如果bitset中的值不能存入给定类型,则会引发overflow_error异常。 +`to_ulong`和`to_ullong`操作用来提取`bitset`的值。只有当`bitset`的大小不大于对应操作的返回值(`to_ulong`为`unsigned long`,`to_ullong`为`unsigned long long`)时,才能使用这两个操作。如果`bitset`中的值不能存入给定类型,则会引发`overflow_error`异常。 ```c++ unsigned long ulong = bitvec3.to_ulong(); cout << "ulong = " << ulong << endl; ``` -bitset的输入运算符从输入流读取字符,保存到临时的string对象中。遇到下列情况时停止读取: +`bitset`的输入运算符从输入流读取字符,保存到临时的`string`对象中。遇到下列情况时停止读取: -- 读取的字符数达到对应bitset的大小。 +- 读取的字符数达到对应`bitset`的大小。 - 遇到不是1和0的字符。 - 遇到文件结尾。 - 输入出现错误。 -读取结束后用临时string对象初始化bitset。如果读取的字符数小于bitset的大小,则bitset的高位被置为0。 +读取结束后用临时`string`对象初始化`bitset`。如果读取的字符数小于`bitset`的大小,则`bitset`的高位被置为0。 ## 正则表达式(Regular Expressions) @@ -132,40 +132,40 @@ if (regex_search(test_str, results, r)) // if there is a match cout << results.str() << endl; // print the matching word ``` -`regex_match`和`regex_search`函数确定一个给定的字符序列与一个regex是否匹配。如果整个输入序列与表达式匹配,则regex_match函数返回true;如果输入序列中的一个子串与表达式匹配,则regex_search函数返回true。这两个函数的其中一个重载版本接受一个类型为`smatch`的附加参数。如果匹配成功,函数会将匹配信息保存在给定的smatch对象中。二者的参数形式如下: +`regex_match`和`regex_search`函数确定一个给定的字符序列与一个`regex`是否匹配。如果整个输入序列与表达式匹配,则`regex_match`函数返回`true`;如果输入序列中的一个子串与表达式匹配,则`regex_search`函数返回`true`。这两个函数的其中一个重载版本接受一个类型为`smatch`的附加参数。如果匹配成功,函数会将匹配信息保存在给定的`smatch`对象中。二者的参数形式如下: ![17-6](Image/17-6.png) ### 使用正则表达式库(Using the Regular Expression Library) -默认情况下,regex使用的正则表达式语言是ECMAScript。 +默认情况下,`regex`使用的正则表达式语言是ECMAScript。 -定义一个regex或者对一个regex调用assign为其赋新值时,可以指定一些标志来影响regex的操作。`ECMAScript`、`basic`、`extended`、`awk`、`grep`和`egrep`这六个标志指定编写正则表达式时所使用的语言。这六个标志中必须设置其中之一,且只能设置一个。默认情况下,ECMAScript标志被设置,regex会使用ECMA-262规范,这也是很多Web浏览器使用的正则表达式语言。 +定义一个`regex`或者对一个`regex`调用`assign`为其赋新值时,可以指定一些标志来影响`regex`的操作。`ECMAScript`、`basic`、`extended`、`awk`、`grep`和`egrep`这六个标志指定编写正则表达式时所使用的语言。这六个标志中必须设置其中之一,且只能设置一个。默认情况下,`ECMAScript`标志被设置,`regex`会使用ECMA-262规范,这也是很多Web浏览器使用的正则表达式语言。 ![17-7](Image/17-7.png) -正则表达式的语法是否正确是在运行期间解析的。如果正则表达式存在错误,标准库会抛出类型为`regex_error`的异常。除了what操作外,regex_error还有一个名为`code`的成员,用来返回错误类型对应的数值编码。code返回的值是由具体实现定义的。RE库能抛出的标准错误如下,code返回对应错误的编号(从0开始)。 +正则表达式的语法是否正确是在运行期间解析的。如果正则表达式存在错误,标准库会抛出类型为`regex_error`的异常。除了`what`操作外,`regex_error`还有一个名为`code`的成员,用来返回错误类型对应的数值编码。`code`返回的值是由具体实现定义的。RE库能抛出的标准错误如下,`code`返回对应错误的编号(从0开始)。 ![17-8](Image/17-8.png) -正则表达式在程序运行时才编译,这是一个非常慢的操作。因此构造一个regex对象或者给一个已经存在的regex赋值是很耗时间的。为了最小化这种开销,应该尽量避免创建不必要的regex。特别是在循环中使用正则表达式时,应该在循环体外部创建regex对象。 +正则表达式在程序运行时才编译,这是一个非常慢的操作。因此构造一个`regex`对象或者给一个已经存在的`regex`赋值是很耗时间的。为了最小化这种开销,应该尽量避免创建不必要的`regex`。特别是在循环中使用正则表达式时,应该在循环体外部创建`regex`对象。 RE库为不同的输入序列都定义了对应的类型。使用时RE库类型必须与输入类型匹配。 -- regex类保存char类型的正则表达式;`wregex`保存wchar_t类型的正则表达式。 -- smatch表示string类型的输入序列;`cmatch`表示字符数组类型的输入序列;`wsmatch`表示wstring类型的输入序列;`wcmatch`表示宽字符数组类型的输入序列。 +- `regex`类保存`char`类型的正则表达式;`wregex`保存`wchar_t`类型的正则表达式。 +- `smatch`表示`string`类型的输入序列;`cmatch`表示字符数组类型的输入序列;`wsmatch`表示`wstring`类型的输入序列;`wcmatch`表示宽字符数组类型的输入序列。 ![17-9](Image/17-9.png) ### 匹配与Regex迭代器类型(The Match and Regex Iterator Types) -regex迭代器是一种迭代器适配器,它被绑定到一个输入序列和一个regex对象上,每种输入类型都有对应的迭代器类型。 +`regex`迭代器是一种迭代器适配器,它被绑定到一个输入序列和一个`regex`对象上,每种输入类型都有对应的迭代器类型。 `sregex_iterator`操作: ![17-10](Image/17-10.png) -以sregex_iterator为例,将sregex_iterator绑定到一个string和一个regex对象时,迭代器自动定位至给定string中的第一个匹配位置。即,sregex_iterator构造函数对给定string和regex调用regex_search。解引用迭代器时,返回最近一次搜索结果的smatch对象。递增迭代器时,它调用regex_search在输入string中查找下一个匹配位置。 +以`sregex_iterator`为例,将`sregex_iterator`绑定到一个`string`和一个`regex`对象时,迭代器自动定位至给定`string`中的第一个匹配位置。即,`sregex_iterator`构造函数对给定`string`和`regex`调用`regex_search`。解引用迭代器时,返回最近一次搜索结果的`smatch`对象。递增迭代器时,它调用`regex_search`在输入`string`中查找下一个匹配位置。 ```c++ // find the characters ei that follow a character other than c @@ -181,7 +181,7 @@ for (sregex_iterator it(file.begin(), file.end(), r), end_it; ![17-11](Image/17-11.png) -匹配类型有两个名为`prefix`和`suffix`的成员,分别返回表示输入序列中当前匹配之前和之后部分的`ssub_match`对象。一个ssub_match对象有两个名为`str`和`length`的成员,分别返回匹配的string和该string的长度。 +匹配类型有两个名为`prefix`和`suffix`的成员,分别返回表示输入序列中当前匹配之前和之后部分的`ssub_match`对象。一个`ssub_match`对象有两个名为`str`和`length`的成员,分别返回匹配的`string`和该`string`的长度。 ```c++ // same for loop header as before @@ -199,7 +199,7 @@ for (sregex_iterator it(file.begin(), file.end(), r), end_it; ![17-12](Image/17-12.png) -smatch支持的操作: +`smatch`支持的操作: ![17-13](Image/17-13.png) @@ -239,7 +239,7 @@ ECMAScript正则表达式语言的一些特性: ![17-15](Image/17-15.png) -标准库定义了用于在正则表达式替换过程中控制匹配或格式的标志。这些标志可以传递给regex_search、regex_match函数或者smatch类的format成员。匹配和格式化标志的类型为`match_flag_type`,定义在命名空间*regex_constants*中。由于*regex_constants*定义在*std*中,因此在使用这些名字时,需要同时加上两个命名空间的限定符。 +标准库定义了用于在正则表达式替换过程中控制匹配或格式的标志。这些标志可以传递给`regex_search`、`regex_match`函数或者`smatch`类的`format`成员。匹配和格式化标志的类型为`match_flag_type`,定义在命名空间*regex_constants*中。由于*regex_constants*定义在*std*中,因此在使用这些名字时,需要同时加上两个命名空间的限定符。 ![17-16](Image/17-16.png) @@ -256,15 +256,15 @@ cout << regex_replace(s, r, fmt2, format_no_copy) << endl; 在新标准出现之前,C和C++都依赖于一个简单的C库函数`rand`来生成随机数。该函数生成均匀分布的伪随机整数,每个随机数的范围在0和一个系统相关的最大值(至少为32767)之间。 -头文件*random*中的随机数库定义了一组类来解决rand函数的一些问题:随机数引擎类(random-number engines)可以生成unsigned随机数序列;随机数分布类(random-number distribution classes)使用引擎类生成指定类型、范围和概率分布的随机数。 +头文件*random*中的随机数库定义了一组类来解决`rand`函数的一些问题:随机数引擎类(random-number engines)可以生成`unsigned`随机数序列;随机数分布类(random-number distribution classes)使用引擎类生成指定类型、范围和概率分布的随机数。 ![17-17](Image/17-17.png) -C++程序不应该使用rand函数,而应该使用`default_random_engine`类和恰当的分布类对象。 +C++程序不应该使用`rand`函数,而应该使用`default_random_engine`类和恰当的分布类对象。 ### 随机数引擎和分布(Random-Number Engines and Distribution) -随机数引擎是函数对象类,定义了一个不接受参数的调用运算符,返回一个随机unsigned整数。调用一个随机数引擎对象可以生成原始随机数。 +随机数引擎是函数对象类,定义了一个不接受参数的调用运算符,返回一个随机`unsigned`整数。调用一个随机数引擎对象可以生成原始随机数。 ```c++ default_random_engine e; // generates random unsigned integers @@ -273,7 +273,7 @@ for (size_t i = 0; i < 10; ++i) cout << e() << " "; ``` -标准库定义了多个随机数引擎类,区别在于性能和随机性质量。每个编译器都会指定其中一个作为default_random_engine类型,此类型一般具有最常用的特性。 +标准库定义了多个随机数引擎类,区别在于性能和随机性质量。每个编译器都会指定其中一个作为`default_random_engine`类型,此类型一般具有最常用的特性。 随机数引擎操作: @@ -281,7 +281,7 @@ for (size_t i = 0; i < 10; ++i) 大多数情况下,随机数引擎的输出是不能直接使用的,因为生成的随机数范围通常与程序所需要的不符。 -使用分布类对象可以得到指定范围的随机数。新标准库的`uniform_int_distribution`类型生成均匀分布的unsigned值。 +使用分布类对象可以得到指定范围的随机数。新标准库的`uniform_int_distribution`类型生成均匀分布的`unsigned`值。 ```c++ // uniformly distributed from 0 to 9 inclusive @@ -297,11 +297,11 @@ for (size_t i = 0; i < 10; ++i) 随机数发生器指分布对象和引擎对象的组合。 -rand函数的生成范围在0到`RAND_MAX`之间,随机数引擎生成的unsigned整数在一个系统定义的范围内。一个引擎类型的范围可以通过调用该类型对象的`min`和`max`成员来获得。 +`rand`函数的生成范围在0到`RAND_MAX`之间,随机数引擎生成的`unsigned`整数在一个系统定义的范围内。一个引擎类型的范围可以通过调用该类型对象的`min`和`max`成员来获得。 即使随机数发生器生成的数看起来是随机的,但对于一个给定的发生器,每次运行程序时它都会返回相同的数值序列。 -如果函数需要局部的随机数发生器,应该将其(包括引擎和分布对象)定义为static对象,这样随机数发生器就能在函数调用期间保持状态。否则每次调用函数都会生成相同的序列。 +如果函数需要局部的随机数发生器,应该将其(包括引擎和分布对象)定义为`static`对象,这样随机数发生器就能在函数调用期间保持状态。否则每次调用函数都会生成相同的序列。 ```c++ // returns a vector of 100 uniformly distributed random numbers @@ -332,17 +332,17 @@ default_random_engine e3; // uses the default seed value e3.seed(32767); // call seed to set a new seed value ``` -选择种子的常用方法是调用系统函数`time`。该函数定义在头文件*ctime*中,返回从一个特定时刻到当前经过的秒数。time函数接受单个指针参数,指向用于写入时间的数据结构。如果指针为空,则函数简单地返回时间。 +选择种子的常用方法是调用系统函数`time`。该函数定义在头文件*ctime*中,返回从一个特定时刻到当前经过的秒数。`time`函数接受单个指针参数,指向用于写入时间的数据结构。如果指针为空,则函数简单地返回时间。 ```c++ default_random_engine e1(time(0)); // a somewhat random seed ``` -由于time函数返回以秒计算的时间,因此用time返回值作为种子的方式只适用于生成种子的间隔为秒级或更长时间的应用。另外如果程序作为一个自动过程的一部分反复运行,这种方式也会无效,可能多次使用的是相同的种子。 +由于`time`函数返回以秒计算的时间,因此用`time`返回值作为种子的方式只适用于生成种子的间隔为秒级或更长时间的应用。另外如果程序作为一个自动过程的一部分反复运行,这种方式也会无效,可能多次使用的是相同的种子。 ### 其他随机数分布(Other Kinds of Distributions) -从rand函数获得随机浮点数的一个常用但不正确的方法是用rand()的结果除以*RAND_MAX*。但因为随机整数的精度通常低于随机浮点数,所以使用这种方法时,有一些浮点值永远不会被生成。 +从`rand`函数获得随机浮点数的一个常用但不正确的方法是用`rand`的结果除以`RAND_MAX`。但因为随机整数的精度通常低于随机浮点数,所以使用这种方法时,有一些浮点值永远不会被生成。 使用新标准库的`uniform_real_distribution`类型可以获得随机浮点数。 @@ -358,16 +358,16 @@ for (size_t i = 0; i < 10; ++i) ![17-19](Image/17-19.png) -除了总是生成bool类型的`bernouilli_distribution`外,其他分布类型都是模板。每个模板都接受单个类型参数,指定分布生成的结果类型。 +除了总是生成`bool`类型的`bernouilli_distribution`外,其他分布类型都是模板。每个模板都接受单个类型参数,指定分布生成的结果类型。 -分布类型限制了可以作为模板类型的参数类型,一些模板只能生成浮点数,而其他模板只能生成整数。分布类型还定义了一个默认模板类型参数,整型分布的默认参数是int,浮点数分布的默认参数是double。使用默认类型时应该在模板名后使用空尖括号。 +分布类型限制了可以作为模板类型的参数类型,一些模板只能生成浮点数,而其他模板只能生成整数。分布类型还定义了一个默认模板类型参数,整型分布的默认参数是`int`,浮点数分布的默认参数是`double`。使用默认类型时应该在模板名后使用空尖括号。 ```c++ // empty <> signify we want to use the default result type uniform_real_distribution<> u(0,1); // generates double by default ``` -bernouilli_distribution类型是一个普通类,而非模板。该分布返回一个bool值,其中true的概率是一个常数,默认为0.5。 +`bernouilli_distribution`类型是一个普通类,而非模板。该分布返回一个`bool`值,其中`true`的概率是一个常数,默认为0.5。 由于引擎会返回相同的随机数序列,因此需要在循环中使用引擎时,必须在循环体外定义引擎对象。否则每次循环都会创建新引擎,生成相同序列。同样,分布对象也需要保持运行状态,也必须在循环体外定义。 @@ -375,7 +375,7 @@ bernouilli_distribution类型是一个普通类,而非模板。该分布返回 ### 格式化输入与输出(Formatted Input and Output) -除了条件状态外,每个iostream对象还维护着一个格式状态来控制IO格式化细节。 +除了条件状态外,每个`iostream`对象还维护着一个格式状态来控制IO格式化细节。 标准库定义了一组操纵符(manipulator)来修改流的格式状态。操纵符是一个函数或对象,会影响流的状态,并能作为输入和输出运算符的运算对象。类似输入和输出运算符,操纵符也返回它所处理的流对象。 @@ -383,7 +383,7 @@ bernouilli_distribution类型是一个普通类,而非模板。该分布返回 操纵符改变流的格式状态时,通常改变后的状态对所有后续IO都生效。大多数改变格式状态的操纵符都是设置/复原成对的,一个操纵符用于设置新格式,另一个用于恢复正常格式。 -默认情况下,bool值输出为1(true)或0(false)。对流使用`boolalpha`操纵符可以输出true或false,还原格式时使用`noboolalpha`操纵符。 +默认情况下,`bool`值输出为1(`true`)或0(`false`)。对流使用`boolalpha`操纵符可以输出`true`或`false`,还原格式时使用`noboolalpha`操纵符。 ```c++ cout << "default bool values: " << true << " " << false @@ -416,9 +416,9 @@ hex: 14 400 decimal: 20 1024 ``` -hex、oct和dec操纵符只影响整型运算对象,浮点值的表示形式不受影响。 +`hex`、`oct`和`dec`操纵符只影响整型运算对象,浮点值的表示形式不受影响。 -默认情况下,在输出数值时,没有可见的标识指出当前使用的进制模式。如果需要输出八进制或十六进制值,应该使用`showbase`操纵符。对流应用showbase后,在输出结果中会显示进制,显示模式和指定整型常量进制的规范相同。 +默认情况下,在输出数值时,没有可见的标识指出当前使用的进制模式。如果需要输出八进制或十六进制值,应该使用`showbase`操纵符。对流应用`showbase`后,在输出结果中会显示进制,显示模式和指定整型常量进制的规范相同。 - 前导`0x`表示十六进制。 - 前导`0`表示八进制。 @@ -470,10 +470,10 @@ printed in hexadecimal: 0X14 0X400 调用IO对象的`precision`成员或者使用`setprecision`操纵符可以改变精度。 -- precision成员是重载的。一个版本接受一个int值,将精度设置为此值,并返回旧精度值。另一个版本不接受参数,直接返回当前精度值。 -- setprecision操纵符接受一个参数来设置精度。 +- `precision`成员是重载的。一个版本接受一个`int`值,将精度设置为此值,并返回旧精度值。另一个版本不接受参数,直接返回当前精度值。 +- `setprecision`操纵符接受一个参数来设置精度。 -setprecision操纵符和其他接受参数的操纵符都定义在头文件*iomanip*中。 +`setprecision`操纵符和其他接受参数的操纵符都定义在头文件*iomanip*中。 ```c++ // cout.precision reports the current precision value @@ -528,7 +528,7 @@ hexadecimal: 0x1.1ad7bcp+7 use defaults: 141.421 ``` -scientific、fixed和hexfloat操纵符会改变流的精度含义。执行这些操纵符后,精度控制的将是小数点后面的数字位数,而默认情况下控制的是数字总位数。 +`scientific`、`fixed`和`hexfloat`操纵符会改变流的精度含义。执行这些操纵符后,精度控制的将是小数点后面的数字位数,而默认情况下控制的是数字总位数。 默认情况下,当浮点值的小数部分为0时,不显示小数点。使用`showpoint`操纵符可以强制输出小数点,`noshowpoint`操纵符还原默认行为。 @@ -546,7 +546,7 @@ cout << showpoint << 10.0 // prints 10.0000 - `internal`控制负数的符号位置,它左对齐符号,右对齐值,中间空间用空格填充。 - `setfill`指定一个字符代替默认的空格进行补白。 -setw类似endl,不改变输出流的内部状态,只影响下一次输出的大小。 +`setw`类似`endl`,不改变输出流的内部状态,只影响下一次输出的大小。 ```c++ int i = -16; @@ -621,12 +621,12 @@ while (cin.get(ch)) 有时读取完一个字符后才发现目前无法处理该字符,希望将其放回流中。标准库提供了三种方法退回字符。 - `peek`返回输入流中下一个字符的副本,但不会将其从流中删除。 -- `unget`使输入流向后移动,令最后读取的值回到流中。即使不知道最后从流中读取了什么值,也可以调用unget。 -- `putback`是特殊版本的unget,它退回从流中读取的最后一个值,但它接受一个参数,该参数必须与最后读取的值相同。 +- `unget`使输入流向后移动,令最后读取的值回到流中。即使不知道最后从流中读取了什么值,也可以调用`unget`。 +- `putback`是特殊版本的`unget`,它退回从流中读取的最后一个值,但它接受一个参数,该参数必须与最后读取的值相同。 一般情况下,在读取下一个值之前,标准库保证程序可以退回最多一个值。 -peek和无参数的get函数都以int类型从输入流返回字符。这些函数使用int的原因是可以返回文件尾标记。char范围中的每个值都表示一个真实字符,因此没有额外的值可以表示文件尾。返回int的函数先将要返回的字符转换为unsigned char,再将结果提升为int。因此即使字符集中有字符映射到负值,返回的int也是正值。而标准库使用负值表示文件尾,这样就能保证文件尾与任何合法字符的值都不相同。头文件*cstdio*定义了一个名为`EOF`的常量值,可以用它检测函数返回的值是否是文件尾。 +`peek`和无参数的`get`函数都以`int`类型从输入流返回字符。这些函数使用`int`的原因是可以返回文件尾标记。`char`范围中的每个值都表示一个真实字符,因此没有额外的值可以表示文件尾。返回`int`的函数先将要返回的字符转换为`unsigned char`,再将结果提升为`int`。因此即使字符集中有字符映射到负值,返回的`int`也是正值。而标准库使用负值表示文件尾,这样就能保证文件尾与任何合法字符的值都不相同。头文件*cstdio*定义了一个名为`EOF`的常量值,可以用它检测函数返回的值是否是文件尾。 ```c++ int ch; // use an int, not a char to hold the return from get() @@ -635,7 +635,7 @@ while ((ch = cin.get()) != EOF) cout.put(ch); ``` -一个常见的编程错误是将get或peek函数的返回值赋给char而非int对象,但编译器不能发现这个错误。 +一个常见的编程错误是将`get`或`peek`函数的返回值赋给`char`而非`int`对象,但编译器不能发现这个错误。 ```c++ char ch; // using a char here invites disaster! @@ -644,25 +644,25 @@ while ((ch = cin.get()) != EOF) cout.put(ch); ``` -当get返回*EOF*时,该值会先被转换为unsigned char,之后提升得到的int值与*EOF*值不再相等,因此循环永远不会停止。 +当`get`返回`EOF`时,该值会先被转换为`unsigned char`,之后提升得到的`int`值与`EOF`值不再相等,因此循环永远不会停止。 一些未格式化IO操作一次处理大块数据,这些操作可以提高程序执行速度,但需要自己分配并管理用来保存和提取数据的字符数组。 ![17-23](Image/17-23.png) -get和`getline`函数接受相同的参数,它们的行为类似但不相同。两个函数都一直读取数据,直到遇到下列情况之一: +`get`和`getline`函数接受相同的参数,它们的行为类似但不相同。两个函数都一直读取数据,直到遇到下列情况之一: - 已经读取了*size - 1*个字符。 -- 遇到了文件尾(*EOF*)。 +- 遇到了文件尾(`EOF`)。 - 遇到了分隔符。 -两个函数的区别在于处理分隔符的方式:get将分隔符留在输入流中作为下一个字符,而getline读取并丢弃分隔符。两个函数都不会将分隔符保存在结果数组中。 +两个函数的区别在于处理分隔符的方式:`get`将分隔符留在输入流中作为下一个字符,而`getline`读取并丢弃分隔符。两个函数都不会将分隔符保存在结果数组中。 读取流数据时的一个常见错误是忘记从流中删除分隔符。 -一些操作可能从输入流中读取了未知个数的字节,使用`gcount`函数可以确定上一次未格式化输入操作读取了多少字符。gcount函数应该在任何后续未格式化输入操作前调用,将字符退回流的操作也属于未格式化输入操作。如果在调用gcount前使用了peek、unget或putback操作,则gcount的返回值为0。 +一些操作可能从输入流中读取了未知个数的字节,使用`gcount`函数可以确定上一次未格式化输入操作读取了多少字符。`gcount`函数应该在任何后续未格式化输入操作前调用,将字符退回流的操作也属于未格式化输入操作。如果在调用`gcount`前使用了`peek`、`unget`或`putback`操作,则`gcount`的返回值为0。 -使用`clear`、`ignore`和`sync`函数可以清空输入流中的数据。读到非法字符时,输入流将处于错误状态。为了继续获取输入数据,先调用clear函数重置流的错误标记。再调用ignore清空流中指定大小的数据,或者调用sync直接清空流中所有数据。`numeric_limits::max()`返回流的缓冲区大小。 +使用`clear`、`ignore`和`sync`函数可以清空输入流中的数据。读到非法字符时,输入流将处于错误状态。为了继续获取输入数据,先调用`clear`函数重置流的错误标记。再调用`ignore`清空流中指定大小的数据,或者调用`sync`直接清空流中所有数据。`numeric_limits::max()`返回流的缓冲区大小。 ```c++ // 重置错误标志 @@ -680,23 +680,23 @@ cin.ignore(numeric_limits::max(), '\n'); 随机IO本质上是依赖于操作系统的。 -为了支持随机访问,IO类型通过维护一个标记来确定下一次读写操作的位置。`seek`函数用于移动标记,`tell`函数用于获取标记。标准库实际上定义了两对seek和tell函数,一对用于输入流(后缀为`g`,表示get),一对用于输出流(后缀为`p`,表示put)。 +为了支持随机访问,IO类型通过维护一个标记来确定下一次读写操作的位置。`seek`函数用于移动标记,`tell`函数用于获取标记。标准库实际上定义了两对`seek`和`tell`函数,一对用于输入流(后缀为`g`,表示get),一对用于输出流(后缀为`p`,表示put)。 ![17-24](Image/17-24.png) -虽然标准库为所有流类型都定义了seek和tell函数,但它们是否有意义取决于流绑定到哪个设备。在大多数系统中,绑定到cin、cout、cerr和clog的流不支持随机访问。对这些流可以调用seek和tell函数,但在运行时会出现错误,流也会被置为无效状态。 +虽然标准库为所有流类型都定义了`seek`和`tell`函数,但它们是否有意义取决于流绑定到哪个设备。在大多数系统中,绑定到`cin`、`cout`、`cerr`和`clog`的流不支持随机访问。对这些流可以调用`seek`和`tell`函数,但在运行时会出现错误,流也会被置为无效状态。 -从逻辑上考虑,seek和tell函数的使用范围如下: +从逻辑上考虑,`seek`和`tell`函数的使用范围如下: -- 可以对istream、ifstream、istringstream类型使用*g*版本。 -- 可以对ostream、ofstream、ostringstream类型使用*p*版本。 -- 可以对iostream、fstream、stringstream类型使用*g*和*p*版本。 +- 可以对`istream`、`ifstream`、`istringstream`类型使用`g`版本。 +- 可以对`ostream`、`ofstream`、`ostringstream`类型使用`p`版本。 +- 可以对`iostream`、`fstream`、`stringstream`类型使用`g`和`p`版本。 -一个流中只有一个标记——不存在独立的读标记和写标记。fstream和stringstream类型可以读写同一个流。在这些类型中,有单一的缓冲区用于保存读写的数据,同时标记也只有一个,表示缓冲区中的当前位置。标准库将两个版本的seek和tell函数都映射到这个标记。 +一个流中只有一个标记——不存在独立的读标记和写标记。`fstream`和`stringstream`类型可以读写同一个流。在这些类型中,有单一的缓冲区用于保存读写的数据,同时标记也只有一个,表示缓冲区中的当前位置。标准库将两个版本的`seek`和`tell`函数都映射到这个标记。 -由于流中只有一个标记,因此在切换读写操作时,必须使用seek函数来重定位标记。 +由于流中只有一个标记,因此在切换读写操作时,必须使用`seek`函数来重定位标记。 -seek函数有两个重载版本:一个版本使用绝对地址移动流标记;另一个版本使用指定位置和偏移量移动流标记。 +`seek`函数有两个重载版本:一个版本使用绝对地址移动流标记;另一个版本使用指定位置和偏移量移动流标记。 ```c++ // set the marker to a fixed position @@ -707,6 +707,7 @@ seekg(offset, from); // set the read marker offset distance from from seekp(offset, from); // offset has type off_type ``` -参数*new_position*和*offset*的类型分别是`pos_type`和`off_type`,这两个类型都是机器相关的,定义在头文件*istream*和*ostream*中。pos_type表示文件位置,而off_type表示距离当前位置的偏移量,偏移量可以是正数也可以是负数。 +参数*new_position*和*offset*的类型分别是`pos_type`和`off_type`,这两个类型都是机器相关的,定义在头文件*istream*和*ostream*中。`pos_type`表示文件位置,而`off_type`表示距离当前位置的偏移量,偏移量可以是正数也可以是负数。 + +`tellg`和`tellp`函数返回一个`pos_type`值,表示流的当前位置。 -tellg和tellp函数返回一个pos_type值,表示流的当前位置。 \ No newline at end of file diff --git a/Chapter-18/Image/18-1.png b/Chapter-18 Tools for Large Programs/Image/18-1.png similarity index 100% rename from Chapter-18/Image/18-1.png rename to Chapter-18 Tools for Large Programs/Image/18-1.png diff --git a/Chapter-18/Image/18-2.png b/Chapter-18 Tools for Large Programs/Image/18-2.png similarity index 100% rename from Chapter-18/Image/18-2.png rename to Chapter-18 Tools for Large Programs/Image/18-2.png diff --git a/Chapter-18/Image/18-3.png b/Chapter-18 Tools for Large Programs/Image/18-3.png similarity index 100% rename from Chapter-18/Image/18-3.png rename to Chapter-18 Tools for Large Programs/Image/18-3.png diff --git a/Chapter-18/README.md b/Chapter-18 Tools for Large Programs/README.md similarity index 64% rename from Chapter-18/README.md rename to Chapter-18 Tools for Large Programs/README.md index 80cf5fd..a5142f1 100644 --- a/Chapter-18/README.md +++ b/Chapter-18 Tools for Large Programs/README.md @@ -8,48 +8,45 @@ 在C++中,通过抛出(throwing)一条表达式来引发(raised)一个异常。被抛出的表达式类型和当前的调用链共同决定了应该使用哪段处理代码(handler)来处理该异常。被选中的处理代码是在调用链中与抛出对象类型匹配且距离最近的代码。 -执行一个throw语句时,跟在throw后面的语句将不再执行。程序的控制权从throw转移到与之匹配的catch语句中。该catch可能是同一个函数中的局部catch,也可能位于直接或间接调用了发生异常的函数的另一个函数中。控制权的转移意味着两个问题: +执行一个`throw`语句时,跟在`throw`后面的语句将不再执行。程序的控制权从`throw`转移到与之匹配的`catch`语句中。该`catch`可能是同一个函数中的局部`catch`,也可能位于直接或间接调用了发生异常的函数的另一个函数中。控制权的转移意味着两个问题: - 沿着调用链的函数可能会提前退出。 - 一旦程序开始执行异常处理代码,则沿着调用链创建的对象会被销毁。 -抛出异常后,程序暂停执行当前函数并立即寻找对应catch语句的过程叫做栈展开(stack unwinding)。栈展开沿着嵌套函数的调用链不断查找,直到找到了与异常匹配的catch语句为止。如果没有对应的catch语句,则退出主函数后查找过程结束。 +抛出异常后,程序暂停执行当前函数并立即寻找对应`catch`语句的过程叫做栈展开(stack unwinding)。栈展开沿着嵌套函数的调用链不断查找,直到找到了与异常匹配的`catch`语句为止。如果没有对应的`catch`语句,则退出主函数后查找过程结束。 -- 如果找到了匹配的catch语句,则程序进入该子句并执行其中的代码。catch语句执行结束后,程序会转移到与try块关联的最后一个catch语句之后的位置继续执行。 -- 如果没有找到匹配的catch语句,程序会调用标准库的`terminate`函数,终止运行。 +- 如果找到了匹配的`catch`语句,则程序进入该子句并执行其中的代码。`catch`语句执行结束后,程序会转移到与`try`块关联的最后一个`catch`语句之后的位置继续执行。 +- 如果没有找到匹配的`catch`语句,程序会调用标准库的`terminate`函数,终止运行。 在栈展开过程中,位于调用链上的语句块可能会提前退出,其中的局部对象也会被销毁。如果异常发生在构造函数或者数组及容器的元素初始化过程中,则当前的对象可能只构造了一部分,此时必须确保已构造的成员能被正确销毁。 如果一个块分配了资源,并且在执行资源释放代码前发生了异常,则资源不会被释放。 -由于栈展开可能会调用析构函数,因此析构函数不应该抛出不能被它自身处理的异常。即,如果析构函数需要执行某个可能引发异常的操作,则该操作应该被放置在一个try语句块中,并在析构函数内部得到处理。实际编程中,析构函数仅仅是释放资源,不太可能引发异常。所有的标准库类型都能确保它们的析构函数不会引发异常。 +由于栈展开可能会调用析构函数,因此析构函数不应该抛出不能被它自身处理的异常。即,如果析构函数需要执行某个可能引发异常的操作,则该操作应该被放置在一个`try`语句块中,并在析构函数内部得到处理。实际编程中,析构函数仅仅是释放资源,不太可能引发异常。所有的标准库类型都能确保它们的析构函数不会引发异常。 -编译器使用异常抛出表达式对异常对象(exception object)进行拷贝初始化,因此throw语句中的表达式必须具有完全类型。如果该表达式是类类型,则相应的类必须含有可访问的析构函数和拷贝/移动构造函数。如果该表达式是数组或函数类型,则表达式会被转换成对应的指针类型。 +编译器使用异常抛出表达式对异常对象(exception object)进行拷贝初始化,因此`throw`语句中的表达式必须具有完全类型。如果该表达式是类类型,则相应的类必须含有可访问的析构函数和拷贝/移动构造函数。如果该表达式是数组或函数类型,则表达式会被转换成对应的指针类型。 -抛出一条表达式时,该表达式的静态编译类型决定了异常对象的类型。如果throw表达式解引用一个基类指针,而该指针实际指向派生类对象,则只有基类部分会被抛出。 +抛出一条表达式时,该表达式的静态编译类型决定了异常对象的类型。如果`throw`表达式解引用一个基类指针,而该指针实际指向派生类对象,则只有基类部分会被抛出。 抛出指针时必须确保在任何对应的处理代码中,指针指向的对象一定存在。 ### 捕获异常(Catching an Exception) -catch语句(catch clause)中的异常声明(exception declaration)类似只包含一个形参的函数形参列表。声明的类型决定了处理代码所能捕获的异常类型。该类型必须是完全类型,可以是左值引用,但不能是右值引用。如果catch无须访问抛出的表达式,则可以忽略捕获形参的名字。 +`catch`语句(catch clause)中的异常声明(exception declaration)类似只包含一个形参的函数形参列表。声明的类型决定了处理代码所能捕获的异常类型。该类型必须是完全类型,可以是左值引用,但不能是右值引用。如果`catch`无须访问抛出的表达式,则可以忽略捕获形参的名字。 -进入catch语句后,使用异常对象初始化异常声明中的参数。catch参数的特性和函数参数类似。 +进入`catch`语句后,使用异常对象初始化异常声明中的参数。`catch`参数的特性和函数参数类似。 -- 如果catch的参数类型是非引用类型,则该参数是异常对象的一个副本,改变参数不会影响异常对象本身。 +- 如果`catch`的参数类型是非引用类型,则该参数是异常对象的一个副本,改变参数不会影响异常对象本身。 +- 如果`catch`的参数类型是引用类型,则该参数是异常对象的一个别名,改变参数就是改变异常对象本身。 +- 在继承体系中,如果`catch`的参数类型是基类类型,则可以使用其派生类类型的异常对象对其初始化。 + - `catch`的参数是基类非引用类型时,异常对象会被切除一部分。 + - `catch`的参数是基类引用类型时,以常规方式绑定到异常对象。 -- 如果catch的参数类型是引用类型,则该参数是异常对象的一个别名,改变参数就是改变异常对象本身。 +异常声明的静态类型决定了`catch`语句所能执行的操作。如果`catch`的参数是基类类型,则无法使用派生类特有的成员。 -- 在继承体系中,如果catch的参数类型是基类类型,则可以使用其派生类类型的异常对象对其初始化。 +通常情况下,如果`catch`接受的异常与某个继承体系有关,则最好将`catch`参数定义为引用类型。 -- - catch的参数是基类非引用类型时,异常对象会被切除一部分。 - - catch的参数是基类引用类型时,以常规方式绑定到异常对象。 - -异常声明的静态类型决定了catch语句所能执行的操作。如果catch的参数是基类类型,则无法使用派生类特有的成员。 - -通常情况下,如果catch接受的异常与某个继承体系有关,则最好将catch参数定义为引用类型。 - -查找异常处理代码时,最终结果是第一个与异常匹配的catch语句,但这未必是最佳匹配。因此,越特殊的catch越应该位于整个catch列表的前端。当程序使用具有继承关系的异常时,派生类异常的处理代码应该位于基类异常的处理代码之前。 +查找异常处理代码时,最终结果是第一个与异常匹配的`catch`语句,但这未必是最佳匹配。因此,越特殊的`catch`越应该位于整个`catch`列表的前端。当程序使用具有继承关系的异常时,派生类异常的处理代码应该位于基类异常的处理代码之前。 异常和异常声明的匹配规则比函数参数严格,绝大多数类型转换都不能使用。 @@ -57,17 +54,17 @@ catch语句(catch clause)中的异常声明(exception declaration)类似 - 允许从派生类到基类的类型转换。 - 数组被转换成指向数组元素类型的指针,函数被转换成指向该函数类型的指针。 -除此之外,包括标准算术类型转换和类类型转换在内的其他所有转换规则都不能在catch匹配过程中使用。 +除此之外,包括标准算术类型转换和类类型转换在内的其他所有转换规则都不能在`catch`匹配过程中使用。 -有时一个单独的catch语句不能完整处理某个异常。执行完一些校正操作后,当前的catch可能会让位于调用链上层的函数继续处理异常。一个catch语句通过重新抛出(rethrowing)的操作将异常传递给另一个catch语句。重新抛出是一条不包含表达式的throw语句。 +有时一个单独的`catch`语句不能完整处理某个异常。执行完一些校正操作后,当前的`catch`可能会让位于调用链上层的函数继续处理异常。一个`catch`语句通过重新抛出(rethrowing)的操作将异常传递给另一个`catch`语句。重新抛出是一条不包含表达式的`throw`语句。 ```c++ throw; ``` -空throw语句只能出现在catch或catch语句调用的函数之内。如果在异常处理代码之外的区域遇到了空throw语句,编译器将调用terminate函数。 +空`throw`语句只能出现在`catch`或`catch`语句调用的函数之内。如果在异常处理代码之外的区域遇到了空`throw`语句,编译器将调用`terminate`函数。 -重新抛出语句不指定新的表达式,而是将当前的异常对象沿着调用链向上传递。如果catch语句修改了其参数并重新抛出异常,则只有当catch异常声明是引用类型时,程序对参数所做的改变才会被保留并继续传播。 +重新抛出语句不指定新的表达式,而是将当前的异常对象沿着调用链向上传递。如果`catch`语句修改了其参数并重新抛出异常,则只有当`catch`异常声明是引用类型时,程序对参数所做的改变才会被保留并继续传播。 ```c++ catch (my_error &eObj) @@ -98,13 +95,13 @@ catch (...) } ``` -catch(…)通常与重新抛出语句一起使用。 +`catch(…)`通常与重新抛出语句一起使用。 -如果catch(…)与其他catch语句一起使用,则catch(…)必须位于最后,否则catch(…)后面的catch语句永远不会被匹配。 +如果`catch(…)`与其他`catch`语句一起使用,则`catch(…)`必须位于最后,否则`catch(…)`后面的`catch`语句永远不会被匹配。 ### 函数try语句块与构造函数(Function try Blocks and Constructors) -要想处理构造函数初始值列表抛出的异常,必须将构造函数写成函数try语句块(function try block)的形式。函数try语句块使得一组catch语句可以同时处理构造函数体和构造函数初始化过程中的异常。 +要想处理构造函数初始值列表抛出的异常,必须将构造函数写成函数`try`语句块(function try block)的形式。函数`try`语句块使得一组`catch`语句可以同时处理构造函数体和构造函数初始化过程中的异常。 ```c++ template @@ -119,9 +116,9 @@ catch(const std::bad_alloc &e) } ``` -函数try语句块的catch语句会在结尾处隐式地重新抛出异常,通知上层函数对象构造失败。上层函数需要继续处理该异常。 +函数`try`语句块的`catch`语句会在结尾处隐式地重新抛出异常,通知上层函数对象构造失败。上层函数需要继续处理该异常。 -在初始化构造函数参数时发生的异常不属于函数try语句块处理的范围。 +在初始化构造函数参数时发生的异常不属于函数`try`语句块处理的范围。 ### noexcept异常说明(The noexcept Exception Specification) @@ -132,15 +129,15 @@ void recoup(int) noexcept; // won't throw void alloc(int); // might throw ``` -noexcept说明的出现位置: +`noexcept`说明的出现位置: -- 关键字noexcept位于函数的参数列表之后,尾置返回类型之前。 -- 对于一个函数来说,noexcept说明必须同时出现在该函数的所有声明和定义语句中。 -- 函数指针的声明和定义也可以指定noexcept。 -- 在typedef或类型别名中不能使用noexcept。 -- 在成员函数中,关键字noexcept位于const或引用限定符之后,final、override或虚函数的=0之前。 +- 关键字`noexcept`位于函数的参数列表之后,尾置返回类型之前。 +- 对于一个函数来说,`noexcept`说明必须同时出现在该函数的所有声明和定义语句中。 +- 函数指针的声明和定义也可以指定`noexcept`。 +- 在`typedef`或类型别名中不能使用`noexcept`。 +- 在成员函数中,关键字`noexcept`位于`const`或引用限定符之后,`final`、`override`或虚函数的`=0`之前。 -编译器并不会在编译时检查noexcept说明。如果一个函数在指定了noexcept的同时又含有throw语句或其他可能抛出异常的操作,仍然会通过编译(个别编译器可能会提出警告)。 +编译器并不会在编译时检查`noexcept`说明。如果一个函数在指定了`noexcept`的同时又含有`throw`语句或其他可能抛出异常的操作,仍然会通过编译(个别编译器可能会提出警告)。 ```c++ // this function will compile, even though it clearly violates its exception specification @@ -150,42 +147,42 @@ void f() noexcept // promises not to throw any exception } ``` -一旦noexcept函数抛出异常,程序会调用terminate函数终止运行(该过程是否执行栈展开未作规定)。因此noexcept可以用于两种情况: +一旦`noexcept`函数抛出异常,程序会调用`terminate`函数终止运行(该过程是否执行栈展开未作规定)。因此`noexcept`可以用于两种情况: - 确认函数不会抛出异常。 - 不知道该如何处理函数抛出的异常。 指明某个函数不会抛出异常可以让调用者不必再考虑异常处理操作。 -早期的C++版本设计了一套更详细的异常说明方案。函数可以使用一个关键字throw,后面跟上用括号包围的异常类型列表,用于指定函数可能抛出的异常类型。关键字throw出现的位置与C++11的noexcept相同。该方案在C++11中被取消。但如果一个函数被声明为throw()的,则也说明该函数不会抛出异常。 +早期的C++版本设计了一套更详细的异常说明方案。函数可以使用一个关键字`throw`,后面跟上用括号包围的异常类型列表,用于指定函数可能抛出的异常类型。关键字`throw`出现的位置与C++11的`noexcept`相同。该方案在C++11中被取消。但如果一个函数被声明为`throw()`的,则也说明该函数不会抛出异常。 ```c++ void recoup(int) noexcept; // recoup doesn't throw void recoup(int) throw(); // equivalent declaration ``` -noexcept说明符接受一个可选的实参,该实参必须能转换为bool类型。如果实参为true,则函数不会抛出异常;如果实参为false,则函数可能抛出异常。 +`noexcept`说明符接受一个可选的实参,该实参必须能转换为`bool`类型。如果实参为`true`,则函数不会抛出异常;如果实参为`false`,则函数可能抛出异常。 ```c++ void recoup(int) noexcept(true); // recoup won't throw void alloc(int) noexcept(false); // alloc can throw ``` -noexcept运算符(noexcept operator)是一个一元运算符,返回bool类型的右值常量表达式,表示给定的运算对象是否会抛出异常。和sizeof类似,noexcept运算符也不会对运算对象求值。 +`noexcept`运算符(noexcept operator)是一个一元运算符,返回`bool`类型的右值常量表达式,表示给定的运算对象是否会抛出异常。和`sizeof`类似,`noexcept`运算符也不会对运算对象求值。 ```c++ noexcept(e) ``` -当*e*调用的函数都含有noexcept说明且*e*本身不含有throw语句时,上述表达式返回true,否则返回false。 +当*e*调用的函数都含有`noexcept`说明且*e*本身不含有`throw`语句时,上述表达式返回`true`,否则返回`false`。 -noexcept运算符通常在noexcept说明符的实参中使用。 +`noexcept`运算符通常在`noexcept`说明符的实参中使用。 ```c++ void f() noexcept(noexcept(g())); // f has same exception specifier as g ``` -函数指针与该指针指向的函数必须具有一致的异常说明。如果某个函数指针是noexcept的,则该指针只能指向noexcept函数;如果显式或隐式地说明了函数指针可能抛出异常,则该指针可以指向任何函数。 +函数指针与该指针指向的函数必须具有一致的异常说明。如果某个函数指针是`noexcept`的,则该指针只能指向`noexcept`函数;如果显式或隐式地说明了函数指针可能抛出异常,则该指针可以指向任何函数。 ```c++ // both recoup and pf1 promise not to throw @@ -196,7 +193,7 @@ pf1 = alloc; // error: alloc might throw but pf1 said it wouldn't pf2 = alloc; // ok: both pf2 and alloc might throw ``` -如果一个虚函数是noexcept的,则后续派生出来的虚函数必须也是noexcept的。如果基类的虚函数允许抛出异常,则派生类的对应函数既可以允许,也可以禁止抛出异常。 +如果一个虚函数是`noexcept`的,则后续派生出来的虚函数必须也是`noexcept`的。如果基类的虚函数允许抛出异常,则派生类的对应函数既可以允许,也可以禁止抛出异常。 ```c++ class Base @@ -216,7 +213,7 @@ public: }; ``` -编译器合成拷贝控制成员时,也会生成一个异常声明。如果所有的成员和基类操作都含有noexcept说明,则合成成员也是noexcept的。 +编译器合成拷贝控制成员时,也会生成一个异常声明。如果所有的成员和基类操作都含有`noexcept`说明,则合成成员也是`noexcept`的。 ### 异常类层次(Exception Class Hierarchies) @@ -224,11 +221,11 @@ public: ![18-1](Image/18-1.png) -`exception`类型只定义了拷贝构造函数、拷贝赋值运算符、一个虚析构函数和一个名为`what`的虚成员。what函数返回一个const char*,指向一个以null结尾的字符数组,并且不会抛出异常。 +`exception`类型只定义了拷贝构造函数、拷贝赋值运算符、一个虚析构函数和一个名为`what`的虚成员。`what`函数返回一个`const char*`,指向一个以`NULL`结尾的字符数组,并且不会抛出异常。 -exception、`bad_cast`和`bad_alloc`类型定义了默认构造函数。`runtime_error`和`logic_error`类型没有默认构造函数,但是有一个接受C风格字符串或string类型实参的构造函数,该实参通常用于提供错误信息。what函数返回用于初始化异常对象的错误信息。 +`exception`、`bad_cast`和`bad_alloc`类型定义了默认构造函数。`runtime_error`和`logic_error`类型没有默认构造函数,但是有一个接受C风格字符串或`string`类型实参的构造函数,该实参通常用于提供错误信息。`what`函数返回用于初始化异常对象的错误信息。 -实际编程中通常会自定义exception(或者exception的标准库派生类)的派生类以扩展其继承体系。这些面向具体应用的异常类表示了与应用相关的异常状态。 +实际编程中通常会自定义`exception`(或者`exception`的标准库派生类)的派生类以扩展其继承体系。这些面向具体应用的异常类表示了与应用相关的异常状态。 ## 命名空间(Namespaces) @@ -300,7 +297,7 @@ namespace cplusplus_primer } ``` -通常情况下,#include不应该出现在命名空间内部。否则头文件中的所有名字都会被定义为该命名空间的成员。 +通常情况下,`#include`不应该出现在命名空间内部。否则头文件中的所有名字都会被定义为该命名空间的成员。 定义多个类型不相关的命名空间时应该使用单独的文件分别表示每个类型。 @@ -349,7 +346,7 @@ template <> struct std::hash 命名空间可以嵌套。嵌套的命名空间同时也是一个嵌套的作用域,它嵌套在外层命名空间的作用域内。内层命名空间声明的名字会隐藏外层命名空间的同名成员。在嵌套的命名空间中定义的名字只在内层命名空间中有效,外层命名空间中的代码在访问时需要在名字前添加限定符。 -C++11新增了内联命名空间(inline namespace)。和一般的嵌套命名空间不同,内联命名空间中的名字可以被外层命名空间直接使用。定义内联命名空间的方式是在namespace前添加关键字`inline`。inline必须出现在该命名空间第一次定义的地方。 +C++11新增了内联命名空间(inline namespace)。和一般的嵌套命名空间不同,内联命名空间中的名字可以被外层命名空间直接使用。定义内联命名空间的方式是在`namespace`前添加关键字`inline`。`inline`必须出现在该命名空间第一次定义的地方。 ```c++ inline namespace FifthEd @@ -383,7 +380,7 @@ namespace cplusplus_primer 因为*FifthEd*是内联的,所以形如`cplusplus_primer::`的代码可以直接获得*FifthEd*的成员。如果想使用早期版本,则必须加上完整的外层命名空间名字。 -未命名的命名空间(unnamed namespace)指关键字namespace后紧跟以花括号包围的一系列声明语句。未命名的命名空间中定义的变量拥有静态生命周期:它们在第一次使用前创建,直到程序结束才销毁。 +未命名的命名空间(unnamed namespace)指关键字`namespace`后紧跟以花括号包围的一系列声明语句。未命名的命名空间中定义的变量拥有静态生命周期:它们在第一次使用前创建,直到程序结束才销毁。 一个未命名的命名空间可以在某个给定的文件内不连续,但是不能跨越多个文件。每个文件定义自己的未命名的命名空间。如果一个头文件定义了未命名的命名空间,则该命名空间中定义的名字在每个包含该头文件的文件中对应不同实体。 @@ -411,11 +408,11 @@ namespace local local::i = 42; ``` -在标准C++引入命名空间的概念之前,程序需要将名字声明为static的以令其对整个文件有效。在文件中进行静态声明的做法是从C语言继承而来的。在C语言中,声明为static的全局实体在其所在的文件之外不可见。该做法已经被C++标准取消,现在应该使用未命名的命名空间。 +在标准C++引入命名空间的概念之前,程序需要将名字声明为`static`的以令其对整个文件有效。在文件中进行静态声明的做法是从C语言继承而来的。在C语言中,声明为`static`的全局实体在其所在的文件之外不可见。该做法已经被C++标准取消,现在应该使用未命名的命名空间。 ### 使用命名空间成员(Using Namespace Members) -可以使用关键字namespace和`=`为命名空间定义别名(namespace alias)。别名必须出现在命名空间的定义之后。 +可以使用关键字`namespace`和`=`为命名空间定义别名(namespace alias)。别名必须出现在命名空间的定义之后。 ```c++ namespace primer = cplusplus_primer; @@ -423,19 +420,19 @@ namespace primer = cplusplus_primer; 一个命名空间可以有多个别名,它们都与命名空间的原名等价。 -一条using声明(using declaration)一次只引入命名空间的一个成员。 +一条`using`声明(using declaration)一次只引入命名空间的一个成员。 -using声明的有效范围从using声明语句开始,一直到using声明所在的作用域结束为止。在此过程中,外层作用域的同名实体会被隐藏。未加限定的名字只能在using声明所在的作用域及其内层作用域中使用。 +`using`声明的有效范围从`using`声明语句开始,一直到`using`声明所在的作用域结束为止。在此过程中,外层作用域的同名实体会被隐藏。未加限定的名字只能在`using`声明所在的作用域及其内层作用域中使用。 -using声明可以出现在全局作用域、局部作用域、命名空间作用域和类的作用域中。在类的作用域中使用时,using声明只能指向基类成员。 +`using`声明可以出现在全局作用域、局部作用域、命名空间作用域和类的作用域中。在类的作用域中使用时,`using`声明只能指向基类成员。 -和using声明不同,using指示使某个命名空间中的所有名字都可见。 +和`using`声明不同,`using`指示使某个命名空间中的所有名字都可见。 -using指示可以出现在全局作用域、局部作用域和命名空间作用域中,不能出现在类的作用域中。 +`using`指示可以出现在全局作用域、局部作用域和命名空间作用域中,不能出现在类的作用域中。 -如果对*std*等命名空间使用了using指示而未做任何特殊控制的话,会重新引入多个库之间的名字冲突问题。 +如果对*std*等命名空间使用了`using`指示而未做任何特殊控制的话,会重新引入多个库之间的名字冲突问题。 - using指示具有将命名空间成员提升到包含命名空间本身和using指示的最近外层作用域的能力。 + `using`指示具有将命名空间成员提升到包含命名空间本身和`using`指示的最近外层作用域的能力。 ```c++ // namespace A and function f are defined at global scope @@ -476,14 +473,14 @@ void manip() } ``` -头文件如果在其顶层作用域中使用using声明或using指示,则会将名字注入到包含该头文件的所有文件中。通常,头文件只负责定义接口部分的名字,而不定义实现部分的名字。因此,头文件最多只能在它的函数或命名空间内使用using声明或using指示。 +头文件如果在其顶层作用域中使用`using`声明或`using`指示,则会将名字注入到包含该头文件的所有文件中。通常,头文件只负责定义接口部分的名字,而不定义实现部分的名字。因此,头文件最多只能在它的函数或命名空间内使用`using`声明或`using`指示。 -相比于使用using指示,在程序中对命名空间中的每个成员分别使用using声明效果更好。 +相比于使用`using`指示,在程序中对命名空间中的每个成员分别使用`using`声明效果更好。 -- 如果程序使用了多个不同的库,而这些库中的名字通过using指示变得可见,则全局命名空间污染问题将重新出现。 -- using指示引发的二义性错误只有在使用了冲突名字的地方才会被发现。而using声明引发的二义性错误在声明处就能发现。 +- 如果程序使用了多个不同的库,而这些库中的名字通过`using`指示变得可见,则全局命名空间污染问题将重新出现。 +- `using`指示引发的二义性错误只有在使用了冲突名字的地方才会被发现。而`using`声明引发的二义性错误在声明处就能发现。 -建议在命名空间本身的实现文件中使用using指示。 +建议在命名空间本身的实现文件中使用`using`指示。 ### 类、命名空间与作用域(Classes,Namespaces,and Scope) @@ -516,7 +513,7 @@ namespace A 可以从函数的限定名推断出名字查找时检查作用域的顺序,限定名以相反的顺序指出被查找的作用域。 -命名空间中名字的隐藏规则有一个例外:传递给函数一个类类型的对象、指向类的引用或指针时,除了在常规作用域查找名字外,还会查找实参类所属的命名空间。该例外允许概念上作为类接口一部分的非成员函数无须单独的using声明就能被程序使用。 +命名空间中名字的隐藏规则有一个例外:传递给函数一个类类型的对象、指向类的引用或指针时,除了在常规作用域查找名字外,还会查找实参类所属的命名空间。该例外允许概念上作为类接口一部分的非成员函数无须单独的`using`声明就能被程序使用。 ```c++ std::string s; @@ -530,26 +527,26 @@ using std::operator>>; std::operator>>(std::cin, s); ``` -标准库定义的move和forward模板函数接受一个右值引用形参,可以匹配任何类型。如果应用程序也定义了一个接受单一参数的move和forward函数,则不管形参是什么类型,都会与标准库的版本冲突。对于这两个函数来说,冲突大多是无意的,因此建议使用它们的含有限定语的完整版本(即std::move、std::forward)。 +标准库定义的`move`和`forward`模板函数接受一个右值引用形参,可以匹配任何类型。如果应用程序也定义了一个接受单一参数的`move`和`forward`函数,则不管形参是什么类型,都会与标准库的版本冲突。对于这两个函数来说,冲突大多是无意的,因此建议使用它们的含有限定语的完整版本(即`std::move`、`std::forward`)。 如果一个未声明的类或函数第一次出现在友元声明中,则会被认定是离它最近的外层命名空间的成员。 ### 重载与命名空间(Overloading and Namespaces) -using声明和using指示能将某些函数添加到候选函数集。 +`using`声明和`using`指示能将某些函数添加到候选函数集。 确定候选函数集时,会在函数的每个实参类(以及实参类的基类)所属的命名空间中搜索候选函数。这些命名空间中所有与被调用函数同名的函数都会被添加到候选集当中,即使其中某些函数在调用语句处不可见也是如此。 -using声明语句声明的是一个名字,而非一个特定的函数。一个using声明囊括了重载函数的所有版本以确保不违反命名空间的接口。 +`using`声明语句声明的是一个名字,而非一个特定的函数。一个`using`声明囊括了重载函数的所有版本以确保不违反命名空间的接口。 ```C++ using NS::print(int); // error: cannot specify a parameter list using NS::print; // ok: using declarations specify names only ``` -一个using声明引入的函数将重载该声明语句所属作用域中已有的其他同名函数。如果using声明出现在局部作用域中,则引入的名字会隐藏外层作用域的相关声明。如果using声明所在的作用域中已经有一个函数与引入的函数同名且形参列表相同,则该using声明会引发错误。除此之外,using声明将为引入的名字添加额外的重载实例,并最终扩充候选函数集的规模。 +一个`using`声明引入的函数将重载该声明语句所属作用域中已有的其他同名函数。如果`using`声明出现在局部作用域中,则引入的名字会隐藏外层作用域的相关声明。如果`using`声明所在的作用域中已经有一个函数与引入的函数同名且形参列表相同,则该`using`声明会引发错误。除此之外,`using`声明将为引入的名字添加额外的重载实例,并最终扩充候选函数集的规模。 -using指示将命名空间的成员提升到外层作用域中,如果命名空间的某个函数与该命名空间所属作用域中的函数同名,则命名空间的函数会被添加到重载集合中。 +`using`指示将命名空间的成员提升到外层作用域中,如果命名空间的某个函数与该命名空间所属作用域中的函数同名,则命名空间的函数会被添加到重载集合中。 ```c++ namespace libs_R_us @@ -572,9 +569,9 @@ void fooBar(int ival) } ``` -与using声明不同,using指示引入一个与已有函数形参列表完全相同的函数并不会引发错误。但需要明确指出调用的是命名空间中的函数版本还是当前作用域中的版本。 +与`using`声明不同,`using`指示引入一个与已有函数形参列表完全相同的函数并不会引发错误。但需要明确指出调用的是命名空间中的函数版本还是当前作用域中的版本。 -如果存在多个using指示,则来自每个命名空间的名字都会成为候选函数集的一部分。 +如果存在多个`using`指示,则来自每个命名空间的名字都会成为候选函数集的一部分。 ## 多重继承与虚继承(Multiple and Virtual Inheritance) @@ -582,14 +579,14 @@ void fooBar(int ival) ### 多重继承(Multiple Inheritance) -派生类的派生列表中可以包含多个基类。每个基类都包含一个可选的访问说明符。和单继承相同,如果访问说明符被省略,则关键字class对应的默认访问说明符是private,关键字struct对应的是public。 +派生类的派生列表中可以包含多个基类。每个基类都包含一个可选的访问说明符。和单继承相同,如果访问说明符被省略,则关键字`class`对应的默认访问说明符是`private`,关键字`struct`对应的是`public`。 ```c++ class Bear : public ZooAnimal { /* ... */ }; class Panda : public Bear, public Endangered { /* ... */ }; ``` -和单继承相同,多重继承的派生列表也只能包含已经被定义过的类,且这些类不能是final的。 +和单继承相同,多重继承的派生列表也只能包含已经被定义过的类,且这些类不能是`final`的。 多重继承关系中,派生类对象包含每个基类的子对象。 @@ -647,7 +644,7 @@ struct D2: public Base1, public Base2 }; ``` -和单继承相同,多重继承的派生类如果定义了自己的拷贝/赋值构造函数和赋值运算符,则必须在完整的对象上执行这些操作。只有当派生类使用的是合成版本的拷贝、移动或赋值成员时,才会自动处理其基类部分。在合成版本的拷贝控制成员中,每个基类分别使用自己的对应成员隐式地完成构造、赋值或销毁等工作。 +和单继承相同,多重继承的派生类如果定义了自己的拷贝/赋值构造函数和赋值运算符,则必须在完整的对象上执行这些操作。只有当派生类使用的是合成版本的拷贝、移动或赋值成员时,才会自动处理其基类部分。在合成版本的拷贝控制成员中,每个基类分别使用自己的对应成员隐式地完成构造、赋值或销毁等工作。 ### 类型转换与多个基类(Conversions and Multiple Base Classes) @@ -675,7 +672,7 @@ print(ying_yang); // error: ambiguous 尽管在派生列表中同一个基类只能出现一次,但实际上派生类可以多次继承同一个类。派生类可以通过它的两个直接基类分别继承同一个间接基类,也可以直接继承某个基类,然后通过另一个基类再次间接继承该类。 -默认情况下,派生类含有继承链上每个类对应的子部分。如果某个类在派生过程中出现了多次,则派生类中会包含该类的多个子对象。这种默认情况对某些类并不适用。例如iostream,它直接继承自istream和ostream,而istream和ostream都继承自base_ios,所以iostream继承了base_ios两次。如果iostream对象包含base_ios的两份拷贝,则无法在同一个缓冲区中进行读写操作。 +默认情况下,派生类含有继承链上每个类对应的子部分。如果某个类在派生过程中出现了多次,则派生类中会包含该类的多个子对象。这种默认情况对某些类并不适用。例如`iostream`,它直接继承自`istream`和`ostream`,而`istream`和`ostream`都继承自`base_ios`,所以`iostream`继承了`base_ios`两次。如果`iostream`对象包含`base_ios`的两份拷贝,则无法在同一个缓冲区中进行读写操作。 虚继承可以让某个类共享它的基类,其中共享的基类子对象称为虚基类(virtual base class)。在该机制下,不论虚基类在继承体系中出现了多少次,派生类都只包含唯一一个共享的虚基类子对象。 diff --git a/Chapter-19/Image/19-1.png b/Chapter-19 Specialized Tools and Techniques/Image/19-1.png similarity index 100% rename from Chapter-19/Image/19-1.png rename to Chapter-19 Specialized Tools and Techniques/Image/19-1.png diff --git a/Chapter-19/README.md b/Chapter-19 Specialized Tools and Techniques/README.md similarity index 72% rename from Chapter-19/README.md rename to Chapter-19 Specialized Tools and Techniques/README.md index 066cad7..9c3a91c 100644 --- a/Chapter-19/README.md +++ b/Chapter-19 Specialized Tools and Techniques/README.md @@ -4,24 +4,24 @@ ### 重载new和delete(Overloading new and delete) -使用new表达式时,实际执行了三步操作: +使用`new`表达式时,实际执行了三步操作: -- new表达式调用名为`operator new`(或`operator new[]`)的标准库函数。该函数分配一块足够大、原始、未命名的内存空间以便存储特定类型的对象(或对象数组)。 +- `new`表达式调用名为`operator new`(或`operator new[]`)的标准库函数。该函数分配一块足够大、原始、未命名的内存空间以便存储特定类型的对象(或对象数组)。 - 编译器调用对应的构造函数构造这些对象并初始化。 - 对象被分配了空间并构造完成,返回指向该对象的指针。 -使用delete表达式时,实际执行了两步操作: +使用`delete`表达式时,实际执行了两步操作: - 对指针所指向的对象(或对象数组)执行对应的析构函数。 - 编译器调用名为`operator delete`(或`operator delete[]`)的标准库函数释放内存空间。 -如果程序希望控制内存分配的过程,则需要定义自己的operator new和operator delete函数。编译器会用自定义版本替换标准库版本。 +如果程序希望控制内存分配的过程,则需要定义自己的`operator new`和`operator delete`函数。编译器会用自定义版本替换标准库版本。 -程序可以在全局作用域中定义operator new和operator delete函数,也可以将其定义为成员函数。编译器发现new或delete表达式后,将在程序中查找可供调用的operator函数。如果被分配或释放的对象是类类型,编译器会先在类及其基类的作用域中查找。如果该类含有operator成员,则表达式会调用这些成员。否则编译器会继续在全局作用域查找。如果找到自定义版本,则使用该版本的函数。如果没找到,则使用标准库定义的版本。 +程序可以在全局作用域中定义`operator new`和`operator delete`函数,也可以将其定义为成员函数。编译器发现`new`或`delete`表达式后,将在程序中查找可供调用的`operator`函数。如果被分配或释放的对象是类类型,编译器会先在类及其基类的作用域中查找。如果该类含有`operator`成员,则表达式会调用这些成员。否则编译器会继续在全局作用域查找。如果找到自定义版本,则使用该版本的函数。如果没找到,则使用标准库定义的版本。 -可以使用作用域运算符令new或delete表达式忽略定义在类中的函数,直接执行全局作用域版本。 +可以使用作用域运算符令`new`或`delete`表达式忽略定义在类中的函数,直接执行全局作用域版本。 -标准库定义了operator new和operator delete函数的8个重载版本,其中前4个版本可能抛出bad_alloc异常,后4个版本不会抛出异常。重载这些运算符时,必须使用关键字noexcept指定其不抛出异常。 +标准库定义了`operator new`和`operator delete`函数的8个重载版本,其中前4个版本可能抛出`bad_alloc`异常,后4个版本不会抛出异常。重载这些运算符时,必须使用关键字`noexcept`指定其不抛出异常。 ```c++ // these versions might throw an exception @@ -36,23 +36,23 @@ void *operator delete(void*, nothrow_t&) noexcept; void *operator delete[](void*, nothrow_t&) noexcept ``` -`nothrow_t`类型是定义在头文件*new*中的一个结构体,这个类型不包含任何成员。头文件*new*还定义了一个名为`nothrow`的const对象,用户可以通过这个对象请求new的非抛出版本。 +`nothrow_t`类型是定义在头文件*new*中的一个结构体,这个类型不包含任何成员。头文件*new*还定义了一个名为`nothrow`的`const`对象,用户可以通过这个对象请求`new`的非抛出版本。 -将operator函数定义为类的成员时,它们是隐式静态的,无须显式地声明static。因为operator new用在对象构造之前,operator delete用在对象销毁之后,所以它们必须是静态成员,而且不能操纵类的任何数据成员。 +将`operator`函数定义为类的成员时,它们是隐式静态的,无须显式地声明`static`。因为`operator new`用在对象构造之前,`operator delete`用在对象销毁之后,所以它们必须是静态成员,而且不能操纵类的任何数据成员。 -operator new和operator new[]函数的返回类型必须是void\*,第一个形参的类型必须是size_t且不能有默认实参。编译器调用operator new时,用存储指定类型对象所需的字节数初始化size_t形参;调用operator new[]时,传入函数的则是存储数组中所有元素所需的空间。 +`operator new`和`operator new[]`函数的返回类型必须是`void*`,第一个形参的类型必须是`size_t`且不能有默认实参。编译器调用`operator new`时,用存储指定类型对象所需的字节数初始化`size_t`形参;调用`operator new[]`时,传入函数的则是存储数组中所有元素所需的空间。 -自定义operator new函数时可以为它提供额外的形参,用到这些自定义函数的new表达式必须使用new的定位形式传递参数。下面这种形式的new函数只供标准库使用,不能被用户重定义: +自定义`operator new`函数时可以为它提供额外的形参,用到这些自定义函数的`new`表达式必须使用`new`的定位形式传递参数。下面这种形式的`new`函数只供标准库使用,不能被用户重定义: ```c++ void *operator new(size_t, void*); // this version may not be redefined ``` -operator delete和operator delete[]函数的返回类型必须是void,第一个形参的类型必须是void\*。函数被调用时,编译器会用指向待释放内存的指针来初始化void\*形参。 +`operator delete`和`operator delete[]`函数的返回类型必须是`void`,第一个形参的类型必须是`void*`。函数被调用时,编译器会用指向待释放内存的指针来初始化`void*`形参。 -将operator delete或operator delete[]定义为类的成员时,可以包含另一个类型为size_t的形参。该形参的初始值是第一个形参所指向对象的字节数。size_t形参可用于删除继承体系中的对象。如果基类有一个虚析构函数,则传递给operator delete的字节数会因待删除指针所指向对象的动态类型不同而有所区别。实际运行的operator delete函数版本也由对象的动态类型决定。 +将`operator delete`或`operator delete[]`定义为类的成员时,可以包含另一个类型为`size_t`的形参。该形参的初始值是第一个形参所指向对象的字节数。`size_t`形参可用于删除继承体系中的对象。如果基类有一个虚析构函数,则传递给`operator delete`的字节数会因待删除指针所指向对象的动态类型不同而有所区别。实际运行的`operator delete`函数版本也由对象的动态类型决定。 -`malloc`函数接受一个表示待分配字节数的size_t参数,返回指向分配空间的指针,或者返回0以表示分配失败。`free`函数接受一个void\*参数,它是malloc返回的指针的副本,free将相关内存返回给系统。调用free(0)没有任何意义。 +`malloc`函数接受一个表示待分配字节数的`size_t`参数,返回指向分配空间的指针,或者返回0以表示分配失败。`free`函数接受一个`void*`参数,它是`malloc`返回的指针的副本,`free`将相关内存返回给系统。调用`free(0)`没有任何意义。 ```c++ void *operator new(size_t size) @@ -71,9 +71,9 @@ void operator delete(void *mem) noexcept ### 定位new表达式(Placement new Expressions) -在C++的早期版本中,allocator类还不是标准库的一部分。如果程序想分开内存分配和初始化过程,需要直接调用operator new和operator delete函数。它们类似allocator类的allocate和deallocate成员,负责分配或释放内存空间,但不会构造或销毁对象。 +在C++的早期版本中,`allocator`类还不是标准库的一部分。如果程序想分开内存分配和初始化过程,需要直接调用`operator new`和`operator delete`函数。它们类似`allocator`类的`allocate`和`deallocate`成员,负责分配或释放内存空间,但不会构造或销毁对象。 -不能使用allocator类的construct函数在operator new分配的内存空间中构造对象,而应该使用定位new表达式构造。 +不能使用`allocator`类的`construct`函数在`operator new`分配的内存空间中构造对象,而应该使用定位`new`表达式构造。 ```c++ new (place_address) type @@ -84,9 +84,9 @@ new (place_address) type [size] { braced initializer list } 其中*place_address*是一个指针。*initializers*是一个以逗号分隔的初始值列表(可能为空),该列表用于构造新分配的对象。 -当仅通过一个地址值调用定位new时,它会使用operator new(size_t, void*)函数(用户无法重载的版本)。该函数不分配任何内存,直接返回指针形参。然后由new表达式负责在指定的地址初始化对象。 +当仅通过一个地址值调用定位`new`时,它会使用`operator new(size_t, void*)`函数(用户无法重载的版本)。该函数不分配任何内存,直接返回指针形参。然后由`new`表达式负责在指定的地址初始化对象。 -传递给construct函数的指针必须指向同一个allocator对象分配的空间,但是传递给定位new的指针无须指向operator new分配的内存,甚至不需要指向动态内存。 +传递给`construct`函数的指针必须指向同一个`allocator`对象分配的空间,但是传递给定位`new`的指针无须指向`operator new`分配的内存,甚至不需要指向动态内存。 调用析构函数会销毁对象,但不会释放内存。如果需要的话,可以重新使用该空间。 @@ -101,7 +101,7 @@ RTTI运算符适用于以下情况:想通过基类对象的指针或引用执 ### dynamic_cast运算符(The dynamic_cast Operator) -dynamic_cast运算符的形式如下: +`dynamic_cast`运算符的形式如下: ```c++ dynamic_cast(e) @@ -117,7 +117,7 @@ dynamic_cast(e) 如果条件符合,则类型转换成功,否则转换失败。转换失败可能有两种结果: -- 如果dynamic_cast语句的转换目标是指针类型,则结果为0。 +- 如果`dynamic_cast`语句的转换目标是指针类型,则结果为0。 ```c++ if (Derived *dp = dynamic_cast(bp)) @@ -130,7 +130,7 @@ dynamic_cast(e) } ``` -- 如果dynamic_cast语句的转换目标是引用类型,则抛出`bad_cast`异常(定义在头文件*typeinfo*中)。 +- 如果`dynamic_cast`语句的转换目标是引用类型,则抛出`bad_cast`异常(定义在头文件*typeinfo*中)。 ```c++ void f(const Base &b) @@ -147,19 +147,19 @@ dynamic_cast(e) } ``` -在条件判断部分执行dynamic_cast可以确保类型转换和结果检查在同一条表达式中完成。 +在条件判断部分执行`dynamic_cast`可以确保类型转换和结果检查在同一条表达式中完成。 -可以对一个空指针执行dynamic_cast,结果是所需类型的空指针。 +可以对一个空指针执行`dynamic_cast`,结果是所需类型的空指针。 ### typeid运算符(The typeid Operator) -typeid表达式的形式是`typeid(e)`,其中*e*可以是任意表达式或类型名称。typeid的结果是一个指向常量对象的引用,该对象的类型是标准库`type_info`(定义在头文件*typeinfo*中)或type_info的公有派生类型。 +`typeid`表达式的形式是`typeid(e)`,其中*e*可以是任意表达式或类型名称。`typeid`的结果是一个指向常量对象的引用,该对象的类型是标准库`type_info`(定义在头文件*typeinfo*中)或`type_info`的公有派生类型。 -typeid可以作用于任何类型的表达式,其中的顶层const会被忽略。如果表达式是一个引用,则typeid返回该引用所指对象的类型。当typeid作用于数组或函数时,不会执行向指针的标准类型转换。 +`typeid`可以作用于任何类型的表达式,其中的顶层`const`会被忽略。如果表达式是一个引用,则`typeid`返回该引用所指对象的类型。当`typeid`作用于数组或函数时,不会执行向指针的标准类型转换。 -当typeid的运算对象不属于类类型或者是一个不包含任何虚函数的类时,typeid返回其静态类型。而当运算对象是至少包含一个虚函数的类的左值时,typeid的结果直到运行期间才会确定。 +当`typeid`的运算对象不属于类类型或者是一个不包含任何虚函数的类时,`typeid`返回其静态类型。而当运算对象是至少包含一个虚函数的类的左值时,`typeid`的结果直到运行期间才会确定。 -通常情况下,typeid用于比较两条表达式的类型是否相同,或者比较一条表达式的类型是否与指定类型相同。 +通常情况下,`typeid`用于比较两条表达式的类型是否相同,或者比较一条表达式的类型是否与指定类型相同。 ```c++ C++Derived *dp = new Derived; @@ -176,7 +176,7 @@ if (typeid(*bp) == typeid(Derived)) } ``` -typeid应该作用于对象。当typeid作用于指针时,返回的结果是该指针的静态编译类型。 +`typeid`应该作用于对象。当`typeid`作用于指针时,返回的结果是该指针的静态编译类型。 ```c++ // test always fails: the type of bp is pointer to Base @@ -186,7 +186,7 @@ if (typeid(bp) == typeid(Derived)) } ``` -只有当类型含有虚函数时,编译器才会对typeid的表达式求值以确定返回类型。对于typeid(\*p),如果指针p所指向的类型不包含虚函数,则p可以是一个无效指针。否则\*p会在运行期间求值,此时p必须是一个有效指针。如果p是空指针,typeid(\*p)会抛出bad_typeid异常。 +只有当类型含有虚函数时,编译器才会对`typeid`的表达式求值以确定返回类型。对于`typeid(*p)`,如果指针*p*所指向的类型不包含虚函数,则*p*可以是一个无效指针。否则`*p`会在运行期间求值,此时*p*必须是一个有效指针。如果*p*是空指针,`typeid(*p)`会抛出`bad_typeid`异常。 ### 使用RTTI(Using RTTI) @@ -215,7 +215,7 @@ protected: }; ``` -使用typeid检查两个运算对象的类型是否一致,类型一致才会继续判断每个数据成员的取值是否相同。 +使用`typeid`检查两个运算对象的类型是否一致,类型一致才会继续判断每个数据成员的取值是否相同。 ```c++ bool operator==(const Base &lhs, const Base &rhs) @@ -225,7 +225,7 @@ bool operator==(const Base &lhs, const Base &rhs) } ``` -每个类定义的equal函数负责比较类型自己的数据成员。equal函数的形参都是基类的引用,但是在比较之前需要先把运算对象转换成自己的类型。 +每个类定义的`equal`函数负责比较类型自己的数据成员。`equal`函数的形参都是基类的引用,但是在比较之前需要先把运算对象转换成自己的类型。 ```c++ bool Derived::equal(const Base &rhs) const @@ -243,15 +243,15 @@ bool Base::equal(const Base &rhs) const ### type_info类(The type_info Class) -type_info类的精确定义会根据编译器的不同而略有差异。但是C++规定type_info必须定义在头文件*typeinfo*中,并且至少提供以下操作: +`type_info`类的精确定义会根据编译器的不同而略有差异。但是C++规定`type_info`必须定义在头文件*typeinfo*中,并且至少提供以下操作: ![19-1](Image/19-1.png) -type_info类一般是作为一个基类出现,所以它还应该提供一个公有虚析构函数。当编译器希望提供额外的类型信息时,通常在type_info的派生类中完成。 +`type_info`类一般是作为一个基类出现,所以它还应该提供一个公有虚析构函数。当编译器希望提供额外的类型信息时,通常在`type_info`的派生类中完成。 -type_info类没有默认构造函数,而且它的拷贝和移动构造函数以及赋值运算符都被定义为删除的。创建type_info对象的唯一方式就是使用typeid运算符。 +`type_info`类没有默认构造函数,而且它的拷贝和移动构造函数以及赋值运算符都被定义为删除的。创建`type_info`对象的唯一方式就是使用`typeid`运算符。 -对于某种给定类型来说,`name`成员的返回值因编译器而异并且不一定与在程序中使用的名字一致。对于name返回值的唯一要求就是类型不同则返回的字符串必须有所区别。 +对于某种给定类型来说,`name`成员的返回值因编译器而异并且不一定与在程序中使用的名字一致。对于`name`返回值的唯一要求就是类型不同则返回的字符串必须有所区别。 ## 枚举类型(Enumerations) @@ -270,7 +270,7 @@ C++包含两种枚举: }; ``` -- 不限定作用域的枚举(unscoped enumeration)。定义时省略关键字class(或struct),枚举类型名字是可选的。 +- 不限定作用域的枚举(unscoped enumeration)。定义时省略关键字`class`(或`struct`),枚举类型名字是可选的。 ```c++ C++// unscoped enumeration @@ -316,12 +316,12 @@ enum class intTypes 枚举值可以不唯一。如果没有显式提供初始值,则当前枚举成员的值等于之前枚举成员的值加1。 -枚举成员是const的,因此在初始化枚举成员时提供的初始值必须是常量表达式。 +枚举成员是`const`的,因此在初始化枚举成员时提供的初始值必须是常量表达式。 可以在任何需要常量表达式的地方使用枚举成员。如: -- 定义枚举类型的constexpr变量。 -- 将枚举类型对象作为switch语句的表达式,而将枚举值作为case标签。 +- 定义枚举类型的`constexpr`变量。 +- 将枚举类型对象作为`switch`语句的表达式,而将枚举值作为`case`标签。 - 将枚举类型作为非类型模板形参使用。 - 在类的定义中初始化枚举类型的静态数据成员。 @@ -348,7 +348,7 @@ enum intValues : unsigned long long }; ``` -如果没有指定枚举的潜在类型,则默认情况下限定作用域的枚举成员类型是int。不限定作用域的枚举成员不存在默认类型。 +如果没有指定枚举的潜在类型,则默认情况下限定作用域的枚举成员类型是`int`。不限定作用域的枚举成员不存在默认类型。 C++11中可以提前声明枚举。枚举的前置声明必须指定(无论隐式或显式)其成员的类型。 @@ -366,7 +366,7 @@ enum class open_modes; // scoped enums can use int by default ### 数据成员指针(Pointers to Data Members) -声明成员指针时必须在`*`前添加*classname::*以表示当前定义的指针可以指向*classname*的成员。 +声明成员指针时必须在`*`前添加`classname::`以表示当前定义的指针可以指向*classname*的成员。 ```c++ class Screen @@ -407,7 +407,7 @@ s = pScreen->*pdata; ### 成员函数指针(Pointers to Member Functions) -类似于其他函数指针,指向成员函数的指针也需要指定目标函数的返回类型和形参列表。如果成员函数是const成员或引用成员,则指针也必须包含const或引用限定符。 +类似于其他函数指针,指向成员函数的指针也需要指定目标函数的返回类型和形参列表。如果成员函数是`const`成员或引用成员,则指针也必须包含`const`或引用限定符。 ```c++ // pmf is a pointer that can point to a Screen member function that is const @@ -452,22 +452,22 @@ auto fp = &string::empty; // fp points to the string empty function find_if(svec.begin(), svec.end(), fp); ``` -从成员函数指针获取可调用对象的一种方法是使用标准库模板function。 +从成员函数指针获取可调用对象的一种方法是使用标准库模板`function`。 ```c++ function fcn = &string::empty; find_if(svec.begin(), svec.end(), fcn); ``` -定义一个function对象时,必须指定该对象所能表示的函数类型(即可调用对象的形式)。如果可调用对象是一个成员函数,则第一个形参必须表示该成员是在哪个对象上执行的。 +定义一个`function`对象时,必须指定该对象所能表示的函数类型(即可调用对象的形式)。如果可调用对象是一个成员函数,则第一个形参必须表示该成员是在哪个对象上执行的。 -使用标准库功能`mem_fn`(定义在头文件*functional*中)可以让编译器推断成员的类型。和function一样,mem_fn可以从成员指针生成可调用对象。但mem_fn可以根据成员指针的类型推断可调用对象的类型,无须显式指定。 +使用标准库功能`mem_fn`(定义在头文件*functional*中)可以让编译器推断成员的类型。和`function`一样,`mem_fn`可以从成员指针生成可调用对象。但`mem_fn`可以根据成员指针的类型推断可调用对象的类型,无须显式指定。 ```c++ find_if(svec.begin(), svec.end(), mem_fn(&string::empty)); ``` -mem_fn生成的可调用对象可以通过对象和指针调用。 +`mem_fn`生成的可调用对象可以通过对象和指针调用。 ```c++ auto f = mem_fn(&string::empty); // f takes a string or a string* @@ -510,7 +510,7 @@ class TextQuery::QueryResult 联合不能包含引用类型的成员。在C++11中,含有构造函数或析构函数的类类型也可以作为联合的成员类型。 -联合可以为其成员指定public、protected和private等保护标记。默认情况下,联合的成员都是公有的。 +联合可以为其成员指定`public`、`protected`和`private`等保护标记。默认情况下,联合的成员都是公有的。 联合可以定义包括构造函数和析构函数在内的成员函数。但是由于联合既不能继承自其他类,也不能作为基类使用,所以在联合中不能含有虚函数。 @@ -554,7 +554,7 @@ cval = 'c'; // assigns a new value to the unnamed, anonymous union object ival = 42; // that object now holds the value 42 ``` -匿名联合不能包含protected和private成员,也不能定义成员函数。 +匿名联合不能包含`protected`和`private`成员,也不能定义成员函数。 C++的早期版本规定,在联合中不能含有定义了构造函数或拷贝控制成员的类类型成员。C++11取消了该限制。但是如果联合的成员类型定义了自己的构造函数或拷贝控制成员,该联合的用法会比只含有内置类型成员的联合复杂得多。 @@ -670,11 +670,11 @@ File &File::open(File::modes m) ### volatile限定符(volatile Qualifier) -当对象的值可能在程序的控制或检测之外被改变时(如子线程),应该将该对象声明为`volatile`。关键字volatile的作用是告知编译器不要优化这样的对象。 +当对象的值可能在程序的控制或检测之外被改变时(如子线程),应该将该对象声明为`volatile`。关键字`volatile`的作用是告知编译器不要优化这样的对象。 -volatile的确切含义与机器有关,只能通过查阅编译器文档来理解。要想让一个使用了volatile的程序在移植到新机器或新编译器后仍然有效,通常需要对该程序进行一些修改。 +`volatile`的确切含义与机器有关,只能通过查阅编译器文档来理解。要想让一个使用了`volatile`的程序在移植到新机器或新编译器后仍然有效,通常需要对该程序进行一些修改。 -volatile的用法和const类似,都是对类型的额外修饰。二者相互之间并没有影响。 +`volatile`的用法和`const`类似,都是对类型的额外修饰。二者相互之间并没有影响。 ```c++ volatile int display_register; // int value that might change @@ -682,9 +682,9 @@ volatile Task *curr_task; // curr_task points to a volatile object volatile int iax[max_size]; // each element in iax is volatile ``` -类可以将成员函数定义为volatile的。volatile对象只能调用volatile成员函数。 +类可以将成员函数定义为`volatile`的。`volatile`对象只能调用`volatile`成员函数。 -volatile和指针的关系类似const。可以声明volatile指针、指向volatile对象的指针和指向volatile对象的volatile指针。 +`volatile`和指针的关系类似`const`。可以声明`volatile`指针、指向`volatile`对象的指针和指向`volatile`对象的`volatile`指针。 ```c++ volatile int v; // v is a volatile int @@ -698,9 +698,9 @@ int *ip = &v; // error: must use a pointer to volatile vivp = &v; // ok: vivp is a volatile pointer to volatile ``` -不能使用合成的拷贝/移动构造函数和赋值运算符初始化volatile对象或者给volatile对象赋值。合成的成员接受的形参类型是非volatile常量引用,不能把非volatile引用绑定到volatile对象上。 +不能使用合成的拷贝/移动构造函数和赋值运算符初始化`volatile`对象或者给`volatile`对象赋值。合成的成员接受的形参类型是非`volatile`常量引用,不能把非`volatile`引用绑定到`volatile`对象上。 -如果类需要拷贝、移动或赋值它的volatile对象,则必须自定义拷贝或移动操作。 +如果类需要拷贝、移动或赋值它的`volatile`对象,则必须自定义拷贝或移动操作。 ```c++ class Foo @@ -735,7 +735,7 @@ extern "C" 链接指示包含关键字`extern`、字符串字面值常量和一个函数声明。其中的字符串字面值常量指出了编写函数所用的语言。 -复合形式的链接指示可以应用于整个头文件。当一个#include指示被放置在复合链接指示的花括号中时,头文件中的所有函数声明都会被认为是由链接指示的语言编写的。链接指示可以嵌套,因此如果头文件包含自带链接指示的函数,该函数不会受到影响。 +复合形式的链接指示可以应用于整个头文件。当一个`#include`指示被放置在复合链接指示的花括号中时,头文件中的所有函数声明都会被认为是由链接指示的语言编写的。链接指示可以嵌套,因此如果头文件包含自带链接指示的函数,该函数不会受到影响。 ```c++ // compound-statement linkage directive diff --git a/Chapter-2/Image/2-1.png b/Chapter-2 Variables and Basic Types/Image/2-1.png similarity index 100% rename from Chapter-2/Image/2-1.png rename to Chapter-2 Variables and Basic Types/Image/2-1.png diff --git a/Chapter-2/Image/2-2.png b/Chapter-2 Variables and Basic Types/Image/2-2.png similarity index 100% rename from Chapter-2/Image/2-2.png rename to Chapter-2 Variables and Basic Types/Image/2-2.png diff --git a/Chapter-2/Image/2-3.png b/Chapter-2 Variables and Basic Types/Image/2-3.png similarity index 100% rename from Chapter-2/Image/2-3.png rename to Chapter-2 Variables and Basic Types/Image/2-3.png diff --git a/Chapter-2/Image/2-4.png b/Chapter-2 Variables and Basic Types/Image/2-4.png similarity index 100% rename from Chapter-2/Image/2-4.png rename to Chapter-2 Variables and Basic Types/Image/2-4.png diff --git a/Chapter-2/README.md b/Chapter-2 Variables and Basic Types/README.md similarity index 80% rename from Chapter-2/README.md rename to Chapter-2 Variables and Basic Types/README.md index 2361038..bfc6c87 100644 --- a/Chapter-2/README.md +++ b/Chapter-2 Variables and Basic Types/README.md @@ -12,32 +12,32 @@ 一个`char`的大小和一个机器字节一样,确保可以存放机器基本字符集中任意字符对应的数字值。`wchar_t`确保可以存放机器最大扩展字符集中的任意一个字符。 -在整型类型大小方面,C++规定`short` ≤ `int` ≤ `long` ≤ `long long`(long long是C++11定义的类型)。 +在整型类型大小方面,C++规定`short` ≤ `int` ≤ `long` ≤ `long long`(`long long`是C++11定义的类型)。 浮点型可表示单精度(single-precision)、双精度(double-precision)和扩展精度(extended-precision)值,分别对应`float`、`double`和`long double`类型。 -除去布尔型和扩展字符型,其他整型可以分为带符号(signed)和无符号(unsigned)两种。带符号类型可以表示正数、负数和0,无符号类型只能表示大于等于0的数值。类型int、short、long和long long都是带符号的,在类型名前面添加unsigned可以得到对应的无符号类型,如`unsigned int`。 +除去布尔型和扩展字符型,其他整型可以分为带符号(signed)和无符号(unsigned)两种。带符号类型可以表示正数、负数和0,无符号类型只能表示大于等于0的数值。类型`int`、`short`、`long`和`long long`都是带符号的,在类型名前面添加`unsigned`可以得到对应的无符号类型,如`unsigned int`。 -字符型分为`char`、`signed char`和`unsigned char`三种,但是表现形式只有带符号和无符号两种。类型char和signed char并不一样, char的具体形式由编译器(compiler)决定。 +字符型分为`char`、`signed char`和`unsigned char`三种,但是表现形式只有带符号和无符号两种。类型`char`和`signed char`并不一样, `char`的具体形式由编译器(compiler)决定。 如何选择算数类型: - 当明确知晓数值不可能为负时,应该使用无符号类型。 -- 使用int执行整数运算,如果数值超过了int的表示范围,应该使用long long类型。 +- 使用`int`执行整数运算,如果数值超过了`int`的表示范围,应该使用`long long`类型。 -- 在算数表达式中不要使用char和bool类型。如果需要使用一个不大的整数,应该明确指定它的类型是signed char还是unsigned char。 +- 在算数表达式中不要使用`char`和`bool`类型。如果需要使用一个不大的整数,应该明确指定它的类型是`signed char`还是`unsigned char`。 -- 执行浮点数运算时建议使用double类型。 +- 执行浮点数运算时建议使用`double`类型。 ### 类型转换(Type Conversions) 进行类型转换时,类型所能表示的值的范围决定了转换的过程。 -- 把非布尔类型的算术值赋给布尔类型时,初始值为0则结果为false,否则结果为true。 -- 把布尔值赋给非布尔类型时,初始值为false则结果为0,初始值为true则结果为1。 +- 把非布尔类型的算术值赋给布尔类型时,初始值为0则结果为`false`,否则结果为`true`。 +- 把布尔值赋给非布尔类型时,初始值为`false`则结果为0,初始值为`true`则结果为1。 - 把浮点数赋给整数类型时,进行近似处理,结果值仅保留浮点数中的整数部分。 - 把整数值赋给浮点类型时,小数部分记为0。如果该整数所占的空间超过了浮点类型的容量,精度可能有损失。 -- 赋给无符号类型一个超出它表示范围的值时,结果是初始值对无符号类型表示数值总数(8比特大小的unsigned char能表示的数值总数是256)取模后的余数。 +- 赋给无符号类型一个超出它表示范围的值时,结果是初始值对无符号类型表示数值总数(8比特大小的`unsigned char`能表示的数值总数是256)取模后的余数。 - 赋给带符号类型一个超出它表示范围的值时,结果是未定义的(undefined)。 避免无法预知和依赖于实现环境的行为。 @@ -50,7 +50,7 @@ for (unsigned u = 10; u >= 0; --u) std::cout << u << std::endl; ``` -当*u*等于0时,*--u*的结果将会是4294967295。一种解决办法是用while语句来代替for语句,前者可以在输出变量前先减去1。 +当*u*等于0时,*--u*的结果将会是4294967295。一种解决办法是用`while`语句来代替`for`语句,前者可以在输出变量前先减去1。 ```c++ unsigned u = 11; // start the loop one past the first element we want to print @@ -77,9 +77,9 @@ std::cout << 0B1'101; // 输出"13" std::cout << 1'100'000; // 输出"1100000" ``` -浮点型字面值默认是一个double。 +浮点型字面值默认是一个`double`。 -由单引号括起来的一个字符称为char型字面值,双引号括起来的零个或多个字符称为字符串字面值。 +由单引号括起来的一个字符称为`char`型字面值,双引号括起来的零个或多个字符称为字符串字面值。 字符串字面值的类型是由常量字符构成的数组(array)。编译器在每个字符串的结尾处添加一个空字符`'\0'`,因此字符串字面值的实际长度要比它的内容多一位。 @@ -115,7 +115,7 @@ std::cout << '\115' << '\n'; // prints M followed by a newline ![2-2](Image/2-2.png) -使用一个长整型字面值时,最好使用大写字母`L`进行标记,小写字母l和数字1容易混淆。 +使用一个长整型字面值时,最好使用大写字母`L`进行标记,小写字母`l`和数字`1`容易混淆。 ## 变量(Variables) @@ -154,7 +154,7 @@ extern int i; // declares but does not define i int j; // declares and defines j ``` -extern语句如果包含了初始值就不再是声明了,而变成了定义。 +`extern`语句如果包含了初始值就不再是声明了,而变成了定义。 变量能且只能被定义一次,但是可以被声明多次。 @@ -265,7 +265,7 @@ cout << *p; // * yields the object to which p points; prints 42 空指针(null pointer)不指向任何对象,在试图使用一个指针前代码可以先检查它是否为空。得到空指针最直接的办法是用字面值`nullptr`来初始化指针。 -旧版本程序通常使用`NULL`(预处理变量,定义于头文件*cstdlib*中,值为0)给指针赋值,但在C++11中,最好使用nullptr初始化空指针。 +旧版本程序通常使用`NULL`(预处理变量,定义于头文件*cstdlib*中,值为0)给指针赋值,但在C++11中,最好使用`nullptr`初始化空指针。 ```c++ int *p1 = nullptr; // equivalent to int *p1 = 0; @@ -276,7 +276,7 @@ int *p3 = NULL; // equivalent to int *p3 = 0; 建议初始化所有指针。 -void\*是一种特殊的指针类型,可以存放任意对象的地址,但不能直接操作void\*指针所指的对象。 +`void*`是一种特殊的指针类型,可以存放任意对象的地址,但不能直接操作`void*`指针所指的对象。 ### 理解复合类型的声明(Understanding Compound Type Declarations) @@ -304,20 +304,20 @@ r = &i; // r refers to a pointer; assigning &i to r makes p point to i ## const限定符(Const Qualifier) -在变量类型前添加关键字`const`可以创建值不能被改变的对象。const变量必须被初始化。 +在变量类型前添加关键字`const`可以创建值不能被改变的对象。`const`变量必须被初始化。 ```c++ const int bufSize = 512; // input buffer size bufSize = 512; // error: attempt to write to const object ``` -默认情况下,const对象被设定成仅在文件内有效。当多个文件中出现了同名的const变量时,其实等同于在不同文件中分别定义了独立的变量。 +默认情况下,`const`对象被设定成仅在文件内有效。当多个文件中出现了同名的`const`变量时,其实等同于在不同文件中分别定义了独立的变量。 -如果想在多个文件间共享const对象: +如果想在多个文件间共享`const`对象: -- 若const对象的值在编译时已经确定,则应该定义在头文件中。其他源文件包含该头文件时,不会产生重复定义错误。 +- 若`const`对象的值在编译时已经确定,则应该定义在头文件中。其他源文件包含该头文件时,不会产生重复定义错误。 -- 若const对象的值直到运行时才能确定,则应该在头文件中声明,在源文件中定义。此时const变量的声明和定义前都应该添加extern关键字。 +- 若`const`对象的值直到运行时才能确定,则应该在头文件中声明,在源文件中定义。此时`const`变量的声明和定义前都应该添加`extern`关键字。 ```c++ // file_1.cc defines and initializes a const that is accessible to other files @@ -328,7 +328,7 @@ bufSize = 512; // error: attempt to write to const object ### const的引用(References to const) -把引用绑定在const对象上即为对常量的引用(reference to const)。对常量的引用不能被用作修改它所绑定的对象。 +把引用绑定在`const`对象上即为对常量的引用(reference to const)。对常量的引用不能被用作修改它所绑定的对象。 ```c++ const int ci = 1024; @@ -369,7 +369,7 @@ double dval = 3.14; // dval is a double; its value can be changed cptr = &dval; // ok: but can't change dval through cptr ``` -定义语句中把`*`放在const之前用来说明指针本身是一个常量,常量指针(const pointer)必须初始化。 +定义语句中把`*`放在`const`之前用来说明指针本身是一个常量,常量指针(const pointer)必须初始化。 ```c++ int errNumb = 0; @@ -382,7 +382,7 @@ const double *const pip = π // pip is a const pointer to a const object ### 顶层const(Top-Level const) -顶层const表示指针本身是个常量,底层const(low-level const)表示指针所指的对象是一个常量。指针类型既可以是顶层const也可以是底层const。 +顶层`const`表示指针本身是个常量,底层`const`(low-level const)表示指针所指的对象是一个常量。指针类型既可以是顶层`const`也可以是底层`const`。 ```c++ int i = 0; @@ -393,16 +393,16 @@ const int *const p3 = p2; // right-most const is top-level, left-most is not const int &r = ci; // const in reference types is always low-level ``` -当执行拷贝操作时,常量是顶层const还是底层const区别明显: +当执行拷贝操作时,常量是顶层`const`还是底层`const`区别明显: -- 顶层const没有影响。拷贝操作不会改变被拷贝对象的值,因此拷入和拷出的对象是否是常量无关紧要。 +- 顶层`const`没有影响。拷贝操作不会改变被拷贝对象的值,因此拷入和拷出的对象是否是常量无关紧要。 ```c++ i = ci; // ok: copying the value of ci; top-level const in ci is ignored p2 = p3; // ok: pointed-to type matches; top-level const in p3 is ignored ``` -- 拷入和拷出的对象必须具有相同的底层const资格。或者两个对象的数据类型可以相互转换。一般来说,非常量可以转换成常量,反之则不行。 +- 拷入和拷出的对象必须具有相同的底层`const`资格。或者两个对象的数据类型可以相互转换。一般来说,非常量可以转换成常量,反之则不行。 ```c++ int *p = p3; // error: p3 has a low-level const but p doesn't @@ -433,11 +433,11 @@ constexpr int limit = mf + 1; // mf + 1 is a constant expression constexpr int sz = size(); // ok only if size is a constexpr function ``` -指针和引用都能定义成constexpr,但是初始值受到严格限制。constexpr指针的初始值必须是0、nullptr或者是存储在某个固定地址中的对象。 +指针和引用都能定义成`constexpr`,但是初始值受到严格限制。`constexpr`指针的初始值必须是0、`nullptr`或者是存储在某个固定地址中的对象。 -函数体内定义的普通变量一般并非存放在固定地址中,因此constexpr指针不能指向这样的变量。相反,函数体外定义的变量地址固定不变,可以用来初始化constexpr指针。 +函数体内定义的普通变量一般并非存放在固定地址中,因此`constexpr`指针不能指向这样的变量。相反,函数体外定义的变量地址固定不变,可以用来初始化`constexpr`指针。 -在constexpr声明中如果定义了一个指针,限定符constexpr仅对指针本身有效,与指针所指的对象无关。constexpr把它所定义的对象置为了顶层const。 +在`constexpr`声明中如果定义了一个指针,限定符`constexpr`仅对指针本身有效,与指针所指的对象无关。`constexpr`把它所定义的对象置为了顶层`const`。 ```c++ constexpr int *p = nullptr; // p是指向int的const指针 @@ -445,9 +445,9 @@ constexpr int i = 0; constexpr const int *cp = &i; // cp是指向const int的const指针 ``` -const和constexpr限定的值都是常量。但constexpr对象的值必须在编译期间确定,而const对象的值可以延迟到运行期间确定。 +`const`和`constexpr`限定的值都是常量。但`constexpr`对象的值必须在编译期间确定,而`const`对象的值可以延迟到运行期间确定。 -建议使用constexpr修饰表示数组大小的对象,因为数组的大小必须在编译期间确定且不能改变。 +建议使用`constexpr`修饰表示数组大小的对象,因为数组的大小必须在编译期间确定且不能改变。 ## 处理类型(Dealing with Types) @@ -468,23 +468,23 @@ using SI = Sales_item; // SI is a synonym for Sales_item ### auto类型说明符(The auto Type Specifier) -C++11新增`auto`类型说明符,能让编译器自动分析表达式所属的类型。auto定义的变量必须有初始值。 +C++11新增`auto`类型说明符,能让编译器自动分析表达式所属的类型。`auto`定义的变量必须有初始值。 ```c++ // the type of item is deduced from the type of the result of adding val1 and val2 auto item = val1 + val2; // item initialized to the result of val1 + val2 ``` -编译器推断出来的auto类型有时和初始值的类型并不完全一样。 +编译器推断出来的`auto`类型有时和初始值的类型并不完全一样。 -- 当引用被用作初始值时,编译器以引用对象的类型作为auto的类型。 +- 当引用被用作初始值时,编译器以引用对象的类型作为`auto`的类型。 ```c++ int i = 0, &r = i; auto a = r; // a is an int (r is an alias for i, which has type int) ``` -- auto一般会忽略顶层const。 +- `auto`一般会忽略顶层`const`。 ```c++ const int ci = i, &cr = ci; @@ -494,14 +494,14 @@ auto item = val1 + val2; // item initialized to the result of val1 + val2 auto e = &ci; // e is const int*(& of a const object is low-level const) ``` - 如果希望推断出的auto类型是一个顶层const,需要显式指定const auto。 + 如果希望推断出的`auto`类型是一个顶层`const`,需要显式指定`const auto`。 ```C++ const auto f = ci; // deduced type of ci is int; f has type const int ``` -设置类型为auto的引用时,原来的初始化规则仍然适用,初始值中的顶层常量属性仍然保留。 +设置类型为`auto`的引用时,原来的初始化规则仍然适用,初始值中的顶层常量属性仍然保留。 ```c++ auto &g = ci; // g is a const int& that is bound to ci @@ -517,7 +517,7 @@ C++11新增`decltype`类型指示符,作用是选择并返回操作数的数 decltype(f()) sum = x; // sum has whatever type f returns ``` -decltype处理顶层const和引用的方式与auto有些不同,如果decltype使用的表达式是一个变量,则decltype返回该变量的类型(包括顶层const和引用)。 +`decltype`处理顶层`const`和引用的方式与`auto`有些不同,如果`decltype`使用的表达式是一个变量,则`decltype`返回该变量的类型(包括顶层`const`和引用)。 ```c++ const int ci = 0, &cj = ci; @@ -526,9 +526,9 @@ decltype(cj) y = x; // y has type const int& and is bound to x decltype(cj) z; // error: z is a reference and must be initialized ``` -如果decltype使用的表达式不是一个变量,则decltype返回表达式结果对应的类型。如果表达式的内容是解引用操作,则decltype将得到引用类型。如果decltype使用的是一个不加括号的变量,则得到的结果就是该变量的类型;如果给变量加上了一层或多层括号,则decltype会得到引用类型,因为变量是一种可以作为赋值语句左值的特殊表达式。 +如果`decltype`使用的表达式不是一个变量,则`decltype`返回表达式结果对应的类型。如果表达式的内容是解引用操作,则`decltype`将得到引用类型。如果`decltype`使用的是一个不加括号的变量,则得到的结果就是该变量的类型;如果给变量加上了一层或多层括号,则`decltype`会得到引用类型,因为变量是一种可以作为赋值语句左值的特殊表达式。 -`decltype((var))`的结果永远是引用,而`decltype(var)`的结果只有当var本身是一个引用时才会是引用。 +`decltype((var))`的结果永远是引用,而`decltype(var)`的结果只有当*var*本身是一个引用时才会是引用。 ## 自定义数据结构(Defining Our Own Data Structures) @@ -538,7 +538,7 @@ C++11规定可以为类的数据成员(data member)提供一个类内初始 类定义的最后应该加上分号。 -头文件(header file)通常包含那些只能被定义一次的实体,如类、const和constexpr变量。 +头文件(header file)通常包含那些只能被定义一次的实体,如类、`const`和`constexpr`变量。 头文件一旦改变,相关的源文件必须重新编译以获取更新之后的声明。 diff --git a/Chapter-3/Image/3-1.png b/Chapter-3 Strings, Vectors, and Arrays/Image/3-1.png similarity index 100% rename from Chapter-3/Image/3-1.png rename to Chapter-3 Strings, Vectors, and Arrays/Image/3-1.png diff --git a/Chapter-3/Image/3-10.png b/Chapter-3 Strings, Vectors, and Arrays/Image/3-10.png similarity index 100% rename from Chapter-3/Image/3-10.png rename to Chapter-3 Strings, Vectors, and Arrays/Image/3-10.png diff --git a/Chapter-3/Image/3-2.png b/Chapter-3 Strings, Vectors, and Arrays/Image/3-2.png similarity index 100% rename from Chapter-3/Image/3-2.png rename to Chapter-3 Strings, Vectors, and Arrays/Image/3-2.png diff --git a/Chapter-3/Image/3-3.png b/Chapter-3 Strings, Vectors, and Arrays/Image/3-3.png similarity index 100% rename from Chapter-3/Image/3-3.png rename to Chapter-3 Strings, Vectors, and Arrays/Image/3-3.png diff --git a/Chapter-3/Image/3-4.png b/Chapter-3 Strings, Vectors, and Arrays/Image/3-4.png similarity index 100% rename from Chapter-3/Image/3-4.png rename to Chapter-3 Strings, Vectors, and Arrays/Image/3-4.png diff --git a/Chapter-3/Image/3-5.png b/Chapter-3 Strings, Vectors, and Arrays/Image/3-5.png similarity index 100% rename from Chapter-3/Image/3-5.png rename to Chapter-3 Strings, Vectors, and Arrays/Image/3-5.png diff --git a/Chapter-3/Image/3-6.png b/Chapter-3 Strings, Vectors, and Arrays/Image/3-6.png similarity index 100% rename from Chapter-3/Image/3-6.png rename to Chapter-3 Strings, Vectors, and Arrays/Image/3-6.png diff --git a/Chapter-3/Image/3-7.png b/Chapter-3 Strings, Vectors, and Arrays/Image/3-7.png similarity index 100% rename from Chapter-3/Image/3-7.png rename to Chapter-3 Strings, Vectors, and Arrays/Image/3-7.png diff --git a/Chapter-3/Image/3-8.png b/Chapter-3 Strings, Vectors, and Arrays/Image/3-8.png similarity index 100% rename from Chapter-3/Image/3-8.png rename to Chapter-3 Strings, Vectors, and Arrays/Image/3-8.png diff --git a/Chapter-3/Image/3-9.png b/Chapter-3 Strings, Vectors, and Arrays/Image/3-9.png similarity index 100% rename from Chapter-3/Image/3-9.png rename to Chapter-3 Strings, Vectors, and Arrays/Image/3-9.png diff --git a/Chapter-3/README.md b/Chapter-3 Strings, Vectors, and Arrays/README.md similarity index 62% rename from Chapter-3/README.md rename to Chapter-3 Strings, Vectors, and Arrays/README.md index 2259700..ee214c4 100644 --- a/Chapter-3/README.md +++ b/Chapter-3 Strings, Vectors, and Arrays/README.md @@ -2,15 +2,15 @@ ## 命名空间的using声明(Namespace using Declarations) -使用using声明后就无须再通过专门的前缀去获取所需的名字了。 +使用`using`声明后就无须再通过专门的前缀去获取所需的名字了。 ```c++ using std::cout; ``` -程序中使用的每个名字都需要用独立的using声明引入。 +程序中使用的每个名字都需要用独立的`using`声明引入。 -头文件中通常不应该包含using声明。 +头文件中通常不应该包含`using`声明。 ## 标准库类型string(Library string Type) @@ -18,7 +18,7 @@ using std::cout; ### 定义和初始化string对象(Defining and Initializing strings) -初始化string的方式: +初始化`string`的方式: ![3-1](Image/3-1.png) @@ -26,19 +26,19 @@ using std::cout; ### string对象上的操作(Operations on strings) -string的操作: +`string`的操作: ![3-2](Image/3-2.png) -在执行读取操作时,string对象会自动忽略开头的空白(空格符、换行符、制表符等)并从第一个真正的字符开始读取,直到遇见下一处空白为止。 +在执行读取操作时,`string`对象会自动忽略开头的空白(空格符、换行符、制表符等)并从第一个真正的字符开始读取,直到遇见下一处空白为止。 -使用`getline`函数可以读取一整行字符。该函数只要遇到换行符就结束读取并返回结果,如果输入的开始就是一个换行符,则得到空string。触发getline函数返回的那个换行符实际上被丢弃掉了,得到的string对象中并不包含该换行符。 +使用`getline`函数可以读取一整行字符。该函数只要遇到换行符就结束读取并返回结果,如果输入的开始就是一个换行符,则得到空`string`。触发`getline`函数返回的那个换行符实际上被丢弃掉了,得到的`string`对象中并不包含该换行符。 -`size`函数返回string对象的长度,返回值是`string::size_type`类型,这是一种无符号类型。要使用size_type,必须先指定它是由哪种类型定义的。 +`size`函数返回`string`对象的长度,返回值是`string::size_type`类型,这是一种无符号类型。要使用`size_type`,必须先指定它是由哪种类型定义的。 -如果一个表达式中已经有了size函数就不要再使用int了,这样可以避免混用int和unsigned int可能带来的问题。 +如果一个表达式中已经有了`size`函数就不要再使用`int`了,这样可以避免混用`int`和`unsigned int`可能带来的问题。 -当把string对象和字符字面值及字符串字面值混合在一条语句中使用时,必须确保每个加法运算符两侧的运算对象中至少有一个是string。 +当把`string`对象和字符字面值及字符串字面值混合在一条语句中使用时,必须确保每个加法运算符两侧的运算对象中至少有一个是`string`。 ```c++ string s4 = s1 + ", "; // ok: adding a string and a literal @@ -46,7 +46,7 @@ string s5 = "hello" + ", "; // error: no string operand string s6 = s1 + ", " + "world"; // ok: each + has a string operand ``` -为了与C兼容,C++语言中的字符串字面值并不是标准库string的对象。 +为了与C兼容,C++语言中的字符串字面值并不是标准库`string`的对象。 ### 处理string对象中的字符(Dealing with the Characters in a string) @@ -56,7 +56,7 @@ string s6 = s1 + ", " + "world"; // ok: each + has a string operand 建议使用C++版本的C标准库头文件。C语言中名称为*name.h*的头文件,在C++中则被命名为*cname*。 -C++11提供了范围for(range for)语句,可以遍历给定序列中的每个元素并执行某种操作。 +C++11提供了范围`for`(range for)语句,可以遍历给定序列中的每个元素并执行某种操作。 ```c++ for (declaration : expression) @@ -72,37 +72,37 @@ for (auto c : str) // for every char in str cout << c << endl; // print the current character followed by a newline ``` -如果想在范围for语句中改变string对象中字符的值,必须把循环变量定义成引用类型。 +如果想在范围`for`语句中改变`string`对象中字符的值,必须把循环变量定义成引用类型。 -下标运算符接收的输入参数是string::size_type类型的值,表示要访问字符的位置,返回值是该位置上字符的引用。 +下标运算符接收的输入参数是`string::size_type`类型的值,表示要访问字符的位置,返回值是该位置上字符的引用。 下标数值从0记起,范围是0至*size - 1*。使用超出范围的下标将引发不可预知的后果。 -C++标准并不要求标准库检测下标是否合法。编程时可以把下标的类型定义为相应的size_type,这是一种无符号数,可以确保下标不会小于0,此时代码只需要保证下标小于size的值就可以了。另一种确保下标合法的有效手段就是使用范围for语句。 +C++标准并不要求标准库检测下标是否合法。编程时可以把下标的类型定义为相应的`size_type`,这是一种无符号数,可以确保下标不会小于0,此时代码只需要保证下标小于`size`的值就可以了。另一种确保下标合法的有效手段就是使用范围`for`语句。 ## 标准库类型vector(Library vector Type) -标准库类型`vector`表示对象的集合,也叫做容器(container),定义在头文件*vector*中。vector中所有对象的类型都相同,每个对象都有一个索引与之对应并用于访问该对象。 +标准库类型`vector`表示对象的集合,也叫做容器(container),定义在头文件*vector*中。`vector`中所有对象的类型都相同,每个对象都有一个索引与之对应并用于访问该对象。 -vector是模板(template)而非类型,由vector生成的类型必须包含vector中元素的类型,如vector\。 +`vector`是模板(template)而非类型,由`vector`生成的类型必须包含`vector`中元素的类型,如`vector`。 -因为引用不是对象,所以不存在包含引用的vector。 +因为引用不是对象,所以不存在包含引用的`vector`。 -在早期的C++标准中,如果vector的元素还是vector,定义时必须在外层vector对象的右尖括号和其元素类型之间添加一个空格,如vector\ \>。但是在C++11标准中,可以直接写成vector\\>,不需要添加空格。 +在早期的C++标准中,如果`vector`的元素还是`vector`,定义时必须在外层`vector`对象的右尖括号和其元素类型之间添加一个空格,如`vector >`。但是在C++11标准中,可以直接写成`vector>`,不需要添加空格。 ### 定义和初始化vector对象(Defining and Initializing vectors) -初始化vector对象的方法: +初始化`vector`对象的方法: ![3-4](Image/3-4.png) -初始化vector对象时如果使用圆括号,可以说提供的值是用来构造(construct)vector对象的;如果使用的是花括号,则是在列表初始化(list initialize)该vector对象。 +初始化`vector`对象时如果使用圆括号,可以说提供的值是用来构造(construct)`vector`对象的;如果使用的是花括号,则是在列表初始化(list initialize)该`vector`对象。 -可以只提供vector对象容纳的元素数量而省略初始值,此时会创建一个值初始化(value-initialized)的元素初值,并把它赋给容器中的所有元素。这个初值由vector对象中的元素类型决定。 +可以只提供`vector`对象容纳的元素数量而省略初始值,此时会创建一个值初始化(value-initialized)的元素初值,并把它赋给容器中的所有元素。这个初值由`vector`对象中的元素类型决定。 ### 向vector对象中添加元素(Adding Elements to a vector) -`push_back`函数可以把一个值添加到vector的尾端。 +`push_back`函数可以把一个值添加到`vector`的尾端。 ```c++ vector v2; // empty vector @@ -111,22 +111,22 @@ for (int i = 0; i != 100; ++i) // at end of loop v2 has 100 elements, values 0 . . . 99 ``` -范围for语句体内不应该改变其所遍历序列的大小。 +范围`for`语句体内不应该改变其所遍历序列的大小。 ### 其他vector操作(Other vector Operations) -vector支持的操作: +`vector`支持的操作: ![3-5](Image/3-5.png) -size函数返回vector对象中元素的个数,返回值是由vector定义的size_type类型。vector对象的类型包含其中元素的类型。 +`size`函数返回`vector`对象中元素的个数,返回值是由`vector`定义的`size_type`类型。`vector`对象的类型包含其中元素的类型。 ```c++ vector::size_type // ok vector::size_type // error ``` -vector和string对象的下标运算符只能用来访问已经存在的元素,而不能用来添加元素。 +`vector`和`string`对象的下标运算符只能用来访问已经存在的元素,而不能用来添加元素。 ```c++ vector ivec; // empty vector @@ -143,26 +143,26 @@ for (decltype(ivec.size()) ix = 0; ix != 10; ++ix) ### 使用迭代器(Using Iterators) -定义了迭代器的类型都拥有`begin`和`end`两个成员函数。begin函数返回指向第一个元素的迭代器,end函数返回指向容器“尾元素的下一位置(one past the end)”的迭代器,通常被称作尾后迭代器(off-the-end iterator)或者简称为尾迭代器(end iterator)。尾后迭代器仅是个标记,表示程序已经处理完了容器中的所有元素。迭代器一般为`iterator`类型。 +定义了迭代器的类型都拥有`begin`和`end`两个成员函数。`begin`函数返回指向第一个元素的迭代器,`end`函数返回指向容器“尾元素的下一位置(one past the end)”的迭代器,通常被称作尾后迭代器(off-the-end iterator)或者简称为尾迭代器(end iterator)。尾后迭代器仅是个标记,表示程序已经处理完了容器中的所有元素。迭代器一般为`iterator`类型。 ```c++ // b denotes the first element and e denotes one past the last element in ivec auto b = ivec.begin(), e = ivec.end(); // b and e have the same type ``` -如果容器为空,则begin和end返回的是同一个迭代器,都是尾后迭代器。 +如果容器为空,则`begin`和`end`返回的是同一个迭代器,都是尾后迭代器。 标准容器迭代器的运算符: ![3-6](Image/3-6.png) -因为end返回的迭代器并不实际指向某个元素,所以不能对其进行递增或者解引用的操作。 +因为`end`返回的迭代器并不实际指向某个元素,所以不能对其进行递增或者解引用的操作。 -在for或者其他循环语句的判断条件中,最好使用`!=`而不是`<`。所有标准库容器的迭代器都定义了`==`和`!=`,但是只有其中少数同时定义了`<`运算符。 +在`for`或者其他循环语句的判断条件中,最好使用`!=`而不是`<`。所有标准库容器的迭代器都定义了`==`和`!=`,但是只有其中少数同时定义了`<`运算符。 -如果vector或string对象是常量,则只能使用`const_iterator`迭代器,该迭代器只能读元素,不能写元素。 +如果`vector`或`string`对象是常量,则只能使用`const_iterator`迭代器,该迭代器只能读元素,不能写元素。 -begin和end返回的迭代器具体类型由对象是否是常量决定,如果对象是常量,则返回const_iterator;如果对象不是常量,则返回iterator。 +`begin`和`end`返回的迭代器具体类型由对象是否是常量决定,如果对象是常量,则返回`const_iterator`;如果对象不是常量,则返回`iterator`。 ```c++ vector v; @@ -171,13 +171,13 @@ auto it1 = v.begin(); // it1 has type vector::iterator auto it2 = cv.begin(); // it2 has type vector::const_iterator ``` -C++11新增了`cbegin`和`cend`函数,不论vector或string对象是否为常量,都返回const_iterator迭代器。 +C++11新增了`cbegin`和`cend`函数,不论`vector`或`string`对象是否为常量,都返回`const_iterator`迭代器。 任何可能改变容器对象容量的操作,都会使该对象的迭代器失效。 ### 迭代器运算(Iterator Arithmetic) -vector和string迭代器支持的操作: +`vector`和`string`迭代器支持的操作: ![3-7](Image/3-7.png) @@ -185,9 +185,9 @@ vector和string迭代器支持的操作: ## 数组(Arrays) -数组类似vector,但数组的大小确定不变,不能随意向数组中添加元素。 +数组类似`vector`,但数组的大小确定不变,不能随意向数组中添加元素。 -如果不清楚元素的确切个数,应该使用vector。 +如果不清楚元素的确切个数,应该使用`vector`。 ### 定义和初始化内置数组(Defining and Initializing Built-in Arrays) @@ -195,7 +195,7 @@ vector和string迭代器支持的操作: 默认情况下,数组的元素被默认初始化。 -定义数组的时候必须指定数组的类型,不允许用auto关键字由初始值列表推断类型。 +定义数组的时候必须指定数组的类型,不允许用`auto`关键字由初始值列表推断类型。 如果定义数组时提供了元素的初始化列表,则允许省略数组维度,编译器会根据初始值的数量计算维度。但如果显式指明了维度,那么初始值的数量不能超过指定的大小。如果维度比初始值的数量大,则用提供的值初始化数组中靠前的元素,剩下的元素被默认初始化。 @@ -230,7 +230,7 @@ int (&arrRef)[10] = arr; // arrRef refers to an array of ten ints ### 访问数组元素(Accessing the Elements of an Array) -数组下标通常被定义成`size_t`类型,这是一种机器相关的无符号类型,可以表示内存中任意对象的大小。size_t定义在头文件*cstddef*中。 +数组下标通常被定义成`size_t`类型,这是一种机器相关的无符号类型,可以表示内存中任意对象的大小。`size_t`定义在头文件*cstddef*中。 大多数常见的安全问题都源于缓冲区溢出错误。当数组或其他类似数据结构的下标越界并试图访问非法内存区域时,就会产生此类错误。 @@ -248,7 +248,7 @@ string *p2 = nums; // equivalent to p2 = &nums[0] ![3-8](Image/3-8.png) -当使用数组作为一个auto变量的初始值时,推断得到的类型是指针而非数组。但decltype关键字不会发生这种转换,直接返回数组类型。 +当使用数组作为一个`auto`变量的初始值时,推断得到的类型是指针而非数组。但`decltype`关键字不会发生这种转换,直接返回数组类型。 ```c++ int ia[] = {0,1,2,3,4,5,6,7,8,9}; // ia is an array of ten ints @@ -277,7 +277,7 @@ int *last = end(ia); // pointer one past the last element in ia C风格字符串是将字符串存放在字符数组中,并以空字符结束(null terminated)。这不是一种类型,而是一种为了表达和使用字符串而形成的书写方法。 -C++标准支持C风格字符串,但是最好不要在C++程序中使用它们。对大多数程序来说,使用标准库string要比使用C风格字符串更加安全和高效。 +C++标准支持C风格字符串,但是最好不要在C++程序中使用它们。对大多数程序来说,使用标准库`string`要比使用C风格字符串更加安全和高效。 C风格字符串的函数: @@ -289,11 +289,11 @@ C风格字符串函数不负责验证其参数的正确性,传入此类函数 任何出现字符串字面值的地方都可以用以空字符结束的字符数组来代替: -- 允许使用以空字符结束的字符数组来初始化string对象或为string对象赋值。 -- 在string对象的加法运算中,允许使用以空字符结束的字符数组作为其中一个运算对象(不能两个运算对象都是)。 -- 在string对象的复合赋值运算中,允许使用以空字符结束的字符数组作为右侧运算对象。 +- 允许使用以空字符结束的字符数组来初始化`string`对象或为`string`对象赋值。 +- 在`string`对象的加法运算中,允许使用以空字符结束的字符数组作为其中一个运算对象(不能两个运算对象都是)。 +- 在`string`对象的复合赋值运算中,允许使用以空字符结束的字符数组作为右侧运算对象。 -不能用string对象直接初始化指向字符的指针。为了实现该功能,string提供了一个名为`c_str`的成员函数,返回const char*类型的指针,指向一个以空字符结束的字符数组,数组的数据和string对象一样。 +不能用`string`对象直接初始化指向字符的指针。为了实现该功能,`string`提供了一个名为`c_str`的成员函数,返回`const char*`类型的指针,指向一个以空字符结束的字符数组,数组的数据和`string`对象一样。 ```c++ string s("Hello World"); // s holds Hello World @@ -301,9 +301,9 @@ char *str = s; // error: can't initialize a char* from a string const char *str = s.c_str(); // ok ``` -针对string对象的后续操作有可能会让c_str函数之前返回的数组失去作用,如果程序想一直都能使用其返回的数组,最好将该数组重新拷贝一份。 +针对`string`对象的后续操作有可能会让`c_str`函数之前返回的数组失去作用,如果程序想一直都能使用其返回的数组,最好将该数组重新拷贝一份。 -可以使用数组来初始化vector对象,但是需要指明要拷贝区域的首元素地址和尾后地址。 +可以使用数组来初始化`vector`对象,但是需要指明要拷贝区域的首元素地址和尾后地址。 ```c++ int int_arr[] = {0, 1, 2, 3, 4, 5}; @@ -311,7 +311,7 @@ int int_arr[] = {0, 1, 2, 3, 4, 5}; vector ivec(begin(int_arr), end(int_arr)); ``` -在新版本的C++程序中应该尽量使用vector、string和迭代器,避免使用内置数组、C风格字符串和指针。 +在新版本的C++程序中应该尽量使用`vector`、`string`和迭代器,避免使用内置数组、C风格字符串和指针。 ## 多维数组(Multidimensional Arrays) @@ -346,7 +346,7 @@ int (&row)[4] = ia[1]; // binds row to the second four-element array in ia ![3-10](Image/3-10.png) -使用范围for语句处理多维数组时,为了避免数组被自动转换成指针,语句中的外层循环控制变量必须声明成引用类型。 +使用范围`for`语句处理多维数组时,为了避免数组被自动转换成指针,语句中的外层循环控制变量必须声明成引用类型。 ```c++ for (const auto &row : ia) // for every element in the outer array @@ -354,14 +354,14 @@ for (const auto &row : ia) // for every element in the outer array cout << col << endl; ``` -如果*row*不是引用类型,编译器初始化*row*时会自动将数组形式的元素转换成指向该数组内首元素的指针,这样得到的*row*就是int\*类型,而之后的内层循环则试图在一个int\*内遍历,程序将无法通过编译。 +如果*row*不是引用类型,编译器初始化*row*时会自动将数组形式的元素转换成指向该数组内首元素的指针,这样得到的*row*就是`int*`类型,而之后的内层循环则试图在一个`int*`内遍历,程序将无法通过编译。 ```c++ for (auto row : ia) for (auto col : row) ``` -使用范围for语句处理多维数组时,除了最内层的循环,其他所有外层循环的控制变量都应该定义成引用类型。 +使用范围`for`语句处理多维数组时,除了最内层的循环,其他所有外层循环的控制变量都应该定义成引用类型。 因为多维数组实际上是数组的数组,所以由多维数组名称转换得到的指针指向第一个内层数组。 @@ -378,7 +378,7 @@ int *ip[4]; // array of pointers to int int (*ip)[4]; // pointer to an array of four ints ``` -使用auto和decltype能省略复杂的指针定义。 +使用`auto`和`decltype`能省略复杂的指针定义。 ```c++ // print the value of each element in ia, with each inner array on its own line diff --git a/Chapter-4/Image/4-1.png b/Chapter-4 Expressions/Image/4-1.png similarity index 100% rename from Chapter-4/Image/4-1.png rename to Chapter-4 Expressions/Image/4-1.png diff --git a/Chapter-4/Image/4-2.png b/Chapter-4 Expressions/Image/4-2.png similarity index 100% rename from Chapter-4/Image/4-2.png rename to Chapter-4 Expressions/Image/4-2.png diff --git a/Chapter-4/Image/4-3.png b/Chapter-4 Expressions/Image/4-3.png similarity index 100% rename from Chapter-4/Image/4-3.png rename to Chapter-4 Expressions/Image/4-3.png diff --git a/Chapter-4/README.md b/Chapter-4 Expressions/README.md similarity index 80% rename from Chapter-4/README.md rename to Chapter-4 Expressions/README.md index 22859c1..57e5c5c 100644 --- a/Chapter-4/README.md +++ b/Chapter-4 Expressions/README.md @@ -8,7 +8,7 @@ C++定义了一元运算符(unary operator)和二元运算符(binary operator)。除此之外,还有一个作用于三个运算对象的三元运算符。函数调用也是一种特殊的运算符,它对运算对象的数量没有限制。 -表达式求值过程中,小整数类型(如bool、char、short等)通常会被提升(promoted)为较大的整数类型,主要是int。 +表达式求值过程中,小整数类型(如`bool`、`char`、`short`等)通常会被提升(promoted)为较大的整数类型,主要是`int`。 C++定义了运算符作用于内置类型和复合类型的运算对象时所执行的操作。当运算符作用于类类型的运算对象时,用户可以自定义其含义,这被称作运算符重载(overloaded operator)。 @@ -16,10 +16,10 @@ C++的表达式分为右值(rvalue)和左值(lvalue)。当一个对象 - 赋值运算符需要一个非常量左值作为其左侧运算对象,返回结果也是一个左值。 - 取地址符作用于左值运算对象,返回指向该运算对象的指针,该指针是一个右值。 -- 内置解引用运算符、下标运算符、迭代器解引用运算符、string和vector的下标运算符都返回左值。 +- 内置解引用运算符、下标运算符、迭代器解引用运算符、`string`和`vector`的下标运算符都返回左值。 - 内置类型和迭代器的递增递减运算符作用于左值运算对象。前置版本返回左值,后置版本返回右值。 -如果decltype作用于一个求值结果是左值的表达式,会得到引用类型。 +如果`decltype`作用于一个求值结果是左值的表达式,会得到引用类型。 ### 优先级与结合律(Precedence and Associativity) @@ -41,7 +41,7 @@ cout << i << " " << ++i << endl; // undefined - 不确定求值顺序时,使用括号来强制让表达式的组合关系符合程序逻辑的要求。 - 如果表达式改变了某个运算对象的值,则在表达式的其他位置不要再使用这个运算对象。 -当改变运算对象的子表达式本身就是另一个子表达式的运算对象时,第二条规则无效。如*\*++iter*,递增运算符改变了*iter*的值,而改变后的*iter*又是解引用运算符的运算对象。类似情况下,求值的顺序不会成为问题。 +当改变运算对象的子表达式本身就是另一个子表达式的运算对象时,第二条规则无效。如`*++iter`,递增运算符改变了*iter*的值,而改变后的*iter*又是解引用运算符的运算对象。类似情况下,求值的顺序不会成为问题。 ## 算术运算符(Arithmetic Operators) @@ -62,7 +62,7 @@ cout << i << " " << ++i << endl; // undefined - 对于逻辑与运算符来说,当且仅当左侧运算对象为真时才对右侧运算对象求值。 - 对于逻辑或运算符来说,当且仅当左侧运算对象为假时才对右侧运算对象求值。 -进行比较运算时,除非比较的对象是布尔类型,否则不要使用布尔字面值true和false作为运算对象。 +进行比较运算时,除非比较的对象是布尔类型,否则不要使用布尔字面值`true`和`false`作为运算对象。 ## 赋值运算符(Assignment Operators) @@ -113,7 +113,7 @@ cout << *iter++ << endl; ## 成员访问运算符(The Member Access Operators) -点运算符`.`和箭头运算符`->`都可以用来访问成员,表达式*ptr->mem*等价于*(\*ptr).mem*。 +点运算符`.`和箭头运算符`->`都可以用来访问成员,表达式`ptr->mem`等价于`(*ptr).mem`。 ```c++ string s1 = "a string", *p = &s1; @@ -150,22 +150,22 @@ cond ? expr1 : expr2; ## sizeof运算符(The sizeof Operator) -`sizeof`运算符返回一个表达式或一个类型名字所占的字节数,返回值是size_t类型。 +`sizeof`运算符返回一个表达式或一个类型名字所占的字节数,返回值是`size_t`类型。 -在sizeof的运算对象中解引用一个无效指针仍然是一种安全的行为,因为指针实际上并没有被真正使用。 +在`sizeof`的运算对象中解引用一个无效指针仍然是一种安全的行为,因为指针实际上并没有被真正使用。 -sizeof运算符的结果部分依赖于其作用的类型: +`sizeof`运算符的结果部分依赖于其作用的类型: -- 对char或者类型为char的表达式执行sizeof运算,返回值为1。 -- 对引用类型执行sizeof运算得到被引用对象所占空间的大小。 -- 对指针执行sizeof运算得到指针本身所占空间的大小。 -- 对解引用指针执行sizeof运算得到指针指向的对象所占空间的大小,指针不需要有效。 -- 对数组执行sizeof运算得到整个数组所占空间的大小。 -- 对string或vector对象执行sizeof运算只返回该类型固定部分的大小,不会计算对象中元素所占空间的大小。 +- 对`char`或者类型为`char`的表达式执行`sizeof`运算,返回值为1。 +- 对引用类型执行`sizeof`运算得到被引用对象所占空间的大小。 +- 对指针执行`sizeof`运算得到指针本身所占空间的大小。 +- 对解引用指针执行`sizeof`运算得到指针指向的对象所占空间的大小,指针不需要有效。 +- 对数组执行`sizeof`运算得到整个数组所占空间的大小。 +- 对`string`或`vector`对象执行`sizeof`运算只返回该类型固定部分的大小,不会计算对象中元素所占空间的大小。 ## 逗号运算符(Comma Operator) -逗号运算符`,`含有两个运算对象,按照从左向右的顺序依次求值,最后返回右侧表达式的值。逗号运算符经常用在for循环中。 +逗号运算符`,`含有两个运算对象,按照从左向右的顺序依次求值,最后返回右侧表达式的值。逗号运算符经常用在`for`循环中。 ```c++ vector::size_type cnt = ivec.size(); @@ -188,9 +188,9 @@ for(vector::size_type ix = 0; ix != ivec.size(); ++ix, --cnt) 在大多数表达式中,数组名字自动转换成指向数组首元素的指针。 -常量整数值0或字面值nullptr能转换成任意指针类型;指向任意非常量的指针能转换成void\*;指向任意对象的指针能转换成const void\*。 +常量整数值0或字面值`nullptr`能转换成任意指针类型;指向任意非常量的指针能转换成`void*`;指向任意对象的指针能转换成`const void*`。 -任意一种算术类型或指针类型都能转换成布尔类型。如果指针或算术类型的值为0,转换结果是false,否则是true。 +任意一种算术类型或指针类型都能转换成布尔类型。如果指针或算术类型的值为0,转换结果是`false`,否则是`true`。 指向非常量类型的指针能转换成指向相应的常量类型的指针。 @@ -206,10 +206,10 @@ cast-name(expression); 其中*type*是转换的目标类型,*expression*是要转换的值。如果*type*是引用类型,则转换结果是左值。*cast-name*是`static_cast`、`dynamic_cast`、`const_cast`和`reinterpret_cast`中的一种,用来指定转换的方式。 -- dynamic_cast支持运行时类型识别。 -- 任何具有明确定义的类型转换,只要不包含底层const,都能使用static_cast。 -- const_cast只能改变运算对象的底层const,不能改变表达式的类型。同时也只有const_cast能改变表达式的常量属性。const_cast常常用于函数重载。 -- reinterpret_cast通常为运算对象的位模式提供底层上的重新解释。 +- `dynamic_cast`支持运行时类型识别。 +- 任何具有明确定义的类型转换,只要不包含底层`const`,都能使用`static_cast`。 +- `const_cast`只能改变运算对象的底层`const`,不能改变表达式的类型。同时也只有`const_cast`能改变表达式的常量属性。`const_cast`常常用于函数重载。 +- `reinterpret_cast`通常为运算对象的位模式提供底层上的重新解释。 早期版本的C++语言中,显式类型转换包含两种形式: diff --git a/Chapter-5/Image/5-1.png b/Chapter-5 Statements/Image/5-1.png similarity index 100% rename from Chapter-5/Image/5-1.png rename to Chapter-5 Statements/Image/5-1.png diff --git a/Chapter-5/Image/5-2.png b/Chapter-5 Statements/Image/5-2.png similarity index 100% rename from Chapter-5/Image/5-2.png rename to Chapter-5 Statements/Image/5-2.png diff --git a/Chapter-5/Image/5-3.png b/Chapter-5 Statements/Image/5-3.png similarity index 100% rename from Chapter-5/Image/5-3.png rename to Chapter-5 Statements/Image/5-3.png diff --git a/Chapter-5 Statements/README.md b/Chapter-5 Statements/README.md new file mode 100644 index 0000000..359c605 --- /dev/null +++ b/Chapter-5 Statements/README.md @@ -0,0 +1,266 @@ +# 第5章 语句 + +## 简单语句(Simple Statements) + +如果在程序的某个地方,语法上需要一条语句但是逻辑上不需要,则应该使用空语句(null statement)。空语句中只含有一个单独的分号`;`。 + +```c++ +// read until we hit end-of-file or find an input equal to sought +while (cin >> s && s != sought) + ; // null statement +``` + +使用空语句时应该加上注释,从而令读这段代码的人知道该语句是有意省略的。 + +多余的空语句并非总是无害的。 + +```c++ +// disaster: extra semicolon: loop body is this null statement +while (iter != svec.end()) ; // the while body is the empty statement + ++iter; // increment is not part of the loop +``` + +复合语句(compound statement)是指用花括号括起来的(可能为空)语句和声明的序列。复合语句也叫做块(block),一个块就是一个作用域。在块中引入的名字只能在块内部以及嵌套在块中的子块里访问。通常,名字在有限的区域内可见,该区域从名字定义处开始,到名字所在(最内层)块的结尾处为止。 + +语句块不以分号作为结束。 + +空块的作用等价于空语句。 + +## 语句作用域(Statement Scope) + +可以在`if`、`switch`、`while`和`for`语句的控制结构内定义变量,这些变量只在相应语句的内部可见,一旦语句结束,变量也就超出了其作用范围。 + +```c++ +while (int i = get_num()) // i is created and initialized on each iteration + cout << i << endl; +i = 0; // error: i is not accessible outside the loop +``` + +## 条件语句(Conditional Statements) + +### if语句(The if Statement) + +`if`语句的形式: + +```c++ +if (condition) + statement +``` + +`if-else`语句的形式: + +```c++ +if (condition) + statement +else + statement2 +``` + +其中*condition*是判断条件,可以是一个表达式或者初始化了的变量声明。*condition*必须用圆括号括起来。 + +- 如果*condition*为真,则执行*statement*。执行完成后,程序继续执行`if`语句后面的其他语句。 +- 如果*condition*为假,则跳过*statement*。对于简单`if`语句来说,程序直接执行`if`语句后面的其他语句;对于`if-else`语句来说,程序先执行*statement2*,再执行`if`语句后面的其他语句。 + +`if`语句可以嵌套,其中`else`与离它最近的尚未匹配的`if`相匹配。 + +### switch语句(The switch Statement) + +`switch`语句的形式: + +![5-1](Image/5-1.png) + +`switch`语句先对括号里的表达式求值,值转换成整数类型后再与每个`case`标签(case label)的值进行比较。如果表达式的值和某个`case`标签匹配,程序从该标签之后的第一条语句开始执行,直到到达`switch`的结尾或者遇到`break`语句为止。`case`标签必须是整型常量表达式。 + +通常情况下每个`case`分支后都有`break`语句。如果确实不应该出现`break`语句,最好写一段注释说明程序的逻辑。 + +尽管`switch`语句没有强制要求在最后一个`case`标签后写上`break`,但为了安全起见,最好添加`break`。这样即使以后增加了新的`case`分支,也不用再在前面补充`break`语句了。 + +`switch`语句中可以添加一个`default`标签(default label),如果没有任何一个`case`标签能匹配上`switch`表达式的值,程序将执行`default`标签后的语句。 + +即使不准备在`default`标签下做任何操作,程序中也应该定义一个`default`标签。其目的在于告诉他人我们已经考虑到了默认情况,只是目前不需要实际操作。 + +不允许跨过变量的初始化语句直接跳转到该变量作用域内的另一个位置。如果需要为`switch`的某个`case`分支定义并初始化一个变量,则应该把变量定义在块内。 + +```c++ +case true: +{ + // ok: declaration statement within a statement block + string file_name = get_file_name(); + // ... +} +``` + +## 迭代语句(Iterative Statements) + +迭代语句通常称为循环,它重复执行操作直到满足某个条件才停止。`while`和`for`语句在执行循环体之前检查条件,`do-while`语句先执行循环体再检查条件。 + +### while语句(The while Statement) + +`while`语句的形式: + +```c++ +while (condition) + statement +``` + +只要*condition*的求值结果为`true`,就一直执行*statement*(通常是一个块)。*condition*不能为空,如果*condition*第一次求值就是`false`,*statement*一次都不会执行。 + +定义在`while`条件部分或者循环体内的变量每次迭代都经历从创建到销毁的过程。 + +在不确定迭代次数,或者想在循环结束后访问循环控制变量时,使用`while`比较合适。 + +### 传统的for语句(Traditional for Statement) + +`for`语句的形式: + +```c++ +for (initializer; condition; expression) + statement +``` + +一般情况下,*initializer*负责初始化一个值,这个值会随着循环的进行而改变。*condition*作为循环控制的条件,只要*condition*的求值结果为`true`,就执行一次*statement*。执行后再由*expression*负责修改*initializer*初始化的变量,这个变量就是*condition*检查的对象。如果*condition*第一次求值就是`false`,*statement*一次都不会执行。*initializer*中也可以定义多个对象,但是只能有一条声明语句,因此所有变量的基础类型必须相同。 + +`for`语句头中定义的对象只在`for`循环体内可见。 + +### 范围for语句(Range for Statement) + +范围`for`语句的形式: + +```c++ +for (declaration : expression) + statement +``` + +其中*expression*表示一个序列,拥有能返回迭代器的`begin`和`end`成员。*declaration*定义一个变量,序列中的每个元素都应该能转换成该变量的类型(可以使用`auto`)。如果需要对序列中的元素执行写操作,循环变量必须声明成引用类型。每次迭代都会重新定义循环控制变量,并将其初始化为序列中的下一个值,之后才会执行*statement*。 + +### do-while语句(The do-while Statement) + +`do-while`语句的形式: + +```c++ +do + statement +while (condition); +``` + +计算*condition*的值之前会先执行一次*statement*,*condition*不能为空。如果*condition*的值为`false`,循环终止,否则重复执行*statement*。 + +因为`do-while`语句先执行语句或块,再判断条件,所以不允许在条件部分定义变量。 + +## 跳转语句(Jump Statements) + +跳转语句中断当前的执行过程。 + +### break语句(The break Statement) + +`break`语句只能出现在迭代语句或者`switch`语句的内部,负责终止离它最近的`while`、`do-while`、`for`或者`switch`语句,并从这些语句之后的第一条语句开始执行。 + +```c++ +string buf; +while (cin >> buf && !buf.empty()) +{ + switch(buf[0]) + { + case '-': + // process up to the first blank + for (auto it = buf.begin()+1; it != buf.end(); ++it) + { + if (*it == ' ') + break; // #1, leaves the for loop + // . . . + } + // break #1 transfers control here + // remaining '-' processing: + break; // #2, leaves the switch statement + case '+': + // . . . + } // end switch +// end of switch: break #2 transfers control here +} // end while +``` + +### continue语句(The continue Statement) + +`continue`语句只能出现在迭代语句的内部,负责终止离它最近的循环的当前一次迭代并立即开始下一次迭代。和`break`语句不同的是,只有当`switch`语句嵌套在迭代语句内部时,才能在`switch`中使用`continue`。 + +`continue`语句中断当前迭代后,具体操作视迭代语句类型而定: + +- 对于`while`和`do-while`语句来说,继续判断条件的值。 +- 对于传统的`for`语句来说,继续执行`for`语句头中的第三部分,之后判断条件的值。 +- 对于范围`for`语句来说,是用序列中的下一个元素初始化循环变量。 + +### goto语句(The goto Statement) + +`goto`语句(labeled statement)是一种特殊的语句,在它之前有一个标识符和一个冒号。 + +```c++ +end: return; // labeled statement; may be the target of a goto +``` + +标签标识符独立于变量和其他标识符的名字,它们之间不会相互干扰。 + +`goto`语句的形式: + +```c++ +goto label; +``` + +`goto`语句使程序无条件跳转到标签为*label*的语句处执行,但两者必须位于同一个函数内,同时`goto`语句也不能将程序的控制权从变量的作用域之外转移到作用域之内。 + +建议不要在程序中使用`goto`语句,它使得程序既难理解又难修改。 + +## try语句块和异常处理(try Blocks and Exception Handling) + +异常(exception)是指程序运行时的反常行为,这些行为超出了函数正常功能的范围。当程序的某一部分检测到一个它无法处理的问题时,需要使用异常处理(exception handling)。 + +异常处理机制包括`throw`表达式(throw expression)、`try`语句块(try block)和异常类(exception class)。 + +- 异常检测部分使用`throw`表达式表示它遇到了无法处理的问题(`throw`引发了异常)。 +- 异常处理部分使用`try`语句块处理异常。`try`语句块以关键字`try`开始,并以一个或多个`catch`子句(catch clause)结束。`try`语句块中代码抛出的异常通常会被某个`catch`子句处理,`catch`子句也被称作异常处理代码(exception handler)。 +- 异常类用于在`throw`表达式和相关的`catch`子句之间传递异常的具体信息。 + +### throw表达式(A throw Expression) + +`throw`表达式包含关键字`throw`和紧随其后的一个表达式,其中表达式的类型就是抛出的异常类型。 + +### try语句块(The try Block) + +`try`语句块的通用形式: + +```c++ +try +{ + program-statements +} +catch (exception-declaration) +{ + handler-statements +} +catch (exception-declaration) +{ + handler-statements +} // . . . +``` + +`try`语句块中的*program-statements*组成程序的正常逻辑,其内部声明的变量在块外无法访问,即使在`catch`子句中也不行。`catch`子句包含关键字`catch`、括号内一个对象的声明(异常声明,exception declaration)和一个块。当选中了某个`catch`子句处理异常后,执行与之对应的块。`catch`一旦完成,程序会跳过剩余的所有`catch`子句,继续执行后面的语句。 + +如果最终没能找到与异常相匹配的`catch`子句,程序会执行名为`terminate`的标准库函数。该函数的行为与系统有关,一般情况下,执行该函数将导致程序非正常退出。类似的,如果一段程序没有`try`语句块且发生了异常,系统也会调用`terminate`函数并终止当前程序的执行。 + +### 标准异常(Standard Exceptions) + +异常类分别定义在4个头文件中: + +- 头文件*exception*定义了最通用的异常类`exception`。它只报告异常的发生,不提供任何额外信息。 + +- 头文件*stdexcept*定义了几种常用的异常类。 + + ![5-2](Image/5-2.png) + +- 头文件*new*定义了`bad_alloc`异常类。 +- 头文件*type_info*定义了`bad_cast`异常类。 + +标准库异常类的继承体系: + +![5-3](Image/5-3.png) + +只能以默认初始化的方式初始化`exception`、`bad_alloc`和`bad_cast`对象,不允许为这些对象提供初始值。其他异常类的对象在初始化时必须提供一个`string`或一个C风格字符串,通常表示异常信息。`what`成员函数可以返回该字符串的`string`副本。 \ No newline at end of file diff --git a/Chapter-5/README.md b/Chapter-5/README.md deleted file mode 100644 index 371b067..0000000 --- a/Chapter-5/README.md +++ /dev/null @@ -1,266 +0,0 @@ -# 第5章 语句 - -## 简单语句(Simple Statements) - -如果在程序的某个地方,语法上需要一条语句但是逻辑上不需要,则应该使用空语句(null statement)。空语句中只含有一个单独的分号`;`。 - -```c++ -// read until we hit end-of-file or find an input equal to sought -while (cin >> s && s != sought) - ; // null statement -``` - -使用空语句时应该加上注释,从而令读这段代码的人知道该语句是有意省略的。 - -多余的空语句并非总是无害的。 - -```c++ -// disaster: extra semicolon: loop body is this null statement -while (iter != svec.end()) ; // the while body is the empty statement - ++iter; // increment is not part of the loop -``` - -复合语句(compound statement)是指用花括号括起来的(可能为空)语句和声明的序列。复合语句也叫做块(block),一个块就是一个作用域。在块中引入的名字只能在块内部以及嵌套在块中的子块里访问。通常,名字在有限的区域内可见,该区域从名字定义处开始,到名字所在(最内层)块的结尾处为止。 - -语句块不以分号作为结束。 - -空块的作用等价于空语句。 - -## 语句作用域(Statement Scope) - -可以在if、switch、while和for语句的控制结构内定义变量,这些变量只在相应语句的内部可见,一旦语句结束,变量也就超出了其作用范围。 - -```c++ -while (int i = get_num()) // i is created and initialized on each iteration - cout << i << endl; -i = 0; // error: i is not accessible outside the loop -``` - -## 条件语句(Conditional Statements) - -### if语句(The if Statement) - -`if`语句的形式: - -```c++ -if (condition) - statement -``` - -`if-else`语句的形式: - -```c++ -if (condition) - statement -else - statement2 -``` - -其中*condition*是判断条件,可以是一个表达式或者初始化了的变量声明。*condition*必须用圆括号括起来。 - -- 如果*condition*为真,则执行*statement*。执行完成后,程序继续执行if语句后面的其他语句。 -- 如果*condition*为假,则跳过*statement*。对于简单if语句来说,程序直接执行if语句后面的其他语句;对于if-else语句来说,程序先执行*statement2*,再执行if语句后面的其他语句。 - -if语句可以嵌套,其中else与离它最近的尚未匹配的if相匹配。 - -### switch语句(The switch Statement) - -`switch`语句的形式: - -![5-1](Image/5-1.png) - -switch语句先对括号里的表达式求值,值转换成整数类型后再与每个`case`标签(case label)的值进行比较。如果表达式的值和某个case标签匹配,程序从该标签之后的第一条语句开始执行,直到到达switch的结尾或者遇到break语句为止。case标签必须是整型常量表达式。 - -通常情况下每个case分支后都有break语句。如果确实不应该出现break语句,最好写一段注释说明程序的逻辑。 - -尽管switch语句没有强制要求在最后一个case标签后写上break,但为了安全起见,最好添加break。这样即使以后增加了新的case分支,也不用再在前面补充break语句了。 - -switch语句中可以添加一个`default`标签(default label),如果没有任何一个case标签能匹配上switch表达式的值,程序将执行default标签后的语句。 - -即使不准备在default标签下做任何操作,程序中也应该定义一个default标签。其目的在于告诉他人我们已经考虑到了默认情况,只是目前不需要实际操作。 - -不允许跨过变量的初始化语句直接跳转到该变量作用域内的另一个位置。如果需要为switch的某个case分支定义并初始化一个变量,则应该把变量定义在块内。 - -```c++ -case true: -{ - // ok: declaration statement within a statement block - string file_name = get_file_name(); - // ... -} -``` - -## 迭代语句(Iterative Statements) - -迭代语句通常称为循环,它重复执行操作直到满足某个条件才停止。while和for语句在执行循环体之前检查条件,do-while语句先执行循环体再检查条件。 - -### while语句(The while Statement) - -`while`语句的形式: - -```c++ -while (condition) - statement -``` - -只要*condition*的求值结果为真,就一直执行*statement*(通常是一个块)。*condition*不能为空,如果*condition*第一次求值就是false,*statement*一次都不会执行。 - -定义在while条件部分或者循环体内的变量每次迭代都经历从创建到销毁的过程。 - -在不确定迭代次数,或者想在循环结束后访问循环控制变量时,使用while比较合适。 - -### 传统的for语句(Traditional for Statement) - -`for`语句的形式: - -```c++ -for (initializer; condition; expression) - statement -``` - -一般情况下,*initializer*负责初始化一个值,这个值会随着循环的进行而改变。*condition*作为循环控制的条件,只要*condition*的求值结果为真,就执行一次*statement*。执行后再由*expression*负责修改*initializer*初始化的变量,这个变量就是*condition*检查的对象。如果*condition*第一次求值就是false,*statement*一次都不会执行。*initializer*中也可以定义多个对象,但是只能有一条声明语句,因此所有变量的基础类型必须相同。 - -for语句头中定义的对象只在for循环体内可见。 - -### 范围for语句(Range for Statement) - -范围for语句的形式: - -```c++ -for (declaration : expression) - statement -``` - -其中*expression*表示一个序列,拥有能返回迭代器的begin和end成员。*declaration*定义一个变量,序列中的每个元素都应该能转换成该变量的类型(可以使用auto)。如果需要对序列中的元素执行写操作,循环变量必须声明成引用类型。每次迭代都会重新定义循环控制变量,并将其初始化为序列中的下一个值,之后才会执行*statement*。 - -### do-while语句(The do-while Statement) - -`do-while`语句的形式: - -```c++ -do - statement -while (condition); -``` - -计算*condition*的值之前会先执行一次*statement*,*condition*不能为空。如果*condition*的值为假,循环终止,否则重复执行*statement*。 - -因为do-while语句先执行语句或块,再判断条件,所以不允许在条件部分定义变量。 - -## 跳转语句(Jump Statements) - -跳转语句中断当前的执行过程。 - -### break语句(The break Statement) - -`break`语句只能出现在迭代语句或者switch语句的内部,负责终止离它最近的while、do-while、for或者switch语句,并从这些语句之后的第一条语句开始执行。 - -```c++ -string buf; -while (cin >> buf && !buf.empty()) -{ - switch(buf[0]) - { - case '-': - // process up to the first blank - for (auto it = buf.begin()+1; it != buf.end(); ++it) - { - if (*it == ' ') - break; // #1, leaves the for loop - // . . . - } - // break #1 transfers control here - // remaining '-' processing: - break; // #2, leaves the switch statement - case '+': - // . . . - } // end switch -// end of switch: break #2 transfers control here -} // end while -``` - -### continue语句(The continue Statement) - -`continue`语句只能出现在迭代语句的内部,负责终止离它最近的循环的当前一次迭代并立即开始下一次迭代。和break语句不同的是,只有当switch语句嵌套在迭代语句内部时,才能在switch中使用continue。 - -continue语句中断当前迭代后,具体操作视迭代语句类型而定: - -- 对于while和do-while语句来说,继续判断条件的值。 -- 对于传统的for语句来说,继续执行for语句头中的第三部分,之后判断条件的值。 -- 对于范围for语句来说,是用序列中的下一个元素初始化循环变量。 - -### goto语句(The goto Statement) - -`goto`语句(labeled statement)是一种特殊的语句,在它之前有一个标识符和一个冒号。 - -```c++ -end: return; // labeled statement; may be the target of a goto -``` - -标签标识符独立于变量和其他标识符的名字,它们之间不会相互干扰。 - -goto语句的形式: - -```c++ -goto label; -``` - -goto语句使程序无条件跳转到标签为*label*的语句处执行,但两者必须位于同一个函数内,同时goto语句也不能将程序的控制权从变量的作用域之外转移到作用域之内。 - -建议不要在程序中使用goto语句,它使得程序既难理解又难修改。 - -## try语句块和异常处理(try Blocks and Exception Handling) - -异常(exception)是指程序运行时的反常行为,这些行为超出了函数正常功能的范围。当程序的某一部分检测到一个它无法处理的问题时,需要使用异常处理(exception handling)。 - -异常处理机制包括throw表达式(throw expression)、try语句块(try block)和异常类(exception class)。 - -- 异常检测部分使用`throw`表达式表示它遇到了无法处理的问题(throw引发了异常)。 -- 异常处理部分使用`try`语句块处理异常。try语句块以关键字try开始,并以一个或多个`catch`子句(catch clause)结束。try语句块中代码抛出的异常通常会被某个catch子句处理,catch子句也被称作异常处理代码(exception handler)。 -- 异常类用于在throw表达式和相关的catch子句之间传递异常的具体信息。 - -### throw表达式(A throw Expression) - -throw表达式包含关键字throw和紧随其后的一个表达式,其中表达式的类型就是抛出的异常类型。 - -### try语句块(The try Block) - -try语句块的通用形式: - -```c++ -try -{ - program-statements -} -catch (exception-declaration) -{ - handler-statements -} -catch (exception-declaration) -{ - handler-statements -} // . . . -``` - -try语句块中的*program-statements*组成程序的正常逻辑,其内部声明的变量在块外无法访问,即使在catch子句中也不行。catch子句包含关键字catch、括号内一个对象的声明(异常声明,exception declaration)和一个块。当选中了某个catch子句处理异常后,执行与之对应的块。catch一旦完成,程序会跳过剩余的所有catch子句,继续执行后面的语句。 - -如果最终没能找到与异常相匹配的catch子句,程序会执行名为`terminate`的标准库函数。该函数的行为与系统有关,一般情况下,执行该函数将导致程序非正常退出。类似的,如果一段程序没有try语句块且发生了异常,系统也会调用terminate函数并终止当前程序的执行。 - -### 标准异常(Standard Exceptions) - -异常类分别定义在4个头文件中: - -- 头文件*exception*定义了最通用的异常类`exception`。它只报告异常的发生,不提供任何额外信息。 - -- 头文件*stdexcept*定义了几种常用的异常类。 - - ![5-2](Image/5-2.png) - -- 头文件*new*定义了`bad_alloc`异常类。 -- 头文件*type_info*定义了`bad_cast`异常类。 - -标准库异常类的继承体系: - -![5-3](Image/5-3.png) - -只能以默认初始化的方式初始化exception、bad_alloc和bad_cast对象,不允许为这些对象提供初始值。其他异常类的对象在初始化时必须提供一个string或一个C风格字符串,通常表示异常信息。`what`成员函数可以返回该字符串的string副本。 \ No newline at end of file diff --git a/Chapter-6/Image/6-1.png b/Chapter-6 Functions/Image/6-1.png similarity index 100% rename from Chapter-6/Image/6-1.png rename to Chapter-6 Functions/Image/6-1.png diff --git a/Chapter-6/Image/6-2.png b/Chapter-6 Functions/Image/6-2.png similarity index 100% rename from Chapter-6/Image/6-2.png rename to Chapter-6 Functions/Image/6-2.png diff --git a/Chapter-6/README.md b/Chapter-6 Functions/README.md similarity index 81% rename from Chapter-6/README.md rename to Chapter-6 Functions/README.md index 5f901f9..04eb8f3 100644 --- a/Chapter-6/README.md +++ b/Chapter-6 Functions/README.md @@ -33,7 +33,7 @@ int main() `return`语句结束函数的执行过程,完成两项工作: -- 返回return语句中的值(可能没有值)。 +- 返回`return`语句中的值(可能没有值)。 - 将控制权从被调函数转移回主调函数,函数的返回值用于初始化调用表达式的结果。 实参是形参的初始值,两者的顺序和类型必须一一对应。 @@ -124,9 +124,9 @@ void reset(int &i) // i is just another name for the object passed to reset ### const形参和实参(const Parameters and Arguments) -当形参有顶层const时,传递给它常量对象或非常量对象都是可以的。 +当形参有顶层`const`时,传递给它常量对象或非常量对象都是可以的。 -可以使用非常量对象初始化一个底层const形参,但是反过来不行。 +可以使用非常量对象初始化一个底层`const`形参,但是反过来不行。 把函数不会改变的形参定义成普通引用会极大地限制函数所能接受的实参类型,同时也会给别人一种误导,即函数可以修改实参的值。 @@ -160,7 +160,7 @@ f(int (&arr)[10]) // ok: arr is a reference to an array of ten ints ### main:处理命令行选项(main:Handling Command-Line Options) -可以在命令行中向main函数传递参数,形式如下: +可以在命令行中向`main`函数传递参数,形式如下: ```c++ int main(int argc, char *argv[]) { /*...*/ } @@ -169,9 +169,9 @@ int main(int argc, char **argv) { /*...*/ } 第二个形参*argv*是一个数组,数组元素是指向C风格字符串的指针;第一个形参*argc*表示数组中字符串的数量。 -当实参传递给main函数后,*argv*的第一个元素指向程序的名字或者一个空字符串,接下来的元素依次传递命令行提供的实参。最后一个指针之后的元素值保证为0。 +当实参传递给`main`函数后,*argv*的第一个元素指向程序的名字或者一个空字符串,接下来的元素依次传递命令行提供的实参。最后一个指针之后的元素值保证为0。 -在Visual Studio中可以设置main函数调试参数: +在*Visual Studio*中可以设置`main`函数调试参数: ![6-1](Image/6-1.png) @@ -194,17 +194,17 @@ C++11新标准提供了两种主要方法处理实参数量不定的函数。 C++还可以使用省略符形参传递可变数量的实参,但这种功能一般只用在与C函数交换的接口程序中。 -initializer_list是一种标准库类型,定义在头文件*initializer_list*中,表示某种特定类型的值的数组。 +`initializer_list`是一种标准库类型,定义在头文件*initializer_list*中,表示某种特定类型的值的数组。 -initializer_list提供的操作: +`initializer_list`提供的操作: ![6-2](Image/6-2.png) -拷贝或赋值一个initializer_list对象不会拷贝列表中的元素。拷贝后,原始列表和副本共享元素。 +拷贝或赋值一个`initializer_list`对象不会拷贝列表中的元素。拷贝后,原始列表和副本共享元素。 -initializer_list对象中的元素永远是常量值。 +`initializer_list`对象中的元素永远是常量值。 -如果想向initializer_list形参传递一个值的序列,则必须把序列放在一对花括号内。 +如果想向`initializer_list`形参传递一个值的序列,则必须把序列放在一对花括号内。 ```c++ if (expected != actual) @@ -213,7 +213,7 @@ else error_msg(ErrCode(0), {"functionX", "okay"}); ``` -因为initializer_list包含begin和end成员,所以可以使用范围for循环处理其中的元素。 +因为`initializer_list`包含`begin`和`end`成员,所以可以使用范围`for`循环处理其中的元素。 省略符形参是为了便于C++程序访问某些特殊的C代码而设置的,这些代码使用了名为`varargs`的C标准库功能。通常,省略符形参不应该用于其他目的。 @@ -221,7 +221,7 @@ else ## 返回类型和return语句(Return Types and the return Statement) -return语句有两种形式,作用是终止当前正在执行的函数并返回到调用该函数的地方。 +`return`语句有两种形式,作用是终止当前正在执行的函数并返回到调用该函数的地方。 ```c++ return; @@ -230,19 +230,19 @@ return expression; ### 无返回值函数(Functions with No Return Value) -没有返回值的return语句只能用在返回类型是void的函数中。返回void的函数不要求非得有return语句,因为在这类函数的最后一条语句后面会隐式地执行return。 +没有返回值的`return`语句只能用在返回类型是`void`的函数中。返回`void`的函数可以省略`return`语句,因为在这类函数的最后一条语句后面会隐式地执行`return`。 -通常情况下,如果void函数想在其中间位置提前退出,可以使用return语句。 +通常情况下,如果`void`函数想在其中间位置提前退出,可以使用`return`语句。 -一个返回类型是void的函数也能使用return语句的第二种形式,不过此时return语句的expression必须是另一个返回void的函数。 +一个返回类型是`void`的函数也能使用`return`语句的第二种形式,不过此时`return`语句的*expression*必须是另一个返回`void`的函数。 -强行令void函数返回其他类型的表达式将产生编译错误。 +强行令`void`函数返回其他类型的表达式将产生编译错误。 ### 有返回值函数(Functions That Return a Value) -return语句的第二种形式提供了函数的结果。只要函数的返回类型不是void,该函数内的每条return语句就必须返回一个值,并且返回值的类型必须与函数的返回类型相同,或者能隐式地转换成函数的返回类型(main函数例外)。 +`return`语句的第二种形式提供了函数的结果。只要函数的返回类型不是`void`,该函数内的每条`return`语句就必须返回一个值,并且返回值的类型必须与函数的返回类型相同,或者能隐式地转换成函数的返回类型(`main`函数例外)。 -在含有return语句的循环后面应该也有一条return语句,否则程序就是错误的,但很多编译器无法发现此错误。 +在含有`return`语句的循环后面应该也有一条`return`语句,否则程序就是错误的,但很多编译器无法发现此错误。 函数返回一个值的方式和初始化一个变量或形参的方式完全一样:返回的值用于初始化调用点的一个临时量,该临时量就是函数调用的结果。 @@ -287,11 +287,11 @@ C++11规定,函数可以返回用花括号包围的值的列表。同其他返 } ``` -main函数可以没有return语句直接结束。如果控制流到达了main函数的结尾处并且没有return语句,编译器会隐式地插入一条返回0的return语句。 +`main`函数可以没有`return`语句直接结束。如果控制流到达了`main`函数的结尾处并且没有`return`语句,编译器会隐式地插入一条返回0的`return`语句。 -main函数的返回值可以看作是状态指示器。返回0表示执行成功,返回其他值表示执行失败,其中非0值的具体含义依机器而定。 +`main`函数的返回值可以看作是状态指示器。返回0表示执行成功,返回其他值表示执行失败,其中非0值的具体含义依机器而定。 -为了使main函数的返回值与机器无关,头文件*cstdlib*定义了`EXIT_SUCCESS`和`EXIT_FAILURE`这两个预处理变量,分别表示执行成功和失败。 +为了使`main`函数的返回值与机器无关,头文件*cstdlib*定义了`EXIT_SUCCESS`和`EXIT_FAILURE`这两个预处理变量,分别表示执行成功和失败。 ```c++ int main() @@ -303,7 +303,7 @@ int main() } ``` -建议使用预处理变量EXIT_SUCCESS和EXIT_FAILURE表示main函数的执行结果。 +建议使用预处理变量`EXIT_SUCCESS`和`EXIT_FAILURE`表示`main`函数的执行结果。 如果一个函数调用了它自身,不管这种调用是直接的还是间接的,都称该函数为递归函数(recursive function)。 @@ -321,7 +321,7 @@ int factorial(int val) 相对于循环迭代,递归的效率较低。但在某些情况下使用递归可以增加代码的可读性。循环迭代适合处理线性问题(如链表,每个节点有唯一前驱、唯一后继),而递归适合处理非线性问题(如树,每个节点的前驱、后继不唯一)。 -main函数不能调用它自身。 +`main`函数不能调用它自身。 ### 返回数组指针(Returning a Pointer to an Array) @@ -335,7 +335,7 @@ Type (*function(parameter_list))[dimension] 其中*Type*表示元素类型,*dimension*表示数组大小,*(\*function (parameter_list))*两端的括号必须存在。 -C++11允许使用尾置返回类型(trailing return type)简化复杂函数声明。尾置返回类型跟在形参列表后面,并以一个`->`符号开头。为了表示函数真正的返回类型在形参列表之后,需要在本应出现返回类型的地方添加auto关键字。 +C++11允许使用尾置返回类型(trailing return type)简化复杂函数声明。尾置返回类型跟在形参列表后面,并以一个`->`符号开头。为了表示函数真正的返回类型在形参列表之后,需要在本应出现返回类型的地方添加`auto`关键字。 ```c++ // fcn takes an int argument and returns a pointer to an array of ten ints @@ -344,7 +344,7 @@ auto func(int i) -> int(*)[10]; 任何函数的定义都能使用尾置返回类型,但是这种形式更适用于返回类型比较复杂的函数。 -如果我们知道函数返回的指针将指向哪个数组,就可以使用decltype关键字声明返回类型。但decltype并不会把数组类型转换成指针类型,所以还要在函数声明中添加一个`*`符号。 +如果我们知道函数返回的指针将指向哪个数组,就可以使用`decltype`关键字声明返回类型。但`decltype`并不会把数组类型转换成指针类型,所以还要在函数声明中添加一个`*`符号。 ```c++ int odd[] = {1,3,5,7,9}; @@ -360,11 +360,11 @@ decltype(odd) *arrPtr(int i) 同一作用域内的几个名字相同但形参列表不同的函数叫做重载函数。 -main函数不能重载。 +`main`函数不能重载。 不允许两个函数除了返回类型以外的其他所有要素都相同。 -顶层const不影响传入函数的对象,一个拥有顶层const的形参无法和另一个没有顶层const的形参区分开来。 +顶层`const`不影响传入函数的对象,一个拥有顶层`const`的形参无法和另一个没有顶层`const`的形参区分开来。 ```c++ Record lookup(Phone); @@ -373,7 +373,7 @@ Record lookup(Phone*); Record lookup(Phone* const); // redeclares Record lookup(Phone*) ``` -如果形参是某种类型的指针或引用,则通过区分其指向的对象是常量还是非常量可以实现函数重载,此时的const是底层的。当我们传递给重载函数一个非常量对象或者指向非常量对象的指针时,编译器会优先选用非常量版本的函数。 +如果形参是某种类型的指针或引用,则通过区分其指向的对象是常量还是非常量可以实现函数重载,此时的`const`是底层的。当我们传递给重载函数一个非常量对象或者指向非常量对象的指针时,编译器会优先选用非常量版本的函数。 ```c++ // functions taking const and nonconst references or pointers have different parameters @@ -384,7 +384,7 @@ Record lookup(Account*); // new function, takes a pointer to Account Record lookup(const Account*); // new function, takes a pointer to const ``` -const_cast可以用于函数的重载。当函数的实参不是常量时,将得到普通引用。 +`const_cast`可以用于函数的重载。当函数的实参不是常量时,将得到普通引用。 ```c++ // return a reference to the shorter of two strings @@ -506,11 +506,11 @@ inline const string &horterString(const string &s1, const string &s2) } ``` -在函数声明和定义中都能使用关键字inline,但是建议只在函数定义时使用。 +在函数声明和定义中都能使用关键字`inline`,但是建议只在函数定义时使用。 -一般来说,内联机制适用于优化规模较小、流程直接、调用频繁的函数。内联函数中不允许有循环语句和switch语句,否则函数会被编译为普通函数。 +一般来说,内联机制适用于优化规模较小、流程直接、调用频繁的函数。内联函数中不允许有循环语句和`switch`语句,否则函数会被编译为普通函数。 -`constexpr`函数是指能用于常量表达式的函数。constexpr函数的返回类型及所有形参的类型都得是字面值类型。另外C++11标准要求constexpr函数体中必须有且只有一条return语句,但是此限制在C++14标准中被删除。 +`constexpr`函数是指能用于常量表达式的函数。`constexpr`函数的返回类型及所有形参的类型都得是字面值类型。另外C++11标准要求`constexpr`函数体中必须有且只有一条`return`语句,但是此限制在C++14标准中被删除。 ```c++ constexpr int new_sz() @@ -521,7 +521,7 @@ constexpr int new_sz() constexpr int foo = new_sz(); // ok: foo is a constant expression ``` -constexpr函数的返回值可以不是一个常量。 +`constexpr`函数的返回值可以不是一个常量。 ```c++ // scale(arg) is a constant expression if arg is a constant expression @@ -535,19 +535,19 @@ int i = 2; // i is not a constant expression int a2[scale(i)]; // error: scale(i) is not a constant expression ``` -constexpr函数被隐式地指定为内联函数。 +`constexpr`函数被隐式地指定为内联函数。 -和其他函数不同,内联函数和constexpr函数可以在程序中多次定义。因为在编译过程中,编译器需要函数的定义来随时展开函数。对于某个给定的内联函数或constexpr函数,它的多个定义必须完全一致。因此内联函数和constexpr函数通常定义在头文件中。 +和其他函数不同,内联函数和`constexpr`函数可以在程序中多次定义。因为在编译过程中,编译器需要函数的定义来随时展开函数。对于某个给定的内联函数或`constexpr`函数,它的多个定义必须完全一致。因此内联函数和`constexpr`函数通常定义在头文件中。 ### 调试帮助(Aids for Debugging) -| 变量名称 | 内容 | -| :----------: | :----------: | -| \_\_func\_\_ | 当前函数名称 | -| \_\_FILE\_\_ | 当前文件名称 | -| \_\_LINE\_\_ | 当前行号 | -| \_\_TIME\_\_ | 文件编译时间 | -| \_\_DATE\_\_ | 文件编译日期 | +| 变量名称 | 内容 | +| :--------: | :----------: | +| `__func__` | 当前函数名称 | +| `__FILE__` | 当前文件名称 | +| `__LINE__` | 当前行号 | +| `__TIME__` | 文件编译时间 | +| `__DATE__` | 文件编译日期 | ## 函数匹配(Function Matching) @@ -563,7 +563,7 @@ constexpr函数被隐式地指定为内联函数。 所有算术类型转换的级别都一样。 -如果载函数的区别在于它们的引用或指针类型的形参是否含有底层const,则调用发生时编译器通过实参是否是常量来决定函数的版本。 +如果载函数的区别在于它们的引用或指针类型的形参是否含有底层`const`,则调用发生时编译器通过实参是否是常量来决定函数的版本。 ```c++ Record lookup(Account&); // function that takes a reference to Account @@ -617,6 +617,6 @@ void useBigger(const string &s1, const string &s2, bool (*pf)(const string &, co useBigger(s1, s2, lengthCompare); ``` -关键字decltype作用于函数时,返回的是函数类型,而不是函数指针类型。 +关键字`decltype`作用于函数时,返回的是函数类型,而不是函数指针类型。 函数可以返回指向函数的指针。但返回类型不会像函数类型的形参一样自动地转换成指针,必须显式地将其指定为指针类型。 \ No newline at end of file diff --git a/Chapter-7/README.md b/Chapter-7 Classes/README.md similarity index 88% rename from Chapter-7/README.md rename to Chapter-7 Classes/README.md index 9e6b25a..288a905 100644 --- a/Chapter-7/README.md +++ b/Chapter-7 Classes/README.md @@ -27,7 +27,7 @@ struct Sales_data }; ``` -成员函数通过一个名为`this`的隐式额外参数来访问调用它的对象。this参数是一个常量指针,被初始化为调用该函数的对象地址。在函数体内可以显式使用this指针。 +成员函数通过一个名为`this`的隐式额外参数来访问调用它的对象。`this`参数是一个常量指针,被初始化为调用该函数的对象地址。在函数体内可以显式使用`this`指针。 ```c++ total.isbn() @@ -38,9 +38,9 @@ std::string isbn() const { return this->bookNo; } std::string isbn() const { return bookNo; } ``` -默认情况下,this的类型是指向类类型非常量版本的常量指针。this也遵循初始化规则,所以默认不能把this绑定到一个常量对象上,即不能在常量对象上调用普通的成员函数。 +默认情况下,`this`的类型是指向类类型非常量版本的常量指针。`this`也遵循初始化规则,所以默认不能把`this`绑定到一个常量对象上,即不能在常量对象上调用普通的成员函数。 -C++允许在成员函数的参数列表后面添加关键字`const`,表示this是一个指向常量的指针。使用关键字const的成员函数被称作常量成员函数(const member function)。 +C++允许在成员函数的参数列表后面添加关键字`const`,表示`this`是一个指向常量的指针。使用关键字`const`的成员函数被称作常量成员函数(const member function)。 ```c++ // pseudo-code illustration of how the implicit this pointer is used @@ -56,7 +56,7 @@ std::string Sales_data::isbn(const Sales_data *const this) 类本身就是一个作用域,成员函数的定义嵌套在类的作用域之内。编译器处理类时,会先编译成员声明,再编译成员函数体(如果有的话),因此成员函数可以随意使用类的其他成员而无须在意这些成员的出现顺序。 -在类的外部定义成员函数时,成员函数的定义必须与它的声明相匹配。如果成员函数被声明为常量成员函数,那么它的定义也必须在参数列表后面指定const属性。同时,类外部定义的成员名字必须包含它所属的类名。 +在类的外部定义成员函数时,成员函数的定义必须与它的声明相匹配。如果成员函数被声明为常量成员函数,那么它的定义也必须在参数列表后面指定`const`属性。同时,类外部定义的成员名字必须包含它所属的类名。 ```c++ double Sales_data::avg_price() const @@ -68,7 +68,7 @@ double Sales_data::avg_price() const } ``` -可以定义返回this对象的成员函数。 +可以定义返回`this`对象的成员函数。 ```c++ Sales_data& Sales_data::combine(const Sales_data &rhs) @@ -109,7 +109,7 @@ ostream &print(ostream &os, const Sales_data &item) 类通过一个或几个特殊的成员函数来控制其对象的初始化操作,这些函数被称作构造函数。只要类的对象被创建,就会执行构造函数。 -构造函数的名字和类名相同,没有返回类型,且不能被声明为const函数。构造函数在const对象的构造过程中可以向其写值。 +构造函数的名字和类名相同,没有返回类型,且不能被声明为`const`函数。构造函数在`const`对象的构造过程中可以向其写值。 ```c++ struct Sales_data @@ -137,7 +137,7 @@ struct Sales_data - 如果类包含内置类型或者复合类型的成员,则只有当这些成员全部存在类内初始值时,这个类才适合使用合成的默认构造函数。否则用户在创建类的对象时就可能得到未定义的值。 - 编译器不能为某些类合成默认构造函数。例如类中包含一个其他类类型的成员,且该类型没有默认构造函数,那么编译器将无法初始化该成员。 -在C++11中,如果类需要默认的函数行为,可以通过在参数列表后面添加`=default`来要求编译器生成构造函数。其中=default既可以和函数声明一起出现在类的内部,也可以作为定义出现在类的外部。和其他函数一样,如果=default在类的内部,则默认构造函数是内联的。 +在C++11中,如果类需要默认的函数行为,可以通过在参数列表后面添加`=default`来要求编译器生成构造函数。其中`=default`既可以和函数声明一起出现在类的内部,也可以作为定义出现在类的外部。和其他函数一样,如果`=default`在类的内部,则默认构造函数是内联的。 ```c++ Sales_data() = default; @@ -167,8 +167,8 @@ Sales_data(const std::string &s): 使用访问说明符(access specifier)可以加强类的封装性: -- 定义在`public`说明符之后的成员在整个程序内都可以被访问。public成员定义类的接口。 -- 定义在`private`说明符之后的成员可以被类的成员函数访问,但是不能被使用该类的代码访问。private部分封装了类的实现细节。 +- 定义在`public`说明符之后的成员在整个程序内都可以被访问。`public`成员定义类的接口。 +- 定义在`private`说明符之后的成员可以被类的成员函数访问,但是不能被使用该类的代码访问。`private`部分封装了类的实现细节。 ```c++ class Sales_data @@ -192,7 +192,7 @@ private: // access specifier added 一个类可以包含零或多个访问说明符,每个访问说明符指定了接下来的成员的访问级别,其有效范围到出现下一个访问说明符或类的结尾处为止。 -使用关键字`struct`定义类时,定义在第一个访问说明符之前的成员是public的;而使用关键字`class`时,这些成员是private的。二者唯一的区别就是默认访问权限不同。 +使用关键字`struct`定义类时,定义在第一个访问说明符之前的成员是`public`的;而使用关键字`class`时,这些成员是`private`的。二者唯一的区别就是默认访问权限不同。 ### 友元(Friends) @@ -245,7 +245,7 @@ std::ostream &print(std::ostream&, const Sales_data&); ### 类成员再探(Class Members Revisited) -由类定义的类型名字和其他成员一样存在访问限制,可以是public或private中的一种。 +由类定义的类型名字和其他成员一样存在访问限制,可以是`public`或`private`中的一种。 ```c++ class Screen @@ -261,11 +261,11 @@ public: 定义在类内部的成员函数是自动内联的。 -如果需要显式声明内联成员函数,建议只在类外部定义的位置说明inline。 +如果需要显式声明内联成员函数,建议只在类外部定义的位置说明`inline`。 -inline成员函数该与类定义在同一个头文件中。 +`inline`成员函数该与类定义在同一个头文件中。 -使用关键字`mutable`可以声明可变数据成员(mutable data member)。可变数据成员永远不会是const的,即使它在const对象内。因此const成员函数可以修改可变成员的值。 +使用关键字`mutable`可以声明可变数据成员(mutable data member)。可变数据成员永远不会是`const`的,即使它在`const`对象内。因此`const`成员函数可以修改可变成员的值。 ```c++ class Screen @@ -288,9 +288,9 @@ void Screen::some_member() const ### 返回\*this的成员函数(Functions That Return \*this) -const成员函数如果以引用形式返回\*this,则返回类型是常量引用。 +`const`成员函数如果以引用形式返回`*this`,则返回类型是常量引用。 -通过区分成员函数是否为const的,可以对其进行重载。在常量对象上只能调用const版本的函数;在非常量对象上,尽管两个版本都能调用,但会选择非常量版本。 +通过区分成员函数是否为`const`的,可以对其进行重载。在常量对象上只能调用`const`版本的函数;在非常量对象上,尽管两个版本都能调用,但会选择非常量版本。 ```c++ class Screen @@ -450,7 +450,7 @@ private: }; ``` -可以通过作用域运算符`::`或显式this指针来强制访问被隐藏的类成员。 +可以通过作用域运算符`::`或显式`this`指针来强制访问被隐藏的类成员。 ```c++ // bad practice: names local to member functions shouldn't hide member names @@ -474,7 +474,7 @@ void Screen::dummy_fcn(pos ht) 如果没有在构造函数初始值列表中显式初始化成员,该成员会在构造函数体之前执行默认初始化。 -如果成员是const、引用,或者是某种未定义默认构造函数的类类型,必须在初始值列表中将其初始化。 +如果成员是`const`、引用,或者是某种未定义默认构造函数的类类型,必须在初始值列表中将其初始化。 ```c++ class ConstRef @@ -577,11 +577,11 @@ public: }; ``` -explicit关键字只对接受一个实参的构造函数有效。 +`explicit`关键字只对接受一个实参的构造函数有效。 -只能在类内声明构造函数时使用explicit关键字,在类外定义时不能重复。 +只能在类内声明构造函数时使用`explicit`关键字,在类外定义时不能重复。 -执行拷贝初始化时(使用`=`)会发生隐式转换,所以explicit构造函数只能用于直接初始化。 +执行拷贝初始化时(使用`=`)会发生隐式转换,所以`explicit`构造函数只能用于直接初始化。 ```c++ Sales_data item1 (null_book); // ok: direct initialization @@ -589,7 +589,7 @@ Sales_data item1 (null_book); // ok: direct initialization Sales_data item2 = null_book; ``` -可以使用explicit构造函数显式地强制转换类型。 +可以使用`explicit`构造函数显式地强制转换类型。 ```c++ // ok: the argument is an explicitly constructed Sales_data object @@ -602,7 +602,7 @@ item.combine(static_cast(cin)); 聚合类满足如下条件: -- 所有成员都是public的。 +- 所有成员都是`public`的。 - 没有定义任何构造函数。 - 没有类内初始值。 - 没有基类。 @@ -628,13 +628,13 @@ Data val1 = { 0, "Anna" }; 数据成员都是字面值类型的聚合类是字面值常量类。或者一个类不是聚合类,但符合下列条件,则也是字面值常量类: - 数据成员都是字面值类型。 -- 类至少含有一个constexpr构造函数。 -- 如果数据成员含有类内初始值,则内置类型成员的初始值必须是常量表达式。如果成员属于类类型,则初始值必须使用成员自己的constexpr构造函数。 +- 类至少含有一个`constexpr`构造函数。 +- 如果数据成员含有类内初始值,则内置类型成员的初始值必须是常量表达式。如果成员属于类类型,则初始值必须使用成员自己的`constexpr`构造函数。 - 类必须使用析构函数的默认定义。 -constexpr构造函数用于生成constexpr对象以及constexpr函数的参数或返回类型。 +`constexpr`构造函数用于生成`constexpr`对象以及`constexpr`函数的参数或返回类型。 -constexpr构造函数必须初始化所有数据成员,初始值使用constexpr构造函数或常量表达式。 +`constexpr`构造函数必须初始化所有数据成员,初始值使用`constexpr`构造函数或常量表达式。 ## 类的静态成员(static Class Members) @@ -656,7 +656,7 @@ private: }; ``` -由于静态成员不与任何对象绑定,因此静态成员函数不能声明为const的,也不能在静态成员函数内使用this指针。 +由于静态成员不与任何对象绑定,因此静态成员函数不能声明为`const`的,也不能在静态成员函数内使用`this`指针。 用户代码可以使用作用域运算符访问静态成员,也可以通过类对象、引用或指针访问。类的成员函数可以直接访问静态成员。 @@ -680,7 +680,7 @@ private: }; ``` -在类外部定义静态成员时,不能重复static关键字,其只能用于类内部的声明语句。 +在类外部定义静态成员时,不能重复`static`关键字,其只能用于类内部的声明语句。 由于静态数据成员不属于类的任何一个对象,因此它们并不是在创建类对象时被定义的。通常情况下,不应该在类内部初始化静态成员。而必须在类外部定义并初始化每个静态成员。一个静态成员只能被定义一次。一旦它被定义,就会一直存在于程序的整个生命周期中。 @@ -691,7 +691,7 @@ double Account::interestRate = initRate(); 建议把静态数据成员的定义与其他非内联函数的定义放在同一个源文件中,这样可以确保对象只被定义一次。 -尽管在通常情况下,不应该在类内部初始化静态成员。但是可以为静态成员提供const整数类型的类内初始值,不过要求静态成员必须是字面值常量类型的constexpr。初始值必须是常量表达式。 +尽管在通常情况下,不应该在类内部初始化静态成员。但是可以为静态成员提供`const`整数类型的类内初始值,不过要求静态成员必须是字面值常量类型的`constexpr`。初始值必须是常量表达式。 ```c++ class Account diff --git a/Chapter-8/Image/8-1.png b/Chapter-8 The IO Library/Image/8-1.png similarity index 100% rename from Chapter-8/Image/8-1.png rename to Chapter-8 The IO Library/Image/8-1.png diff --git a/Chapter-8/Image/8-2.png b/Chapter-8 The IO Library/Image/8-2.png similarity index 100% rename from Chapter-8/Image/8-2.png rename to Chapter-8 The IO Library/Image/8-2.png diff --git a/Chapter-8/Image/8-3.png b/Chapter-8 The IO Library/Image/8-3.png similarity index 100% rename from Chapter-8/Image/8-3.png rename to Chapter-8 The IO Library/Image/8-3.png diff --git a/Chapter-8/Image/8-4.png b/Chapter-8 The IO Library/Image/8-4.png similarity index 100% rename from Chapter-8/Image/8-4.png rename to Chapter-8 The IO Library/Image/8-4.png diff --git a/Chapter-8/Image/8-5.png b/Chapter-8 The IO Library/Image/8-5.png similarity index 100% rename from Chapter-8/Image/8-5.png rename to Chapter-8 The IO Library/Image/8-5.png diff --git a/Chapter-8/README.md b/Chapter-8 The IO Library/README.md similarity index 62% rename from Chapter-8/README.md rename to Chapter-8 The IO Library/README.md index 185ace0..1519183 100644 --- a/Chapter-8/README.md +++ b/Chapter-8 The IO Library/README.md @@ -4,20 +4,20 @@ - `istream`:输入流类型,提供输入操作。 - `ostream`:输出流类型,提供输出操作。 -- `cin`:istream对象,从标准输入读取数据。 -- `cout`:ostream对象,向标准输出写入数据。 -- `cerr`:ostream对象,向标准错误写入数据。 -- `>>`运算符:从istream对象读取输入数据。 -- `<<`运算符:向ostream对象写入输出数据。 -- `getline`函数:从istream对象读取一行数据,写入string对象。 +- `cin`:`istream`对象,从标准输入读取数据。 +- `cout`:`ostream`对象,向标准输出写入数据。 +- `cerr`:`ostream`对象,向标准错误写入数据。 +- `>>`运算符:从`istream`对象读取输入数据。 +- `<<`运算符:向`ostream`对象写入输出数据。 +- `getline`函数:从`istream`对象读取一行数据,写入`string`对象。 ## IO类(The IO Classes) -头文件*iostream*定义了用于读写流的基本类型,*fstream*定义了读写命名文件的类型,*sstream*定义了读写内存中string对象的类型。 +头文件*iostream*定义了用于读写流的基本类型,*fstream*定义了读写命名文件的类型,*sstream*定义了读写内存中`string`对象的类型。 ![8-1](Image/8-1.png) -宽字符版本的IO类型和函数的名字以`w`开始,如`wcin`、`wcout`和`wcerr`分别对应cin、cout和cerr。它们与其对应的普通char版本都定义在同一个头文件中,如头文件*fstream*定义了`ifstream`和`wifstream`类型。 +宽字符版本的IO类型和函数的名字以`w`开始,如`wcin`、`wcout`和`wcerr`分别对应`cin`、`cout`和`cerr`。它们与其对应的普通`char`版本都定义在同一个头文件中,如头文件*fstream*定义了`ifstream`和`wifstream`类型。 可以将派生类的对象当作其基类的对象使用。 @@ -32,7 +32,7 @@ ofstream print(ofstream); // error: can't initialize the ofstream parameter out2 = print(out2); // error: cannot copy stream objects ``` -由于IO对象不能拷贝,因此不能将函数形参或返回类型定义为流类型。进行IO操作的函数通常以引用方式传递和返回流。读写一个IO对象会改变其状态,因此传递和返回的引用不能是const的。 +由于IO对象不能拷贝,因此不能将函数形参或返回类型定义为流类型。进行IO操作的函数通常以引用方式传递和返回流。读写一个IO对象会改变其状态,因此传递和返回的引用不能是`const`的。 ### 条件状态(Condition States) @@ -40,16 +40,16 @@ IO库条件状态: ![8-2](Image/8-2.png) -`badbit`表示系统级错误,如不可恢复的读写错误。通常情况下,一旦badbit被置位,流就无法继续使用了。在发生可恢复错误后,`failbit`会被置位,如期望读取数值却读出一个字符。如果到达文件结束位置,`eofbit`和failbit都会被置位。如果流未发生错误,则`goodbit`的值为0。如果badbit、failbit和eofbit任何一个被置位,检测流状态的条件都会失败。 +`badbit`表示系统级错误,如不可恢复的读写错误。通常情况下,一旦`badbit`被置位,流就无法继续使用了。在发生可恢复错误后,`failbit`会被置位,如期望读取数值却读出一个字符。如果到达文件结束位置,`eofbit`和`failbit`都会被置位。如果流未发生错误,则`goodbit`的值为0。如果`badbit`、`failbit`和`eofbit`任何一个被置位,检测流状态的条件都会失败。 ```c++ while (cin >> word) // ok: read operation successful... ``` -`good`函数在所有错误均未置位时返回true。而`bad`、`fail`和`eof`函数在对应错误位被置位时返回true。此外,在badbit被置位时,fail函数也会返回true。因此应该使用good或fail函数确定流的总体状态,eof和bad只能检测特定错误。 +`good`函数在所有错误均未置位时返回`true`。而`bad`、`fail`和`eof`函数在对应错误位被置位时返回`true`。此外,在`badbit`被置位时,`fail`函数也会返回`true`。因此应该使用`good`或`fail`函数确定流的总体状态,`eof`和`bad`只能检测特定错误。 -流对象的`rdstate`成员返回一个`iostate`值,表示流的当前状态。`setstate`成员用于将指定条件置位(叠加原始流状态)。`clear`成员的无参版本清除所有错误标志;含参版本接受一个iostate值,用于设置流的新状态(覆盖原始流状态)。 +流对象的`rdstate`成员返回一个`iostate`值,表示流的当前状态。`setstate`成员用于将指定条件置位(叠加原始流状态)。`clear`成员的无参版本清除所有错误标志;含参版本接受一个`iostate`值,用于设置流的新状态(覆盖原始流状态)。 ```c++ // remember the current state of cin @@ -66,8 +66,8 @@ cin.setstate(old_state); // now reset cin to its old state - 程序正常结束。 - 缓冲区已满。 - 使用操纵符(如`endl`)显式刷新缓冲区。 -- 在每个输出操作之后,可以用`unitbuf`操纵符设置流的内部状态,从而清空缓冲区。默认情况下,对cerr是设置unitbuf的,因此写到cerr的内容都是立即刷新的。 -- 一个输出流可以被关联到另一个流。这种情况下,当读写被关联的流时,关联到的流的缓冲区会被刷新。默认情况下,cin和cerr都关联到cout,因此,读cin或写cerr都会刷新cout的缓冲区。 +- 在每个输出操作之后,可以用`unitbuf`操纵符设置流的内部状态,从而清空缓冲区。默认情况下,对`cerr`是设置`unitbuf`的,因此写到`cerr`的内容都是立即刷新的。 +- 一个输出流可以被关联到另一个流。这种情况下,当读写被关联的流时,关联到的流的缓冲区会被刷新。默认情况下,`cin`和`cerr`都关联到`cout`,因此,读`cin`或写`cerr`都会刷新`cout`的缓冲区。 `flush`操纵符刷新缓冲区,但不输出任何额外字符。`ends`向缓冲区插入一个空字符,然后刷新缓冲区。 @@ -77,7 +77,7 @@ cout << "hi!" << flush; // writes hi, then flushes the buffer; adds no data cout << "hi!" << ends; // writes hi and a null, then flushes the buffer ``` -如果想在每次输出操作后都刷新缓冲区,可以使用unitbuf操纵符。它令流在接下来的每次写操作后都进行一次flush操作。而`nounitbuf`操纵符则使流恢复使用正常的缓冲区刷新机制。 +如果想在每次输出操作后都刷新缓冲区,可以使用`unitbuf`操纵符。它令流在接下来的每次写操作后都进行一次`flush`操作。而`nounitbuf`操纵符则使流恢复使用正常的缓冲区刷新机制。 ```C++ cout << unitbuf; // all writes will be flushed immediately @@ -87,7 +87,7 @@ cout << nounitbuf; // returns to normal buffering 如果程序异常终止,输出缓冲区不会被刷新。 -当一个输入流被关联到一个输出流时,任何试图从输入流读取数据的操作都会先刷新关联的输出流。标准库将cout和cin关联在一起,因此下面的语句会导致cout的缓冲区被刷新: +当一个输入流被关联到一个输出流时,任何试图从输入流读取数据的操作都会先刷新关联的输出流。标准库将`cout`和`cin`关联在一起,因此下面的语句会导致`cout`的缓冲区被刷新: ```c++ cin >> ival; @@ -95,7 +95,7 @@ cin >> ival; 交互式系统通常应该关联输入流和输出流。这意味着包括用户提示信息在内的所有输出,都会在读操作之前被打印出来。 -使用`tie`函数可以关联两个流。它有两个重载版本:无参版本返回指向输出流的指针。如果本对象已关联到一个输出流,则返回的就是指向这个流的指针,否则返回空指针。tie的第二个版本接受一个指向ostream的指针,将本对象关联到此ostream。 +使用`tie`函数可以关联两个流。它有两个重载版本:无参版本返回指向输出流的指针。如果本对象已关联到一个输出流,则返回的就是指向这个流的指针,否则返回空指针。`tie`的第二个版本接受一个指向`ostream`的指针,将本对象关联到此`ostream`。 ```c++ cin.tie(&cout); // illustration only: the library ties cin and cout for us @@ -106,7 +106,7 @@ cin.tie(&cerr); // reading cin flushes cerr, not cout cin.tie(old_tie); // reestablish normal tie between cin and cout ``` -每个流同时最多关联一个流,但多个流可以同时关联同一个ostream。向tie传递空指针可以解开流的关联。 +每个流同时最多关联一个流,但多个流可以同时关联同一个`ostream`。向`tie`传递空指针可以解开流的关联。 ## 文件输入输出(File Input and Output) @@ -118,22 +118,22 @@ cin.tie(old_tie); // reestablish normal tie between cin and cout 每个文件流类型都定义了`open`函数,它完成一些系统操作,定位指定文件,并视情况打开为读或写模式。 -创建文件流对象时,如果提供了文件名(可选),open会被自动调用。 +创建文件流对象时,如果提供了文件名(可选),`open`会被自动调用。 ```C++ ifstream in(ifile); // construct an ifstream and open the given file ofstream out; // output file stream that is not associated with any file ``` -在C++11中,文件流对象的文件名可以是string对象或C风格字符数组。旧版本的标准库只支持C风格字符数组。 +在C++11中,文件流对象的文件名可以是`string`对象或C风格字符数组。旧版本的标准库只支持C风格字符数组。 -在要求使用基类对象的地方,可以用继承类型的对象代替。因此一个接受iostream类型引用或指针参数的函数,可以用对应的fstream类型来调用。 +在要求使用基类对象的地方,可以用继承类型的对象代替。因此一个接受`iostream`类型引用或指针参数的函数,可以用对应的`fstream`类型来调用。 -可以先定义空文件流对象,再调用open函数将其与指定文件关联。如果open调用失败,failbit会被置位。 +可以先定义空文件流对象,再调用`open`函数将其与指定文件关联。如果`open`调用失败,`failbit`会被置位。 -对一个已经打开的文件流调用open会失败,并导致failbit被置位。随后试图使用文件流的操作都会失败。如果想将文件流关联到另一个文件,必须先调用`close`关闭当前文件,再调用`clear`重置流的条件状态(close不会重置流的条件状态)。 +对一个已经打开的文件流调用`open`会失败,并导致`failbit`被置位。随后试图使用文件流的操作都会失败。如果想将文件流关联到另一个文件,必须先调用`close`关闭当前文件,再调用`clear`重置流的条件状态(`close`不会重置流的条件状态)。 -当fstream对象被销毁时,close会自动被调用。 +当`fstream`对象被销毁时,`close`会自动被调用。 ### 文件模式(File Modes) @@ -141,15 +141,15 @@ ofstream out; // output file stream that is not associated with any file ![8-4](Image/8-4.png) -- 只能对ofstream或fstream对象设定`out`模式。 -- 只能对ifstream或fstream对象设定`in`模式。 -- 只有当out被设定时才能设定`trunc`模式。 -- 只要trunc没被设定,就能设定`app`模式。在app模式下,即使没有设定out模式,文件也是以输出方式打开。 -- 默认情况下,即使没有设定trunc,以out模式打开的文件也会被截断。如果想保留以out模式打开的文件内容,就必须同时设定app模式,这会将数据追加写到文件末尾;或者同时设定in模式,即同时进行读写操作。 +- 只能对`ofstream`或`fstream`对象设定`out`模式。 +- 只能对`ifstream`或`fstream`对象设定`in`模式。 +- 只有当`out`被设定时才能设定`trunc`模式。 +- 只要`trunc`没被设定,就能设定`app`模式。在`app`模式下,即使没有设定`out`模式,文件也是以输出方式打开。 +- 默认情况下,即使没有设定`trunc`,以`out`模式打开的文件也会被截断。如果想保留以`out`模式打开的文件内容,就必须同时设定`app`模式,这会将数据追加写到文件末尾;或者同时设定`in`模式,即同时进行读写操作。 - `ate`和`binary`模式可用于任何类型的文件流对象,并可以和其他任何模式组合使用。 -- 与ifstream对象关联的文件默认以in模式打开,与ofstream对象关联的文件默认以out模式打开,与fstream对象关联的文件默认以in和out模式打开。 +- 与`ifstream`对象关联的文件默认以`in`模式打开,与`ofstream`对象关联的文件默认以`out`模式打开,与`fstream`对象关联的文件默认以`in`和`out`模式打开。 -默认情况下,打开ofstream对象时,文件内容会被丢弃,阻止文件清空的方法是同时指定app或in模式。 +默认情况下,打开`ofstream`对象时,文件内容会被丢弃,阻止文件清空的方法是同时指定`app`或`in`模式。 流对象每次打开文件时都可以改变其文件模式。 @@ -163,7 +163,7 @@ out.close(); ## string流(string Streams) -头文件*sstream*定义了三个类型来支持内存IO:`istringstream`从string读取数据,`ostringstream`向string写入数据,`stringstream`可以同时读写string的数据。 +头文件*sstream*定义了三个类型来支持内存IO:`istringstream`从`string`读取数据,`ostringstream`向`string`写入数据,`stringstream`可以同时读写`string`的数据。 ![8-5](Image/8-5.png) diff --git a/Chapter-9/Image/9-1.png b/Chapter-9 Sequential Containers/Image/9-1.png similarity index 100% rename from Chapter-9/Image/9-1.png rename to Chapter-9 Sequential Containers/Image/9-1.png diff --git a/Chapter-9/Image/9-10.png b/Chapter-9 Sequential Containers/Image/9-10.png similarity index 100% rename from Chapter-9/Image/9-10.png rename to Chapter-9 Sequential Containers/Image/9-10.png diff --git a/Chapter-9/Image/9-11.png b/Chapter-9 Sequential Containers/Image/9-11.png similarity index 100% rename from Chapter-9/Image/9-11.png rename to Chapter-9 Sequential Containers/Image/9-11.png diff --git a/Chapter-9/Image/9-12.png b/Chapter-9 Sequential Containers/Image/9-12.png similarity index 100% rename from Chapter-9/Image/9-12.png rename to Chapter-9 Sequential Containers/Image/9-12.png diff --git a/Chapter-9/Image/9-13.png b/Chapter-9 Sequential Containers/Image/9-13.png similarity index 100% rename from Chapter-9/Image/9-13.png rename to Chapter-9 Sequential Containers/Image/9-13.png diff --git a/Chapter-9/Image/9-14.png b/Chapter-9 Sequential Containers/Image/9-14.png similarity index 100% rename from Chapter-9/Image/9-14.png rename to Chapter-9 Sequential Containers/Image/9-14.png diff --git a/Chapter-9/Image/9-15.png b/Chapter-9 Sequential Containers/Image/9-15.png similarity index 100% rename from Chapter-9/Image/9-15.png rename to Chapter-9 Sequential Containers/Image/9-15.png diff --git a/Chapter-9/Image/9-16.png b/Chapter-9 Sequential Containers/Image/9-16.png similarity index 100% rename from Chapter-9/Image/9-16.png rename to Chapter-9 Sequential Containers/Image/9-16.png diff --git a/Chapter-9/Image/9-17.png b/Chapter-9 Sequential Containers/Image/9-17.png similarity index 100% rename from Chapter-9/Image/9-17.png rename to Chapter-9 Sequential Containers/Image/9-17.png diff --git a/Chapter-9/Image/9-18.png b/Chapter-9 Sequential Containers/Image/9-18.png similarity index 100% rename from Chapter-9/Image/9-18.png rename to Chapter-9 Sequential Containers/Image/9-18.png diff --git a/Chapter-9/Image/9-19.png b/Chapter-9 Sequential Containers/Image/9-19.png similarity index 100% rename from Chapter-9/Image/9-19.png rename to Chapter-9 Sequential Containers/Image/9-19.png diff --git a/Chapter-9/Image/9-2.png b/Chapter-9 Sequential Containers/Image/9-2.png similarity index 100% rename from Chapter-9/Image/9-2.png rename to Chapter-9 Sequential Containers/Image/9-2.png diff --git a/Chapter-9/Image/9-20.png b/Chapter-9 Sequential Containers/Image/9-20.png similarity index 100% rename from Chapter-9/Image/9-20.png rename to Chapter-9 Sequential Containers/Image/9-20.png diff --git a/Chapter-9/Image/9-21.png b/Chapter-9 Sequential Containers/Image/9-21.png similarity index 100% rename from Chapter-9/Image/9-21.png rename to Chapter-9 Sequential Containers/Image/9-21.png diff --git a/Chapter-9/Image/9-3.png b/Chapter-9 Sequential Containers/Image/9-3.png similarity index 100% rename from Chapter-9/Image/9-3.png rename to Chapter-9 Sequential Containers/Image/9-3.png diff --git a/Chapter-9/Image/9-4.png b/Chapter-9 Sequential Containers/Image/9-4.png similarity index 100% rename from Chapter-9/Image/9-4.png rename to Chapter-9 Sequential Containers/Image/9-4.png diff --git a/Chapter-9/Image/9-5.png b/Chapter-9 Sequential Containers/Image/9-5.png similarity index 100% rename from Chapter-9/Image/9-5.png rename to Chapter-9 Sequential Containers/Image/9-5.png diff --git a/Chapter-9/Image/9-6.png b/Chapter-9 Sequential Containers/Image/9-6.png similarity index 100% rename from Chapter-9/Image/9-6.png rename to Chapter-9 Sequential Containers/Image/9-6.png diff --git a/Chapter-9/Image/9-7.png b/Chapter-9 Sequential Containers/Image/9-7.png similarity index 100% rename from Chapter-9/Image/9-7.png rename to Chapter-9 Sequential Containers/Image/9-7.png diff --git a/Chapter-9/Image/9-8.png b/Chapter-9 Sequential Containers/Image/9-8.png similarity index 100% rename from Chapter-9/Image/9-8.png rename to Chapter-9 Sequential Containers/Image/9-8.png diff --git a/Chapter-9/Image/9-9.png b/Chapter-9 Sequential Containers/Image/9-9.png similarity index 100% rename from Chapter-9/Image/9-9.png rename to Chapter-9 Sequential Containers/Image/9-9.png diff --git a/Chapter-9/README.md b/Chapter-9 Sequential Containers/README.md similarity index 53% rename from Chapter-9/README.md rename to Chapter-9 Sequential Containers/README.md index 3732dc9..b111b04 100644 --- a/Chapter-9/README.md +++ b/Chapter-9 Sequential Containers/README.md @@ -13,26 +13,20 @@ | `list` | 双向链表。只支持双向顺序访问。在任何位置插入/删除速度都很快 | | `forward_list` | 单向链表。只支持单向顺序访问。在任何位置插入/删除速度都很快 | | `array` | 固定大小数组。支持快速随机访问。不能添加/删除元素 | -| `string` | 类似vector,但用于保存字符。支持快速随机访问。在尾部插入/删除速度很快 | +| `string` | 类似`vector`,但用于保存字符。支持快速随机访问。在尾部插入/删除速度很快 | -forward_list和array是C++11新增类型。与内置数组相比,array更安全易用。forward_list没有size操作。 +`forward_list`和`array`是C++11新增类型。与内置数组相比,`array`更安全易用。`forward_list`没有`size`操作。 容器选择原则: -- 除非有合适的理由选择其他容器,否则应该使用vector。 - -- 如果程序有很多小的元素,且空间的额外开销很重要,则不要使用list或forward_list。 - -- 如果程序要求随机访问容器元素,则应该使用vector或deque。 - -- 如果程序需要在容器头尾位置插入/删除元素,但不会在中间位置操作,则应该使用deque。 - -- 如果程序只有在读取输入时才需要在容器中间位置插入元素,之后需要随机访问元素。则 - -- - 先确定是否真的需要在容器中间位置插入元素。当处理输入数据时,可以先向vector追加数据,再调用标准库的sort函数重排元素,从而避免在中间位置添加元素。 - - 如果必须在中间位置插入元素,可以在输入阶段使用list。输入完成后将list中的内容拷贝到vector中。 - -- 不确定应该使用哪种容器时,可以先只使用vector和list的公共操作:使用迭代器,不使用下标操作,避免随机访问。这样在必要时选择vector或list都很方便。 +- 除非有合适的理由选择其他容器,否则应该使用`vector`。 +- 如果程序有很多小的元素,且空间的额外开销很重要,则不要使用`list`或`forward_list`。 +- 如果程序要求随机访问容器元素,则应该使用`vector`或`deque`。 +- 如果程序需要在容器头尾位置插入/删除元素,但不会在中间位置操作,则应该使用`deque`。 +- 如果程序只有在读取输入时才需要在容器中间位置插入元素,之后需要随机访问元素。则: + - 先确定是否真的需要在容器中间位置插入元素。当处理输入数据时,可以先向`vector`追加数据,再调用标准库的`sort`函数重排元素,从而避免在中间位置添加元素。 + - 如果必须在中间位置插入元素,可以在输入阶段使用`list`。输入完成后将`list`中的内容拷贝到`vector`中。 +- 不确定应该使用哪种容器时,可以先只使用`vector`和`list`的公共操作:使用迭代器,不使用下标操作,避免随机访问。这样在必要时选择`vector`或`list`都很方便。 ## 容器库概览(Container Library Overview) @@ -42,15 +36,15 @@ forward_list和array是C++11新增类型。与内置数组相比,array更安 ### 迭代器(Iterators) -forward_list类型不支持递减运算符`--`。 +`forward_list`类型不支持递减运算符`--`。 -一个迭代器范围(iterator range)由一对迭代器表示。这两个迭代器通常被称为begin和end,分别指向同一个容器中的元素或尾后地址。end迭代器不会指向范围中的最后一个元素,而是指向尾元素之后的位置。这种元素范围被称为左闭合区间(left-inclusive interval),其标准数学描述为*[begin,end)*。迭代器begin和end必须指向相同的容器,end可以与begin指向相同的位置,但不能指向begin之前的位置(由程序员确保)。 +一个迭代器范围(iterator range)由一对迭代器表示。这两个迭代器通常被称为`begin`和`end`,分别指向同一个容器中的元素或尾后地址。`end`迭代器不会指向范围中的最后一个元素,而是指向尾元素之后的位置。这种元素范围被称为左闭合区间(left-inclusive interval),其标准数学描述为`[begin,end)`。迭代器`begin`和`end`必须指向相同的容器,`end`可以与`begin`指向相同的位置,但不能指向`begin`之前的位置(由程序员确保)。 -假定begin和end构成一个合法的迭代器范围,则: +假定`begin`和`end`构成一个合法的迭代器范围,则: -- 如果begin等于end,则范围为空。 -- 如果begin不等于end,则范围内至少包含一个元素,且begin指向该范围内的第一个元素。 -- 可以递增begin若干次,令begin等于end。 +- 如果`begin`等于`end`,则范围为空。 +- 如果`begin`不等于`end`,则范围内至少包含一个元素,且`begin`指向该范围内的第一个元素。 +- 可以递增`begin`若干次,令`begin`等于`end`。 ```c++ while (begin != end) @@ -68,7 +62,7 @@ while (begin != end) `begin`和`end`操作生成指向容器中第一个元素和尾后地址的迭代器。其常见用途是形成一个包含容器中所有元素的迭代器范围。 -begin和end操作有多个版本:带`r`的版本返回反向迭代器。以`c`开头的版本(C++11新增)返回const迭代器。不以`c`开头的版本都是重载的,当对非常量对象调用这些成员时,返回普通迭代器,对const对象调用时,返回const迭代器。 +`begin`和`end`操作有多个版本:带`r`的版本返回反向迭代器。以`c`开头的版本(C++11新增)返回`const`迭代器。不以`c`开头的版本都是重载的,当对非常量对象调用这些成员时,返回普通迭代器,对`const`对象调用时,返回`const`迭代器。 ```c++ list a = {"Milton", "Shakespeare", "Austen"}; @@ -78,7 +72,7 @@ auto it3 = a.cbegin(); // list::const_iterator auto it4 = a.crbegin(); // list::const_reverse_iterator ``` -当auto与begin或end结合使用时,返回的迭代器类型依赖于容器类型。但调用以`c`开头的版本仍然可以获得const迭代器,与容器是否是常量无关。 +当`auto`与`begin`或`end`结合使用时,返回的迭代器类型依赖于容器类型。但调用以`c`开头的版本仍然可以获得`const`迭代器,与容器是否是常量无关。 当程序不需要写操作时,应该使用`cbegin`和`cend`。 @@ -111,7 +105,7 @@ list authors = {"Milton", "Shakespeare", "Austen"}; vector articles = {"a", "an", "the"}; ``` -定义和使用array类型时,需要同时指定元素类型和容器大小。 +定义和使用`array`类型时,需要同时指定元素类型和容器大小。 ```c++ array // type is: array that holds 42 ints @@ -120,9 +114,9 @@ array::size_type i; // array type includes element type and size array::size_type j; // error: array is not a type ``` -对array进行列表初始化时,初始值的数量不能大于array的大小。如果初始值的数量小于array的大小,则只初始化靠前的元素,剩余元素会被值初始化。如果元素类型是类类型,则该类需要一个默认构造函数。 +对`array`进行列表初始化时,初始值的数量不能大于`array`的大小。如果初始值的数量小于`array`的大小,则只初始化靠前的元素,剩余元素会被值初始化。如果元素类型是类类型,则该类需要一个默认构造函数。 -可以对array进行拷贝或赋值操作,但要求二者的元素类型和大小都相同。 +可以对`array`进行拷贝或赋值操作,但要求二者的元素类型和大小都相同。 ### 赋值和swap(Assignment and swap) @@ -140,9 +134,9 @@ names = oldstyle; // error: container types don't match names.assign(oldstyle.cbegin(), oldstyle.cend()); ``` -由于其旧元素被替换,因此传递给assign的迭代器不能指向调用assign的容器本身。 +由于其旧元素被替换,因此传递给`assign`的迭代器不能指向调用`assign`的容器本身。 -`swap`交换两个相同类型容器的内容。除array外,swap不对任何元素进行拷贝、删除或插入操作,只交换两个容器的内部数据结构,因此可以保证快速完成。 +`swap`交换两个相同类型容器的内容。除`array`外,`swap`不对任何元素进行拷贝、删除或插入操作,只交换两个容器的内部数据结构,因此可以保证快速完成。 ```c++ vector svec1(10); // vector with ten elements @@ -150,9 +144,9 @@ vector svec2(24); // vector with 24 elements swap(svec1, svec2); ``` -赋值相关运算会导致指向左边容器内部的迭代器、引用和指针失效。而swap操作交换容器内容,不会导致迭代器、引用和指针失效(array和string除外)。 +赋值相关运算会导致指向左边容器内部的迭代器、引用和指针失效。而`swap`操作交换容器内容,不会导致迭代器、引用和指针失效(`array`和`string`除外)。 -对于array,swap会真正交换它们的元素。因此在swap操作后,指针、引用和迭代器所绑定的元素不变,但元素值已经被交换。 +对于`array`,`swap`会真正交换它们的元素。因此在`swap`操作后,指针、引用和迭代器所绑定的元素不变,但元素值已经被交换。 ```c++ array a = { 1, 2, 3 }; @@ -167,7 +161,7 @@ while (p != q) } ``` -对于其他容器类型(除string),指针、引用和迭代器在swap操作后仍指向操作前的元素,但这些元素已经属于不同的容器了。 +对于其他容器类型(除`string`),指针、引用和迭代器在`swap`操作后仍指向操作前的元素,但这些元素已经属于不同的容器了。 ```c++ vector a = { 1, 2, 3 }; @@ -182,7 +176,7 @@ while (p != q) } ``` -array不支持assign,也不允许用花括号列表进行赋值。 +`array`不支持`assign`,也不允许用花括号列表进行赋值。 ```c++ array a1 = {0,1,2,3,4,5,6,7,8,9}; @@ -191,17 +185,17 @@ a1 = a2; // replaces elements in a1 a2 = {0}; // error: cannot assign to an array from a braced list ``` -新标准库同时提供了成员和非成员函数版本的swap。非成员版本的swap在泛型编程中非常重要,建议统一使用非成员版本的swap。 +新标准库同时提供了成员和非成员函数版本的`swap`。非成员版本的`swap`在泛型编程中非常重要,建议统一使用非成员版本的`swap`。 ### 容器大小操作(Container Size Operations) -`size`成员返回容器中元素的数量;`empty`当size为0时返回true,否则返回false;`max_size`返回一个大于或等于该类型容器所能容纳的最大元素数量的值。forward_list支持max_size和empty,但不支持size。 +`size`成员返回容器中元素的数量;`empty`当`size`为0时返回`true`,否则返回`false`;`max_size`返回一个大于或等于该类型容器所能容纳的最大元素数量的值。`forward_list`支持`max_size`和`empty`,但不支持`size`。 ### 关系运算符(Relational Operators) 每个容器类型都支持相等运算符(`==`、`!=`)。除无序关联容器外,其他容器都支持关系运算符(`>`、`>=`、`<`、`<=`)。关系运算符两侧的容器类型和保存元素类型都必须相同。 -两个容器的比较实际上是元素的逐对比较,其工作方式与string的关系运算符类似: +两个容器的比较实际上是元素的逐对比较,其工作方式与`string`的关系运算符类似: - 如果两个容器大小相同且所有元素对应相等,则这两个容器相等。 - 如果两个容器大小不同,但较小容器中的每个元素都等于较大容器中的对应元素,则较小容器小于较大容器。 @@ -224,7 +218,7 @@ v1 == v2 // false; v2 has fewer elements than v1 ### 向顺序容器添加元素(Adding Elements to a Sequential Container) -除array外,所有标准库容器都提供灵活的内存管理,在运行时可以动态添加或删除元素。 +除`array`外,所有标准库容器都提供灵活的内存管理,在运行时可以动态添加或删除元素。 ![9-5](Image/9-5.png) @@ -237,7 +231,7 @@ while (cin >> word) container.push_back(word); ``` -`insert`将元素插入到迭代器指定的位置之前。一些不支持push_front的容器可以使用insert将元素插入开始位置。 +`insert`将元素插入到迭代器指定的位置之前。一些不支持`push_front`的容器可以使用`insert`将元素插入开始位置。 ```c++ vector svec; @@ -249,9 +243,9 @@ slist.insert(slist.begin(), "Hello!"); svec.insert(svec.begin(), "Hello!"); ``` -将元素插入到vector、deque或string的任何位置都是合法的,但可能会很耗时。 +将元素插入到`vector`、`deque`或`string`的任何位置都是合法的,但可能会很耗时。 -在新标准库中,接受元素个数或范围的insert版本返回指向第一个新增元素的迭代器,而旧版本中这些操作返回void。如果范围为空,不插入任何元素,insert会返回第一个参数。 +在新标准库中,接受元素个数或范围的`insert`版本返回指向第一个新增元素的迭代器,而旧版本中这些操作返回`void`。如果范围为空,不插入任何元素,`insert`会返回第一个参数。 ```c++ list 1st; @@ -260,7 +254,7 @@ while (cin >> word) iter = 1st.insert(iter, word); // same as calling push_front ``` -新标准库增加了三个直接构造而不是拷贝元素的操作:`emplace_front`、`emplace_back`和`emplace`,其分别对应push_front、push_back和insert。当调用push或insert时,元素对象被拷贝到容器中。而调用emplace时,则是将参数传递给元素类型的构造函数,直接在容器的内存空间中构造元素。 +新标准库增加了三个直接构造而不是拷贝元素的操作:`emplace_front`、`emplace_back`和`emplace`,其分别对应`push_front`、`push_back`和`insert`。当调用`push`或`insert`时,元素对象被拷贝到容器中。而调用`emplace`时,则是将参数传递给元素类型的构造函数,直接在容器的内存空间中构造元素。 ```c++ // construct a Sales_data object at the end of c @@ -272,23 +266,23 @@ c.push_back("978-0590353403", 25, 15.99); c.push_back(Sales_data("978-0590353403", 25, 15.99)); ``` -传递给emplace的参数必须与元素类型的构造函数相匹配。 +传递给`emplace`的参数必须与元素类型的构造函数相匹配。 -forward_list有特殊版本的insert和emplace操作,且不支持push_back和emplace_back。vector和string不支持push_front和emplace_front。 +`forward_list`有特殊版本的`insert`和`emplace`操作,且不支持`push_back`和`emplace_back`。`vector`和`string`不支持`push_front`和`emplace_front`。 ### 访问元素(Accessing Elements) -每个顺序容器都有一个`front`成员函数,而除了forward_list之外的顺序容器还有一个`back`成员函数。这两个操作分别返回首元素和尾元素的引用。 +每个顺序容器都有一个`front`成员函数,而除了`forward_list`之外的顺序容器还有一个`back`成员函数。这两个操作分别返回首元素和尾元素的引用。 -在调用front和back之前,要确保容器非空。 +在调用`front`和`back`之前,要确保容器非空。 顺序容器的元素访问操作: ![9-6](Image/9-6.png) -在容器中访问元素的成员函数都返回引用类型。如果容器是const对象,则返回const引用,否则返回普通引用。 +在容器中访问元素的成员函数都返回引用类型。如果容器是`const`对象,则返回`const`引用,否则返回普通引用。 -可以快速随机访问的容器(string、vector、deque和array)都提供下标运算符。保证下标有效是程序员的责任。如果希望确保下标合法,可以使用`at`成员函数。at类似下标运算,但如果下标越界,at会抛出out_of_range异常。 +可以快速随机访问的容器(`string`、`vector`、`deque`和`array`)都提供下标运算符。保证下标有效是程序员的责任。如果希望确保下标合法,可以使用`at`成员函数。`at`类似下标运算,但如果下标越界,`at`会抛出`out_of_range`异常。 ```c++ vector svec; // empty vector @@ -302,13 +296,13 @@ cout << svec.at(0); // throws an out_of_range exception ![9-7](Image/9-7.png) -删除deque中除首尾位置之外的任何元素都会使所有迭代器、引用和指针失效。删除vector或string的元素后,指向删除点之后位置的迭代器、引用和指针也都会失效。 +删除`deque`中除首尾位置之外的任何元素都会使所有迭代器、引用和指针失效。删除`vector`或`string`的元素后,指向删除点之后位置的迭代器、引用和指针也都会失效。 删除元素前,程序员必须确保目标元素存在。 -`pop_front`和`pop_back`函数分别删除首元素和尾元素。vector和string类型不支持pop_front,forward_list类型不支持pop_back。 +`pop_front`和`pop_back`函数分别删除首元素和尾元素。`vector`和`string`类型不支持`pop_front`,`forward_list`类型不支持`pop_back`。 -`erase`函数删除指定位置的元素。可以删除由一个迭代器指定的单个元素,也可以删除由一对迭代器指定的范围内的所有元素。两种形式的erase都返回指向删除元素(最后一个)之后位置的迭代器。 +`erase`函数删除指定位置的元素。可以删除由一个迭代器指定的单个元素,也可以删除由一对迭代器指定的范围内的所有元素。两种形式的`erase`都返回指向删除元素(最后一个)之后位置的迭代器。 ```c++ // delete the range of elements between two iterators @@ -320,11 +314,11 @@ elem1 = slist.erase(elem1, elem2); // after the call elem1 == elem2 ### 特殊的forward_list操作(Specialized forward_list Operations) -在forward_list中添加或删除元素的操作是通过改变给定元素之后的元素来完成的。 +在`forward_list`中添加或删除元素的操作是通过改变给定元素之后的元素来完成的。 ![9-8](Image/9-8.png) -forward_list的插入和删除操作: +`forward_list`的插入和删除操作: ![9-9](Image/9-9.png) @@ -334,27 +328,24 @@ forward_list的插入和删除操作: ![9-10](Image/9-10.png) -`resize`函数接受一个可选的元素值参数,用来初始化添加到容器中的元素,否则新元素进行值初始化。如果容器保存的是类类型元素,且resize向容器添加新元素,则必须提供初始值,或元素类型提供默认构造函数。 +`resize`函数接受一个可选的元素值参数,用来初始化添加到容器中的元素,否则新元素进行值初始化。如果容器保存的是类类型元素,且`resize`向容器添加新元素,则必须提供初始值,或元素类型提供默认构造函数。 ### 容器操作可能使迭代器失效(Container Operations May Invalidate Iterators) 向容器中添加或删除元素可能会使指向容器元素的指针、引用或迭代器失效。失效的指针、引用或迭代器不再表示任何元素,使用它们是一种严重的程序设计错误。 - 向容器中添加元素后: - -- - 如果容器是vector或string类型,且存储空间被重新分配,则指向容器的迭代器、指针和引用都会失效。如果存储空间未重新分配,指向插入位置之前元素的迭代器、指针和引用仍然有效,但指向插入位置之后元素的迭代器、指针和引用都会失效。 - - 如果容器是deque类型,添加到除首尾之外的任何位置都会使迭代器、指针和引用失效。如果添加到首尾位置,则迭代器会失效,而指针和引用不会失效。 - - 如果容器是list或forward_list类型,指向容器的迭代器、指针和引用仍然有效。 - + - 如果容器是`vector`或`string`类型,且存储空间被重新分配,则指向容器的迭代器、指针和引用都会失效。如果存储空间未重新分配,指向插入位置之前元素的迭代器、指针和引用仍然有效,但指向插入位置之后元素的迭代器、指针和引用都会失效。 + - 如果容器是`deque`类型,添加到除首尾之外的任何位置都会使迭代器、指针和引用失效。如果添加到首尾位置,则迭代器会失效,而指针和引用不会失效。 + - 如果容器是`list`或`forward_list`类型,指向容器的迭代器、指针和引用仍然有效。 - 从容器中删除元素后,指向被删除元素的迭代器、指针和引用失效: - -- - 如果容器是list或forward_list类型,指向容器其他位置的迭代器、指针和引用仍然有效。 - - 如果容器是deque类型,删除除首尾之外的任何元素都会使迭代器、指针和引用失效。如果删除尾元素,则尾后迭代器失效,其他迭代器、指针和引用不受影响。如果删除首元素,这些也不会受影响。 - - 如果容器是vector或string类型,指向删除位置之前元素的迭代器、指针和引用仍然有效。但尾后迭代器总会失效。 + - 如果容器是`list`或`forward_list`类型,指向容器其他位置的迭代器、指针和引用仍然有效。 + - 如果容器是`deque`类型,删除除首尾之外的任何元素都会使迭代器、指针和引用失效。如果删除尾元素,则尾后迭代器失效,其他迭代器、指针和引用不受影响。如果删除首元素,这些也不会受影响。 + - 如果容器是`vector`或`string`类型,指向删除位置之前元素的迭代器、指针和引用仍然有效。但尾后迭代器总会失效。 必须保证在每次改变容器后都正确地重新定位迭代器。 -不要保存end函数返回的迭代器。 +不要保存`end`函数返回的迭代器。 ```c++ // safer: recalculate end on each trip whenever the loop adds/erases elements @@ -369,7 +360,7 @@ while (begin != v.end()) ## vector对象是如何增长的(How a vector Grows) -vector和string的实现通常会分配比新空间需求更大的内存空间,容器预留这些空间作为备用,可用来保存更多新元素。 +`vector`和`string`的实现通常会分配比新空间需求更大的内存空间,容器预留这些空间作为备用,可用来保存更多新元素。 容器大小管理操作: @@ -379,33 +370,33 @@ vector和string的实现通常会分配比新空间需求更大的内存空间 ![9-12](Image/9-12.png) -只有当需要的内存空间超过当前容量时,reserve才会真正改变容器容量,分配不小于需求大小的内存空间。当需求大小小于当前容量时,reserve并不会退回内存空间。因此在调用reserve之后,capacity会大于或等于传递给reserve的参数。 +只有当需要的内存空间超过当前容量时,`reserve`才会真正改变容器容量,分配不小于需求大小的内存空间。当需求大小小于当前容量时,`reserve`并不会退回内存空间。因此在调用`reserve`之后,`capacity`会大于或等于传递给`reserve`的参数。 -在C++11中可以使用`shrink_to_fit`函数来要求deque、vector和string退回不需要的内存空间(并不保证退回)。 +在C++11中可以使用`shrink_to_fit`函数来要求`deque`、`vector`和`string`退回不需要的内存空间(并不保证退回)。 ## 额外的string操作(Additional string Operations) ### 构造string的其他方法(Other Ways to Construct strings) -构造string的其他方法: +构造`string`的其他方法: ![9-13](Image/9-13.png) -从另一个string对象拷贝字符构造string时,如果提供的拷贝开始位置(可选)大于给定string的大小,则构造函数会抛出out_of_range异常。 +从另一个`string`对象拷贝字符构造`string`时,如果提供的拷贝开始位置(可选)大于给定`string`的大小,则构造函数会抛出`out_of_range`异常。 子字符串操作: ![9-14](Image/9-14.png) -如果传递给`substr`函数的开始位置超过string的大小,则函数会抛出out_of_range异常。 +如果传递给`substr`函数的开始位置超过`string`的大小,则函数会抛出`out_of_range`异常。 ### 改变string的其他方法(Other Ways to Change a string) -修改string的操作: +修改`string`的操作: ![9-15](Image/9-15.png) -`append`函数是在string末尾进行插入操作的简写形式。 +`append`函数是在`string`末尾进行插入操作的简写形式。 ```c++ string s("C++ Primer"), s2 = s; // initialize s and s2 to "C++ Primer" @@ -413,7 +404,7 @@ s.insert(s.size(), " 4th Ed."); // s == "C++ Primer 4th Ed." s2.append(" 4th Ed."); // equivalent: appends " 4th Ed." to s2; s == s2 ``` -`replace`函数是调用erase和insert函数的简写形式。 +`replace`函数是调用`erase`和`insert`函数的简写形式。 ```c++ // equivalent way to replace "4th" by "5th" @@ -425,31 +416,31 @@ s2.replace(11, 3, "5th"); // equivalent: s == s2 ### string搜索操作(string Search Operations) -string的每个搜索操作都返回一个string::size_type值,表示匹配位置的下标。如果搜索失败,则返回一个名为`string::npos`的static成员。标准库将npos定义为const string::size_type类型,并初始化为-1。 +`string`的每个搜索操作都返回一个`string::size_type`值,表示匹配位置的下标。如果搜索失败,则返回一个名为`string::npos`的`static`成员。标准库将`npos`定义为`const string::size_type`类型,并初始化为-1。 -不建议用int或其他带符号类型来保存string搜索函数的返回值。 +不建议用`int`或其他带符号类型来保存`string`搜索函数的返回值。 -string搜索操作: +`string`搜索操作: ![9-16](Image/9-16.png) ### compare函数(The compare Functions) -string类型提供了一组`compare`函数进行字符串比较操作,类似C标准库的strcmp函数。 +`string`类型提供了一组`compare`函数进行字符串比较操作,类似C标准库的`strcmp`函数。 -compare函数的几种参数形式: +`compare`函数的几种参数形式: ![9-17](Image/9-17.png) ### 数值转换(Numeric Conversions) -C++11增加了string和数值之间的转换函数: +C++11增加了`string`和数值之间的转换函数: ![9-18](Image/9-18.png) -进行数值转换时,string参数的第一个非空白字符必须是符号(`+`或`-`)或数字。它可以以`0x`或`0X`开头来表示十六进制数。对于转换目标是浮点值的函数,string参数也可以以小数点开头,并可以包含`e`或`E`来表示指数部分。 +进行数值转换时,`string`参数的第一个非空白字符必须是符号(`+`或`-`)或数字。它可以以`0x`或`0X`开头来表示十六进制数。对于转换目标是浮点值的函数,`string`参数也可以以小数点开头,并可以包含`e`或`E`来表示指数部分。 -如果给定的string不能转换为一个数值,则转换函数会抛出invalid_argument异常。如果转换得到的数值无法用任何类型表示,则抛出out_of_range异常。 +如果给定的`string`不能转换为一个数值,则转换函数会抛出`invalid_argument`异常。如果转换得到的数值无法用任何类型表示,则抛出`out_of_range`异常。 ## 容器适配器(Container Adaptors) @@ -459,7 +450,7 @@ C++11增加了string和数值之间的转换函数: ![9-19](Image/9-19.png) -默认情况下,stack和queue是基于deque实现的,priority_queue是基于vector实现的。可以在创建适配器时将一个命名的顺序容器作为第二个类型参数,来重载默认容器类型。 +默认情况下,`stack`和`queue`是基于`deque`实现的,`priority_queue`是基于`vector`实现的。可以在创建适配器时将一个命名的顺序容器作为第二个类型参数,来重载默认容器类型。 ```c++ // empty stack implemented on top of vector @@ -468,14 +459,15 @@ stack> str_stk; stack> str_stk2(svec); ``` -所有适配器都要求容器具有添加和删除元素的能力,因此适配器不能构造在array至上。适配器还要求容器具有添加、删除和访问尾元素的能力,因此也不能用forward_list构造适配器。 +所有适配器都要求容器具有添加和删除元素的能力,因此适配器不能构造在`array`上。适配器还要求容器具有添加、删除和访问尾元素的能力,因此也不能用`forward_list`构造适配器。 -栈适配器stack定义在头文件*stack*中,其支持的操作如下: +栈适配器`stack`定义在头文件*stack*中,其支持的操作如下: ![9-20](Image/9-20.png) -队列适配器queue和priority_queue定义在头文件*queue*中,其支持的操作如下: +队列适配器`queue`和`priority_queue`定义在头文件*queue*中,其支持的操作如下: ![9-21](Image/9-21.png) -queue使用先进先出(first-in,first-out,FIFO)的存储和访问策略。进入队列的对象被放置到队尾,而离开队列的对象则从队首删除。 \ No newline at end of file +`queue`使用先进先出(first-in,first-out,FIFO)的存储和访问策略。进入队列的对象被放置到队尾,而离开队列的对象则从队首删除。 + diff --git a/README.md b/README.md index d226636..bba1bd3 100644 --- a/README.md +++ b/README.md @@ -1,52 +1,51 @@ -# Cpp-Primer-5th-Note-CN -《C++ Primer中文版(第5版)》笔记 +# 《C++ Primer中文版(第5版)》笔记 ![Cover](Cover.png) ## 目录 -[第1章 开始](https://github.com/czs108/Cpp-Primer-5th-Note-CN/tree/master/Chapter-1) +[第1章 开始](https://github.com/czs108/Cpp-Primer-5th-Note-CN/tree/master/Chapter-1%20Getting%20Started) ### 第I部分 C++基础 -[第2章 变量和基本类型](https://github.com/czs108/Cpp-Primer-5th-Note-CN/tree/master/Chapter-2) +[第2章 变量和基本类型](https://github.com/czs108/Cpp-Primer-5th-Note-CN/tree/master/Chapter-2%20Variables%20and%20Basic%20Types) -[第3章 字符串、向量和数组](https://github.com/czs108/Cpp-Primer-5th-Note-CN/tree/master/Chapter-3) +[第3章 字符串、向量和数组](https://github.com/czs108/Cpp-Primer-5th-Note-CN/tree/master/Chapter-3%20Strings%2C%20Vectors%2C%20and%20Arrays) -[第4章 表达式](https://github.com/czs108/Cpp-Primer-5th-Note-CN/tree/master/Chapter-4) +[第4章 表达式](https://github.com/czs108/Cpp-Primer-5th-Note-CN/tree/master/Chapter-4%20Expressions) -[第5章 语句](https://github.com/czs108/Cpp-Primer-5th-Note-CN/tree/master/Chapter-5) +[第5章 语句](https://github.com/czs108/Cpp-Primer-5th-Note-CN/tree/master/Chapter-5%20Statements) -[第6章 函数](https://github.com/czs108/Cpp-Primer-5th-Note-CN/tree/master/Chapter-6) +[第6章 函数](https://github.com/czs108/Cpp-Primer-5th-Note-CN/tree/master/Chapter-6%20Functions) -[第7章 类](https://github.com/czs108/Cpp-Primer-5th-Note-CN/tree/master/Chapter-7) +[第7章 类](https://github.com/czs108/Cpp-Primer-5th-Note-CN/tree/master/Chapter-7%20Classes) ### 第II部分 C++标准库 -[第8章 IO库](https://github.com/czs108/Cpp-Primer-5th-Note-CN/tree/master/Chapter-8) +[第8章 IO库](https://github.com/czs108/Cpp-Primer-5th-Note-CN/tree/master/Chapter-8%20The%20IO%20Library) -[第9章 顺序容器](https://github.com/czs108/Cpp-Primer-5th-Note-CN/tree/master/Chapter-9) +[第9章 顺序容器](https://github.com/czs108/Cpp-Primer-5th-Note-CN/tree/master/Chapter-9%20%20Sequential%20Containers) -[第10章 泛型算法](https://github.com/czs108/Cpp-Primer-5th-Note-CN/tree/master/Chapter-10) +[第10章 泛型算法](https://github.com/czs108/Cpp-Primer-5th-Note-CN/tree/master/Chapter-10%20Generic%20Algorithms) -[第11章 关联容器](https://github.com/czs108/Cpp-Primer-5th-Note-CN/tree/master/Chapter-11) +[第11章 关联容器](https://github.com/czs108/Cpp-Primer-5th-Note-CN/tree/master/Chapter-11%20Associative%20Containers) -[第12章 动态内存](https://github.com/czs108/Cpp-Primer-5th-Note-CN/tree/master/Chapter-12) +[第12章 动态内存](https://github.com/czs108/Cpp-Primer-5th-Note-CN/tree/master/Chapter-12%20Dynamic%20Memory) ### 第III部分 类设计者的工具 -[第13章 拷贝控制](https://github.com/czs108/Cpp-Primer-5th-Note-CN/tree/master/Chapter-13) +[第13章 拷贝控制](https://github.com/czs108/Cpp-Primer-5th-Note-CN/tree/master/Chapter-13%20Copy%20Control) -[第14章 操作重载与类型转换](https://github.com/czs108/Cpp-Primer-5th-Note-CN/tree/master/Chapter-14) +[第14章 操作重载与类型转换](https://github.com/czs108/Cpp-Primer-5th-Note-CN/tree/master/Chapter-14%20Overloaded%20Operations%20and%20Conversions) -[第15章 面向对象程序设计](https://github.com/czs108/Cpp-Primer-5th-Note-CN/tree/master/Chapter-15) +[第15章 面向对象程序设计](https://github.com/czs108/Cpp-Primer-5th-Note-CN/tree/master/Chapter-15%20Object-Oriented%20Programming) -[第16章 模板与泛型编程](https://github.com/czs108/Cpp-Primer-5th-Note-CN/tree/master/Chapter-16) +[第16章 模板与泛型编程](https://github.com/czs108/Cpp-Primer-5th-Note-CN/tree/master/Chapter-16%20Templates%20and%20Generic%20Programming) ### 第IV部分 高级主题 -[第17章 标准库特殊设施](https://github.com/czs108/Cpp-Primer-5th-Note-CN/tree/master/Chapter-17) +[第17章 标准库特殊设施](https://github.com/czs108/Cpp-Primer-5th-Note-CN/tree/master/Chapter-17%20Specialized%20Library%20Facilities) -[第18章 用于大型程序的工具](https://github.com/czs108/Cpp-Primer-5th-Note-CN/tree/master/Chapter-18) +[第18章 用于大型程序的工具](https://github.com/czs108/Cpp-Primer-5th-Note-CN/tree/master/Chapter-18%20Tools%20for%20Large%20Programs) -[第19章 特殊工具与技术](https://github.com/czs108/Cpp-Primer-5th-Note-CN/tree/master/Chapter-19) +[第19章 特殊工具与技术](https://github.com/czs108/Cpp-Primer-5th-Note-CN/tree/master/Chapter-19%20Specialized%20Tools%20and%20Techniques) \ No newline at end of file