diff --git a/docs/Cpp-Notes-main/Cpp-Primer/10.泛型算法.md b/docs/Cpp-Notes-main/Cpp-Primer/10.泛型算法.md new file mode 100644 index 0000000..6faf3ed --- /dev/null +++ b/docs/Cpp-Notes-main/Cpp-Primer/10.泛型算法.md @@ -0,0 +1,225 @@ +# 第十章 泛型算法 + +## 泛型算法 + +- 因为它们实现共同的操作,所以称之为“**算法**”;而“**泛型**”、指的是它们可以操作在多种容器类型上。 +- 泛型算法本身不执行容器操作,只是单独依赖迭代器和迭代器操作实现。 +- 头文件: `#include `或者 `#include `(算数相关) +- 大多数算法是通过遍历两个迭代器标记的一段元素来实现其功能。 +- 必要的编程假定:算法永远不会改变底层容器的大小。算法可能改变容器中保存的元素的值,也可能在容器内移动元素,但不能直接添加或者删除元素。 + +### find + +- `vector::const_iterator result = find(vec.begin(), vec.end(), search_value);` +- 输入:两个标记范围的迭代器和目标查找值。返回:如果找到,返回对应的迭代器,否则返回第二个参数,即标记结尾的迭代器。 + +## 初识泛型算法 + +- 标准库提供了超过100个算法,但这些算法有一致的结构。 +- 理解算法的最基本的方法是了解它们是否读取元素、改变元素、重排元素顺序。 + +### 只读算法 + +- 只读取范围中的元素,不改变元素。 +- 如 `find`和 `accumulate`(在`numeric`中定义,求和)。 +- `find_first_of`,输入:两对迭代器标记两段范围,在第一段中找第二段中任意元素,返回第一个匹配的元素,找不到返回第一段的`end`迭代器。 +- 通常最好使用`cbegin`和`cend`。 +- `equal`:确定两个序列是否保存相同的值。 + +### 写容器元素的算法 + +- 一些算法将新值赋予序列中的元素。 +- 算法不检查写操作。 +- `fill`: `fill(vec.begin(), vec.end(), 0);` 将每个元素重置为0 +- `fill_n`: `fill_n(vec.begin(), 10, 0);` +- 插入迭代器`back_inserter`: + - 用来确保算法有足够的空间存储数据。 + - `#include ` + - `back_inserter(vec)` +- 拷贝算法`copy`: +- 输入:前两个参数指定输入范围,第三个指向目标序列。 +- `copy (ilst.begin(), ilst.end(), back_inserter(ivec));` +- `copy`时必须保证目标目的序列至少要包含与输入序列一样多的元素。 + +### 重排容器元素的算法 + +- 这些算法会重排容器中元素的顺序。 +- 排序算法`sort`: + - 接受两个迭代器,表示要排序的元素范围。 +- 消除重复`unique`: + - 之前要先调用`sort` + - 返回的迭代器指向最后一个不重复元素之后的位置。 + - 顺序会变,重复的元素被“删除”。 + - 并没有真正删除,真正删除必须使用容器操作。 + +## 定制操作 + +### 向算法传递函数: + +- 谓词(`predicate`): + - 是一个**可调用的表达式**,返回结果是一个能用作条件的值 + - 一元谓词:接受一个参数 + - 二元谓词:接受两个参数 + +- 例子: + - `stable_sort`: + - 保留相等元素的原始相对位置。 + - `stable_sort(words.begin(), words.end(), isShorter);` + +### lambda表达式 + +- 有时可能希望操作可以接受更多的参数。 +- `lambda`表达式表示一个可调用的代码单元,可以理解成是一个未命名的内联函数。 +- 形式:`[capture list](parameter list) -> return type {function body}`。 + - 其中`capture list`捕获列表是一个`lambda`所在函数定义的局部变量的列表(通常为空)。不可忽略。 + - `return type`是返回类型。可忽略。 + - `parameter`是参数列表。可忽略。 + - `function body`是函数体。不可忽略。 + - `auto f = [] {return 42;}` + +- 例子: + - `find_if`: + - 接受一对表示范围的迭代器和一个谓词,用来查找第一个满足特定要求的元素。返回第一个使谓词返回非0值的元素。 + - `auto wc = find_if(words.begin(), words.end(), [sz](const string &a){return a.size() >= sz;});` + - `for_each`: + - 接受一个可调用对象,并对序列中每个元素调用此对象。 + - `for_each(wc, words.end(), [](const string &s){cout << s << " ";})` + +### lambda捕获和返回 + +- 定义`lambda`时会生成一个新的类类型和该类型的一个对象。 +- 默认情况下,从`lambda`生成的类都包含一个对应该`lambda`所捕获的变量的数据成员,在`lambda`对象创建时被初始化。 +- **值捕获**:前提是变量可以拷贝,`size_t v1 = 42; auto f = [v1] {return v1;};`。 +- **引用捕获**:必须保证在`lambda`执行时,变量是存在的,`auto f2 = [&v1] {return v1;};` +- 尽量减少捕获的数据量,尽可能避免捕获指针或引用。 +- **隐式捕获**:让编译器推断捕获列表,在捕获列表中写一个`&`(引用方式)或`=`(值方式)。`auto f3 = [=] {return v1;}` + +**lambda捕获列表**: + +| 捕获列表 | 解释 | +|-----|-----| +| `[]` | 空捕获列表。`lambda`不能使用所在函数中的变量。一个`lambda`只有在捕获变量后才能使用它们。 | +| `[names]` | `names`是一个逗号分隔的名字列表,这些名字都是在`lambda`所在函数的局部变量,捕获列表中的变量都被拷贝,名字前如果使用了`&`,则采用引用捕获方式。 | +| `[&]` | 隐式捕获列表,采用引用捕获方式。`lambda`体中所使用的来自所在函数的实体都采用引用方式使用。 | +| `[=]` | 隐式捕获列表,采用值捕获方式。 | +| `[&, identifier_list]` | `identifier_list`是一个逗号分隔的列表,包含0个或多个来自所在函数的变量。这些变量采用值捕获方式,而任何隐式捕获的变量都采用引用方式捕获。`identifier_list`中的名字前面不能使用`&` | +| `[=, identifier_list]` | `identifier_list`中的变量采用引用方式捕获,而任何隐式捕获的变量都采用值方式捕获。`identifier_list`中的名字不能包括`this`,且前面必须使用`&` | + +### 参数绑定 + +- `lambda`表达式更适合在一两个地方使用的简单操作。 +- 如果是很多地方使用相同的操作,还是需要定义函数。 +- 函数如何包装成一元谓词?使用参数绑定。 +- 标准库`bind`函数: + - 定义在头文件`functional`中,可以看做为一个通用的函数适配器。 + - `auto newCallable = bind(callable, arg_list);` + - 我们再调用`newCallable`的时候,`newCallable`会调用`callable`并传递给它`arg_list`中的参数。 + - `_n`代表第n个位置的参数。定义在`placeholders`的命名空间中。`using std::placeholder::_1;` + - `auto g = bind(f, a, b, _2, c, _1);`,调用`g(_1, _2)`实际上调用`f(a, b, _2, c, _1)` + - 非占位符的参数要使用引用传参,必须使用标准库`ref`函数或者`cref`函数。 + +## 再探迭代器 + +### 插入迭代器 + +- 插入器是一种迭代器适配器,接受一个容器,生成一个迭代器,能实现向给定容器添加元素。 +- 三种类型: + - `back_inserter`:创建一个使用`push_back`的迭代器。 + - `front_inserter`创建一个使用`push_front`的迭代器。 + - `inserter`创建一个使用`insert`的迭代器。接受第二个参数,即一个指向给定容器的迭代器,元素会被查到迭代器所指向的元素之前。 + +**插入迭代器操作**: + +| 操作 | 解释 | +|-----|-----| +| `it=t` | 在`it`指定的当前位置插入值`t`。假定`c`是`it`绑定的容器,依赖于插入迭代器的不同种类,此赋值会分别调用`c.push_back(t)`、`c.push_front(t)`、`c.insert(t, p)`,其中`p`是传递给`inserter`的迭代器位置 | +| `*it, ++it, it++` | 这些操作虽然存在,但不会对`it`做任何事情,每个操作都返回`it` | + +### iostream迭代器 + +- 迭代器可与输入或输出流绑定在一起,用于迭代遍历所关联的 IO 流。 +- 通过使用流迭代器,我们可以用泛型算法从流对象中读取数据以及向其写入数据。 + +**istream_iterator的操作**: + +| 操作 | 解释 | +|-----|-----| +| `istream_iterator in(is);` | `in`从输入流`is`读取类型为`T`的值 | +|`istream_iterator end;` | 读取类型是`T`的值的`istream_iterator`迭代器,表示尾后位置 | +| `in1 == in2` | `in1`和`in2`必须读取相同类型。如果他们都是尾后迭代器,或绑定到相同的输入,则两者相等。 | +| `in1 != in2` | 类似上条 | +| `*in` | 返回从流中读取的值 | +| `in->mem` | 与`*(in).mem`含义相同 | +| `++in, in++` | 使用元素类型所定义的`>>`运算符从流中读取下一个值。前置版本返回一个指向递增后迭代器的引用,后置版本返回旧值。 | + +**ostream_iterator的操作**: + +| 操作 | 解释 | +|-----|-----| +| `ostream_iterator out(os);` | `out`将类型为`T`的值写到输出流`os`中 | +| `ostream_iterator out(os, d);` | `out`将类型为`T`的值写到输出流`os`中,每个值后面都输出一个`d`。`d`指向一个空字符结尾的字符数组。 | +| `out = val` | 用`<<`运算符将`val`写入到`out`所绑定的`ostream`中。`val`的类型必须和`out`可写的类型兼容。 | +| `*out, ++out, out++` | 这些运算符是存在的,但不对`out`做任何事情。每个运算符都返回`out`。 | + +### 反向迭代器 + +- 反向迭代器就是在容器中从尾元素向首元素反向移动的迭代器。 +- 对于反向迭代器,递增和递减的操作含义会颠倒。 +- 实现向后遍历,配合`rbegin`和`rend`。 + +## 泛型算法结构 + +### 5类迭代器 + +| 迭代器类别 | 解释 | 支持的操作| +|-----|-----|-----| +| 输入迭代器 | 只读,不写;单遍扫描,只能递增 | `==`,`!=`,`++`,`*`,`->` | +| 输出迭代器 | 只写,不读;单遍扫描,只能递增 | `++`,`*` | +| 前向迭代器 | 可读写;多遍扫描,只能递增 | `==`,`!=`,`++`,`*`,`->` | +| 双向迭代器 | 可读写;多遍扫描,可递增递减 | `==`,`!=`,`++`,`--`,`*`,`->` | +| 随机访问迭代器 | 可读写,多遍扫描,支持全部迭代器运算 | `==`,`!=`,`<`,`<=`,`>`,`>=`,`++`,`--`,`+`,`+=`,`-`,`-=`,`*`,`->`,`iter[n]`==`*(iter[n])` | + +### 算法的形参模式 + +- `alg(beg, end, other args);` +- `alg(beg, end, dest, other args);` +- `alg(beg, end, beg2, other args);` +- `alg(beg, end, beg2, end2, other args);` + +其中,`alg`是算法名称,`beg`和`end`表示算法所操作的输入范围。`dest`、`beg2`、`end2`都是迭代器参数,是否使用要依赖于执行的操作。 + +### 算法命名规范 + +- 一些算法使用重载形式传递一个谓词。 +- 接受一个元素值的算法通常有一个**不同名**的版本:加`_if`,接受一个谓词代替元素值。 +- 区分拷贝元素的版本和不拷贝的版本:拷贝版本通常加`_copy`。 + +## 特定容器算法 + +- 对于`list`和`forward_list`,优先使用成员函数版本的算法而不是通用算法。 + +**list和forward_list成员函数版本的算法**: + +| 操作 | 解释 | +|-----|-----| +| `lst.merge(lst2)` | 将来自`lst2`的元素合并入`lst`,二者都必须是有序的,元素将从`lst2`中删除。 | +| `lst.merge(lst2, comp)` | 同上,给定比较操作。 | +| `lst.remove(val)` | 调用`erase`删除掉与给定值相等(==)的每个元素 | +| `lst.remove_if(pred)` | 调用`erase`删除掉令一元谓词为真的每个元素 | +| `lst.reverse()` | 反转`lst`中元素的顺序 | +| `lst.sort()` | 使用`<`排序元素 | +| `lst.sort(comp)` | 使用给定比较操作排序元素 | +| `lst.unique()` | 调用`erase`删除同一个值的连续拷贝。使用`==`。 | +| `lst.unique(pred)` | 调用`erase`删除同一个值的连续拷贝。使用给定的二元谓词。 | + +- 上面的操作都返回`void` + +**list和forward_list的splice成员函数版本的参数**: + +| 参数 | 解释 | +|-----|-----| +| `(p, lst2)` | `p`是一个指向`lst`中元素的迭代器,或者一个指向`flst`首前位置的迭代器。函数将`lst2`中的所有元素移动到`lst`中`p`之前的位置或是`flst`中`p`之后的位置。将元素从`lst2`中删除。`lst2`的类型必须和`lst`相同,而且不能是同一个链表。 | +| `(p, lst2, p2)` | 同上,`p2`是一个指向`lst2`中位置的有效的迭代器,将`p2`指向的元素移动到`lst`中,或将`p2`之后的元素移动到`flst`中。`lst2`可以是于`lst`或`flst`相同的链表。 | +| `(p, lst2, b, e)` | `b`和`e`表示`lst2`中的合法范围。将给定范围中的元素从`lst2`移动到`lst`或`first`中。`lst2`与`lst`可以使相同的链表,但`p`不能指向给定范围中的元素。 | + +- 使用`lst.splice(args)`或`flst.splice_after(args)` diff --git a/docs/Cpp-Notes-main/Cpp-Primer/11.关联容器.md b/docs/Cpp-Notes-main/Cpp-Primer/11.关联容器.md new file mode 100644 index 0000000..4a83b2c --- /dev/null +++ b/docs/Cpp-Notes-main/Cpp-Primer/11.关联容器.md @@ -0,0 +1,144 @@ +# 第十一章 关联容器 + +- 关联容器和顺序容器的不同:关联容器中的元素时按照**关键字**来保存和访问的。 +- 关联容器支持通过关键字来高效地查找和读取元素,基本的关联容器类型是 `map`和 `set`。 + +**关联容器类型**: + +| 容器类型 | 解释 | +|-----|-----| +| 按顺序存储 | | +| `map` | 关键数组:保存`关键字-值`对 | +| `set` | 关键字即值,即只保存关键字的容器 | +| `multimap` | 支持同一个键多次出现的`map` | +| `multiset` | 支持同一个键多次出现的`set` | +| 无序集合 | | +| `unordered_map` | 用哈希函数组织的`map` | +| `unordered_set` | 用哈希函数组织的`set` | +| `unordered_multimap` | 哈希组织的`map`,关键字可以重复出现 | +| `unordered_multiset` | 哈希组织的`set`,关键字可以重复出现 | + +## 关联容器概述 + +### 定义关联容器 + +- 需要指定元素类型。 +- 列表初始化: + - `map`:`map word_count = {{"a", 1}, {"b", 2}};` + - `set`:`set exclude = {"the", "a"};` + +### 关键字类型的要求 + +- 对于有序容器,关键字类型必须定义元素比较的方法。默认是`<`。 +- 如果想传递一个比较的函数,可以这样定义:`multiset bookstore(compareIsbn);` + +### pair + +- 在`utility`头文件中定义。 +- 一个`pair`保存两个数据成员,两个类型不要求一样。 + +**pair的操作**: + +| 操作 | 解释 | +|-----|-----| +| `pair p;` | `p`是一个`pair`,两个类型分别是`T1`和`T2`的成员都进行了值初始化。 | +| `pair p(v1, v2);` | `first`和`second`分别用`v1`和`v2`进行初始化。 | +| `pairp = {v1, v2};` | 等价于`p(v1, v2) | +| `make_pair(v1, v2);` | `pair`的类型从`v1`和`v2`的类型推断出来。 | +| `p.first` | 返回`p`的名为`first`的数据成员。 | +| `p.second` | 返回`p`的名为`second`的数据成员。 | +| `p1 relop p2` | 运算关系符按字典序定义。 | +| `p1 == p2` | 必须两对元素两两相等 | +| `p1 != p2` | 同上 | + +## 关联容器操作 + +**关联容器额外的类型别名**: + +| 类型别名 | 解释 | +|-----|-----| +| `key_type` | 此容器类型的关键字类型 | +| `mapped_type` | 每个关键字关联的类型,只适用于`map` | +| `value_type` | 对于`map`,是`pair`; 对于`set`,和`key_type`相同。 | + +### 关联容器迭代器 + +- 解引用一个关联容器迭代器时,会得到一个类型为容器的`value_type`的值的引用。 +- `set`的迭代器是`const`的。 +- 遍历关联容器:使用`begin`和`end`,遍历`map`、`multimap`、`set`、`multiset`时,迭代器按**关键字升序**遍历元素。 + +### 添加元素 + +**关联容器`insert`操作**: + +| `insert`操作 | 关联容器 | +|-----|-----| +| `c.insert(v)` `c.emplace(args)` | `v`是`value_type`类型的对象;`args`用来构造一个元素。 对于`map`和`set`,只有元素的关键字不存在`c`中才插入或构造元素。函数返回一个`pair`,包含一个迭代器,指向具有指定关键字的元素,以及一个指示插入是否成功的`bool`值。对于`multimap`和`multiset`则会插入范围中的每个元素。| +| `c.insert(b, e)` `c.insert(il)` | `b`和`e`是迭代器,表示一个`c::value_type`类型值的范围;`il`是这种值的花括号列表。函数返回`void`。对于 `map`和`set`,只插入关键字不在`c`中的元素。 | +| `c.insert(p, v)` `c.emplace(p, args)` | 类似`insert(v)`,但将迭代器`p`作为一个提示,指出从哪里开始搜索新元素应该存储的位置。返回一个迭代器,指向具有给定关键字的元素。 | + +向`map`添加元素: +- `word_count.insert({word, 1});` +- `word_count.insert(make_pair(word, 1));` +- `word_count.insert(pair(word, 1));` +- `word_count.insert(map::value_type (word, 1));` + +### 删除元素 + +**从关联容器中删除元素**: + +| 操作 | 解释 | +|-----|-----| +| `c.erase(k)` | 从`c`中删除每个关键字为`k`的元素。返回一个`size_type`值,指出删除的元素的数量。 | +| `c.erase(p)` | 从`c`中删除迭代器`p`指定的元素。`p`必须指向`c`中一个真实元素,不能等于`c.end()`。返回一个指向`p`之后元素的迭代器,若`p`指向`c`中的尾元素,则返回`c.end()` | +| `c.erase(b, e)` | 删除迭代器对`b`和`e`所表示范围中的元素。返回`e`。 | + +### 下标操作 + +**`map`和`unordered_map`的下标操作**: + +| 操作 | 解释 | +|-----|-----| +| `c[k]` | 返回关键字为`k`的元素;如果`k`不在`c`中,添加一个关键字为`k`的元素,对其值初始化。 | +| `c.at(k)` | 访问关键字为`k`的元素,带参数检查;若`k`不存在在`c`中,抛出一个`out_of_range`异常。 | + +### 查找元素 + +**在一个关联容器中查找元素**: + +| 操作 | 解释 | +|-----|-----| +| `c.find(k)` | 返回一个迭代器,指向第一个关键字为`k`的元素,若`k`不在容器中,则返回尾后迭代器 | +| `c.count(k)` | 返回关键字等于`k`的元素的数量。对于不允许重复关键字的容器,返回值永远是0或1。 | +| `c.lower_bound(k)` | 返回一个迭代器,指向第一个关键字**不小于**`k`的元素。 | +| `c.upper_bound(k)` | 返回一个迭代器,指向第一个关键字**大于**`k`的元素。 | +| `c.equal_range(k)` | 返回一个迭代器`pair`,表示关键字等于`k`的元素的范围。若`k`不存在,`pair`的两个成员均等于`c.end()`。 | + +- `lower_bound`和`upper_bound`不适用于无序容器。 +- 下标和`at`操作只适用于非`const`的`map`和`unordered_map`。 + +## 无序容器 + +- 有序容器使用比较运算符来组织元素;无序容器使用哈希函数和关键字类型的`==`运算符。 +- 理论上哈希技术可以获得更好的性能。 +- 无序容器在存储上组织为一组桶(bucket),每个桶保存零个或多个元素。无序容器使用一个哈希函数将元素映射到桶。 + +**无序容器管理操作**: + +| 操作 | 解释 | +|-----|-----| +| **桶接口** | | +| `c.bucket_count()` | 正在使用的桶的数目 | +| `c.max_bucket_count()` | 容器能容纳的最多的桶的数目 | +| `c.bucket_size(n)` | 第`n`个桶中有多少个元素 | +| `c.bucket(k)` | 关键字为`k`的元素在哪个桶中 | +| **桶迭代** | | +| `local_iterator` | 可以用来访问桶中元素的迭代器类型 | +| `const_local_iterator` | 桶迭代器的`const`版本 | +| `c.begin(n)`,`c.end(n)` | 桶`n`的首元素迭代器 | +| `c.cbegin(n)`,`c.cend(n)` | 与前两个函数类似,但返回`const_local_iterator`。 | +| **哈希策略** | | +| `c.load_factor()` | 每个桶的平均元素数量,返回`float`值。 | +| `c.max_load_factor()` | `c`试图维护的平均比桶大小,返回`float`值。`c`会在需要时添加新的桶,以使得`load_factor<=max_load_factor` | +| `c.rehash(n)` | 重组存储,使得`bucket_count>=n`,且`bucket_count>size/max_load_factor` | +| `c.reverse(n)` | 重组存储,使得`c`可以保存`n`个元素且不必`rehash`。 | diff --git a/docs/Cpp-Notes-main/Cpp-Primer/12.动态内存.md b/docs/Cpp-Notes-main/Cpp-Primer/12.动态内存.md new file mode 100644 index 0000000..63fc838 --- /dev/null +++ b/docs/Cpp-Notes-main/Cpp-Primer/12.动态内存.md @@ -0,0 +1,176 @@ +# 第十二章 动态内存 + +- 对象的生命周期: + - 全局对象在程序启动时分配,结束时销毁。 + - 局部对象在进入程序块时创建,离开块时销毁。 + - 局部`static`对象在第一次使用前分配,在程序结束时销毁。 + - 动态分配对象:只能显式地被释放。 + +- 对象的内存位置: + - **静态内存**用来保存局部`static`对象、类`static`对象、定义在任何函数之外的变量。 + - **栈内存**用来保存定义在函数内的非`static`对象。 + - **堆内存**,又称自由空间,用来存储**动态分配**的对象。 + +## 动态内存与智能指针 + +- 动态内存管理: + - `new`:在动态内存中为对象分配空间并返回一个指向该对象的指针。 + - `delete`:接受一个动态对象的指针,销毁该对象,并释放与之关联的内存。 +- 智能指针: + - 管理动态对象。 + - 行为类似常规指针。 + - 负责自动释放所指向的对象。 + - 智能指针也是模板。 + +### shared_ptr类 + +**shared_ptr和unique_ptr都支持的操作**: + +| 操作 | 解释 | +|-----|-----| +| `shared_ptr sp` `unique_ptr up` | 空智能指针,可以指向类型是`T`的对象 | +| `p` | 将`p`用作一个条件判断,若`p`指向一个对象,则为`true` | +| `*p` | 解引用`p`,获得它指向的对象。 | +| `p->mem` | 等价于`(*p).mem` | +| `p.get()` | 返回`p`中保存的指针,要小心使用,若智能指针释放了对象,返回的指针所指向的对象也就消失了。 | +| `swap(p, q)` `p.swap(q)` | 交换`p`和`q`中的指针 | + +**shared_ptr独有的操作**: + +| 操作 | 解释 | +|-----|-----| +| `make_shared(args)` | 返回一个`shared_ptr`,指向一个动态分配的类型为`T`的对象。使用`args`初始化此对象。 | +| `shared_ptrp(q)` | `p`是`shared_ptr q`的拷贝;此操作会**递增**`q`中的计数器。`q`中的指针必须能转换为`T*` | +| `p = q` | `p`和`q`都是`shared_ptr`,所保存的指针必须能互相转换。此操作会**递减**`p`的引用计数,**递增**`q`的引用计数;若`p`的引用计数变为0,则将其管理的原内存释放。 | +| `p.unique()` | 若`p.use_count()`是1,返回`true`;否则返回`false` | +| `p.use_count()` | 返回与`p`共享对象的智能指针数量;可能很慢,主要用于调试。 | + +- **使用动态内存的三种原因**: + - 程序不知道自己需要使用多少对象(比如容器类)。 + - 程序不知道所需要对象的准确类型。 + - 程序需要在多个对象间共享数据。 + +### 直接管理内存 + +- 用`new`动态分配和初始化对象。 + - `new`无法为分配的对象命名(因为自由空间分配的内存是无名的),因此是返回一个指向该对象的指针。 + - `int *pi = new int(123);` + - 一旦内存耗尽,会抛出类型是`bad_alloc`的异常。 +- 用`delete`将动态内存归还给系统。 + - 接受一个指针,指向要释放的对象。 + - `delete`后的指针称为空悬指针(dangling pointer)。 +- 使用`new`和`delete`管理动态内存存在三个常见问题: + - 1.忘记`delete`内存。 + - 2.使用已经释放掉的对象。 + - 3.同一块内存释放两次。 +- 坚持只使用智能指针可以避免上述所有问题。 + +### shared_ptr和new结合使用 + +**定义和改变shared_ptr的其他方法**: + +| 操作 | 解释 | +|-----|-----| +| `shared_ptr p(q)` | `p`管理内置指针`q`所指向的对象;`q`必须指向`new`分配的内存,且能够转换为`T*`类型 | +| `shared_ptr p(u)` | `p`从`unique_ptr u`那里接管了对象的所有权;将`u`置为空 | +| `shared_ptr p(q, d)` | `p`接管了内置指针`q`所指向的对象的所有权。`q`必须能转换为`T*`类型。`p`将使用可调用对象`d`来代替`delete`。 | +| `shared_ptr p(p2, d)` | `p`是`shared_ptr p2`的拷贝,唯一的区别是`p`将可调用对象`d`来代替`delete`。 | +| `p.reset()` | 若`p`是唯一指向其对象的`shared_ptr`,`reset`会释放此对象。若传递了可选的参数内置指针`q`,会令`p`指向`q`,否则会将`p`置空。若还传递了参数`d`,则会调用`d`而不是`delete`来释放`q`。 | +| `p.reset(q)` | 同上 | +| `p.reset(q, d)` | 同上 | + +### 智能指针和异常 + +- 如果使用智能指针,即使程序块由于异常过早结束,智能指针类也能确保在内存不需要的时候将其释放。 +- **智能指针陷阱**: + - 不用相同的内置指针初始化(或`reset`)多个智能指针 + - 不`delete get()`返回的指针。 + - 如果你使用`get()`返回的指针,记得当最后一个对应的智能指针销毁后,你的指针就无效了。 + - 如果你使用智能指针管理的资源不是`new`分配的内存,记住传递给它一个删除器。 + +### unique_ptr + +- 某一个时刻只能有一个`unique_ptr`指向一个给定的对象。 +- 不支持拷贝或者赋值操作。 +- 向后兼容:`auto_ptr`:老版本,具有`unique_ptr`的部分特性。特别是,不能在容器中保存`auto_ptr`,也不能从函数返回`auto_ptr`。 + +**unique_ptr操作**: + +| 操作 | 解释 | +|-----|-----| +| `unique_ptr u1` | 空`unique_ptr`,可以指向类型是`T`的对象。`u1`会使用`delete`来是释放它的指针。 | +| `unique_ptr u2` | `u2`会使用一个类型为`D`的可调用对象来释放它的指针。 | +| `unique_ptr u(d)` | 空`unique_ptr`,指向类型为`T`的对象,用类型为`D`的对象`d`代替`delete` | +| `u = nullptr` | 释放`u`指向的对象,将`u`置为空。 | +| `u.release()` | `u`放弃对指针的控制权,返回指针,并将`u`置空。 | +| `u.reset()` | 释放`u`指向的对象 | +| `u.reset(q)` | 令`u`指向`q`指向的对象 | +| `u.reset(nullptr)` | 将`u`置空 | + +### weak_ptr + +- `weak_ptr`是一种不控制所指向对象生存期的智能指针。 +- 指向一个由`shared_ptr`管理的对象,不改变`shared_ptr`的引用计数。 +- 一旦最后一个指向对象的`shared_ptr`被销毁,对象就会被释放,不管有没有`weak_ptr`指向该对象。 + +**weak_ptr操作**: + +| 操作 | 解释 | +|-----|-----| +| `weak_ptr w` | 空`weak_ptr`可以指向类型为`T`的对象 | +| `weak_ptr w(sp)` | 与`shared_ptr`指向相同对象的`weak_ptr`。`T`必须能转换为`sp`指向的类型。 | +| `w = p` | `p`可以是`shared_ptr`或一个`weak_ptr`。赋值后`w`和`p`共享对象。 | +| `w.reset()` | 将`w`置为空。 | +| `w.use_count()` | 与`w`共享对象的`shared_ptr`的数量。 | +| `w.expired()` | 若`w.use_count()`为0,返回`true`,否则返回`false` | +| `w.lock()` | 如果`expired`为`true`,则返回一个空`shared_ptr`;否则返回一个指向`w`的对象的`shared_ptr`。 | + +## 动态数组 + +### new和数组 + +- `new`一个动态数组: + - 类型名之后加一对方括号,指明分配的对象数目(必须是整型,不必是常量)。 + - 返回**指向第一个对象的指针**。 + - `int *p = new int[size];` + +- `delete`一个动态数组: + - `delete [] p;` + +- `unique_ptr`和数组: + - 指向数组的`unique_ptr`不支持成员访问运算符(点和箭头)。 + +| 操作 | 解释 | +|-----|-----| +| `unique_ptr u` | `u`可以指向一个动态分配的数组,整数元素类型为`T` | +| `unique_ptr u(p)` | `u`指向内置指针`p`所指向的动态分配的数组。`p`必须能转换为类型`T*`。 | +| `u[i]` | 返回`u`拥有的数组中位置`i`处的对象。`u`必须指向一个数组。 | + +### allocator类 + +- 标准库`allocator`类定义在头文件`memory`中,帮助我们将内存分配和对象构造分离开。 +- 分配的是原始的、未构造的内存。 +- `allocator`是一个模板。 +- `allocator alloc;` + +**标准库allocator类及其算法**: + +| 操作 | 解释 | +|-----|-----| +| `allocator a` | 定义了一个名为`a`的`allocator`对象,它可以为类型为`T`的对象分配内存 | +| `a.allocate(n)` | 分配一段原始的、未构造的内存,保存`n`个类型为`T`的对象。 | +| `a.deallocate(p, n)` | 释放从`T*`指针`p`中地址开始的内存,这块内存保存了`n`个类型为`T`的对象;`p`必须是一个先前由`allocate`返回的指针。且`n`必须是`p`创建时所要求的大小。在调用`deallocate`之前,用户必须对每个在这块内存中创建的对象调用`destroy`。 | +| `a.construct(p, args)` | `p`必须是一个类型是`T*`的指针,指向一块原始内存;`args`被传递给类型为`T`的构造函数,用来在`p`指向的内存中构造一个对象。 | +| `a.destroy(p)` | `p`为`T*`类型的指针,此算法对`p`指向的对象执行析构函数。 | + +**allocator伴随算法**: + +| 操作 | 解释 | +|-----|-----| +| `uninitialized_copy(b, e, b2)` | 从迭代器`b`和`e`给定的输入范围中拷贝元素到迭代器`b2`指定的未构造的原始内存中。`b2`指向的内存必须足够大,能够容纳输入序列中元素的拷贝。 | +| `uninitialized_copy_n(b, n, b2)` | 从迭代器`b`指向的元素开始,拷贝`n`个元素到`b2`开始的内存中。 | +| `uninitialized_fill(b, e, t)` | 在迭代器`b`和`e`执行的原始内存范围中创建对象,对象的值均为`t`的拷贝。 | +| `uninitialized_fill_n(b, n, t)` | 从迭代器`b`指向的内存地址开始创建`n`个对象。`b`必须指向足够大的未构造的原始内存,能够容纳给定数量的对象。 | + +- 定义在头文件`memory`中。 +- 在给定目的位置创建元素,而不是由系统分配内存给他们。 diff --git a/docs/Cpp-Notes-main/Cpp-Primer/13.拷贝控制.md b/docs/Cpp-Notes-main/Cpp-Primer/13.拷贝控制.md new file mode 100644 index 0000000..7748c78 --- /dev/null +++ b/docs/Cpp-Notes-main/Cpp-Primer/13.拷贝控制.md @@ -0,0 +1,123 @@ +# 第十三章 拷贝控制 + +**拷贝控制操作**(copy control): + +- 拷贝构造函数(copy constructor) +- 拷贝赋值运算符(copy-assignment operator) +- 移动构造函数(move constructor) +- 移动赋值函数(move-assignement operator) +- 析构函数(destructor) + +## 拷贝、赋值和销毁 + +### 拷贝构造函数 + +- 如果一个构造函数的第一个参数是**自身类类型的引用**,且任何额外参数都有默认值,则此构造函数是**拷贝构造函数**。 +- `class Foo{ public: Foo(const Foo&); }` +- **合成的拷贝构造函数**(synthesized copy constructor):会将参数的成员逐个拷贝到正在创建的对象中。 +- **拷贝初始化**: + - 将右侧运算对象拷贝到正在创建的对象中,如果需要,还需进行类型转换。 + - 通常使用拷贝构造函数完成。 + - `string book = "9-99";` + - 出现场景: + - 用`=`定义变量时。 + - 将一个对象作为实参传递给一个非引用类型的形参。 + - 从一个返回类型为非引用类型的函数返回一个对象。 + - 用花括号列表初始化一个数组中的元素或者一个聚合类中的成员。 + +### 拷贝赋值运算符 + +- **重载赋值运算符**: + - 重写一个名为`operator=`的函数. + - 通常返回一个指向其左侧运算对象的引用。 + - `Foo& operator=(const Foo&);` +- **合成拷贝赋值运算符**: + - 将右侧运算对象的每个非`static`成员赋予左侧运算对象的对应成员。 + +### 析构函数 + +- 释放对象所使用的资源,并销毁对象的非`static`数据成员。 +- 名字由波浪号接类名构成。没有返回值,也不接受参数。 +- `~Foo();` +- 调用时机: + - 变量在离开其作用域时。 + - 当一个对象被销毁时,其成员被销毁。 + - 容器被销毁时,其元素被销毁。 + - 动态分配的对象,当对指向它的指针应用`delete`运算符时。 + - 对于临时对象,当创建它的完整表达式结束时。 +- **合成析构函数**: + - 空函数体执行完后,**成员会被自动销毁。** + - 注意:析构函数体本身并不直接销毁成员。 + +### 三/五法则 + +- 需要析构函数的类也需要拷贝和赋值操作。 +- 需要拷贝操作的类也需要赋值操作,反之亦然。 + +### 使用=default + +- 可以通过将拷贝控制成员定义为`=default`来显式地要求编译器生成合成的版本。 +- 合成的函数将隐式地声明为内联的。 + +### 阻止拷贝 + +- 大多数类应该定义默认构造函数、拷贝构造函数和拷贝赋值运算符,无论是隐式地还是显式地。 +- 定义删除的函数:`=delete`。 +- 虽然声明了它们,但是不能以任何方式使用它们。 +- 析构函数不能是删除的成员。 +- 如果一个类有数据成员不能默认构造、拷贝、复制或者销毁,则对应的成员函数将被定义为删除的。 +- 老版本使用`private`声明来阻止拷贝。 + +## 拷贝控制和资源管理 + +- 类的行为可以像一个值,也可以像一个指针。 + - 行为像值:对象有自己的状态,副本和原对象是完全独立的。 + - 行为像指针:共享状态,拷贝一个这种类的对象时,副本和原对象使用相同的底层数据。 + +## 交换操作 + +- 管理资源的类通常还定义一个名为`swap`的函数。 +- 经常用于重排元素顺序的算法。 +- 用`swap`而不是`std::swap`。 + +## 对象移动 + +- 很多拷贝操作后,原对象会被销毁,因此引入移动操作可以大幅度提升性能。 +- 在新标准中,我们可以用容器保存不可拷贝的类型,只要它们可以被移动即可。 +- 标准库容器、`string`和`shared_ptr`类既可以支持移动也支持拷贝。`IO`类和`unique_ptr`类可以移动但不能拷贝。 + +### 右值引用 + +- 新标准引入右值引用以支持移动操作。 +- 通过`&&`获得右值引用。 +- 只能绑定到一个将要销毁的对象。 +- 常规引用可以称之为左值引用。 +- 左值持久,右值短暂。 + +**move函数**: + +- `int &&rr2 = std::move(rr1);` +- `move`告诉编译器,我们有一个左值,但我希望像右值一样处理它。 +- 调用`move`意味着:除了对`rr1`赋值或者销毁它外,我们将不再使用它。 + +### 移动构造函数和移动赋值运算符 + +- **移动构造函数**: + - 第一个参数是该类类型的一个引用,关键是,这个引用参数是一个**右值引用**。 + - `StrVec::StrVec(StrVec &&s) noexcept{}` + - 不分配任何新内存,只是接管给定的内存。 +- **移动赋值运算符**: + - `StrVec& StrVec::operator=(StrVec && rhs) noexcept{}` +- 移动右值,拷贝左值。 +- 如果没有移动构造函数,右值也被拷贝。 +- 更新三/五法则:如果一个类定义了任何一个拷贝操作,它就应该定义所有五个操作。 +- 移动迭代器: + - `make_move_iterator`函数讲一个普通迭代器转换为一个移动迭代器。 +- 建议:小心地使用移动操作,以获得性能提升。 + +### 右值引用和成员函数 + +- 区分移动和拷贝的重载函数通常有一个版本接受一个`const T&`,而另一个版本接受一个`T&&`。 +- 引用限定符: + - 在参数列表后面防止一个`&`,限定只能向可修改的左值赋值而不能向右值赋值。 + diff --git a/docs/Cpp-Notes-main/Cpp-Primer/14.重载运算与类型转换.md b/docs/Cpp-Notes-main/Cpp-Primer/14.重载运算与类型转换.md new file mode 100644 index 0000000..9946535 --- /dev/null +++ b/docs/Cpp-Notes-main/Cpp-Primer/14.重载运算与类型转换.md @@ -0,0 +1,147 @@ +# 第十四章 重载运算与类型转换 + +## 基本概念 + +- 重载运算符是具有特殊名字的函数:由关键字`operator`和其后要定义的运算符号共同组成。 +- 当一个重载的运算符是成员函数时,`this`绑定到左侧运算对象。动态运算符符函数的参数数量比运算对象的数量**少一个**。 +- 只能重载大多数的运算符,而不能发明新的运算符号。 +- 重载运算符的优先级和结合律跟对应的内置运算符保持一致。 +- 调用方式: + - `data1 + data2;` + - `operator+(data1, data2);` +- 是否是成员函数: + - 赋值(`=`)、下标(`[]`)、调用(`()`)和成员访问箭头(`->`)运算符必须是成员。 + - 复合赋值运算符一般来说是成员。 + - 改变对象状态的运算符或者和给定类型密切相关的运算符通常是成员,如递增、解引用。 + - 具有对称性的运算符如算术、相等性、关系和位运算符等,通常是非成员函数。 + +**运算符**: + +| 可以被重载 | 不可以被重载 | +|-----|-----| +| `+`, `-`, `*`, `/`, `%`, `^` | `::`, `.*`, `.`, `? :`, | +| `&`, `|`, `~`, `!`, `,`, `=` | | +| `<`, `>`, `<=`, `>=`, `++`, `--` | | +| `<<`, `>>`, `==`, `!=`, `&&`, `||` | | +| `+=`, `-=`, `/=`, `%=`, `^=`, `&=` | | +| |=, `*=`, `<<=`, `>>=`, `[]`, `()` | | +| `->`, `->*`, `new`, `new[]`, `delete`, `delete[]` | | + +## 输入和输出运算符 + +### 重载输出运算符<< + +- 第一个形参通常是一个非常量的`ostream`对象的引用。非常量是因为向流中写入会改变其状态;而引用是因为我们无法复制一个`ostream`对象。 +- 输入输出运算符必须是非成员函数。 + +### 重载输入运算符>> + +- 第一个形参通常是运算符将要读取的流的引用,第二个形参是将要读取到的(非常量)对象的引用。 +- 输入运算符必须处理输入可能失败的情况,而输出运算符不需要。 + +## 算数和关系运算符(+、-、*、/) + +- 如果类同时定义了算数运算符和相关的复合赋值运算符,则通常情况下应该使用复合赋值来实现算数运算符。 + +### 相等运算符== + +- 如果定义了`operator==`,则这个类也应该定义`operator!=`。 +- 相等运算符和不等运算符的一个应该把工作委托给另一个。 +- 相等运算符应该具有传递性。 +- 如果某个类在逻辑上有相等性的含义,则该类应该定义`operator==`,这样做可以使用户更容易使用标准库算法来处理这个类。 + +### 关系运算符 + +- 如果存在唯一一种逻辑可靠的`<`定义,则应该考虑为这个类定义`<`运算符。如果同时还包含`==`,则当且晋档`<`的定义和`++`产生的结果一直时才定义`<`运算符。 + +## 赋值运算符= + +- 我们可以重载赋值运算符。不论形参的类型是什么,赋值运算符都必须定义为成员函数。 +- 赋值运算符必须定义成类的成员,复合赋值运算符通常情况下也应该这么做。这两类运算符都应该返回左侧运算对象的引用。 + +## 下标运算符[] + +- 下标运算符必须是成员函数。 +- 一般会定义两个版本: + - 1.返回普通引用。 + - 2.类的常量成员,并返回常量引用。 + +## 递增和递减运算符(++、--) + +- 定义递增和递减运算符的类应该同时定义前置版本和后置版本。 +- 通常应该被定义成类的成员。 +- 为了和内置版本保持一致,前置运算符应该返回递增或递减后对象的引用。 +- 同样为了和内置版本保持一致,后置运算符应该返回递增或递减前对象的值,而不是引用。 +- 后置版本接受一个额外的,不被使用的`int`类型的形参。因为不会用到,所以无需命名。 + +## 成员访问运算符(*、->) + +- 箭头运算符必须是类的成员。解引用运算符通常也是类的成员,尽管并非必须如此。 +- 重载的箭头运算符必须返回类的指针或者自定义了箭头运算符的某个类的对象。 +- 解引用和乘法的区别是一个是一元运算符,一个是二元运算符。 + +## 函数调用运算符 + +- 可以像使用函数一样,调用该类的对象。因为这样对待类同时也能存储状态,所以与普通函数相比更加灵活。 +- 函数调用运算符必须是成员函数。 +- 一个类可以定义多个不同版本的调用运算符,相互之间应该在参数数量或类型上有所区别。 +- 如果累定义了调用运算符,则该类的对象称作**函数对象**。 + +### `lambda`是函数对象 + +- `lambda`捕获变量:`lambda`产生的类必须为每个值捕获的变量建立对应的数据成员,同时创建构造函数。 + +### 标准库定义的函数对象 + +**标准库函数对象**: + +| 算术 | 关系 | 逻辑 | +|-----|-----|-----| +| `plus` | `equal_to` | `logical_and` | +| `minus` | `not_equal_to` | `logical_or` | +| `multiplies` | `greater` | `logical_not` | +| `divides` | `greater_equal` | | +| `modulus` | `less` | | +| `negate` | `less_equal` | | + +- 可以在算法中使用标准库函数对象。 + +### 可调用对象与function + +**标准库function类型**: + +| 操作 | 解释 | +|-----|-----| +| `function f;` | `f`是一个用来存储可调用对象的空`function`,这些可调用对象的调用形式应该与类型`T`相同。 | +| `function f(nullptr);` | 显式地构造一个空`function` | +| `function f(obj)` | 在`f`中存储可调用对象`obj`的副本 | +| `f` | 将`f`作为条件:当`f`含有一个可调用对象时为真;否则为假。 | +| 定义为`function`的成员的类型 | | +| `result_type` | 该`function`类型的可调用对象返回的类型 | +| `argument_type` | 当`T`有一个或两个实参时定义的类型。如果`T`只有一个实参,则`argument_type` | +| `first_argument_type` | 第一个实参的类型 | +| `second_argument_type` | 第二个实参的类型 | + +- 例如:声明一个`function`类型,它可以表示接受两个`int`,返回一个`int`的可调用对象。`function` + +## 重载、类型转换、运算符 + +### 类型转换运算符 + +- 类型转换运算符是类的一种特殊成员函数,它负责将一个类类型的值转换成其他类型。类型转换函数的一般形式如下:`operator type() const;` +- 一个类型转换函数必须是类的成员函数;它不能声明返回类型,形参列表也必须为空。类型转换函数通常应该是`const`。 +- 避免过度使用类型转换函数。 +- C++11引入了显式的类型转换运算符。 +- 向`bool`的类型转换通常用在条件部分,因此`operator bool`一般定义成`explicit`的。 + +### 避免有二义性的类型转换 + +- 通常,不要为类第几个亿相同的类型转换,也不要在类中定义两个及以上转换源或转换目标是算术类型的转换。 +- 在调用重载函数时,如果需要额外的标准类型转换,则该转换的级别只有当所有可行函数都请求同一个用户定义的类型转换时才有用。如果所需的用户定义的类型转换不止一个,则该调用具有二义性。 + +### 函数匹配与重载运算符 + +- 如果`a`是一种类型,则表达式`a sym b`可能是: + - `a.operatorsym(b);` + - `operatorsym(a,b);` +- 如果我们队同一个类既提供了转换目标是算术类型的类型转换,也提供了重载的运算符,则将会遇到重载运算符与内置运算符的二义性问题。 diff --git a/docs/Cpp-Notes-main/Cpp-Primer/15.面向对象程序设计.md b/docs/Cpp-Notes-main/Cpp-Primer/15.面向对象程序设计.md new file mode 100644 index 0000000..66b35b3 --- /dev/null +++ b/docs/Cpp-Notes-main/Cpp-Primer/15.面向对象程序设计.md @@ -0,0 +1,148 @@ +# 第十五章 面向对象程序设计 + +## OOP:概述 + +- 面向对象程序设计(object-oriented programming)的核心思想是数据抽象、继承和动态绑定。 +- **继承**(inheritance): + - 通过继承联系在一起的类构成一种层次关系。 + - 通常在层次关系的根部有一个**基类**(base class)。 + - 其他类直接或者简介从基类继承而来,这些继承得到的类成为**派生类**(derived class)。 + - 基类负责定义在层次关系中所有类共同拥有的成员,而每个派生类定义各自特有的成员。 + - 对于某些函数,基类希望它的派生类个自定义适合自己的版本,此时基类就将这些函数声明成**虚函数**(virtual function)。 + - 派生类必须通过使用**类派生列表**(class derivation list)明确指出它是从哪个基类继承而来。形式:一个冒号,后面紧跟以逗号分隔的基类列表,每个基类前都可以有访问说明符。`class Bulk_quote : public Quote{};` + - 派生类必须在其内部对所有重新定义的虚函数进行声明。可以在函数之前加上`virtual`关键字,也可以不加。C++11新标准允许派生类显式地注明它将使用哪个成员函数改写基类的虚函数,即在函数的形参列表之后加一个`override`关键字。 +- **动态绑定**(dynamic binding,又称运行时绑定): + - 使用同一段代码可以分别处理基类和派生类的对象。 + - 函数的运行版本由实参决定,即在运行时选择函数的版本。 + +## 定义基类和派生类 + +### 定义基类 + +- 基类通常都应该定义一个虚析构函数,即使该函数不执行任何实际操作也是如此。 +- 基类通过在其成员函数的声明语句前加上关键字`virtual`使得该函数执行**动态绑定**。 +- 如果成员函数没有被声明为虚函数,则解析过程发生在编译时而非运行时。 +- 访问控制: + - `protected` : 基类和和其派生类还有友元可以访问。 + - `private` : 只有基类本身和友元可以访问。 + +### 定义派生类 + +- 派生类必须通过类派生列表(class derivation list)明确指出它是从哪个基类继承而来。形式:冒号,后面紧跟以逗号分隔的基类列表,每个基类前面可以有一下三种访问说明符的一个:`public`、`protected`、`private`。 +- C++11新标准允许派生类显式地注明它将使用哪个成员函数改写基类的虚函数,即在函数的形参列表之后加一个`override`关键字。 +- 派生类构造函数:派生类必须使用基类的构造函数去初始化它的基类部分。 +- 静态成员:如果基类定义了一个基类成员,则在整个继承体系中只存在该成员的唯一定义。 +- 派生类的声明:声明中不包含它的派生列表。 +- C++11新标准提供了一种防止继承的方法,在类名后面跟一个关键字`final`。 + +### 类型转换与继承 + +- 理解基类和派生类之间的类型抓换是理解C++语言面向对象编程的关键所在。 +- 可以将基类的指针或引用绑定到派生类对象上。 +- 不存在从基类向派生类的隐式类型转换。 +- 派生类向基类的自动类型转换只对指针或引用类型有效,对象之间不存在类型转换。 + +## 虚函数 + +- 使用虚函数可以执行动态绑定。 +- OOP的核心思想是多态性(polymorphism)。 +- 当且仅当对通过指针或引用调用虚函数时,才会在运行时解析该调用,也只有在这种情况下对象的动态类型才有可能与静态类型不同。 +- 派生类必须在其内部对所有重新定义的虚函数进行声明。可以在函数之前加上`virtual`关键字,也可以不加。 +- C++11新标准允许派生类显式地注明它将使用哪个成员函数改写基类的虚函数,即在函数的形参列表之后加一个`override`关键字。 +- 如果我们想覆盖某个虚函数,但不小心把形参列表弄错了,这个时候就不会覆盖基类中的虚函数。加上`override`可以明确程序员的意图,让编译器帮忙确认参数列表是否出错。 +- 如果虚函数使用默认实参,则基类和派生类中定义的默认实参最好一致。 +- 通常,只有成员函数(或友元)中的代码才需要使用**作用域运算符**(`::`)来回避虚函数的机制。 + +## 抽象基类 + +- **纯虚函数**(pure virtual):清晰地告诉用户当前的函数是没有实际意义的。纯虚函数无需定义,只用在函数体的位置前书写`=0`就可以将一个虚函数说明为纯虚函数。 +- 含有纯虚函数的类是**抽象基类**(abstract base class)。不能创建抽象基类的对象。 + +## 访问控制与继承 + +- 受保护的成员: + - `protected`说明符可以看做是`public`和`private`中的产物。 + - 类似于私有成员,受保护的成员对类的用户来说是不可访问的。 + - 类似于公有成员,受保护的成员对于派生类的成员和友元来说是可访问的。 + - 派生类的成员或友元只能通过派生类对象来访问基类的受保护成员。派生类对于一个基类对象中的受保护成员没有任何访问特权。 +- 派生访问说明符: + - 对于派生类的成员(及友元)能否访问其直接积累的成员没什么影响。 + - 派生访问说明符的目的是:控制派生类用户对于基类成员的访问权限。比如`struct Priv_Drev: private Base{}`意味着在派生类`Priv_Drev`中,从`Base`继承而来的部分都是`private`的。 +- 友元关系不能继承。 +- 改变个别成员的可访问性:使用`using`。 +- 默认情况下,使用`class`关键字定义的派生类是私有继承的;使用`struct`关键字定义的派生类是公有继承的。 + +## 继承中的类作用域 + +- 每个类定义自己的作用域,在这个作用域内我们定义类的成员。当存在继承关系时,派生类的作用域嵌套在其基类的作用域之内。 +- 派生类的成员将隐藏同名的基类成员。 +- 除了覆盖继承而来的虚函数之外,派生类最好不要重用其他定义在基类中的名字。 + +## 构造函数与拷贝控制 + +### 虚析构函数 + +- 基类通常应该定义一个虚析构函数,这样我们就能动态分配继承体系中的对象了。 +- 如果基类的析构函数不是虚函数,则`delete`一个指向派生类对象的基类指针将产生未定义的行为。 +- 虚析构函数将阻止合成移动操作。 + +### 合成拷贝控制与继承 + +- 基类或派生类的合成拷贝控制成员的行为和其他合成的构造函数、赋值运算符或析构函数类似:他们对类本身的成员依次进行初始化、赋值或销毁的操作。 + +### 派生类的拷贝控制成员 + +- 当派生类定义了拷贝或移动操作时,该操作负责拷贝或移动包括基类部分成员在内的整个对象。 +- 派生类析构函数:派生类析构函数先执行,然后执行基类的析构函数。 + +### 继承的构造函数 + +- C++11新标准中,派生类可以重用其直接基类定义的构造函数。 +- 如`using Disc_quote::Disc_quote;`,注明了要继承`Disc_quote`的构造函数。 + +## 容器与继承 + +- 当我们使用容器存放继承体系中的对象时,通常必须采用间接存储的方式。 +- 派生类对象直接赋值给积累对象,其中的派生类部分会被切掉。 +- 在容器中放置(智能)指针而非对象。 +- 对于C++面向对象的编程来说,一个悖论是我们无法直接使用对象进行面向对象编程。相反,我们必须使用指针和引用。因为指针会增加程序的复杂性,所以经常定义一些辅助的类来处理这些复杂的情况。 + + +## 文本查询程序再探 + +- 使系统支持:单词查询、逻辑非查询、逻辑或查询、逻辑与查询。 + +### 面向对象的解决方案 + +- 将几种不同的查询建模成相互独立的类,这些类共享一个公共基类: + - `WordQuery` + - `NotQuery` + - `OrQuery` + - `AndQuery` +- 这些类包含两个操作: + - `eval`:接受一个`TextQuery`对象并返回一个`QueryResult`。 + - `rep`:返回基础查询的`string`表示形式。 +- 继承和组合: + - 当我们令一个类公有地继承另一个类时,派生类应当反映与基类的“是一种(Is A)”的关系。 + - 类型之间另一种常见的关系是“有一个(Has A)”的关系。 +- 对于面向对象编程的新手来说,想要理解一个程序,最困难的部分往往是理解程序的设计思路。一旦掌握了设计思路,接下来的实现也就水到渠成了。 + +**Query程序设计**: + +| 操作 | 解释 | +|-----|-----| +| `Query`程序接口类和操作 | | +| `TextQuery` | 该类读入给定的文件并构建一个查找图。包含一个`query`操作,它接受一个`string`实参,返回一个`QueryResult`对象;该`QueryResult`对象表示`string`出现的行。 | +| `QueryResult` | 该类保存一个`query`操作的结果。 | +| `Query` | 是一个接口类,指向`Query_base`派生类的对象。 | +| `Query q(s)` | 将`Query`对象`q`绑定到一个存放着`string s`的新`WordQuery`对象上。 | +| `q1 & q2` | 返回一个`Query`对象,该`Query`绑定到一个存放`q1`和`q2`的新`AndQuery`对象上。 | +| `q1 | q2` | 返回一个`Query`对象,该`Query`绑定到一个存放`q1`和`q2`的新`OrQuery`对象上。 | +| `~q` | 返回一个`Query`对象,该`Query`绑定到一个存放`q`的新`NotQuery`对象上。 | +| `Query`程序实现类 | | +| `Query_base` | 查询类的抽象基类 | +| `WordQuery` | `Query_base`的派生类,用于查找一个给定的单词 | +| `NotQuery` | `Query_base`的派生类,用于查找一个给定的单词 | +| `BinaryQuery` | `Query_base`的派生类,查询结果是`Query`运算对象没有出现的行的集合 | +| `OrQuery` | `Query_base`的派生类,返回它的两个运算对象分别出现的行的并集 | +| `AndQuery` | `Query_base`的派生类,返回它的两个运算对象分别出现的行的交集 | diff --git a/docs/Cpp-Notes-main/Cpp-Primer/16.模板和泛型编程.md b/docs/Cpp-Notes-main/Cpp-Primer/16.模板和泛型编程.md new file mode 100644 index 0000000..ecf9441 --- /dev/null +++ b/docs/Cpp-Notes-main/Cpp-Primer/16.模板和泛型编程.md @@ -0,0 +1,182 @@ +# 第十六章 模板和泛型编程 + +- 面向对象编程和泛型编程都能处理在编写程序时不知道类型的情况。 + - OOP能处理类型在程序运行之前都未知的情况; + - 泛型编程中,在编译时就可以获知类型。 + +## 定义模板 + +- **模板**:模板是泛型编程的基础。一个模板就是一个创建类或函数的蓝图或者说公式。 + +### 函数模板 + +- `template int compare(const T &v1, const T &v2){}` +- 模板定义以关键字 `template`开始,后接**模板形参表**,模板形参表是用**尖括号**`<>`括住的一个或多个**模板形参**的列表,用逗号分隔,**不能为空**。 +- 使用模板时,我们显式或隐式地指定模板实参,将其绑定到模板参数上。 +- 模板类型参数:类型参数前必须使用关键字`class`或者`typename`,这两个关键字含义相同,可以互换使用。旧的程序只能使用`class`。 +- 非类型模板参数:表示一个值而非一个类型。实参必须是常量表达式。`template void array_init(T (&parm)[N]){}` +- 内联函数模板: `template inline T min(const T&, const T&);` +- 模板程序应该尽量减少对实参类型的要求。 +- 函数模板和类模板成员函数的定义通常放在头文件中。 + +### 类模板 + +- 类模板用于生成类的蓝图。 +- 不同于函数模板,编译器不能推断模板参数类型。 +- **定义类模板**: + - `template class Queue {};` +- 实例化类模板:提供显式模板实参列表,来实例化出特定的类。 +- 一个类模板中所有的实例都形成一个独立的类。 +- **模板形参作用域**:模板形参的名字可以在声明为模板形参之后直到模板声明或定义的末尾处使用。 +- 类模板的成员函数: + - `template ret-type Blob::member-name(parm-list)` +- 默认情况下,对于一个实例化了的类模板,其成员只有在使用时才被实例化。 +- 新标准允许模板将自己的类型参数成为友元。`template class Bar{friend T;};`。 +- 模板类型别名:因为模板不是一个类型,因此无法定义一个`typedef`引用一个模板,但是新标准允许我们为类模板定义一个类型别名:`template using twin = pair;` + +### 模板参数 + +- 模板参数与作用域:一个模板参数名的可用范围是在声明之后,至模板声明或定义结束前。 +- 一个特定文件所需要的所有模板的声明通常一起放置在文件开始位置。 +- 当我们希望通知编译器一个名字表示类型时,必须使用关键字`typename`,而不能使用`class`。 +- 默认模板实参:`template class Numbers{}` + +### 成员模板 + +- 成员模板(member template):本身是模板的函数成员。 + - 普通(非模板)类的成员模板。 + - 类模板的成员模板。 + +### 控制实例化 + +- 动机:在多个文件中实例化相同模板的额外开销可能非常严重。 +- 显式实例化: + - `extern template declaration; // 实例化声明` + - `template declaration; // 实例化定义` + +### 效率与灵活性 + + +## 模板实参推断 + +- 对函数模板,编译器利用调用中的函数实参来确定其模板参数,这个过程叫**模板实参推断**。 + +### 类型转换与模板类型参数 + +- 能够自动转换类型的只有: + - 和其他函数一样,顶层`const`会被忽略。 + - 数组实参或函数实参转换为指针。 + +### 函数模板显式实参 + +- 某些情况下,编译器无法推断出模板实参的类型。 +- 定义:`template T1 sum(T2, T3);` +- 使用函数显式实参调用:`auto val3 = sum(i, lng); // T1是显式指定,T2和T3都是从函数实参类型推断而来` +- **注意**:正常类型转换可以应用于显式指定的实参。 + +### 尾置返回类型与类型转换 + +- 使用场景:并不清楚返回结果的准确类型,但知道所需类型是和参数相关的。 +- `template auto fcn(It beg, It end) -> decltype(*beg)` +- 尾置返回允许我们在参数列表之后声明返回类型。 + +标准库的**类型转换**模板: + +- 定义在头文件`type_traits`中。 + +| 对`Mod`,其中`Mod`是: | 若`T`是: | 则`Mod::type`是: | +|-----|-----|-----| +| `remove_reference` | `X&`或`X&&` | `X` | +| | 否则 | `T` | +| `add_const` | `X&`或`const X`或函数 | `T` | +| | 否则 | `const T` | +| `add_lvalue_reference` | `X&` | `T` | +| | `X&&` | `X&` | +| | 否则 | `T&` | +| `add_rvalue_reference` | `X&`或`X&&` | `T` | +| | 否则 | `T&&` | +| `remove_pointer` | `X*` | `X` | +| | 否则 | `T`| +| `add_pointer` | `X&`或`X&&` | `X*` | +| | 否则 | `T*` | +| `make_signed` | `unsigned X` | `X` | +| | 否则 | `T` | +| `make_unsigned` | 带符号类型 | `unsigned X` | +| | 否则 | `T` | +| `remove_extent` | `X[n]` | `X` | +| | 否则 | `T` | +| `remove_all_extents` | `X[n1][n2]...` | `X` | +| | 否则 | `T` | + +### 函数指针和实参推断 + +- 当使用一个函数模板初始化一个函数指针或为一个函数指针赋值时,编译器使用指针的类型来推断模板实参。 + +### 模板实参推断和引用 + +- 从左值引用函数推断类型:若形如`T&`,则只能传递给它一个左值。但如果是`const T&`,则可以接受一个右值。 +- 从右值引用函数推断类型:若形如`T&&`,则只能传递给它一个右值。 +- 引用折叠和右值引用参数: + - 规则1:当我们将一个左值传递给函数的右值引用参数,且右值引用指向模板类型参数时(如`T&&`),编译器会推断模板类型参数为实参的左值引用类型。 + - 规则2:如果我们间接创造一个引用的引用,则这些引用形成了**折叠**。折叠引用只能应用在间接创造的引用的引用,如类型别名或模板参数。对于一个给定类型`X`: + - `X& &`、`X& &&`和`X&& &`都折叠成类型`X&`。 + - 类型`X&& &&`折叠成`X&&`。 + - 上面两个例外规则导致两个重要结果: + - 1.如果一个函数参数是一个指向模板类型参数的右值引用(如`T&&`),则它可以被绑定到一个左值上; + - 2.如果实参是一个左值,则推断出的模板实参类型将是一个左值引用,且函数参数将被实例化为一个左值引用参数(`T&`)。 + +### 理解std::move + +- 标准库`move`函数是使用右值引用的模板的一个很好的例子。 +- 从一个左值`static_cast`到一个右值引用是允许的。 + +```cpp +template +typename remove_reference::type&& move(T&& t) +{ + return static_cast::type&&>(t); +} +``` + +### 转发 + +- 使用一个名为`forward`的新标准库设施来传递参数,它能够保持原始实参的类型。 +- 定义在头文件`utility`中。 +- 必须通过显式模板实参来调用。 +- `forward`返回显式实参类型的右值引用。即,`forward`的返回类型是`T&&`。 + +## 重载与模板 + +- 多个可行模板:当有多个重载模板对一个调用提供同样好的匹配时,会选择最特例化的版本。 +- 非模板和模板重载:对于一个调用,如果一个非函数模板与一个函数模板提供同样好的匹配,则选择非模板版本。 + +## 可变参数模板 + +**可变参数模板**就是一个接受可变数目参数的模板函数或模板类。 + - 可变数目的参数被称为参数包。 + - 模板参数包:标识另个或多个模板参数。 + - 函数参数包:标识另个或者多个函数参数。 + - 用一个省略号来指出一个模板参数或函数参数,表示一个包。 + - `template `,`Args`第一个模板参数包。 + - `void foo(const T &t, const Args& ... rest);`,`rest`是一个函数参数包。 + - `sizeof...`运算符,返回参数的数目。 + +### 编写可变参数函数模板 + +- 可变参数函数通常是递归的:第一步调用处理包中的第一个实参,然后用剩余实参调用自身。 + +### 包扩展 + +- 对于一个参数包,除了获取它的大小,唯一能做的事情就是**扩展**(expand)。 +- 扩展一个包时,还要提供用于每个扩展元素的**模式**(pattern)。 + +### 转发参数包 + +- 新标准下可以组合使用可变参数模板和`forward`机制,实现将实参不变地传递给其他函数。 + +## 模板特例化(Specializations) + +- 定义函数模板特例化:关键字`template`后面跟一个空尖括号对(`<>`)。 +- 特例化的本质是实例化一个模板,而不是重载它。特例化不影响函数匹配。 +- 模板及其特例化版本应该声明在同一个头文件中。所有同名模板的声明应该放在前面,然后是特例化版本。 +- 我们可以部分特例化类模板,但不能部分特例化函数模板。 diff --git a/docs/Cpp-Notes-main/Cpp-Primer/17.标准库特殊设施.md b/docs/Cpp-Notes-main/Cpp-Primer/17.标准库特殊设施.md new file mode 100644 index 0000000..adff081 --- /dev/null +++ b/docs/Cpp-Notes-main/Cpp-Primer/17.标准库特殊设施.md @@ -0,0 +1,367 @@ +# 第十七章 标准库特殊设施 + +## tuple类型 + +- `tuple`是类似`pair`的模板,每个成员类型都可以不同,但`tuple`可以有任意数量的成员。 +- 但每个确定的`tuple`类型的成员数目是固定的。 +- 我们可以将`tuple`看做一个“快速而随意”的数据结构。 + +**tuple支持的操作**: + +| 操作 | 解释 | +|-----|-----| +| `tuple t;` | `t`是一个`tuple`,成员数为`n`,第`i`个成员的类型是`Ti`所有成员都进行值初始化。 | +| `tuple t(v1, v2, ..., vn);` | 每个成员用对应的初始值`vi`进行初始化。此构造函数是`explicit`的。 | +| `make_tuple(v1, v2, ..., vn)` | 返回一个用给定初始值初始化的`tuple`。`tuple`的类型从初始值的类型**推断**。 | +| `t1 == t2` | 当两个`tuple`具有相同数量的成员且成员对应相等时,两个`tuple`相等。 | +| `t1 relop t2` | `tuple`的关系运算使用**字典序**。两个`tuple`必须具有相同数量的成员。 | +| `get(t)` | 返回`t`的第`i`个数据成员的引用:如果`t`是一个左值,结果是一个左值引用;否则,结果是一个右值引用。`tuple`的所有成员都是`public`的。 | +| `tuple_size::value` | 一个类模板,可以通过一个`tuple`类型来初始化。它有一个名为`value`的`public constexpr static`数据成员,类型为`size_t`,表示给定`tuple`类型中成员的数量。 | +| `tuple_element::type` | 一个类模板,可以通过一个整型常量和一个`tuple`类型来初始化。它有一个名为`type`的`public`成员,表示给定`tuple`类型中指定成员的类型。 | + +### 定义和初始化tuple + +定义和初始化示例: + +- `tuple threeD;` +- `tuple threeD{1,2,3};` +- `auto item = make_tuple("0-999-78345-X", 3, 2.00);` + +访问tuple成员: + +- `auto book = get<0>(item);` +- `get<2>(item) *= 0.8;` + +### 使用tuple返回多个值 + +- `tuple`最常见的用途是从一个函数返回多个值。 + +## bitset类型 + +- 处理二进制位的有序集; +- `bitset`也是类模板,但尖括号中输入的是`bitset`的长度而不是元素类型,因为元素类型是固定的,都是一个二进制位。 + +初始化`bitset`的方法: + +| 操作 | 解释 | +|-----|-----| +| `bitset b;` | `b`有`n`位;每一位均是0.此构造函数是一个`constexpr`。 | +| `bitset b(u);` | `b`是`unsigned long long`值`u`的低`n`位的拷贝。如果`n`大于`unsigned long long`的大小,则`b`中超出`unsigned long long`的高位被置为0。此构造函数是一个`constexpr`。 | +| `bitset b(s, pos, m, zero, one);` | `b`是`string s`从位置`pos`开始`m`个字符的拷贝。`s`只能包含字符`zero`或`one`:如果`s`包含任何其他字符,构造函数会抛出`invalid_argument`异常。字符在`b`中分别保存为`zero`和`one`。`pos`默认为0,`m`默认为`string::npos`,`zero`默认为'0',`one`默认为'1'。 | +| `bitset b(cp, pos, m, zero, one);` | 和上一个构造函数相同,但从`cp`指向的字符数组中拷贝字符。如果未提供`m`,则`cp`必须指向一个`C`风格字符串。如果提供了`m`,则从`cp`开始必须至少有`m`个`zero`或`one`字符。 | + +初始化案例; +- `bitset<13> bitvec1(0xbeef);` +- `bitset<32> bitvec4("1100");` + +`bitset`操作: + +| 操作 | 解释 | +|-----|-----| +| `b.any()` | `b`中是否存在1。 | +| `b.all()` | `b`中都是1。 | +| `b.none()` | `b`中是否没有1。 | +| `b.count()` | `b`中1的个数。 | +| `b.size()` | | +| `b.test(pos)` | `pos`下标是否是1 | +| `b.set(pos)` | `pos`置1 | +| `b.set()` | 所有都置1 | +| `b.reset(pos)` | 将位置`pos`处的位复位 | +| `b.reset()` | 将`b`中所有位复位 | +| `b.flip(pos)` | 将位置`pos`处的位取反 | +| `b.flip()` | 将`b`中所有位取反 | +| `b[pos]` | 访问`b`中位置`pos`处的位;如果`b`是`const`的,则当该位置位时,返回`true`;否则返回`false`。 | +| `b.to_ulong()` | 返回一个`unsigned long`值,其位模式和`b`相同。如果`b`中位模式不能放入指定的结果类型,则抛出一个`overflow_error`异常。 | +| `b.to_ullong()` | 类似上面,返回一个`unsigned long long`值。 | +| `b.to_string(zero, one)` | 返回一个`string`,表示`b`中位模式。`zero`和`one`默认为0和1。 | +| `os << b` | 将`b`中二进制位打印为字符`1`或`0`,打印到流`os`。 | +| `is >> b` | 从`is`读取字符存入`b`。当下一个字符不是1或0时,或是已经读入`b.size()`个位时,读取过程停止。 | + +## 正则表达式 + +- 正则表达式(reqular expression)是一种描述字符序列的方法,是一种很强大的工具。 + +正则表达式库组件: + +| 组件 | 解释 | +|-----|-----| +| `regex` | 表示一个正则表达式的类 | +| `regex_match` | 将一个字符序列与一个正则表达式匹配 | +| `regex_search` | 寻找第一个与正则表达式匹配的子序列 | +| `regex_replace` | 使用给定格式替换一个正则表达式 | +| `sregex_iterator` | 迭代器适配器,调用`regex_searcg`来遍历一个`string`中所有匹配的子串 | +| `smatch` | 容器类,保存在`string`中搜索的结果 | +| `ssub_match` | `string`中匹配的子表达式的结果 | + + +`regex_match`和`regex_search`的参数: + +| 操作 | 解释 | +|-----|-----| +| `(seq, m, r, mft)` | 在字符序列`seq`中查找`regex`对象`r`中的正则表达式。`seq`可以是一个`string`、标识范围的一对迭代器、一个指向空字符结尾的字符数组的指针。 | +| `(seq, r, mft)` | `m`是一个`match`对象,用来保存匹配结果的相关细节。`m`和`seq`必须具有兼容的类型。`mft`是一个可选的`regex_constants::match_flag_type`值。 | + +- 这些操作会返回`bool`值,指出是否找到匹配。 + +### 使用正则表达式库 + +- `regex`使用的正则表达式语言是`ECMAScript`,模式`[[::alpha::]]`匹配任意字母。 +- 由于反斜线是C++中的特殊字符,在模式中每次出现`\`的地方,必须用一个额外的反斜线`\\`告知C++我们需要一个反斜线字符。 +- 简单案例: + - `string pattern("[^c]ei"); pattern = "[[:alpha:]]*" + pattern + "[[:alpha:]]*"` 查找不在字符c之后的字符串ei + - `regex r(pattern);` 构造一个用于查找模式的regex + - `smatch results;` 定义一个对象保存搜索结果 + - `string test_str = "receipt freind theif receive";` + - `if (regex_search(test_str, results, r)) cout << results.str() << endl;` 如有匹配子串,打印匹配的单词。 + +`regex`(和`wregex`)选项: + +| 操作 | 解释 | +|-----|-----| +| `regex r(re)` `regex r(re, f)` | `re`表示一个正则表达式,它可以是一个`string`、一对表示字符范围的迭代器、一个指向空字符结尾的字符数组的指针、一个字符指针和一个计数器、一个花括号包围的字符列表。`f`是指出对象如何处理的标志。`f`通过下面列出来的值来设置。如果未指定`f`,其默认值为`ECMAScript`。 | +| `r1 = re` | 将`r1`中的正则表达式替换Wie`re`。`re`表示一个正则表达式,它可以是另一个`regex`对象、一个`string`、一个指向空字符结尾的字符数组的指针或是一个花括号包围的字符列表。 | +| `r1.assign(re, f)` | 和使用赋值运算符(=)的效果相同:可选的标志`f`也和`regex`的构造函数中对应的参数含义相同。 | +| `r.mark_count()` | `r`中子表达式的数目 | +| `r.flags()` | 返回`r`的标志集 | + +定义`regex`时指定的标志: + +| 操作 | 解释 | +|-----|-----| +| `icase` | 在匹配过程中忽略大小写 | +| `nosubs` | 不保存匹配的子表达式 | +| `optimize` | 执行速度优先于构造速度 | +| `ECMAScript` | 使用`ECMA-262`指定的语法 | +| `basic` | 使用`POSIX`基本的正则表达式语法 | +| `extended` | 使用`POSIX`扩展的正则表达式语法 | +| `awk` | 使用`POSIX`版本的`awk`语言的语法 | +| `grep` | 使用`POSIX`版本的`grep`的语法 | +| `egrep` | 使用`POSIX`版本的`egrep`的语法 | + +- 可以将正则表达式本身看做是一种简单程序语言设计的程序。在运行时,当一个`regex`对象被初始化或被赋予新模式时,才被“编译”。 +- 如果编写的正则表达式存在错误,会在运行时抛出一个`regex_error`的异常。 +- 避免创建不必要的正则表达式。构建一个`regex`对象可能比较耗时。 + +### 匹配与regex迭代器类型 + +`sregex_iterator`操作(用来获得所有匹配): + +| 操作 | 解释 | +|-----|-----| +| `sregex_iterator it(b, e, r);` | 一个`sregex_iterator`,遍历迭代器`b`和`e`表示的`string`。它调用`sregex_search(b, e, r)`将`it`定位到输入中第一个匹配的位置。 | +| `sregex_iterator end;` | `sregex_iterator`的尾后迭代器 | +| `*it`, `it->` | 根据最后一个调用`regex_search`的结果,返回一个`smatch`对象的引用或一个指向`smatch`对象的指针。 | +| `++it` , `it++` | 从输入序列当前匹配位置开始调用`regex_search`。前置版本返回递增后迭代器;后置版本返回旧值。 | +| `it1 == it2` | 如果两个`sregex_iterator`都是尾后迭代器,则它们相等。两个非尾后迭代器是从相同的输入序列和`regex`对象构造,则它们相等。 | + +示例: + +```cpp +// 将字符串file中所有匹配模式r的子串输出 +for (sregex_iterator it(file.begin(), file.end(), r), end_it; it != end_it; ++it){ + cout << it ->str() << endl; +} +``` + +`smatch`操作: + +| 操作 | 解释 | +|-----|-----| +| `m.ready()` | 如果已经通过调用`regex_search`或`regex_match`设置了`m`,则返回`true`;否则返回`false`。如果`ready`返回`false`,则对`m`进行操作是未定义的。 | +| `m.size()` | 如果匹配失败,则返回0,;否则返回最近一次匹配的正则表达式中子表达式的数目。 | +| `m.empty()` | 等价于`m.size() == 0` | +| `m.prefix()` | 一个`ssub_match`对象,标识当前匹配之前的序列 | +| `m.suffix()` | 一个`ssub_match`对象,标识当前匹配之后的部分 | +| `m.format(...)` | | +| `m.length(n)` | 第`n`个匹配的子表达式的大小 | +| `m.position(n)` | 第`n`个子表达式距离序列开始的长度 | +| `m.str(n)` | 第`n`个子表达式匹配的`string` | +| `m[n]` | 对应第`n`个子表达式的`ssub_match`对象 | +| `m.begin(), m.end()` | 表示`m`中`ssub_match`元素范围的迭代器。 | +| `m.cbegin(), m.cend()` | 常量迭代器 | + +### 使用子表达式 + +- 正则表达式语法通常用括号表示子表达式。 +- 子表达式的索引从1开始。 +- 在`fmt`中用`$`后跟子表达式的索引号来标识一个特定的子表达式。 + +示例: + +```cpp +if (regex_search(filename, results, r)) + cout << results.str(1) << endl; // .str(1)获取第一个子表达式匹配结果 +``` + +`ssub_match`子匹配操作: + +| 操作 | 解释 | +|-----|-----| +| `matched` | 一个`public bool`数据成员,指出`ssub_match`是否匹配了 | +| `first`, `second` | `public`数据成员,指向匹配序列首元素和尾后位置的迭代器。如果未匹配,则`first`和`second`是相等的。 | +| `length()` | 匹配的大小,如果`matched`为`false`,则返回0。 | +| `str()` | 返回一个包含输入中匹配部分的`string`。如果`matched`为`false`,则返回空`string`。 | +| `s = ssub` | 将`ssub_match`对象`ssub`转化为`string`对象`s`。等价于`s=ssub.str()`,转换运算符不是`explicit`的。 | + +### 使用regex_replace + +正则表达式替换操作: + +| 操作 | 解释 | +|-----|-----| +| `m.format(dest, fmt, mft)`, `m.format(fmt, mft)` | 使用格式字符串`fmt`生成格式化输出,匹配在`m`中,可选的`match_flag_type`标志在`mft`中。第一个版本写入迭代器`dest`指向的目的为止,并接受`fmt`参数,可以是一个`string`,也可以是一个指向空字符结尾的字符数组的指针。`mft`的默认值是`format_default`。 | +| `rege_replace(dest, seq, r, fmt, mft)`, `regex_replace(seq, r, fmt, mft)` | 遍历`seq`,用`regex_search`查找与`regex`对象`r`相匹配的子串,使用格式字符串`fmt`和可选的`match_flag_type`标志来生成输出。`mft`的默认值是`match_default` | + +示例: + +```cpp +string phone = "(\\()?(\\d{3})(\\))?([-. ])?(\\d{3})([-. ]?)(\\d{4})" +string fmt = "$2.$5.$7"; // 将号码格式改为ddd.ddd.dddd +regex r(phone); // 用来寻找模式的regex对象 +string number = "(908) 555-1800"; +cout << regex_replace(number, r, fmt) << endl; +``` + +匹配标志: + +| 操作 | 解释 | +|-----|-----| +| `match_default` | 等价于`format_default` | +| `match_not_bol` | 不将首字符作为行首处理 | +| `match_not_eol` | 不将尾字符作为行尾处理 | +| `match_not_bow` | 不将首字符作为单词首处理 | +| `match_not_eow` | 不将尾字符作为单词尾处理 | +| `match_any` | 如果存在多于一个匹配,则可以返回任意一个匹配 | +| `match_not_null` | 不匹配任何空序列 | +| `match_continuous` | 匹配必须从输入的首字符开始 | +| `match_prev_avail` | 输入序列包含第一个匹配之前的内容 | +| `format_default` | 用`ECMAScript`规则替换字符串 | +| `format_sed` | 用`POSIX sed`规则替换字符串 | +| `format_no_copy` | 不输出输入序列中未匹配的部分 | +| `format_first_only` | 只替换子表达式的第一次出现 | + +## 随机数 + +- 新标准之前,C和C++都依赖一个简单的C库函数`rand`来生成随机数,且只符合均匀分布。 +- 新标准:**随机数引擎** + **随机数分布类**, 定义在 `random`头文件中。 +- C++程序应该使用`default_random_engine`类和恰当的分布类对象。 + +### 随机数引擎和分布 + +随机数引擎操作 + +| 操作 | 解释 | +|-----|-----| +| `Engine e;` | 默认构造函数;使用该引擎类型默认的种子 | +| `Engine e(s);` | 使用整型值`s`作为种子 | +| `e.seed(s)` | 使用种子`s`重置引擎的状态 | +| `e.min()`,`e.max()` | 此引擎可生成的最小值和最大值 | +| `Engine::result_type` | 此引擎生成的`unsigned`整型类型 | +| `e.discard(u)` | 将引擎推进`u`步;`u`的类型为`unsigned long long` | + +示例: + +```cpp +// 初始化分布类型 +uniform_int_distribution u(0, 9); +// 初始化引擎 +default_random_engine e; +// 随机生成0-9的无符号整数 +cout << u(e) << endl; +``` + +**设置随机数发生器种子**: + +- 种子就是一个数值,引擎可以利用它从序列中一个新位置重新开始生成随机数。 +- 种子可以使用系统函数`time(0)`。 + +### 其他随机数分布 + +分布类型的操作: + +| 操作 | 解释 | +|-----|-----| +| `Dist d;` | 默认够赞函数;使`d`准备好被使用。其他构造函数依赖于`Dist`的类型;分布类型的构造函数是`explicit`的。 | +| `d(e)` | 用相同的`e`连续调用`d`的话,会根据`d`的分布式类型生成一个随机数序列;`e`是一个随机数引擎对象。 | +| `d.min()`,`d.max()` | 返回`d(e)`能生成的最小值和最大值。 | +| `d.reset()` | 重建`d`的状态,是的随后对`d`的使用不依赖于`d`已经生成的值。 | + +## IO库再探 + +### 格式化输入与输出 + +- 使用操纵符改变格式状态。 +- 控制布尔值的格式: `cout << boolalpha << true << endl;` +- 指定整型的进制:`cout << dec << 20 << endl;` + +定义在`iostream`中的操纵符: + +| 操纵符 | 解释 | +|-----|-----| +| `boolalpha` | 将`true`和`false`输出为字符串 | +| `* noboolalpha` | 将`true`和`false`输出为1,0 | +| `showbase` | 对整型值输出表示进制的前缀 | +| `* noshowbase` | 不生成表示进制的前缀 | +| `showpoint` | 对浮点值总是显示小数点 | +| `* noshowpoint` | 只有当浮点值包含小数部分时才显示小数点 | +| `showpos` | 对非负数显示`+` | +| `* noshowpos` | 对非负数不显示`+` | +| `uppercase` | 在十六进制中打印`0X`,在科学计数法中打印`E` | +| `* nouppercase` | 在十六进制中打印`0x`,在科学计数法中打印`e` | +| `* dec` | 整型值显示为十进制 | +| `hex` | 整型值显示为十六进制 | +| `oct` | 整型值显示为八进制 | +| `left` | 在值的右侧添加填充字符 | +| `right` | 在值的左侧添加填充字符 | +| `internal` | 在符号和值之间添加填充字符 | +| `fixed` | 浮点值显示为定点十进制 | +| `scientific` | 浮点值显示为科学计数法 | +| `hexfloat` | 浮点值显示为十六进制(C++11) | +| `defaultfloat` | 充值浮点数格式为十进制(C++11) | +| `unitbuf` | 每次输出操作后都刷新缓冲区 | +1| `* nounitbuf` | 恢复正常的缓冲区刷新模式 | +| `* skipws` | 输入运算符跳过空白符 | +| `noskipws` | 输入运算符不跳过空白符 | +| `flush` | 刷新`ostream`缓冲区 | +| `ends` | 插入空字符,然后刷新`ostream`缓冲区 | +| `endl` | 插入换行,然后刷新`ostream`缓冲区 | + +其中`*`表示默认的流状态。 + +### 未格式化的输入/输出操作 + +单字节低层IO操作: + +| 操作 | 解释 | +|-----|-----| +| `is.get(ch)` | 从`istream is`读取下一个字节存入字符`cn`中。返回`is`。 | +| `os.put(ch)` | 将字符`ch`输出到`ostream os`。返回`os`。 | +| `is.get()` | 将`is`的下一个字节作为`int`返回 | +| `is.putback(ch)` | 将字符`ch`放回`is`。返回`is`。 | +| `is.unget()` | 将`is`向后移动一个字节。返回`is`。 | +| `is.peek()` | 将下一个字节作为`int`返回,但不从流中删除它。 | + +多字节低层IO操作: + +| 操作 | 解释 | +|-----|-----| +| `is.get(sink, size, delim)` | 从`is`中读取最多`size`个字节,并保存在字符数组中,字符数组的起始地址由`sink`给出。读取过程直到遇到字符`delim`或读取了`size`个字节或遇到文件尾时停止。如果遇到了`delim`,则将其留在输入流中,不读取出来存入`sink`。 | +| `is.getline(sink, size, delim)` | 与接收三个参数的`get`版本类似,但会读取并丢弃`delim`。 | +| `is.read(sink, size)` | 读取最多`size`个字节,存入字符数组`sink`中。返回`is`。 | +| `is.gcount()` | 返回上一个未格式化读取从`is`读取的字节数 | +| `os.write(source, size)` | 将字符数组`source`中的`size`个字节写入`os`。返回`os`。 | +| `is.ignore(size, delim)` | 读取并忽略最多`size`个字符,包括`delim`。与其他未格式化函数不同,`ignore`有默认参数:`size`默认值是1,`delim`的默认值为文件尾。 | + +- 注意:一般情况下,主张使用标准库提供的高层抽象,低层函数容易出错。 + +### 流随机访问 + +- 只适用于`fstream`和`sstream`。 +- 通过将标记`seek`到一个给定位置来重定位它。 +- `tell`告诉我们标记的当前位置。 + +| 操作 | 解释 | +|-----|-----| +| `tellg()`,`tellp` | 返回一个输入流中(`tellg`)或输出流中(`tellp`)标记的当前位置。 | +| `seekg(pos)`,`seekp(pos)` | 在一个输入流或输出流中将标记重定位到给定的绝对地址。`pos`通常是一个当前`teelg`或`tellp`返回的值。 | +| `seekp(off, from)`,`seekg(off, from)` | 在一个输入流或输出流中将标记定位到`from`之前或之后`off`个字符,`from`可以是下列值之一:`beg`,偏移量相对于流开始位置;`cur`,偏移量相对于流当前位置;`end`,偏移量相对于流结尾位置。 | \ No newline at end of file diff --git a/docs/Cpp-Notes-main/Cpp-Primer/18.用于大型程序的工具.md b/docs/Cpp-Notes-main/Cpp-Primer/18.用于大型程序的工具.md new file mode 100644 index 0000000..f4eefbc --- /dev/null +++ b/docs/Cpp-Notes-main/Cpp-Primer/18.用于大型程序的工具.md @@ -0,0 +1,200 @@ +# 第十八章 用于大型程序的工具 + +大规模应用程序的特殊要求包括: + +- 在独立开发的子系统之间协同处理错误的能力。 +- 使用各种库进行协同开发的能力。 +- 对比较复杂的应用概念建模的能力。 + +## 异常处理 + +**异常处理**(exception handling)机制允许程序中独立开发的部分能够在运行时就出现的问题进行通信并作出相应的处理。 + +### 抛出异常 + +在C++语言中,我们通过**抛出**(throwing)一条表达式来**引发**(raised)一个异常。异常类型和当前的调用链决定了哪段**处理代码**(handler)将用来处理该异常。 + +程序的控制权从`throw`转移到`catch`模块。 + +**栈展开**:当`throw`出现在一个`try语句块`时,检查该`try语句块`相关的`catch`字句,若有匹配则处理;若无匹配,则继续检查外层的`try`匹配的`catch`。 + +若一个异常没有被捕获,则它将终止当前的程序。 + +对象销毁: + +- 块退出后,它的局部对象将被销毁。 +- 若异常发生在构造函数中,即使某个对象只构造了一部分,也要确保已构造的成员正确地被销毁。 +- 将资源释放放在类的析构函数中,以保证资源能被正确释放。析构函数本身不会引发异常。 + +### 捕获异常 + +若无需访问抛出的异常对象,则可以忽略捕获形参的名字。 + +通常,若`catch`接受的异常与某个继承体系有关,则最好将该`catch`的参数定义成引用类型。 + +搜索`catch`未必是最佳匹配,而是第一个匹配,因此,越细化的`catch`越应该放在`catch`列表前段。 + +重新抛出:`catch`代码执行一条`throw;`将异常传递给另一个`catch`语句。 + +捕获所有异常:`catch(...)` + +### 构造函数 + +处理构造函数初始值异常的唯一方法是将构造函数协程函数`try`语句块。 + +示例: + +```cpp +template +Blob::Blob(std::initializer_list il) try: + data(std::make_shared >(il){ + /*函数体*/ + } catch(const std::bad_alloc &e){ handle_out_of_memory(e); } +``` + +### noexcept异常说明 + +使用`noexcept`说明指定某个函数不会抛出异常。 + +示例: + +```cpp +void recoup(int) noexcept; //C++11 +coid recoup(int) throw(); //老版本 +``` + +### 异常类层次 + +标准exception层次: + +- exception + - bad_cast + - bad_alloc + - runtime_error + - overflow_error + - underflow_error + - range_error + - logic_error + - domain_error + - invalid_argument + - out_of_range + - length_error + +自定义异常类: + +示例: + +```cpp +class out_of_stock: public std::runtime_error { + explicit out_of_stock(const std::string &s): + std::runtime_error(s){ } +}; +``` + +## 命名空间 + +多个库将名字放置在全局命名空间中将引发**命名空间污染**(namespace pollution)。**命名空间**(namespace)分割了全局命名空间,其中每个命名空间是一个作用域。 + +### 命名空间定义 + +命名空间的定义包含两部分:1.关键字`namespace`;2.命名空间名称。后面是一系列由花括号括起来的声明和定义。命名空间作用域后面无需分号。 + +示例: + +```cpp +namespace cplusplus_primer{ + +} +``` + +每个命名空间都是一个**作用域**。定义在某个命名空间内的名字可以被该命名空间内的其他成员直接访问,也可以被这些成员内嵌套作用域中的任何单位访问。位于该命名空间之外的代码必须明确指出所用的名字是属于哪个命名空间的。 + +命名空间可以是**不连续**的。这点不同于其他作用域,意味着同一命名空间可以在多处出现。 + +**内联命名空间**(C++11): + +无需使用该命名空间的前缀,通过外层命名空间就可以直接访问。 + +示例: + +```cpp +namespace cplusplus_primer{ + inline namespace FifthEd{ + // 表示本书第5版代码 + class Query_base {}; + } +} + +cplusplus_primer::Query_base qb; +``` + +**未命名的命名空间**: + +指关键字`namespace`后面紧跟花括号的用法。未命名的命名空间中定义的变量拥有静态的声明周期:在第一次使用前创建,直到程序结束才销毁。不能跨越多个文件。 + +### 使用命名空间成员 + +像`namespace_name::member_name`这样使用命名空间的成员非常繁琐。 + +**命名空间的别名**: + +```cpp +namespace primer = cplusplus_primer; +``` + +**using声明**(using declaration): + +一条`using`声明语句一次只引入命名空间的一个成员。 + +```cpp +using std::string; + +string s = "hello"; +``` + +**using指示**(using directive): + +使得某个特定的命名空间中所有的名字都可见。 + +```cpp +using namespace std; + +string s = "hello"; +``` + +### 类、命名空间与作用域 + +```cpp +namespace A{ + class C1{ + public: + int f3(); + } +} + +A::C1::f3 +``` + +### 重载与命名空间 + +`using`声明语句声明的是一个名字,而非特定的函数,也就是包括该函数的所有版本,都被引入到当前作用域中。 + +## 多重继承与虚继承 + +### 多重继承 + +### 类型转换与多个基类 + +### 多重继承下的类作用域 + +* 当一个类拥有多个基类时,有可能出现派生类从两个或更多基类中继承了同名成员的情况。此时,不加前缀限定符直接使用该名字将引发二义性。 + +### 虚继承 + +* 虚继承的目的是令某个类做出声明,承诺愿意共享它的基类。其中,共享的基类子对象成为**虚基类**。在这种机制下,不论虚基类在继承体系中出现了多少次,在派生类中都只包含唯一一个共享的虚基类子对象。 +* 虚派生只影响从指定了虚基类的派生类中进一步派生出的类,它不会影响派生类本身。 + +### 构造函数与虚继承 + +* h含有虚基类的对象的构造顺序与一般的顺序稍有**区别**:首先使用提供给最底层派生类构造函数的初始值初始化该对象的虚基类子部分,接下来按照直接基类在派生列表中出现的次序对其进行初始化。 +* 虚基类总是先于非虚基类构造,与它们在继承体系中的次序和位置无关。 \ No newline at end of file diff --git a/docs/Cpp-Notes-main/Cpp-Primer/19.特殊工具与技术.md b/docs/Cpp-Notes-main/Cpp-Primer/19.特殊工具与技术.md new file mode 100644 index 0000000..3050bbe --- /dev/null +++ b/docs/Cpp-Notes-main/Cpp-Primer/19.特殊工具与技术.md @@ -0,0 +1,377 @@ +# 第十九章 特殊工具与技术 + +## 控制内存分配 + +### 重载new和delete + +* **`new`表达式的工作机理**: + +```c++ +string *sp = new string("a value"); //分配并初始化一个string对象 +string *arr = new string[10]; // 分配10个默认初始化的string对象 +``` + +* 上述代码实际执行了**三步操作**: + * `new`表达式调用一个名为`operator new`(或`operator new []`)的标准库函数,它分配一块**足够大的**、**原始的**、**未命名的**内存空间以便存储特定类型的对象(或对象的数组)。 + * 编译器运行相应的构造函数以构造这些对象,并为其传入初始值。 + * 对象被分配了空间并构造完成,返回一个指向该对象的指针。 + +* **`delete`表达式的工作机理**: + +```c++ +delete sp; // 销毁*sp,然后释放sp指向的内存空间 +delete [] arr; // 销毁数组中的元素,然后释放对应的内存空间 +``` + +* 上述代码实际执行了**两步操作**: + * 对`sp`所指向的对象或者`arr`所指的数组中的元素执行对应的析构函数。 + * 编译器调用名为`operator delete`(或`operator delete[]`)的标准库函数释放内存空间。 +* 当自定义了全局的`operator new`函数和`operator delete`函数后,我们就担负起了控制动态内存分配的职责。这两个函数**必须是正确的**。因为它们是程序整个处理过程中至关重要的一部分。 +* 标准库定义了`operator new`函数和`operator delete`函数的8个重载版本: + +```c++ +// 这些版本可能抛出异常 +void *operator new(size_t); // 分配一个对象 +void *operator new[](size_t); // 分配一个数组 +void *operator delete(void*) noexcept; // 释放一个对象 +void *operator delete[](void*) noexcept; // 释放一个数组 + +// 这些版本承诺不会抛出异常 +void *operator new(size_t, nothrow_t&) noexcept; +void *operator new[](size_t, nothrow_t&) noexcept; +void *operator delete(void*, nothrow_t&) noexcept; +void *operator delete[](void*, nothrow_t&) noexcept; +``` + +* 应用程序可以自定义上面函数版本中的任意一个,前提是自定义的版本必须位于**全局作用域**或者**类作用域**中。 +* **注意:** 提供新的`operator new`函数和`operator delete`函数的目的在于改变内存分配的方式,但是不管怎样,都不能改变`new`运算符和`delete`运算符的基本含义。 +* 使用从C语言继承的函数`malloc`和`free`函数能实现以某种方式执行分配内存和释放内存的操作: + +```c++ +#include + +void *operator new(size_t size) { + if(void *mem = malloc(size)) + return mme; + else + throw bad_alloc(); +} + +void operator delete(void *mem) noexcept { + free(mem); +} +``` + +### 定位new表达式 + +* 应该使用new的定位`new(placement new)`形式传递一个地址,定位`new`的形式如下: + +```c++ +new (place_address) type +new (place_address) type (initializers) +new (place_address) type [size] +new (place_address) type [size] {braced initializer list} +// place_address必须是一个指针,同时在initializers中提供一个(可能为空的)以逗号分隔的初始值列表,该初始值列表将用于构造新分配的对象。 +``` + +* 当只传入一个指针类型的实参时,定位`new`表达式构造对象但是不分配内存。 +* 调用析构函数会销毁对象,但是不会释放内存。 + +```c++ +string *sp = new string("a value"); // 分配并初始化一个string对象 +sp->~string(); +``` + +## 运行时类型识别 + +* 运行时类型识别`(run-time type identification, RTTI)`的功能由两个运算符实现: + * `typeid`运算符, 用于返回表达式的类型。 + * `dynamic_cast`运算符,用于将基类的指针或引用安全地转换曾派生类的指针或引用。 +* 使用`RTTI`必须要加倍小心。在可能的情况下,最好定义虚函数而非直接接管类型管理的重任。 + +### dynamic_cast运算符 + +* dynamic_cast运算符的使用形式如下: + +```c++ +dynamic_cast(e) // e必须是一个有效的指针 +dynamic_cast(e) // e必须是一个左值 +dynamic_cast(e) // e不能是左值 +// 以上,type类型必须时一个类类型,并且通常情况下该类型应该含有虚函数。 +// e的类型必须符合三个条件中的任意一个,它们是: +// 1. e的类型是目标type的公有派生类; +// 2. e的类型是目标type的共有基类; +// 3. e的类型就是目标type的类型; + +// 指针类型的dynamic_cast +// 假设Base类至少含有一个虚函数,Derived是Base的共有派生类。 +if (Derived *dp = dynamic_cast(bp)) { + // 使用dp指向的Derived对象 +} else { // bp指向一个Base对象 + // 使用dp指向的Base对象 +} + +// 引用类型的dynamic_cast +void f(const Base &b) { + try { + const Derived &d = dynamic_cast(b); + // 使用b引用的Derived对象 + } catch (bad_cast) { + // 处理类型转换失败的情况 + } +} +``` + +* 可以对一个空指针执行`dynamic_cast`,结果是所需类型的空指针。 + +### typeid运算符 + +* `typeid运算符(typeid operator)`,它允许程序向表达式提问:**你的对象是什么类型?** +* `typeid`表达式的形式是`typeid(e)`,其中`e`可以是任意表达式或类型的名字,它操作的结果是一个常量对象的引用。它可以作用于任意类型的表达式。 +* 通常情况下,使用typeid比较两条表达式的类型是否相同,或者比较一条表达式的类型是否与指定类型相同: + +```c++ +Derived *dp = new Derived; +Base *bp = dp; + +if (typeid(*bp) == typeid(*dp)) { + // bp和dp指向同一类型的对象 +} + +if (typeid(*bp) == typeid(Derived)) { + // bp实际指向Derived对象 +} +``` + +* 当typeid作用于指针时(而非指针所指向的对象),返回的结果是该指针的静态编译时类型。 + +```c++ +// 下面的检查永远是失败的:bp的类型是指向Base的指针 +if (typeid(bp) == typeid(Derived)) { + // 永远不会执行 +} +``` + +### 使用RTTI + +* 用途:为具有继承关系的类实现相等运算符时。对于两个对象来说,如果它们的类型相同并且对应的数据成员取值相同,则说这两个对象是相等的。 + +```c++ +// 类的层次关系 +class Base { + friend bool operator==(const Base&, const Base&); +public: + // Base的接口成员 +protected: + virtual bool equal(const Base&) const; + // Base的数据成员和其他用于实现的成员 +}; + +class Derived: public Base { +public: + // Derived的其他接口成员 +protected: + bool equal(const Base&) const; + // Derived的数据成员和其他用于实现的成员 +}; + +// 类型敏感的相等运算符 +bool operator==(const Base &lhs, const Base &rhs) { + // 如果typeid不相同,返回false;否则虚调用equal + return typeid(lhs) == typeid(rhs) && lhs.equal(rhs); +} + +// 虚equal函数 +bool Derived::equal(const Base &rhs) const { + auto r = dynamic_cast(rhs); + // 执行比较两个Derived对象的操作并返回结果 +} + +// 基类equal函数 +bool Base::equal(const Base &rhs) const { + // 执行比较Base对象的操作 +} +``` + +### type_info类 + +## 枚举类型 + +* 枚举类型`(enumeration)`使我们可以将一组整型常量组织在一起。枚举属于字面值常量类型。 +* **限定作用域的枚举类型(scoped enumeration)**:首先是关键字`enum class(或enum struct)`,随后是枚举类型名字以及用花括号括起来的以逗号分隔的枚举成员列表,最后是一个分号。 + +```c++ +enum class open_modes {input, output, append}; +``` + +* 不限定作用域的枚举类型`(unscoped enumeration)`:省略关键字`class(或struct)`,枚举类型的名字是可选的。 + +```c++ +enum color {red, yellow, green}; + +enum {floatPrec = 6, doublePrec = 10, double_doublePrec = 10}; + +``` + +## 类成员指针 + +**成员指针**:指可以指向类的非静态成员的指针。 + +### 数据成员指针 + +* 和其他指针一样,在声明成员指针时也使用*来表示当前声明的名字是一个指针。与普通指针不同的时,成员指针还必须包含成员所属的类。 + +```c++ +// pdata可以指向一个常量(非常量)Screen对象的string成员 +const string Screen::*pdata; + +// C++11 +auto pdata = &Screen::contents; +``` + +* 当我们初始化一个成员指针或为成员指针赋值时,该指针没有指向任何数据。成员指针指定了成员而非该成员所属的对象,只有当解引用成员指针时才提供对象的信息。 + +```c++ +Screen myScreen, *pScreen = &myScreen; + +auto s = myScreen.*pdata; + +s = pScreen->*pdata; +``` + +### 成员函数指针 + +* 因为函数调用运算符的优先级较高,所以在声明指向成员函数的指针并使用这些的指针进行函数调用时,括号必不可少:`(C::*p)(parms)`和`(obj.*p)(args)`。 + +### 将成员函数用作可调用对象 + +## 嵌套类 + +* 一个类可以定义在另一个类的内部,前者称为嵌套类(nested class)或嵌套类型(nested type)。**嵌套类常用于定义作为实现部分的类**。 +* 嵌套类是一个独立的类,与外层类基本没有什么关系。特别是,外层类的对象和嵌套类的对象是相互独立的。 +* 嵌套类的名字在外层类作用域中是可见的,在外层类作用域之外不可见。 + +## union:一种节省空间的类 + +* `联合(union)`是一种特殊的类。一个`union`可以有多个数据成员,但是在任意时刻只有一个数据成员可以有值。**它不能含有引用类型的成员和虚函数**。 + +```c++ +// Token类型的对象只有一个成员,该成员的类型可能是下列类型中的任意一种 +union Token { + // 默认情况下成员是共有的 + char cval; + int ival; + double dval; +}; + +``` + +* `匿名union(anonymous union)`是一个未命名的`union`,并且在右花括号和分号之间没有任何声明。 + +```c++ +union { + char cval; + int ival; + double dval; +}; + +// 可以直接访问它的成员 +cal = 'c'; +ival = 42; +``` + +* **注意:** `匿名union`不能包含受保护的成员或私有成员,也不能定义成员函数。 + +## 局部类 + +* `局部类(local class)`:可以定义在某个函数的内部的类。它的类型只在定义它的作用域内可见。和嵌套类不同,局部类的成员受到严格限制。 +* 局部类的所有成员(包括函数在内)都必须完整定义在类的内部。因此,局部类的作用与嵌套类相比相差很远。 +* **局部类不能使用函数作用域中的变量。** + +```c++ +int a, val; +void foo(int val) { + static inti si; + enum loc { a = 1024, b}; + + // Bar是foo的局部类 + struct Bar { + Loc locVal; // 正确:使用一个局部类型名 + int barVal; + + void fooBar(Loc l = a) { // 正确:默认实参是Loc::a + barVal = val; // 错误:val是foo的局部变量 + barVal == ::val; // 正确:使用一个全局对象 + barVal = si; // 正确:使用一个静态局部对象 + locVal = b; // 正确:使用一个枚举成员 + } + }; +} + +``` + +## 固有的不可移植的特性 + +所谓不可移植的特性是指**因机器而异的特性**,当将含有不可移植特性的程序从一台机器转移到另一台机器上时,通常需要重新编写该程序。 + +### 位域 + +* 类可以将其(非静态)数据成员定义成**位域(bit-field)**,在一个位域中含有一定数量的二进制位。当一个程序需要向其他程序或硬件设备传递二进制数据时,通常会用到位域。 +* 位域在内存中的布局是与机器相关的。 +* 位域的类型必须是整型或枚举类型。因为带符号位域的行为是由具体实现确定的,通常情况下我们使用无符号类型保存一个位域。 + +```c++ +typedef unsigned int Bit; +class File { + Bit mode: 2; + Bit modified: 1; + Bit prot_owner: 3; + Bit prot_group: 3; + Bit prot_world: 3; +public: + enum modes {READ = 01, WRITE = 02, EXECUTE = 03}; + File &open(modes); + void close(); + void write(); + bool isRead() const; + void setWrite(); +} + +// 使用位域 +void File::write() { + modified = 1; + // ... +} + +void File::close() { + if( modified) + // ...保存内容 +} + +File &File::open(File::modes m) { + mode |= READ; // 按默认方式设置READ + // 其他处理 + if(m & WRITE) // 如果打开了READ和WRITE + // 按照读/写方式打开文件 + return *this; +} +``` + +### volatile限定符 + +* 当对象的值可能在程序的控制或检测之外被改变时,应该将该对象声明为`volatile`。关键字`volatile`告诉编译器不应对这样的对象进行优化。 +* `const`和`volatile`的一个重要区别是不能使用合成的拷贝/移动构造函数及赋值运算符初始化`volatile`对象或者从`volatile`对象赋值。 + +### 链接指示:extern "C" + +* `C++`使用`链接指示(linkage directive)`指出任意非`C++`函数所用的语言。 +* 要想把`C++`代码和其他语言(包括`C`语言)编写的代码放在一起使用,要求我们必须有权访问该语言的编译器,并且这个编译器与当前的`C++`编译器是兼容的。 +* `C++`从C语言继承的标准库函数可以定义为`C`函数,但并非必须:决定使用`C`还是`C++`实现的`C`标准库,是每个`C++`实现的事情。 +* 有时需要在C和C++中编译同一个源文件,为了实现这一目的,在编译C++版本的程序时预处理器定义`__cplusplus`。 + +```c++ +#ifdef __cplusplus +extern "C" +#endif +int strcmp(const char*, const char*); +``` diff --git a/docs/Cpp-Primer-exercise/1.开始.md b/docs/Cpp-Primer-exercise/1.开始.md deleted file mode 100644 index 4690342..0000000 --- a/docs/Cpp-Primer-exercise/1.开始.md +++ /dev/null @@ -1,476 +0,0 @@ ---- -sort: 1 ---- - -# 开始 - -## 练习1.1 -查阅你使用的编译器的文档,确定它所使用的文件名约定。编译并运行第2页的main程序。 - -解: -- ``g++ 1.1.cpp -o main`` -- ``./main`` - -## 练习1.2 -改写程序,让它返回-1。返回值-1通常被当做程序错误的标识。重新编译并运行你的程序,观察你的系统如何处理main返回的错误标识。 - -解: -- 在ubuntu下,使用g++,返回-1,``./main``没有发现任何异常。 -- ``echo $?``,返回255。 - -## 练习1.3 -编写程序,在标准输出上打印Hello, World。 - -解: - -```cpp -#include - -int main() -{ - std::cout << "Hello,World" << std::endl; - return 0; -} -``` - -## 练习1.4 -我们的程序使用加法运算符`+`来将两个数相加。编写程序使用乘法运算符`*`,来打印两个数的积。 - -解: -```cpp -#include - -int main() { - std::cout << "Enter two numbers" << std::endl; - int v1 = 0, v2 = 0; - std::cin>> v1 >> v2; - std::cout << "The product of " << v1 << " and " << v2 << " is "<< v1 * v2 << std::endl; -} -``` - -## 练习1.5 -我们将所有的输出操作放在一条很长的语句中,重写程序,将每个运算对象的打印操作放在一条独立的语句中。 - -解: -```cpp -#include - -int main() -{ - std::cout << "Enter two numbers" << std::endl; - int v1 = 0, v2 = 0; - std::cin >> v1 >> v2; - std::cout << "The product of "; - std::cout << v1; - std::cout << " and "; - std::cout << v2; - std::cout << " is "; - std::cout << v1 * v2; - std::cout << std::endl; -} -``` - -## 练习1.6 -解释下面程序片段是否合法。 -```cpp -std::cout << "The sum of " << v1; - << " and " << v2; - << " is " << v1 + v2 << std::endl; -``` -如果程序是合法的,它的输出是什么?如果程序不合法,原因何在?应该如何修正? - -解: - -程序不合法,有多余的分号,修改如下: -```cpp -std::cout << "The sum of " << v1 - << " and " << v2 - << " is " << v1 + v2 << std::endl; -``` - -## 练习1.7 -编译一个包含不正确的嵌套注释的程序,观察编译器返回的错误信息。 - -解: -```cpp -/* 正常注释 /* 嵌套注释 */ 正常注释*/ -``` -错误信息: -``` - /* 正常注释 /* 嵌套注释 */ 正常注释*/ - ^ -ch1.cpp:97:37: error: stray ‘\255’ in program -ch1.cpp:97:37: error: stray ‘\243’ in program -ch1.cpp:97:37: error: stray ‘\345’ in program -ch1.cpp:97:37: error: stray ‘\270’ in program -ch1.cpp:97:37: error: stray ‘\270’ in program -ch1.cpp:97:37: error: stray ‘\346’ in program -ch1.cpp:97:37: error: stray ‘\263’ in program -ch1.cpp:97:37: error: stray ‘\250’ in program -ch1.cpp:97:37: error: stray ‘\351’ in program -ch1.cpp:97:37: error: stray ‘\207’ in program -ch1.cpp:97:37: error: stray ‘\212’ in program -ch1.cpp: In function ‘int main()’: -ch1.cpp:97:50: error: expected primary-expression before ‘/’ token - /* 正常注释 /* 嵌套注释 */ 正常注释*/ - ^ -ch1.cpp:98:5: error: expected primary-expression before ‘return’ - return 0; - ^ -``` - -## 练习1.8 -指出下列哪些输出语句是合法的(如果有的话): -```cpp -std::cout << "/*"; -std::cout << "*/"; -std::cout << /* "*/" */; -std::cout << /* "*/" /* "/*" */; -``` -预测编译这些语句会产生什么样的结果,实际编译这些语句来验证你的答案(编写一个小程序,每次将上述一条语句作为其主体),改正每个编译错误。 - -解: - -只有第三句编译出错,改成如下即可: -```cpp -std::cout << /* "*/" */"; -``` -第四句等价于输出 `" /* "`。 - -## 练习1.9 - -编写程序,使用`while`循环将50到100整数相加。 - -解: -```cpp -#include - -int main() { - int i = 50, sum = 0; - while (i <= 100) { - sum += i; - i += 1; - } - std::cout << "sum is " << sum << std::endl; - -} -``` - -## 练习1.10 -除了`++`运算符将运算对象的值增加1之外,还有一个递减运算符`--`实现将值减少1.编写程序与,使用递减运算符在循环中按递减顺序打印出10到0之间的整数。 - -解: -```cpp -#include - -int main() { - int i = 10; - while (i >= 0) { - std::cout << i << std::endl; - i--; - } -} -``` - -## 练习1.11 -编写程序,提示用户输入两个整数,打印出这两个整数所指定的范围内的所有整数。 - -解: -```cpp -#include - -int main() { - int temp = 0; - std::cout << "Please enter two numbers"<< std::endl; - int v1 = 0, v2 = 0; - std::cin >> v1 >> v2; - // 若v1 > v2,则调换顺序 - if (v1 > v2) { - temp = v2; - v2 = v1; - v1 = temp; - } - - while (v1 <= v2){ - std::cout << v1 << std::endl; - v1 += 1; - } -} -``` - -## 练习1.12 - -下面的for循环完成了什么功能?sum的终值是多少? -```cpp -int sum = 0; -for (int i = -100; i <= 100; ++i) - sum += i; -``` - -解: - -从-100加到100,sum的终值是0。 - -## 练习1.13 -使用for循环重做1.4.1节中的所有练习(练习1.9到1.11)。 - -解: - -### 练习1.9 - -```cpp -#include -// 修改1.9 -int main() { - int sum = 0; - for (int i = 50; i <= 100; i++) { - sum += i; - } - std::cout << "sum is " << sum << std::endl; -} -``` - -### 练习1.10 - -```cpp -#include - -int main() { - for (int i = 10; i >= 0; i--) { - std::cout << i << std::endl; - } -} - - -``` - -### 练习1.11 - -```cpp -#include - -int main() { - int temp = 0; - std::cout << "Please enter two numbers"<< std::endl; - int v1 = 0, v2 = 0; - std::cin >> v1 >> v2; - // 若v1 > v2,则调换顺序 - if (v1 > v2) { - temp = v2; - v2 = v1; - v1 = temp; - } - - for (;v1 <= v2;v1++){ - std::cout << v1 << std::endl; - } -} - -``` - -## 练习1.14 -对比for循环和while循环,两种形式的优缺点各是什么? - -解: -- -- -``` -The main difference between the `for`'s and the `while`'s is a matter of pragmatics: -we usually use `for` when there is a known number of iterations, -and use `while` constructs when the number of iterations in not known in advance. -The `while` vs `do ... while` issue is also of pragmatics, -the second executes the instructions once at start, -and afterwards it behaves just like the simple `while`. -``` - - -## 练习1.15 -编写程序,包含第14页“再探编译”中讨论的常见错误。熟悉编译器生成的错误信息。 - -解: - -编译器可以检查出的错误有: -- 语法错误 -- 类型错误 -- 声明错误 - -## 练习1.16 -编写程序,从cin读取一组数,输出其和。 - -解: - -```cpp -#include - -int main() { - int sum = 0, value = 0; - while(std::cin >> value) { - sum += value; - } - std::cout << "sum is " << sum << std::endl; - return 0; -} - -``` - -## 练习1.17 -如果输入的所有值都是相等的,本节的程序会输出什么?如果没有重复值,输出又会是怎样的? - - -## 练习1.18 -编译并运行本节的程序,给它输入全都相等的值。再次运行程序,输入没有重复的值。 - -解: -![20220512125903-2022-05-12-12-59-04](https://cdn.jsdelivr.net/gh/ironartisan/picRepo/20220512125903-2022-05-12-12-59-04.png) - - -![20220512130841-2022-05-12-13-08-42](https://cdn.jsdelivr.net/gh/ironartisan/picRepo/20220512130841-2022-05-12-13-08-42.png) - -## 练习1.19 -修改你为1.4.1节练习1.11(第11页)所编写的程序(打印一个范围内的数),使其能处理用户输入的第一个数比第二个数小的情况。 - -解: - -```cpp -#include - -int main() -{ - int start = 0, end = 0; - std::cout << "Please input two num: "; - std::cin >> start >> end; - if (start <= end) { - while (start <= end){ - std::cout << start << " "; - ++start; - } - std::cout << std::endl; - } - else{ - std::cout << "start should be smaller than end !!!"; - } -} -``` - -## 练习1.20 -在网站http://www.informit.com/title/032174113 上,第1章的代码目录包含了头文件 Sales_item.h。将它拷贝到你自己的工作目录中。用它编写一个程序,读取一组书籍销售记录,将每条记录打印到标准输出上。 - -解: - -```cpp -#include -#include "Sales_item.h" - -int main() -{ - for (Sales_item item; std::cin >> item; std::cout << item << std::endl); - return 0; -} -``` - -命令: -``` -./main < data/add_item -``` - -输出: -``` -0-201-78345-X 3 60 20 -0-201-78345-X 2 50 25 -``` - -## 练习1.21 -编写程序,读取两个 ISBN 相同的 Sales_item 对象,输出他们的和。 - -解: - -```cpp -#include -#include "Sales_item.h" - -int main() -{ - Sales_item item_1; - Sales_item item_2; - std::cin >> item_1; - std::cout << item_1 << std::endl; - std::cin >> item_2; - std::cout << item_2 << std::endl; - std::cout << "sum of sale items: " << item_1 + item_2 << std::endl; - return 0; -} -``` - -命令: -``` -./main < data/add_item -``` - -输出: -``` -0-201-78345-X 3 60 20 -0-201-78345-X 2 50 25 -sum of sale items: 0-201-78345-X 5 110 22 -``` - -## 练习1.22 -编写程序,读取多个具有相同 ISBN 的销售记录,输出所有记录的和。 - -解: - -```cpp -#include -#include "Sales_item.h" - -int main() { - Sales_item sum_item; - - for (Sales_item item; std::cin >> item; std::cout << item << std::endl) { - sum_item += item; - } - - std::cout << "sum is " << sum_item << std::endl; - return 0; -} -``` - - -## 练习1.23 -编写程序,读取多条销售记录,并统计每个 ISBN(每本书)有几条销售记录。 - -## 练习1.24 -输入表示多个 ISBN 的多条销售记录来测试上一个程序,每个 ISBN 的记录应该聚在一起。 - -解: - -```cpp -#include -#include "Sales_item.h" - -int main() -{ - Sales_item total; - if (std::cin >> total){ - Sales_item trans; - while (std::cin >> trans){ - if (total.isbn() == trans.isbn()) { - total += trans; - } - else { - std::cout << total << std::endl; - total = trans; - } - } - std::cout << total << std::endl; - } - else { - std::cerr << "No data?!" << std::endl; - return -1; - } - return 0; -} -``` - -``` -## 练习1.25 -借助网站上的`Sales_item.h`头文件,编译并运行本节给出的书店程序。 diff --git a/docs/Cpp-Primer-exercise/2.变量和基本类型.md b/docs/Cpp-Primer-exercise/2.变量和基本类型.md deleted file mode 100644 index d3d30c6..0000000 --- a/docs/Cpp-Primer-exercise/2.变量和基本类型.md +++ /dev/null @@ -1,774 +0,0 @@ ---- -sort: 2 ---- - -# 变量和基本类型 - -## 练习2.1 -类型 int、long、long long 和 short 的区别是什么?无符号类型和带符号类型的区别是什么?float 和 double的区别是什么? - -解: - -在C++中,int、long、long long和short都属于整型,区别是C++规定的尺寸的最小值(即该类型在内存中所占的比特数)不同,其中, short 和 int 至少16位,long 至少32位,long long 至少64位。 C++标准允许不同的编译器赋予这些类型更大的尺寸。某一类型栈的比特数不同,它所能表示的数据范围也不一样。 - -大多数整数都能划分为无符号类型和带符号类型,在无符号类型中所有比特都用来存储数值,但只能表示大于等于0的值,带符号类型则可以表示正数、负数或0. - -float 和 double 分别是单精度浮点数和双精度浮点数,区别主要是在内存中所占的比特数不同,以及默认规定的有效位数不同。 - -## 练习2.2 -计算按揭贷款时,对于利率、本金和付款分别应选择何种数据类型?说明你的理由。 - -解: - -在实际应用中,利率、本金和付款既有可能是整数,也有可能是普通的实数。因此应该选择一种浮点类型来表示。在三种可供选择的浮点类型float、double和long double中double和float的计算代价比较接近且表示范围更广,long double的计算代价则相对较大,一般情况下没有选择的必要。综合以上分析,选择 double 是比较恰当的。 - -## 练习2.3 -读程序写结果。 -```cpp -unsigned u = 10, u2 = 42; -std::cout << u2 - u << std::endl; -std::cout << u - u2 << std::endl; -int i = 10, i2 = 42; -std::cout << i2 - i << std::endl; -std::cout << i - i2 << std::endl; -std::cout << i - u << std::endl; -std::cout << u - i << std::endl; -``` - -解: - -输出: -``` -32 -4294967264 -32 --32 -0 -0 -``` - -0 -u和u2都是无符号整数,因此u2-u得到了正确的结果(42-10=32);u-u2也能正确计算,但是因为直接计算的结果是-32,所以在表示为无符号整数时自动加上模,在作者的编译环境中int占32位,因此加模的结果是 4294967264。 - -i和i2 都是带符号整数,因此中间两个式子的结果比较直观,42-10=32,10-42=-32。 - -在最后两个式子中,u和i分别是无符号整数和带符号整数,计算时编译器先把带符号数转换为无符号数,幸运的是,i本身是一个正数,因此转换后不会出现异常情况,两个式子的计算结果都是0。 - -不过需要提醒读者注意的是,一般情况下请不要在同一个表达式中混合使用无符号类型和带符号类型。因为计算前带符号类型会自动转换成无符号类型,当带符号类型取值为负时就会出现异常结果。 - - -## 练习2.4 -编写程序检查你的估计是否正确,如果不正确,请仔细研读本节直到弄明白问题所在。 - -## 练习2.5 -指出下述字面值的数据类型并说明每一组内几种字面值的区别: -``` -(a) 'a', L'a', "a", L"a" -(b) 10, 10u, 10L, 10uL, 012, 0xC -(c) 3.14, 3.14f, 3.14L -(d) 10, 10u, 10., 10e-2 -``` - -解: -- (a): 字符字面值,宽字符字面值,字符串字面值,宽字符串字面值。 -- (b): 十进制整型,十进制无符号整型,十进制长整型,十进制无符号长整型, 八进制整型,十六进制整型。 -- (c): double, float, long double -- (d): 十进制整型,十进制无符号整型,double, double - -## 练习2.6 -下面两组定义是否有区别,如果有,请叙述之: -```cpp -int month = 9, day = 7; -int month = 09, day = 07; -``` -解: - -第一行定义的是十进制的整型,第二行定义的是八进制的整型。但是month变量有误,八进制不能直接写9。 - -## 练习2.7 -下述字面值表示何种含义?它们各自的数据类型是什么? -```cpp -(a) "Who goes with F\145rgus?\012" -(b) 3.14e1L -(c) 1024f -(d) 3.14L -``` - -解: -- (a)是一个字符串,包含两个转义字符,其中\145表示字符e,\012 表示一个换行符,因此该字符串的输出结果是Who goes with Fergus? -- (b)是一个科学计数法表示的扩展精度浮点数,大小为3.14*10=31.4。 -- (c)试图表示一个单精度浮点数,但是该形式在某些编译器中将报错,因为后缀f直接跟在了整数1024后面;改写成1024f就可以了。 -- (d)是一个扩展精度浮点数,类型是long double,大小为3.14。 - -## 练习2.8 -请利用转义序列编写一段程序,要求先输出 2M,然后转到新一行。修改程序使其先输出 2,然后输出制表符,再输出 M,最后转到新一行。 - -解: -```cpp -#include -int main() -{ - std::cout << 2 << "\115\012"; - std::cout << 2 << "\t\115\012"; - return 0; -} -``` - -## 练习2.9 -解释下列定义的含义,对于非法的定义,请说明错在何处并将其改正。 - -- (a) std::cin >> int input_value; -- (b) int i = { 3.14 }; -- (c) double salary = wage = 9999.99; -- (d) int i = 3.14; - -解: - -(a): 应该先定义再使用。 -```cpp -int input_value = 0; -std::cin >> input_value; -``` - -(b): 用列表初始化内置类型的变量时,如果存在丢失信息的风险,则编译器将报错。 -```cpp -double i = { 3.14 }; -``` - -(c): 声明多个变量时应该用逗号将变量名隔开,不能用赋值运算符连接。 -```cpp -double salary,wage; -double salary = wage = 9999.99; -``` - -(d): 不报错,但是小数部分会被截断。 -```cpp -double i = 3.14; -``` - -## 练习2.10 -下列变量的初值分别是什么? -```cpp -std::string global_str; -int global_int; -int main() -{ - int local_int; - std::string local_str; -} -``` - -解: - -`global_str`和`global_int`是全局变量,所以初值分别为空字符串和0。 -`local_int`是局部变量并且没有初始化,它的初值是未定义的。 -`local_str` 是 `string` 类的对象,它的值由类确定,为空字符串。 - -## 练习2.11 -指出下面的语句是声明还是定义: - -- (a) extern int ix = 1024; -- (b) int iy; -- (c) extern int iz; - -解: -声明与定义的关系是:声明使得名字为程序所知,而定义负责创建与名字关联的实体。 -(a): 定义变量ix -(b): 声明并定义变量iy -(c): 声明变量iz - -## 练习2.12 -请指出下面的名字中哪些是非法的? - -- (a) int double = 3.14; -- (b) int _; -- (c) int catch-22; -- (d) int 1_or_2 = 1; -- (e) double Double = 3.14; - -解: - -(a)是非法的,因为double是C++关键字,代表一种数据类型,不能作为变量的名字。 -(c)是非法的,在标识符中只能出现字母、数字和下画线,不能出现符号-,如果改成“int catch 22;”就是合法的了。 -(d)是非法的,因为标识符必须以字母或下画线开头,不能以数字开头。(b)和(e)是合法的命名。 - -## 练习2.13 -下面程序中`j`的值是多少? - -```cpp -int i = 42; -int main() -{ - int i = 100; - int j = i; -} -``` - -解: - -`j`的值是100,局部变量`i`覆盖了全局变量`i`。 - -## 练习2.14 -下面的程序合法吗?如果合法,它将输出什么? -```cpp -int i = 100, sum = 0; -for (int i = 0; i != 10; ++i) - sum += i; -std::cout << i << " " << sum << std::endl; -``` - -解: - -合法。输出是 100 45 。 - -该程序存在嵌套的作用域,其中for循环之外是外层作用域,for循环内部是内层作用域。首先在外层作用域中定义了i和sum,但是在for循环内部i被重新定义了,因此for循环实际上是从i=0循环到了i=9,内层作用域中没有重新定义 sum,因此sum的初始值是0并在此基础上依次累加。最后一句输出语句位于外层作用域中,此时在for循环内部重新定义的i已经失效,因此实际输出的仍然是外层作用域的i,值为100;而sum经由循环累加,值变为了 45。 - -## 练习2.15 -下面的哪个定义是不合法的?为什么? - -- (a) int ival = 1.01; -- (b) int &rval1 = 1.01; -- (c) int &rval2 = ival; -- (d) int &rval3; - -解: - -(b)和(d)不合法,(b)引用必须绑定在对象上,(d)引用必须初始化。 - -## 练习2.16 -考察下面的所有赋值然后回答:哪些赋值是不合法的?为什么?哪些赋值是合法的?它们执行了哪些操作? - -```cpp -int i = 0, &r1 = i; -double d = 0, &r2 = d; -``` -- (a) r2 = 3.14159; -- (b) r2 = r1; -- (c) i = r2; -- (d) r1 = d; - -解: - -- (a): 合法。给 d 赋值为 3.14159。 -- (b): 合法。会执行自动转换(int->double)。 -- (c): 合法。会发生小数截取。 -- (d): 合法。会发生小数截取。 - -## 练习2.17 -执行下面的代码段将输出什么结果? -```cpp -int i, &ri = i; -i = 5; ri = 10; -std::cout << i << " " << ri << std::endl; -``` - -解: - -程序的输出结果是10 10。 - -引用不是对象,它只是为已经存在的对象起了另外一个名字,因此ri实际上是 i的别名。在上述程序中,首先将i赋值为5,然后把这个值更新为10。因为ri是i的引用,所以它们的输出结果是一样的。 - -## 练习2.18 -编写代码分别改变指针的值以及指针所指对象的值。 - -解: -```cpp -int main() { - int i = 1, j = 2; - int *p = &i; - std::cout << p << " " << *p << std::endl; - p = &j; //更改指针的值,令p指向另一个整数对象j - std::cout << p << " " << *p << std::endl; - *p = 10; // 显式改变指针p指向的内容 - std::cout << p << " " << *p << std::endl; - j = 20; // 通过改变变量j的值 - std::cout << p << " " << *p << std::endl; - return 0; -} -``` - -输出结果: - -```cpp -0x61fe14 1 -0x61fe10 2 -0x61fe10 10 -0x61fe10 20 -``` - -## 练习2.19 -说明指针和引用的主要区别 - -解: - -指针“指向”内存中的某个对象,而引用“绑定到”内存中的某个对象,他们都实现了对于娶她对象的间接访问,二者的区别主要有两方面: -1. 指针本身就是一个对象,允许对指针复制和拷贝,而且在指针的生命周期内它可以指向几个不同的对象;引用不是一个对象,无法令引用重新绑定到另外一个对象。 -2. 指针无须在定义时赋初值,和其他内置类型一样,在块作用域内定义的指针如果没有被初始化,也将拥有一个不确定的值;引用则必须在定义时赋初值。 - -## 练习2.20 -请叙述下面这段代码的作用。 - -```cpp -int i = 42; -int *p1 = &i; -*p1 = *p1 * *p1; -``` - -解: - -这段代码首先定义了一个整数变量 `i` 并设其初值为 `42`;接着定义了一个整型指针 `p1`,令其指向变量 `i`;最后取出 `p1` 所指的当前值,计算平方后重新赋给 `p1` 所指的变量 `i`。 - -第二行的 `*` 表示声明一个指针,第三行的 `*` 表示解引用运算,即取出指针 `p1` 所指对象的值。 - - -## 练习2.21 -请解释下述定义。在这些定义中有非法的吗?如果有,为什么? - -`int i = 0;` -- (a) double* dp = &i; -- (b) int *ip = i; -- (c) int *p = &i; - -解: - -- (a): 非法。不能将一个指向 `double` 的指针指向 `int` 。 -- (b): 非法。不能将 `int` 变量赋给指针。 -- (c): 合法。 - -## 练习2.22 -假设 p 是一个 int 型指针,请说明下述代码的含义。 - -```cpp -if (p) // ... -if (*p) // ... -``` - -解: - -指针 p 作为 if 语句的条件时,实际检验的是指针本身的值,即指针所指的地址值。如果指针指向一个真实存在的变量,则其值必不为 0,此时条件为真;如果指针没有指向任何对象或者是无效指针,则对 p 的使用将引发不可预计的结果。 - -解引用运算符 `*p` 作为 if 语句的条件时,实际检验的是指针所指的对象内容,在上面的示例中是指针p所指的int值。如果该 int 值为0,则条件为假;否则。如果该 int 值不为0,对应条件为真。 - - -## 练习2.23 -给定指针 p,你能知道它是否指向了一个合法的对象吗?如果能,叙述判断的思路;如果不能,也请说明原因。 - -解: - -在 C++ 程序中,应该尽量初始化所有指针,并且尽可能等定义了对象之后再定义指向它的指针。如果实在不清楚指针应该指向何处,就把它初始化为 nullptr 或者 0,这样程序就能检测并知道它有没有指向一个具体的对象了。其中,nullptr 是C++11新标准刚刚引入的一个特殊字面值,它可以转换成任意其他的指针类型。在此前提下,判断p是否指向合法的对象,只需把 p 作为 if 语句的条件即可,如果 p 的值是 nullptr,则条件为假;反之,条件为真。 -如果不注意初始化所有指针而贸然判断指针的值,则有可能引发不可预知的结果。一种处理的办法是把if(p)置于try结构中,当程序块顺利执行时,表示p指向了合法的对象:当程序块出错跳转到catch语句时,表示p没有指向合法的对象。 - -## 练习2.24 -在下面这段代码中为什么 p 合法而 lp 非法? - -```cpp -int i = 42; -void *p = &i; -long *lp = &i; -``` - -解: - -`void *`是一种特殊的指针类型,可用于存放任意对象的地址。 -而其他指针类型必须要与所指对象严格匹配。 - -## 练习2.25 -说明下列变量的类型和值。 -```cpp -(a) int* ip, i, &r = i; -(b) int i, *ip = 0; -(c) int* ip, ip2; -``` - -解: -- (a): ip 是一个指向 int 的指针, i 是一个 int, r 是 i 的引用。 -- (b): i 是 int , ip 是一个空指针。 -- (c): ip 是一个指向 int 的指针, ip2 是一个 int。 - -## 练习2.26 -下面哪些语句是合法的?如果不合法,请说明为什么? - -解: -```cpp -const int buf; // 不合法, const 对象必须初始化 -int cnt = 0; // 合法 -const int sz = cnt; // 合法 -++cnt; ++sz; // 不合法, const 对象不能被改变 -``` - -## 练习2.27 -下面的哪些初始化是合法的?请说明原因。 - -解: - -```cpp -int i = -1, &r = 0; // 不合法, 非常用引用 r 不能引用字面值常量 0 -int *const p2 = &i2; // 合法,常量指针 -const int i = -1, &r = 0; // 合法, r 是一个常量引用,可以绑定到字面值常量 0 -const int *const p3 = &i2; // 合法 -const int *p1 = &i2; // 合法 -const int &const r2; // 不合法, r2 是引用,引用本身不是对象,不能让引用恒定不变 -const int i2 = i, &r = i; // 合法,i2是一个常量,r是一个常量引用 -``` - -## 练习2.28 -说明下面的这些定义是什么意思,挑出其中不合法的。 - -解: -```cpp -int i, *const cp; // 不合法, const 指针必须初始化 -int *p1, *const p2; // 不合法, const 指针必须初始化 -const int ic, &r = ic; // 不合法, const int 必须初始化 -const int *const p3; // 不合法, const 指针必须初始化 -const int *p; // 合法. 一个指针,指向 const int -``` - -## 练习2.29 -假设已有上一个练习中定义的那些变量,则下面的哪些语句是合法的?请说明原因。 - -解: -```cpp -i = ic; // 合法, 常量赋值给普通变量 -p1 = p3; // 不合法, p3 是const指针不能赋值给普通指针 -p1 = ⁣ // 不合法, 普通指针不能指向常量 -p3 = ⁣ // 不合法, p3 是一个常量指针,不能被赋值 -p2 = p1; // 不合法, p2 是一个常量指针,不能被赋值 -ic = *p3; // 不合法, ic是常量,不能被赋值 -``` - -## 练习2.30 -对于下面的这些语句,请说明对象被声明成了顶层const还是底层const? - -```cpp -const int v2 = 0; int v1 = v2; -int *p1 = &v1, &r1 = v1; -const int *p2 = &v2, *const p3 = &i, &r2 = v2; -``` - -解: - -v2 和 p3 是顶层const,分别表示一个整型常量和一个整型常量指针;p2 和 r2 是底层const,分别表示他们所指的对象是常量。 - -## 练习2.31 -假设已有上一个练习中所做的那些声明,则下面的哪些语句是合法的?请说明顶层const和底层const在每个例子中有何体现。 - -解: - - -```cpp -r1 = v2; -p1 = p2; -p2 = p1; -p1 = p3; -p2 = p3; -``` - -在执行拷贝操作时,顶层const和底层const区别明显。其中,顶层 const不受影响,这是因为拷贝操作并不会改变被拷贝对象的值。底层const的限制则不容忽视,拷入和拷出的对象必须具有相同的底层const资格,或者两个对象的数据类型必须能够转换。一般来说,非常量可以转换成常量,反之则不行。 - -r1=v2;是合法的,r1是一个非常量引用,v2是一个常量(顶层const),把v2的值拷贝给r1不会对v2有任何影响。 - -pl=p2;是非法的,p1是普通指针,指向的对象可以是任意值,p2是指向常量的指针(底层const),令p1指向p2所指的内容,有可能错误地改变常量的值。 - -p2=p1;是合法的,与上一条语句相反,p2可以指向一个非常量,只不过我们不会通过p2更改它所指的值。 - -pl=p3;是非法的,p3包含底层const定义(p3所指的对象是常量),不能把 p3的值赋给普通指针。 - -p2=p3;是合法的,p2和p3包含相同的底层const,p3的顶层const则可以忽略不计。 - - -## 练习2.32 -下面的代码是否合法?如果非法,请设法将其修改正确。 -```cpp -int null = 0, *p = null; -``` -解: - -上述代码是非法的,null是一个int 变量,p是一个int指针,二者不能直 接绑定。仅从语法角度来说,可以将代码修修改为: -`int null=0,*p=&null;` - -显然,这种改法与代码的原意不一定相符。另一种改法是使用nullptr: `int null=0,*p=nullptri` - -## 练习2.33 -利用本节定义的变量,判断下列语句的运行结果。 - -解: - -```cpp -a=42; // a 是 int -b=42; // b 是一个 int,(ci的顶层const在拷贝时被忽略掉了) -c=42; // c 也是一个int -d=42; // d 是一个 int *,所以语句非法 -e=42; // e 是一个 const int *, 所以语句非法 -g=42; // g 是一个 const int 的引用,引用都是底层const,所以不能被赋值 -``` -【出题思路】 - -本题旨在考查auto说明符与复合类型、常量混合使用时的各种情形。首先,使用引用其实是使用引用的对象,所以当引用被用作初始值时,真正参与初始化的其实是引用对象的值,编译器以引用对象的类型作为auto的推断类型。其次,auto一般会忽略掉顶层const,同时保留底层const。 - -【解答】 -前3条赋值语句是合法的,原因如下: -r是i的别名,而i是一个整数,所以a的类型推断结果是一个整数;ci是一个整型常量,在类型推断时顶层const被忽略掉了,所以b是一个整数;cr是ci的别名,而ci是一个整型常量,所以c的类型推断结果是一个整数。因为a、b、c都是整数,所以为其赋值 42 是合法的。 -后3条赋值语句是非法的,原因如下: -i是一个整数,&i是i的地址,所以d的类型推断结果是一个整型指针;ci是一个整型常量,&ci是一个整型常量的地址,所以e的类型推断结果是一个指向整型常量的指针:ci是一个整型常量,所以a的类型推断结果是一个整型常量引用。因为d和e都是指针,所以不能直接用字面值常量为其赋值;g绑定到了整型常量,所以不能修改它的值。 - - -## 练习2.34 -基于上一个练习中的变量和语句编写一段程序,输出赋值前后变量的内容,你刚才的推断正确吗?如果不对,请反复研读本节的示例直到你明白错在何处为止。 - -## 练习2.35 -判断下列定义推断出的类型是什么,然后编写程序进行验证。 - -```cpp -const int i = 42; -auto j = i; const auto &k = i; auto *p = &i; -const auto j2 = i, &k2 = i; -``` - -解: - -j 是 int,k 是 const int的引用,p 是const int *,j2 是const int,k2 是 const int 的引用。 - -## 练习2.36 -关于下面的代码,请指出每一个变量的类型以及程序结束时它们各自的值。 - -```cpp -int a = 3, b = 4; -decltype(a) c = a; -decltype((b)) d = a; -++c; -++d; -``` - -解: - -在本题的程序中,初始情况下a的值是3、b的值是4。decltype(a)c=ai使用的是一个不加括号的变量,因此c的的类型就是a的类型,即该语句等同于`int c=a;`,此时c是一个新整型变量,值为 3. -`decltype((b)) d=a;`使用的是一个 -加了括号的变量,因此d的类型是引用, 即该语句等同于`int &d=a;`,此时d是变量a的别名。 -执行++c;++d;时,变量c的值自增为4,因为d是a的别名,所以d自增1 -意味着a的值变成了4。当程序结束时, a、b、c、d的值都是 4。 - -## 练习2.37 -赋值是会产生引用的一类典型表达式,引用的类型就是左值的类型。也就是说,如果 i 是 int,则表达式 i=x 的类型是 int&。根据这一特点,请指出下面的代码中每一个变量的类型和值。 - -```cpp -int a = 3, b = 4; -decltype(a) c = a; -decltype(a = b) d = a; -``` - -解: - -c 是 int 类型,值为 3。d 是 int& 类型,绑定到 a。 - -## 练习2.38 -说明由decltype 指定类型和由auto指定类型有何区别。请举一个例子,decltype指定的类型与auto指定的类型一样;再举一个例子,decltype指定的类型与auto指定的类型不一样。 - -解: - -decltype 处理顶层const和引用的方式与 auto不同,decltype会将顶层const和引用保留起来。 - -auto和decltype的区别主要有三个方面: - -第一,auto类型说明符用编译器计算变量的初始值来推断其类型,而decltype虽然也让编译器分析表达式并得到它的类型,但是不实际计算表达式的值。 - -第二,编译器推断出来的auto类型有时候和初始值的类型并不完全一样,编译器会适当地改变结果类型使其更符合初女治化规则。例如,auto一般会忽略掉顶层 const,而把底层const保留下来。与之相目反,decltype会保留变量的顶层const。 - -第三,与auto不同,decltype的结果类型与表达式形式密切相关,如果变量名加上了一对括号,则得到的类型与不加括号时会有不同。如果decltype使用的是一个不加括号的变量,则得到的结果就就是该变量的类型;如果给变量加上了一层或多层括号,则编译器将推断得到引用类型。 - -```cpp -#include - - -int main() { - int a = 1; - auto c1 = a; - decltype(a) c2 = a; - decltype((a)) c3 = a; - - const int d = 3; - auto f1 = d; - decltype(d) f2 = d; - - std::cout << typeid(c1).name() << std::endl; - std::cout << typeid(c2).name() << std::endl; - std::cout << typeid(c3).name() << std::endl; - std::cout << typeid(f1).name() << std::endl; - std::cout << typeid(f2).name() << std::endl; - - c1++; - c2++; - c3++; - f1++; -// f2++; f2是整型变量,不能执行自增操作 - - std::cout << c1 << " " << c2 << " "<< c3 << " "<< f1 << " "<< f2 << " " << std::endl; - return 0; -} - -``` - -## 练习2.39 -编译下面的程序观察其运行结果,注意,如果忘记写类定义体后面的分号会发生什么情况?记录下相关的信息,以后可能会有用。 -```cpp -struct Foo { /* 此处为空 */ } // 注意:没有分号 -int main() -{ - return 0; -}。 -``` -解: - -提示应输入分号。 - -## 练习2.40 -根据自己的理解写出 Sales_data 类,最好与书中的例子有所区别。 - -```cpp -struct Sale_data -{ - std::string bookNo; - std::string bookName; - unsigned units_sold = 0; - double revenue = 0.0; - double price = 0.0; - //... -} -``` - -## 练习2.41 -使用你自己的Sale_data类重写1.5.1节(第20页)、1.5.2节(第21页)和1.6节(第22页)的练习。眼下先把Sales_data类的定义和main函数放在一个文件里。 - -```cpp -// 1.5.1 - -#include -#include - -struct Sale_data -{ - std::string bookNo; - unsigned units_sold = 0; - double revenue = 0.0; -}; - -int main() -{ - Sale_data book; - double price; - std::cin >> book.bookNo >> book.units_sold >> price; - book.revenue = book.units_sold * price; - std::cout << book.bookNo << " " << book.units_sold << " " << book.revenue << " " << price; - - return 0; -} - -``` - -```cpp -// 1.5.2 - -#include -#include - -struct Sale_data -{ - std::string bookNo; - unsigned units_sold = 0; - double revenue = 0.0; -}; - -int main() -{ - Sale_data book1, book2; - double price1, price2; - std::cin >> book1.bookNo >> book1.units_sold >> price1; - std::cin >> book2.bookNo >> book2.units_sold >> price2; - book1.revenue = book1.units_sold * price1; - book2.revenue = book2.units_sold * price2; - - if (book1.bookNo == book2.bookNo) - { - unsigned totalCnt = book1.units_sold + book2.units_sold; - double totalRevenue = book1.revenue + book2.revenue; - std::cout << book1.bookNo << " " << totalCnt << " " << totalRevenue << " "; - if (totalCnt != 0) - std::cout << totalRevenue / totalCnt << std::endl; - else - std::cout << "(no sales)" << std::endl; - return 0; - } - else - { - std::cerr << "Data must refer to same ISBN" << std::endl; - return -1; // indicate failure - } -} - -``` - -```cpp -// 1.6 - -#include -#include - -struct Sale_data -{ - std::string bookNo; - unsigned units_sold = 0; - double revenue = 0.0; -}; - -int main() -{ - Sale_data total; - double totalPrice; - if (std::cin >> total.bookNo >> total.units_sold >> totalPrice) - { - total.revenue = total.units_sold * totalPrice; - - Sale_data trans; - double transPrice; - while (std::cin >> trans.bookNo >> trans.units_sold >> transPrice) - { - trans.revenue = trans.units_sold * transPrice; - - if (total.bookNo == trans.bookNo) - { - total.units_sold += trans.units_sold; - total.revenue += trans.revenue; - } - else - { - std::cout << total.bookNo << " " << total.units_sold << " " << total.revenue << " "; - if (total.units_sold != 0) - std::cout << total.revenue / total.units_sold << std::endl; - else - std::cout << "(no sales)" << std::endl; - - total.bookNo = trans.bookNo; - total.units_sold = trans.units_sold; - total.revenue = trans.revenue; - } - } - - std::cout << total.bookNo << " " << total.units_sold << " " << total.revenue << " "; - if (total.units_sold != 0) - std::cout << total.revenue / total.units_sold << std::endl; - else - std::cout << "(no sales)" << std::endl; - - return 0; - } - else - { - std::cerr << "No data?!" << std::endl; - return -1; // indicate failure - } -} -``` - -## 练习2.42 -根据你自己的理解重写一个Sales_data.h头文件,并以此为基础重做2.6.2节(第67页)的练习。 - - diff --git a/docs/Cpp-Primer-exercise/3.字符串、向量和数组.md b/docs/Cpp-Primer-exercise/3.字符串、向量和数组.md deleted file mode 100644 index a8ecc03..0000000 --- a/docs/Cpp-Primer-exercise/3.字符串、向量和数组.md +++ /dev/null @@ -1,1360 +0,0 @@ ---- -sort: 3 ---- - -# 字符串、向量和数组 - - -## 练习3.1 - -使用恰当的using 声明重做 1.4.1节和2.6.2节的练习。 - -解: - -1.4.1 - -```cpp -#include -using std::cout; -using std::endl; -int main() { - int i = 50, sum = 0; - while (i <= 100) { - sum += i; - i += 1; - } - cout << "sum is " << sum << endl; -} -``` - -2.6.2 类似 - -## 练习3.2 -编写一段程序从标准输入中一次读入一行,然后修改该程序使其一次读入一个词。 - -解: - -一次读入一行: - -```cpp -#include -#include - -using std::string; -using std::cin; -using std::cout; -using std::endl; -using std::getline; - -int main() -{ - string s; - while (getline(cin,s)) - { - cout << s << endl; - } - return 0; -} -``` - -一次读入一个词 - -```cpp -#include -#include - -using std::string; -using std::cin; -using std::cout; -using std::endl; -using std::getline; - -int main() -{ - string s; - while (cin >> s) - { - cout << s << endl; - } - return 0; -} -``` - - -## 练习3.3 -请说明string类的输入运算符和getline函数分别是如何处理空白字符的。 - -解: - -- 类似`is >> s`的读取:string对象会忽略开头的空白(空格符、换行符、制表符等)并从第一个真正的字符开始,直到遇见下一**空白**为止。 -- 类似`getline(is, s)`的读取:string对象会从输入流中读取字符,直到遇见**换行符**为止。 - - -## 练习3.4 -编写一段程序读取两个字符串,比较其是否相等并输出结果。如果不相等,输出比较大的那个字符串。改写上述程序,比较输入的两个字符串是否等长,如果不等长,输出长度较大的那个字符串。 - -解: - - - -```cpp -#include -#include - -using namespace std; - -int main() { - string str1, str2; - cout << "请输入两个字符串" << endl; - cin >> str1; - cin >> str2; - - if (str1 == str2) { - cout << "两个字符相等" << endl; - }else if (str1 > str2) { - cout << str1 << endl; - }else { - cout << str2 < len2) - cout << str1 << "比" << str2 << "的长度多" << len1 - len2 << endl; - else - cout << str1 << "比" << str2 << "的长度少" << len2 - len1 << endl; - - - return 0; -} - -``` - - -## 练习3.5 -编写一段程序从标准输入中读入多个字符串并将他们连接起来,输出连接成的大字符串。然后修改上述程序,用空格把输入的多个字符串分割开来。 - -解: - -未隔开的: -```cpp -#include -#include - -using namespace std; - -int main() { - char cont = 'y'; - string s, result; - cout << "请输入字符串" << endl; - while(cin >> s) { - result += s; - cout << "是否继续(y or n)" << endl; - cin >> cont; - if (cont == 'y' || cont == 'Y'){ - cout << "请输入下一个字符串" << endl; - }else { - break; - } - } - cout << "拼接后的字符为" < -#include - -using std::string; -using std::cin; -using std::cout; -using std::endl; - -int main() -{ - string result, s; - while (cin >> s) - { - result += s + " "; - } - cout << result << endl; - - return 0; -} -``` - -## 练习3.6 -编写一段程序,使用范围for语句将字符串内所有字符用X代替。 - -解: - -```cpp -#include -#include - -using namespace std; - -int main() { - string s; - cout << "请输入一个字符串" < -#include - -using namespace std; -// while -int main() { - string s; - cout << "请输入一个字符串" < -#include - -using namespace std; - -int main() { - string s; - cout << "请输入一个字符串" < -#include - -using namespace std; - -int main() { - string s; - cout << "请输入一个字符串" <> ivec; // 在C++11当中合法 -vector svec = ivec; // 不合法,类型不一样 -vector svec(10, "null"); // 合法 -``` - -## 练习3.13 -下列的vector对象各包含多少个元素?这些元素的值分别是多少? - -```cpp -vector v1; // size:0, no values. -vector v2(10); // size:10, value:0 -vector v3(10, 42); // size:10, value:42 -vector v4{ 10 }; // size:1, value:10 -vector v5{ 10, 42 }; // size:2, value:10, 42 -vector v6{ 10 }; // size:10, value:"" -vector v7{ 10, "hi" }; // size:10, value:"hi" -``` - -## 练习3.14 -编写一段程序,用cin读入一组整数并把它们存入一个vector对象。 - -解: - -```cpp -##include -#include -#include - -using namespace std; -using std::vector; - -int main() { - vector v; - int i; - char cont = 'y'; - cout << "请输入数字" << endl; - while(cin >> i) { - v.push_back(i); - cout << "是否继续(y or n)" << endl; - cin >> cont; - if (cont == 'y' || cont == 'Y'){ - cout << "请输入下一个整数" << endl; - }else { - break; - } - } - for (auto &m : v) - cout << "输入的整数为" < -#include -#include - -using namespace std; -using std::vector; - -int main() { - vector v; - string i; - char cont = 'y'; - cout << "请输入字符串" << endl; - while(cin >> i) { - v.push_back(i); - cout << "是否继续(y or n)" << endl; - cin >> cont; - if (cont == 'y' || cont == 'Y'){ - cout << "请输入下一个字符串" << endl; - }else { - break; - } - } - for (auto &m : v) - cout << m << " "; - cout << endl; - return 0; -} -``` - -## 练习3.16 -编写一段程序,把练习3.13中vector对象的容量和具体内容输出出来 - -解: - -```cpp -#include -#include -#include - -using namespace std; - -int main() -{ - vector v1; // size:0, no values. - vector v2(10); // size:10, value:0 - vector v3(10, 42); // size:10, value:42 - vector v4{ 10 }; // size:1, value:10 - vector v5{ 10, 42 }; // size:2, value:10, 42 - vector v6{ 10 }; // size:10, value:"" - vector v7{ 10, "hi" }; // size:10, value:"hi" - - cout << "v1 size :" << v1.size() << endl; - cout << "v2 size :" << v2.size() << endl; - cout << "v3 size :" << v3.size() << endl; - cout << "v4 size :" << v4.size() << endl; - cout << "v5 size :" << v5.size() << endl; - cout << "v6 size :" << v6.size() << endl; - cout << "v7 size :" << v7.size() << endl; - - cout << "v1 content: "; - for (auto i : v1) - { - cout << i << " , "; - } - cout << endl; - - cout << "v2 content: "; - for (auto i : v2) - { - cout << i << " , "; - } - cout << endl; - - cout << "v3 content: "; - for (auto i : v3) - { - cout << i << " , "; - } - cout << endl; - - cout << "v4 content: "; - for (auto i : v4) - { - cout << i << " , "; - } - cout << endl; - - cout << "v5 content: "; - for (auto i : v5) - { - cout << i << " , "; - } - cout << endl; - - cout << "v6 content: "; - for (auto i : v6) - { - cout << i << " , "; - } - cout << endl; - - cout << "v7 content: "; - for (auto i : v7) - { - cout << i << " , "; - } - cout << endl; - return 0; -} -``` - -## 练习3.17 -从cin读入一组词并把它们存入一个vector对象,然后设法把所有词都改为大写形式。输出改变后的结果,每个词占一行。 - -解: - -```cpp -#include -#include -#include -#include - -using std::cin; -using std::cout; -using std::endl; -using std::vector; -using std::string; - -int main() -{ - vector v; - string s; - - while (cin >> s) - { - v.push_back(s); - } - - for (auto &str : v) - { - for (auto &c : str) - { - c = toupper(c); - } - } - - for (auto i : v) - { - cout << i << endl; - } - return 0; -} -``` - -## 练习3.18 -下面的程序合法吗?如果不合法,你准备如何修改? - -```cpp -vector ivec; -ivec[0] = 42; -``` - -解: - -不合法。应改为: -```cpp -ivec.push_back(42); -``` - -## 练习3.19 -如果想定义一个含有10个元素的vector对象,所有元素的值都是42,请例举三种不同的实现方法,哪种方式更好呢? - -如下三种: - -```cpp -vector ivec1(10, 42); - -vector ivec2{ 42, 42, 42, 42, 42, 42, 42, 42, 42, 42 }; - -vector ivec3 = { 42, 42, 42, 42, 42, 42, 42, 42, 42, 42 }; - -vector ivec4; -for (int i = 0; i < 10; ++i) - ivec4.push_back(42); -``` -vector ivec5(10); -for (auto &i : ivec5) { - i = 42; -} - -第一种方式简介直观,当对象数量多且取值重复时适合使用;思路4不限制元素的个数,比较灵活。 - -## 练习3.20 -读入一组整数并把他们存入一个vector对象,将每对相邻整数的和输出出来。改写你的程序,这次要求先输出第一个和最后一个元素的和,接着输出第二个和倒数第二个元素的和,以此类推。 - -解: - -```cpp -#include -#include -#include - -using namespace std; -using std::vector; - -int main() { - vector v; - int w; - cout << "请输入数字" << endl; - while (cin >> w) { - v.push_back(w); - } - if (v.size() == 0) { - cout << "没有任何元素" < -#include -#include - -using namespace std; - -int main() -{ - vector v1; // size:0, no values. - vector v2(10); // size:10, value:0 - vector v3(10, 42); // size:10, value:42 - vector v4{ 10 }; // size:1, value:10 - vector v5{ 10, 42 }; // size:2, value:10, 42 - vector v6{ 10 }; // size:10, value:"" - vector v7{ 10, "hi" }; // size:10, value:"hi" - - cout << "v1 size :" << v1.size() << endl; - cout << "v2 size :" << v2.size() << endl; - cout << "v3 size :" << v3.size() << endl; - cout << "v4 size :" << v4.size() << endl; - cout << "v5 size :" << v5.size() << endl; - cout << "v6 size :" << v6.size() << endl; - cout << "v7 size :" << v7.size() << endl; - - cout << "v1 content: "; - if (v1.cbegin() != v1.cend()) { - for (auto it = v1.cbegin(); it != v1.cend(); it++) { - cout << *it << " , "; - } - cout << endl; - } - cout << "v2 content: "; - if (v2.cbegin() != v2.cend()) { - for (auto it = v2.cbegin(); it != v2.cend(); it++) { - cout << *it << " , "; - } - cout << endl; - } - - cout << "v3 content: "; - if (v3.cbegin()!= v3.cend()) { - for (auto it = v3.cbegin(); it != v3.cend(); it++){ - cout << *it << " , "; - } - cout << endl; - } - - - - cout << "v4 content: "; - if (v4.cbegin() != v4.cend()) { - for(auto it = v4.cbegin(); it != v4.cend(); it++) { - cout << *it << ","; - } - cout << endl; - } - - cout << "v5 content: "; - if (v5.cbegin() != v5.cend()) { - for(auto it = v5.cbegin(); it != v5.cend(); it++) { - cout << *it << ","; - } - cout << endl; - } - - - cout << "v6 content: "; - if (v6.cbegin() != v6.cend()) { - for(auto it = v6.cbegin(); it != v6.cend(); it++) { - cout << *it << ","; - } - cout << endl; - } - - - cout << "v7 content: "; - if (v7.cbegin() != v7.cend()) { - for(auto it = v7.cbegin(); it != v7.cend(); it++) { - cout << *it << ","; - } - cout << endl; - } - - return 0; -} -``` - -## 练习3.22 -修改之前那个输出text第一段的程序,首先把text的第一段全部改成大写形式,然后输出它。 - -解: -```cpp -#include -#include -#include - -using namespace std; -using std::vector; - -int main() { - vector v; - string w; - cout << "请输入一段话" << endl; - - while (getline(cin, w)) { //利用getline读取一句话,直接回车产生一个空串,表示段落结束 - v.push_back(w); - } - for(auto it = v.begin(); it != v.end() && !it -> empty();it++) { - for (auto it2 = it->begin(); it2 != it->end(); it2++){ - *it2 = toupper(*it2); - } - cout << *it << endl; - } - return 0; -} -``` - -## 练习3.23 -编写一段程序,创建一个含有10个整数的vector对象,然后使用迭代器将所有元素的值都变成原来的两倍。输出vector对象的内容,检验程序是否正确。 - -解: - -```cpp -#include -#include -#include - -using namespace std; -using std::vector; - -int main() { - - vector words; - for (int i = 0; i < 10; i++) { - words.push_back(rand() % 1000); - } - cout << "随机生成的数字是:" < -#include -#include -#include - -using std::cin; -using std::cout; -using std::endl; -using std::vector; -using std::string; - -int main() -{ -#include -#include -#include - -using namespace std; -using std::vector; - -int main() { - vector v; - int w; - cout << "请输入数字" << endl; - while (cin >> w) { - v.push_back(w); - } - if (v.cbegin() == v.cend()) { - cout << "没有任何元素" < -#include -#include - -using namespace std; -using std::vector; - -int main() { - vector v(11); - auto it = v.begin(); - int i; - cout << "请输入一组成绩"<> i) { - if (i < 101) { - ++*(it + i / 10); - } - } - cout << "各分数段的人数分布式" << endl; - for (it = v.begin(); it!= v.end();it++) { - cout << *it << " "; - } - cout << endl; - return 0; -} -``` - -## 练习3.26 -在100页的二分搜索程序中,为什么用的是 `mid = beg + (end - beg) / 2`, 而非 `mid = (beg + end) / 2 ;` ? - -解: - -因为两个迭代器相互之间支持的运算只有 `-` ,而没有 `+` ,两个迭代器之间相减的结果代表他们之间的距离; -但是迭代器和迭代器差值(整数值)之间支持 `+`。 - -## 练习3.27 -假设`txt_size`是一个无参函数,它的返回值是`int`。请回答下列哪个定义是非法的,为什么? -```cpp -unsigned buf_size = 1024; -(a) int ia[buf_size]; -(b) int ia[4 * 7 - 14]; -(c) int ia[txt_size()]; -(d) char st[11] = "fundamental"; -``` - -解: -- (a) 非法。 维度必须是一个常量表达式, `buf_size`是一个普通的无符号数,不是常量 -- (b) 合法。 -- (c) 非法。txt_size() 的值必须要到运行时才能得到,没有被定义为`constexpr`,不能作为数组的维度。 -- (d) 非法。数组的大小应该是12。 - -## 练习3.28 -下列数组中元素的值是什么? -```cpp -string sa[10]; -int ia[10]; -int main() { - string sa2[10]; - int ia2[10]; -} -``` - -解: - -对于内置类型`int`来说,数组`ia`定义在所有函数体之外,所有元素默认初始化为0,而`ia2`定义在main函数的内部,将不被初始化,如果程序试图拷贝或输出未初始化的变量,将遇到未定义的奇异值。 - -string类本身接收无参数的初始化方式,所以不论数组定义在函数内还是函数外,都被默认初始化为空串。 - -## 练习3.29 -相比于vector 来说,数组有哪些缺点,请例举一些。 - -解: - -- 数组与vector的相似之处是都能存放类型相同的对象,且这些对象本身没有名字,需要通过其所在位置访问。 -- 数组与vector的最大不同是,数组的大小固定不变,不能随意向数组中增加额外的元素,虽然在某些情境下运行时性能能较好,但是与vector相比损失了灵活性。 -- 具体来说,数组的维度在定义时已经确定,如果我们想更改数组的长度,只能创建一个更大的新数组,然后把原数组的所有有元素复制到新数组中去。我们也无法像vector那样使用 size函数直接获取数组的维度。如果是字符数组,可以调用strlen函数得到字符串的长度; 如果是其他数组,只能使用 sizeof(array)/sizeof(array[0])的方式计算数组的维度。 - -## 练习3.30 -指出下面代码中的索引错误。 - -```cpp -constexpr size_t array_size = 10; -int ia[array_size]; -for (size_t ix = 1; ix <= array_size; ++ix) - ia[ix] = ix; -``` - -解: - -当`ix`增长到 10 的时候,`ia[ix]`的下标越界。 - -## 练习3.31 -编写一段程序,定义一个含有10个int的数组,令每个元素的值就是其下标值。 - -```cpp -#include -#include -#include - -using namespace std; -using std::vector; - -int main() { - const int sz = 10; - int num[sz]; - for (int i = 0; i < sz; i++) { - num[i] = i; - } - - for (auto val : num) { - cout << val << " "; - } - cout << endl; - return 0; -} -``` - -## 练习3.32 -将上一题刚刚创建的数组拷贝给另一数组。利用vector重写程序,实现类似的功能。 - -```cpp -#include -#include -#include - -using namespace std; -using std::vector; - -int main() { - const int sz = 10; - vector num3; - vector num4; - int num1[sz], num2[sz]; - for (int i = 0; i < sz; i++) { - num1[i] = i; - } - for (int i = 0; i < sz; i++) { - num2[i] = num1[i]; - } - cout << "num2中的值为" << endl; - for (auto val : num2) { - cout << val << " "; - } - cout << endl; - - for (int i = 0; i < sz; i++) { - num3.push_back(i); - } - for (int i = 0; i < sz; i++) { - num4.push_back(num3[i]); - } - cout << "num4中的值为" << endl; - for (auto val : num4) { - cout << val << " "; - } - cout << endl; - - return 0; -} -``` - -## 练习3.33 -对于104页的程序来说,如果不初始化scores将会发生什么? - -解: - -该数组会含有未定义的数值,因为scores是定义在函数内部的整型数组,不会执行默认初始化。 - -## 练习3.34 -假定`p1` 和 `p2` 都指向同一个数组中的元素,则下面程序的功能是什么?什么情况下该程序是非法的? -```cpp -p1 += p2 - p1; -``` - -解: - -将 `p1` 移动到 `p2` 的位置。 - -如果`p1` 和 `p2`的类型不同,则编译时报错。 - -## 练习3.35 -编写一段程序,利用指针将数组中的元素置为0。 - -解: - -```cpp -#include -#include -#include - -using namespace std; -using std::vector; - -int main() { - const int sz = 10; - int nums[sz]; - for (int i = 0; i < sz; i++) { - nums[i] = i; - } - - - for (auto ptr = nums; ptr != nums + sz; ++ptr) *ptr = 0; -// int *p = begin(nums); -// while (p != end(nums)) { -// *p = 0; -// p++; -// } - - for (auto num : nums) { - cout << num << " "; - } - cout << endl; - return 0; -} -``` - -## 练习3.36 -编写一段程序,比较两个数组是否相等。再写一段程序,比较两个vector对象是否相等。 - -解: - -```cpp -#include -#include -#include - -using std::begin; using std::end; using std::cout; using std::endl; using std::vector; - -// pb point to begin of the array, pe point to end of the array. -bool compare(int* const pb1, int* const pe1, int* const pb2, int* const pe2) -{ - if ((pe1 - pb1) != (pe2 - pb2)) // have different size. - return false; - else - { - for (int* i = pb1, *j = pb2; (i != pe1) && (j != pe2); ++i, ++j) - if (*i != *j) return false; - } - - return true; -} - -int main() -{ - int arr1[3] = { 0, 1, 2 }; - int arr2[3] = { 0, 2, 4 }; - - if (compare(begin(arr1), end(arr1), begin(arr2), end(arr2))) - cout << "The two arrays are equal." << endl; - else - cout << "The two arrays are not equal." << endl; - - cout << "==========" << endl; - - vector vec1 = { 0, 1, 2 }; - vector vec2 = { 0, 1, 2 }; - - if (vec1 == vec2) - cout << "The two vectors are equal." << endl; - else - cout << "The two vectors are not equal." << endl; - - return 0; -} -``` - -## 练习3.37 -下面的程序是何含义,程序的输出结果是什么? -```cpp -const char ca[] = { 'h', 'e', 'l', 'l', 'o' }; -const char *cp = ca; -while (*cp) { - cout << *cp << endl; - ++cp; -} -``` - -解: - -会将ca 字符数组中的元素打印出来。但是因为没有空字符的存在,程序不会退出循环。 - -## 练习3.38 -在本节中我们提到,将两个指针相加不但是非法的,而且也没有什么意义。请问为什么两个指针相加没有意义? - -解: - -指针的值是它所指对象的内存地址,将两个指针相减可以表示两个指针(在同一数组中)相距的距离,将指针加上一个整数也可以表示移动这个指针到某一位置。但是两个指针相加并没有逻辑上的意义,因此两个指针不能相加。 - -## 练习3.39 -编写一段程序,比较两个 `string` 对象。再编写一段程序,比较两个C风格字符串的内容。 - -解: - -```cpp -#include -#include -#include -using std::cout; using std::endl; using std::string; - -int main() -{ - // use string. - string s1("Mooophy"), s2("Pezy"); - if (s1 == s2) - cout << "same string." << endl; - else if (s1 > s2) - cout << "Mooophy > Pezy" << endl; - else - cout << "Mooophy < Pezy" << endl; - - cout << "=========" << endl; - - // use C-Style character strings. - const char* cs1 = "Wangyue"; - const char* cs2 = "Pezy"; - auto result = strcmp(cs1, cs2); - if (result == 0) - cout << "same string." << endl; - else if (result < 0) - cout << "Wangyue < Pezy" << endl; - else - cout << "Wangyue > Pezy" << endl; - - return 0; -} -``` - -## 练习3.40 -编写一段程序,定义两个字符数组并用字符串字面值初始化它们;接着再定义一个字符数组存放前面两个数组连接后的结果。使用`strcpy`和`strcat`把前两个数组的内容拷贝到第三个数组当中。 - -解: - -```cpp -#include -#include - -const char cstr1[]="Hello"; -const char cstr2[]="world!"; - -int main() -{ - constexpr size_t new_size = strlen(cstr1) + strlen(" ") + strlen(cstr2) +1; - char cstr3[new_size]; - - strcpy(cstr3, cstr1); - strcat(cstr3, " "); - strcat(cstr3, cstr2); - - std::cout << cstr3 << std::endl; -} -``` - -## 练习3.41 -编写一段程序,用整型数组初始化一个vector对象。 - -```cpp -#include -#include -using std::vector; using std::cout; using std::endl; using std::begin; using std::end; - -int main() -{ - int arr[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; - vector v(begin(arr), end(arr)); - - for (auto i : v) cout << i << " "; - cout << endl; - - return 0; -} -``` - -## 练习3.42 -编写一段程序,将含有整数元素的 `vector` 对象拷贝给一个整型数组。 - -解: - -```cpp -#include -#include - -using namespace std; - -int main() -{ - - - const int sz = 10; - vector words; - int nums[10]; - - for (int i = 0; i < sz; i++) { - words.push_back(i); - } - - auto it = words.cbegin(); - for (auto & val : nums) { - val = *it; - cout << val << " "; - it++; - } - cout << endl; - - - return 0; -} -``` - -## 练习3.43 -编写3个不同版本的程序,令其均能输出`ia`的元素。 -版本1使用范围`for`语句管理迭代过程;版本2和版本3都使用普通`for`语句,其中版本2要求使用下标运算符,版本3要求使用指针。 -此外,在所有3个版本的程序中都要直接写出数据类型,而不能使用类型别名、`auto`关键字和`decltype`关键字。 - -解: - -```cpp -#include -#include - -using namespace std; - -int main() -{ - // 范围for语句 - int ia[2][2] = {1, 2, 3, 4}; - for (int (&row)[2] : ia){ - for(int &val : row) { - cout << val << " "; - } - cout << endl; - } - // 普通for语句,下标形式 - for (int i = 0; i != 2; i++){ - for(int j = 0; j != 2; j++) { - cout << ia[i][j] << " "; - } - cout << endl; - } - // 普通for语句,指针形式 - for (int (*p)[2] = ia; p != ia + 2; p++){ - for(int *q = *p; q != *p + 2; q++) { - cout << *q << " "; - } - cout << endl; - } - return 0; -} -``` - -## 练习3.44 -改写上一个练习中的程序,使用类型别名来代替循环控制变量的类型。 - -解: - -```cpp -#include -#include - -using namespace std; -using int_array = int[2]; - -int main() -{ - // 范围for语句 - int ia[2][2] = {1, 2, 3, 4}; - for (int_array &row : ia){ - for(int &val : row) { - cout << val << " "; - } - cout << endl; - } - // 普通for语句,下标形式 - for (int i = 0; i != 2; i++){ - for(int j = 0; j != 2; j++) { - cout << ia[i][j] << " "; - } - cout << endl; - } - // 普通for语句,指针形式 - for (int_array *p = ia; p != ia + 2; p++){ - for(int *q = *p; q != *p + 2; q++) { - cout << *q << " "; - } - cout << endl; - } - return 0; -} -``` - -## 练习3.45 -再一次改写程序,这次使用 `auto` 关键字。 - -解: - -```cpp -#include -#include - -using namespace std; -using int_array = int[2]; - -int main() -{ - // 范围for语句 - int ia[2][2] = {1, 2, 3, 4}; - for (auto &row : ia){ - for(auto &val : row) { - cout << val << " "; - } - cout << endl; - } - // 普通for语句,下标形式 - for (auto i = 0; i != 2; i++){ - for(auto j = 0; j != 2; j++) { - cout << ia[i][j] << " "; - } - cout << endl; - } - // 普通for语句,指针形式 - for (auto *p = ia; p != ia + 2; p++){ - for(auto *q = *p; q != *p + 2; q++) { - cout << *q << " "; - } - cout << endl; - } - return 0; -} -``` \ No newline at end of file diff --git a/docs/Cpp-Primer-exercise/4.表达式.md b/docs/Cpp-Primer-exercise/4.表达式.md deleted file mode 100644 index ae923c7..0000000 --- a/docs/Cpp-Primer-exercise/4.表达式.md +++ /dev/null @@ -1,637 +0,0 @@ ---- -sort: 4 ---- - - -# 表达式 - -## 练习4.1 - -表达式`5 + 10 * 20 / 2`的求值结果是多少? - -解: - -等价于`5 + ((10 * 20) / 2) = 105` - -## 练习4.2 - -根据4.12节中的表,在下述表达式的合理位置添加括号,使得添加括号后运算对象的组合顺序与添加括号前一致。 -(a) `*vec.begin()` -(b) `*vec.begin() + 1` - -解: - -```cpp -*(vec.begin()) -(*(vec.begin())) + 1 -``` - -## 练习4.3 - -C++语言没有明确规定大多数二元运算符的求值顺序,给编译器优化留下了余地。这种策略实际上是在代码生成效率和程序潜在缺陷之间进行了权衡,你认为这可以接受吗?请说出你的理由。 - -解: - -正如题目所说,C++只规定了非常少的二元运算符(逻辑与运算符、逻辑或运算符、逗号运算符)的求值顺序,其他绝大多数二元运算符的求值顺序并没有明确规定。这样做提高了代码生成的效率,但是可能引发潜在的缺陷。 - -关键是缺陷的风险有多大?我们知道,对于没有指定执行顺序的运算符来说,如果表达式指向并修改了同一个对象,将会引发错误并产生未定义的行为;而如果运算对象彼此无关,它们既不会改变同一对象的状态也不执行IO任务,则函数的调用顺序不受限制。 - -就作者的观点而言,这样的做法在一定程度上是可以接受的,前提是在编写程序时注意以下两点:一是拿不准的时候最好用括号来强制让表达式的组合关系符合程序逻辑的要求;二是一旦改变了某个运算对象的值,在表达式的其地方就不要再使用这个运算对象了。 - -## 练习4.4 -在下面的表达式中添加括号,说明其求值过程及最终结果。编写程序编译该(不加括号的)表达式并输出结果验证之前的推断。 - -`12 / 3 * 4 + 5 * 15 + 24 % 4 / 2` - -解: - -`((12 / 3) * 4) + (5 * 15) + ((24 % 4) / 2) = 16 + 75 + 0 = 91` - -## 练习4.5 - -写出下列表达式的求值结果。 -```cpp --30 * 3 + 21 / 5 // -90+4 = -86 --30 + 3 * 21 / 5 // -30+63/5 = -30+12 = -18 -30 / 3 * 21 % 5 // 10*21%5 = 210%5 = 0 --30 / 3 * 21 % 4 // -10*21%4 = -210%4 = -2 -``` - -## 练习4.6 - -写出一条表达式用于确定一个整数是奇数还是偶数。 - -解: - -```cpp -if (i % 2 == 0) /* ... */ -``` -或者 -```cpp -if (i & 0x1) /* ... */ -``` - -## 练习4.7 - -溢出是何含义?写出三条将导致溢出的表达式。 - -解: - -当计算的结果超出该类型所能表示的范围时就会产生溢出。 - -```cpp -short svalue = 32767; ++svalue; // -32768 -unsigned uivalue = 0; --uivalue; // 4294967295 -unsigned short usvalue = 65535; ++usvalue; // 0 -``` - -## 练习4.8 - -说明在逻辑与、逻辑或及相等性运算符中运算对象的求值顺序。 - -解: - -对于逻辑与运算符来说,当且仅当两两个运算对象都为真时结果为真;对于逻辑或运算符来说,只要两个运算对象中的1个为真结果就为真。 - -逻辑与运算符和逻辑或运算符都是先先求左侧运算对象的值再求右侧运算对象的值,当且仅当左侧运算对象无法确定表过达式的结果时才会计算右侧运算对象的值。这种策略就是短路求值。其策略是:对于干逻辑与运算符来说,当且仅当左侧运算对象为真时才计算右侧运算对象;对于逻转辑或运算符来说,当且仅当左侧运算对象为假时才计算右侧运算对象。 - -## 练习4.9 - -解释在下面的`if`语句中条件部分的判断过程。 -```cpp -const char *cp = "Hello World"; -if (cp && *cp) -``` - -解: - -cp是指向字符串的指针,因此上式的条件部分含义是首先检查指针cp是否有效。如果cp为空指针或无效指针,则条件不满足。如果cp有效,即cp指向了内存中的某个有效地址,继续解引用指针cp并检查cp所指的对象是否为空字符'\0',如果cp所指的对象不是空字符则条件满足; 否则不满足。 - -在本例中,显然初始状态下cp指向了字符串的首字符,是有效的;同时当前cp所指的对象是字符'H,不是空字符,所以 if的条件部分为真。 - -## 练习4.10 - -为`while`循环写一个条件,使其从标准输入中读取整数,遇到`42`时停止。 - -解: - -```cpp -int i; -while(cin >> i && i != 42) -``` - -## 练习4.11 - -书写一条表达式用于测试4个值a、b、c、d的关系,确保a大于b、b大于c、c大于d。 - -解: - -```cpp -a>b && b>c && c>d -``` - -## 练习4.12 - -假设`i`、`j`和`k`是三个整数,说明表达式`i != j < k`的含义。 - -解: - -这个表达式等于`i != (j < k)`。首先得到`j < k`的结果为`true`或`false`,转换为整数值是`1`或`0`,然后判断`i`不等于`1`或`0` ,最终的结果为`bool`值。 - -## 练习4.13 - -在下述语句中,当赋值完成后 i 和 d 的值分别是多少? - -```cpp -int i; double d; -d = i = 3.5; // i = 3, d = 3.0 -i = d = 3.5; // d = 3.5, i = 3 -``` - -## 练习4.14 - -执行下述 if 语句后将发生什么情况? - -```cpp -if (42 = i) // 编译错误。赋值运算符左侧必须是一个可修改的左值。而字面值是右值。 -if (i = 42) // 所有非0整数转换成布尔值时都对应true,该条件恒为真。 -``` - -## 练习4.15 - -下面的赋值是非法的,为什么?应该如何修改? - -```cpp -double dval; int ival; int *pi; -dval = ival = pi = 0; -``` - -解: -该赋值语句是非法的,虽然连续赋值的的形式本身并没有错,但是参与赋值的几个变量类型不同。其中,dval是双精度浮点数,ival是整数,pi是整型指针。 - -自右向左分析赋值操作的含义,pi=0天表示pi是一个空指针,接下来ival=pi试图把整型指针的值赋给整数,这是不符合合语法规范的操作,无法编译通过。稍作调整,就可以把上述程序改为合法。 - -```cpp -dval = ival = 0; -pi = 0; -``` - -## 练习4.16 - -尽管下面的语句合法,但它们实际执行的行为可能和预期并不一样,为什么?应该如何修改? - -```cpp -if (p = getPtr() != 0) -if (i = 1024) -``` - -解: - -(a)的原意是把getPtr()得到的指针赋值给p,然后判断p是否是一个空指针,但上述表达式的实际执行结果与之相距甚这元。因为赋值运算符的优先级低于不相等运算符,所以真正的表达式求值过程是先判到断getPtr()的返回值是否为空指针,如果是则p=0,否则p=1,最后以p的值作为if语句的条件。要想符合原意,应该修改为: - -```cpp -if ((p=getPtr()) != 0) -if (i == 1024) -``` - -## 练习4.17 - -说明前置递增运算符和后置递增运算符的区别。 - -解: - -递增和递减运算符有两种形式:前置版本和后置版本。前置版本首先将运算对象加1(或减 1),然后把改变后的对象作为求值结果。后置版本也将运算对象加(或减1),但是求值结果是运算对象改变之前那个值的副本。这两种运算符必须作用于左值运算对象。前置版本将对象本身作为左值返回,后置版本则将对象原始值的副本作为右值返回。 - -我们的建议是,除非必须,否则不用递增(递减)运算符的后置版本。前置版本的递增运算符避免了不必要的工作,它把值加 1 后直接返回改变了的运算对象。与之相比,后置版本需要将原始值存储下来以便于返回这个未修改的内容。如果我们不需要修改之前的值,那么后置版本的操作就是一种浪费。 - -对于整数和指针类型来说,编译器可能对这种额外的工作进行了一定的优化; -但是对于相对复杂的迭代器类型来说,这种额外的工作就消耗巨大了。建议养成使用前置版本的习惯,这样不仅不需要担心性能问题,而且更重要的是写出的代码会更符合编程人员的初衷。 - -## 练习4.18 - -如果132页那个输出`vector`对象元素的`while`循环使用前置递增运算符,将得到什么结果? - -解: - -【出题思路】 - -前置递增运算符先将运算对象加1, 然后把改变后的对象作为求值结果;后置递增运算符也将运算对象加1,但是求值结果是运算对象改变之前那个值的副本。 - -简言之,如果在一条表达式中出现了 递增运算符,则其计算规律是:++在前, 先加1,后参与运算;++在后,先参与运 算,后加1。 - -【解答】 - -基于上述分析,本题不应该把whil e循环的后置递增运算符改为前置递增运算符。如果这样做了,会产生两个错误结果:一是无法输出vector对象的第一个元素;二是当所有元素都不为负时,移动至到最后一个元素的地方,程序试图继续向前移动迭代器并解引用一个根本不存在的元素。 - -## 练习4.19 - -假设`ptr`的类型是指向`int`的指针、`vec`的类型是`vector`、`ival`的类型是`int`,说明下面的表达式是何含义?如果有表达式不正确,为什么?应该如何修改? - -```cpp -(a) ptr != 0 && *ptr++ -(b) ival++ && ival -(c) vec[ival++] <= vec[ival] -``` - -解: - -- (a)的含义是先判定指针ptr是否为空, 如果不为空,继续判断指针ptr所指的整数是否为非0数。如果非0,则该表达式的们最终求值结果为真;否则为假。最后把指针ptr向后移动一位。该表达式从语法上分析是合法的,但是最后的指针移位操作不一定有意义。如果ptr所指的是整型数组中的某个元素,则ptr可以按照预期移动到下一个元素。如果ptr所指的只是一个独立的整数变量,则移动指针操作将产生未定义的结果。 -- (b)的含义是先检查`ival`的值是否非0,如果非0继续检查`(ival+1)`的值是否非0。只有当两个值都是非0值时,表达式的求值结果为真;否则为假。在4.1.3 节中我们学习到,如果二元运算符的两个运算对象涉及同一个对象并改变对象的值,则这是一种不好的程序写法,应该改写。所以按照程序的原意,本式应该改写成`ival&&(ival +1)`。 -- (c)的含义是比较`vec[ivall和vec[ival+1]`的大小,如果前者较小则求值结果为真,否则为假。与(b)式一样,本式也出现了二元运算符的两个运算对象涉及同一个对象并改变对象值的情况,应该改写为`vec[ivall <=vec[ival +11`。 - -## 练习4.20 - -假设`iter`的类型是`vector::iterator`, 说明下面的表达式是否合法。如果合法,表达式的含义是什么?如果不合法,错在何处? - -```cpp -(a) *iter++; -(b) (*iter)++; -(c) *iter.empty(); -(d) iter->empty(); -(e) ++*iter; -(f) iter++->empty(); -``` - -解: - -- (a)是合法的,后置递增运算符的优先级高于解引用运算符,其含义是解引用当前迭代器所处位置的对象内容,然后把迭代器的位置向后移动一位。 -- (b)是非法的,解引用iter得到vector对象当前的元素,结果是一个string,显然string没有后置递增操作。 -- (c)是非法的,解引用运算符的优先级低于点运算符,所以该式先计算 iter.empty(),而迭代器并没有定义empty函数,所以无法通过编译。 -- (d)是合法的,iter->empty(();等价于(*iter).empty();。解引用迭代器得到迭代器当前所指的元素,结果是一个string,显然字符串可以判断是否为空, empty函数在此处有效。 -- (e)是非法的,该式先解引用iter,得到迭代器当前所指的元素,结果是一个 string,显然string没有后置递增操作。 -- (f)是合法的,iter++->empty();等价于(*iter++).empty();。含义是解引用迭代器当前位置的对象内容,得到一个字符串,判断该字符串是否为空,然后把迭代器向后移动一位。 - -## 练习4.21 - -编写一段程序,使用条件运算符从`vector`中找到哪些元素的值是奇数,然后将这些奇数值翻倍。 - -解: - -```cpp -#include -#include -#include -#include - -using namespace std; - -int main() { - vector vInt; - const int sz = 10; - srand((unsigned ) time(NULL)); - - cout <<"数组的初始值为" << endl; - - for(int i = 0; i != sz; ++i) { - vInt.push_back(rand() % 100); - cout << vInt[i] << " "; - } - cout < -#include -#include -#include - -using namespace std; - -int main() { - - string finalGrade; - int grade; - - cout <<"请输入成绩" << endl; - - while (cin >> grade && grade >=0 && grade <=100) { - finalGrade = (grade >90) ? "high pass": (grade > 75)? "pass": (grade > 60) ? "low pass": "fail"; - } - cout << "等级为" << finalGrade << endl; - - // ------------------------- - if (grade > 90) cout << "high pass"; - else if (grade < 60) cout << "fail"; - else if (grade < 75) cout << "low pass"; - else cout << "pass"; - cout << endl; - - return 0; -} - -``` -第二个版本容易理解。当条件运算符嵌套层数变多之后,代码的可读性急剧下降。而`if else`的逻辑很清晰。 - -## 练习4.23 - -因为运算符的优先级问题,下面这条表达式无法通过编译。根据4.12节中的表指出它的问题在哪里?应该如何修改? - -```cpp -string s = "word"; -string pl = s + s[s.size() - 1] == 's' ? "" : "s" ; -``` - -解: - -加法运算符的优先级高于条件运算符。因此要改为: - -```cpp -string pl = s + (s[s.size() - 1] == 's' ? "" : "s") ; -``` - -## 练习4.24 - -本节的示例程序将成绩划分为`high pass`、`pass`、和`fail`三种,它的依据是条件运算符满足右结合律。假如条件运算符满足的是左结合律,求值的过程将是怎样的? - -解: - -如果条件运算符满足的是左结合律。那么 - -`finalgrade = (grade > 90) ? "high pass" : (grade < 60) ? "fail" : "pass";` -等同于 -`finalgrade = ((grade > 90) ? "high pass" : (grade < 60)) ? "fail" : "pass";` -假如此时 `grade > 90` ,第一个条件表达式的结果是 `"high pass"` ,而字符串字面值的类型是 `const char *`,非空所以为真。因此第二个条件表达式的结果是 `"fail"`。这样就出现了自相矛盾的逻辑。 - -## 练习4.25 - -如果一台机器上`int`占32位、`char`占8位,用的是`Latin-1`字符集,其中字符`'q'` 的二进制形式是`01110001`,那么表达式`~'q' << 6`的值是什么? - -解: - -首先将`char`类型提升为`int`类型,即`00000000 00000000 00000000 01110001`,然后取反,再左移6位,结果是-7296。 - -## 练习4.26 - -在本节关于测验成绩的例子中,如果使用`unsigned int` 作为`quiz1` 的类型会发生什么情况? - -解: - -在有的机器上,`unsigned int` 类型可能只有 16 位,因此结果是未定义的。 - -## 练习4.27 - -下列表达式的结果是什么? -```cpp -unsigned long ul1 = 3, ul2 = 7; -(a) ul1 & ul2 -(b) ul1 | ul2 -(c) ul1 && ul2 -(d) ul1 || ul2 -``` - -解: - -- (a) 3 -- (b) 7 -- (c) true -- (d) ture - -## 练习4.28 - -编写一段程序,输出每一种内置类型所占空间的大小。 - -解: - -```cpp -#include - -using namespace std; - -int main() -{ - cout << "bool:\t\t" << sizeof(bool) << " bytes" << endl << endl; - - cout << "char:\t\t" << sizeof(char) << " bytes" << endl; - cout << "wchar_t:\t" << sizeof(wchar_t) << " bytes" << endl; - cout << "char16_t:\t" << sizeof(char16_t) << " bytes" << endl; - cout << "char32_t:\t" << sizeof(char32_t) << " bytes" << endl << endl; - - cout << "short:\t\t" << sizeof(short) << " bytes" << endl; - cout << "int:\t\t" << sizeof(int) << " bytes" << endl; - cout << "long:\t\t" << sizeof(long) << " bytes" << endl; - cout << "long long:\t" << sizeof(long long) << " bytes" << endl << endl; - - cout << "float:\t\t" << sizeof(float) << " bytes" << endl; - cout << "double:\t\t" << sizeof(double) << " bytes" << endl; - cout << "long double:\t" << sizeof(long double) << " bytes" << endl << endl; - - return 0; -} -``` - -输出: - -``` -bool: 1 bytes - -char: 1 bytes -wchar_t: 4 bytes -char16_t: 2 bytes -char32_t: 4 bytes - -short: 2 bytes -int: 4 bytes -long: 8 bytes -long long: 8 bytes - -float: 4 bytes -double: 8 bytes -long double: 16 bytes -``` - -## 练习4.29 - -推断下面代码的输出结果并说明理由。实际运行这段程序,结果和你想象的一样吗?如不一样,为什么? - -```cpp -int x[10]; int *p = x; -cout << sizeof(x)/sizeof(*x) << endl; -cout << sizeof(p)/sizeof(*p) << endl; -``` - -解: - -sizeof(x)的运算对象x是数组的名字,求值结果是整个数组所占空间的大小,等价于对数组中所有的元素各执行一次sizeof运算并对所得结果求和。读者尤其需要注意,sizeof运算符不会把数组转换成指针来处理。在本例中,x是一个int数组且包含10个元素,所以sizeof(x)的求值结果是10个int值所占的内存空间总和。 -sizeof(*x)的运算对象*x是一条解引用表达式,此处的x既是数组的名称,也表示指向数组首元素的指针,解引用该指针得到指针所指的内容,在本例中是一个int.所以sizeof(*x)在这里等价于sizeof(int),即int所占的内存空间。 -sizeof(x)/sizeof(*x)可以理解为数组x所占的全部空间除以其中一个元素所占的空间,得到的结果应该是数组x的元素总数。实际上,因为C++的内置数组并没有定义成员函数size0),所以通常无法直接得到数组的容量。本题所示的方法是计算得到数组容量的一种常规方法。 -sizeof(p)的运算对象p是一个指针,求值结果是指针所占的空间大小。 -sizeof(*p)的运算对象*p是指针p所指的对象,即int变量x,所以求值结果是int值所占的空间大小。 -在作者的编译环境中,int占4字节,指针也占4字节,所以本题程序的输出结果是: -10 -1 - -## 练习4.30 - -根据4.12节中的表,在下述表达式的适当位置加上括号,使得加上括号之后的表达式的含义与原来的含义相同。 - -```cpp -(a) sizeof x + y -(b) sizeof p->mem[i] -(c) sizeof a < b -(d) sizeof f() -``` - -解: - -(a)的含义是先求变量x所占空间的大小,然后与变量y的值相加;因为sizeof运算符的优先级高于加法运算符的优先级,所以如果想求表达式x+y所占的内存空间,应该改为`sizeof(x+y)`。 - -(b)的含义是先定位到指针p所指的对象,然后求该对象中名为mem的数组成员第i个元素的尺寸。因为成员选择运算符的优先级高于sizeof的优先级,所以本例无须添加括号。 - -(c)的含义是先求变量a在内存中所占空间的大小,再把求得的值与变量b的值比较。因为sizeof运算符的优先级高于关系运算符的优先级,所以如果想求表达式`a(d); -``` - -## 练习4.37 - -练习4.37 -用命名的强制类型转换改写下列旧式的转换语句。 - -```cpp -int i; double d; const string *ps; char *pc; void *pv; -(a) pv = (void*)ps; -(b) i = int(*pc); -(c) pv = &d; -(d) pc = (char*)pv; -``` - -解: - -```cpp -(a) pv = static_cast(const_cast(ps)); -(b) i = static_cast(*pc); -(c) pv = static_cast(&d); -(d) pc = static_cast(pv); -``` - -## 练习4.38 - -说明下面这条表达式的含义。 - -```cpp -double slope = static_cast(j/i); -``` - -解: - -将`j/i`的结果值转换为`double`,然后赋值给`slope`。 - diff --git a/docs/Cpp-Primer-exercise/5.语句.md b/docs/Cpp-Primer-exercise/5.语句.md deleted file mode 100644 index 1444d9d..0000000 --- a/docs/Cpp-Primer-exercise/5.语句.md +++ /dev/null @@ -1,854 +0,0 @@ ---- -sort: 5 ---- - -# 语句 - -## 练习5.1 - -什么是空语句?什么时候会用到空语句? - -解: - -空语句是最简单的语句,空语句由一个单独的分号构成。如果在程序的某个地方,语法上需要一条语句但是逻辑上不需要,此时应该使用空语句,空语句什么也不做。 - -一种常见的情况是,当循环的全部工作在条件部分就可以完成时,我们通常会用到空语句。使用空语句时最好加上注释,从而令代码的阅读者知道这条语句是有意省略内容的。 - -```cpp -while (cin >> s && s != sought) - ; -``` - -## 练习5.2 - -什么是块?什么时候会用到块? - -解: - -块是指用花括号括起来的语句和声明的序列,也称为复合语句。一个块就是一个作用域,在块中引入的名字只能在块内部以及嵌套在块中的子块里访问。如果在 - -程序的某个地方,语法上需要一条语句, 但是逻辑上需要多条语句,此时应该使用 块。块不需要以分号结束。 - -例如,循环体必须是一条语句,但是是我们通常需要在循环体内做很多事情,此时就应该把多条语句用花括号括起来,从而把语句序列转变成块。 - -```cpp -{ - // ... -} -``` -如果在程序的某个地方,语法上需要一条语句,而逻辑上需要多条语句,此时应该使用块 -```cpp -while (val <= 10) { - sum += val; - ++val; -} -``` - -## 练习5.3 -使用逗号运算符重写1.4.1节的`while`循环,使它不再需要块,观察改写之后的代码可读性提高了还是降低了。 - -```cpp -while (val <= 10) - sum += val, ++val; -``` -代码的可读性反而降低了。 - -## 练习5.4 - -说明下列例子的含义,如果存在问题,试着修改它。 - -```cpp -(a) while (string::iterator iter != s.end()) { /* . . . */ } - -(b) while (bool status = find(word)) { /* . . . */ } - if (!status) { /* . . . */ } -``` - -解: - -- (a) 这个循环试图用迭代器遍历`string`,但是变量的定义应该放在循环的外面,目前每次循环都会重新定义一个变量,明显是错误的。 -- (b) 这个循环的`while`和`if`是两个独立的语句,`if`语句中无法访问`status`变量,正确的做法是应该将status定义在`while`之前。 - -## 练习5.5 -写一段自己的程序,使用`if else`语句实现把数字转换为字母成绩的要求。 - -```cpp -#include -#include -#include -using namespace std; - -int main() -{ - int g; - - cout <<"请输入成绩:"<> g; - - if (g < 0 || g > 100) { - cout << "该成绩不合法" < 7) { - level = "+"; - }else if (j < 3) { - level = "-"; - }else { - level = ""; - } - finalGrade = score + level; - - cout << "等级成绩是:" << finalGrade << endl; - return 0; -} -``` - -## 练习5.6 - -改写上一题的程序,使用条件运算符代替`if else`语句。 - -```cpp -#include -#include -#include -using namespace std; - -int main() -{ - - int g; - - cout <<"请输入成绩:"<> g; - - if (g < 0 || g > 100) { - cout << "该成绩不合法" < 7) ? "+" : (j < 3)? "-": ""; - - finalGrade = score + level; - - cout << "等级成绩是:" << finalGrade << endl; - return 0; -} -``` - -## 练习5.7 -改写下列代码段中的错误。 - -```cpp -(a) if (ival1 != ival2) - ival1 = ival2 - else - ival1 = ival2 = 0; -(b) if (ival < minval) - minval = ival; - occurs = 1; -(c) if (int ival = get_value()) - cout << "ival = " << ival << endl; - if (!ival) - cout << "ival = 0\n"; -(d) if (ival = 0) - ival = get_value(); -``` - -解: - -- (a) `ival1 = ival2` 后面少了分号。 -- (b) 应该用花括号括起来。 -- (c) ival是定义在if语句中的变量,应定义在两个if语句的外部。 -- (d) `if (ival = 0)` 应该改为 `if (ival == 0)`。 - -## 练习5.8 -什么是“悬垂else”?C++语言是如何处理else子句的? - -解: - -用来描述在嵌套的`if else`语句中,如果`if`比`else`多时如何处理的问题。C++使用的方法是`else`匹配最近没有配对的`if`。 - -## 练习5.9 -编写一段程序,使用一系列`if`语句统计从`cin`读入的文本中有多少元音字母。 - -解: - -```cpp -#include - -using namespace std; - -int main() -{ - unsigned aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0; - char ch; - cout << "请输入一段文本" << endl; - while (cin >> ch) - { - if (ch == 'a') ++aCnt; - else if (ch == 'e') ++eCnt; - else if (ch == 'i') ++iCnt; - else if (ch == 'o') ++oCnt; - else if (ch == 'u') ++uCnt; - } - cout << "Number of vowel a: \t" << aCnt << '\n' - << "Number of vowel e: \t" << eCnt << '\n' - << "Number of vowel i: \t" << iCnt << '\n' - << "Number of vowel o: \t" << oCnt << '\n' - << "Number of vowel u: \t" << uCnt << '\n' - << "Total number: \t" << aCnt + eCnt + iCnt + oCnt +uCnt<< endl; - - return 0; -} -``` - -## 练习5.10 -我们之前实现的统计元音字母的程序存在一个问题:如果元音字母以大写形式出现,不会被统计在内。编写一段程序,既统计元音字母的小写形式,也统计元音字母的大写形式,也就是说,新程序遇到'a'和'A'都应该递增`aCnt`的值,以此类推。 - -解: - -```cpp -#include - -using namespace std; - - -int main() -{ - unsigned aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0; - char ch; - cout << "请输入一段文本" << endl; - while (cin >> ch) - { - switch (ch) { - case 'a': - case 'A': - ++aCnt; - break; - case 'e': - case 'E': - ++eCnt; - break; - case 'I': - case 'i': - ++iCnt; - break; - case 'o': - case 'O': - ++oCnt; - break; - case 'u': - case 'U': - ++uCnt; - break; - } - - } - cout << "Number of vowel a: \t" << aCnt << '\n' - << "Number of vowel e: \t" << eCnt << '\n' - << "Number of vowel i: \t" << iCnt << '\n' - << "Number of vowel o: \t" << oCnt << '\n' - << "Number of vowel u: \t" << uCnt << '\n' - << "Total number: \t" << aCnt + eCnt + iCnt + oCnt +uCnt<< endl; - - return 0; -} -``` - -## 练习5.11 -修改统计元音字母的程序,使其也能统计空格、制表符、和换行符的数量。 - -解: - -```cpp -#include - -using namespace std; - - -int main() -{ - unsigned int aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0; - unsigned int spaceCnt = 0, tabCnt = 0,newlineCnt = 0; - char ch; - cout << "请输入一段文本" << endl; - while (cin >> ch) - { - switch (ch) { - case 'a': - case 'A': - ++aCnt; - break; - case 'e': - case 'E': - ++eCnt; - break; - case 'I': - case 'i': - ++iCnt; - break; - case 'o': - case 'O': - ++oCnt; - break; - case 'u': - case 'U': - ++uCnt; - break; - case ' ': - ++spaceCnt; - break; - case '\t': - ++tabCnt; - break; - case '\n': - ++newlineCnt; - break; - } - - } - cout << "Number of vowel a: \t" << aCnt << '\n' - << "Number of vowel e: \t" << eCnt << '\n' - << "Number of vowel i: \t" << iCnt << '\n' - << "Number of vowel o: \t" << oCnt << '\n' - << "Number of vowel u: \t" << uCnt << '\n' - << "Number of space: \t" << spaceCnt << '\n' - << "Number of tab: \t" << tabCnt << '\n' - << "Number of newline: \t" << newlineCnt << '\n' - << "Total number: \t" << aCnt + eCnt + iCnt + oCnt +uCnt + spaceCnt + tabCnt + newlineCnt<< endl; - - return 0; -} -``` - - -## 练习5.12 -修改统计元音字母的程序,使其能统计含以下两个字符的字符序列的数量:`ff`、`fl`和`fi`。 - -解: - -```cpp -#include - -using namespace std; - - -int main() -{ - unsigned int flCnt = 0, ffCnt = 0, fiCnt = 0; - char ch, prech= '\0'; - cout << "请输入一段文本" << endl; - while (cin >> ch) - { - bool flag = true; - if (prech == 'f') { - switch (ch) { - case 'f': - ++ffCnt; - flag = false; - break; - case 'l': - ++flCnt; - break; - case 'i': - ++flCnt; - break; - } - } - if (!flag) { - prech = '\0'; - }else { - prech = ch;} - } - cout << "Number of ff : \t" << ffCnt << '\n' - << "Number of fi \t" << fiCnt << '\n' - << "Number of fl: \t" << flCnt << '\n' - << endl; - - return 0; -} - - -``` - -## 练习5.13 -下面显示的每个程序都含有一个常见的编码错误,指出错误在哪里,然后修改它们。 -```cpp -(a) unsigned aCnt = 0, eCnt = 0, iouCnt = 0; - char ch = next_text(); - switch (ch) { - case 'a': aCnt++; - case 'e': eCnt++; - default: iouCnt++; - } -(b) unsigned index = some_value(); - switch (index) { - case 1: - int ix = get_value(); - ivec[ ix ] = index; - break; - default: - ix = ivec.size()-1; - ivec[ ix ] = index; - } -(c) unsigned evenCnt = 0, oddCnt = 0; - int digit = get_num() % 10; - switch (digit) { - case 1, 3, 5, 7, 9: - oddcnt++; - break; - case 2, 4, 6, 8, 10: - evencnt++; - break; - } -(d) unsigned ival=512, jval=1024, kval=4096; - unsigned bufsize; - unsigned swt = get_bufCnt(); - switch(swt) { - case ival: - bufsize = ival * sizeof(int); - break; - case jval: - bufsize = jval * sizeof(int); - break; - case kval: - bufsize = kval * sizeof(int); - break; - } -``` - -解: - -(a) 少了`break`语句。应该为: -```cpp -unsigned aCnt = 0, eCnt = 0, iouCnt = 0; - char ch = next_text(); - switch (ch) { - case 'a': aCnt++; break; - case 'e': eCnt++; break; - default: iouCnt++; break; - } -``` - -(b) 在`default`分支当中,`ix`未定义。应该在外部定义`ix`。 -```cpp -unsigned index = some_value(); - int ix; - switch (index) { - case 1: - ix = get_value(); - ivec[ ix ] = index; - break; - default: - ix = static_cast(ivec.size())-1; - ivec[ ix ] = index; - } -``` - -(c) `case`后面应该用冒号而不是逗号。 -```cpp -unsigned evenCnt = 0, oddCnt = 0; - int digit = get_num() % 10; - switch (digit) { - case 1: case 3: case 5: case 7: case 9: - oddcnt++; - break; - case 2: case 4: case 6: case 8: case 0: - evencnt++; - break; - } -``` - -(d) `case`标签必须是整型常量表达式。 -```cpp -const unsigned ival=512, jval=1024, kval=4096; - unsigned bufsize; - unsigned swt = get_bufCnt(); - switch(swt) { - case ival: - bufsize = ival * sizeof(int); - break; - case jval: - bufsize = jval * sizeof(int); - break; - case kval: - bufsize = kval * sizeof(int); - break; - } -``` - -## 练习5.14 -编写一段程序,从标准输入中读取若干`string`对象并查找连续重复出现的单词,所谓连续重复出现的意思是:一个单词后面紧跟着这个单词本身。要求记录连续重复出现的最大次数以及对应的单词。如果这样的单词存在,输出重复出现的最大次数;如果不存在,输出一条信息说明任何单词都没有连续出现过。 -例如:如果输入是: -``` -how now now now brown cow cow -``` -那么输出应该表明单词now连续出现了3次。 - -解: - -```cpp -##include - -using namespace std; - - -int main() -{ - string currStr, preStr = " ", maxStr; - - int currCnt = 1, maxCnt = 0; - - - while (cin >> currStr) { - // - if (currStr == preStr) { - ++currCnt; - if (currCnt > maxCnt) { - maxCnt = currCnt; - maxStr = currStr; - } - }else { - currCnt = 1; - } - preStr = currStr; - } - if (maxCnt >1) { - cout << "出现最多的字符串是 : " << maxStr << ", 次数是:" << maxCnt << endl; - }else { - cout << "每个字符串都出现了一次" << endl; - } - return 0; -} -``` - -## 练习5.15 -说明下列循环的含义并改正其中的错误。 - -```cpp -(a) for (int ix = 0; ix != sz; ++ix) { /* ... */ } - if (ix != sz) - // . . . -(b) int ix; - for (ix != sz; ++ix) { /* ... */ } -(c) for (int ix = 0; ix != sz; ++ix, ++sz) { /*...*/ } -``` - -解: - -应该改为下面这样: - -```cpp -(a) int ix; - for (ix = 0; ix != sz; ++ix) { /* ... */ } - if (ix != sz) - // . . . -(b) int ix; - for (; ix != sz; ++ix) { /* ... */ } -(c) for (int ix = 0; ix != sz; ++ix) { /*...*/ } -``` - -## 练习5.16 - -`while`循环特别适用于那种条件不变、反复执行操作的情况,例如,当未达到文件末尾时不断读取下一个值。 -`for`循环更像是在按步骤迭代,它的索引值在某个范围内一次变化。根据每种循环的习惯各自编写一段程序,然后分别用另一种循环改写。 -如果只能使用一种循环,你倾向于哪种?为什么? - -解: - -```cpp -int i; -while ( cin >> i ) - // ... - - -for (int i = 0; cin >> i;) - // ... - - -for (int i = 0; i != size; ++i) - // ... - - -int i = 0; -while (i != size) -{ - // ... - ++i; -} -``` -如果只能用一种循环,我会更倾向使用`while`,因为`while`显得简洁,代码可读性强。 - -## 练习5.17 - -假设有两个包含整数的`vector`对象,编写一段程序,检验其中一个`vector`对象是否是另一个的前缀。 -为了实现这一目标,对于两个不等长的`vector`对象,只需挑出长度较短的那个,把它的所有元素和另一个`vector`对象比较即可。 -例如,如果两个`vector`对象的元素分别是0、1、1、2 和 0、1、1、2、3、5、8,则程序的返回结果为真。 - -解: - -```cpp -#include -#include - -using std::cout; using std::vector; - -bool is_prefix(vector const& lhs, vector const& rhs) -{ - if(lhs.size() > rhs.size()) - return is_prefix(rhs, lhs); - for(unsigned i = 0; i != lhs.size(); ++i) - if(lhs[i] != rhs[i]) return false; - return true; -} - -int main() -{ - vector l{ 0, 1, 1, 2 }; - vector r{ 0, 1, 1, 2, 3, 5, 8 }; - cout << (is_prefix(r, l) ? "yes\n" : "no\n"); - - return 0; -} -``` - -## 练习5.18 -说明下列循环的含义并改正其中的错误。 - -```cpp -(a) do { // 应该添加花括号 - int v1, v2; - cout << "Please enter two numbers to sum:" ; - if (cin >> v1 >> v2) - cout << "Sum is: " << v1 + v2 << endl; - }while (cin); -(b) int ival; - do { - // . . . - } while (ival = get_response()); // 应该将ival 定义在循环外 -(c) int ival = get_response(); - do { - ival = get_response(); - } while (ival); // 应该将ival 定义在循环外 -``` - -## 练习5.19 -编写一段程序,使用`do while`循环重复地执行下述任务: -首先提示用户输入两个`string`对象,然后挑出较短的那个并输出它。 - -解: - -```cpp -#include -#include - -using std::cout; using std::cin; using std::endl; using std::string; - -int main() -{ - string rsp; - do { - cout << "Input two strings: "; - string str1, str2; - cin >> str1 >> str2; - cout << (str1 <= str2 ? str1 : str2) - << " is less than the other. " << "\n\n" - << "More? Enter yes or no: "; - cin >> rsp; - } while (!rsp.empty() && tolower(rsp[0]) == 'y'); - return 0; -} -``` - -## 练习5.20 -编写一段程序,从标准输入中读取`string`对象的序列直到连续出现两个相同的单词或者所有的单词都读完为止。 -使用`while`循环一次读取一个单词,当一个单词连续出现两次时使用`break`语句终止循环。 -输出连续重复出现的单词,或者输出一个消息说明没有任何单词是连续重复出现的。 - -解: - -```cpp -#include -#include -using std::cout; using std::cin; using std::endl; using std::string; - -int main() -{ - string read, tmp; - while (cin >> read) - if (read == tmp) break; else tmp = read; - - if (cin.eof()) cout << "no word was repeated." << endl; //eof(end of file)判断输入是否结束,或者文件结束符,等同于 CTRL+Z - else cout << read << " occurs twice in succession." << endl; - - return 0; -} -``` - -## 练习5.21 -修改5.5.1节练习题的程序,使其找到的重复单词必须以大写字母开头。 - -解: - -```cpp -#include -using std::cin; using std::cout; using std::endl; -#include -using std::string; - -int main() -{ - string curr, prev; - bool no_twice = true; - while (cin >> curr) - { - if (isupper(curr[0]) && prev == curr) - { - cout << curr << ": occurs twice in succession." << endl; - no_twice = false; - break; - } - prev = curr; - } - - if (no_twice) - cout << "no word was repeated." << endl; - - return 0; -} -``` - -## 练习5.22 -本节的最后一个例子跳回到`begin`,其实使用循环能更好的完成该任务,重写这段代码,注意不再使用`goto`语句。 - -```cpp -// 向后跳过一个带初始化的变量定义是合法的 -begin: - int sz = get_size(); - if (sz <= 0) { - goto begin; - } -``` - -解: - -用 for 循环修改的话就是这样 -```cpp -for (int sz = get_size(); sz <=0; sz = get_size()) - ; -``` - -## 练习5.23 -编写一段程序,从标准输入读取两个整数,输出第一个数除以第二个数的结果。 - -解: - -```cpp -#include -using std::cin; -using std::cout; -using std::endl; - -int main() -{ - int i, j; - cin >> i >> j; - cout << i / j << endl; - - return 0; -} -``` - -## 练习5.24 -修改你的程序,使得当第二个数是0时抛出异常。先不要设定`catch`子句,运行程序并真的为除数输入0,看看会发生什么? - -解: - -```cpp -#include -#include - -int main(void) -{ - int i, j; - std::cin >> i >> j; - if (j == 0) - throw std::runtime_error("divisor is 0"); - std::cout << i / j << std::endl; - - return 0; -} -``` - -## 练习5.25 -修改上一题的程序,使用`try`语句块去捕获异常。`catch`子句应该为用户输出一条提示信息,询问其是否输入新数并重新执行`try`语句块的内容。 - -解: - -```cpp -#include -#include -using std::cin; using std::cout; using std::endl; using std::runtime_error; - -int main(void) -{ - for (int i, j; cout << "Input two integers:\n", cin >> i >> j; ) - { - try - { - if (j == 0) - throw runtime_error("divisor is 0"); - cout << i / j << endl; - } - catch (runtime_error err) - { - cout << err.what() << "\nTry again? Enter y or n" << endl; - char c; - cin >> c; - if (!cin || c == 'n') - break; - } - } - - return 0; -} -``` \ No newline at end of file diff --git a/docs/Cpp-Primer-exercise/6.函数.md b/docs/Cpp-Primer-exercise/6.函数.md deleted file mode 100644 index 1d6a5df..0000000 --- a/docs/Cpp-Primer-exercise/6.函数.md +++ /dev/null @@ -1,1063 +0,0 @@ ---- -sort: 6 ---- - -# 函数 - -## 练习6.1 -实参和形参的区别的什么? - -解: - -形参出现在函数定义的地方,形参列表可以包含0个、1个或多个形参,多个形参之间以逗号分隔。形参规定了一个函数所接受数据的类型和数量。 -实参出现在函数调用的地方,实参的数量与形参一样多。实参的主要作用是初始化形参,并且这种初始化过程是一一对应的,即第一个实参初始化第一个形参、第二个实参初始化第二个形参,以此类推。实参参的类型必须与对应的形参类型匹配。 - -## 练习6.2 -请指出下列函数哪个有错误,为什么?应该如何修改这些错误呢? - -```cpp -(a) int f() { - string s; - // ... - return s; - } -(b) f2(int i) { /* ... */ } -(c) int calc(int v1, int v1) { /* ... */ } -(d) double square (double x) return x * x; -``` - -解: - -应该改为下面这样: - -```cpp -(a) 定义的类型和返回类型不一致。 - string f() { - string s; - // ... - return s; - } -(b) 函数缺少返回值类型。void f2(int i) { /* ... */ } -(c) int calc(int v1, int v2) { /* ... */ return ; } -(d) double square (double x) { return x * x; } -``` - -## 练习6.3 -编写你自己的`fact`函数,上机检查是否正确。注:阶乘。 - -解: - -```cpp -#include - -int fact(int i) -{ - if(i<0) - { - std::runtime_error err("Input cannot be a negative number"); - std::cout << err.what() << std::endl; - } - return i > 1 ? i * fact( i - 1 ) : 1; -} - -int main() -{ - std::cout << std::boolalpha << (120 == fact(5)) << std::endl; - return 0; -} -``` - -启用`std::boolalpha`,可以输出 `"true"`或者 `"false"`。 - -## 练习6.4 -编写一个与用户交互的函数,要求用户输入一个数字,计算生成该数字的阶乘。在main函数中调用该函数。 - -```cpp -#include -using namespace std; - -int fact(int val); - -int main() { - cout << fact(5) << endl; - return 0; -} - -int fact(int val) { - if (val == 1) { - return 1; - } - return val > 1 ? val * fact(val - 1): 1; -} -``` - -## 练习6.5 -编写一个函数输出其实参的绝对值。 - -```cpp -#include - -int abs(int i) -{ - return i > 0 ? i : -i; -} - -int main() -{ - std::cout << abs(-5) << std::endl; - return 0; -} -``` - -## 练习6.6 -说明形参、局部变量以及局部静态变量的区别。编写一个函数,同时达到这三种形式。 - -解: - -* 形参是一种自动化对象,函数开始时为形参申请内存空间,我们用调用函数时提供的实参初始化形参对应的自动对象; -* 普通变量对应的自动对象也容易理解骂我们在定义该变量的语句处创建自动对象,如果定义语句提供了初始化免责用该值初始化;否则,执行默认初始化。当改变量所在的块结束后,变量失效; -* 局部静态变量比较特殊,它的生命周期贯穿函数调用及之后的时间。局部静态变量对应的对象成为局部静态对象,他的生命周期从定义语句处开始,直到程序结束才终于。 - -```cpp -// 例子 -int count_add(int n) // n是形参 -{ - static int ctr = 0; // ctr 是局部静态变量 - ctr += n; - return ctr; -} - -int main() -{ - for (int i = 0; i != 10; ++i) // i 是局部变量 - cout << count_add(i) << endl; - - return 0; -} -``` - -## 练习6.7 -编写一个函数,当它第一次被调用时返回0,以后每次被调用返回值加1。 - -解: - -```cpp -int generate() -{ - static int ctr = 0; - return ctr++; -} -``` - -## 练习6.8 -编写一个名为Chapter6.h 的头文件,令其包含6.1节练习中的函数声明。 - -解: - -```cpp - -int fact(int val); -int func(); - -template //参考:https://blog.csdn.net/fightingforcv/article/details/51472586 - -T abs(T i) -{ - return i >= 0 ? i : -i; -} -``` - -## 练习6.9 : fact.cc | factMain.cc -编写你自己的fact.cc 和factMain.cc ,这两个文件都应该包含上一小节的练习中编写的 Chapter6.h 头文件。通过这些文件,理解你的编译器是如何支持分离式编译的。 - -解: - -fact.cc: - -```cpp -#include "Chapter6.h" -#include - -int fact(int val) -{ - if (val == 0 || val == 1) return 1; - else return val * fact(val-1); -} - -int func() -{ - int n, ret = 1; - std::cout << "input a number: "; - std::cin >> n; - while (n > 1) ret *= n--; - return ret; -} - -``` - -factMain.cc: - -```cpp -#include "Chapter6.h" -#include - -int main() -{ - std::cout << "5! is " << fact(5) << std::endl; - std::cout << func() << std::endl; - std::cout << abs(-9.78) << std::endl; -} -``` - -编译: `g++ factMain.cpp fact.cpp -o main` - -## 练习6.10 -编写一个函数,使用指针形参交换两个整数的值。 -在代码中调用该函数并输出交换后的结果,以此验证函数的正确性。 - -解: - -```cpp -#include -#include - -void swap(int* lhs, int* rhs) -{ - int tmp; - tmp = *lhs; - *lhs = *rhs; - *rhs = tmp; -} - -int main() -{ - for (int lft, rht; std::cout << "Please Enter:\n", std::cin >> lft >> rht;) - { - swap(&lft, &rht); - std::cout << lft << " " << rht << std::endl; - } - - return 0; -} -``` - -## 练习6.11 -编写并验证你自己的reset函数,使其作用于引用类型的参数。注:reset即置0。 - -解: - -```cpp -#include - -void reset(int &i) -{ - i = 0; -} - -int main() -{ - int i = 42; - reset(i); - std::cout << i << std::endl; - return 0; -} -``` - -## 练习6.12 -改写6.2.1节练习中的程序,使其引用而非指针交换两个整数的值。你觉得哪种方法更易于使用呢?为什么? - -```cpp -#include -using namespace std; - -void swap(int &p, int &q) { - int temp = p; - p = q; - q = temp; -} - -int main() { - int a = 5, b = 3; - swap(a, b); - cout << "交换后:a= " << a << ", b =" << b << endl; - return 0; -} - -``` -与使用指针相比,使用引用交换变量的内容从形式上看更简单一些,并且无须额外声明指针变量,也避免了拷贝指针的值。 - -## 练习6.13 -假设`T`是某种类型的名字,说明以下两个函数声明的区别: -一个是`void f(T)`, 另一个是`void f(&T)`。 - -解: - -`void f(T)`的参数通过值传递,在函数中`T`是实参的副本,改变`T`不会影响到原来的实参。 -`void f(&T)`的参数通过引用传递,在函数中的`T`是实参的引用,`T`的改变也就是实参的改变。 - -## 练习6.14 -举一个形参应该是引用类型的例子,再举一个形参不能是引用类型的例子。 - -解: -1. 当函数的目的是交换两个参数的内容时,应该使用引用类型的形参; -2. 当参数是string对象时,为了避免拷贝很长的字符串,应该使用引用类型。 -例如交换两个整数的函数,形参应该是引用 - -```cpp -void swap(int& lhs, int& rhs) -{ - int temp = lhs; - lhs = rhs; - rhs = temp; -} -``` - -当实参的值是右值时,形参不能为引用类型 - -```cpp -int add(int a, int b) -{ - return a + b; -} - -int main() -{ - int i = add(1,2); - return 0; -} -``` - -## 练习6.15 -说明`find_char`函数中的三个形参为什么是现在的类型,特别说明为什么`s`是常量引用而`occurs`是普通引用? -为什么`s`和`occurs`是引用类型而`c`不是? -如果令`s`是普通引用会发生什么情况? -如果令`occurs`是常量引用会发生什么情况? - -解: - -- 因为字符串可能很长,因此使用引用避免拷贝; -- 而在函数中我们不希望改变`s`的内容,所以令`s`为常量。 -- `occurs`是要传到函数外部的变量,所以使用引用,`occurs`的值会改变,所以是普通引用。 -- 因为我们只需要`c`的值,这个实参可能是右值(右值实参无法用于引用形参),所以`c`不能用引用类型。 -- 如果`s`是普通引用,也可能会意外改变原来字符串的内容。 -- `occurs`如果是常量引用,那么意味着不能改变它的值,那也就失去意义了。 - -## 练习6.16 -下面的这个函数虽然合法,但是不算特别有用。指出它的局限性并设法改善。 -```cpp -bool is_empty(string& s) { return s.empty(); } -``` - -解: - -局限性在于常量字符串和字符串字面值无法作为该函数的实参,如果下面这样调用是非法的: - -```cpp -const string str; -bool flag = is_empty(str); //非法 -bool flag = is_empty("hello"); //非法 -``` - -所以要将这个函数的形参定义为常量引用: - -```cpp -bool is_empty(const string& s) { return s.empty(); } -``` - -## 练习6.17 -编写一个函数,判断`string`对象中是否含有大写字母。 -编写另一个函数,把`string`对象全部改写成小写形式。 -在这两个函数中你使用的形参类型相同吗?为什么? - -解: - -两个函数的形参不一样。第一个函数使用常量引用,第二个函数使用普通引用。 - -## 练习6.18 -为下面的函数编写函数声明,从给定的名字中推测函数具备的功能。 - -- (a) 名为`compare`的函数,返回布尔值,两个参数都是`matrix`类的引用。 -- (b) 名为`change_val`的函数,返回`vector`的迭代器,有两个参数:一个是`int`,另一个是`vector`的迭代器。 - -解: - -```cpp -(a) bool compare(matrix &m1, matrix &m2); -(b) vector::iterator change_val(int, vector::iterator); -``` - -## 练习6.19 -假定有如下声明,判断哪个调用合法、哪个调用不合法。对于不合法的函数调用,说明原因。 - -```cpp -double calc(double); -int count(const string &, char); -int sum(vector::iterator, vector::iterator, int); -vector vec(10); -(a) calc(23.4, 55.1); -(b) count("abcda",'a'); -(c) calc(66); -(d) sum(vec.begin(), vec.end(), 3.8); -``` - -解: - -- (a) 不合法。`calc`只有一个参数。 -- (b) 合法。 -- (c) 合法。 -- (d) 合法。 - -## 练习6.20 -引用形参什么时候应该是常量引用?如果形参应该是常量引用,而我们将其设为了普通引用,会发生什么情况? - -解: - -应该尽量将引用形参设为常量引用,除非有明确的目的是为了改变这个引用变量。 -如果形参应该是常量引用,而我们将其设为了普通引用,那么常量实参将无法作用于普通引用形参。 - -## 练习6.21 -编写一个函数,令其接受两个参数:一个是`int`型的数,另一个是`int`指针。 -函数比较`int`的值和指针所指的值,返回较大的那个。 -在该函数中指针的类型应该是什么? - -解: - -```cpp -##include -#include -#include -#include - -using namespace std; - -int compare(const int *p, const int val) { // 判断是否含有大写字母 - return (val > *p) ? val : *p; -} - - - - -int main() { - - srand((unsigned) time(NULL)); - int a[10]; - - for(auto &i : a) - i = rand() % 100; - - cout << "请输入一个数" <> j; - cout<< "较大的数是" << compare(a, j) << endl; - return 0; -} - -``` - -应该是`const int *`类型。 - -## 练习6.22 -编写一个函数,令其交换两个`int`指针。 - -解: - -```cpp -#include -#include -#include -#include - -using namespace std; - -int swap(int *p, int *q) { // 判断是否含有大写字母 - int temp = *p; - *p = *q; - *q = temp; -} - -int main() { - int a = 5, b =10; - int *p = &a, *q = &b; - swap(p, q); - cout << "a=" < -#include - -int sum(std::initializer_list const& il) -{ - int sum = 0; - for (auto i : il) sum += i; - return sum; -} - -int main(void) -{ - auto il = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; - std::cout << sum(il) << std::endl; - - return 0; -} -``` - -## 练习6.28 -在`error_msg`函数的第二个版本中包含`ErrCode`类型的参数,其中循环内的`elem`是什么类型? - -解: - -`elem`是`const string &`类型。 - -## 练习6.29 -在范围`for`循环中使用`initializer_list`对象时,应该将循环控制变量声明成引用类型吗?为什么? - -解: - -应该使用常量引用类型。`initializer_list`对象中的元素都是常量,我们无法修改`initializer_list`对象中的元素的值。 - -## 练习6.30 -编译第200页的`str_subrange`函数,看看你的编译器是如何处理函数中的错误的。 - -解: - -编译器信息: -``` -g++ (Ubuntu 5.4.0-6ubuntu1~16.04.10) 5.4.0 20160609 -``` - -编译错误信息: - -``` -ch6.cpp:38:9: error: return-statement with no value, in function returning ‘bool’ [-fpermissive] -``` - -## 练习6.31 -什么情况下返回的引用无效?什么情况下返回常量的引用无效? - -解: - -当返回的引用的对象是局部变量时,返回的引用无效;当我们希望返回的对象被修改时,返回常量的引用无效。 - -## 练习6.32 -下面的函数合法吗?如果合法,说明其功能;如果不合法,修改其中的错误并解释原因。 - -```cpp -int &get(int *array, int index) { return array[index]; } -int main() -{ - int ia[10]; - for (int i = 0; i != 10; ++i) - get(ia, i) = i; -} -``` - -解: - -合法。`get`函数根据索引取得数组中的元素的引用。 - -## 练习6.33 -编写一个递归函数,输出`vector`对象的内容。 - -解: - -```cpp -#include -#include -using std::vector; using std::cout; -using Iter = vector::const_iterator; - -void print(Iter first, Iter last) -{ - if (first != last) - { - cout << *first << " "; - print(++first, last); - } -} - -int main() -{ - vector vec{ 1, 2, 3, 4, 5, 6, 7, 8, 9 }; - print(vec.cbegin(), vec.cend()); - - return 0; -} -``` - -## 练习6.34 -如果`factorial`函数的停止条件如下所示,将发生什么? - -```cpp -if (val != 0) -``` - -解: -如果`val`为正数,从结果上来说没有区别(多乘了个1); -如果`val`为负数,那么递归永远不会结束。 - -## 练习6.35 -在调用`factorial`函数时,为什么我们传入的值是`val-1`而非`val--`? - -解: - -如果传入的值是`val--`,变量的递减操作与读取变量值的操作共存于同一个表达式中,这时有可能产生未定义的值。 - -## 练习6.36 -编写一个函数声明,使其返回数组的引用并且该数组包含10个`string`对象。 -不用使用尾置返回类型、`decltype`或者类型别名。 - -解: - -```cpp -string (&fun())[10]; -``` - -## 练习6.37 -为上一题的函数再写三个声明,一个使用类型别名,另一个使用尾置返回类型,最后一个使用`decltype`关键字。 -你觉得哪种形式最好?为什么? - -解: - -```cpp -typedef string str_arr[10]; -str_arr& fun(); - -auto fun()->string(&)[10]; - -string s[10]; -decltype(s)& fun(); -``` - -我觉得尾置返回类型最好,就一行代码。 - -## 练习6.38 -修改`arrPtr`函数,使其返回数组的引用。 - -解: - -```cpp -decltype(odd)& arrPtr(int i) -{ - return (i % 2) ? odd : even; -} -``` - -## 练习6.39 -说明在下面的每组声明中第二条语句是何含义。 -如果有非法的声明,请指出来。 - -```cpp -(a) int calc(int, int); - int calc(const int, const int); -(b) int get(); - double get(); -(c) int *reset(int *); - double *reset(double *); -``` - -解: - -- (a) 非法。因为顶层const不影响传入函数的对象,所以第二个声明无法与第一个声明区分开来。 -- (b) 非法。对于重载的函数来说,它们应该只有形参的数量和形参的类型不同。返回值与重载无关。 -- (c) 合法。 - -## 练习6.40 -下面的哪个声明是错误的?为什么? - -```cpp -(a) int ff(int a, int b = 0, int c = 0); -(b) char *init(int ht = 24, int wd, char bckgrnd); -``` - -解: - -(a) 正确。 -(b) 错误。因为一旦某个形参被赋予了默认值,那么它之后的形参都必须要有默认值。 - -## 练习6.41 -下面的哪个调用是非法的?为什么?哪个调用虽然合法但显然与程序员的初衷不符?为什么? - -```cpp -char *init(int ht, int wd = 80, char bckgrnd = ' '); -(a) init(); -(b) init(24,10); -(c) init(14,'*'); -``` - -解: - -- (a) 非法。第一个参数不是默认参数,最少需要一个实参。 -- (b) 合法。 -- (c) 合法,但与初衷不符。字符`*`被解释成`int`传入到了第二个参数。而初衷是要传给第三个参数。 - -## 练习6.42 -给`make_plural`函数的第二个形参赋予默认实参's', 利用新版本的函数输出单词success和failure的单数和复数形式。 - -解: - -```cpp -#include -#include - -using std::string; -using std::cout; -using std::endl; - -string make_plural(size_t ctr, const string& word, const string& ending = "s") -{ - return (ctr > 1) ? word + ending : word; -} - -int main() -{ - cout << "single: " << make_plural(1, "success", "es") << " " - << make_plural(1, "failure") << endl; - cout << "plural : " << make_plural(2, "success", "es") << " " - << make_plural(2, "failure") << endl; - - return 0; -} -``` - -## 练习6.43 -你会把下面的哪个声明和定义放在头文件中?哪个放在源文件中?为什么? - -```cpp -(a) inline bool eq(const BigInt&, const BigInt&) {...} -(b) void putValues(int *arr, int size); -``` - -解: - -全部都放进头文件。(a) 是内联函数,(b) 是声明。 - -## 练习6.44 -将6.2.2节的`isShorter`函数改写成内联函数。 - -解: - -```cpp -inline bool is_shorter(const string &lft, const string &rht) -{ - return lft.size() < rht.size(); -} -``` - -## 练习6.45 -回顾在前面的练习中你编写的那些函数,它们应该是内联函数吗? -如果是,将它们改写成内联函数;如果不是,说明原因。 - -解: - -一般来说,内联机制用于优化规模小、流程直接、频繁调用的函数。 - -## 练习6.46 -能把`isShorter`函数定义成`constexpr`函数吗? -如果能,将它改写成`constxpre`函数;如果不能,说明原因。 - -解: - -不能。`constexpr`函数的返回值类型及所有形参都得是字面值类型。 - -## 练习6.47 -改写6.3.2节练习中使用递归输出`vector`内容的程序,使其有条件地输出与执行过程有关的信息。 -例如,每次调用时输出`vector`对象的大小。 -分别在打开和关闭调试器的情况下编译并执行这个程序。 - -解: - -```cpp -#include -#include -using std::vector; using std::cout; using std::endl; - -void printVec(vector &vec) -{ -#ifndef NDEBUG - cout << "vector size: " << vec.size() << endl; -#endif - if (!vec.empty()) - { - auto tmp = vec.back(); - vec.pop_back(); - printVec(vec); - cout << tmp << " "; - } -} - -int main() -{ - vector vec{ 1, 2, 3, 4, 5, 6, 7, 8, 9 }; - printVec(vec); - cout << endl; - - return 0; -} -``` - -## 练习6.48 -说明下面这个循环的含义,它对assert的使用合理吗? - -```cpp -string s; -while (cin >> s && s != sought) { } //空函数体 -assert(cin); -``` - -解: - -不合理。从这个程序的意图来看,应该用 - -```cpp -assert(s == sought); -``` - -## 练习6.49 -什么是候选函数?什么是可行函数? - -解: - -候选函数:与被调用函数同名,并且其声明在调用点可见。 -可行函数:形参与实参的数量相等,并且每个实参类型与对应的形参类型相同或者能转换成形参的类型。 - -## 练习6.50 -已知有第217页对函数`f`的声明,对于下面的每一个调用列出可行函数。 -其中哪个函数是最佳匹配? -如果调用不合法,是因为没有可匹配的函数还是因为调用具有二义性? - -```cpp -(a) f(2.56, 42) -(b) f(42) -(c) f(42, 0) -(d) f(2.56, 3.14) -``` - -解: - -- (a) `void f(int, int);`和`void f(double, double = 3.14);`是可行函数。 -该调用具有二义性而不合法。 -- (b) `void f(int);` 是可行函数。调用合法。 -- (c) `void f(int, int);`和`void f(double, double = 3.14);`是可行函数。 -`void f(int, int);`是最佳匹配。 -- (d) `void f(int, int);`和`void f(double, double = 3.14);`是可行函数。 -`void f(double, double = 3.14);`是最佳匹配。 - -## 练习6.51 -编写函数`f`的4版本,令其各输出一条可以区分的消息。 -验证上一个练习的答案,如果你的回答错了,反复研究本节内容直到你弄清自己错在何处。 - -解: - -```cpp -#include -using std::cout; using std::endl; - -void f() -{ - cout << "f()" << endl; -} - -void f(int) -{ - cout << "f(int)" << endl; -} - -void f(int, int) -{ - cout << "f(int, int)" << endl; -} - -void f(double, double) -{ - cout << "f(double, double)" << endl; -} - -int main() -{ - //f(2.56, 42); // error: 'f' is ambiguous. - f(42); - f(42, 0); - f(2.56, 3.14); - - return 0; -} - -``` - -## 练习6.52 -已知有如下声明: -```cpp -void manip(int ,int); -double dobj; -``` -请指出下列调用中每个类型转换的等级。 - -```cpp -(a) manip('a', 'z'); -(b) manip(55.4, dobj); -``` - -解: - -- (a) 第3级。类型提升实现的匹配。 -- (b) 第4级。算术类型转换实现的匹配。 - -## 练习6.53 -说明下列每组声明中的第二条语句会产生什么影响,并指出哪些不合法(如果有的话)。 - - -```cpp -(a) int calc(int&, int&); - int calc(const int&, const int&); -(b) int calc(char*, char*); - int calc(const char*, const char*); -(c) int calc(char*, char*); - int calc(char* const, char* const); -``` - -解: - -(c) 不合法。顶层const不影响传入函数的对象。 - -## 练习6.54 -编写函数的声明,令其接受两个`int`形参并返回类型也是`int`;然后声明一个`vector`对象,令其元素是指向该函数的指针。 - -解: - -```cpp -int func(int, int); -vector v; -``` - -## 练习6.55 -编写4个函数,分别对两个`int`值执行加、减、乘、除运算;在上一题创建的`vector`对象中保存指向这些函数的指针。 - -解: - -```cpp -int add(int a, int b) { return a + b; } -int subtract(int a, int b) { return a - b; } -int multiply(int a, int b) { return a * b; } -int divide(int a, int b) { return b != 0 ? a / b : 0; } - -v.push_back(add); -v.push_back(subtract); -v.push_back(multiply); -v.push_back(divide); -``` - -## 练习6.56 -调用上述`vector`对象中的每个元素并输出结果。 - -解: - -```cpp -std::vector vec{ add, subtract, multiply, divide }; -for (auto f : vec) - std::cout << f(2, 2) << std::endl; -``` \ No newline at end of file diff --git a/docs/Cpp-Primer-exercise/7.类.md b/docs/Cpp-Primer-exercise/7.类.md deleted file mode 100644 index 4fb2296..0000000 --- a/docs/Cpp-Primer-exercise/7.类.md +++ /dev/null @@ -1,1370 +0,0 @@ ---- -sort: 7 ---- - - -# 类 - -## 练习7.1 - -使用2.6.1节定义的`Sales_data`类为1.6节的交易处理程序编写一个新版本。 - -解: - -```cpp -#include -#include -using std::cin; using std::cout; using std::endl; using std::string; - -struct Sales_data -{ - string bookNo; - unsigned units_sold = 0; - double revenue = 0.0; -}; - -int main() -{ - Sales_data total; - if (cin >> total.bookNo >> total.units_sold >> total.revenue) - { - Sales_data trans; - while (cin >> trans.bookNo >> trans.units_sold >> trans.revenue) - { - if (total.bookNo == trans.bookNo) - { - total.units_sold += trans.units_sold; - total.revenue += trans.revenue; - } - else - { - cout << total.bookNo << " " << total.units_sold << " " << total.revenue << endl; - total = trans; - } - } - cout << total.bookNo << " " << total.units_sold << " " << total.revenue << endl; - } - else - { - std::cerr << "No data?!" << std::endl; - return -1; - } - return 0; -} -``` - -## 练习7.2 - -曾在2.6.2节的练习中编写了一个`Sales_data`类,请向这个类添加`combine`函数和`isbn`成员。 - -解: - -```cpp -#include - -struct Sales_data { - std::string isbn() const { return bookNo; }; - Sales_data& combine(const Sales_data&); - - std::string bookNo; - unsigned units_sold = 0; - double revenue = 0.0; -}; - -Sales_data& Sales_data::combine(const Sales_data& rhs) -{ - units_sold += rhs.units_sold; - revenue += rhs.revenue; - return *this; -} -``` - -## 练习7.3 - -修改7.1.1节的交易处理程序,令其使用这些成员。 - -解: - -```cpp -#include -using std::cin; using std::cout; using std::endl; - -int main() -{ - Sales_data total; - if (cin >> total.bookNo >> total.units_sold >> total.revenue) - { - Sales_data trans; - while (cin >> trans.bookNo >> trans.units_sold >> trans.revenue) { - if (total.isbn() == trans.isbn()) - total.combine(trans); - else { - cout << total.bookNo << " " << total.units_sold << " " << total.revenue << endl; - total = trans; - } - } - cout << total.bookNo << " " << total.units_sold << " " << total.revenue << endl; - } - else - { - std::cerr << "No data?!" << std::endl; - return -1; - } - return 0; -} -``` - -## 练习7.4 - -编写一个名为`Person`的类,使其表示人员的姓名和地址。使用`string`对象存放这些元素,接下来的练习将不断充实这个类的其他特征。 - -解: - -```cpp -#include - -class Person { - std::string name; - std::string address; -}; -``` - -## 练习7.5 - -在你的`Person`类中提供一些操作使其能够返回姓名和地址。 -这些函数是否应该是`const`的呢?解释原因。 - -解: - -```cpp -#include - -class Person -{ - std::string name; - std::string address; -public: - auto get_name() const -> std::string const& { return name; } - auto get_addr() const -> std::string const& { return address; } -}; -``` -应该是`const`的。因为常量的`Person`对象也需要使用这些函数操作。 - -## 练习7.6 - -对于函数`add`、`read`和`print`,定义你自己的版本。 - -解: - -```cpp -#include -#include - -struct Sales_data { - std::string const& isbn() const { return bookNo; }; - Sales_data& combine(const Sales_data&); - - std::string bookNo; - unsigned units_sold = 0; - double revenue = 0.0; -}; - -// member functions. -Sales_data& Sales_data::combine(const Sales_data& rhs) -{ - units_sold += rhs.units_sold; - revenue += rhs.revenue; - return *this; -} - -// nonmember functions -std::istream &read(std::istream &is, Sales_data &item) -{ - double price = 0; - is >> item.bookNo >> item.units_sold >> price; - item.revenue = price * item.units_sold; - return is; -} - -std::ostream &print(std::ostream &os, const Sales_data &item) -{ - os << item.isbn() << " " << item.units_sold << " " << item.revenue; - return os; -} - -Sales_data add(const Sales_data &lhs, const Sales_data &rhs) -{ - Sales_data sum = lhs; - sum.combine(rhs); - return sum; -} -``` - -## 练习7.7 - -使用这些新函数重写7.1.2节练习中的程序。 - -```cpp -int main() -{ - Sales_data total; - if (read(std::cin, total)) - { - Sales_data trans; - while (read(std::cin, trans)) { - if (total.isbn() == trans.isbn()) - total.combine(trans); - else { - print(std::cout, total) << std::endl; - total = trans; - } - } - print(std::cout, total) << std::endl; - } - else - { - std::cerr << "No data?!" << std::endl; - return -1; - } - - return 0; -} -``` - -## 练习7.8 - -为什么`read`函数将其`Sales_data`参数定义成普通的引用,而`print`函数将其参数定义成常量引用? - -解: - -因为`read`函数会改变对象的内容,而`print`函数不会。 - -## 练习7.9 - -对于7.1.2节练习中代码,添加读取和打印`Person`对象的操作。 - -解: - -```cpp -#include -#include - -struct Person -{ - std::string const& getName() const { return name; } - std::string const& getAddress() const { return address; } - - std::string name; - std::string address; -}; - -std::istream &read(std::istream &is, Person &person) -{ - return is >> person.name >> person.address; -} - -std::ostream &print(std::ostream &os, const Person &person) -{ - return os << person.name << " " << person.address; -} -``` - -## 练习7.10 - -在下面这条`if`语句中,条件部分的作用是什么? - -```cpp -if (read(read(cin, data1), data2)) //等价read(std::cin, data1);read(std::cin, data2); -``` - -解: - -`read`函数的返回值是`istream`对象, -`if`语句中条件部分的作用是从输入流中读取数据给两个`data`对象。 - -## 练习7.11 : - -在你的`Sales_data`类中添加构造函数, -然后编写一段程序令其用到每个构造函数。 - -解: - -头文件: - -```cpp -#include -#include - -struct Sales_data { - Sales_data() = default; - Sales_data(const std::string &s):bookNo(s) { } - Sales_data(const std::string &s, unsigned n, double p):bookNo(s), units_sold(n), revenue(n*p){ } - Sales_data(std::istream &is); - - std::string isbn() const { return bookNo; }; - Sales_data& combine(const Sales_data&); - - std::string bookNo; - unsigned units_sold = 0; - double revenue = 0.0; -}; - -// nonmember functions -std::istream &read(std::istream &is, Sales_data &item) -{ - double price = 0; - is >> item.bookNo >> item.units_sold >> price; - item.revenue = price * item.units_sold; - return is; -} - -std::ostream &print(std::ostream &os, const Sales_data &item) -{ - os << item.isbn() << " " << item.units_sold << " " << item.revenue; - return os; -} - -Sales_data add(const Sales_data &lhs, const Sales_data &rhs) -{ - Sales_data sum = lhs; - sum.combine(rhs); - return sum; -} - -// member functions. -Sales_data::Sales_data(std::istream &is) -{ - read(is, *this); -} - -Sales_data& Sales_data::combine(const Sales_data& rhs) -{ - units_sold += rhs.units_sold; - revenue += rhs.revenue; - return *this; -} -``` - -主函数: - -```cpp -int main() -{ - Sales_data item1; - print(std::cout, item1) << std::endl; - - Sales_data item2("0-201-78345-X"); - print(std::cout, item2) << std::endl; - - Sales_data item3("0-201-78345-X", 3, 20.00); - print(std::cout, item3) << std::endl; - - Sales_data item4(std::cin); - print(std::cout, item4) << std::endl; - - return 0; -} -``` - -## 练习7.12 - -把只接受一个`istream`作为参数的构造函数移到类的内部。 - -解: - -```cpp -#include -#include - -struct Sales_data; -std::istream &read(std::istream&, Sales_data&); - -struct Sales_data { - Sales_data() = default; - Sales_data(const std::string &s):bookNo(s) { } - Sales_data(const std::string &s, unsigned n, double p):bookNo(s), units_sold(n), revenue(n*p){ } - Sales_data(std::istream &is) { read(is, *this); } - - std::string isbn() const { return bookNo; }; - Sales_data& combine(const Sales_data&); - - std::string bookNo; - unsigned units_sold = 0; - double revenue = 0.0; -}; - -// member functions. -Sales_data& Sales_data::combine(const Sales_data& rhs) -{ - units_sold += rhs.units_sold; - revenue += rhs.revenue; - return *this; -} - -// nonmember functions -std::istream &read(std::istream &is, Sales_data &item) -{ - double price = 0; - is >> item.bookNo >> item.units_sold >> price; - item.revenue = price * item.units_sold; - return is; -} - -std::ostream &print(std::ostream &os, const Sales_data &item) -{ - os << item.isbn() << " " << item.units_sold << " " << item.revenue; - return os; -} - -Sales_data add(const Sales_data &lhs, const Sales_data &rhs) -{ - Sales_data sum = lhs; - sum.combine(rhs); - return sum; -} -``` - -## 练习7.13 -使用`istream`构造函数重写第229页的程序。 - -解: - -```cpp -int main() -{ - Sales_data total(std::cin); - if (!total.isbn().empty()) - { - std::istream &is = std::cin; - while (is) { - Sales_data trans(is); - if (!is) break; - if (total.isbn() == trans.isbn()) - total.combine(trans); - else { - print(std::cout, total) << std::endl; - total = trans; - } - } - print(std::cout, total) << std::endl; - } - else - { - std::cerr << "No data?!" << std::endl; - return -1; - } - - return 0; -} -``` - -## 练习7.14 -编写一个构造函数,令其用我们提供的类内初始值显式地初始化成员。 - -```cpp -Sales_data() : units_sold(0) , revenue(0) { } -``` - -## 练习7.15 -为你的`Person`类添加正确的构造函数。 - -解: - -```cpp -#include -#include - -struct Person; -std::istream &read(std::istream&, Person&); - -struct Person -{ - Person() = default; - Person(const std::string& sname, const std::string& saddr) :name(sname), address(saddr) {} - Person(std::istream &is) { read(is, *this); } - - std::string getName() const { return name; } - std::string getAddress() const { return address; } - - std::string name; - std::string address; -}; - -std::istream &read(std::istream &is, Person &person) -{ - is >> person.name >> person.address; - return is; -} - -std::ostream &print(std::ostream &os, const Person &person) -{ - os << person.name << " " << person.address; - return os; -} -``` - -## 练习7.16 -在类的定义中对于访问说明符出现的位置和次数有限定吗? -如果有,是什么?什么样的成员应该定义在`public`说明符之后? -什么样的成员应该定义在`private`说明符之后? - -解: - -在类的定义中对于访问说明符出现的位置和次数**没有限定**。 - -每个访问说明符指定了接下来的成员的访问级别,其有效范围直到出现下一个访问说明符或者达到类的结尾处为止。 - -如果某个成员能够在整个程序内都被访问,那么它应该定义为`public`; -如果某个成员只能在类内部访问,那么它应该定义为`private`。 - -## 练习7.17 -使用`class`和`struct`时有区别吗?如果有,是什么? - -解: - -`class`和`struct`的唯一区别是默认的访问级别不同。 - -## 练习7.18 -封装是何含义?它有什么用处? - -解: - -将类内部分成员设置为外部不可见,而提供部分接口给外面,这样的行为叫做封装。 - -用处: - -- 1.确保用户的代码不会无意间破坏封装对象的状态。 -- 2.被封装的类的具体实现细节可以随时改变,而无需调整用户级别的代码。 - -## 练习7.19 -在你的`Person`类中,你将把哪些成员声明成`public`的? -哪些声明成`private`的? -解释你这样做的原因。 - -构造函数、`getName()`、`getAddress()`函数将设为`public`。 -`name`和 `address` 将设为`private`。 -函数是暴露给外部的接口,因此要设为`public`; -而数据则应该隐藏让外部不可见。 - -## 练习7.20 -友元在什么时候有用?请分别举出使用友元的利弊。 - -解: - -当其他类或者函数想要访问当前类的私有变量时,这个时候应该用友元。 - -利: - -与当前类有关的接口函数能直接访问类的私有变量。 - -弊: - -牺牲了封装性与可维护性。 - -## 练习7.21 -修改你的`Sales_data`类使其隐藏实现的细节。 -你之前编写的关于`Sales_data`操作的程序应该继续使用,借助类的新定义重新编译该程序,确保其正常工作。 - -解: - -```cpp -#include -#include - -class Sales_data { - friend std::istream &read(std::istream &is, Sales_data &item); - friend std::ostream &print(std::ostream &os, const Sales_data &item); - friend Sales_data add(const Sales_data &lhs, const Sales_data &rhs); - -public: - Sales_data() = default; - Sales_data(const std::string &s):bookNo(s) { } - Sales_data(const std::string &s, unsigned n, double p):bookNo(s), units_sold(n), revenue(n*p){ } - Sales_data(std::istream &is) { read(is, *this); } - - std::string isbn() const { return bookNo; }; - Sales_data& combine(const Sales_data&); - -private: - std::string bookNo; - unsigned units_sold = 0; - double revenue = 0.0; -}; - -// member functions. -Sales_data& Sales_data::combine(const Sales_data& rhs) -{ - units_sold += rhs.units_sold; - revenue += rhs.revenue; - return *this; -} - -// friend functions -std::istream &read(std::istream &is, Sales_data &item) -{ - double price = 0; - is >> item.bookNo >> item.units_sold >> price; - item.revenue = price * item.units_sold; - return is; -} - -std::ostream &print(std::ostream &os, const Sales_data &item) -{ - os << item.isbn() << " " << item.units_sold << " " << item.revenue; - return os; -} - -Sales_data add(const Sales_data &lhs, const Sales_data &rhs) -{ - Sales_data sum = lhs; - sum.combine(rhs); - return sum; -} -``` - -## 练习7.22 -修改你的`Person`类使其隐藏实现的细节。 - -解: - -```cpp -#include -#include - -class Person { - friend std::istream &read(std::istream &is, Person &person); - friend std::ostream &print(std::ostream &os, const Person &person); - -public: - Person() = default; - Person(const std::string sname, const std::string saddr):name(sname), address(saddr){ } - Person(std::istream &is){ read(is, *this); } - - std::string getName() const { return name; } - std::string getAddress() const { return address; } -private: - std::string name; - std::string address; -}; - -std::istream &read(std::istream &is, Person &person) -{ - is >> person.name >> person.address; - return is; -} - -std::ostream &print(std::ostream &os, const Person &person) -{ - os << person.name << " " << person.address; - return os; -} -``` - -## 练习7.23 -编写你自己的`Screen`类型。 - -解: - -```cpp -#include - -class Screen { - public: - using pos = std::string::size_type; - - Screen() = default; - Screen(pos ht, pos wd, char c):height(ht), width(wd), contents(ht*wd, c){ } - - char get() const { return contents[cursor]; } - char get(pos r, pos c) const { return contents[r*width+c]; } - - private: - pos cursor = 0; - pos height = 0, width = 0; - std::string contents; -}; -``` - -# 练习7.24 -给你的`Screen`类添加三个构造函数:一个默认构造函数;另一个构造函数接受宽和高的值,然后将`contents`初始化成给定数量的空白;第三个构造函数接受宽和高的值以及一个字符,该字符作为初始化后屏幕的内容。 - -解: - -```cpp -#include - -class Screen { - public: - using pos = std::string::size_type; - - Screen() = default; // 1 - Screen(pos ht, pos wd):height(ht), width(wd), contents(ht*wd, ' '){ } // 2 - Screen(pos ht, pos wd, char c):height(ht), width(wd), contents(ht*wd, c){ } // 3 - - char get() const { return contents[cursor]; } - char get(pos r, pos c) const { return contents[r*width+c]; } - - private: - pos cursor = 0; - pos height = 0, width = 0; - std::string contents; -}; -``` - -## 练习7.25 -`Screen`能安全地依赖于拷贝和赋值操作的默认版本吗? -如果能,为什么?如果不能?为什么? - -解: - -能。 `Screen`的成员只有内置类型和`string`,因此能安全地依赖于拷贝和赋值操作的默认版本。 - -管理动态内存的类则不能依赖于拷贝和赋值操作的默认版本,而且也应该尽量使用`string`和`vector`来避免动态管理内存的复杂性。 - -## 练习7.26 -将`Sales_data::avg_price`定义成内联函数。 - -解: - -在头文件中加入: - -```cpp -inline double Sales_data::avg_price() const -{ - return units_sold ? revenue/units_sold : 0; -} -``` - -## 练习7.27 -给你自己的`Screen`类添加`move`、`set` 和`display`函数,通过执行下面的代码检验你的类是否正确。 - -```cpp -Screen myScreen(5, 5, 'X'); -myScreen.move(4, 0).set('#').display(cout); -cout << "\n"; -myScreen.display(cout); -cout << "\n"; -``` - -解: - -增加代码: - -```cpp -#include -#include - -class Screen { -public: - ... ... - - inline Screen& move(pos r, pos c); - inline Screen& set(char c); - inline Screen& set(pos r, pos c, char ch); - - const Screen& display(std::ostream &os) const { do_display(os); return *this; } - Screen& display(std::ostream &os) { do_display(os); return *this; } - -private: - void do_display(std::ostream &os) const { os << contents; } - - ... ... -}; - -inline Screen& Screen::move(pos r, pos c) -{ - cursor = r*width + c; - return *this; -} - -inline Screen& Screen::set(char c) -{ - contents[cursor] = c; - return *this; -} - -inline Screen& Screen::set(pos r, pos c, char ch) -{ - contents[r*width+c] = ch; - return *this; -} -``` - -测试代码: - -```cpp -int main() -{ - Screen myScreen(5, 5, 'X'); - myScreen.move(4, 0).set('#').display(std::cout); - std::cout << "\n"; - myScreen.display(std::cout); - std::cout << "\n"; - - return 0; -} -``` - -## 练习7.28 -如果`move`、`set`和`display`函数的返回类型不是`Screen&` 而是`Screen`,则在上一个练习中将会发生什么? - -解: - -如果返回类型是`Screen`,那么`move`返回的是`*this`的一个副本,因此`set`函数只能改变临时副本而不能改变`myScreen`的值。 - -## 练习7.29 -修改你的`Screen`类,令`move`、`set`和`display`函数返回`Screen`并检查程序的运行结果,在上一个练习中你的推测正确吗? - -解: - -推测正确。 - -``` -#with '&' -XXXXXXXXXXXXXXXXXXXX#XXXX -XXXXXXXXXXXXXXXXXXXX#XXXX - ^ -# without '&' -XXXXXXXXXXXXXXXXXXXX#XXXX -XXXXXXXXXXXXXXXXXXXXXXXXX - ^ -``` - -## 练习7.30 -通过`this`指针使用成员的做法虽然合法,但是有点多余。讨论显示使用指针访问成员的优缺点。 - -解: - -优点: - -程序的意图更明确 - -函数的参数可以与成员同名,如 - -```cpp - void setAddr(const std::string &addr) { this->addr = addr; } -``` - -缺点: - -有时候显得有点多余,如 -```cpp -std::string getAddr() const { return this->addr; } -``` - -## 练习7.31 -定义一对类`X`和`Y`,其中`X`包含一个指向`Y`的指针,而`Y`包含一个类型为`X`的对象。 - -解: - -```cpp -class Y; - -class X{ - Y* y = nullptr; -}; - -class Y{ - X x; -}; -``` - -## 练习7.32 -定义你自己的`Screen`和`Window_mgr`,其中`clear`是`Window_mgr`的成员,是`Screen`的友元。 - -解: - -```cpp -#include -#include -#include - -class Screen; - -class Window_mgr -{ -public: - using ScreenIndex = std::vector::size_type; - inline void clear(ScreenIndex); - -private: - std::vector screens; -}; - -class Screen -{ - friend void Window_mgr::clear(ScreenIndex); - -public: - using pos = std::string::size_type; - - Screen() = default; - Screen(pos ht, pos wd) :height(ht), width(wd), contents(ht*wd,' ') {} - Screen(pos ht, pos wd, char c) :height(ht), width(wd), contents(ht*wd, c) {} - - char get() const { return contents[cursor]; } - char get(pos r, pos c) const { return contents[r*width + c]; } - inline Screen& move(pos r, pos c); - inline Screen& set(char c); - inline Screen& set(pos r, pos c, char ch); - - const Screen& display(std::ostream& os) const { do_display(os); return *this; } - Screen& display(std::ostream& os) { do_display(os); return *this; } - -private: - void do_display(std::ostream &os) const { os << contents; } - -private: - pos cursor = 0; - pos width = 0, height = 0; - std::string contents; -}; - - -inline void Window_mgr::clear(ScreenIndex i) -{ - Screen& s = screens[i]; - s.contents = std::string(s.height*s.width,' '); -} - -inline Screen& Screen::move(pos r, pos c) -{ - cursor = r*width + c; - return *this; -} - -inline Screen& Screen::set(char c) -{ - contents[cursor] = c; - return *this; -} - -inline Screen& Screen::set(pos r, pos c, char ch) -{ - contents[r*width + c] = ch; - return *this; -} -``` - -## 练习7.33 -如果我们给`Screen`添加一个如下所示的`size`成员将发生什么情况?如果出现了问题,请尝试修改它。 - -```cpp -pos Screen::size() const -{ - return height * width; -} -``` - -解: - -纠正:错误为 error: extra qualification 'Screen::' on member 'size' [-fpermissive] -则应该去掉Screen::,改为 - ```cpp - pos size() const{ - return height * width; - } -``` -## 练习7.34 -如果我们把第256页`Screen`类的`pos`的`typedef`放在类的最后一行会发生什么情况? - -解: - -在 dummy_fcn(pos height) 函数中会出现 未定义的标识符pos。 - -类型名的定义通常出现在类的开始处,这样就能确保所有使用该类型的成员都出现在类名的定义之后。 - -## 练习7.35 -解释下面代码的含义,说明其中的`Type`和`initVal`分别使用了哪个定义。如果代码存在错误,尝试修改它。 - -```cpp -typedef string Type; -Type initVal(); -class Exercise { -public: - typedef double Type; - Type setVal(Type); - Type initVal(); -private: - int val; -}; -Type Exercise::setVal(Type parm) { - val = parm + initVal(); - return val; -} -``` -解: - -书上255页中说: - -``` -然而在类中,如果成员使用了外层作用域中的某个名字,而该名字代表一种类型,则类不能在之后重新定义该名字。 -``` - -因此重复定义`Type`是错误的行为。 - -虽然重复定义类型名字是错误的行为,但是编译器并不为此负责。所以我们要人为地遵守一些原则,在这里有一些讨论。 - -## 练习7.36 -下面的初始值是错误的,请找出问题所在并尝试修改它。 - -```cpp -struct X { - X (int i, int j): base(i), rem(base % j) {} - int rem, base; -}; -``` - -解: - -应该改为: - -```cpp -struct X { - X (int i, int j): base(i), rem(base % j) {} - int base, rem; -}; -``` - -## 练习7.37 -使用本节提供的`Sales_data`类,确定初始化下面的变量时分别使用了哪个构造函数,然后罗列出每个对象所有的数据成员的值。 - -解: -```cpp -Sales_data first_item(cin); // 使用 Sales_data(std::istream &is) ; 各成员值从输入流中读取 -int main() { - // 使用默认构造函数 bookNo = "", cnt = 0, revenue = 0.0 - Sales_data next; - - // 使用 Sales_data(std::string s = ""); bookNo = "9-999-99999-9", cnt = 0, revenue = 0.0 - Sales_data last("9-999-99999-9"); -} -``` - -## 练习7.38 -有些情况下我们希望提供`cin`作为接受`istream&`参数的构造函数的默认实参,请声明这样的构造函数。 - -解: - -```cpp -Sales_data(std::istream &is = std::cin) { read(is, *this); } -``` - -## 练习7.39 -如果接受`string`的构造函数和接受`istream&`的构造函数都使用默认实参,这种行为合法吗?如果不,为什么? - -解: - -不合法。当你调用`Sales_data()`构造函数时,无法区分是哪个重载。 - -## 练习7.40 -从下面的抽象概念中选择一个(或者你自己指定一个),思考这样的类需要哪些数据成员,提供一组合理的构造函数并阐明这样做的原因。 - -``` -(a) Book -(b) Data -(c) Employee -(d) Vehicle -(e) Object -(f) Tree -``` - -解: - -(a) Book. - -```cpp -class Book -{ -public: - Book(unsigned isbn, std::string const& name, std::string const& author, std::string const& pubdate) - :isbn_(isbn), name_(name), author_(author), pubdate_(pubdate) - { } - - explicit Book(std::istream &in) - { - in >> isbn_ >> name_ >> author_ >> pubdate_; - } - -private: - unsigned isbn_; - std::string name_; - std::string author_; - std::string pubdate_; -}; -``` - -## 练习7.41 -使用委托构造函数重新编写你的`Sales_data`类,给每个构造函数体添加一条语句,令其一旦执行就打印一条信息。用各种可能的方式分别创建`Sales_data`对象,认真研究每次输出的信息直到你确实理解了委托构造函数的执行顺序。 - -解: - -- [头文件](https://github.com/applenob/Cpp_Primer_Practice/tree/master/cpp_source/ch07/ex_7_41.h) -- [源文件](https://github.com/applenob/Cpp_Primer_Practice/tree/master/cpp_source/ch07/ex_7_41.cpp) -- [主函数](https://github.com/applenob/Cpp_Primer_Practice/tree/master/cpp_source/ch07/ex_7_41_main.cpp) - -总结:使用委托构造函数,调用顺序是: -- 1.实际的构造函数的函数体。 -- 2.委托构造函数的函数体。 - -## 练习7.42 -对于你在练习7.40中编写的类,确定哪些构造函数可以使用委托。如果可以的话,编写委托构造函数。如果不可以,从抽象概念列表中重新选择一个你认为可以使用委托构造函数的,为挑选出的这个概念编写类定义。 - -解: - -```cpp -class Book -{ -public: - Book(unsigned isbn, std::string const& name, std::string const& author, std::string const& pubdate) - :isbn_(isbn), name_(name), author_(author), pubdate_(pubdate) - { } - - Book(unsigned isbn) : Book(isbn, "", "", "") {} - - explicit Book(std::istream &in) - { - in >> isbn_ >> name_ >> author_ >> pubdate_; - } - -private: - unsigned isbn_; - std::string name_; - std::string author_; - std::string pubdate_; -}; -``` - -## 练习7.43 -假定有一个名为`NoDefault`的类,它有一个接受`int`的构造函数,但是没有默认构造函数。定义类`C`,`C`有一个 `NoDefault`类型的成员,定义`C`的默认构造函数。 -```cpp -class NoDefault { -public: - NoDefault(int i) { } -}; - -class C { -public: - C() : def(0) { } -private: - NoDefault def; -}; -``` - -## 练习7.44 -下面这条声明合法吗?如果不,为什么? - -```cpp -vector vec(10);//vec初始化有10个元素 -``` - -解: - -不合法。因为`NoDefault`没有默认构造函数。 - -## 练习7.45 -如果在上一个练习中定义的vector的元素类型是C,则声明合法吗?为什么? - -合法。因为`C`有默认构造函数。 - -## 练习7.46 -下面哪些论断是不正确的?为什么? - -- (a) 一个类必须至少提供一个构造函数。 -- (b) 默认构造函数是参数列表为空的构造函数。 -- (c) 如果对于类来说不存在有意义的默认值,则类不应该提供默认构造函数。 -- (d) 如果类没有定义默认构造函数,则编译器将为其生成一个并把每个数据成员初始化成相应类型的默认值。 - -解: - -- (a) 不正确。如果我们的类没有显式地定义构造函数,那么编译器就会为我们隐式地定义一个默认构造函数,并称之为合成的默认构造函数。 -- (b) 不完全正确。为每个参数都提供了默认值的构造函数也是默认构造函数。 -- (c) 不正确。哪怕没有意义的值也需要初始化。 -- (d) 不正确。只有当一个类没有定义**任何构造函数**的时候,编译器才会生成一个默认构造函数。 - -## 练习7.47 -说明接受一个`string`参数的`Sales_data`构造函数是否应该是`explicit`的,并解释这样做的优缺点。 - -解: - -是否需要从`string`到`Sales_data`的转换依赖于我们对用户使用该转换的看法。在此例中,这种转换可能是对的。`null_book`中的`string`可能表示了一个不存在的`ISBN`编号。 - -优点: - -可以抑制构造函数定义的隐式转换 - -缺点: - -为了转换要显式地使用构造函数 - -## 练习7.48 -假定`Sales_data`的构造函数不是`explicit`的,则下述定义将执行什么样的操作? - -解: - -```cpp -string null_isbn("9-999-9999-9"); -Sales_data item1(null_isbn); -Sales_data item2("9-999-99999-9"); -``` -这些定义和是不是`explicit`的无关。 - -## 练习7.49 -对于`combine`函数的三种不同声明,当我们调用`i.combine(s)`时分别发生什么情况?其中`i`是一个`Sales_data`,而` s`是一个`string`对象。 - -解: - -```cpp -(a) Sales_data &combine(Sales_data); // ok -(b) Sales_data &combine(Sales_data&); // error C2664: 无法将参数 1 从“std::string”转换为“Sales_data &” 因为隐式转换只有一次 -(c) Sales_data &combine(const Sales_data&) const; // 该成员函数是const 的,意味着不能改变对象。而 combine函数的本意就是要改变对象 -``` - -## 练习7.50 -确定在你的`Person`类中是否有一些构造函数应该是`explicit` 的。 - -解: - -```cpp -explicit Person(std::istream &is){ read(is, *this); } -``` - -## 练习7.51 -`vector`将其单参数的构造函数定义成`explicit`的,而`string`则不是,你觉得原因何在? - -假如我们有一个这样的函数: -```cpp -int getSize(const std::vector&); -``` -如果`vector`没有将单参数构造函数定义成`explicit`的,我们就可以这样调用: - -```cpp -getSize(34); -``` -很明显这样调用会让人困惑,函数实际上会初始化一个拥有34个元素的`vecto`r的临时量,然后返回34。但是这样没有任何意义。而`string`则不同,`string`的单参数构造函数的参数是`const char *`,因此凡是在需要用到`string`的地方都可以用` const char *`来代替(字面值就是`const char *`)。如: - -```cpp -void print(std::string); -print("hello world"); -``` - -## 练习7.52 -使用2.6.1节的 `Sales_data` 类,解释下面的初始化过程。如果存在问题,尝试修改它。 -```cpp -Sales_data item = {"987-0590353403", 25, 15.99}; -``` - -解: - -`Sales_data` 类不是聚合类,应该修改成如下: -```cpp -struct Sales_data { - std::string bookNo; - unsigned units_sold; - double revenue; -}; -``` - -## 练习7.53 -定义你自己的`Debug`。 - -解: - -```cpp -class Debug { -public: - constexpr Debug(bool b = true) : hw(b), io(b), other(b) { } - constexpr Debug(bool h, bool i, bool o) : hw(r), io(i), other(0) { } - - constexpr bool any() { return hw || io || other; } - void set_hw(bool b) { hw = b; } - void set_io(bool b) { io = b; } - void set_other(bool b) { other = b; } - -private: - bool hw; // runtime error - bool io; // I/O error - bool other; // the others -}; -``` - -## 练习7.54 -`Debug`中以 `set_` 开头的成员应该被声明成`constexpr` 吗?如果不,为什么? - -解: - -不能。`constexpr`函数必须包含一个返回语句。 - -## 练习7.55 -7.5.5节的`Data`类是字面值常量类吗?请解释原因。 - -解: - -不是。因为`std::string`不是字面值类型。 - -## 练习7.56 -什么是类的静态成员?它有何优点?静态成员与普通成员有何区别? - -解: - -与类本身相关,而不是与类的各个对象相关的成员是静态成员。静态成员能用于某些场景,而普通成员不能。 - -## 练习7.57 -编写你自己的`Account`类。 - -解: - -```cpp -class Account { -public: - void calculate() { amount += amount * interestRate; } - static double rate() { return interestRate; } - static void rate(double newRate) { interestRate = newRate; } - -private: - std::string owner; - double amount; - static double interestRate; - static constexpr double todayRate = 42.42; - static double initRate() { return todayRate; } -}; - -double Account::interestRate = initRate(); -``` - -## 练习7.58 -下面的静态数据成员的声明和定义有错误吗?请解释原因。 - -```cpp -//example.h -class Example { -public: - static double rate = 6.5; - static const int vecSize = 20; - static vector vec(vecSize); -}; - -//example.c -#include "example.h" -double Example::rate; -vector Example::vec; -``` - -解: - -`rate`应该是一个**常量表达式**。而类内只能初始化整型类型的静态常量,所以不能在类内初始化`vec`。修改后如下: - -```cpp -// example.h -class Example { -public: - static constexpr double rate = 6.5; - static const int vecSize = 20; - static vector vec; -}; - -// example.C -#include "example.h" -constexpr double Example::rate; -vector Example::vec(Example::vecSize); -``` \ No newline at end of file diff --git a/docs/Cpp-Primer-exercise/8.IO库.md b/docs/Cpp-Primer-exercise/8.IO库.md deleted file mode 100644 index 91271c4..0000000 --- a/docs/Cpp-Primer-exercise/8.IO库.md +++ /dev/null @@ -1,418 +0,0 @@ ---- -sort: 8 ---- - -# IO库 - -## 练习8.1 -> 编写函数,接受一个`istream&`参数,返回值类型也是`istream&`。此函数须从给定流中读取数据,直至遇到文件结束标识时停止。它将读取的数据打印在标准输出上。完成这些操作后,在返回流之前,对流进行复位,使其处于有效状态。 - -解: - -```cpp -std::istream& func(std::istream &is) -{ - std::string buf; - while (is >> buf) - std::cout << buf << std::endl; - is.clear(); - return is; -} -``` - -## 练习8.2 -> 测试函数,调用参数为`cin`。 - -解: - -```cpp -#include -using std::istream; - -istream& func(istream &is) -{ - std::string buf; - while (is >> buf) - std::cout << buf << std::endl; - is.clear(); - return is; -} - -int main() -{ - istream& is = func(std::cin); - std::cout << is.rdstate() << std::endl; - return 0; -} -``` - -## 练习8.3 -> 什么情况下,下面的`while`循环会终止? - -```cpp -while (cin >> i) /* ... */ -``` - -解: - -遇到文件结束符、或者IO流错误,或读入了无效数据。 - -## 练习8.4 -> 编写函数,以读模式打开一个文件,将其内容读入到一个`string`的`vector`中,将每一行作为一个独立的元素存于`vector`中。 - -解: - -```cpp -void ReadFileToVec(const string& fileName, vector& vec) -{ - ifstream ifs(fileName); - if (ifs) - { - string buf; - while (getline(ifs, buf)) - vec.push_back(buf); - } -} -``` - -## 练习8.5 -> 重写上面的程序,将每个单词作为一个独立的元素进行存储。 -解: - -```cpp -void ReadFileToVec(const string& fileName, vector& vec) -{ - ifstream ifs(fileName); - if (ifs) - { - string buf; - while (ifs >> buf) - vec.push_back(buf); - } -} -``` - -## 练习8.6 -> 重写7.1.1节的书店程序,从一个文件中读取交易记录。将文件名作为一个参数传递给`main`。 - -解: - -```cpp -#include -#include - -#include "../ch07/ex7_26.h" -using std::ifstream; using std::cout; using std::endl; using std::cerr; - -int main(int argc, char **argv) -{ - ifstream input(argv[1]); - - Sales_data total; - if (read(input, total)) - { - Sales_data trans; - while (read(input, trans)) - { - if (total.isbn() == trans.isbn()) - total.combine(trans); - else - { - print(cout, total) << endl; - total = trans; - } - } - print(cout, total) << endl; - } - else - { - cerr << "No data?!" << endl; - } - - return 0; -} -``` - -## 练习8.7 -> 修改上一节的书店程序,将结果保存到一个文件中。将输出文件名作为第二个参数传递给`main`函数。 - -解: - -```cpp -#include -#include - -#include "../ch07/ex7_26.h" -using std::ifstream; using std::ofstream; using std::endl; using std::cerr; - -int main(int argc, char **argv) -{ - ifstream input(argv[1]); - ofstream output(argv[2]); - - Sales_data total; - if (read(input, total)) - { - Sales_data trans; - while (read(input, trans)) - { - if (total.isbn() == trans.isbn()) - total.combine(trans); - else - { - print(output, total) << endl; - total = trans; - } - } - print(output, total) << endl; - } - else - { - cerr << "No data?!" << endl; - } - - return 0; -} -``` - -## 练习8.8 -> 修改上一题的程序,将结果追加到给定的文件末尾。对同一个输出文件,运行程序至少两次,检验数据是否得以保留。 - -解: - -```cpp -#include -#include - -#include "../ch07/ex7_26.h" -using std::ifstream; using std::ofstream; using std::endl; using std::cerr; - -int main(int argc, char **argv) -{ - ifstream input(argv[1]); - ofstream output(argv[2], ofstream::app); - - Sales_data total; - if (read(input, total)) - { - Sales_data trans; - while (read(input, trans)) - { - if (total.isbn() == trans.isbn()) - total.combine(trans); - else - { - print(output, total) << endl; - total = trans; - } - } - print(output, total) << endl; - } - else - { - cerr << "No data?!" << endl; - } - - return 0; -} -``` - - -## 练习8.9 -> 使用你为8.1.2节第一个练习所编写的函数打印一个`istringstream`对象的内容。 - -解: - -```cpp -#include -#include -using std::istream; - -istream& func(istream &is) -{ - std::string buf; - while (is >> buf) - std::cout << buf << std::endl; - is.clear(); - return is; -} - -int main() -{ - std::istringstream iss("hello"); - func(iss); - return 0; -} - -``` - -## 练习8.10 -> 编写程序,将来自一个文件中的行保存在一个`vector`中。然后使用一个`istringstream`从`vector`读取数据元素,每次读取一个单词。 - -解: - -```cpp -#include -#include -#include -#include -#include - -using std::vector; using std::string; using std::ifstream; using std::istringstream; using std::cout; using std::endl; using std::cerr; - -int main() -{ - ifstream ifs("../data/book.txt"); - if (!ifs) - { - cerr << "No data?" << endl; - return -1; - } - - vector vecLine; - string line; - while (getline(ifs, line)) - vecLine.push_back(line); - - for (auto &s : vecLine) - { - istringstream iss(s); - string word; - while (iss >> word) - cout << word << endl; - } - - return 0; -} -``` - -## 练习8.11 -> 本节的程序在外层`while`循环中定义了`istringstream`对象。如果`record`对象定义在循环之外,你需要对程序进行怎样的修改?重写程序,将`record`的定义移到`while`循环之外,验证你设想的修改方法是否正确。 - -解: - -```cpp -#include -#include -#include -#include -using std::vector; using std::string; using std::cin; using std::istringstream; - -struct PersonInfo { - string name; - vector phones; -}; - -int main() -{ - string line, word; - vector people; - istringstream record; - while (getline(cin, line)) - { - PersonInfo info; - record.clear(); - record.str(line); - record >> info.name; - while (record >> word) - info.phones.push_back(word); - people.push_back(info); - } - - for (auto &p : people) - { - std::cout << p.name << " "; - for (auto &s : p.phones) - std::cout << s << " "; - std::cout << std::endl; - } - - return 0; -} -``` - -## 练习8.12 -> 我们为什么没有在`PersonInfo`中使用类内初始化? - -解: - -因为这里只需要聚合类就够了,所以没有必要在`PersionInfo`中使用类内初始化。 - -## 练习8.13 -> 重写本节的电话号码程序,从一个命名文件而非`cin`读取数据。 - -解: - -```cpp -#include -#include -#include -#include -#include - -using std::vector; using std::string; using std::cin; using std::istringstream; -using std::ostringstream; using std::ifstream; using std::cerr; using std::cout; using std::endl; -using std::isdigit; - -struct PersonInfo { - string name; - vector phones; -}; - -bool valid(const string& str) -{ - return isdigit(str[0]); -} - -string format(const string& str) -{ - return str.substr(0,3) + "-" + str.substr(3,3) + "-" + str.substr(6); -} - -int main() -{ - ifstream ifs("../data/phonenumbers.txt"); - if (!ifs) - { - cerr << "no phone numbers?" << endl; - return -1; - } - - string line, word; - vector people; - istringstream record; - while (getline(ifs, line)) - { - PersonInfo info; - record.clear(); - record.str(line); - record >> info.name; - while (record >> word) - info.phones.push_back(word); - people.push_back(info); - } - - for (const auto &entry : people) - { - ostringstream formatted, badNums; - for (const auto &nums : entry.phones) - if (!valid(nums)) badNums << " " << nums; - else formatted << " " << format(nums); - if (badNums.str().empty()) - cout << entry.name << " " << formatted.str() << endl; - else - cerr << "input error: " << entry.name - << " invalid number(s) " << badNums.str() << endl; - } - - return 0; -} -``` - -## 练习8.14 -> 我们为什么将`entry`和`nums`定义为`const auto&`? - -解: - -它们都是类类型,因此使用引用避免拷贝。 -在循环当中不会改变它们的值,因此用`const`。 \ No newline at end of file diff --git a/docs/Cpp-Primer-exercise/9.顺序容器.md b/docs/Cpp-Primer-exercise/9.顺序容器.md deleted file mode 100644 index 285e80a..0000000 --- a/docs/Cpp-Primer-exercise/9.顺序容器.md +++ /dev/null @@ -1,1148 +0,0 @@ ---- -sort: 9 ---- - -# 顺序容器 - -## 练习9.1 - -> 对于下面的程序任务,`vector`、`deque`和`list`哪种容器最为适合?解释你的选择的理由。如果没有哪一种容器优于其他容器,也请解释理由。 -* (a) 读取固定数量的单词,将它们按字典序插入到容器中。我们将在下一章中看到,关联容器更适合这个问题。 -* (b) 读取未知数量的单词,总是将单词插入到末尾。删除操作在头部进行。 -* (c) 从一个文件读取未知数量的整数。将这些数排序,然后将它们打印到标准输出。 - -解: - -* (a) `list` ,因为需要频繁的插入操作。 -* (b) `deque` ,总是在头尾进行插入、删除操作。 -* (c) `vector` ,不需要进行插入删除操作。 - -## 练习9.2 - -> 定义一个`list`对象,其元素类型是`int`的`deque`。 - -解: - - -```cpp -std::list> a; -``` - -## 练习9.3 - -> 构成迭代器范围的迭代器有何限制? - -解: - -两个迭代器 `begin` 和 `end`需满足以下条件: -* 它们指向同一个容器中的元素,或者是容器最后一个元素之后的位置。 -* 我们可以通过反复递增`begin`来到达`end`。换句话说,`end` 不在`begin`之前。 - -## 练习9.4 - -> 编写函数,接受一对指向`vector`的迭代器和一个`int`值。在两个迭代器指定的范围中查找给定的值,返回一个布尔值来指出是否找到。 - -解: - -```cpp -bool find(vector::const_iterator begin, vector::const_iterator end, int val) -{ - while (begin++ != end) - { - if (*begin == val) - return true; - } - return false; -} -``` - -## 练习9.5 - -> 重写上一题的函数,返回一个迭代器指向找到的元素。注意,程序必须处理未找到给定值的情况。 - -解: - -```cpp -vector::const_iterator find(vector::const_iterator begin, vector::const_iterator end, int val) -{ - while (begin != end) - { - if (*begin == val) - return begin; - ++begin; - } - return end; -} -``` - -## 练习9.6 - -> 下面的程序有何错误?你应该如何修改它? - -```cpp -list lst1; -list::iterator iter1 = lst1.begin(), - iter2 = lst1.end(); -while (iter1 < iter2) /* ... */ -``` - -解: - -与vector和deque不同,list的迭代器不支持<运算,只支持递增、递减、==及!=等运算。原因在于这几种数据结构的实现有所不同。vector和deque将元素在内存中连续保存,而list则是将元素以链表方式存储,因此前者可以方便地实现迭代器的大小比较来体现元素的前后关系。而在list中,两个指针的大小关系与指向元素的前后关系并不一定吻合,实现<运算将非常困难和低效。 - -修改成如下: -```cpp -while (iter1 != iter2) -``` - -## 练习9.7 - -> 为了索引`int`的`vector`中的元素,应该使用什么类型? - -解: - -```cpp -vector::iterator -``` - -## 练习9.8 - -> 为了读取`string`的`list`中的元素,应该使用什么类型?如果写入`list`,又应该使用什么类型? - -解: - -```cpp -list::value_type // 读 -list::reference // 写,需要引用类型 -``` - -## 练习9.9 - -> `begin`和`cbegin`两个函数有什么不同? - -解: - -`begin` 返回的是普通迭代器,`cbegin` 返回的是常量迭代器。 - -## 练习9.10 - -> 下面4个对象分别是什么类型? -```cpp -vector v1; -const vector v2; -auto it1 = v1.begin(), it2 = v2.begin(); -auto it3 = v1.cbegin(), it4 = v2.cbegin(); -``` - -解: - -`v1`是`int`的`vector`类型 - -`v2`是`int`的常量`vector`类型 - -`it1` 是 `vector::iterator` - -`it2`,`it3` 和 `it4` 是 `vector::const_iterator` - - -## 练习9.11 - -> 对6种创建和初始化`vector`对象的方法,每一种都给出一个实例。解释每个`vector`包含什么值。 - -解: - -```cpp -vector vec; // 0 -vector vec(10); // 10个0 -vector vec(10, 1); // 10个1 -vector vec{ 1, 2, 3, 4, 5 }; // 1, 2, 3, 4, 5 -vector vec(other_vec); // 拷贝 other_vec 的元素 -vector vec(other_vec.begin(), other_vec.end()); // 拷贝 other_vec 的元素 -``` - -## 练习9.12 - -> 对于接受一个容器创建其拷贝的构造函数,和接受两个迭代器创建拷贝的构造函数,解释它们的不同。 - -解: - -* 接受一个容器创建其拷贝的构造函数,初始化完成后,会得到一个一模一样的拷贝。 -* 但不需要已有容器中的全部元素,而只是想拷贝其中一部分时,可以接受两个迭代器的范围的起始和尾后位置的迭代器。 - -## 练习9.13 - -> 如何从一个`list`初始化一个`vector`?从一个`vector`又该如何创建?编写代码验证你的答案。 - -解: - -```cpp -list ilst(5, 4); -vector ivc(5, 5); - -vector dvc(ilst.begin(), ilst.end()); -vector dvc2(ivc.begin(), ivc.end()); -``` - -## 练习9.14 - -> 编写程序,将一个`list`中的`char *`指针元素赋值给一个`vector`中的`string`。 - -解: - -```cpp - std::list l{ "hello", "world" }; - std::vector v; - v.assign(l.cbegin(), l.cend()); -``` - -## 练习9.15 - -> 编写程序,判定两个`vector`是否相等。 - -解: - -```cpp - std::vector vec1{ 1, 2, 3, 4, 5 }; - std::vector vec2{ 1, 2, 3, 4, 5 }; - std::vector vec3{ 1, 2, 3, 4 }; - - std::cout << (vec1 == vec2 ? "true" : "false") << std::endl; - std::cout << (vec1 == vec3 ? "true" : "false") << std::endl; -``` - -## 练习9.16 - -> 重写上一题的程序,比较一个list中的元素和一个vector中的元素。 - -解: - -```cpp - std::list li{ 1, 2, 3, 4, 5 }; - std::vector vec2{ 1, 2, 3, 4, 5 }; - std::vector vec3{ 1, 2, 3, 4 }; - - std::cout << (std::vector(li.begin(), li.end()) == vec2 ? "true" : "false") << std::endl; - std::cout << (std::vector(li.begin(), li.end()) == vec3 ? "true" : "false") << std::endl; -``` - -## 练习9.17 - -> 假定`c1`和`c2`是两个容器,下面的比较操作有何限制? - -解: - -```cpp - if (c1 < c2) -``` - -* `c1`和`c2`必须是相同类型的容器并且保存相同类型的元素 -* 元素类型要支持关系运算符 - -## 练习9.18 - -> 编写程序,从标准输入读取`string`序列,存入一个`deque`中。编写一个循环,用迭代器打印`deque`中的元素。 - -解: - -```cpp -#include -#include -#include - -using std::string; using std::deque; using std::cout; using std::cin; using std::endl; - -int main() -{ - deque input; - for (string str; cin >> str; input.push_back(str)); - for (auto iter = input.cbegin(); iter != input.cend(); ++iter) - cout << *iter << endl; - - return 0; -} -``` - -## 练习9.19 - -> 重写上一题的程序,用`list`替代`deque`。列出程序要做出哪些改变。 - -解: - -只需要在声明上做出改变即可,其他都不变。 -```cpp -deque input; -//改为 -list input; -``` - -## 练习9.20 - -> 编写程序,从一个`list`拷贝元素到两个`deque`中。值为偶数的所有元素都拷贝到一个`deque`中,而奇数值元素都拷贝到另一个`deque`中。 - -解: - -```cpp -#include -#include -#include -using std::deque; using std::list; using std::cout; using std::cin; using std::endl; - -int main() -{ - list l{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; - deque odd, even; - for(auto iter = l.cbegin();iter!=l.cend();iter++) - if(*iter & 1) // 1为偶数,0为奇数 - odd.push_back(i); - else even.push_back(i); - - for (auto i : odd) cout << i << " "; - cout << endl; - for (auto i : even)cout << i << " "; - cout << endl; - - return 0; -} -``` - -## 练习9.21 - -> 如果我们将第308页中使用`insert`返回值将元素添加到`list`中的循环程序改写为将元素插入到`vector`中,分析循环将如何工作。 - -解: - -一样的。如书上所说: -> 第一次调用 `insert` 会将我们刚刚读入的 `string` 插入到 `iter` 所指向的元素之前的位置。`insert` 返回的迭代器恰好指向这个新元素。我们将此迭代器赋予 `iter` 并重复循环,读取下一个单词。只要继续有单词读入,每步 while 循环就会将一个新元素插入到 `iter` 之前,并将 `iter` 改变为新加入元素的尾置。此元素为(新的)首元素。因此,每步循环将一个元素插入到 `list` 首元素之前的位置。 - -## 练习9.22 - -> 假定`iv`是一个`int`的`vector`,下面的程序存在什么错误?你将如何修改? - -解: - -```cpp -vector::iterator iter = iv.begin(), - mid = iv.begin() + iv.size() / 2; -while (iter != mid) - if (*iter == some_val) - iv.insert(iter, 2 * some_val); -``` - -解: - -* 循环不会结束 -* 迭代器可能会失效 - -要改为下面这样: -```cpp -while (iter != mid) -{ - if (*iter == some_val) - { - iter = v.insert(iter, 2 * some_val); - ++iter; - } - ++iter; -} -``` - -## 练习9.23 - -> 在本节第一个程序中,若`c.size()` 为1,则`val`、`val2`、`val3`和`val4`的值会是什么? - -解: - -都会是同一个值(容器中仅有的那个)。 - -## 练习9.24 - -> 编写程序,分别使用`at`、下标运算符、`front` 和 `begin` 提取一个`vector`中的第一个元素。在一个空`vector`上测试你的程序。 - -解: - -```cpp -#include -#include - -int main() -{ - std::vector v; - std::cout << v.at(0); // terminating with uncaught exception of type std::out_of_range - std::cout << v[0]; // Segmentation fault: 11 - std::cout << v.front(); // Segmentation fault: 11 - std::cout << *v.begin(); // Segmentation fault: 11 - return 0; -} -``` - -## 练习9.25 - -> 对于第312页中删除一个范围内的元素的程序,如果 `elem1` 与 `elem2` 相等会发生什么?如果 `elem2` 是尾后迭代器,或者 `elem1` 和 `elem2` 皆为尾后迭代器,又会发生什么? - -解: - -* 如果 `elem1` 和 `elem2` 相等,那么不会发生任何操作。 -* `如果elem2` 是尾后迭代器,那么删除从 `elem1` 到最后的元素。 -* 如果两者皆为尾后迭代器,也什么都不会发生。 - -## 练习9.26 - -> 使用下面代码定义的`ia`,将`ia`拷贝到一个`vector`和一个`list`中。使用单迭代器版本的`erase`从`list`中删除奇数元素,从`vector`中删除偶数元素。 - -```cpp -int ia[] = { 0, 1, 1, 2, 3, 5, 8, 13, 21, 55, 89 }; -``` - -解: - -```cpp -vector vec(ia, end(ia)); -list lst(vec.begin(), vec.end()); - -for (auto it = lst.begin(); it != lst.end(); ) - if (*it & 0x1) - it = lst.erase(it); - else - ++it; - -for (auto it = vec.begin(); it != vec.end(); ) - if (!(*it & 0x1)) - it = vec.erase(it); - else - ++it; -``` - -## 练习9.27 - -> 编写程序,查找并删除`forward_list`中的奇数元素。 - -解: - -```cpp -#include -#include - -using std::forward_list; -using std::cout; - -auto remove_odds(forward_list& flist) -{ - auto is_odd = [] (int i) { return i & 0x1; }; - flist.remove_if(is_odd); -} - -int main() -{ - forward_list data = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; - remove_odds(data); - for (auto i : data) - cout << i << " "; - - return 0; -} -``` - -## 练习9.28 - -> 编写函数,接受一个`forward_list`和两个`string`共三个参数。函数应在链表中查找第一个`string`,并将第二个`string`插入到紧接着第一个`string`之后的位置。若第一个`string`未在链表中,则将第二个`string`插入到链表末尾。 - -```cpp -void find_and_insert(forward_list& flst, const string& s1, const string& s2) -{ - auto prev = flst.before_begin(); - auto curr = flst.begin(); - while (curr != flst.end()) - { - if (*curr == s1) - { - flst.insert_after(curr, s2); - return; - } - prev = curr; - ++curr; - } - flst.insert_after(prev, s2); -} -``` - -## 练习9.29 - -> 假定`vec`包含25个元素,那么`vec.resize(100)`会做什么?如果接下来调用`vec.resize(10)`会做什么? - -解: - -* 将75个值为0的元素添加到`vec`的末尾 -* 从`vec`的末尾删除90个元素 - -## 练习9.30 - -> 接受单个参数的`resize`版本对元素类型有什么限制(如果有的话)? - -解: - -元素类型必须提供一个默认构造函数。 - -## 练习9.31 - -> 第316页中删除偶数值元素并复制奇数值元素的程序不能用于`list`或`forward_list`。为什么?修改程序,使之也能用于这些类型。 - -解: - -```cpp -iter += 2; -``` - -因为复合赋值语句只能用于`string`、`vector`、`deque`、`array`,所以要改为: - -```cpp -++iter; -++iter; -``` - -如果是`forward_list`的话,要增加一个首先迭代器`prev`: - -```cpp -auto prev = flst.before_begin(); -//... -curr == flst.insert_after(prev, *curr); -++curr; ++curr; -++prev; ++prev; -``` - -## 练习9.32 - -> 在第316页的程序中,向下面语句这样调用`insert`是否合法?如果不合法,为什么? - -```cpp -iter = vi.insert(iter, *iter++); -``` - -解: - -不合法。因为参数的求值顺序是未指定的。 - -## 练习9.33 - -> 在本节最后一个例子中,如果不将`insert`的结果赋予`begin`,将会发生什么?编写程序,去掉此赋值语句,验证你的答案。 - -解: - -`begin`将会失效。 - -```cpp -#include -#include - -using std::cout; -using std::endl; -using std::vector; - -int main() -{ - vector data { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; - for(auto cur = data.begin(); cur != data.end(); ++cur) - if(*cur & 0x1) - cur = data.insert(cur, *cur), ++cur; - - for (auto i : data) - cout << i << " "; - - return 0; -} -``` - -## 练习9.34 - -> 假定`vi`是一个保存`int`的容器,其中有偶数值也有奇数值,分析下面循环的行为,然后编写程序验证你的分析是否正确。 - -```cpp -iter = vi.begin(); -while (iter != vi.end()) - if (*iter % 2) - iter = vi.insert(iter, *iter); -++iter; -``` - -解: - -循环永远不会结束。将++iter放入循环中。 - -``` -iter = vi.begin(); -while (iter != vi.end()){ - if (*iter % 2) - iter = vi.insert(iter, *iter); - ++iter; -} -``` - - - -## 练习9.35 - -> 解释一个`vector`的`capacity`和`size`有何区别。 - -解: - -* `capacity`的值表明,在不重新分配内存空间的情况下,容器可以保存多少元素 -* 而`size`的值是指容器已经保存的元素的数量 - -## 练习9.36 - -> 一个容器的`capacity`可能小于它的`size`吗? - -解: - -不可能。 - -## 练习9.37 - -> 为什么`list`或`array`没有`capacity`成员函数? - -解: - -因为`list`是链表,而`array`不允许改变容器大小。 - -## 练习9.38 - -> 编写程序,探究在你的标准实现中,`vector`是如何增长的。 - -解: - - - -```cpp -#include -#include -#include - -using namespace std; - -int main() -{ - vector v; - - for (int i = 0; i < 100; i++) - { - cout << "capacity: " << v.capacity() << " size: " << v.size() << endl; - v.push_back(i); - } - return 0; -} -``` - -输出: - -``` -capacity: 0 size: 0 -capacity: 1 size: 1 -capacity: 2 size: 2 -capacity: 3 size: 3 -capacity: 4 size: 4 -capacity: 6 size: 5 -capacity: 6 size: 6 -capacity: 9 size: 7 -capacity: 9 size: 8 -capacity: 9 size: 9 -capacity: 13 size: 10 -capacity: 13 size: 11 -capacity: 13 size: 12 -capacity: 13 size: 13 -capacity: 19 size: 14 -capacity: 19 size: 15 -capacity: 19 size: 16 -capacity: 19 size: 17 -capacity: 19 size: 18 -capacity: 19 size: 19 -capacity: 28 size: 20 -capacity: 28 size: 21 -capacity: 28 size: 22 -capacity: 28 size: 23 -capacity: 28 size: 24 -capacity: 28 size: 25 -capacity: 28 size: 26 -capacity: 28 size: 27 -capacity: 28 size: 28 -capacity: 42 size: 29 -capacity: 42 size: 30 -capacity: 42 size: 31 -capacity: 42 size: 32 -capacity: 42 size: 33 -capacity: 42 size: 34 -capacity: 42 size: 35 -capacity: 42 size: 36 -capacity: 42 size: 37 -capacity: 42 size: 38 -capacity: 42 size: 39 -capacity: 42 size: 40 -capacity: 42 size: 41 -capacity: 42 size: 42 -capacity: 63 size: 43 -capacity: 63 size: 44 -capacity: 63 size: 45 -capacity: 63 size: 46 -capacity: 63 size: 47 -capacity: 63 size: 48 -capacity: 63 size: 49 -capacity: 63 size: 50 -capacity: 63 size: 51 -capacity: 63 size: 52 -capacity: 63 size: 53 -capacity: 63 size: 54 -capacity: 63 size: 55 -capacity: 63 size: 56 -capacity: 63 size: 57 -capacity: 63 size: 58 -capacity: 63 size: 59 -capacity: 63 size: 60 -capacity: 63 size: 61 -capacity: 63 size: 62 -capacity: 63 size: 63 -capacity: 94 size: 64 -capacity: 94 size: 65 -capacity: 94 size: 66 -capacity: 94 size: 67 -capacity: 94 size: 68 -capacity: 94 size: 69 -capacity: 94 size: 70 -capacity: 94 size: 71 -capacity: 94 size: 72 -capacity: 94 size: 73 -capacity: 94 size: 74 -capacity: 94 size: 75 -capacity: 94 size: 76 -capacity: 94 size: 77 -capacity: 94 size: 78 -capacity: 94 size: 79 -capacity: 94 size: 80 -capacity: 94 size: 81 -capacity: 94 size: 82 -capacity: 94 size: 83 -capacity: 94 size: 84 -capacity: 94 size: 85 -capacity: 94 size: 86 -capacity: 94 size: 87 -capacity: 94 size: 88 -capacity: 94 size: 89 -capacity: 94 size: 90 -capacity: 94 size: 91 -capacity: 94 size: 92 -capacity: 94 size: 93 -capacity: 94 size: 94 -capacity: 141 size: 95 -capacity: 141 size: 96 -capacity: 141 size: 97 -capacity: 141 size: 98 -capacity: 141 size: 99 -``` - -## 练习9.39 - -> 解释下面程序片段做了什么: - -```cpp -vector svec; -svec.reserve(1024); -string word; -while (cin >> word) - svec.push_back(word); -svec.resize(svec.size() + svec.size() / 2); -``` - -解: - -定义一个`vector`,为它分配1024个元素的空间。然后通过一个循环从标准输入中读取字符串并添加到`vector`当中。循环结束后,改变`vector`的容器大小(元素数量)为原来的1.5倍,使用元素的默认初始化值填充。如果容器的大小超过1024,`vector`也会重新分配空间以容纳新增的元素。 - -## 练习9.40 - -> 如果上一题的程序读入了256个词,在`resize`之后容器的`capacity`可能是多少?如果读入了512个、1000个、或1048个呢? - -解: - -* 如果读入了256个词,`capacity` 仍然是 1024 -* 如果读入了512个词,`capacity` 仍然是 1024 -* 如果读入了1000或1048个词,`capacity` 取决于具体实现。 - -## 练习9.41 - -> 编写程序,从一个`vector`初始化一个`string`。 - -解: - -```cpp - vector v{ 'h', 'e', 'l', 'l', 'o' }; - string str(v.cbegin(), v.cend()); -``` - -## 练习9.42 - -> 假定你希望每次读取一个字符存入一个`string`中,而且知道最少需要读取100个字符,应该如何提高程序的性能? - -解: - -使用 `reserve(100)` 函数预先分配100个元素的空间。 - -## 练习9.43 - -> 编写一个函数,接受三个`string`参数是`s`、`oldVal` 和`newVal`。使用迭代器及`insert`和`erase`函数将`s`中所有`oldVal`替换为`newVal`。测试你的程序,用它替换通用的简写形式,如,将"tho"替换为"though",将"thru"替换为"through"。 - -解: - -```cpp -#include -#include -#include -#include - -using std::cout; -using std::endl; -using std::string; - -auto replace_with(string &s, string const& oldVal, string const& newVal) -{ - for (auto cur = s.begin(); cur <= s.end() - oldVal.size(); ) - if (oldVal == string{ cur, cur + oldVal.size() }) - cur = s.erase(cur, cur + oldVal.size()), - cur = s.insert(cur, newVal.begin(), newVal.end()), - cur += newVal.size(); - else - ++cur; -} - -int main() -{ - string s{ "To drive straight thru is a foolish, tho courageous act." }; - replace_with(s, "tho", "though"); - replace_with(s, "thru", "through"); - cout << s << endl; - - return 0; -} -``` - -## 练习9.44 - -> 重写上一题的函数,这次使用一个下标和`replace`。 - -解: - -```cpp -#include -#include - -using std::cout; -using std::endl; -using std::string; - -auto replace_with(string &s, string const& oldVal, string const& newVal) -{ - for (size_t pos = 0; pos <= s.size() - oldVal.size();) - if (s[pos] == oldVal[0] && s.substr(pos, oldVal.size()) == oldVal) - s.replace(pos, oldVal.size(), newVal), - pos += newVal.size(); - else - ++pos; -} - -int main() -{ - string str{ "To drive straight thru is a foolish, tho courageous act." }; - replace_with(str, "tho", "though"); - replace_with(str, "thru", "through"); - cout << str << endl; - return 0; -} -``` - -## 练习9.45 - -> 编写一个函数,接受一个表示名字的`string`参数和两个分别表示前缀(如"Mr."或"Mrs.")和后缀(如"Jr."或"III")的字符串。使用迭代器及`insert`和`append`函数将前缀和后缀添加到给定的名字中,将生成的新`string`返回。 - -解: - -```cpp -#include -#include - -using std::string; -using std::cin; -using std::cout; -using std::endl; - -// Exercise 9.45 -auto add_pre_and_suffix(string name, string const& pre, string const& su) -{ - name.insert(name.begin(), pre.cbegin(), pre.cend()); - return name.append(su); -} - -int main() -{ - string name("Wang"); - cout << add_pre_and_suffix(name, "Mr.", ", Jr.") << endl; - return 0; -} -``` - -## 练习9.46 - -> 重写上一题的函数,这次使用位置和长度来管理`string`,并只使用`insert`。 - -解: - -```cpp -#include -#include - -auto add_pre_and_suffix(std::string name, std::string const& pre, std::string const& su) -{ - name.insert(0, pre); - name.insert(name.size(), su); - return name; -} - -int main() -{ - std::string name("alan"); - std::cout << add_pre_and_suffix(name, "Mr.", ",Jr."); - return 0; -} -``` - -## 练习9.47 - -> 编写程序,首先查找`string`"ab2c3d7R4E6"中每个数字字符,然后查找其中每个字母字符。编写两个版本的程序,第一个要使用`find_first_of`,第二个要使用`find_first_not_of`。 - -解: - -```cpp -#include -#include - -using namespace std; - -int main() -{ - string numbers("0123456789"); - string s("ab2c3d7R4E6"); - - cout << "numeric characters: "; - for (int pos = 0; (pos = s.find_first_of(numbers, pos)) != string::npos; ++pos) - { - cout << s[pos] << " "; - } - - cout << "\nalphabetic characters: "; - for (int pos = 0; (pos = s.find_first_not_of(numbers, pos)) != string::npos; ++pos) - { - cout << s[pos] << " "; - } - - return 0; -} -``` - -## 练习9.48 - -> 假定`name`和`numbers`的定义如325页所示,`numbers.find(name)`返回什么? - -解: - -返回 `string::npos` - -## 练习9.49 - -> 如果一个字母延伸到中线之上,如d或f,则称其有上出头部分(`ascender`)。如果一个字母延伸到中线之下,如p或g,则称其有下出头部分(`descender`)。编写程序,读入一个单词文件,输出最长的既不包含上出头部分,也不包含下出头部分的单词。 - -解: - -```cpp -#include -#include -#include - -using std::string; using std::cout; using std::endl; using std::ifstream; - -int main() -{ - ifstream ifs("../data/letter.txt"); - if (!ifs) return -1; - - string longest; - auto update_with = [&longest](string const& curr) - { - if (string::npos == curr.find_first_not_of("aceimnorsuvwxz")) - longest = longest.size() < curr.size() ? curr : longest; - }; - for (string curr; ifs >> curr; update_with(curr)); - cout << longest << endl; - - return 0; -} -``` - -## 练习9.50 - -> 编写程序处理一个`vector`,其元素都表示整型值。计算`vector`中所有元素之和。修改程序,使之计算表示浮点值的`string`之和。 - -解: - -```cpp -#include -#include -#include - -auto sum_for_int(std::vector const& v) -{ - int sum = 0; - for (auto const& s : v) - sum += std::stoi(s); - return sum; -} - -auto sum_for_float(std::vector const& v) -{ - float sum = 0.0; - for (auto const& s : v) - sum += std::stof(s); - return sum; -} - -int main() -{ - std::vector v = { "1", "2", "3", "4.5" }; - std::cout << sum_for_int(v) << std::endl; - std::cout << sum_for_float(v) << std::endl; - - return 0; -} -``` - -## 练习9.51 - -> 设计一个类,它有三个`unsigned`成员,分别表示年、月和日。为其编写构造函数,接受一个表示日期的`string`参数。你的构造函数应该能处理不同的数据格式,如January 1,1900、1/1/1990、Jan 1 1900 等。 - -解: - -```cpp -#include -#include -#include - -using namespace std; -class My_date{ -private: - unsigned year, month, day; -public: - My_date(const string &s){ - - unsigned tag; - unsigned format; - format = tag = 0; - - // 1/1/1900 - if(s.find_first_of("/")!= string :: npos) - { - format = 0x01; - } - - // January 1, 1900 or Jan 1, 1900 - if((s.find_first_of(',') >= 4) && s.find_first_of(',')!= string :: npos){ - format = 0x10; - } - else{ // Jan 1 1900 - if(s.find_first_of(' ') >= 3 - && s.find_first_of(' ')!= string :: npos){ - format = 0x10; - tag = 1; - } - } - - switch(format){ - - case 0x01: - day = stoi(s.substr(0, s.find_first_of("/"))); - month = stoi(s.substr(s.find_first_of("/") + 1, s.find_last_of("/")- s.find_first_of("/"))); - year = stoi(s.substr(s.find_last_of("/") + 1, 4)); - - break; - - case 0x10: - if( s.find("Jan") < s.size() ) month = 1; - if( s.find("Feb") < s.size() ) month = 2; - if( s.find("Mar") < s.size() ) month = 3; - if( s.find("Apr") < s.size() ) month = 4; - if( s.find("May") < s.size() ) month = 5; - if( s.find("Jun") < s.size() ) month = 6; - if( s.find("Jul") < s.size() ) month = 7; - if( s.find("Aug") < s.size() ) month = 8; - if( s.find("Sep") < s.size() ) month = 9; - if( s.find("Oct") < s.size() ) month =10; - if( s.find("Nov") < s.size() ) month =11; - if( s.find("Dec") < s.size() ) month =12; - - char chr = ','; - if(tag == 1){ - chr = ' '; - } - day = stoi(s.substr(s.find_first_of("123456789"), s.find_first_of(chr) - s.find_first_of("123456789"))); - - year = stoi(s.substr(s.find_last_of(' ') + 1, 4)); - break; - } - } - - void print(){ - cout << "day:" << day << " " << "month: " << month << " " << "year: " << year; - } -}; -int main() -{ - My_date d("Jan 1 1900"); - d.print(); - return 0; -} -``` - -## 练习9.52 - -> 使用`stack`处理括号化的表达式。当你看到一个左括号,将其记录下来。当你在一个左括号之后看到一个右括号,从`stack`中`pop`对象,直至遇到左括号,将左括号也一起弹出栈。然后将一个值(括号内的运算结果)`push`到栈中,表示一个括号化的(子)表达式已经处理完毕,被其运算结果所替代。 - -解: - -这道题可以延伸为逆波兰求值,以及中缀转后缀表达式。 - -```cpp -#include -#include -#include - -using std::string; using std::cout; using std::endl; using std::stack; - -int main() -{ - string expression{ "This is (pezy)." }; - bool bSeen = false; - stack stk; - for (const auto &s : expression) - { - if (s == '(') { bSeen = true; continue; } - else if (s == ')') bSeen = false; - - if (bSeen) stk.push(s); - } - - string repstr; - while (!stk.empty()) - { - repstr += stk.top(); - stk.pop(); - } - - expression.replace(expression.find("(")+1, repstr.size(), repstr); - - cout << expression << endl; - - return 0; -} -``` \ No newline at end of file diff --git a/docs/Cpp-Primer-exercise/README.md b/docs/Cpp-Primer-exercise/README.md deleted file mode 100644 index 208ce9e..0000000 --- a/docs/Cpp-Primer-exercise/README.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -sort: 2 ---- - -# C++ Primer 第5版练习题解答 - -{% include list.liquid %} - - diff --git a/docs/Cpp-Primer-exercise/Resource/chp01/1.1.cpp b/docs/Cpp-Primer-exercise/Resource/chp01/1.1.cpp deleted file mode 100644 index 286e3db..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp01/1.1.cpp +++ /dev/null @@ -1,4 +0,0 @@ -int main() { - - return 0; -} diff --git a/docs/Cpp-Primer-exercise/Resource/chp01/1.10.cpp b/docs/Cpp-Primer-exercise/Resource/chp01/1.10.cpp deleted file mode 100644 index 382c4cf..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp01/1.10.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include - -int main() { - int i = 10; - while (i >= 0) { - std::cout << i << std::endl; - i--; - } -} diff --git a/docs/Cpp-Primer-exercise/Resource/chp01/1.11.cpp b/docs/Cpp-Primer-exercise/Resource/chp01/1.11.cpp deleted file mode 100644 index 70d28d0..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp01/1.11.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#include - -int main() { - int temp = 0; - std::cout << "Please enter two numbers"<< std::endl; - int v1 = 0, v2 = 0; - std::cin >> v1 >> v2; - // 若v1 > v2,则调换顺序 - if (v1 > v2) { - temp = v2; - v2 = v1; - v1 = temp; - } - - while (v1 <= v2){ - std::cout << v1 << std::endl; - v1 += 1; - } -} diff --git a/docs/Cpp-Primer-exercise/Resource/chp01/1.12.cpp b/docs/Cpp-Primer-exercise/Resource/chp01/1.12.cpp deleted file mode 100644 index a75c810..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp01/1.12.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include - -int main() { - int sum = 0; - for (int i = -100; i <= 100; ++i) { - sum += i; - } - std::cout << "sum is " << sum << std::endl; -} \ No newline at end of file diff --git a/docs/Cpp-Primer-exercise/Resource/chp01/1.13-1.cpp b/docs/Cpp-Primer-exercise/Resource/chp01/1.13-1.cpp deleted file mode 100644 index 3da3046..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp01/1.13-1.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include -// 修改1.9 -int main() { - int sum = 0; - for (int i = 50; i <= 100; i++) { - sum += i; - } - std::cout << "sum is " << sum << std::endl; -} \ No newline at end of file diff --git a/docs/Cpp-Primer-exercise/Resource/chp01/1.13-2.cpp b/docs/Cpp-Primer-exercise/Resource/chp01/1.13-2.cpp deleted file mode 100644 index 4ccbb29..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp01/1.13-2.cpp +++ /dev/null @@ -1,7 +0,0 @@ -#include - -int main() { - for (int i = 10; i >= 0; i--) { - std::cout << i << std::endl; - } -} diff --git a/docs/Cpp-Primer-exercise/Resource/chp01/1.13-3.cpp b/docs/Cpp-Primer-exercise/Resource/chp01/1.13-3.cpp deleted file mode 100644 index 91fbbc7..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp01/1.13-3.cpp +++ /dev/null @@ -1,18 +0,0 @@ -#include - -int main() { - int temp = 0; - std::cout << "Please enter two numbers"<< std::endl; - int v1 = 0, v2 = 0; - std::cin >> v1 >> v2; - // 若v1 > v2,则调换顺序 - if (v1 > v2) { - temp = v2; - v2 = v1; - v1 = temp; - } - - for (;v1 <= v2;v1++){ - std::cout << v1 << std::endl; - } -} diff --git a/docs/Cpp-Primer-exercise/Resource/chp01/1.16.cpp b/docs/Cpp-Primer-exercise/Resource/chp01/1.16.cpp deleted file mode 100644 index e2f284f..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp01/1.16.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include - -int main() { - int sum = 0, value = 0; - while(std::cin >> value) { - sum += value; - } - std::cout << "sum is " << sum << std::endl; - return 0; -} diff --git a/docs/Cpp-Primer-exercise/Resource/chp01/1.18.cpp b/docs/Cpp-Primer-exercise/Resource/chp01/1.18.cpp deleted file mode 100644 index 5daa9e6..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp01/1.18.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include - -int main() -{ - int currVal = 0, val = 0; - - if (std::cin >> currVal) - { - int cnt = 1; - while (std::cin >> val) - { - if (val == currVal) - { - ++cnt; - } - else - { - std::cout << currVal << " occurs " << cnt << " times" << std::endl; - currVal = val; - cnt = 1; - } - } - std::cout << currVal << " occurs " << cnt << " times" << std::endl; - } - return 0; -} \ No newline at end of file diff --git a/docs/Cpp-Primer-exercise/Resource/chp01/1.19.cpp b/docs/Cpp-Primer-exercise/Resource/chp01/1.19.cpp deleted file mode 100644 index df484c8..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp01/1.19.cpp +++ /dev/null @@ -1,20 +0,0 @@ -#include - -int main() { - int temp = 0; - std::cout << "Please enter two numbers"<< std::endl; - int v1 = 0, v2 = 0; - std::cin >> v1 >> v2; - // 若v1 > v2,则调换顺序 - if (v1 > v2) { - temp = v2; - v2 = v1; - v1 = temp; - } - - while (v1 <= v2){ - std::cout << v1 << std::endl; - v1 += 1; - } - return 0; -} diff --git a/docs/Cpp-Primer-exercise/Resource/chp01/1.2.cpp b/docs/Cpp-Primer-exercise/Resource/chp01/1.2.cpp deleted file mode 100644 index 936f79a..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp01/1.2.cpp +++ /dev/null @@ -1,5 +0,0 @@ -#include - -int main() { - return -1; -} diff --git a/docs/Cpp-Primer-exercise/Resource/chp01/1.20.cpp b/docs/Cpp-Primer-exercise/Resource/chp01/1.20.cpp deleted file mode 100644 index 11d8f79..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp01/1.20.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include -#include "Sales_item.h" - -int main() { - - for(Sales_item item;std::cin >> item; std::cout << item << std::endl); - - return 0; -} diff --git a/docs/Cpp-Primer-exercise/Resource/chp01/1.21.cpp b/docs/Cpp-Primer-exercise/Resource/chp01/1.21.cpp deleted file mode 100644 index 0dd0e9a..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp01/1.21.cpp +++ /dev/null @@ -1,16 +0,0 @@ -#include -#include "Sales_item.h" - -int main() { - Sales_item item1; - Sales_item item2; - - std::cin >> item1; - std::cin >> item2; - - if (item1.isbn() == item2.isbn()) { - std::cout << item1 + item2 << std::endl; - } - - return 0; -} diff --git a/docs/Cpp-Primer-exercise/Resource/chp01/1.22.cpp b/docs/Cpp-Primer-exercise/Resource/chp01/1.22.cpp deleted file mode 100644 index d20071f..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp01/1.22.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include -#include "Sales_item.h" - -int main() { - Sales_item sum_item; - - for (Sales_item item; std::cin >> item; std::cout << item << std::endl) { - sum_item += item; - } - - std::cout << "sum is " << sum_item << std::endl; - return 0; -} diff --git a/docs/Cpp-Primer-exercise/Resource/chp01/1.23.cpp b/docs/Cpp-Primer-exercise/Resource/chp01/1.23.cpp deleted file mode 100644 index 203ac24..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp01/1.23.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include -#include "Sales_item.h" - -int main() { - Sales_item item1, item2; - int num = 1; - std::cout << "请输入销售记录" << std::endl; - - if (std::cin >> item1) { - while (std::cin >> item2) { - if (compareIsbn(item1, item2)) { //相同 - num++; - } - else { - std::cout << item1.isbn() << "共有 " << num << "条销售记录" << std::endl; - item1 = item2; - num = 1; - } - } - std::cout << item1.isbn() << "共有 " << num << "条销售记录" << std::endl; - } - else { - std::cerr << "没有数据" < - -int main() { - std::cout<< "Hello, World" < - -int main() { - std::cout << "Enter two numbers" << std::endl; - int v1 = 0, v2 = 0; - std::cin>> v1 >> v2; - std::cout << "The product of " << v1 << " and " << v2 << " is "<< v1 * v2 << std::endl; -} diff --git a/docs/Cpp-Primer-exercise/Resource/chp01/1.5.cpp b/docs/Cpp-Primer-exercise/Resource/chp01/1.5.cpp deleted file mode 100644 index 59b5973..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp01/1.5.cpp +++ /dev/null @@ -1,15 +0,0 @@ -#include - -int main() -{ - std::cout << "Enter two numbers" << std::endl; - int v1 = 0, v2 = 0; - std::cin >> v1 >> v2; - std::cout << "The product of "; - std::cout << v1; - std::cout << " and "; - std::cout << v2; - std::cout << " is "; - std::cout << v1 * v2; - std::cout << std::endl; -} \ No newline at end of file diff --git a/docs/Cpp-Primer-exercise/Resource/chp01/1.6.cpp b/docs/Cpp-Primer-exercise/Resource/chp01/1.6.cpp deleted file mode 100644 index 28315b9..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp01/1.6.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include - -int main() { - std::cout << "Enter two numbers" << std::endl; - int v1 = 0, v2 = 0; - std::cin>> v1 >> v2; - std::cout << "The sum of " << v1 - << " and " << v2 - << " is "<< v1 + v2 << std::endl; -} \ No newline at end of file diff --git a/docs/Cpp-Primer-exercise/Resource/chp01/1.7.cpp b/docs/Cpp-Primer-exercise/Resource/chp01/1.7.cpp deleted file mode 100644 index ae7b51b..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp01/1.7.cpp +++ /dev/null @@ -1,5 +0,0 @@ -#include - -int main() { -/* 正常注释 /* 嵌套注释 */ 正常注释*/ -} \ No newline at end of file diff --git a/docs/Cpp-Primer-exercise/Resource/chp01/1.8.cpp b/docs/Cpp-Primer-exercise/Resource/chp01/1.8.cpp deleted file mode 100644 index f45c7d8..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp01/1.8.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include - -int main() { - std::cout << "/*"; - std::cout << "*/"; - std::cout << /* "*/ " */"; - std::cout << /* "*/" /* "/*" */; -} diff --git a/docs/Cpp-Primer-exercise/Resource/chp01/1.9.cpp b/docs/Cpp-Primer-exercise/Resource/chp01/1.9.cpp deleted file mode 100644 index bf35e9b..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp01/1.9.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include - -int main() { - int i = 50, sum = 0; - while (i <= 100) { - sum += i; - i += 1; - } - std::cout << "sum is " << sum << std::endl; -} diff --git a/docs/Cpp-Primer-exercise/Resource/chp01/Sales_item.h b/docs/Cpp-Primer-exercise/Resource/chp01/Sales_item.h deleted file mode 100644 index 4ba7ad0..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp01/Sales_item.h +++ /dev/null @@ -1,154 +0,0 @@ -/* - * This file contains code from "C++ Primer, Fifth Edition", by Stanley B. - * Lippman, Josee Lajoie, and Barbara E. Moo, and is covered under the - * copyright and warranty notices given in that book: - * - * "Copyright (c) 2013 by Objectwrite, Inc., Josee Lajoie, and Barbara E. Moo." - * - * - * "The authors and publisher have taken care in the preparation of this book, - * but make no expressed or implied warranty of any kind and assume no - * responsibility for errors or omissions. No liability is assumed for - * incidental or consequential damages in connection with or arising out of the - * use of the information or programs contained herein." - * - * Permission is granted for this code to be used for educational purposes in - * association with the book, given proper citation if and when posted or - * reproduced.Any commercial use of this code requires the explicit written - * permission of the publisher, Addison-Wesley Professional, a division of - * Pearson Education, Inc. Send your request for permission, stating clearly - * what code you would like to use, and in what specific way, to the following - * address: - * - * Pearson Education, Inc. - * Rights and Permissions Department - * One Lake Street - * Upper Saddle River, NJ 07458 - * Fax: (201) 236-3290 -*/ - -/* This file defines the Sales_item class used in chapter 1. - * The code used in this file will be explained in - * Chapter 7 (Classes) and Chapter 14 (Overloaded Operators) - * Readers shouldn't try to understand the code in this file - * until they have read those chapters. -*/ - -#ifndef SALESITEM_H -// we're here only if SALESITEM_H has not yet been defined -#define SALESITEM_H - -#include "Version_test.h" - -// Definition of Sales_item class and related functions goes here -#include -#include - -class Sales_item { -// these declarations are explained section 7.2.1, p. 270 -// and in chapter 14, pages 557, 558, 561 - friend std::istream& operator>>(std::istream&, Sales_item&); - friend std::ostream& operator<<(std::ostream&, const Sales_item&); - friend bool operator<(const Sales_item&, const Sales_item&); - friend bool - operator==(const Sales_item&, const Sales_item&); -public: - // constructors are explained in section 7.1.4, pages 262 - 265 - // default constructor needed to initialize members of built-in type -#if defined(IN_CLASS_INITS) && defined(DEFAULT_FCNS) - Sales_item() = default; -#else - Sales_item(): units_sold(0), revenue(0.0) { } -#endif - Sales_item(const std::string &book): - bookNo(book), units_sold(0), revenue(0.0) { } - Sales_item(std::istream &is) { is >> *this; } -public: - // operations on Sales_item objects - // member binary operator: left-hand operand bound to implicit this pointer - Sales_item& operator+=(const Sales_item&); - - // operations on Sales_item objects - std::string isbn() const { return bookNo; } - double avg_price() const; -// private members as before -private: - std::string bookNo; // implicitly initialized to the empty string -#ifdef IN_CLASS_INITS - unsigned units_sold = 0; // explicitly initialized - double revenue = 0.0; -#else - unsigned units_sold; - double revenue; -#endif -}; - -// used in chapter 10 -inline -bool compareIsbn(const Sales_item &lhs, const Sales_item &rhs) -{ return lhs.isbn() == rhs.isbn(); } - -// nonmember binary operator: must declare a parameter for each operand -Sales_item operator+(const Sales_item&, const Sales_item&); - -inline bool -operator==(const Sales_item &lhs, const Sales_item &rhs) -{ - // must be made a friend of Sales_item - return lhs.units_sold == rhs.units_sold && - lhs.revenue == rhs.revenue && - lhs.isbn() == rhs.isbn(); -} - -inline bool -operator!=(const Sales_item &lhs, const Sales_item &rhs) -{ - return !(lhs == rhs); // != defined in terms of operator== -} - -// assumes that both objects refer to the same ISBN -Sales_item& Sales_item::operator+=(const Sales_item& rhs) -{ - units_sold += rhs.units_sold; - revenue += rhs.revenue; - return *this; -} - -// assumes that both objects refer to the same ISBN -Sales_item -operator+(const Sales_item& lhs, const Sales_item& rhs) -{ - Sales_item ret(lhs); // copy (|lhs|) into a local object that we'll return - ret += rhs; // add in the contents of (|rhs|) - return ret; // return (|ret|) by value -} - -std::istream& -operator>>(std::istream& in, Sales_item& s) -{ - double price; - in >> s.bookNo >> s.units_sold >> price; - // check that the inputs succeeded - if (in) - s.revenue = s.units_sold * price; - else - s = Sales_item(); // input failed: reset object to default state - return in; -} - -std::ostream& -operator<<(std::ostream& out, const Sales_item& s) -{ - out << s.isbn() << " " << s.units_sold << " " - << s.revenue << " " << s.avg_price(); - return out; -} - -double Sales_item::avg_price() const -{ - if (units_sold) - return revenue/units_sold; - else - return 0; -} -#endif \ No newline at end of file diff --git a/docs/Cpp-Primer-exercise/Resource/chp01/Version_test.h b/docs/Cpp-Primer-exercise/Resource/chp01/Version_test.h deleted file mode 100644 index 0d25d1f..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp01/Version_test.h +++ /dev/null @@ -1,80 +0,0 @@ -/* - * This file contains code from "C++ Primer, Fifth Edition", by Stanley B. - * Lippman, Josee Lajoie, and Barbara E. Moo, and is covered under the - * copyright and warranty notices given in that book: - * - * "Copyright (c) 2013 by Objectwrite, Inc., Josee Lajoie, and Barbara E. Moo." - * - * - * "The authors and publisher have taken care in the preparation of this book, - * but make no expressed or implied warranty of any kind and assume no - * responsibility for errors or omissions. No liability is assumed for - * incidental or consequential damages in connection with or arising out of the - * use of the information or programs contained herein." - * - * Permission is granted for this code to be used for educational purposes in - * association with the book, given proper citation if and when posted or - * reproduced. Any commercial use of this code requires the explicit written - * permission of the publisher, Addison-Wesley Professional, a division of - * Pearson Education, Inc. Send your request for permission, stating clearly - * what code you would like to use, and in what specific way, to the following - * address: - * - * Pearson Education, Inc. - * Rights and Permissions Department - * One Lake Street - * Upper Saddle River, NJ 07458 - * Fax: (201) 236-3290 -*/ - -#ifndef VERSION_TEST_H -#define VERSION_TEST_H - -/* As of the first printing of C++ Primer, 5th Edition (July 2012), - * the Microsoft Complier did not yet support a number of C++ 11 features. - * - * The code we distribute contains both normal C++ code and - * workarounds for missing features. We use a series of CPP variables to - * determine whether a given features is implemented in a given release - * of the MS compiler. The base version we used to test the code in the book - * is Compiler Version 17.00.50522.1 for x86. - * - * When new releases are available we will update this file which will - * #define the features implmented in that release. -*/ - -#if _MSC_FULL_VER == 170050522 || _MSC_FULL_VER == 170050727 -// base version, future releases will #define those features as they are -// implemented by Microsoft - -/* Code in this delivery use the following variables to control compilation - - Variable tests C++ 11 Feature -CONSTEXPR_VARS constexpr variables -CONSTEXPR_FCNS constexpr functions -CONSTEXPR_CTORS constexpr constructors and other member functions -DEFAULT_FCNS = default -DELETED_FCNS = delete -FUNC_CPP __func__ local static -FUNCTION_PTRMEM function template with pointer to member function -IN_CLASS_INITS in class initializers -INITIALIZER_LIST library initializer_list template -LIST_INIT list initialization of ordinary variables -LROUND lround function in cmath -NOEXCEPT noexcept specifier and noexcept operator -SIZEOF_MEMBER sizeof class_name::member_name -TEMPLATE_FCN_DEFAULT_ARGS default template arguments for function templates -TYPE_ALIAS_DECLS type alias declarations -UNION_CLASS_MEMS unions members that have constructors or copy control -VARIADICS variadic templates -*/ -#endif // ends compiler version check - -#ifndef LROUND -inline long lround(double d) -{ - return (d >= 0) ? long(d + 0.5) : long(d - 0.5); -} -#endif - -#endif // ends header guard diff --git a/docs/Cpp-Primer-exercise/Resource/chp02/2.18.cpp b/docs/Cpp-Primer-exercise/Resource/chp02/2.18.cpp deleted file mode 100644 index e4aad5d..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp02/2.18.cpp +++ /dev/null @@ -1,15 +0,0 @@ -#include - - -int main() { - int i = 1, j = 2; - int *p = &i; - std::cout << p << " " << *p << std::endl; - p = &j; //更改指针的值,令p指向另一个整数对象j - std::cout << p << " " << *p << std::endl; - *p = 10; // 显式改变指针p指向的内容 - std::cout << p << " " << *p << std::endl; - j = 20; // 通过改变变量j的值 - std::cout << p << " " << *p << std::endl; - return 0; -} diff --git a/docs/Cpp-Primer-exercise/Resource/chp02/2.34.cpp b/docs/Cpp-Primer-exercise/Resource/chp02/2.34.cpp deleted file mode 100644 index bf1a4c0..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp02/2.34.cpp +++ /dev/null @@ -1,17 +0,0 @@ -#include - - -int main() { - int i = 0, &r = i; - auto a = r; - const int ci = i, &cr = ci; - auto b = ci; - auto c = cr; - auto d = &i; - auto e = &ci; - auto &g = ci; - - std::cout << a << " "<< b << " " << c << " " << d << " " << e << " " << g << " " << std::endl; - - return 0; -} diff --git a/docs/Cpp-Primer-exercise/Resource/chp02/2.35.cpp b/docs/Cpp-Primer-exercise/Resource/chp02/2.35.cpp deleted file mode 100644 index 6dca28c..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp02/2.35.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#include - - -int main() { - const int i = 42; - auto j = i; - const auto &k = i; - auto *p = &i; - const auto j2 = i, &k2 = i; - - std::cout << typeid(i).name() << std::endl; - std::cout << typeid(j).name() << std::endl; - std::cout << typeid(k).name() << std::endl; - std::cout << typeid(p).name() << std::endl; - std::cout << typeid(j2).name() << std::endl; - std::cout << typeid(k2).name() << std::endl; - - return 0; -} diff --git a/docs/Cpp-Primer-exercise/Resource/chp02/2.38.cpp b/docs/Cpp-Primer-exercise/Resource/chp02/2.38.cpp deleted file mode 100644 index aeb4d7e..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp02/2.38.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include - - -int main() { - int a = 1; - auto c1 = a; - decltype(a) c2 = a; - decltype((a)) c3 = a; - - const int d = 3; - auto f1 = d; - decltype(d) f2 = d; - - std::cout << typeid(c1).name() << std::endl; - std::cout << typeid(c2).name() << std::endl; - std::cout << typeid(c3).name() << std::endl; - std::cout << typeid(f1).name() << std::endl; - std::cout << typeid(f2).name() << std::endl; - - c1++; - c2++; - c3++; - f1++; -// f2++; f2是整型变量,不能执行自增操作 - - std::cout << c1 << " " << c2 << " "<< c3 << " "<< f1 << " "<< f2 << " " << std::endl; - return 0; -} diff --git a/docs/Cpp-Primer-exercise/Resource/chp02/2.4.cpp b/docs/Cpp-Primer-exercise/Resource/chp02/2.4.cpp deleted file mode 100644 index ac41d00..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp02/2.4.cpp +++ /dev/null @@ -1,14 +0,0 @@ -#include - - -int main() { - unsigned u = 10, u2 = 42; - std::cout << u2 - u << std::endl; - std::cout << u - u2 << std::endl; - int i = 10, i2 = 42; - std::cout << i2 - i << std::endl; - std::cout << i - i2 << std::endl; - std::cout << i - u << std::endl; - std::cout << u - i << std::endl; - return 0; -} diff --git a/docs/Cpp-Primer-exercise/Resource/chp02/2.41.cpp b/docs/Cpp-Primer-exercise/Resource/chp02/2.41.cpp deleted file mode 100644 index aeb4d7e..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp02/2.41.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include - - -int main() { - int a = 1; - auto c1 = a; - decltype(a) c2 = a; - decltype((a)) c3 = a; - - const int d = 3; - auto f1 = d; - decltype(d) f2 = d; - - std::cout << typeid(c1).name() << std::endl; - std::cout << typeid(c2).name() << std::endl; - std::cout << typeid(c3).name() << std::endl; - std::cout << typeid(f1).name() << std::endl; - std::cout << typeid(f2).name() << std::endl; - - c1++; - c2++; - c3++; - f1++; -// f2++; f2是整型变量,不能执行自增操作 - - std::cout << c1 << " " << c2 << " "<< c3 << " "<< f1 << " "<< f2 << " " << std::endl; - return 0; -} diff --git a/docs/Cpp-Primer-exercise/Resource/chp02/2.42.cpp b/docs/Cpp-Primer-exercise/Resource/chp02/2.42.cpp deleted file mode 100644 index 597d90f..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp02/2.42.cpp +++ /dev/null @@ -1,29 +0,0 @@ -#include -#include "Sales_data.h" - -int main() { - Sales_data item1, item2; - int num = 1; - std::cout << "请输入销售记录" << std::endl; - - if (std::cin >> item1) { - while (std::cin >> item2) { - if (compareIsbn(item1, item2)) { //相同 - num++; - } - else { - std::cout << item1.isbn() << "共有 " << num << "条销售记录" << std::endl; - item1 = item2; - num = 1; - } - } - std::cout << item1.isbn() << "共有 " << num << "条销售记录" << std::endl; - } - else { - std::cerr << "没有数据" < - - -int main() { - unsigned u = 10, u2 = 42; - std::cout << u2 - u << std::endl; - std::cout << u - u2 << std::endl; - int i = 10, i2 = 42; - std::cout << i2 - i << std::endl; - std::cout << i - i2 << std::endl; - std::cout << i - u << std::endl; - std::cout << u - i << std::endl; - return 0; -} diff --git a/docs/Cpp-Primer-exercise/Resource/chp02/2.8.cpp b/docs/Cpp-Primer-exercise/Resource/chp02/2.8.cpp deleted file mode 100644 index 37ae530..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp02/2.8.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include - - -int main() { - std::cout << "2\x4d\012"; - std::cout <<"2\tM\n"; - return 0; -} diff --git a/docs/Cpp-Primer-exercise/Resource/chp02/Sales_data.h b/docs/Cpp-Primer-exercise/Resource/chp02/Sales_data.h deleted file mode 100644 index 430ca5f..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp02/Sales_data.h +++ /dev/null @@ -1,72 +0,0 @@ -// -// Created by ChenYL on 2022/5/14. -// - -#ifndef PAT_SALES_DATA_H -#define PAT_SALES_DATA_H -# include -# include - -class Sales_data { -friend std::istream& operator >> (std::istream&, Sales_data&); -friend std::ostream& operator >> (std::ostream&, const Sales_data&); -friend bool operator < (const Sales_data&, const Sales_data&); -friend bool operator == (const Sales_data&, const Sales_data&); - -public: - Sales_data() = default; - Sales_data(const std::string &book):bookNo(book) { } - Sales_data(std::istream &is) {is >> *this;} - -public: - Sales_data& operator += (const Sales_data&); - std::string isbn() const { return bookNo;} - -private: - std::string bookNo; //书籍编号 - unsigned units_sold = 0; //销售量 - double selling_price = 0.0; //原始价格 - double sale_price =0.0; //市售价格 - double discount = 0.0; //折扣 -}; - -inline bool compareIsbn(const Sales_data &lhs, const Sales_data &rhs) { - return lhs.isbn() == rhs.isbn(); -} - -inline bool operator !=(const Sales_data &lhs, const Sales_data &rhs) { - return !(lhs == rhs); //基于运算符==给出!=的定义 -} - -Sales_data& Sales_data::operator+=(const Sales_data& rhs) { - units_sold += rhs.units_sold; - sale_price = (rhs.sale_price * rhs.units_sold + sale_price * units_sold) / (rhs.units_sold + units_sold); - if (selling_price != 0) - discount = sale_price / selling_price; - return *this; -} - -Sales_data operator + (const Sales_data &lhs, const Sales_data &rhs) -{ - Sales_data ret(lhs);//把hs的内容拷贝到临时变量ret中,这种做法便于运算 - ret += rhs; //把rhs的内容加入其中 - return ret; //返回ret -} - -std::istream& operator >> (std::istream& in, Sales_data& s) { - in >> s.bookNo >> s.units_sold >> s.selling_price >> s.sale_price; - if (in && s.selling_price != 0) - s.discount = s.sale_price / s.selling_price; - else - s = Sales_data();//输入错误,重置输入的数据 - return in; -} - -std::ostream& operator << (std::ostream& out,const Sales_data& s) -{ - out << s.isbn(); -// out << s.isbn() <<" " << s.units_sold << " "<< s.selling_price <<""<< s.sale_price <<""<< s.discount; - return out; -} - -#endif //PAT_SALES_DATA_H diff --git a/docs/Cpp-Primer-exercise/Resource/chp03/3.1.cpp b/docs/Cpp-Primer-exercise/Resource/chp03/3.1.cpp deleted file mode 100644 index 2285ce3..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp03/3.1.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include -using std::cout; -using std::endl; -int main() { - int i = 50, sum = 0; - while (i <= 100) { - sum += i; - i += 1; - } - cout << "sum is " << sum << endl; -} diff --git a/docs/Cpp-Primer-exercise/Resource/chp03/3.10.cpp b/docs/Cpp-Primer-exercise/Resource/chp03/3.10.cpp deleted file mode 100644 index 811a54a..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp03/3.10.cpp +++ /dev/null @@ -1,16 +0,0 @@ -#include -#include - -using namespace std; - -int main() { - string s; - cout << "请输入一个字符串" < -#include -#include - -using namespace std; -using std::vector; - -int main() { - vector v; - int i; - char cont = 'y'; - cout << "请输入数字" << endl; - while(cin >> i) { - v.push_back(i); - cout << "是否继续(y or n)" << endl; - cin >> cont; - if (cont == 'y' || cont == 'Y'){ - cout << "请输入下一个整数" << endl; - }else { - break; - } - } - for (auto &m : v) - cout << "输入的整数为" < -#include -#include - -using namespace std; -using std::vector; - -int main() { - vector v; - string i; - char cont = 'y'; - cout << "请输入字符串" << endl; - while(cin >> i) { - v.push_back(i); - cout << "是否继续(y or n)" << endl; - cin >> cont; - if (cont == 'y' || cont == 'Y'){ - cout << "请输入下一个字符串" << endl; - }else { - break; - } - } - for (auto &m : v) - cout << m << " "; - cout << endl; - return 0; -} diff --git a/docs/Cpp-Primer-exercise/Resource/chp03/3.16.cpp b/docs/Cpp-Primer-exercise/Resource/chp03/3.16.cpp deleted file mode 100644 index 4612684..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp03/3.16.cpp +++ /dev/null @@ -1,74 +0,0 @@ -#include -#include -#include - -using namespace std; - -int main() -{ - vector v1; // size:0, no values. - vector v2(10); // size:10, value:0 - vector v3(10, 42); // size:10, value:42 - vector v4{ 10 }; // size:1, value:10 - vector v5{ 10, 42 }; // size:2, value:10, 42 - vector v6{ 10 }; // size:10, value:"" - vector v7{ 10, "hi" }; // size:10, value:"hi" - - cout << "v1 size :" << v1.size() << endl; - cout << "v2 size :" << v2.size() << endl; - cout << "v3 size :" << v3.size() << endl; - cout << "v4 size :" << v4.size() << endl; - cout << "v5 size :" << v5.size() << endl; - cout << "v6 size :" << v6.size() << endl; - cout << "v7 size :" << v7.size() << endl; - - cout << "v1 content: "; - for (auto i : v1) - { - cout << i << " , "; - } - cout << endl; - - cout << "v2 content: "; - for (auto i : v2) - { - cout << i << " , "; - } - cout << endl; - - cout << "v3 content: "; - for (auto i : v3) - { - cout << i << " , "; - } - cout << endl; - - cout << "v4 content: "; - for (auto i : v4) - { - cout << i << " , "; - } - cout << endl; - - cout << "v5 content: "; - for (auto i : v5) - { - cout << i << " , "; - } - cout << endl; - - cout << "v6 content: "; - for (auto i : v6) - { - cout << i << " , "; - } - cout << endl; - - cout << "v7 content: "; - for (auto i : v7) - { - cout << i << " , "; - } - cout << endl; - return 0; -} \ No newline at end of file diff --git a/docs/Cpp-Primer-exercise/Resource/chp03/3.17.cpp b/docs/Cpp-Primer-exercise/Resource/chp03/3.17.cpp deleted file mode 100644 index f2294a6..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp03/3.17.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include -#include -#include - -using namespace std; -using std::vector; - -int main() { - vector v; - string i; - char cont = 'y'; - cout << "请输入字符串" << endl; - while (cin >> i) { - v.push_back(i); - cout << "是否继续(y or n)" << endl; - cin >> cont; - if (cont == 'y' || cont == 'Y') { - cout << "请输入下一个字符串" << endl; - } else { - break; - } - } - for (auto &m: v) { - for (auto &c: m){ - c = toupper(c); - } - cout << m << " "; - cout << endl; - } - return 0; -} \ No newline at end of file diff --git a/docs/Cpp-Primer-exercise/Resource/chp03/3.2.cpp b/docs/Cpp-Primer-exercise/Resource/chp03/3.2.cpp deleted file mode 100644 index 8d5f971..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp03/3.2.cpp +++ /dev/null @@ -1,18 +0,0 @@ -#include -#include - -using namespace std; - -int main() { -// string line; -// cout << "请输入字符串" <> word) { // 读入一个词 - cout << word << endl; - } - - return 0; -} diff --git a/docs/Cpp-Primer-exercise/Resource/chp03/3.20.cpp b/docs/Cpp-Primer-exercise/Resource/chp03/3.20.cpp deleted file mode 100644 index 1cae110..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp03/3.20.cpp +++ /dev/null @@ -1,37 +0,0 @@ -#include -#include -#include - -using namespace std; -using std::vector; - -int main() { - vector v; - int w; - cout << "请输入数字" << endl; - while (cin >> w) { - v.push_back(w); - } - if (v.size() == 0) { - cout << "没有任何元素" < -#include -#include - -using namespace std; - -int main() -{ - vector v1; // size:0, no values. - vector v2(10); // size:10, value:0 - vector v3(10, 42); // size:10, value:42 - vector v4{ 10 }; // size:1, value:10 - vector v5{ 10, 42 }; // size:2, value:10, 42 - vector v6{ 10 }; // size:10, value:"" - vector v7{ 10, "hi" }; // size:10, value:"hi" - - cout << "v1 size :" << v1.size() << endl; - cout << "v2 size :" << v2.size() << endl; - cout << "v3 size :" << v3.size() << endl; - cout << "v4 size :" << v4.size() << endl; - cout << "v5 size :" << v5.size() << endl; - cout << "v6 size :" << v6.size() << endl; - cout << "v7 size :" << v7.size() << endl; - - cout << "v1 content: "; - if (v1.cbegin() != v1.cend()) { - for (auto it = v1.cbegin(); it != v1.cend(); it++) { - cout << *it << " , "; - } - cout << endl; - } - cout << "v2 content: "; - if (v2.cbegin() != v2.cend()) { - for (auto it = v2.cbegin(); it != v2.cend(); it++) { - cout << *it << " , "; - } - cout << endl; - } - - cout << "v3 content: "; - if (v3.cbegin()!= v3.cend()) { - for (auto it = v3.cbegin(); it != v3.cend(); it++){ - cout << *it << " , "; - } - cout << endl; - } - - - - cout << "v4 content: "; - if (v4.cbegin() != v4.cend()) { - for(auto it = v4.cbegin(); it != v4.cend(); it++) { - cout << *it << ","; - } - cout << endl; - } - - cout << "v5 content: "; - if (v5.cbegin() != v5.cend()) { - for(auto it = v5.cbegin(); it != v5.cend(); it++) { - cout << *it << ","; - } - cout << endl; - } - - - cout << "v6 content: "; - if (v6.cbegin() != v6.cend()) { - for(auto it = v6.cbegin(); it != v6.cend(); it++) { - cout << *it << ","; - } - cout << endl; - } - - - cout << "v7 content: "; - if (v7.cbegin() != v7.cend()) { - for(auto it = v7.cbegin(); it != v7.cend(); it++) { - cout << *it << ","; - } - cout << endl; - } - - return 0; -} \ No newline at end of file diff --git a/docs/Cpp-Primer-exercise/Resource/chp03/3.22.cpp b/docs/Cpp-Primer-exercise/Resource/chp03/3.22.cpp deleted file mode 100644 index e66a6a6..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp03/3.22.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include -#include -#include - -using namespace std; -using std::vector; - -int main() { - vector v; - string w; - cout << "请输入一段话" << endl; - - while (getline(cin, w)) { //利用getline读取一句话,直接回车产生一个空串,表示段落结束 - v.push_back(w); - } - for(auto it = v.begin(); it != v.end() && !it -> empty();it++) { - for (auto it2 = it->begin(); it2 != it->end(); it2++){ - *it2 = toupper(*it2); - } - cout << *it << endl; - } - return 0; -} \ No newline at end of file diff --git a/docs/Cpp-Primer-exercise/Resource/chp03/3.23.cpp b/docs/Cpp-Primer-exercise/Resource/chp03/3.23.cpp deleted file mode 100644 index b5a531b..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp03/3.23.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include -#include -#include - -using namespace std; -using std::vector; - -int main() { - - vector words; - for (int i = 0; i < 10; i++) { - words.push_back(rand() % 1000); - } - cout << "随机生成的数字是:" < -#include -#include - -using namespace std; -using std::vector; - -int main() { - vector v; - int w; - cout << "请输入数字" << endl; - while (cin >> w) { - v.push_back(w); - } - if (v.cbegin() == v.cend()) { - cout << "没有任何元素" < -#include -#include - -using namespace std; -using std::vector; - -int main() { - vector v(11); - auto it = v.begin(); - int i; - cout << "请输入一组成绩"<> i) { - if (i < 101) { - ++*(it + i / 10); - } - } - cout << "各分数段的人数分布式" << endl; - for (it = v.begin(); it!= v.end();it++) { - cout << *it << " "; - } - cout << endl; - return 0; -} \ No newline at end of file diff --git a/docs/Cpp-Primer-exercise/Resource/chp03/3.31.cpp b/docs/Cpp-Primer-exercise/Resource/chp03/3.31.cpp deleted file mode 100644 index ef5d555..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp03/3.31.cpp +++ /dev/null @@ -1,20 +0,0 @@ -#include -#include -#include - -using namespace std; -using std::vector; - -int main() { - const int sz = 10; - int num[sz]; - for (int i = 0; i < sz; i++) { - num[i] = i; - } - - for (auto val : num) { - cout << val << " "; - } - cout << endl; - return 0; -} \ No newline at end of file diff --git a/docs/Cpp-Primer-exercise/Resource/chp03/3.32.cpp b/docs/Cpp-Primer-exercise/Resource/chp03/3.32.cpp deleted file mode 100644 index 23ac882..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp03/3.32.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include -#include -#include - -using namespace std; -using std::vector; - -int main() { - const int sz = 10; - vector num3; - vector num4; - int num1[sz], num2[sz]; - for (int i = 0; i < sz; i++) { - num1[i] = i; - } - for (int i = 0; i < sz; i++) { - num2[i] = num1[i]; - } - cout << "num2中的值为" << endl; - for (auto val : num2) { - cout << val << " "; - } - cout << endl; - - for (int i = 0; i < sz; i++) { - num3.push_back(i); - } - for (int i = 0; i < sz; i++) { - num4.push_back(num3[i]); - } - cout << "num4中的值为" << endl; - for (auto val : num4) { - cout << val << " "; - } - cout << endl; - - return 0; -} \ No newline at end of file diff --git a/docs/Cpp-Primer-exercise/Resource/chp03/3.35.cpp b/docs/Cpp-Primer-exercise/Resource/chp03/3.35.cpp deleted file mode 100644 index 0c81f60..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp03/3.35.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include -#include -#include - -using namespace std; -using std::vector; - -int main() { - const int sz = 10; - int nums[sz]; - for (int i = 0; i < sz; i++) { - nums[i] = i; - } - - - for (auto ptr = nums; ptr != nums + sz; ++ptr) *ptr = 0; -// int *p = begin(nums); -// while (p != end(nums)) { -// *p = 0; -// p++; -// } - - for (auto num : nums) { - cout << num << " "; - } - cout << endl; - return 0; -} \ No newline at end of file diff --git a/docs/Cpp-Primer-exercise/Resource/chp03/3.36.cpp b/docs/Cpp-Primer-exercise/Resource/chp03/3.36.cpp deleted file mode 100644 index c1bd98f..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp03/3.36.cpp +++ /dev/null @@ -1,42 +0,0 @@ -#include -#include -#include - -using std::begin; using std::end; using std::cout; using std::endl; using std::vector; - -// pb point to begin of the array, pe point to end of the array. -bool compare(int* const pb1, int* const pe1, int* const pb2, int* const pe2) -{ - if ((pe1 - pb1) != (pe2 - pb2)) // have different size. - return false; - else - { - for (int* i = pb1, *j = pb2; (i != pe1) && (j != pe2); ++i, ++j) - if (*i != *j) return false; - } - - return true; -} - -int main() -{ - int arr1[3] = { 0, 1, 2 }; - int arr2[3] = { 0, 2, 4 }; - - if (compare(begin(arr1), end(arr1), begin(arr2), end(arr2))) - cout << "The two arrays are equal." << endl; - else - cout << "The two arrays are not equal." << endl; - - cout << "==========" << endl; - - vector vec1 = { 0, 1, 2 }; - vector vec2 = { 0, 1, 2 }; - - if (vec1 == vec2) - cout << "The two vectors are equal." << endl; - else - cout << "The two vectors are not equal." << endl; - - return 0; -} \ No newline at end of file diff --git a/docs/Cpp-Primer-exercise/Resource/chp03/3.39.1.cpp b/docs/Cpp-Primer-exercise/Resource/chp03/3.39.1.cpp deleted file mode 100644 index b77c60e..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp03/3.39.1.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#include -#include - -using namespace std; - -int main() -{ - string str1, str2; - cout << "请输入两个字符串" <> str1 >> str2; - - if (str1 > str2) - cout << "第一个字符串大于第二个字符串" << endl; - else if (str1 < str2) - cout << "第一个字符串小于第二个字符串" << endl; - else - cout << "第一个字符串等于第二个字符串" << endl; - return 0; -} \ No newline at end of file diff --git a/docs/Cpp-Primer-exercise/Resource/chp03/3.39.2.cpp b/docs/Cpp-Primer-exercise/Resource/chp03/3.39.2.cpp deleted file mode 100644 index b3d5fa4..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp03/3.39.2.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include -#include - -using namespace std; - -int main() -{ - char str1[80], str2[80]; - - cout << "请输入两个字符串" <> str1 >> str2; - auto result = strcmp(str1, str2); - switch(result) { - case 1: - cout << "第一个字符串大于第二个字符串" << endl; - break; - case -1: - cout << "第一个字符串小于第二个字符串" << endl; - break; - case 0 : - cout << "第一个字符串等于第二个字符串" << endl; - break; - default: - cout << "未定义的结果" << endl; - break; - } - return 0; -} \ No newline at end of file diff --git a/docs/Cpp-Primer-exercise/Resource/chp03/3.4.cpp b/docs/Cpp-Primer-exercise/Resource/chp03/3.4.cpp deleted file mode 100644 index 22d52f5..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp03/3.4.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include -#include - -using namespace std; - -int main() { - string str1, str2; - cout << "请输入两个字符串" << endl; - cin >> str1; - cin >> str2; - - if (str1 == str2) { - cout << "两个字符相等" << endl; - }else if (str1 > str2) { - cout << str1 << endl; - }else { - cout << str2 < len2) - cout << str1 << "比" << str2 << "的长度多" << len1 - len2 << endl; - else - cout << str1 << "比" << str2 << "的长度少" << len2 - len1 << endl; - - - return 0; -} diff --git a/docs/Cpp-Primer-exercise/Resource/chp03/3.40.cpp b/docs/Cpp-Primer-exercise/Resource/chp03/3.40.cpp deleted file mode 100644 index 4f87997..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp03/3.40.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include -#include - -using namespace std; - -int main() -{ - char str1[] = "Welcome to"; - char str2[] = "C++"; - - char result[strlen(str1) + strlen(str2) - 1]; - - strcpy(result, str1); - strcat(result, str2); - - cout << "第一个字符为" << str1 < -#include - -using namespace std; - -int main() -{ - - int nums[] = {1, 2, 3, 4} ; - - vector words(begin(nums), end(nums)); - - for (auto val : words) { - cout << val << " "; - } - cout << endl; - - return 0; -} \ No newline at end of file diff --git a/docs/Cpp-Primer-exercise/Resource/chp03/3.42.cpp b/docs/Cpp-Primer-exercise/Resource/chp03/3.42.cpp deleted file mode 100644 index 6cf4dde..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp03/3.42.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include -#include - -using namespace std; - -int main() -{ - - - const int sz = 10; - vector words; - int nums[10]; - - for (int i = 0; i < sz; i++) { - words.push_back(i); - } - - auto it = words.cbegin(); - for (auto & val : nums) { - val = *it; - cout << val << " "; - it++; - } - cout << endl; - - - return 0; -} \ No newline at end of file diff --git a/docs/Cpp-Primer-exercise/Resource/chp03/3.43.cpp b/docs/Cpp-Primer-exercise/Resource/chp03/3.43.cpp deleted file mode 100644 index 65c62d1..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp03/3.43.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include -#include - -using namespace std; - -int main() -{ - // 范围for语句 - int ia[2][2] = {1, 2, 3, 4}; - for (int (&row)[2] : ia){ - for(int &val : row) { - cout << val << " "; - } - cout << endl; - } - // 普通for语句,下标形式 - for (int i = 0; i != 2; i++){ - for(int j = 0; j != 2; j++) { - cout << ia[i][j] << " "; - } - cout << endl; - } - // 普通for语句,指针形式 - for (int (*p)[2] = ia; p != ia + 2; p++){ - for(int *q = *p; q != *p + 2; q++) { - cout << *q << " "; - } - cout << endl; - } - return 0; -} \ No newline at end of file diff --git a/docs/Cpp-Primer-exercise/Resource/chp03/3.44.cpp b/docs/Cpp-Primer-exercise/Resource/chp03/3.44.cpp deleted file mode 100644 index fd4891e..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp03/3.44.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include -#include - -using namespace std; -using int_array = int[2]; - -int main() -{ - // 范围for语句 - int ia[2][2] = {1, 2, 3, 4}; - for (int_array &row : ia){ - for(int &val : row) { - cout << val << " "; - } - cout << endl; - } - // 普通for语句,下标形式 - for (int i = 0; i != 2; i++){ - for(int j = 0; j != 2; j++) { - cout << ia[i][j] << " "; - } - cout << endl; - } - // 普通for语句,指针形式 - for (int_array *p = ia; p != ia + 2; p++){ - for(int *q = *p; q != *p + 2; q++) { - cout << *q << " "; - } - cout << endl; - } - return 0; -} \ No newline at end of file diff --git a/docs/Cpp-Primer-exercise/Resource/chp03/3.45.cpp b/docs/Cpp-Primer-exercise/Resource/chp03/3.45.cpp deleted file mode 100644 index 5574a16..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp03/3.45.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include -#include - -using namespace std; -using int_array = int[2]; - -int main() -{ - // 范围for语句 - int ia[2][2] = {1, 2, 3, 4}; - for (auto &row : ia){ - for(auto &val : row) { - cout << val << " "; - } - cout << endl; - } - // 普通for语句,下标形式 - for (auto i = 0; i != 2; i++){ - for(auto j = 0; j != 2; j++) { - cout << ia[i][j] << " "; - } - cout << endl; - } - // 普通for语句,指针形式 - for (auto *p = ia; p != ia + 2; p++){ - for(auto *q = *p; q != *p + 2; q++) { - cout << *q << " "; - } - cout << endl; - } - return 0; -} \ No newline at end of file diff --git a/docs/Cpp-Primer-exercise/Resource/chp03/3.5.cpp b/docs/Cpp-Primer-exercise/Resource/chp03/3.5.cpp deleted file mode 100644 index 4573cbd..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp03/3.5.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include -#include - -using namespace std; - -int main() { - char cont = 'y'; - string s, result; - cout << "请输入字符串" << endl; - while(cin >> s) { - if (!result.size()) - result += s; - else - result = result + " " + s; - cout << "是否继续(y or n)" << endl; - cin >> cont; - if (cont == 'y' || cont == 'Y'){ - cout << "请输入下一个字符串" << endl; - }else { - break; - } - } - cout << "拼接后的字符为" < -#include - -using namespace std; - -int main() { - string s; - cout << "请输入一个字符串" < -#include - -using namespace std; - -int main() { - string s; - cout << "请输入一个字符串" < -#include - -using namespace std; -// while -int main() { - string s; - cout << "请输入一个字符串" < -#include - -using namespace std; - -int main() { - string s; - cout << "请输入一个字符串" < -#include -#include -#include - -using namespace std; - -int main() { - vector vInt; - const int sz = 10; - srand((unsigned ) time(NULL)); - - cout <<"数组的初始值为" << endl; - - for(int i = 0; i != sz; ++i) { - vInt.push_back(rand() % 100); - cout << vInt[i] << " "; - } - cout < -#include -#include -#include - -using namespace std; - -int main() { - - string finalGrade; - int grade; - - cout <<"请输入成绩" << endl; - - while (cin >> grade && grade >=0 && grade <=100) { - finalGrade = (grade >90) ? "high pass": (grade > 75)? "pass": (grade > 60) ? "low pass": "fail"; - } - cout << "等级为" << finalGrade << endl; - - // ------------------------- - if (grade > 90) cout << "high pass"; - else if (grade < 60) cout << "fail"; - else if (grade < 75) cout << "low pass"; - else cout << "pass"; - cout << endl; - - return 0; -} diff --git a/docs/Cpp-Primer-exercise/Resource/chp04/4.23.cpp b/docs/Cpp-Primer-exercise/Resource/chp04/4.23.cpp deleted file mode 100644 index 110ff11..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp04/4.23.cpp +++ /dev/null @@ -1,24 +0,0 @@ -#include - -using namespace std; - -int main() -{ - cout << "bool:\t\t" << sizeof(bool) << " bytes" << endl << endl; - - cout << "char:\t\t" << sizeof(char) << " bytes" << endl; - cout << "wchar_t:\t" << sizeof(wchar_t) << " bytes" << endl; - cout << "char16_t:\t" << sizeof(char16_t) << " bytes" << endl; - cout << "char32_t:\t" << sizeof(char32_t) << " bytes" << endl << endl; - - cout << "short:\t\t" << sizeof(short) << " bytes" << endl; - cout << "int:\t\t" << sizeof(int) << " bytes" << endl; - cout << "long:\t\t" << sizeof(long) << " bytes" << endl; - cout << "long long:\t" << sizeof(long long) << " bytes" << endl << endl; - - cout << "float:\t\t" << sizeof(float) << " bytes" << endl; - cout << "double:\t\t" << sizeof(double) << " bytes" << endl; - cout << "long double:\t" << sizeof(long double) << " bytes" << endl << endl; - - return 0; -} diff --git a/docs/Cpp-Primer-exercise/Resource/chp05/5.10.cpp b/docs/Cpp-Primer-exercise/Resource/chp05/5.10.cpp deleted file mode 100644 index 65bb6d6..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp05/5.10.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include - -using namespace std; - - -int main() -{ - unsigned aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0; - char ch; - cout << "请输入一段文本" << endl; - while (cin >> ch) - { - if (ch == 'a') ++aCnt; - else if (ch == 'e') ++eCnt; - else if (ch == 'i') ++iCnt; - else if (ch == 'o') ++oCnt; - else if (ch == 'u') ++uCnt; - } - cout << "Number of vowel a: \t" << aCnt << '\n' - << "Number of vowel e: \t" << eCnt << '\n' - << "Number of vowel i: \t" << iCnt << '\n' - << "Number of vowel o: \t" << oCnt << '\n' - << "Number of vowel u: \t" << uCnt << '\n' - << "Total number: \t" << aCnt + eCnt + iCnt + oCnt +uCnt<< endl; - - return 0; -} \ No newline at end of file diff --git a/docs/Cpp-Primer-exercise/Resource/chp05/5.11.cpp b/docs/Cpp-Primer-exercise/Resource/chp05/5.11.cpp deleted file mode 100644 index 880677e..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp05/5.11.cpp +++ /dev/null @@ -1,58 +0,0 @@ -#include - -using namespace std; - - -int main() -{ - unsigned int aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0; - unsigned int spaceCnt = 0, tabCnt = 0,newlineCnt = 0; - char ch; - cout << "请输入一段文本" << endl; - while (cin >> ch) - { - switch (ch) { - case 'a': - case 'A': - ++aCnt; - break; - case 'e': - case 'E': - ++eCnt; - break; - case 'I': - case 'i': - ++iCnt; - break; - case 'o': - case 'O': - ++oCnt; - break; - case 'u': - case 'U': - ++uCnt; - break; - case ' ': - ++spaceCnt; - break; - case '\t': - ++tabCnt; - break; - case '\n': - ++newlineCnt; - break; - } - - } - cout << "Number of vowel a: \t" << aCnt << '\n' - << "Number of vowel e: \t" << eCnt << '\n' - << "Number of vowel i: \t" << iCnt << '\n' - << "Number of vowel o: \t" << oCnt << '\n' - << "Number of vowel u: \t" << uCnt << '\n' - << "Number of space: \t" << spaceCnt << '\n' - << "Number of tab: \t" << tabCnt << '\n' - << "Number of newline: \t" << newlineCnt << '\n' - << "Total number: \t" << aCnt + eCnt + iCnt + oCnt +uCnt + spaceCnt + tabCnt + newlineCnt<< endl; - - return 0; -} \ No newline at end of file diff --git a/docs/Cpp-Primer-exercise/Resource/chp05/5.12.cpp b/docs/Cpp-Primer-exercise/Resource/chp05/5.12.cpp deleted file mode 100644 index 43d455c..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp05/5.12.cpp +++ /dev/null @@ -1,39 +0,0 @@ -#include - -using namespace std; - - -int main() -{ - unsigned int flCnt = 0, ffCnt = 0, fiCnt = 0; - char ch, prech= '\0'; - cout << "请输入一段文本" << endl; - while (cin >> ch) - { - bool flag = true; - if (prech == 'f') { - switch (ch) { - case 'f': - ++ffCnt; - flag = false; - break; - case 'l': - ++flCnt; - break; - case 'i': - ++flCnt; - break; - } - } - if (!flag) { - prech = '\0'; - }else { - prech = ch;} - } - cout << "Number of ff : \t" << ffCnt << '\n' - << "Number of fi \t" << fiCnt << '\n' - << "Number of fl: \t" << flCnt << '\n' - << endl; - - return 0; -} \ No newline at end of file diff --git a/docs/Cpp-Primer-exercise/Resource/chp05/5.14.cpp b/docs/Cpp-Primer-exercise/Resource/chp05/5.14.cpp deleted file mode 100644 index e9ee0cb..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp05/5.14.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include - -using namespace std; - - -int main() -{ - string currStr, preStr = " ", maxStr; - - int currCnt = 1, maxCnt = 0; - - - while (cin >> currStr) { - // - if (currStr == preStr) { - ++currCnt; - if (currCnt > maxCnt) { - maxCnt = currCnt; - maxStr = currStr; - } - }else { - currCnt = 1; - } - preStr = currStr; - } - if (maxCnt >1) { - cout << "出现最多的字符串是 : " << maxStr << ", 次数是:" << maxCnt << endl; - }else { - cout << "每个字符串都出现了一次" << endl; - } - return 0; -} \ No newline at end of file diff --git a/docs/Cpp-Primer-exercise/Resource/chp05/5.5.cpp b/docs/Cpp-Primer-exercise/Resource/chp05/5.5.cpp deleted file mode 100644 index 16ab17b..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp05/5.5.cpp +++ /dev/null @@ -1,53 +0,0 @@ -#include -#include -#include -using namespace std; - -int main() -{ - - int g; - - cout <<"请输入成绩:"<> g; - - if (g < 0 || g > 100) { - cout << "该成绩不合法" < 7) { - level = "+"; - }else if (j < 3) { - level = "-"; - }else { - level = ""; - } - finalGrade = score + level; - - cout << "等级成绩是:" << finalGrade << endl; - return 0; -} \ No newline at end of file diff --git a/docs/Cpp-Primer-exercise/Resource/chp05/5.6.cpp b/docs/Cpp-Primer-exercise/Resource/chp05/5.6.cpp deleted file mode 100644 index 8060ba6..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp05/5.6.cpp +++ /dev/null @@ -1,40 +0,0 @@ -#include -#include -#include -using namespace std; - -int main() -{ - - int g; - - cout <<"请输入成绩:"<> g; - - if (g < 0 || g > 100) { - cout << "该成绩不合法" < 7) ? "+" : (j < 3)? "-": ""; - - finalGrade = score + level; - - cout << "等级成绩是:" << finalGrade << endl; - return 0; -} \ No newline at end of file diff --git a/docs/Cpp-Primer-exercise/Resource/chp05/5.9.cpp b/docs/Cpp-Primer-exercise/Resource/chp05/5.9.cpp deleted file mode 100644 index 65bb6d6..0000000 --- a/docs/Cpp-Primer-exercise/Resource/chp05/5.9.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include - -using namespace std; - - -int main() -{ - unsigned aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0; - char ch; - cout << "请输入一段文本" << endl; - while (cin >> ch) - { - if (ch == 'a') ++aCnt; - else if (ch == 'e') ++eCnt; - else if (ch == 'i') ++iCnt; - else if (ch == 'o') ++oCnt; - else if (ch == 'u') ++uCnt; - } - cout << "Number of vowel a: \t" << aCnt << '\n' - << "Number of vowel e: \t" << eCnt << '\n' - << "Number of vowel i: \t" << iCnt << '\n' - << "Number of vowel o: \t" << oCnt << '\n' - << "Number of vowel u: \t" << uCnt << '\n' - << "Total number: \t" << aCnt + eCnt + iCnt + oCnt +uCnt<< endl; - - return 0; -} \ No newline at end of file diff --git a/docs/Cpp-Primer/1.开始.md b/docs/Cpp-Primer/1.开始.md deleted file mode 100644 index bda1574..0000000 --- a/docs/Cpp-Primer/1.开始.md +++ /dev/null @@ -1,149 +0,0 @@ ---- -sort: 1 ---- - -# 开始 - -## 编写简单的C++程序 - -一个函数的定义包含四部分:返回类型、函数名、一个括号包围的形参列表(允许为空)以及函数体。 - -## 熟悉编译器 - -**g++**: - -- 编译:`g++ 1.1.cpp -o main` -- 运行:`./main` -- 查看运行状态:`echo $?` -- 编译多个文件:`g++ ch2.cpp Sales_item.cc -o main` - -输入 `g++ --help`,查看编译器选项: - -``` -Usage: g++ [options] file... -Options: - -pass-exit-codes Exit with highest error code from a phase - --help Display this information - --target-help Display target specific command line options - --help={common|optimizers|params|target|warnings|[^]{joined|separate|undocumented}}[,...] - Display specific types of command line options - (Use '-v --help' to display command line options of sub-processes) - --version Display compiler version information - -dumpspecs Display all of the built in spec strings - -dumpversion Display the version of the compiler - -dumpmachine Display the compiler's target processor - -print-search-dirs Display the directories in the compiler's search path - -print-libgcc-file-name Display the name of the compiler's companion library - -print-file-name= Display the full path to library - -print-prog-name= Display the full path to compiler component - -print-multiarch Display the target's normalized GNU triplet, used as - a component in the library path - -print-multi-directory Display the root directory for versions of libgcc - -print-multi-lib Display the mapping between command line options and - multiple library search directories - -print-multi-os-directory Display the relative path to OS libraries - -print-sysroot Display the target libraries directory - -print-sysroot-headers-suffix Display the sysroot suffix used to find headers - -Wa, Pass comma-separated on to the assembler - -Wp, Pass comma-separated on to the preprocessor - -Wl, Pass comma-separated on to the linker - -Xassembler Pass on to the assembler - -Xpreprocessor Pass on to the preprocessor - -Xlinker Pass on to the linker - -save-temps Do not delete intermediate files - -save-temps= Do not delete intermediate files - -no-canonical-prefixes Do not canonicalize paths when building relative - prefixes to other gcc components - -pipe Use pipes rather than intermediate files - -time Time the execution of each subprocess - -specs= Override built-in specs with the contents of - -std= Assume that the input sources are for - --sysroot= Use as the root directory for headers - and libraries - -B Add to the compiler's search paths - -v Display the programs invoked by the compiler - -### Like -v but options quoted and commands not executed - -E Preprocess only; do not compile, assemble or link - -S Compile only; do not assemble or link - -c Compile and assemble, but do not link - -o Place the output into - -pie Create a position independent executable - -shared Create a shared library - -x Specify the language of the following input files - Permissible languages include: c c++ assembler none - 'none' means revert to the default behavior of - guessing the language based on the file's extension - -``` - -输入 `g++ -v --help`可以看到更完整的指令。 -例如还有些常用的: -``` --h FILENAME, -soname FILENAME: Set internal name of shared library --I PROGRAM, --dynamic-linker PROGRAM: Set PROGRAM as the dynamic linker to use --l LIBNAME, --library LIBNAME: Search for library LIBNAME --L DIRECTORY, --library-path DIRECTORY: Add DIRECTORY to library search path -``` - -**获得程序状态**: - -- windows: ``echo %ERRORLEVEL%`` -- UNIX: ``echo $?`` - -## IO - -- ```#include ``` -- ```std::cout << "hello"``` -- ```std::cin >> v1``` - -记住`>>`和`<<`返回的结果都是左操作数,也就是输入流和输出流本身。 - -**endl**:这是一个被称为**操纵符**(manipulator)的特殊值,效果是结束当前行,并将设备关联的缓冲区(buffer)中的内容刷到设备中。 - -UNIX和Mac下键盘输入文件结束符:`ctrl+d`,Windows下:`ctrl+z` - -**头文件**:类的类型一般存储在头文件中,标准库的头文件使用`<>`,非标准库的头文件使用`""`。申明写在`.h`文件,定义实现写在`.cpp`文件。 - -**避免多次包含同一头文件**: - -```cpp -#ifndef SALESITEM_H -#define SALESITEM_H -// Definition of Sales_itemclass and related functions goes here -#endif -``` - -**成员函数(类方法)**:使用`.`调用。 - -**命名空间(namespace)**:使用作用域运算符`::`调用。 - -## 注释 - -- 单行注释: `//` -- 多行注释: `/**/`。编译器将`/*`和`*/`之间的内容都作为注释内容忽略。注意不能嵌套。 -```cpp -#define SALESITEM_H -/* - * 多行注释格式 - * 每一行加一个* - */ -``` - -## while语句 - -循环执行,(直到条件(condition)为假。 - -## for语句 - -循环头由三部分组成: - -- 一个初始化语句(init-statement) -- 一个循环条件(condition) -- 一个表达式(expression) - -## 使用文件重定向 - -``./main outfile`` - - - diff --git a/docs/Cpp-Primer/2.变量和基本类型.md b/docs/Cpp-Primer/2.变量和基本类型.md deleted file mode 100644 index 8f83f44..0000000 --- a/docs/Cpp-Primer/2.变量和基本类型.md +++ /dev/null @@ -1,249 +0,0 @@ ---- -sort: 2 ---- - -# 变量和基本类型 - -### 基本内置类型 - -**基本算数类型**: - -| 类型 | 含义 | 最小尺寸 | -| ------------- | -------------- | ------------------------------ | -| `bool` | 布尔类型 | 8bits | -| `char` | 字符 | 8bits | -| `wchar_t` | 宽字符 | 16bits | -| `char16_t` | Unicode字符 | 16bits | -| `char32_t` | Unicode字符 | 32bits | -| `short` | 短整型 | 16bits | -| `int` | 整型 | 16bits (在32位机器中是32bits) | -| `long` | 长整型 | 32bits | -| `long long` | 长整型 | 64bits (是在C++11中新定义的) | -| `float` | 单精度浮点数 | 6位有效数字 | -| `double` | 双精度浮点数 | 10位有效数字 | -| `long double` | 扩展精度浮点数 | 10位有效数字 | - - -### 如何选择类型 - -- 1.当明确知晓数值不可能是负数时,选用无符号类型; -- 2.使用`int`执行整数运算。一般`long`的大小和`int`一样,而`short`常常显得太小。除非超过了`int`的范围,选择`long long`。 -- 3.算术表达式中不要使用`char`或`bool`。 -- 4.浮点运算选用`double`。 - -### 类型转换 - -- 非布尔型赋给布尔型,初始值为0则结果为false,否则为true。 -- 布尔型赋给非布尔型,初始值为false结果为0,初始值为true结果为1。 - -### 字面值常量 - -- 一个形如`42`的值被称作**字面值常量**(literal)。 - - 整型和浮点型字面值。 - - 字符和字符串字面值。 - - 使用空格连接,继承自C。 - - 字符字面值:单引号, `'a'` - - 字符串字面值:双引号, `"Hello World""` - - 分多行书写字符串。 - ``` - std:cout<<"wow, a really, really long string" - "literal that spans two lines" < 字符串型实际上时常量字符构成的数组,结尾处以`'\0'`结束,所以字符串类型实际上长度比内容多1。 - -## 变量 - -**变量**提供一个**具名**的、可供程序操作的存储空间。 `C++`中**变量**和**对象**一般可以互换使用。 - -### 变量定义(define) - -- **定义形式**:类型说明符(type specifier) + 一个或多个变量名组成的列表。如`int sum = 0, value, units_sold = 0;` -- **初始化**(initialize):对象在创建时获得了一个特定的值。 - - **初始化不是赋值!**: - - 初始化 = 创建变量 + 赋予初始值 - - 赋值 = 擦除对象的当前值 + 用新值代替 - - **列表初始化**:使用花括号`{}`,如`int units_sold{0};` - - 默认初始化:定义时没有指定初始值会被默认初始化;**在函数体内部的内置类型变量将不会被初始化**。 - - 建议初始化每一个内置类型的变量。 - -### 变量的**声明**(declaration) vs **定义**(define) - - 为了支持分离式编译,`C++`将声明和定义区分开。**声明**使得名字为程序所知。**定义**负责创建与名字关联的实体。 - - **extern**:只是说明变量定义在其他地方。 - - 只声明而不定义: 在变量名前添加关键字 `extern`,如`extern int i;`。但如果包含了初始值,就变成了定义:`extern double pi = 3.14;` - - 变量只能被定义一次,但是可以多次声明。定义只出现在一个文件中,其他文件使用该变量时需要对其声明。 -- 名字的**作用域**(namescope)`{}` - - **第一次使用变量时再定义它**。 - - 嵌套的作用域 - - 同时存在全局和局部变量时,已定义局部变量的作用域中可用`::reused`显式访问全局变量reused。 - - **但是用到全局变量时,尽量不适用重名的局部变量。** - -#### 变量命名规范 -1. 需体现实际意义 -2. 变量名用小写字母 -3. 自定义类名用大写字母开头:Sales_item -4. 标识符由多个单词组成,中间须有明确区分:student_loan或studentLoan,不要用studentloan。 - -## 左值和右值 - -- **左值**(l-value)**可以**出现在赋值语句的左边或者右边,比如变量; -- **右值**(r-value)**只能**出现在赋值语句的右边,比如常量。 - - -## 复合类型 - -### 引用 - -> 一般说的引用是指的左值引用 -- **引用**:引用是一个对象的别名,引用类型引用(refer to)另外一种类型。如`int &refVal = val;`。 -- 引用必须初始化。 -- 引用和它的初始值是**绑定bind**在一起的,而**不是拷贝**。一旦定义就不能更改绑定为其他的对象 - -### 指针 - -> int *p; //**指向int型对象**的指针 - -- 是一种 `"指向(point to)"`另外一种类型的复合类型。 - -- **定义**指针类型: `int *ip1;`,**从右向左读有助于阅读**,`ip1`是指向`int`类型的指针。 - -- 指针存放某个对象的**地址**。 - -- 获取对象的地址: `int i=42; int *p = &i;`。 `&`是**取地址符**。 - -- 指针的类型与所指向的对象类型必须一致(均为同一类型int、double等) - -- 指针的值的四种状态: - - 1.指向一个对象; - - 2.指向紧邻对象的下一个位置; - - 3.空指针; - - 4.无效指针。 - - >**对无效指针的操作均会引发错误,第二种和第三种虽为有效的,但理论上是不被允许的** - -- 指针访问对象: `cout << *p;`输出p指针所指对象的数据, `*`是**解引用符**。 -- 像 `&` 和 `*`这样的符号,既能用作表达式里的运算符,也能作为声明的一部分出现,符号的上下文决定了符号的意义。 -```cpp -int i = 42; -int &r = i; // & 紧随类型名出现,因此是声明的一部分,r是一个引用 -int *p; // * 紧随类型名出现,因此是声明的一部分,p是一个指针 -p = &i; // & 出现在表达式中,是一个取地址符 -*p = i; // * 出现在表达式中,是一个解引用符 -int &r2 = *p; // &是声明的一部分,*是一个解引用符 -``` - - - -- 空指针不指向任何对象。使用`int *p=nullptr;`来使用空指针。 - -- > 指针和引用的区别:引用本身并非一个对象,引用定义后就不能绑定到其他的对象了;指针并没有此限制,相当于变量一样使用。 - -- > 赋值语句永远改变的是**左侧**的对象。 - -- `void*`指针可以存放**任意**对象的地址。因无类型,仅操作内存空间,对所存对象无法访问。 - -- 其他指针类型必须要与所指对象**严格匹配**。 - -- 两个指针相减的类型是`ptrdiff_t`。 - -- 建议:初始化所有指针。 - -- `int* p1, p2;//*是对p1的修饰,所以p2还是int型` - -## const限定符 - -- 动机:希望定义一些不能被改变值的变量。 - -### 初始化和const -- `const` 对象**必须初始化**,且**不能被改变**。 -- `const` 变量默认不能被其他文件访问,非要访问,必须在指定 `const` 定义之前加 `extern` 。要想在多个文件中使用const变量共享,定义和声明都加 `extern` 关键字即可。 - -### const的引用 - -- **reference to const**(对常量的引用):指向const对象的引用,如 `const int ival=1; const int &refVal = ival;`,可以读取但不能修改`refVal`。 -- **临时量**(temporary)对象:当编译器需要一个空间来暂存表达式的求值结果时,临时创建的一个未命名的对象。 -- 对临时量的引用是非法行为。 - -### 指针和const - -- **pointer to const**(指向常量的指针):不能用于改变其所指对象的值, 如 `const double pi = 3.14; const double *cptr = π`。 -- **const pointer**:指针本身是常量,也就是说指针固定指向该对象,(存放在指针中的地址不变,地址所对应的那个对象值可以修改)如 `int i = 0; int *const ptr = &i;` - -### 顶层const - -- `顶层const`:指针本身是个常量。 -- `底层const`:指针指向的对象是个常量。拷贝时严格要求相同的底层const资格。 - -### `constexpr`和常量表达式(▲可选) - -- 常量表达式:指值不会改变,且在编译过程中就能得到计算结果的表达式。 -- `C++11`新标准规定,允许将变量声明为`constexpr`类型以便由编译器来验证变量的值是否是一个常量的表达式。 - -## 处理类型 - -### 类型别名 - -- 传统别名:使用**typedef**来定义类型的同义词。 `typedef double wages;` -- 新标准别名:别名声明(alias declaration): `using SI = Sales_item;`(C++11) - -```cpp -// 对于复合类型(指针等)不能代回原式来进行理解 -typedef char *pstring; // pstring是char*的别名 -const pstring cstr = 0; // 指向char的常量指针 -// 如改写为const char *cstr = 0;不正确,为指向const char的指针 - -// 辅助理解(可代回后加括号) -// const pstring cstr = 0;代回后const (char *) cstr = 0; -// const char *cstr = 0;即为(const char *) cstr = 0; -``` - -### auto类型说明符 c++11 - -- **auto**类型说明符:让编译器**自动推断类型**。 -- 一条声明语句只能有一个数据类型,所以一个auto声明多个变量时只能相同的变量类型(包括复杂类型&和*)。`auto sz = 0, pi =3.14//错误` -- `int i = 0, &r = i; auto a = r;` 推断`a`的类型是`int`。 -- 会忽略`顶层const`。 -- `const int ci = 1; const auto f = ci;`推断类型是`int`,如果希望是顶层const需要自己加`const` - -### decltype类型指示符 - -- 从表达式的类型推断出要定义的变量的类型。 -- **decltype**:选择并返回操作数的**数据类型**。 -- `decltype(f()) sum = x;` 推断`sum`的类型是函数`f`的返回类型。 -- 不会忽略`顶层const`。 -- 如果对变量加括号,编译器会将其认为是一个表达式,如int i-->(i),则decltype((i))得到结果为int&引用。 -- 赋值是会产生引用的一类典型表达式,引用的类型就是左值的类型。也就是说,如果 i 是 int,则表达式 i=x 的类型是 int&。 -- decltype((variable))(双层括号)的结果永远是引用,而decltype(variable)的结果只有当 **variable** 本身是一个引用时才是引用 - -## 自定义数据结构 - -### struct - -> 尽量不要把类定义和对象定义放在一起。如`struct Student{} xiaoming,xiaofang;` -- 类可以以关键字`struct`开始,紧跟类名和类体。 -- 类数据成员:类体定义类的成员。 -- `C++11`:可以为类数据成员提供一个**类内初始值**(in-class initializer)。 - -### 编写自己的头文件 - -- 头文件通常包含哪些只能被定义一次的实体:类、`const`和`constexpr`变量。 - -预处理器概述: - -- **预处理器**(preprocessor):确保头文件多次包含仍能安全工作。 -- 当预处理器看到`#include`标记时,会用指定的头文件内容代替`#include` -- **头文件保护符**(header guard):头文件保护符依赖于预处理变量的状态:已定义和未定义。 - - `#indef`已定义时为真 - - `#inndef`未定义时为真 - - 头文件保护符的名称需要唯一,且保持全部大写。养成良好习惯,不论是否该头文件被包含,要加保护符。 - -```cpp -#ifndef SALES_DATA_H //SALES_DATA_H未定义时为真 -#define SALES_DATA_H -strct Sale_data{ - ... -} -#endif -``` \ No newline at end of file diff --git a/docs/Cpp-Primer/3.字符串、向量和数组.md b/docs/Cpp-Primer/3.字符串、向量和数组.md deleted file mode 100644 index 5904618..0000000 --- a/docs/Cpp-Primer/3.字符串、向量和数组.md +++ /dev/null @@ -1,242 +0,0 @@ ---- -sort: 3 ---- - -# 字符串、向量和数组 - -## using声明 -- 使用某个命名空间:例如 `using std::cin`表示使用命名空间`std`中的名字`cin`。 -- 头文件中不应该包含`using`声明。这样使用了该头文件的源码也会使用这个声明,会带来风险。 - -## string -- 标准库类型`string`表示可变长的字符序列。 -- `#include `,然后 `using std::string;` -- **string对象**:注意,不同于字符串字面值。 - -### 定义和初始化string对象 - -初始化`string`对象的方式: - -| 方式 | 解释 | -| --------------------- | ------------------------------------------------------- | -| `string s1` | 默认初始化,`s1`是个空字符串 | -| `string s2(s1)` | `s2`是`s1`的副本 | -| `string s2 = s1` | 等价于`s2(s1)`,`s2`是`s1`的副本 | -| `string s3("value")` | `s3`是字面值“value”的副本,除了字面值最后的那个空字符外 | -| `string s3 = "value"` | 等价于`s3("value")`,`s3`是字面值"value"的副本 | -| `string s4(n, 'c')` | 把`s4`初始化为由连续`n`个字符`c`组成的串 | - -- 拷贝初始化(copy initialization):使用等号`=`将一个已有的对象拷贝到正在创建的对象。 -- 直接初始化(direct initialization):通过括号给对象赋值。 - -### string对象上的操作 - -`string`的操作: - -| 操作 | 解释 | -| -------------------- | ------------------------------------------------------------------------------------------ | -| `os << s` | 将`s`写到输出流`os`当中,返回`os` | -| `is >> s` | 从`is`中读取字符串赋给`s`,字符串以空白分割,返回`is` | -| `getline(is, s)` | 从`is`中读取一行赋给`s`,返回`is` | -| `s.empty()` | `s`为空返回`true`,否则返回`false` | -| `s.size()` | 返回`s`中字符的个数 | -| `s[n]` | 返回`s`中第`n`个字符的引用,位置`n`从0计起 | -| `s1+s2` | 返回`s1`和`s2`连接后的结果 | -| `s1=s2` | 用`s2`的副本代替`s1`中原来的字符 | -| `s1==s2` | 如果`s1`和`s2`中所含的字符完全一样,则它们相等;`string`对象的相等性判断对字母的大小写敏感 | -| `s1!=s2` | 同上 | -| `<`, `<=`, `>`, `>=` | 利用字符在字典中的顺序进行比较,且对字母的大小写敏感(对第一个不相同的位置进行比较) | - -- string io: - - 执行读操作`>>`:忽略掉开头的空白(包括空格、换行符和制表符),直到遇到下一处空白为止。 - - `getline`:读取一整行,**包括空白符**。 -- `s.size()`返回的时`string::size_type`类型,记住是一个**无符号**类型的值,不要和`int`混用 -- `s1+s2`使用时,保证至少一侧是string类型。`string s1 = "hello" + "world" // 错误,两侧均为字符串字面值` -- **字符串字面值和string是不同的类型。** - -### 处理string对象中的字符 - -- **ctype.h vs. cctype**:C++修改了c的标准库,名称为去掉`.h`,前面加`c`。 - > 如c++版本为`cctype`,c版本为`ctype.h` - - **尽量使用c++版本的头文件**,即`cctype` - -`cctype`头文件中定义了一组标准函数: - -| 函数 | 解释 | -| ------------- | ------------------------------------------------------------------------- | -| `isalnum(c)` | 当`c`是字母或数字时为真 | -| `isalpha(c)` | 当`c`是字母时为真 | -| `iscntrl(c)` | 当`c`是控制字符时为真 | -| `isdigit(c)` | 当`c`是数字时为真 | -| `isgraph(c)` | 当`c`不是空格但可以打印时为真 | -| `islower(c)` | 当`c`是小写字母时为真 | -| `isprint(c)` | 当`c`是可打印字符时为真 | -| `ispunct(c)` | 当`c`是标点符号时为真 | -| `isspace(c)` | 当`c`是空白时为真(空格、横向制表符、纵向制表符、回车符、换行符、进纸符) | -| `isupper(c)` | 当`c`是大写字母时为真 | -| `isxdigit(c)` | 当`c`是十六进制数字时为真 | -| `tolower(c)` | 当`c`是大写字母,输出对应的小写字母;否则原样输出`c` | -| `toupper(c)` | 当`c`是小写字母,输出对应的大写字母;否则原样输出`c` | - -- 遍历字符串:使用**范围for**(range for)语句: `for (auto c: str)`,或者 `for (auto &c: str)`使用引用直接改变字符串中的字符。 (C++11) -- `str[x]`,[]输入参数为`string::size_type`类型,给出`int`整型也会自动转化为该类型 - -## vector -- vector是一个**容器**,也是一个类模板; -- `#include ` 然后 `using std::vector;` -- 容器:包含其他对象。 -- 类模板:本身不是类,但可以**实例化instantiation**出一个类。 `vector`是一个模板, `vector`是一个类型。 -- 通过将类型放在类模板名称后面的**尖括号**中来指定**类型**,如`vector ivec`。 - -### 定义和初始化vector对象 - -初始化`vector`对象的方法 - -| 方法 | 解释 | -| --------------------------- | ------------------------------------------------------------- | -| `vector v1` | `v1`是一个空`vector`,它潜在的元素是`T`类型的,执行默认初始化 | -| `vector v2(v1)` | `v2`中包含有`v1`所有元素的副本 | -| `vector v2 = v1` | 等价于`v2(v1)`,`v2`中包含`v1`所有元素的副本 | -| `vector v3(n, val)` | `v3`包含了n个重复的元素,每个元素的值都是`val` | -| `vector v4(n)` | `v4`包含了n个重复地执行了值初始化的对象 | -| `vector v5{a, b, c...}` | `v5`包含了初始值个数的元素,每个元素被赋予相应的初始值 | -| `vector v5={a, b, c...}` | 等价于`v5{a, b, c...}` | - -- 列表初始化: `vector v{"a", "an", "the"};` (C++11) - -### 向vector对象中添加元素 - -- `v.push_back(e)` 在尾部增加元素。 - -### 其他vector操作 - -`vector`支持的操作: - -| 操作 | 解释 | -| ------------------ | ---------------------------------------------------------------- | -| `v.emtpy()` | 如果`v`不含有任何元素,返回真;否则返回假 | -| `v.size()` | 返回`v`中元素的个数 | -| `v.push_back(t)` | 向`v`的尾端添加一个值为`t`的元素 | -| `v[n]` | 返回`v`中第`n`个位置上元素的**引用** | -| `v1 = v2` | 用`v2`中的元素拷贝替换`v1`中的元素 | -| `v1 = {a,b,c...}` | 用列表中元素的拷贝替换`v1`中的元素 | -| `v1 == v2` | `v1`和`v2`相等当且仅当它们的元素数量相同且对应位置的元素值都相同 | -| `v1 != v2` | 同上 | -| `<`,`<=`,`>`, `>=` | 以字典顺序进行比较 | - -- 范围`for`语句内不应该改变其遍历序列的大小。 -- `vector`对象(以及`string`对象)的下标运算符,只能对确知已存在的元素执行下标操作,不能用于添加元素。 -- 两个`vector`对象相等当且仅当他们所含的元素个数相同,而且对应位置的元素也相同。 - -## 迭代器iterator - -- 所有标准库容器都可以使用迭代器。 -- 类似于指针类型,迭代器也提供了对对象的间接访问。 - -### 使用迭代器 - -- `vector::iterator iter`。 -- `auto b = v.begin();`返回指向第一个元素的迭代器。 -- `auto e = v.end();`返回指向最后一个元素的下一个(哨兵,尾后,one past the end)的迭代器(off the end)。 -- 如果容器为空, `begin()`和 `end()`返回的是同一个迭代器,都是尾后迭代器。 -- 使用解引用符`*`访问迭代器指向的元素。 -- 养成使用迭代器和`!=`的习惯(泛型编程)。 -- **容器**:可以包含其他对象;但所有的对象必须类型相同。 -- **迭代器(iterator)**:每种标准容器都有自己的迭代器。`C++`倾向于用迭代器而不是下标遍历元素。 -- **const_iterator**:只能读取容器内元素不能改变。 -- **箭头运算符**: 解引用 + 成员访问,`it->mem`等价于 `(*it).mem` -- **谨记**:但凡是使用了**迭代器**的循环体,都**不要**向迭代器所属的容器**添加元素**。 - -标准容器迭代器的运算符: - -| 运算符 | 解释 | -| ---------------- | -------------------------------------- | -| `*iter` | 返回迭代器`iter`所指向的**元素的引用** | -| `iter->mem` | 等价于`(*iter).mem` | -| `++iter` | 令`iter`指示容器中的下一个元素 | -| `--iter` | 令`iter`指示容器中的上一个元素 | -| `iter1 == iter2` | 判断两个迭代器是否相等 | - -### 迭代器运算 - -`vector`和`string`迭代器支持的运算: - -| 运算符 | 解释 | -| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `iter + n` | 迭代器加上一个整数值仍得到一个迭代器,迭代器指示的新位置和原来相比向前移动了若干个元素。结果迭代器或者指示容器内的一个元素,或者指示容器尾元素的下一位置。 | -| `iter - n` | 迭代器减去一个证书仍得到一个迭代器,迭代器指示的新位置比原来向后移动了若干个元素。结果迭代器或者指向容器内的一个元素,或者指示容器尾元素的下一位置。 | -| `iter1 += n` | 迭代器加法的复合赋值语句,将`iter1`加n的结果赋给`iter1` | -| `iter1 -= n` | 迭代器减法的复合赋值语句,将`iter2`减n的加过赋给`iter1` | -| `iter1 - iter2` | 两个迭代器相减的结果是它们之间的距离,也就是说,将运算符右侧的迭代器向前移动差值个元素后得到左侧的迭代器。参与运算的两个迭代器必须指向的是同一个容器中的元素或者尾元素的下一位置。 | -| `>`、`>=`、`<`、`<=` | 迭代器的关系运算符,如果某迭代器 | - -- **difference_type**:保证足够大以存储任何两个迭代器对象间的距离,可正可负。 - -## 数组 - -- 相当于vector的低级版,**长度固定**。 - -### 定义和初始化内置数组 - -- 初始化:`char input_buffer[buffer_size];`,长度必须是const表达式,或者不写,让编译器自己推断。 -- 定义数组必须指定数组的类型,不允许用`auto`关键字由初始值的列表推断类型,但在遍历时允许使用`auto`。 -- 不能将数组的内容拷贝给其他数组作为其初始值,也不能用数组为其他数组赋值。 -- 不允许使用一个数组为另一个内置类型的数组赋值,也不允许使用`vector`对象初始化数组,相反地,允许使用数组来初始化`vector`对象。 -- 不存在引用的数组。 -- 理解数组声明的含义,最好的板房是从数组的名字开始按照由内向外的顺序阅读。 - -### 访问数组元素 - -- 数组下标的类型:`size_t` 。 -- 字符数组的特殊性:结尾处有一个空字符,如 `char a[] = "hello";` 。 -- 用数组初始化 `vector`: `int a[] = {1,2,3,4,5}; vector v(begin(a), end(a));` 。 - -### 数组和指针 - -- 使用数组时,编译器一般会把它转换成指针。 -- 标准库类型限定使用的下标必须是无符号类型,而内置的下标可以处理负值。 -- **指针访问数组**:在表达式中使用数组名时,名字会自动转换成指向数组的第一个元素的指针。 - -## C风格字符串 - -- 从C继承来的字符串。 -- 用空字符结束(`\0`)。 -- 对大多数应用来说,使用标准库 `string`比使用C风格字符串更安全、更高效。 -- 获取 `string` 中的 `cstring` : `const char *str = s.c_str();` 。 - -C标准库String函数,定义在`` 中: - -| 函数 | 介绍 | -| ---------------- | --------------------------------------------------------------------------------------------------- | -| `strlen(p)` | 返回`p`的长度,空字符不计算在内 | -| `strcmp(p1, p2)` | 比较`p1`和`p2`的相等性。如果`p1==p2`,返回0;如果`p1>p2`,返回一个正值;如果`p1 text; -for(const auto &s: text){ - cout<=0) - cout<<*iter++< **简介是一种美德**,追求简洁能降低程序出错可能性 - -## 成员访问运算符 - -`ptr->mem`等价于`(*ptr).mem` - -注意`.`运算符优先级大于`*`,所以记得加括号 - -## 条件运算符 - -- 条件运算符(`?:`)允许我们把简单的`if-else`逻辑嵌入到单个表达式中去,按照如下形式:`cond? expr1: expr2` - -- 可以嵌套使用,**右结合律**,从右向左顺序组合 - - - ```c++ - finalgrade = (grade > 90) ? "high pass" - : (grade < 60) ? "fail" : "pass"; - //等价于 - finalgrade = (grade > 90) ? "high pass" - : ((grade < 60) ? "fail" : "pass"); - ``` - -- 输出表达式使用条件运算符记得加括号,条件运算符优先级太低。 - -## 位运算符 - -用于检查和设置二进制位的功能。 - -- 位运算符是作用于**整数类型**的运算对象。 -- 二进制位向左移(`<<`)或者向右移(`>>`),移出边界外的位就被舍弃掉了。 -- 位取反(`~`)(逐位求反)、与(`&`)、或(`|`)、异或(`^`) - -有符号数负值可能移位后变号,所以强烈建议**位运算符仅用于无符号数**。 - -应用: - -```c++ -unsigned long quiz1 = 0; // 每一位代表一个学生是否通过考试 -1UL << 12; // 代表第12个学生通过 -quiz1 |= (1UL << 12); // 将第12个学生置为已通过 -quiz1 &= ~(1UL << 12); // 将第12个学生修改为未通过 -bool stu12 = quiz1 & (1UL << 12); // 判断第12个学生是否通过 -``` - -> 位运算符使用较少,但是重载cout、cin大家都用过 - -位运算符满足左结合律,优先级介于中间,使用时尽量加括号。 - -## sizeof运算符 - -- 返回一条表达式或一个类型名字所占的**字节数**。 -- 返回的类型是 `size_t`的常量表达式。 -- `sizeof`并不实际计算其运算对象的值。 -- 两种形式: - 1. `sizeof (type)`,给出类型名 - 2. `sizeof expr`,给出表达式 -- 可用sizeof返回数组的大小 - -- sizeof运算符的结果部分地依赖于其作用的类型: - - 对char或者类型为char的表达式执行sizeof运算,结果得1。 - - 对引用类型执行sizeof运算得到被被引用对象所占空间的大小。 - - 对指针执行sizeof运算得到指针大本身所占空间的大小。 - - 对解引用指针执行sizeof运算得至到指针指向的对象所占空间的大小,指针不需有效。 - - 对数组执行sizeof运算得到整个数组所占空间的大小,等价于对数组中所有的元素各执行一次sizeof运算并将所得结果求和。注意,sizeof运算不会把数组转换成指针来处理。 - - 对string对象或vector对象执行sizeof运算只返回该类型固定部分的大小,不会计算对象中的元素占用了多少空间。 - -因为执行sizeof运算能得到整个数组的大小,所以可以用数组的大小除以单个元素的大小得到数组中元素的个数: - -```c++ -int ia[10]; -// sizeof(ia)返回整个数组所占空间的大小 -// sizeof(ia)/sizeof(*ia)返回数组的大小 -constexpr size_t sz = sizeof(ia)/sizeof(*ia); -int arr[sz]; -``` - -## 逗号运算符 - -从左向右依次求值。 - -左侧求值结果丢弃,逗号运算符**结果是右侧表达式**的值。 - -## 类型转换 - -### 隐式类型转换 - -> 设计为尽可能避免损失精度,即转换为更精细类型。 - -- 比 `int`类型小的整数值先提升为较大的整数类型。 -- 条件中,非布尔转换成布尔。 -- 初始化中,初始值转换成变量的类型。 -- 算术运算或者关系运算的运算对象有多种类型,要转换成同一种类型。 -- 函数调用时也会有转换。 - -#### 算术转换 - -##### 整型提升 - -* 常见的char、bool、short能存在int就会转换成int,否则提升为`unsigned int` -* `wchar_t,char16_t,char32_t`提升为整型中`int,long,long long ……`最小的,且能容纳原类型所有可能值的类型。 - -#### 其他转换 - -> p143 - -### 显式类型转换(尽量避免) - -- **static_cast**:任何明确定义的类型转换,只要不包含底层const,都可以使用。 `double slope = static_cast(j);` - -- **dynamic_cast**:支持运行时类型识别。 - -- **const_cast**:只能改变运算对象的底层const,一般可用于去除const性质。 `const char *pc; char *p = const_cast(pc)` - - > 只有其可以改变常量属性 - -- **reinterpret_cast**:通常为运算对象的位模式提供低层次上的重新解释。 - -#### 旧式强制类型转换 - -`type expr` - -## 运算符优先级表 - -![20220522224008-2022-05-22-22-40-08](https://cdn.jsdelivr.net/gh/ironartisan/picRepo/20220522224008-2022-05-22-22-40-08.png) - -![20220522224021-2022-05-22-22-40-22](https://cdn.jsdelivr.net/gh/ironartisan/picRepo/20220522224021-2022-05-22-22-40-22.png) \ No newline at end of file diff --git a/docs/Cpp-Primer/5.语句.md b/docs/Cpp-Primer/5.语句.md deleted file mode 100644 index 19e376c..0000000 --- a/docs/Cpp-Primer/5.语句.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -sort: 5 ---- - -# 语句 - -## 简单语句 - -- **表达式语句**:一个表达式末尾加上分号,就变成了表达式语句。 -- **空语句**:只有一个单独的分号。 -- **复合语句(块)**:用花括号 `{}`包裹起来的语句和声明的序列。一个块就是一个作用域。 - -## 条件语句 - -- **悬垂else**(dangling else):用来描述在嵌套的`if else`语句中,如果`if`比`else`多时如何处理的问题。C++使用的方法是`else`匹配最近没有配对的`if`。 - -## 迭代语句 - -- **while**:当不确定到底要迭代多少次时,使用 `while`循环比较合适,比如读取输入的内容。 -- **for**: `for`语句可以省略掉 `init-statement`, `condition`和 `expression`的任何一个;**甚至全部**。 -- **范围for**: `for (declaration: expression) statement` - -## 跳转语句 - -- **break**:`break`语句负责终止离它最近的`while`、`do while`、`for`或者`switch`语句,并从这些语句之后的第一条语句开始继续执行。 -- **continue**:终止最近的循环中的当前迭代并立即开始下一次迭代。只能在`while`、`do while`、`for`循环的内部。 - -## try语句块和异常处理 - -- **throw表达式**:异常检测部分使用 `throw`表达式来表示它遇到了无法处理的问题。我们说 `throw`引发 `raise`了异常。 -- **try语句块**:以 `try`关键词开始,以一个或多个 `catch`字句结束。 `try`语句块中的代码抛出的异常通常会被某个 `catch`捕获并处理。 `catch`子句也被称为**异常处理代码**。 -- **异常类**:用于在 `throw`表达式和相关的 `catch`子句之间传递异常的具体信息。 diff --git a/docs/Cpp-Primer/6.函数.md b/docs/Cpp-Primer/6.函数.md deleted file mode 100644 index 0aca411..0000000 --- a/docs/Cpp-Primer/6.函数.md +++ /dev/null @@ -1,184 +0,0 @@ ---- -sort: 6 ---- - -# 函数 - -## 函数基础 - -- **函数定义**:包括返回类型、函数名字和0个或者多个**形参**(parameter)组成的列表和函数体。 -- **调用运算符**:调用运算符的形式是一对圆括号 `()`,作用于一个表达式,该表达式是函数或者指向函数的指针。 -- 圆括号内是用逗号隔开的**实参**(argument)列表。 -- 函数调用过程: - - 1.主调函数(calling function)的执行被中断。 - - 2.被调函数(called function)开始执行。 -- **形参和实参**:形参和实参的**个数**和**类型**必须匹配上。 -- **返回类型**: `void`表示函数不返回任何值。函数的返回类型不能是数组类型或者函数类型,但可以是指向数组或者函数的指针。 - - - -### 局部对象 - -- **名字**:名字的作用于是程序文本的一部分,名字在其中可见。 -- **生命周期**:对象的生命周期是程序执行过程中该对象存在的一段时间。 -- **局部变量**(local variable):形参和函数体内部定义的变量统称为局部变量。它对函数而言是局部的,对函数外部而言是**隐藏**的。 -- **自动对象**:只存在于块执行期间的对象。当块的执行结束后,它的值就变成**未定义**的了。 -- **局部静态对象**: `static`类型的局部变量,生命周期贯穿函数调用前后。 - -### 函数声明 - -- **函数声明**:函数的声明和定义唯一的区别是声明无需函数体,用一个分号替代。函数声明主要用于描述函数的接口,也称**函数原型**。 -- **在头文件中进行函数声明**:建议变量在头文件中声明;在源文件中定义。 -- **分离编译**: `CC a.cc b.cc`直接编译生成可执行文件;`CC -c a.cc b.cc`编译生成对象代码`a.o b.o`; `CC a.o b.o`编译生成可执行文件。 - -## 参数传递 - -- 形参初始化的机理和变量初始化一样。 -- **引用传递**(passed by reference):又称传引用调用(called by reference),指**形参是引用类型**,引用形参是它对应的实参的别名。 -- **值传递**(passed by value):又称传值调用(called by value),指实参的值是通过**拷贝**传递给形参。 - -### 传值参数 - -- 当初始化一个非引用类型的变量时,初始值被拷贝给变量。 -- 函数对形参做的所有操作都不会影响实参。 -- **指针形参**:常用在C中,`C++`建议使用引用类型的形参代替指针。 - -### 传引用参数 - -- 通过使用引用形参,允许函数改变一个或多个实参的值。 -- 引用形参直接关联到绑定的对象,而非对象的副本。 -- 使用引用形参可以用于**返回额外的信息**。 -- 经常用引用形参来避免不必要的复制。 -- `void swap(int &v1, int &v2)` -- 如果无需改变引用形参的值,最好将其声明为常量引用。 - -### const形参和实参 - -- 形参的顶层`const`被忽略。`void func(const int i);`调用时既可以传入`const int`也可以传入`int`。 -- 我们可以使用非常量初始化一个底层`const`对象,但是反过来不行。 -- 在函数中,不能改变实参的**局部副本**。 -- 尽量使用常量引用。 - -### 数组形参 - -- 当我们为函数传递一个数组时,实际上传递的是指向数组首元素的指针。 -- 要注意数组的实际长度,不能越界。 - -### main处理命令行选项 - -- `int main(int argc, char *argv[]){...}` -- 第一个形参代表参数的个数;第二个形参是参数C风格字符串数组。 - -### 可变形参 - -`initializer_list`提供的操作(`C++11`): - -| 操作 | 解释 | -|-----|-----| -| `initializer_list lst;` | 默认初始化;`T`类型元素的空列表 | -| `initializer_list lst{a,b,c...};` | `lst`的元素数量和初始值一样多;`lst`的元素是对应初始值的副本;列表中的元素是`const`。 | -| `lst2(lst)` | 拷贝或赋值一个`initializer_list`对象不会拷贝列表中的元素;拷贝后,原始列表和副本共享元素。 | -| `lst2 = lst` | 同上 | -| `lst.size()` | 列表中的元素数量 | -| `lst.begin()` | 返回指向`lst`中首元素的指针 | -| `lst.end()` | 返回指向`lst`中微元素下一位置的指针 | - -`initializer_list`使用demo: - -```cpp -void err_msg(ErrCode e, initializer_list il){ - cout << e.msg << endl; - for (auto bed = il.begin(); beg != il.end(); ++ beg) - cout << *beg << " "; - cout << endl; -} - -err_msg(ErrCode(0), {"functionX", "okay}); -``` - -- 所有实参类型相同,可以使用 `initializer_list`的标准库类型。 -- 实参类型不同,可以使用`可变参数模板`。 -- 省略形参符: `...`,便于`C++`访问某些C代码,这些C代码使用了 `varargs`的C标准功能。 - -## 返回类型和return语句 - -### 无返回值函数 - -没有返回值的 `return`语句只能用在返回类型是 `void`的函数中,返回 `void`的函数不要求非得有 `return`语句。 - -### 有返回值函数 - -- `return`语句的返回值的类型必须和函数的返回类型相同,或者能够**隐式地**转换成函数的返回类型。 -- 值的返回:返回的值用于初始化调用点的一个**临时量**,该临时量就是函数调用的结果。 -- **不要返回局部对象的引用或指针**。 -- **引用返回左值**:函数的返回类型决定函数调用是否是左值。调用一个返回引用的函数得到左值;其他返回类型得到右值。 -- **列表初始化返回值**:函数可以返回花括号包围的值的列表。(`C++11`) -- **主函数main的返回值**:如果结尾没有`return`,编译器将隐式地插入一条返回0的`return`语句。返回0代表执行成功。 - -### 返回数组指针 - -- `Type (*function (parameter_list))[dimension]` -- 使用类型别名: `typedef int arrT[10];` 或者 `using arrT = int[10;]`,然后 `arrT* func() {...}` -- 使用 `decltype`: `decltype(odd) *arrPtr(int i) {...}` -- **尾置返回类型**: 在形参列表后面以一个`->`开始:`auto func(int i) -> int(*)[10]`(`C++11`) - -## 函数重载 - -- **重载**:如果同一作用域内几个函数名字相同但形参列表不同,我们称之为重载(overload)函数。 -- `main`函数不能重载。 -- **重载和const形参**: - - 一个有顶层const的形参和没有它的函数无法区分。 `Record lookup(Phone* const)`和 `Record lookup(Phone*)`无法区分。 - - 相反,是否有某个底层const形参可以区分。 `Record lookup(Account*)`和 `Record lookup(const Account*)`可以区分。 -- **重载和作用域**:若在内层作用域中声明名字,它将隐藏外层作用域中声明的同名实体,在不同的作用域中无法重载函数名。 - -## 特殊用途语言特性 - -### 默认实参 - -- `string screen(sz ht = 24, sz wid = 80, char backgrnd = ' ');` -- 一旦某个形参被赋予了默认值,那么它之后的形参都必须要有默认值。 - -### 内联(inline)函数 - -- 普通函数的缺点:调用函数比求解等价表达式要慢得多。 -- `inline`函数可以避免函数调用的开销,可以让编译器在编译时**内联地展开**该函数。 -- `inline`函数应该在头文件中定义。 - -### constexpr函数 - -- 指能用于常量表达式的函数。 -- `constexpr int new_sz() {return 42;}` -- 函数的返回类型及所有形参类型都要是字面值类型。 -- `constexpr`函数应该在头文件中定义。 - -### 调试帮助 - -- `assert`预处理宏(preprocessor macro):`assert(expr);` - -开关调试状态: - -`CC -D NDEBUG main.c`可以定义这个变量`NDEBUG`。 - -```cpp -void print(){ - #ifndef NDEBUG - cerr << __func__ << "..." << endl; - #endif -} -``` - -## 函数匹配 - -- 重载函数匹配的**三个步骤**:1.候选函数;2.可行函数;3.寻找最佳匹配。 -- **候选函数**:选定本次调用对应的重载函数集,集合中的函数称为候选函数(candidate function)。 -- **可行函数**:考察本次调用提供的实参,选出可以被这组实参调用的函数,新选出的函数称为可行函数(viable function)。 -- **寻找最佳匹配**:基本思想:实参类型和形参类型越接近,它们匹配地越好。 - -## 函数指针 - -- **函数指针**:是指向函数的指针。 -- `bool (*pf)(const string &, const string &);` 注:两端的括号不可少。 -- **函数指针形参**: - - 形参中使用函数定义或者函数指针定义效果一样。 - - 使用类型别名或者`decltype`。 -- **返回指向函数的指针**:1.类型别名;2.尾置返回类型。 \ No newline at end of file diff --git a/docs/Cpp-Primer/7.类.md b/docs/Cpp-Primer/7.类.md deleted file mode 100644 index 67cc1c6..0000000 --- a/docs/Cpp-Primer/7.类.md +++ /dev/null @@ -1,753 +0,0 @@ ---- -sort: 7 ---- - -# 类 - -类的基本思想是数据抽象(data abstraction)和封装(encapsulation)。数据抽象是一种依赖于接口(interface)和实现(implementation)分离的编程及设计技术。类的接口包括用户所能执行的操作;类的实现包括类的数据成员、负责接口实现的函数体以及其他私有函数。 - -## 定义抽象数据类型(Defining Abstract Data Types) - -### 设计Sales_data类(Designing the Sales_data Class) - -类的用户是程序员,而非应用程序的最终使用者。 - -### 定义改进的Sales_data类(Defining the Revised Sales_data Class) - -成员函数(member function)的声明必须在类的内部,定义则既可以在类的内部也可以在类的外部。定义在类内部的函数是隐式的内联函数。 - -```c++ -struct Sales_data -{ - // new members: operations on Sales_data objects - std::string isbn() const { return bookNo; } - Sales_data& combine(const Sales_data&); - double avg_price() const; - - // data members - std::string bookNo; - unsigned units_sold = 0; - double revenue = 0.0; -}; -``` - -成员函数通过一个名为`this`的隐式额外参数来访问调用它的对象。`this`参数是一个常量指针,被初始化为调用该函数的对象地址。在函数体内可以显式使用`this`指针。 - -```c++ -total.isbn() -// pseudo-code illustration of how a call to a member function is translated -Sales_data::isbn(&total) - -std::string isbn() const { return this->bookNo; } -std::string isbn() const { return bookNo; } -``` - -默认情况下,`this`的类型是指向类类型非常量版本的常量指针。`this`也遵循初始化规则,所以默认不能把`this`绑定到一个常量对象上,即不能在常量对象上调用普通的成员函数。 - -C++允许在成员函数的参数列表后面添加关键字`const`,表示`this`是一个指向常量的指针。使用关键字`const`的成员函数被称作常量成员函数(const member function)。 - -```c++ -// pseudo-code illustration of how the implicit this pointer is used -// this code is illegal: we may not explicitly define the this pointer ourselves -// note that this is a pointer to const because isbn is a const member -std::string Sales_data::isbn(const Sales_data *const this) -{ - return this->isbn; -} -``` - -常量对象和指向常量对象的引用或指针都只能调用常量成员函数。 - -类本身就是一个作用域,成员函数的定义嵌套在类的作用域之内。编译器处理类时,会先编译成员声明,再编译成员函数体(如果有的话),因此成员函数可以随意使用类的其他成员而无须在意这些成员的出现顺序。 - -在类的外部定义成员函数时,成员函数的定义必须与它的声明相匹配。如果成员函数被声明为常量成员函数,那么它的定义也必须在参数列表后面指定`const`属性。同时,类外部定义的成员名字必须包含它所属的类名。 - -```c++ -double Sales_data::avg_price() const -{ - if (units_sold) - return revenue / units_sold; - else - return 0; -} -``` - -可以定义返回`this`对象的成员函数。 - -```c++ -Sales_data& Sales_data::combine(const Sales_data &rhs) -{ - units_sold += rhs.units_sold; // add the members of rhs into - revenue += rhs.revenue; // the members of 'this' object - return *this; // return the object on which the function was called -} -``` - -### 定义类相关的非成员函数(Defining Nonmember Class-Related Functions) - -类的作者通常会定义一些辅助函数,尽管这些函数从概念上来说属于类接口的组成部分,但实际上它们并不属于类本身。 - -```c++ -// input transactions contain ISBN, number of copies sold, and sales price -istream &read(istream &is, Sales_data &item) -{ - double price = 0; - is >> item.bookNo >> item.units_sold >> price; - item.revenue = price * item.units_sold; - return is; -} - -ostream &print(ostream &os, const Sales_data &item) -{ - os << item.isbn() << " " << item.units_sold << " " - << item.revenue << " " << item.avg_price(); - return os; -} -``` - -如果非成员函数是类接口的组成部分,则这些函数的声明应该与类放在同一个头文件中。 - -一般来说,执行输出任务的函数应该尽量减少对格式的控制。 - -### 构造函数(Constructors) - -类通过一个或几个特殊的成员函数来控制其对象的初始化操作,这些函数被称作构造函数。只要类的对象被创建,就会执行构造函数。 - -构造函数的名字和类名相同,没有返回类型,且不能被声明为`const`函数。构造函数在`const`对象的构造过程中可以向其写值。 - -```c++ -struct Sales_data -{ - // constructors added - Sales_data() = default; - Sales_data(const std::string &s): bookNo(s) { } - Sales_data(const std::string &s, unsigned n, double p): - bookNo(s), units_sold(n), revenue(p*n) { } - Sales_data(std::istream &); - // other members as before -}; -``` - -类通过默认构造函数(default constructor)来控制默认初始化过程,默认构造函数无须任何实参。 - -如果类没有显式地定义构造函数,则编译器会为类隐式地定义一个默认构造函数,该构造函数也被称为合成的默认构造函数(synthesized default constructor)。对于大多数类来说,合成的默认构造函数初始化数据成员的规则如下: - -- 如果存在类内初始值,则用它来初始化成员。 - -- 否则默认初始化该成员。 - -某些类不能依赖于合成的默认构造函数。 - -- 只有当类没有声明任何构造函数时,编译器才会自动生成默认构造函数。一旦类定义了其他构造函数,那么除非再显式地定义一个默认的构造函数,否则类将没有默认构造函数。 - -- 如果类包含内置类型或者复合类型的成员,则只有当这些成员全部存在类内初始值时,这个类才适合使用合成的默认构造函数。否则用户在创建类的对象时就可能得到未定义的值。 - -- 编译器不能为某些类合成默认构造函数。例如类中包含一个其他类类型的成员,且该类型没有默认构造函数,那么编译器将无法初始化该成员。 - -在C++11中,如果类需要默认的函数行为,可以通过在参数列表后面添加`=default`来要求编译器生成构造函数。其中`=default`既可以和函数声明一起出现在类的内部,也可以作为定义出现在类的外部。和其他函数一样,如果`=default`在类的内部,则默认构造函数是内联的。 - -```c++ -Sales_data() = default; -``` -构造函数初始值列表(constructor initializer list)负责为新创建对象的一个或几个数据成员赋初始值。形式是每个成员名字后面紧跟括号括起来的(或者在花括号内的)成员初始值,不同成员的初始值通过逗号分隔。 - -```c++ -Sales_data(const std::string &s): bookNo(s) { } -Sales_data(const std::string &s, unsigned n, double p): - bookNo(s), units_sold(n), revenue(p*n) { } -``` -当某个数据成员被构造函数初始值列表忽略时,它会以与合成默认构造函数相同的方式隐式初始化。 - -```c++ -// has the same behavior as the original constructor defined above -Sales_data(const std::string &s): - bookNo(s), units_sold(0), revenue(0) { } -``` - -构造函数不应该轻易覆盖掉类内初始值,除非新值与原值不同。如果编译器不支持类内初始值,则所有构造函数都应该显式初始化每个内置类型的成员。 - -### 拷贝、赋值和析构(Copy、Assignment,and Destruction) - -编译器能合成拷贝、赋值和析构函数,但是对于某些类来说合成的版本无法正常工作。特别是当类需要分配类对象之外的资源时,合成的版本通常会失效。 - -## 访问控制与封装(Access Control and Encapsulation) - -使用访问说明符(access specifier)可以加强类的封装性: - -- 定义在`public`说明符之后的成员在整个程序内都可以被访问。`public`成员定义类的接口。 - -- 定义在`private`说明符之后的成员可以被类的成员函数访问,但是不能被使用该类的代码访问。`private`部分封装了类的实现细节。 - -```c++ -class Sales_data -{ -public: // access specifier added - Sales_data() = default; - Sales_data(const std::string &s, unsigned n, double p): - bookNo(s), units_sold(n), revenue(p*n) { } - Sales_data(const std::string &s): bookNo(s) { } - Sales_data(std::istream&); - std::string isbn() const { return bookNo; } - Sales_data &combine(const Sales_data&); - -private: // access specifier added - double avg_price() const { return units_sold ? revenue/units_sold : 0; } - std::string bookNo; - unsigned units_sold = 0; - double revenue = 0.0; -}; -``` - -一个类可以包含零或多个访问说明符,每个访问说明符指定了接下来的成员的访问级别,其有效范围到出现下一个访问说明符或类的结尾处为止。 - -使用关键字`struct`定义类时,定义在第一个访问说明符之前的成员是`public`的;而使用关键字`class`时,这些成员是`private`的。二者唯一的区别就是默认访问权限不同。 - -### 友元(Friends) - -类可以允许其他类或函数访问它的非公有成员,方法是使用关键字`friend`将其他类或函数声明为它的友元。 - -```C++ -class Sales_data -{ - // friend declarations for nonmember Sales_data operations added - friend Sales_data add(const Sales_data&, const Sales_data&); - friend std::istream &read(std::istream&, Sales_data&); - friend std::ostream &print(std::ostream&, const Sales_data&); - - // other members and access specifiers as before -public: - Sales_data() = default; - Sales_data(const std::string &s, unsigned n, double p): - bookNo(s), units_sold(n), revenue(p*n) { } - Sales_data(const std::string &s): bookNo(s) { } - Sales_data(std::istream&); - std::string isbn() const { return bookNo; } - Sales_data &combine(const Sales_data&); - -private: - std::string bookNo; - unsigned units_sold = 0; - double revenue = 0.0; -}; - -// declarations for nonmember parts of the Sales_data interface -Sales_data add(const Sales_data&, const Sales_data&); -std::istream &read(std::istream&, Sales_data&); -std::ostream &print(std::ostream&, const Sales_data&); -``` - -友元声明只能出现在类定义的内部,具体位置不限。友元不是类的成员,也不受它所在区域访问级别的约束。 - -通常情况下,最好在类定义开始或结束前的位置集中声明友元。 - -封装的好处: - -- 确保用户代码不会无意间破坏封装对象的状态。 - -- 被封装的类的具体实现细节可以随时改变,而无须调整用户级别的代码。 - -友元声明仅仅指定了访问权限,而并非一个通常意义上的函数声明。如果希望类的用户能调用某个友元函数,就必须在友元声明之外再专门对函数进行一次声明(部分编译器没有该限制)。 - -为了使友元对类的用户可见,通常会把友元的声明(类的外部)与类本身放在同一个头文件中。 - -## 类的其他特性(Additional Class Features) - -### 类成员再探(Class Members Revisited) - -由类定义的类型名字和其他成员一样存在访问限制,可以是`public`或`private`中的一种。 - -```c++ -class Screen -{ -public: - // alternative way to declare a type member using a type alias - using pos = std::string::size_type; - // other members as before -}; -``` - -与普通成员不同,用来定义类型的成员必须先定义后使用。类型成员通常位于类起始处。 - -定义在类内部的成员函数是自动内联的。 - -如果需要显式声明内联成员函数,建议只在类外部定义的位置说明`inline`。 - -`inline`成员函数该与类定义在同一个头文件中。 - -使用关键字`mutable`可以声明可变数据成员(mutable data member)。可变数据成员永远不会是`const`的,即使它在`const`对象内。因此`const`成员函数可以修改可变成员的值。 - -```c++ -class Screen -{ -public: - void some_member() const; -private: - mutable size_t access_ctr; // may change even in a const object - // other members as before -}; - -void Screen::some_member() const -{ - ++access_ctr; // keep a count of the calls to any member function - // whatever other work this member needs to do -} -``` - -提供类内初始值时,必须使用`=`或花括号形式。 - -### 返回\*this的成员函数(Functions That Return \*this) - -`const`成员函数如果以引用形式返回`*this`,则返回类型是常量引用。 - -通过区分成员函数是否为`const`的,可以对其进行重载。在常量对象上只能调用`const`版本的函数;在非常量对象上,尽管两个版本都能调用,但会选择非常量版本。 - -```c++ -class Screen -{ -public: - // display overloaded on whether the object is const or not - Screen &display(std::ostream &os) - { do_display(os); return *this; } - const Screen &display(std::ostream &os) const - { do_display(os); return *this; } - -private: - // function to do the work of displaying a Screen - void do_display(std::ostream &os) const - { os << contents; } - // other members as before -}; - -Screen myScreen(5,3); -const Screen blank(5, 3); -myScreen.set('#').display(cout); // calls non const version -blank.display(cout); // calls const version -``` - -### 类类型(Class Types) - -每个类定义了唯一的类型。即使两个类的成员列表完全一致,它们也是不同的类型。 - -可以仅仅声明一个类而暂时不定义它。这种声明被称作前向声明(forward declaration),用于引入类的名字。在类声明之后定义之前都是一个不完全类型(incomplete type)。 - -```c++ -class Screen; // declaration of the Screen class -``` - -可以定义指向不完全类型的指针或引用,也可以声明(不能定义)以不完全类型作为参数或返回类型的函数。 - -只有当类全部完成后才算被定义,所以一个类的成员类型不能是该类本身。但是一旦类的名字出现,就可以被认为是声明过了,因此类可以包含指向它自身类型的引用或指针。 - -```c++ -class Link_screen -{ - Screen window; - Link_screen *next; - Link_screen *prev; -}; -``` - -### 友元再探(Friendship Revisited) - -除了普通函数,类还可以把其他类或其他类的成员函数声明为友元。友元类的成员函数可以访问此类包括非公有成员在内的所有成员。 - -```c++ -class Screen -{ - // Window_mgr members can access the private parts of class Screen - friend class Window_mgr; - // ... rest of the Screen class -}; -``` - -友元函数可以直接定义在类的内部,这种函数是隐式内联的。但是必须在类外部提供相应声明令函数可见。 - -```c++ -struct X -{ - friend void f() { /* friend function can be defined in the class body */ } - X() { f(); } // error: no declaration for f - void g(); - void h(); -}; - -void X::g() { return f(); } // error: f hasn't been declared -void f(); // declares the function defined inside X -void X::h() { return f(); } // ok: declaration for f is now in scope -``` - -友元关系不存在传递性。 - -把其他类的成员函数声明为友元时,必须明确指定该函数所属的类名。 - -```c++ -class Screen -{ - // Window_mgr::clear must have been declared before class Screen - friend void Window_mgr::clear(ScreenIndex); - // ... rest of the Screen class -}; -``` - -如果类想把一组重载函数声明为友元,需要对这组函数中的每一个分别声明。 - -## 类的作用域(Class Scope) - -当成员函数定义在类外时,返回类型中使用的名字位于类的作用域之外,此时返回类型必须指明它是哪个类的成员。 - -```c++ -class Window_mgr -{ -public: - // add a Screen to the window and returns its index - ScreenIndex addScreen(const Screen&); - // other members as before -}; - -// return type is seen before we're in the scope of Window_mgr -Window_mgr::ScreenIndex Window_mgr::addScreen(const Screen &s) -{ - screens.push_back(s); - return screens.size() - 1; -} -``` - -### 名字查找与作用域(Name Lookup and Class Scope) - -成员函数体直到整个类可见后才会被处理,因此它能使用类中定义的任何名字。 - -声明中使用的名字,包括返回类型或参数列表,都必须确保使用前可见。 - -如果类的成员使用了外层作用域的某个名字,而该名字表示一种类型,则类不能在之后重新定义该名字。 - -```c++ -typedef double Money; -class Account -{ -public: - Money balance() { return bal; } // uses Money from the outer scop -private: - typedef double Money; // error: cannot redefine Money - Money bal; - // ... -}; -``` - -类型名定义通常出现在类起始处,这样能确保所有使用该类型的成员都位于类型名定义之后。 - -成员函数中名字的解析顺序: - -- 在成员函数内查找该名字的声明,只有在函数使用之前出现的声明才会被考虑。 - -- 如果在成员函数内没有找到,则会在类内继续查找,这时会考虑类的所有成员。 - -- 如果类内也没有找到,会在成员函数定义之前的作用域查找。 - -```c++ -// it is generally a bad idea to use the same name for a parameter and a member -int height; // defines a name subsequently used inside Screen -class Screen -{ -public: - typedef std::string::size_type pos; - void dummy_fcn(pos height) - { - cursor = width * height; // which height? the parameter - } - -private: - pos cursor = 0; - pos height = 0, width = 0; -}; -``` - -可以通过作用域运算符`::`或显式`this`指针来强制访问被隐藏的类成员。 - -```c++ -// bad practice: names local to member functions shouldn't hide member names -void Screen::dummy_fcn(pos height) -{ - cursor = width * this->height; // member height - // alternative way to indicate the member - cursor = width * Screen::height; // member height -} - -// good practice: don't use a member name for a parameter or other local variable -void Screen::dummy_fcn(pos ht) -{ - cursor = width * height; // member height -} -``` - -## 构造函数再探(Constructors Revisited) - -### 构造函数初始值列表(Constructor Initializer List) - -如果没有在构造函数初始值列表中显式初始化成员,该成员会在构造函数体之前执行默认初始化。 - -如果成员是`const`、引用,或者是某种未定义默认构造函数的类类型,必须在初始值列表中将其初始化。 - -```c++ -class ConstRef -{ -public: - ConstRef(int ii); -private: - int i; - const int ci; - int &ri; -}; - -// ok: explicitly initialize reference and const members -ConstRef::ConstRef(int ii): i(ii), ci(ii), ri(i) { } -``` - -最好令构造函数初始值的顺序与成员声明的顺序一致,并且尽量避免使用某些成员初始化其他成员。 - -如果一个构造函数为所有参数都提供了默认实参,则它实际上也定义了默认构造函数。 - -### 委托构造函数(Delegating Constructors) - -C++11扩展了构造函数初始值功能,可以定义委托构造函数。委托构造函数使用它所属类的其他构造函数执行它自己的初始化过程。 - -```c++ -class Sales_data -{ -public: - // defines the default constructor as well as one that takes a string argument - Sales_data(std::string s = ""): bookNo(s) { } - // remaining constructors unchanged - Sales_data(std::string s, unsigned cnt, double rev): - bookNo(s), units_sold(cnt), revenue(rev*cnt) { } - Sales_data(std::istream &is) { read(is, *this); } - // remaining members as before -} -``` - -### 默认构造函数的作用(The Role of the Default Constructor) - -当对象被默认初始化或值初始化时会自动执行默认构造函数。 - -默认初始化的发生情况: - -- 在块作用域内不使用初始值定义非静态变量或数组。 - -- 类本身含有类类型的成员且使用合成默认构造函数。 - -- 类类型的成员没有在构造函数初始值列表中显式初始化。 - -值初始化的发生情况: - -- 数组初始化时提供的初始值数量少于数组大小。 - -- 不使用初始值定义局部静态变量。 - -- 通过`T()`形式(`T`为类型)的表达式显式地请求值初始化。 - -类必须包含一个默认构造函数。 - -如果想定义一个使用默认构造函数进行初始化的对象,应该去掉对象名后的空括号对。 - -```c++ -Sales_data obj(); // oops! declares a function, not an object -Sales_data obj2; // ok: obj2 is an object, not a function -``` - -### 隐式的类类型转换(Implicit Class-Type Conversions) - -如果构造函数只接受一个实参,则它实际上定义了转换为此类类型的隐式转换机制。这种构造函数被称为转换构造函数(converting constructor)。 - -```c++ -string null_book = "9-999-99999-9"; -// constructs a temporary Sales_data object -// with units_sold and revenue equal to 0 and bookNo equal to null_book -item.combine(null_book); -``` - -编译器只会自动执行一步类型转换。 - -```c++ -// error: requires two user-defined conversions: -// (1) convert "9-999-99999-9" to string -// (2) convert that (temporary) string to Sales_data -item.combine("9-999-99999-9"); -// ok: explicit conversion to string, implicit conversion to Sales_data -item.combine(string("9-999-99999-9")); -// ok: implicit conversion to string, explicit conversion to Sales_data -item.combine(Sales_data("9-999-99999-9")); -``` - -在要求隐式转换的程序上下文中,可以通过将构造函数声明为`explicit`的加以阻止。 - -```c++ -class Sales_data -{ -public: - Sales_data() = default; - Sales_data(const std::string &s, unsigned n, double p): - bookNo(s), units_sold(n), revenue(p*n) { } - explicit Sales_data(const std::string &s): bookNo(s) { } - explicit Sales_data(std::istream&); - // remaining members as before -}; -``` - -`explicit`关键字只对接受一个实参的构造函数有效。 - -只能在类内声明构造函数时使用`explicit`关键字,在类外定义时不能重复。 - -执行拷贝初始化时(使用`=`)会发生隐式转换,所以`explicit`构造函数只能用于直接初始化。 - -```c++ -Sales_data item1 (null_book); // ok: direct initialization -// error: cannot use the copy form of initialization with an explicit constructor -Sales_data item2 = null_book; -``` - -可以使用`explicit`构造函数显式地强制转换类型。 - -```c++ -// ok: the argument is an explicitly constructed Sales_data object -item.combine(Sales_data(null_book)); -// ok: static_cast can use an explicit constructor -item.combine(static_cast(cin)); -``` - -### 聚合类(Aggregate Classes) - -聚合类满足如下条件: - -- 所有成员都是`public`的。 - -- 没有定义任何构造函数。 - -- 没有类内初始值。 - -- 没有基类。 - -- 没有虚函数。 - -```c++ -struct Data -{ - int ival; - string s; -}; -``` - -可以使用一个用花括号包围的成员初始值列表初始化聚合类的数据成员。初始值顺序必须与声明顺序一致。如果初始值列表中的元素个数少于类的成员个数,则靠后的成员被值初始化。 - -```c++ -// val1.ival = 0; val1.s = string("Anna") -Data val1 = { 0, "Anna" }; -``` - -### 字面值常量类(Literal Classes) - -数据成员都是字面值类型的聚合类是字面值常量类。或者一个类不是聚合类,但符合下列条件,则也是字面值常量类: - -- 数据成员都是字面值类型。 - -- 类至少含有一个`constexpr`构造函数。 - -- 如果数据成员含有类内初始值,则内置类型成员的初始值必须是常量表达式。如果成员属于类类型,则初始值必须使用成员自己的`constexpr`构造函数。 - -- 类必须使用析构函数的默认定义。 - -`constexpr`构造函数用于生成`constexpr`对象以及`constexpr`函数的参数或返回类型。 - -`constexpr`构造函数必须初始化所有数据成员,初始值使用`constexpr`构造函数或常量表达式。 - -## 类的静态成员(static Class Members) - -使用关键字`static`可以声明类的静态成员。静态成员存在于任何对象之外,对象中不包含与静态成员相关的数据。 - -```c++ -class Account -{ -public: - void calculate() { amount += amount * interestRate; } - static double rate() { return interestRate; } - static void rate(double); - -private: - std::string owner; - double amount; - static double interestRate; - static double initRate(); -}; -``` - -由于静态成员不与任何对象绑定,因此静态成员函数不能声明为`const`的,也不能在静态成员函数内使用`this`指针。 - -用户代码可以使用作用域运算符访问静态成员,也可以通过类对象、引用或指针访问。类的成员函数可以直接访问静态成员。 - -```c++ -double r; -r = Account::rate(); // access a static member using the scope operator - -Account ac1; -Account *ac2 = &ac1; -// equivalent ways to call the static member rate function -r = ac1.rate(); // through an Account object or reference -r = ac2->rate(); // through a pointer to an Account object - -class Account -{ -public: - void calculate() { amount += amount * interestRate; } -private: - static double interestRate; - // remaining members as before -}; -``` - -在类外部定义静态成员时,不能重复`static`关键字,其只能用于类内部的声明语句。 - -由于静态数据成员不属于类的任何一个对象,因此它们并不是在创建类对象时被定义的。通常情况下,不应该在类内部初始化静态成员。而必须在类外部定义并初始化每个静态成员。一个静态成员只能被定义一次。一旦它被定义,就会一直存在于程序的整个生命周期中。 - -```c++ -// define and initialize a static class member -double Account::interestRate = initRate(); -``` - -建议把静态数据成员的定义与其他非内联函数的定义放在同一个源文件中,这样可以确保对象只被定义一次。 - -尽管在通常情况下,不应该在类内部初始化静态成员。但是可以为静态成员提供`const`整数类型的类内初始值,不过要求静态成员必须是字面值常量类型的`constexpr`。初始值必须是常量表达式。 - -```c++ -class Account -{ -public: - static double rate() { return interestRate; } - static void rate(double); -private: - static constexpr int period = 30; // period is a constant - double daily_tbl[period]; -}; -``` - -静态数据成员的类型可以是它所属的类类型。 - -```c++ -class Bar -{ - static Bar mem1; // ok: static member can have incomplete type - Bar *mem2; // ok: pointer member can have incomplete type - Bar mem3; // error: data members must have complete type -} -``` - -可以使用静态成员作为函数的默认实参。 - -```c++ -class Screen -{ -public: - // bkground refers to the static member - // declared later in the class definition - Screen& clear(char = bkground); -private: - static const char bkground; -}; -``` \ No newline at end of file diff --git a/docs/Cpp-Primer/8.IO库.md b/docs/Cpp-Primer/8.IO库.md deleted file mode 100644 index 814537b..0000000 --- a/docs/Cpp-Primer/8.IO库.md +++ /dev/null @@ -1,111 +0,0 @@ ---- -sort: 8 ---- - -# IO库 - -## 前面章节已经在用的IO库设施 - -- **istream**:输入流类型,提供输入操作。 -- **ostream**:输出流类型,提供输出操作 -- **cin**:一个`istream`对象,从标准输入读取数据。 -- **cout**:一个`ostream`对象,向标准输出写入数据。 -- **cerr**:一个`ostream`对象,向标准错误写入消息。 -- **>>运算符**:用来从一个`istream`对象中读取输入数据。 -- **<<运算符**:用来向一个`ostream`对象中写入输出数据。 -- **getline函数**:从一个给定的`istream`对象中读取一行数据,存入到一个给定的`string`对象中。 - -## IO类 - -### 标准库定义的IO类型 - -- `iostream`头文件:从标准流中读写数据,`istream`、`ostream`等。 -- `fstream`头文件:从文件中读写数据,`ifstream`、`ofstream`等。 -- `sstream`头文件:从字符串中读写数据,`istringstream`、`ostringstream` - -### IO对象不可复制或赋值 - -- 1.IO对象不能存在容器里. -- 2.形参和返回类型也不能是流类型。 -- 3.形参和返回类型一般是流的**引用**。 -- 4.读写一个IO对象会改变其状态,因此传递和返回的引用不能是`const`的。 - -### 条件状态 - -| 状态 | 解释 | -| ------------------- | ------------------------------------------------------------- | -| `strm:iostate` | 是一种机器无关的**类型**,提供了表达条件状态的完整功能 | -| `strm:badbit` | 用来指出流已经崩溃 | -| `strm:failbit` | 用来指出一个IO操作失败了 | -| `strm:eofbit` | 用来指出流到达了文件结束 | -| `strm:goodbit` | 用来指出流未处于错误状态,此值保证为零 | -| `s.eof()` | 若流`s`的`eofbit`置位,则返回`true` | -| `s.fail()` | 若流`s`的`failbit`置位,则返回`true` | -| `s.bad()` | 若流`s`的`badbit`置位,则返回`true` | -| `s.good()` | 若流`s`处于有效状态,则返回`true` | -| `s.clear()` | 将流`s`中所有条件状态位复位,将流的状态设置成有效,返回`void` | -| `s.clear(flags)` | 将流`s`中指定的条件状态位复位,返回`void` | -| `s.setstate(flags)` | 根据给定的标志位,将流`s`中对应的条件状态位置位,返回`void` | -| `s.rdstate()` | 返回流`s`的当前条件状态,返回值类型为`strm::iostate` | - -上表中,`strm`是一种IO类型,(如`istream`), `s`是一个流对象。 - -### 管理输出缓冲 - -- 每个输出流都管理一个缓冲区,执行输出的代码,文本串可能立即打印出来,也可能被操作系统保存在缓冲区内,随后再打印。 -- 刷新缓冲区,可以使用如下IO操纵符: - - `endl`:输出一个换行符并刷新缓冲区。 - - `flush`:刷新流,单不添加任何字符。 - - `ends`:在缓冲区插入空字符`null`,然后刷新。 - - `unitbuf`:告诉流接下来每次操作之后都要进行一次`flush`操作。 - - `nounitbuf`:回到正常的缓冲方式。 - -## 文件输入输出 - -- 头文件`fstream`定义了三个类型来支持文件IO: - - `ifstream`从一个给定文件读取数据。 - - `ofstream`向一个给定文件写入数据。 - - `fstream`可以读写给定文件。 -- **文件流**:需要读写文件时,必须定义自己的文件流对象,并绑定在需要的文件上。 - -### fstream特有的操作 - -| 操作 | 解释 | -| ------------------------- | ------------------------------------------------------------------------ | -| `fstream fstrm;` | 创建一个未绑定的文件流。 | -| `fstream fstrm(s);` | 创建一个文件流,并打开名为`s`的文件,`s`可以是`string`也可以是`char`指针 | -| `fstream fstrm(s, mode);` | 与前一个构造函数类似,但按指定`mode`打开文件 | -| `fstrm.open(s)` | 打开名为`s`的文件,并和`fstrm`绑定 | -| `fstrm.close()` | 关闭和`fstrm`绑定的文件 | -| `fstrm.is_open()` | 返回一个`bool`值,指出与`fstrm`关联的文件是否成功打开且尚未关闭 | - -上表中,`fstream`是头文件`fstream`中定义的一个类型,`fstrm`是一个文件流对象。 - -### 文件模式 - -| 文件模式 | 解释 | -| -------- | ---------------------------- | -| `in` | 以读的方式打开 | -| `out` | 以写的方式打开 | -| `app` | 每次写操作前均定位到文件末尾 | -| `ate` | 打开文件后立即定位到文件末尾 | -| `trunc` | 截断文件 | -| `binary` | 以二进制方式进行IO操作。 | - -## string流 - -- 头文件`sstream`定义了三个类型来支持内存IO: - - `istringstream`从`string`读取数据。 - - `ostringstream`向`string`写入数据。 - - `stringstream`可以读写给定`string`。 - -### stringstream特有的操作 - -| 操作 | 解释 | -| ----------------- | ---------------------------------- | -| `sstream strm` | 定义一个未绑定的`stringstream`对象 | -| `sstream strm(s)` | 用`s`初始化对象 | -| `strm.str()` | 返回`strm`所保存的`string`的拷贝 | -| `strm.str(s)` | 将`s`拷贝到`strm`中,返回`void` | - -上表中`sstream`是头文件`sstream`中任意一个类型。`s`是一个`string`。 \ No newline at end of file diff --git a/docs/Cpp-Primer/9.顺序容器.md b/docs/Cpp-Primer/9.顺序容器.md deleted file mode 100644 index 43f7621..0000000 --- a/docs/Cpp-Primer/9.顺序容器.md +++ /dev/null @@ -1,345 +0,0 @@ ---- -sort: 9 ---- - -# 顺序容器 - -## 顺序容器概述 - -- **顺序容器**(sequential container):为程序员提供了控制元素存储和访问顺序的能力。这种顺序不依赖于元素的值,而是与元素加入容器时的位置相对应。 - -### 顺序容器类型 - -| 容器类型 | 介绍 | -| -------------- | ----------------------------------------------------------------------------- | -| `vector` | 可变大小数组。支持快速随机访问。在尾部之外的位置插入或删除元素可能很慢。 | -| `deque` | 双端队列。支持快速随机访问。在头尾位置插入/删除速度很快。 | -| `list` | 双向链表。只支持双向顺序访问。在`list`中任何位置进行插入/删除操作速度都很快。 | -| `forward_list` | 单向链表。只支持单向顺序访问。在链表任何位置进行插入/删除操作速度都很快。 | -| `array` | 固定大小数组。支持快速随机访问。不能添加或者删除元素。 | -| `string` | 与`vector`相似的容器,但专门用于保存字符。随机访问块。在尾部插入/删除速度快。 | - -- 除了固定大小的`array`外,其他容器都提供高效、灵活的内存管理。 -- `forward_list`和`array`是新C++标准增加的类型。 -- 通常使用`vector`是最好的选择,除非你有很好的理由选择其他容器。 -- 新标准库的容器比旧版的快得多。 - -## 容器操作 - -### 类型 - -| 操作 | 解释 | -| ----------------- | -------------------------------------------------- | -| `iterator` | 此容器类型的迭代器类型 | -| `const_iterator` | 可以读取元素但不能修改元素的迭代器类型 | -| `size_type` | 无符号整数类型,足够保存此种容器类型最大可能的大小 | -| `difference_type` | 带符号整数类型,足够保存两个迭代器之间的距离 | -| `value_type` | 元素类型 | -| `reference` | 元素的左值类型;和`value_type &`含义相同 | -| `const_reference` | 元素的`const`左值类型,即`const value_type &` | - -### 构造函数 - -| 操作 | 解释 | -| ------------------------- | ---------------------------------------------------------------------- | -| `C c;` | 默认构造函数,构造空容器 | -| `C c1(c2);`或`C c1 = c2;` | 构造`c2`的拷贝`c1` | -| `C c(b, e)` | 构造`c`,将迭代器`b`和`e`指定范围内的所有元素拷贝到`c` | -| `C c(a, b, c...)` | 列表初始化`c` | -| `C c(n)` | 只支持顺序容器,且不包括`array`,包含`n`个元素,这些元素进行了值初始化 | -| `C c(n, t)` | 包含`n`个初始值为`t`的元素 | - -- 只有顺序容器的构造函数才接受大小参数,关联容器并不支持。 -- `array`具有固定大小。 -- 和其他容器不同,默认构造的`array`是非空的。 -- 直接复制:将一个容器复制给另一个容器时,类型必须匹配:容器类型和元素类型都必须相同。 -- 使用迭代器复制:不要求容器类型相同,容器内的元素类型也可以不同。 - -### 赋值和`swap` - -| 操作 | 解释 | -| ------------------- | ------------------------------------------------------------------------------ | -| `c1 = c2;` | 将`c1`中的元素替换成`c2`中的元素 | -| `c1 = {a, b, c...}` | 将`c1`中的元素替换成列表中的元素(不适用于`array`) | -| `c1.swap(c2)` | 交换`c1`和`c2`的元素 | -| `swap(c1, c2)` | 等价于`c1.swap(c2)` | -| `c.assign(b, e)` | 将`c`中的元素替换成迭代器`b`和`e`表示范围中的元素,`b`和`e`不能指向`c`中的元素 | -| `c.assign(il)` | 将`c`中的元素替换成初始化列表`il`中的元素 | -| `c.assign(n, r)` | 将`c`中的元素替换为`n`个值是`t`的元素 | - -- 使用非成员版本的`swap`是一个好习惯。 -- `assign`操作不适用于关联容器和`array` - -### 大小 - -| 操作 | 解释 | -| -------------- | ---------------------------------------------- | -| `c.size()` | `c`中元素的数目(不支持`forward_list`) | -| `c.max_size()` | `c`中可保存的最大元素数目 | -| `c.empty()` | 若`c`中存储了元素,返回`false`,否则返回`true` | - -### 添加元素 - -| 操作 | 解释 | -| ----------------------- | ---------------------------------------------------------------------------------------------------- | -| `c.push_back(t)` | 在`c`尾部创建一个值为`t`的元素,返回`void` | -| `c.emplace_back(args)` | 同上 | -| `c.push_front(t)` | 在`c`头部创建一个值为`t`的元素,返回`void` | -| `c.emplace_front(args)` | 同上 | -| `c.insert(p, t)` | 在迭代器`p`指向的元素之前创建一个值是`t`的元素,返回指向新元素的迭代器 | -| `c.emplace(p, args)` | 同上 | -| `c.insert(p, n, t)` | 在迭代器`p`指向的元素之前插入`n`个值为`t`的元素,返回指向第一个新元素的迭代器;如果`n`是0,则返回`p` | -| `c.insert(p, b, e)` | 将迭代器`b`和`e`范围内的元素,插入到`p`指向的元素之前;如果范围为空,则返回`p` | -| `c.insert(p, il)` | `il`是一个花括号包围中的元素值列表,将其插入到`p`指向的元素之前;如果`il`是空,则返回`p` | - -- 因为这些操作会改变大小,因此不适用于`array`。 -- `forward_list`有自己专有版本的`insert`和`emplace`。 -- `forward_list`不支持`push_back`和`emplace_back`。 -- 当我们用一个对象去初始化容器或者将对象插入到容器时,实际上放入的是对象的拷贝。 -- `emplace`开头的函数是新标准引入的,这些操作是构造而不是拷贝元素。 -- 传递给`emplace`的参数必须和元素类型的构造函数相匹配。 - -### 访问元素 - -| 操作 | 解释 | -| ----------- | ---------------------------------------------------------------------------------------- | -| `c.back()` | 返回`c`中尾元素的引用。若`c`为空,函数行为未定义 | -| `c.front()` | 返回`c`中头元素的引用。若`c`为空,函数行为未定义 | -| `c[n]` | 返回`c`中下标是`n`的元素的引用,`n`时候一个无符号证书。若`n>=c.size()`,则函数行为未定义 | -| `c.at(n)` | 返回下标为`n`的元素引用。如果下标越界,则抛出`out_of_range`异常 | - -- 访问成员函数返回的是引用。 -- `at`和下标操作只适用于`string`、`vector`、`deque`、`array`。 -- `back`不适用于`forward_list`。 -- 如果希望下标是合法的,可以使用`at`函数。 - -### 删除元素 - -| 操作 | 解释 | -| --------------- | ------------------------------------------------------------------------------------------------------------------- | -| `c.pop_back()` | 删除`c`中尾元素,若`c`为空,则函数行为未定义。函数返回`void` | -| `c.pop_front()` | 删除`c`中首元素,若`c`为空,则函数行为未定义。函数返回`void` | -| `c.erase(p)` | 删除迭代器`p`指向的元素,返回一个指向被删除元素之后的元素的迭代器,若`p`本身是尾后迭代器,则函数行为未定义 | -| `c.erase(b, e)` | 删除迭代器`b`和`e`范围内的元素,返回指向最后一个被删元素之后元素的迭代器,若`e`本身就是尾后迭代器,则返回尾后迭代器 | -| `c.clear()` | 删除`c`中所有元素,返回`void` | - -- 会改变容器大小,不适用于`array`。 -- `forward_list`有特殊版本的`erase` -- `forward_list`不支持`pop_back` -- `vector`和`string`不支持`pop_front` - -### 特殊的forwad_list操作 - -- 链表在删除元素时需要修改前置节点的内容,双向链表会前驱的指针,但是单向链表没有保存,因此需要增加获取前置节点的方法。 -- `forward_list`定义了`before_begin`,即首前(off-the-begining)迭代器,允许我们再在首元素之前添加或删除元素。 - -| 操作 | 解释 | -| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | -| `lst.before_begin()` | 返回指向链表首元素之前不存在的元素的迭代器,此迭代器不能解引用。 | -| `lst.cbefore_begin()` | 同上,但是返回的是常量迭代器。 | -| `lst.insert_after(p, t)` | 在迭代器`p`之后插入元素。`t`是一个对象 | -| `lst.insert_after(p, n, t)` | 在迭代器`p`之后插入元素。`t`是一个对象,`n`是数量。若`n`是0则函数行为未定义 | -| `lst.insert_after(p, b, e)` | 在迭代器`p`之后插入元素。由迭代器`b`和`e`指定范围。 | -| `lst.insert_after(p, il)` | 在迭代器`p`之后插入元素。由`il`指定初始化列表。 | -| `emplace_after(p, args)` | 使用`args`在`p`之后的位置,创建一个元素,返回一个指向这个新元素的迭代器。若`p`为尾后迭代器,则函数行为未定义。 | -| `lst.erase_after(p)` | 删除`p`指向位置之后的元素,返回一个指向被删元素之后的元素的迭代器,若`p`指向`lst`的尾元素或者是一个尾后迭代器,则函数行为未定义。 | -| `lst.erase_after(b, e)` | 类似上面,删除对象换成从`b`到`e`指定的范围。 | - -### 改变容器大小 - -| 操作 | 解释 | -| ---------------- | ---------------------------------------------------------------------------------------------------- | -| `c.resize(n)` | 调整`c`的大小为`n`个元素,若`n s2.size()`,则构造函数的行为未定义。 | -| `string s(s2, pos2, len2)` | `s`是`string s2`从下标`pos2`开始的`len2`个字符的拷贝。 | - -- `n`,`len2`,`pos2`都是无符号值。 - -### substr操作 - -| 操作 | 解释 | -| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------- | -| `s.substr(pos, n)` | 返回一个`string`,包含`s`中从`pos`开始的`n`个字符的拷贝。`pos`的默认值是0,`n`的默认值是`s.size() - pos`,即拷贝从`pos`开始的所有字符。 | - -### 改变string的其他方法 - -| 操作 | 解释 | -| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- | -| `s.insert(pos, args)` | 在`pos`之前插入`args`指定的字符。`pos`可以使是下标或者迭代器。接受下标的版本返回指向`s`的引用;接受迭代器的版本返回指向第一个插入字符的迭代器。 | -| `s.erase(pos, len)` | 删除从`pos`开始的`len`个字符,如果`len`被省略,则删除后面所有字符,返回指向`s`的引用。 | -| `s.assign(args)` | 将`s`中的字符替换成`args`指定的字符。返回一个指向`s`的引用。 | -| `s.append(args)` | 将`args`指定的字符追加到`s`,返回一个指向`s`的引用。 | -| `s.replace(range, args)` | 删除`s`中范围`range`中的字符,替换成`args`指定的字符。返回一个指向`s`的引用。 | - -### string搜索操作 - -- `string`类提供了6个不同的搜索函数,每个函数都有4个重载版本。 -- 每个搜索操作都返回一个`string::size_type`值,表示匹配发生位置的下标。如果搜索失败则返回一个名为`string::npos`的`static`成员(类型是`string::size_type`,初始化值是-1,也就是`string`最大的可能大小)。 - -| 搜索操作 | 解释 | -| --------------------------- | ------------------------------------------------- | -| `s.find(args)` | 查找`s`中`args`第一次出现的位置 | -| `s.rfind(args)` | 查找`s`中`args`最后一次出现的位置 | -| `s.find_first_of(args)` | 在`s`中查找`args`中任何一个字符第一次出现的位置 | -| `s.find_last_of(args)` | 在`s`中查找`args`中任何一个字符最后一次出现的位置 | -| `s.find_first_not_of(args)` | 在`s`中查找第一个不在`args`中的字符 | -| `s.find_first_not_of(args)` | 在`s`中查找最后一个不在`args`中的字符 | - -args必须是一下的形式之一: - -| `args`形式 | 解释 | -| ------------ | ----------------------------------------------------------------------------- | -| `c, pos` | 从`s`中位置`pos`开始查找字符`c`。`pos`默认是0 | -| `s2, pos` | 从`s`中位置`pos`开始查找字符串`s`。`pos`默认是0 | -| `cp, pos` | 从`s`中位置`pos`开始查找指针`cp`指向的以空字符结尾的C风格字符串。`pos`默认是0 | -| `cp, pos, n` | 从`s`中位置`pos`开始查找指针`cp`指向的前`n`个字符。`pos`和`n`无默认值。 | - -### s.compare的几种参数形式 - -逻辑类似于C标准库的`strcmp`函数,根据`s`是等于、大于还是小于参数指定的字符串,`s.compare`返回0、正数或负数。 - -| 参数形式 | 解释 | -| ------------------------ | ----------------------------------------------------------------- | -| `s2` | 比较`s`和`s2` | -| `pos1, n1, s2` | 比较`s`从`pos1`开始的`n1`个字符和`s2` | -| `pos1, n1, s2, pos2, n2` | 比较`s`从`pos1`开始的`n1`个字符和`s2` | -| `cp` | 比较`s`和`cp`指向的以空字符结尾的字符数组 | -| `pos1, n1, cp` | 比较`s`从`pos1`开始的`n1`个字符和`cp`指向的以空字符结尾的字符数组 | -| `pos1, n1, cp, n2` | 比较`s`从`pos1`开始的`n1`个字符和`cp`指向的地址开始`n2`个字符 | - -### string和数值转换 - -| 转换 | 解释 | -| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `to_string(val)` | 一组重载函数,返回数值`val`的`string`表示。`val`可以使任何算术类型。对每个浮点类型和`int`或更大的整型,都有相应版本的`to_string()`。和往常一样,小整型会被提升。 | -| `stoi(s, p, b)` | 返回`s`起始子串(表示整数内容)的数值,`p`是`s`中第一个非数值字符的下标,默认是0,`b`是转换所用的基数。返回`int` | -| `stol(s, p, b)` | 返回`long` | -| `stoul(s, p, b)` | 返回`unsigned long` | -| `stoll(s, p, b)` | 返回`long long` | -| `stoull(s, p, b)` | 返回`unsigned long long` | -| `stof(s, p)` | 返回`s`起始子串(表示浮点数内容)的数值,`p`是`s`中第一个非数值字符的下标,默认是0。返回`float` | -| `stod(s, p)` | 返回`double` | -| `stold(s, p)` | 返回`long double` | - -## 容器适配器(adapter) - -- 适配器是使一事物的行为类似于另一事物的行为的一种机制,例如`stack`可以使任何一种顺序容器以栈的方式工作。 -- 初始化 `deque deq; stack stk(deq);` 从`deq`拷贝元素到`stk`。 -- 创建适配器时,指定一个顺序容器,可以覆盖默认的基础容器: `stack > str_stk;`。 - -### 适配器的通用操作和类型 - -| 操作 | 解释 | -| ---------------- | ------------------------------------------------------------------------------------------------- | -| `size_type` | 一种类型,须以保存当前类型的最大对象的大小 | -| `value_type` | 元素类型 | -| `container_type` | 实现适配器的底层容器类型 | -| `A a;` | 创建一个名为`a`的空适配器 | -| `A a(c)` | 创建一个名为`a`的适配器,带有容器`c`的一个拷贝 | -| 关系运算符 | 每个适配器都支持所有关系运算符:`==`、`!=`、`<`、 `<=`、`>`、`>=`这些运算符返回底层容器的比较结果 | -| `a.empty()` | 若`a`包含任何元素,返回`false`;否则返回`true` | -| `a.size()` | 返回`a`中的元素数目 | -| `swap(a, b)` | 交换`a`和`b`的内容,`a`和`b`必须有相同类型,包括底层容器类型也必须相同 | -| `a.swap(b)` | 同上 | - -### stack - -| 操作 | 解释 | -| ----------------- | -------------------------------------------------------- | -| `s.pop()` | 删除栈顶元素,不返回。 | -| `s.push(item)` | 创建一个新元素,压入栈顶,该元素通过拷贝或移动`item`而来 | -| `s.emplace(args)` | 同上,但元素由`args`来构造。 | -| `s.top()` | 返回栈顶元素,不删除。 | - -- 定义在`stack`头文件中。 -- `stack`默认基于`deque`实现,也可以在`list`或`vector`之上实现。 - -### queue和priority_queue - -| 操作 | 解释 | -| ----------------- | ----------------------------------------- | -| `q.pop()` | 删除队首元素,但不返回。 | -| `q.front()` | 返回队首元素的值,不删除。 | -| `q.back()` | 返回队尾元素的值,不删除。只适用于`queue` | -| `q.top()` | 返回具有最高优先级的元素值,不删除。 | -| `q.push(item)` | 在队尾压入一个新元素。 | -| `q.emplace(args)` | | - -- 定义在`queue`头文件中。 -- `queue`默认基于`deque`实现,`priority_queue`默认基于`vector`实现。 -- `queue`可以在`list`或`vector`之上实现,`priority_queue`也可以用`deque`实现。 \ No newline at end of file diff --git a/docs/Cpp-Primer/README.md b/docs/Cpp-Primer/README.md deleted file mode 100644 index dad9082..0000000 --- a/docs/Cpp-Primer/README.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -sort: 1 ---- - -# C++ Primer 第5版学习笔记 - -![book](../images/c-primer-plus.png) - -## 目录 - -[第1章 开始](./1.开始.md) - -### 第I部分 C++基础 - - -[第2章 变量和基本类型](./2.变量和基本类型.md) - -[第3章 字符串、向量和数组](./3.字符串、向量和数组.md) - -[第4章 表达式](./4.表达式.md) - -[第5章 语句](./5.语句.md) - -[第6章 函数](./6.函数.md) - -[第7章 类](./7.类.md) - - -### 第II部分 C++标准库 - -[第8章 IO库](./8.IO库.md) - -[第9章 顺序容器](./9.顺序容器.md) - -[第10章 泛型算法](./10.泛型算法.md) - -[第11章 关联容器](./11.关联容器.md) - -[第12章 动态内存](./12.动态内存.md) - -### 第III部分 类设计者的工具 - -[第13章 拷贝控制](./13.拷贝控制.md) - -[第14章 操作重载与类型转换](./14.操作重载与类型转换.md) - -[第15章 面向对象程序设计](./15.面向对象程序设计.md) - -[第16章 模板与泛型编程](./16.模板与泛型编程.md) - -### 第IV部分 高级主题 - -[第17章 标准库特殊设施](./17.标准库特殊设施.md) - -[第18章 用于大型程序的工具](./18.用于大型程序的工具.md) - -[第19章 特殊工具与技术](./19.特殊工具与技术.md) \ No newline at end of file diff --git a/docs/Design-Patterns/1.设计模式简介.md b/docs/Design-Patterns/1.设计模式简介.md deleted file mode 100644 index fcbcc25..0000000 --- a/docs/Design-Patterns/1.设计模式简介.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -sort: 1 ---- - -# C++设计模式 - -## 目标 -- 理解松耦合设计思想 -- 掌握面向对象设计原则 -- 掌握重构技法改善设计 -- 掌握GOF 核心设计模式 - -## 从面向对象谈起 - -### 底层思维:向下,如何把握机器底层从微观理解对象构造 - -- 语言构造 -- 编译转换 -- 内存模型 -- 运行时机制 - -### 抽象思维:向上,如何将我们的周围世界抽象为程序代码 - -- 面向对象 -- 组件封装 -- 设计模式 -- 架构模式 - -## 深入理解面向对象 - -### 向下: -深入理解三大面向对象机制 - -- 封装,隐藏内部实现 -- 继承,复用现有代码 -- 多态,改写对象行为 - -### 向上: -深刻把握面向对象机制所带来的抽象意义,理解如何使用 -这些机制来表达现实世界,掌握什么是“好的面向对象设计” - -## 如何解决复杂性? - -### 分解 - -人们面对复杂性有一个常见的做法:即分而治之,将大问题分 -解为多个小问题,将复杂问题分解为多个简单问题。 - -### 抽象 - -更高层次来讲,人们处理复杂性有一个通用的技术,即抽象。 -由于不能掌握全部的复杂对象,我们选择忽视它的非本质细节, -而去处理泛化和理想化了的对象模型。 - -## 软件设计的目标 - -什么是好的软件设计?软件设计的金科玉律: - -**复用!** \ No newline at end of file diff --git a/docs/Design-Patterns/2.面向对象设计原则.md b/docs/Design-Patterns/2.面向对象设计原则.md deleted file mode 100644 index 276e037..0000000 --- a/docs/Design-Patterns/2.面向对象设计原则.md +++ /dev/null @@ -1,66 +0,0 @@ ---- -sort: 2 ---- - -# 面向对象设计原则 - -## 面向对象设计,为什么? - -变化是复用的天敌! - -面向对象设计最大的优势在于: - -**抵御变化!** - -## 重新认识面向对象 - -- 理解隔离变化 - - 从宏观层面来看,面向对象的构建方式更能适应软件的变化,能将变化所带来的影响减为最小 -- 各司其职 - - 从微观层面来看,面向对象的方式更强调各个类的“责任” - - 由于需求变化导致的新增类型不应该影响原来类型的实现——是所谓各负其责 -- 对象是什么? - - 从语言实现层面来看,对象封装了代码和数据。 - - 从规格层面讲,对象是一系列可被使用的公共接口。 - - 从概念层面讲,对象是某种拥有责任的抽象。 - -## 面向对象设计原则 - -- 依赖倒置原则(DIP) - - 高层模块(稳定)不应该依赖于低层模块(变化),二者都应该依赖于抽象(稳定) 。 - - 抽象(稳定)不应该依赖于实现细节(变化) ,实现细节应该依赖于抽象(稳定)。 -- 开放封闭原则(OCP) - - 对扩展开放,对更改封闭。 - - 类模块应该是可扩展的,但是不可修改 -- 单一职责原则(SRP) - - 一个类应该仅有一个引起它变化的原因。 - - 变化的方向隐含着类的责任。 -- Liskov 替换原则(LSP) - - 子类必须能够替换它们的基类(IS-A) - - 继承表达类型抽象。 -- 接口隔离原则(ISP) - - 不应该强迫客户程序依赖它们不用的方法。 - - 接口应该小而完备。 -- 优先使用对象组合,而不是类继承 - - 类继承通常为“白箱复用”,对象组合通常为“黑箱复用”。 - - 继承在某种程度上破坏了封装性,子类父类耦合度高。 - - 而对象组合则只要求被组合的对象具有良好定义的接口,耦合度低。 -- 封装变化点 - - 使用封装来创建对象之间的分界层,让设计者可以在分界层的一侧进行修改,而不会对另一侧产生不良的影响,从而实现层次间的松耦合。 -- 针对接口编程,而不是针对实现编程 - - 不将变量类型声明为某个特定的具体类,而是声明为某个接口。 - - 客户程序无需获知对象的具体类型,只需要知道对象所具有的接口。 - - 减少系统中各部分的依赖关系,从而实现“高内聚、松耦合”的类型设计方案。 - -## 面向接口设计 - -产业强盛的标志:**接口标准化!** - -## 将设计原则提升为设计经验 - -- 设计习语 Design Idioms - - Design Idioms 描述与特定编程语言相关的低层模式,技巧,惯用法。 -- 设计模式 Design Patterns - - Design Patterns主要描述的是“类与相互通信的对象之间的组织关系,包括它们的角色、职责、协作方式等方面。 - - 架构模式 Architectural Patterns - - Architectural Patterns描述系统中与基本结构组织关系密切的高层模式,包括子系统划分,职责,以及如何组织它们之间关系的规则。 diff --git a/docs/Design-Patterns/README.md b/docs/Design-Patterns/README.md deleted file mode 100644 index b49bfd3..0000000 --- a/docs/Design-Patterns/README.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -sort: 8 ---- - -# C++设计模式 - -{% include list.liquid %} - - diff --git a/docs/Effective-Modern-C++/README.md b/docs/Effective-Modern-C++/README.md deleted file mode 100644 index c155d48..0000000 --- a/docs/Effective-Modern-C++/README.md +++ /dev/null @@ -1,109 +0,0 @@ ---- -sort: 3 ---- - -# Effective Modern C++ 笔记 - -![book](../images/Effective-Modern-C++.jpg) - -## 目录 - -### 第一章 型别推导 - -[条款1:理解模板型别推导](./item1.md) - -[条款2:理解auto型别推导](./item2.md) - -[条款3:理解decltype](./item3.md) - -[条款4:掌握查看型别推导结果的方法](./item4.md) - - -### 第二章 auto - -[条款5:优先选用auto,而非显式型别声明](./item5.md) - -[条款6:当auto推导的型别不符合要求时,使用带显式型别的初始化物习惯用法](./item6.md) - - -### 第三章 转向现代C++ - -[条款7:在创建对象时注意区分()和{}](./item7.md) - -[条款8:优先选用nullptr,而非0或NULL](./item8.md) - -[条款9:优先选用别名声明,而非typedef](./item9.md) - -[条款10:优先选用限定作用域的枚举型别,而非不限作用域的枚举型别](./item10.md) - -[条款11:优先选用删除函数,而非private未定义函数](./item11.md) - -[条款12:为意在改写的函数添加override声明](./item12.md) - -[条款13:优先选用const_iterator,而非iterator](./item13.md) - -[条款14:只要函数不会发射异常,就为其加上noexcept声明](./item14.md) - -[条款15:只要有可能使用constexpr,就使用它](./item15.md) - -[条款16:保证const成员函数的线程安全性](./item16.md) - -[条款17:理解特种成员函数的生成机制](./item17.md) - -### 第四章 智能指针 - -[条款18:使用std::unique_ptr管理具备专属所有权的资源](./item18.md) - -[条款19:使用std::shared_ptr管理具备共享所有权的资源](./item19.md) - -[条款20:对于类似std::shared_ptr但有可能空悬的指针使用std::weak_ptr](./item20.md) - -[条款21:优先选用std::make_unique和std::make_shared,而非直接使用new](./item21.md) - -[条款22:使用Pimpl习惯用法时,将特殊成员函数的定义放在实现文件中](./item22.md) -### 第五章 右值引用、移动语义和完美转发 - -[条款23:理解std::move和std::forward](./item23.md) - -[条款24:区分万能引用和右值引用](./item24.md) - -[条款25:针对右值引用实施std::move,针对万能引用实施std::forward](./item25.md) - -[条款26:避免依万能引用型别进行重载](./item26.md) - -[条款27:熟悉依万能引用型别进行重载的替代方案](./item27.md) - -[条款28:理解引用折叠](./item28.md) - -[条款29:假定移动操作不存在、成本高、未使用](./item29.md) - -[条款30:熟悉完美转发的失败情形](./item30.md) - -### 第六章 lambda表达式 - -[条款31:避免默认捕获模式](./item31.md) - -[条款32:使用初始化捕获将对象移入闭包](./item32.md) - -[条款33:对auto&&型别的形参使用decltype,以std::forward之](./item33.md) - -[条款34:优先选用lambda式,而非std::bind](./item34.md) - -### 第七章 并发API - -[35:优先选用基于任务而非基于线程的程序设计](./item35.md) - -[36:如果异步是必要的,则指定std::lauch::async](./item36.md) - -[37:使std::thread型别对象在所有路径皆不可联结](./item37.md) - -[38:对变化多端的线程句柄析构函数行为保持关注](./item38.md) - -[39:考虑针对一次性事件通信使用以void为模板性别实参的期值](./item39.md) - -[40:对并发使用std::atomic,对特种内存使用volatile](./item40.md) -### 第8章 微调 - -[41:针对可复制的形参,在移动成本低并且一定会被复制的前提下,考虑将其按值传递](./item41.md) - -[42:考虑置入而非插入](./item42.md) \ No newline at end of file diff --git a/docs/Effective-Modern-C++/item1.md b/docs/Effective-Modern-C++/item1.md deleted file mode 100644 index 9b1ece2..0000000 --- a/docs/Effective-Modern-C++/item1.md +++ /dev/null @@ -1,304 +0,0 @@ ---- -sort: 1 ---- - -# 条款1:型别推导 - -**学习目标:** 模板型别推导如何运作,`auto `的型别推导如何构建在此运作规则之上,以及`decltype`独特的型别推导规则,如何迫使编译器来展示其型别推导的结果。 - -## 条款1:理解模板型别推导 - -* 模板型别推导是 `auto` 的基础,但部分特殊情况下,模板型别推导机制不适用于 `auto` -* 模板的形式可以看成如下伪代码 - -```cpp -template -void f(ParamType x); // ParamType 即 x 的类型 -``` - -* 调用可看成 - -```cpp -f(expr); -``` - -* 编译期间,编译器用 `expr` 推断 `T` 和 `ParamType` ,实际上两者通常不一致,比如 - -```cpp -template -void f(const T& x); - -int x; // 为方便演示,只指定类型不初始化,后续同理 -f(x); // T 被推断为 int,ParamType 被推断为 const int& -``` - -* T 的类型推断与 expr 和 ParamType 相关 - - -### 情形 1:ParamType 是个引用或指针,但不是万能引用 - -最简单的情况是当`ParamType`是一个引用类型或者是一个指针,但并非是万能引用。在这种情况下,类型推导的过程如下: - -* 如果`expr`的类型是个引用,忽略引用的部分。 -* 然后利用`expr`的类型和`ParamType`对比去判断`T`的类型。 - -举一个例子,如果这个是我们的模板, - -```cpp -template -void f(T& param); // param是一个引用类型 -``` - -我们有这样的代码变量声明: - -```cpp -int x = 27; // x是一个int -const int cx = x; // cx是一个const int -const int& rx = x; // rx是const int的引用 -``` - -`param`和`T`在不同的调用下面的类型推导如下: - -```cpp -f(x); // T是int,param的类型时int& - -f(cx); // T是const int, - // param的类型是const int& -f(rx); // T是const int - // param的类型时const int& -``` - -在第二和第三部分的调用,注意`cx`和`rx`由于被指定为`const`类型变量,`T`被推导成`const int`,这也就导致了参数的类型被推导为`const int&`。这对调用者非常重要。 - -当传递一个`const`对象给一个引用参数,他们期望对象会保留常量特性,也就是说,参数变成了`const`的引用。这也就是为什么给一个以`T&`为参数的模板传递一个`const`对象是安全的:对象的`const`特性是`T`类型推导的一部分。 - -在第三个例子中,注意尽管`rx`的类型是一个引用,`T`仍然被推导成了一个非引用的。这是因为`rx`的引用特性会被类型推导所忽略。 - -这些例子展示了左值引用参数的处理方式,但是类型推导在右值引用上也是如此。当然,右值参数只可能传递给右值引用参数,但是这个限制和类型推导没有关系。 - -如果我们把`f`的参数类型从`T&`变成`const T&`,情况就会发生变化,但是并不会令人惊讶。由于`param`的声明是`const`引用的,`cx`和`rx`的`const`特性会被保留,这样的话`T`的`const`特性就没有必要了。 - -```cpp -template -void f(const T& param); // param现在是const的引用 - -int x = 27; // 和之前一样 -const int cx = x; // 和之前一样 -const int& rx = x; // 和之前一样 - -f(x); // T是int,param的类型是const int& - -f(cx); // T是int,param的类型是const int& - -f(rx); // T是int,param的类型是const int& -``` - -和之前一样,`rx`的引用特性在类型推导的过程中会被忽略。 - -如果`param`是一个指针(或者指向`const`的指针)而不是引用,情况也是类似: - -```cpp -template -void f(T* param); // param是一个指针 - -int x = 27; // 和之前一样 -const int *px = &x; // px是一个指向const int x的指针 - -f(&x); // T是int,param的类型是int* - -f(px); // T是const int - // param的类型时const int* -``` - - - -### 情形2:`ParamType`是个万能引用(Universal Reference) - -对于通用的引用参数,情况就变得不是那么明显了。这些参数被声明成右值引用(也就是函数模板使用一个类型参数`T`,一个通用的引用参数的申明类型是`T&&`),但是当传递进去右值参数情况变得不一样。完整的讨论请参考条款24,这里是先行版本。 - -- 如果`expr`是一个左值,`T`和`ParamType`都会被推导成左值引用。这有些不同寻常。第一,这是模板类型`T`被推导成一个引用的唯一情况。第二,尽管`ParamType`利用右值引用的语法来进行推导,但是他最终推导出来的类型是左值引用。 -- 如果`expr`是一个右值,那么就执行“普通”的法则(第一种情况) - -举个例子: - -```cpp -template -void f(T&& param); // param现在是一个通用的引用 - -int x = 27; // 和之前一样 -const int cx = x; // 和之前一样 -const int& rx = x; // 和之前一样 - -f(x); // x是左值,所以T是int& - // param的类型也是int& - -f(cx); // cx是左值,所以T是const int& - // param的类型也是const int& - -f(rx); // rx是左值,所以T是const int& - // param的类型也是const int& - -f(27); // 27是右值,所以T是int - // 所以param的类型是int&& -``` - -条款23解释了这个例子推导的原因。关键的地方在于通用引用的类型推导法则和左值引用或者右值引用的法则大不相同。特殊的情况下,当使用了通用的引用,左值参数和右值参数的类型推导大不相同。这在非通用的类型推到上面绝对不会发生。 - - - -### 情形3:`ParamType`既不是指针也不是引用 - -当`ParamType`既不是指针也不是引用时,我们通过传值(pass-by-value)的方式处理: - -``` -template -void f(T param); //以传值的方式处理param -``` - -这意味着无论传递什么`param`都会成为它的一份拷贝——一个完整的新对象。事实上`param`成为一个新对象这一行为会影响`T`如何从`expr`中推导出结果。 - -1. 和之前一样,如果`expr`的类型是一个引用,忽略这个引用部分 -2. 如果忽略`expr`的引用性(reference-ness)之后,`expr`是一个`const`,那就再忽略`const`。如果它是`volatile`,也忽略`volatile`(`volatile`对象不常见,它通常用于驱动程序的开发中。关于`volatile`的细节请参见[Item40](./item40.md)) - -因此 - -``` -int x=27; //如之前一样 -const int cx=x; //如之前一样 -const int & rx=cx; //如之前一样 - -f(x); //T和param的类型都是int -f(cx); //T和param的类型都是int -f(rx); //T和param的类型都是int -``` - -注意即使`cx`和`rx`表示`const`值,`param`也不是`const`。这是有意义的。`param`是一个完全独立于`cx`和`rx`的对象——是`cx`或`rx`的一个拷贝。具有常量性的`cx`和`rx`不可修改并不代表`param`也是一样。这就是为什么`expr`的常量性`const`ness(或易变性`volatile`ness)在推导`param`类型时会被忽略:因为`expr`不可修改并不意味着它的拷贝也不能被修改。 - -认识到只有在传值给形参时才会忽略`const`(和`volatile`)这一点很重要,正如我们看到的,对于reference-to-`const`和pointer-to-`const`形参来说,`expr`的常量性`const`ness在推导时会被保留。但是考虑这样的情况,`expr`是一个`const`指针,指向`const`对象,`expr`通过传值传递给`param`: - -``` -template -void f(T param); //仍然以传值的方式处理param - -const char* const ptr = //ptr是一个常量指针,指向常量对象 - "Fun with pointers"; - -f(ptr); //传递const char * const类型的实参 -``` - -在这里,解引用符号(*)的右边的`const`表示`ptr`本身是一个`const`:`ptr`不能被修改为指向其它地址,也不能被设置为null(解引用符号左边的`const`表示`ptr`指向一个字符串,这个字符串是`const`,因此字符串不能被修改)。当`ptr`作为实参传给`f`,组成这个指针的每一比特都被拷贝进`param`。像这种情况,`ptr`**自身的值会被传给形参**,根据类型推导的第三条规则,`ptr`自身的常量性`const`ness将会被省略,所以`param`是`const char*`,也就是一个可变指针指向`const`字符串。在类型推导中,这个指针指向的数据的常量性`const`ness将会被保留,但是当拷贝`ptr`来创造一个新指针`param`时,`ptr`自身的常量性`const`ness将会被忽略。 - -### 数组实参 - -上面的内容几乎覆盖了模板类型推导的大部分内容,但这里还有一些小细节值得注意,比如数组类型不同于指针类型,虽然它们两个有时候是可互换的。关于这个错觉最常见的例子是,在很多上下文中数组会退化为指向它的第一个元素的指针。这样的退化允许像这样的代码可以被编译: - -``` -const char name[] = "J. P. Briggs"; //name的类型是const char[13] - -const char * ptrToName = name; //数组退化为指针 -``` - -在这里`const char*`指针`ptrToName`会由`name`初始化,而`name`的类型为`const char[13]`,这两种类型(`const char*`和`const char[13]`)是不一样的,但是由于数组退化为指针的规则,编译器允许这样的代码。 - -但要是一个数组传值给一个模板会怎样?会发生什么? - -``` -template -void f(T param); //传值形参的模板 - -f(name); //T和param会推导成什么类型? -``` - -我们从一个简单的例子开始,这里有一个函数的形参是数组,是的,这样的语法是合法的, - -``` -void myFunc(int param[]); -``` - -但是数组声明会被视作指针声明,这意味着`myFunc`的声明和下面声明是等价的: - -``` -void myFunc(int* param); //与上面相同的函数 -``` - -数组与指针形参这样的等价是C语言的产物,C++又是建立在C语言的基础上,它让人产生了一种数组和指针是等价的的错觉。 - -因为数组形参会视作指针形参,所以传值给模板的一个数组类型会被推导为一个指针类型。这意味着在模板函数`f`的调用中,它的类型形参`T`会被推导为`const char*`: - -``` -f(name); //name是一个数组,但是T被推导为const char* -``` - -但是现在难题来了,虽然函数不能声明形参为真正的数组,但是**可以**接受指向数组的**引用**!所以我们修改`f`为传引用: - -``` -template -void f(T& param); //传引用形参的模板 -``` - -我们这样进行调用, - -``` -f(name); //传数组给f -``` - -`T`被推导为了真正的数组!这个类型包括了数组的大小,在这个例子中`T`被推导为`const char[13]`,`f`的形参(对这个数组的引用)的类型则为`const char (&)[13]`。是的,这种语法看起来简直有毒,但是知道它将会让你在关心这些问题的人的提问中获得大神的称号。 - -有趣的是,可声明指向数组的引用的能力,使得我们可以创建一个模板函数来推导出数组的大小: - -``` -//在编译期间返回一个数组大小的常量值(//数组形参没有名字, -//因为我们只关心数组的大小) -template //关于 -constexpr std::size_t arraySize(T (&)[N]) noexcept //constexpr -{ //和noexcept - return N; //的信息 -} //请看下面 -``` - -在[Item15](./item15.md)提到将一个函数声明为`constexpr`使得结果在编译期间可用。这使得我们可以用一个花括号声明一个数组,然后第二个数组可以使用第一个数组的大小作为它的大小,就像这样: - -``` -int keyVals[] = { 1, 3, 7, 9, 11, 22, 35 }; //keyVals有七个元素 - -int mappedVals[arraySize(keyVals)]; //mappedVals也有七个 -``` - -当然作为一个现代C++程序员,你自然应该想到使用`std::array`而不是内置的数组: - -``` -std::array mappedVals; //mappedVals的大小为7 -``` - -至于`arraySize`被声明为`noexcept`,会使得编译器生成更好的代码,具体的细节请参见[Item14](./item14.md)。 - -### 函数实参 - -在C++中不只是数组会退化为指针,函数类型也会退化为一个函数指针,我们对于数组类型推导的全部讨论都可以应用到函数类型推导和退化为函数指针上来。结果是: - -``` -void someFunc(int, double); //someFunc是一个函数, - //类型是void(int, double) - -template -void f1(T param); //传值给f1 - -template -void f2(T & param); //传引用给f2 - -f1(someFunc); //param被推导为指向函数的指针, - //类型是void(*)(int, double) -f2(someFunc); //param被推导为指向函数的引用, - //类型是void(&)(int, double) -``` - -这个实际上没有什么不同,但是如果你知道数组退化为指针,你也会知道函数退化为指针。 - -这里你需要知道:`auto`依赖于模板类型推导。正如我在开始谈论的,在大多数情况下它们的行为很直接。在通用引用中对于左值的特殊处理使得本来很直接的行为变得有些污点,然而,数组和函数退化为指针把这团水搅得更浑浊。有时你只需要编译器告诉你推导出的类型是什么。这种情况下,翻到[item4](./item4.md),它会告诉你如何让编译器这么做。 - -**总结:** - -- 在模板类型推导时,有引用的实参会被视为无引用,他们的引用会被忽略 -- 对于通用引用的推导,左值实参会被特殊对待 -- 对于传值类型推导,`const`和/或`volatile`实参会被认为是non-`const`的和non-`volatile`的 -- 在模板类型推导时,数组名或者函数名实参会退化为指针,除非它们被用于初始化引用 diff --git a/docs/Effective-Modern-C++/item2.md b/docs/Effective-Modern-C++/item2.md deleted file mode 100644 index b89886d..0000000 --- a/docs/Effective-Modern-C++/item2.md +++ /dev/null @@ -1,209 +0,0 @@ ---- -sort: 2 ---- - - - -# 条款2:理解auto型别推导 - -如果你已经读过[Item1](./item1.md)的模板类型推导,那么你几乎已经知道了`auto`类型推导的大部分内容,至于为什么不是全部是因为这里有一个`auto`不同于模板类型推导的例外。但这怎么可能?模板类型推导包括模板,函数,形参,但`auto`不处理这些东西啊。 - -`auto`类型推导和模板类型推导有一个直接的映射关系。它们之间可以通过一个非常规范、系统化的转换流程来转换彼此。 - -在[Item1](./item1.md)中,模板类型推导使用下面这个函数模板 - -``` -template -void f(ParmaType param); -``` - -和这个调用来解释: - -``` -f(expr); //使用一些表达式调用f -``` - -在`f`的调用中,编译器使用`expr`推导`T`和`ParamType`的类型。 - -当一个变量使用`auto`进行声明时,`auto`扮演了模板中`T`的角色,变量的类型说明符扮演了`ParamType`的角色。废话少说,这里便是更直观的代码描述,考虑这个例子: - -``` -auto x = 27; -``` - -这里`x`的类型说明符是`auto`自己,另一方面,在这个声明中: - -``` -const auto cx = x; -``` - -类型说明符是`const auto`。另一个: - -``` -const auto & rx=cx; -``` - -类型说明符是`const auto&`。在这里例子中要推导`x`,`rx`和`cx`的类型,编译器的行为看起来就像是认为这里每个声明都有一个模板,然后使用合适的初始化表达式进行调用: - -``` -template //概念化的模板用来推导x的类型 -void func_for_x(T param); - -func_for_x(27); //概念化调用: - //param的推导类型是x的类型 - -template //概念化的模板用来推导cx的类型 -void func_for_cx(const T param); - -func_for_cx(x); //概念化调用: - //param的推导类型是cx的类型 - -template //概念化的模板用来推导rx的类型 -void func_for_rx(const T & param); - -func_for_rx(x); //概念化调用: - //param的推导类型是rx的类型 -``` - -正如我说的,`auto`类型推导除了一个例外(我们很快就会讨论),其他情况都和模板类型推导一样。 - -[Item1](./item1.md)基于`ParamType`——在函数模板中`param`的类型说明符——的不同特征,把模板类型推导分成三个部分来讨论。在使用`auto`作为类型说明符的变量声明中,类型说明符代替了`ParamType`,因此Item1描述的三个情景稍作修改就能适用于auto: - -- 情形一:类型说明符是一个指针或引用但不是万能引用 -- 情形二:类型说明符一个万能引用 -- 情形三:类型说明符既不是指针也不是引用 - -我们早已看过情形一和情形三的例子: - -``` -auto x = 27; //情景三(x既不是指针也不是引用) -const auto cx = x; //情景三(cx也一样) -const auto & rx=cx; //情景一(rx是非通用引用) -``` - -情景二像你期待的一样运作: - -``` -auto&& uref1 = x; //x是int左值, - //所以uref1类型为int& -auto&& uref2 = cx; //cx是const int左值, - //所以uref2类型为const int& -auto&& uref3 = 27; //27是int右值, - //所以uref3类型为int&& -``` - -[Item1](./item1.md)讨论并总结了对于non-reference类型说明符,数组和函数名如何退化为指针。那些内容也同样适用于`auto`类型推导: - -``` -const char name[] = //name的类型是const char[13] - "R. N. Briggs"; - -auto arr1 = name; //arr1的类型是const char* -auto& arr2 = name; //arr2的类型是const char (&)[13] - -void someFunc(int, double); //someFunc是一个函数, - //类型为void(int, double) - -auto func1 = someFunc; //func1的类型是void (*)(int, double) -auto& func2 = someFunc; //func2的类型是void (&)(int, double) -``` - -就像你看到的那样,`auto`类型推导和模板类型推导几乎一样的工作,它们就像一个硬币的两面。 - -讨论完相同点接下来就是不同点,前面我们已经说到`auto`类型推导和模板类型推导有一个例外使得它们的工作方式不同,接下来我们要讨论的就是那个例外。 我们从一个简单的例子开始,如果你想声明一个带有初始值27的`int`,C++98提供两种语法选择: - -``` -int x1 = 27; -int x2(27); -``` - -C++11由于也添加了用于支持统一初始化(**uniform initialization**)的语法: - -``` -int x3 = { 27 }; -int x4{ 27 }; -``` - -总之,这四种不同的语法只会产生一个相同的结果:变量类型为`int`值为27 - -但是[Item5](./item5.md)解释了使用`auto`说明符代替指定类型说明符的好处,所以我们应该很乐意把上面声明中的`int`替换为`auto`,我们会得到这样的代码: - -``` -auto x1 = 27; -auto x2(27); -auto x3 = { 27 }; -auto x4{ 27 }; -``` - -这些声明都能通过编译,但是他们不像替换之前那样有相同的意义。前面两个语句确实声明了一个类型为`int`值为27的变量,但是后面两个声明了一个存储一个元素27的 `std::initializer_list`类型的变量。 - -``` -auto x1 = 27; //类型是int,值是27 -auto x2(27); //同上 -auto x3 = { 27 }; //类型是std::initializer_list, - //值是{ 27 } -auto x4{ 27 }; //同上 -``` - -这就造成了`auto`类型推导不同于模板类型推导的特殊情况。当用`auto`声明的变量使用花括号进行初始化,`auto`类型推导推出的类型则为`std::initializer_list`。如果这样的一个类型不能被成功推导(比如花括号里面包含的是不同类型的变量),编译器会拒绝这样的代码: - -``` -auto x5 = { 1, 2, 3.0 }; //错误!无法推导std::initializer_list中的T -``` - -就像注释说的那样,在这种情况下类型推导将会失败,但是对我们来说认识到这里确实发生了两种类型推导是很重要的。一种是由于`auto`的使用:`x5`的类型不得不被推导。因为`x5`使用花括号的方式进行初始化,`x5`必须被推导为`std::initializer_list`。但是`std::initializer_list`是一个模板。`std::initializer_list`会被某种类型`T`实例化,所以这意味着`T`也会被推导。 推导落入了这里发生的第二种类型推导——模板类型推导的范围。在这个例子中推导之所以失败,是因为在花括号中的值并不是同一种类型。 - -对于花括号的处理是`auto`类型推导和模板类型推导唯一不同的地方。当使用`auto`声明的变量使用花括号的语法进行初始化的时候,会推导出`std::initializer_list`的实例化,但是对于模板类型推导这样就行不通: - -``` -auto x = { 11, 23, 9 }; //x的类型是std::initializer_list - -template //带有与x的声明等价的 -void f(T param); //形参声明的模板 - -f({ 11, 23, 9 }); //错误!不能推导出T -``` - -然而如果在模板中指定`T`是`std::initializer_list`而留下未知`T`,模板类型推导就能正常工作: - -``` -template -void f(std::initializer_list initList); - -f({ 11, 23, 9 }); //T被推导为int,initList的类型为 - //std::initializer_list -``` - -因此`auto`类型推导和模板类型推导的真正区别在于,`auto`类型推导假定花括号表示`std::initializer_list`而模板类型推导不会这样(确切的说是不知道怎么办)。 - -你可能想知道为什么`auto`类型推导和模板类型推导对于花括号有不同的处理方式。我也想知道。哎,我至今没找到一个令人信服的解释。但是规则就是规则,这意味着你必须记住如果你使用`auto`声明一个变量,并用花括号进行初始化,`auto`类型推导总会得出`std::initializer_list`的结果。如果你使用**uniform initialization(花括号的方式进行初始化)**用得很爽你就得记住这个例外以免犯错,在C++11编程中一个典型的错误就是偶然使用了`std::initializer_list`类型的变量,这个陷阱也导致了很多C++程序员抛弃花括号初始化,只有不得不使用的时候再做考虑。(在[Item7](./item7.md)讨论了必须使用时该怎么做) - -对于C++11故事已经说完了。但是对于C++14故事还在继续,C++14允许`auto`用于函数返回值并会被推导(参见[Item3](./item3.md)),而且C++14的*lambda*函数也允许在形参声明中使用`auto`。但是在这些情况下`auto`实际上使用**模板类型推导**的那一套规则在工作,而不是`auto`类型推导,所以说下面这样的代码不会通过编译: - -``` -auto createInitList() -{ - return { 1, 2, 3 }; //错误!不能推导{ 1, 2, 3 }的类型 -} -``` - -同样在C++14的lambda函数中这样使用auto也不能通过编译: - -``` -std::vector v; -… -auto resetV = - [&v](const auto& newValue){ v = newValue; }; //C++14 -… -resetV({ 1, 2, 3 }); //错误!不能推导{ 1, 2, 3 }的类型 -``` - -**总结:** - -- `auto`类型推导通常和模板类型推导相同,但是`auto`类型推导假定花括号初始化代表`std::initializer_list`,而模板类型推导不这样做 -- 在C++14中`auto`允许出现在函数返回值或者*lambda*函数形参中,但是它的工作机制是模板类型推导那一套方案,而不是`auto`类型推导 - - - - - diff --git a/docs/Effective-Modern-C++/item3.md b/docs/Effective-Modern-C++/item3.md deleted file mode 100644 index c806048..0000000 --- a/docs/Effective-Modern-C++/item3.md +++ /dev/null @@ -1,137 +0,0 @@ ---- -sort: 3 ---- - -# 条款3:理解decltype - - -* decltype 会推断出直觉预期的类型 - -```cpp -const int i = 0; // decltype(i) 为 const int - -struct Point { - int x, y; // decltype(Point::x) 和 decltype(Point::y) 为 int -}; - -A a; // decltype(a) 为 A -bool f(const A& x); // decltype(x) 为 const A&,decltype(f) 为 bool(const A&) -if (f(a)) { // decltype(f(a)) 为 bool -} - -int a[]{1, 2, 3}; // decltype(a) 为 int[3] -``` - -* decltype 一般用来声明与参数类型相关的返回类型。比如下面模板的参数是容器和索引,而返回类型取决于元素类型 - -```cpp -template -auto f(Container& c, Index i) -> decltype(c[i]) { - return c[i]; // auto 只表示使用类型推断,推断的是 decltype -} -``` - -* C++14 允许省略尾置返回类型,只留下 auto - -```cpp -template -auto f(Container& c, Index i) { - return c[i]; -} -``` - -* 但直接使用会发现问题 - -```cpp -std::vector v{1, 2, 3}; -f(v, 1) = 42; // 返回 v[1] 然后赋值为 42,但不能通过编译 -``` - -* operator[] 返回元素引用,类型为 int&,但 auto 推断为 int,因此上面的操作相当于给一个整型值赋值,显然是错误的 -* 为了得到期望的返回类型,需要对返回类型使用 decltype 的推断机制,C++14 允许将返回类型声明为 decltype(auto) 来实现这点 - -```cpp -template -decltype(auto) f(Container& c, Index i) { - return c[i]; -} -``` - -* decltype(auto) 也可以作为变量声明类型 - -```cpp -int i = 1; -const int& j = i; -decltype(auto) x = j; // const int& x = j; -``` - -* 但还有一些问题,容器传的是 non-const 左值引用,这就无法接受右值 - -```cpp -std::vector makeV(); // 工厂函数 -auto i = f(makeV(), 5); -``` - -* 为了同时匹配左值和右值而又不想重载,只需要模板参数写为转发引用 - -```cpp -template -decltype(auto) f(Container&& c, Index i) { - return std::forward(c)[i]; // 传入的实参是右值时,将 c 转为右值 -} - -// C++11版本 -template -auto f(Container&& c, Index i) -> decltype(std::forward(c)[i]) { - authenticateUser(); - return std::forward(c)[i]; -} -``` - -### decltype的特殊情况 - -* 如果表达式是解引用,decltype 会推断为引用类型 - -```cpp -int* p; // decltype(*p) 是 int& -``` - -* 赋值表达式会产生引用,类型为赋值表达式中左值的引用类型 - -```cpp -int a = 0; -int b = 1; -decltype(a = 1) c = b; // int& -c = 3; -std::cout << a << b << c; // 033 -``` - -* 如果表达式加上一层或多层括号,编译器会将其看作表达式,变量是一种可以作为赋值语句左值的特殊表达式,因此也得到引用类型。decltype((variable)) 结果永远是引用,declytpe(variable) 只有当变量本身是引用时才是引用 - -```cpp -int i; // decltype((i)) 是 int& -``` - -* 在返回类型为 decltype(auto) 时,这可能导致返回局部变量的引用 - -```cpp -decltype(auto) f1() { - int x = 0; - return x; // decltype(x) 是 int,因此返回 int -} - -decltype(auto) f2() { - int x = 0; - return (x); // decltype((x)) 是 int&,因此返回了局部变量的引用 -} -``` - -| 总结: | -| ------------------------------------------------------------ | -| `decltype`几乎总是得到一个变量或表达式的类型而不需要任何修改 | -| 对于非变量名的类型为`T`的左值表达式,`decltype`总是返回`T&` | -| `C++14`支持`decltype(auto)`,它的行为就像`auto`,从初始化操作来推导类型,但是它推导类型时使用`decltype`的规则 | - - - - diff --git a/docs/Effective-Modern-C++/item4.md b/docs/Effective-Modern-C++/item4.md deleted file mode 100644 index 13e92fc..0000000 --- a/docs/Effective-Modern-C++/item4.md +++ /dev/null @@ -1,199 +0,0 @@ ---- -sort: 4 ---- - - -# 条款4:掌握查看型别推导结果的方法 - -选择使用工具查看类型推导,取决于软件开发过程中你想在哪个阶段显示类型推导信息。我们探究三种方案:在你编辑代码的时候获得类型推导的结果,在编译期间获得结果,在运行时获得结果。 - -### IDE编辑器 - -在IDE中的代码编辑器通常可以显示程序代码中变量,函数,参数的类型,你只需要简单的把鼠标移到它们的上面,举个例子,有这样的代码中: - -``` -const int theAnswer = 42; - -auto x = theAnswer; -auto y = &theAnswer; -``` - -IDE编辑器可以直接显示`x`推导的结果为`int`,`y`推导的结果为`const int*`。 - -为此,你的代码必须或多或少的处于可编译状态,因为IDE之所以能提供这些信息是因为一个C++编译器(或者至少是前端中的一个部分)运行于IDE中。如果这个编译器对你的代码不能做出有意义的分析或者推导,它就不会显示推导的结果。 - -对于像`int`这样简单的推导,IDE产生的信息通常令人很满意。正如我们将看到的,如果更复杂的类型出现时,IDE提供的信息就几乎没有什么用了。 - -### 编译器诊断 - -另一个获得推导结果的方法是使用编译器出错时提供的错误消息。这些错误消息无形的提到了造成我们编译错误的类型是什么。 - -举个例子,假如我们想看到之前那段代码中`x`和`y`的类型,我们可以首先声明一个类模板但**不定义**。就像这样: - -``` -template //只对TD进行声明 -class TD; //TD == "Type Displayer" -``` - -如果尝试实例化这个类模板就会引出一个错误消息,因为这里没有用来实例化的类模板定义。为了查看`x`和`y`的类型,只需要使用它们的类型去实例化`TD`: - -``` -TD xType; //引出包含x和y -TD yType; //的类型的错误消息 -``` - -我使用***variableName*****Type**的结构来命名变量,因为这样它们产生的错误消息可以有助于我们查找。对于上面的代码,我的编译器产生了这样的错误信息,我取一部分贴到下面: - -``` -error: aggregate 'TD xType' has incomplete type and - cannot be defined -error: aggregate 'TD yType' has incomplete type and - cannot be defined -``` - -另一个编译器也产生了一样的错误,只是格式稍微改变了一下: - -``` -error: 'xType' uses undefined class 'TD' -error: 'yType' uses undefined class 'TD' -``` - -除了格式不同外,几乎所有我测试过的编译器都产生了这样有用的错误消息。 - -### 运行时输出 - -使用`printf`的方法使类型信息只有在运行时才会显示出来(尽管我不是非常建议你使用`printf`),但是它提供了一种格式化输出的方法。现在唯一的问题是只需对于你关心的变量使用一种优雅的文本表示。“这有什么难的,“你这样想,”这正是`typeid`和`std::type_info::name`的价值所在”。为了实现我们想要查看`x`和`y`的类型的需求,你可能会这样写: - -``` -std::cout << typeid(x).name() << '\n'; //显示x和y的类型 -std::cout << typeid(y).name() << '\n'; -``` - -这种方法对一个对象如`x`或`y`调用`typeid`产生一个`std::type_info`的对象,然后`std::type_info`里面的成员函数`name()`来产生一个C风格的字符串(即一个`const char*`)表示变量的名字。 - -调用`std::type_info::name`不保证返回任何有意义的东西,但是库的实现者尝试尽量使它们返回的结果有用。实现者们对于“有用”有不同的理解。举个例子,GNU和Clang环境下`x`的类型会显示为”`i`“,`y`会显示为”`PKi`“,这样的输出你必须要问问编译器实现者们才能知道他们的意义:”`i`“表示”`int`“,”`PK`“表示”pointer to ~~`konst`~~ `const`“(指向常量的指针)。(这些编译器都提供一个工具`c++filt`,解释这些“混乱的”类型)Microsoft的编译器输出得更直白一些:对于`x`输出”`int`“对于`y`输出”`int const *`“ - -因为对于`x`和`y`来说这样的结果是正确的,你可能认为问题已经接近了,别急,考虑一个更复杂的例子: - -``` -template //要调用的模板函数 -void f(const T& param); - -std::vector createVec(); //工厂函数 - -const auto vw = createVec(); //使用工厂函数返回值初始化vw - -if (!vw.empty()){ - f(&vw[0]); //调用f - … -} -``` - -在这段代码中包含了一个用户定义的类型`Widget`,一个STL容器`std::vector`和一个`auto`变量`vw`,这个更现实的情况是你可能在会遇到的并且想获得他们类型推导的结果,比如模板类型形参`T`,比如函数`f`形参`param`。 - -从这里中我们不难看出`typeid`的问题所在。我们在`f`中添加一些代码来显示类型: - -``` -template -void f(const T& param) -{ - using std::cout; - cout << "T = " << typeid(T).name() << '\n'; //显示T - - cout << "param = " << typeid(param).name() << '\n'; //显示 - … //param -} //的类型 -``` - -GNU和Clang执行这段代码将会输出这样的结果 - -``` -T = PK6Widget -param = PK6Widget -``` - -我们早就知道在这些编译器中`PK`表示“pointer to `const`”,所以只有数字`6`对我们来说是神奇的。其实数字是类名称(`Widget`)的字符串长度,所以这些编译器告诉我们`T`和`param`都是`const Widget*`。 - -Microsoft的编译器也同意上述言论: - -``` -T = class Widget const * -param = class Widget const * -``` - -这三个独立的编译器产生了相同的信息并表示信息非常准确,当然看起来不是那么准确。在模板`f`中,`param`的声明类型是`const T&`。难道你们不觉得`T`和`param`类型相同很奇怪吗?比如`T`是`int`,`param`的类型应该是`const int&`而不是相同类型才对吧。 - -遗憾的是,事实就是这样,`std::type_info::name`的结果并不总是可信的,就像上面一样,三个编译器对`param`的报告都是错误的。因为它们本质上可以不正确,因为`std::type_info::name`规范批准像传值形参一样来对待这些类型。正如[Item1](./item1.md)提到的,如果传递的是一个引用,那么引用部分(reference-ness)将被忽略,如果忽略后还具有`const`或者`volatile`,那么常量性`const`ness或者易变性`volatile`ness也会被忽略。那就是为什么`param`的类型`const Widget * const &`会输出为`const Widget *`,首先引用被忽略,然后这个指针自身的常量性`const`ness被忽略,剩下的就是指针指向一个常量对象。 - -同样遗憾的是,IDE编辑器显示的类型信息也不总是可靠的,或者说不总是有用的。还是一样的例子,一个IDE编辑器可能会把`T`的类型显示为(我没有胡编乱造): - -``` -const -std::_Simple_types>::_Alloc>::value_type>::value_type * -``` - -同样把`param`的类型显示为 - -``` -const std::_Simple_types<...>::value_type *const & -``` - -这个比起`T`来说要简单一些,但是如果你不知道“`...`”表示编译器忽略`T`的部分类型那么可能你还是会产生困惑。如果你运气好点你的IDE可能表现得比这个要好一些。 - -比起运气如果你更倾向于依赖库,那么你乐意被告知`std::type_info::name`和IDE不怎么好,Boost TypeIndex库(通常写作**Boost.TypeIndex**)是更好的选择。这个库不是标准C++的一部分,也不是IDE或者`TD`这样的模板。Boost库(可在[boost.com](http://boost.org/)获得)是跨平台,开源,有良好的开源协议的库,这意味着使用Boost和STL一样具有高度可移植性。 - -这里是如何使用Boost.TypeIndex得到`f`的类型的代码 - -``` -#include - -template -void f(const T& param) -{ - using std::cout; - using boost::typeindex::type_id_with_cvr; - - //显示T - cout << "T = " - << type_id_with_cvr().pretty_name() - << '\n'; - - //显示param类型 - cout << "param = " - << type_id_with_cvr().pretty_name() - << '\n'; -} -``` - -`boost::typeindex::type_id_with_cvr`获取一个类型实参(我们想获得相应信息的那个类型),它不消除实参的`const`,`volatile`和引用修饰符(因此模板名中有“`with_cur`”)。结果是一个`boost::typeindex::type_index`对象,它的`pretty_name`成员函数输出一个`std::string`,包含我们能看懂的类型表示。 基于这个`f`的实现版本,再次考虑那个使用`typeid`时获取`param`类型信息出错的调用: - -``` -std::vetor createVec(); //工厂函数 -const auto vw = createVec(); //使用工厂函数返回值初始化vw -if (!vw.empty()){ - f(&vw[0]); //调用f - … -} -``` - -在GNU和Clang的编译器环境下,使用Boost.TypeIndex版本的`f`最后会产生下面的(准确的)输出: - -``` -T = Widget const * -param = Widget const * const& -``` - -在Microsoft的编译器环境下,结果也是极其相似: - -``` -T = class Widget const * -param = class Widget const * const & -``` - -这样近乎一致的结果是很不错的,但是请记住IDE,编译器错误诊断或者像Boost.TypeIndex这样的库只是用来帮助你理解编译器推导的类型是什么。它们是有用的,但是作为本章结束语我想说它们根本不能替代你对[Item1](./item1.md)-[3](./item3.md)提到的类型推导的理解。 - -**总结:** - -- 类型推断可以从IDE、编译器报错和Boost TypeIndex库的使用可以看出 -- 这些工具可能既不准确也无帮助,所以理解C++类型推导规则才是最重要的 \ No newline at end of file diff --git a/docs/Effective-Modern-C++/item5.md b/docs/Effective-Modern-C++/item5.md deleted file mode 100644 index b425567..0000000 --- a/docs/Effective-Modern-C++/item5.md +++ /dev/null @@ -1,162 +0,0 @@ ---- -sort: 5 ---- - - -# 条款5:优先考虑`auto`而非显式类型声 - -``` -int x; -``` - -由于未初始化`x`,所以`x`的值是不确定的。它可能会被初始化为0,这得取决于工作环境。 - - 对一个局部变量使用解引用迭代器的方式初始化: - -``` -template //对从b到e的所有元素使用 -void dwim(It b, It e) //dwim(“do what I mean”)算法 -{ - while (b != e) { - typename std::iterator_traits::value_type - currValue = *b; - … - } -} -``` - -`typename std::iterator_traits::value_type`是想表达迭代器指向的元素的值的类型吗?我无论如何都说不出它是多么有趣这样的话,该死!等等,我早就说过了吗? - -好吧,声明一个局部变量,类型是一个闭包,闭包的类型只有编译器知道,因此我们写不出来。 - -C++编程不应该是这样不愉快的体验。 - -别担心,它只在过去是这样,到了C++11所有的这些问题都消失了,这都多亏了`auto`。`auto`变量从初始化表达式中推导出类型,所以我们必须初始化。这意味着当你在现代化C++的高速公路上飞奔的同时你不得不对只声明不初始化变量的老旧方法说拜拜: - -``` -int x1; //潜在的未初始化的变量 - -auto x2; //错误!必须要初始化 - -auto x3 = 0; //没问题,x已经定义了 -``` - -而且即使使用解引用迭代器初始化局部变量也不会对你的高速驾驶有任何影响 - -``` -template //如之前一样 -void dwim(It b,It e) -{ - while (b != e) { - auto currValue = *b; - … - } -} -``` - -因为使用[Item2](./item2.md)所述的`auto`类型推导技术,它甚至能表示一些只有编译器才知道的类型: - -``` -auto derefUPLess = - [](const std::unique_ptr &p1, //用于std::unique_ptr - const std::unique_ptr &p2) //指向的Widget类型的 - { return *p1 < *p2; }; //比较函数 -``` - -如果使用C++14,将会变得更酷,因为*lambda*表达式中的形参也可以使用`auto`: - -``` -auto derefLess = //C++14版本 - [](const auto& p1, //被任何像指针一样的东西 - const auto& p2) //指向的值的比较函数 - { return *p1 < *p2; }; -``` - -尽管这很酷,但是你可能会想我们完全不需要使用`auto`声明局部变量来保存一个闭包,因为我们可以使用`std::function`对象。没错,我们的确可以那么做,但是事情可能不是完全如你想的那样。当然现在你可能会问,`std::function`对象到底是什么。让我来给你解释一下。 - -`std::function`是一个C++11标准模板库中的一个模板,它泛化了函数指针的概念。与函数指针只能指向函数不同,`std::function`可以指向任何可调用对象,也就是那些像函数一样能进行调用的东西。当你声明函数指针时你必须指定函数类型(即函数签名),同样当你创建`std::function`对象时你也需要提供函数签名,由于它是一个模板所以你需要在它的模板参数里面提供。举个例子,假设你想声明一个`std::function`对象`func`使它指向一个可调用对象,比如一个具有这样函数签名的函数, - -``` -bool(const std::unique_ptr &, //C++11 - const std::unique_ptr &) //std::unique_ptr - //比较函数的签名 -``` - -你就得这么写: - -``` -std::function &, - const std::unique_ptr &)> func; -``` - -因为*lambda*表达式能产生一个可调用对象,所以我们现在可以把闭包存放到`std::function`对象中。这意味着我们可以不使用`auto`写出C++11版的`derefUPLess`: - -``` -std::function &, - const std::unique_ptr &)> -derefUPLess = [](const std::unique_ptr &p1, - const std::unique_ptr &p2) - { return *p1 < *p2; }; -``` - -语法冗长不说,还需要重复写很多形参类型,使用`std::function`还不如使用`auto`。用`auto`声明的变量保存一个和闭包一样类型的(新)闭包,因此使用了与闭包相同大小存储空间。实例化`std::function`并声明一个对象这个对象将会有固定的大小。这个大小可能不足以存储一个闭包,这个时候`std::function`的构造函数将会在堆上面分配内存来存储,这就造成了使用`std::function`比`auto`声明变量会消耗更多的内存。并且通过具体实现我们得知通过`std::function`调用一个闭包几乎无疑比`auto`声明的对象调用要慢。换句话说,`std::function`方法比`auto`方法要更耗空间且更慢,还可能有*out-of-memory*异常。并且正如上面的例子,比起写`std::function`实例化的类型来,使用`auto`要方便得多。在这场存储闭包的比赛中,`auto`无疑取得了胜利(也可以使用`std::bind`来生成一个闭包,但在[Item34](./item34.md)我会尽我最大努力说服你使用*lambda*表达式代替`std::bind`) - -使用`auto`除了可以避免未初始化的无效变量,省略冗长的声明类型,直接保存闭包外,它还有一个好处是可以避免一个问题,我称之为与类型快捷方式(type shortcuts)有关的问题。你将看到这样的代码——甚至你会这么写: - -``` -std::vector v; -… -unsigned sz = v.size(); -``` - -`v.size()`的标准返回类型是`std::vector::size_type`,但是只有少数开发者意识到这点。`std::vector::size_type`实际上被指定为无符号整型,所以很多人都认为用`unsigned`就足够了,写下了上述的代码。这会造成一些有趣的结果。举个例子,在**Windows 32-bit**上`std::vector::size_type`和`unsigned`是一样的大小,但是在**Windows 64-bit**上`std::vector::size_type`是64位,`unsigned`是32位。这意味着这段代码在Windows 32-bit上正常工作,但是当把应用程序移植到Windows 64-bit上时就可能会出现一些问题。谁愿意花时间处理这些细枝末节的问题呢? - -所以使用`auto`可以确保你不需要浪费时间: - -``` -auto sz =v.size(); //sz的类型是std::vector::size_type -``` - -你还是不相信使用`auto`是多么明智的选择?考虑下面的代码: - -``` -std::unordered_map m; -… - -for(const std::pair& p : m) -{ - … //用p做一些事 -} -``` - -看起来好像很合情合理的表达,但是这里有一个问题,你看到了吗? - -要想看到错误你就得知道`std::unordered_map`的*key*是`const`的,所以*hash table*(`std::unordered_map`本质上的东西)中的`std::pair`的类型不是`std::pair`,而是`std::pair`。但那不是在循环中的变量`p`声明的类型。编译器会努力的找到一种方法把`std::pair`(即*hash table*中的东西)转换为`std::pair`(`p`的声明类型)。它会成功的,因为它会通过拷贝`m`中的对象创建一个临时对象,这个临时对象的类型是`p`想绑定到的对象的类型,即`m`中元素的类型,然后把`p`的引用绑定到这个临时对象上。在每个循环迭代结束时,临时对象将会销毁,如果你写了这样的一个循环,你可能会对它的一些行为感到非常惊讶,因为你确信你只是让成为`p`指向`m`中各个元素的引用而已。 - -使用`auto`可以避免这些很难被意识到的类型不匹配的错误: - -``` -for(const auto& p : m) -{ - … //如之前一样 -} -``` - -这样无疑更具效率,且更容易书写。而且,这个代码有一个非常吸引人的特性,如果你获取`p`的地址,你确实会得到一个指向`m`中元素的指针。在没有`auto`的版本中`p`会指向一个临时变量,这个临时变量在每次迭代完成时会被销毁。 - -后面这两个例子——应当写`std::vector::size_type`时写了`unsigned`,应当写`std::pair`时写了`std::pair`——说明了显式的指定类型可能会导致你不像看到的类型转换。如果你使用`auto`声明目标变量你就不必担心这个问题。 - -基于这些原因我建议你优先考虑`auto`而非显式类型声明。然而`auto`也不是完美的。每个`auto`变量都从初始化表达式中推导类型,有一些表达式的类型和我们期望的大相径庭。关于在哪些情况下会发生这些问题,以及你可以怎么解决这些问题我们在[Item2](./item2.md)和[Item6](./item6.md)讨论,所以这里我不再赘述。我想把注意力放到你可能关心的另一点:使用auto代替传统类型声明对源码可读性的影响。 - -首先,深呼吸,放松,`auto`是**可选项**,不是**命令**,在某些情况下如果你的专业判断告诉你使用显式类型声明比`auto`要更清晰更易维护,那你就不必再坚持使用`auto`。但是要牢记,C++没有在其他众所周知的语言所拥有的类型推导(*type inference*)上开辟新土地。其他静态类型的过程式语言(如C#、D、Sacla、Visual Basic)或多或少都有等价的特性,更不必提那些静态类型的函数式语言了(如ML、Haskell、OCaml、F#等)。在某种程度上,这是因为动态类型语言,如Perl、Python、Ruby等的成功;在这些语言中,几乎没有显式的类型声明。软件开发社区对于类型推导有丰富的经验,他们展示了在维护大型工业强度的代码上使用这种技术没有任何争议。 - -一些开发者也担心使用`auto`就不能瞥一眼源代码便知道对象的类型,然而,IDE扛起了部分担子(也考虑到了[Item4](./item4.md)中提到的IDE类型显示问题),在很多情况下,少量显示一个对象的类型对于知道对象的确切类型是有帮助的,这通常已经足够了。举个例子,要想知道一个对象是容器还是计数器还是智能指针,不需要知道它的确切类型。一个适当的变量名称就能告诉我们大量的抽象类型信息。 - -真正的问题是显式指定类型可以避免一些微妙的错误,以及更具效率和正确性,而且,如果初始化表达式的类型改变,则`auto`推导出的类型也会改变,这意味着使用`auto`可以帮助我们完成一些重构工作。举个例子,如果一个函数返回类型被声明为`int`,但是后来你认为将它声明为`long`会更好,调用它作为初始化表达式的变量会自动改变类型,但是如果你不使用`auto`你就不得不在源代码中挨个找到调用地点然后修改它们。 - - - -**总结:** - -- `auto`变量必须初始化,通常它可以避免一些移植性和效率性的问题,也使得重构更方便,还能让你少打几个字。 -- 正如[Item2](https://github.com/kelthuzadx/EffectiveModernCppChinese/blob/master/1.DeducingTypes/item2.md)和[6](https://github.com/kelthuzadx/EffectiveModernCppChinese/blob/master/2.Auto/item6.md)讨论的,`auto`类型的变量可能会踩到一些陷阱。 \ No newline at end of file diff --git a/docs/Effective-Modern-C++/item6.md b/docs/Effective-Modern-C++/item6.md deleted file mode 100644 index 115791f..0000000 --- a/docs/Effective-Modern-C++/item6.md +++ /dev/null @@ -1,144 +0,0 @@ ---- -sort: 6 ---- - - -# 条款6:当auto推导的型别不符合要求时,使用带显式型别的初始化物习惯用法 - -[Item5](./item5.md)解释了使用`auto`关键字去声明变量,这样就比直接显示声明类型提供了一系列的技术优势,但是有时候`auto`的类型推导会和你想的南辕北辙。举一个例子,假设我有一个函数接受一个`Widget`返回一个`std::vector`,其中每个`bool`表征`Widget`是否接受一个特定的特性: - -```cpp -std::vector features(const Widget& w); -``` - -进一步的,假设第五个bit表示`Widget`是否有高优先级。我们可以这样写代码: - -```cpp -Widget w; -… -bool highPriority = features(w)[5]; // w是不是个高优先级的? -… -processWidget(w, highPriority); // 配合优先级处理w -``` - -这份代码没有任何问题。它工作正常。但是如果我们做一个看起来无伤大雅的修改,把`highPriority`的显式的类型换成`auto`: - -```cpp -auto highPriority = features(w)[5]; // w是不是个高优先级的? -``` - -情况变了。所有的代码还是可以编译,但是他的行为变得不可预测: - -```cpp -processWidget(w, highPriority); // 未定义行为 -``` - -正如注释中所提到的,调用`processWidget`现在会导致未定义的行为。但是为什么呢?答案是非常的令人惊讶的。在使用`auto`的代码中,`highPriority`的类型已经不是`bool`了。尽管`std::vector`从概念上说是`bool`的容器,对`std::vector`的`operator[]`运算符并不一定是返回容器中的元素的引用(`std::vector::operator[]`对所有的类型都返回引用,就是除了`bool`)。事实上,他返回的是一个`std::vector::reference`对象(是一个在`std::vector`中内嵌的class)。 - -`std::vector::reference`存在是因为`std::vector`是对`bool`数据封装的模板特化,一个bit对应一个`bool`。这就给`std::vector::operator[]`带来了问题,因为`std::vector`的`operator[]`应该返回一个`T&`,但是C++禁止bits的引用。没办法返回一个`bool&`,`std::vector`的`operator[]`于是就返回了一个行为上和`bool&`相似的对象。想要这种行为成功,`std::vector::reference`对象必须能在`bool&`的能处的语境中使用。在`std::vector::reference`对象的特性中,是他隐式的转换成`bool`才使得这种操作得以成功。(不是转换成`bool&`,而是`bool`。去解释详细的`std::vector::reference`对象如何模拟一个`bool&`的行为有有些偏离主题,所以我们就只是简单的提一下这种隐式转换只是这种技术中的一部。) - -在大脑中带上这种信息,再次阅读原先的代码: - -```cpp -bool highPriority = features(w)[5]; // 直接显示highPriority的类型 -``` - -这里,`features`返回了一个`std::vector`对象,在这里`operator[]`被调用。`operator[]`返回一个`std::vector::reference`对象,这个然后隐式的转换成`highPriority`需要用来初始化的`bool`类型。于是就以`features`返回的`std::vector`的第五个bit的数值来结束`highPriority`的数值,这也是我们所预期的。 - -和使用`auto`的`highPriority`声明进行对比: - -```cpp -auto highPriority = features(w)[5]; // 推导highPriority的类型 -``` - -这次,`features`返回一个`std::vector`对象,而且,`operator[]`再次被调用。`operator[]`继续返回一个`std::vector::reference`对象,但是现在有一个变化,因为`auto`推导`highPriority`的类型。`highPriority`根本并没有`features`返回的`std::vector`的第五个bit的数值。 - -数值和`std::vector::reference`是如何实现的是有关系的。一种实现是这样的对象包含一个指向包含bit引用的机器word的指针,在word上面加上偏移。考虑这个对`highPriority`的初始化的意义,假设`std::vector::reference`的实现是恰当的。 - -调用`features`会返回一个临时的`std::vector`对象。这个对象是没有名字的,但是对于这个讨论的目的,我会把它叫做`temp`,`operator[]`是在`temp`上调用的,`std::vector::reference`返回一个由`temp`管理的包含一个指向一个包含bits的数据结构的指针,在word上面加上偏移定位到第五个bit。`highPriority`也是一个`std::vector::reference`对象的一份拷贝,所以`highPriority`也在`temp`中包含一个指向word的指针,加上偏移定位到第五个bit。在这个声明的结尾,`temp`被销毁,因为它是个临时对象。因此,`highPriority`包含一个野指针,这也就是调用`processWidget`会造成未定义的行为的原因: - -```cpp -processWidget(w, highPriority); // 未定义的行为,highPriority包含野指针 -``` - -`std::vector::reference`是代理类的一个例子:一个类的存在是为了模拟和对外行为和另外一个类保持一致。代理类在各种各样的目的上被使用。`std::vector::reference`的存在是为了提供一个对`std::vector`的`operator[]`的错觉,让它返回一个对bit的引用,而且标准库的智能指针类型(参考第4章)也是一些对托管的资源的代理类,使得他们的资源管理类似于原始指针。代理类的功能是良好确定的。事实上,“代理”模式是软件设计模式中的最坚挺的成员之一。 - -一些代理类被设计用来隔离用户。这就是`std::shared_ptr`和`std::unique_ptr`的情况。另外一些代理类是为了一些或多或少的不可见性。`std::vector::reference`就是这样一个“不可见”的代理,和他类似的是`std::bitset`,对应的是`std::bitset::reference`。 - -同时在一些C++库里面的类存在一种被称作表达式模板的技术。这些库最开始是为了提高数值运算的效率。提供一个`Matrix`类和`Matrix`对象`m1, m2, m3 and m4`,举一个例子,下面的表达式: - -```cpp -Matrix sum = m1 + m2 + m3 + m4; -``` - -可以计算的更快如果`Matrix`的`operator+`返回一个结果的代理而不是结果本身。这是因为,对于两个`Matrix`,`operator+`可能返回一个类似于`Sum`的代理类而不是一个`Matrix`对象。和`std::vector::reference`一样,这里会有一个隐式的从代理类到`Matrix`的转换,这个可能允许`sum`从由`=`右边的表达式产生的代理对象进行初始化。(其中的对象可能会编码整个初始化表达式,也就是,变成一种类似于`Sum, Matrix>, Matrix>`的类型。这是一个客户端需要屏蔽的类型。) - -作为一个通用的法则,“不可见”的代理类不能和`auto`愉快的玩耍。这种类常常它的生命周期不会被设计成超过一个单个的语句,所以创造这样的类型的变量是会违反库的设计假定。这就是`std::vector::reference`的情况,而且我们可以看到这种违背约定的做法会导致未定义的行为。 - -因此你要避免使用下面的代码的形式: - -```cpp -auto someVar = expression of "invisible" proxy class type; -``` - -但是你怎么能知道代理类被使用呢?软件使用它们的时候并不可能会告知它们的存在。它们是不可见的,至少在概念上!一旦你发现了他们,难道你就必须放弃使用`auto`加之条款5所声明的`auto`的各种好处吗? - -我们先看看怎么解决如何发现它们的问题。尽管“不可见”的代理类被设计用来fly beneath programmer radar in day-to-day use,库使用它们的时候常常会撰写关于它们的文档来解释为什么这样做。你对你所使用的库的基础设计理念越熟悉,你就越不可能在这些库中被代理的使用搞得狼狈不堪。 - -当文档不够用的时候,头文件可以弥补空缺。很少有源码封装一个完全的代理类。它们常常从一些客户调用者期望调用的函数返回,所有函数签名常常可以表征它们的存在。这里是`std::vector::operator[]`的例子: - -```cpp -namespace std { // from C++ Standards - template - class vector { - public: - … - class reference { … }; - reference operator[](size_type n); - … - }; -} -``` - -假设你知道对`std::vector`的`operator[]`常常返回一个`T&`,在这个例子中的这种非常规的`operator[]`的返回类型一般就表征了代理类的使用。在你正在使用的这些接口之上加以关注常常可以发现代理类的存在。 - -在实践上,很多的开发者只会在尝试修复一些奇怪的编译问题或者是调试一些错误的单元测试结果中发现代理类的使用。不管你是如何发现它们,一旦`auto`被决定作为推导代理类的类型而不是它被代理的类型,它就不需要涉及到关于`auto`,`auto`自己本身没有问题。问题在于`auto`推导的类型不是所想让它推导出来的类型。解决方案就是强制一个不同的类型推导。我把这种方法叫做显式的类型初始化原则。 - -显式的类型初始化原则涉及到使用`auto`声明一个变量,但是转换初始化表达式到`auto`想要的类型。下面就是一个强制`highPriority`类型是`bool`的例子: - -```cpp -auto highPriority = static_cast(features(w)[5]); -``` - -这里,`features(w)[5]`还是返回一个`std::vector::reference`的对象,就和它经常的表现一样,但是强制类型转换改变了表达式的类型成为`bool`,然后`auto`才推导其作为`highPriority`的类型。在运行的时候,从`std::vector::operator[]`返回的`std::vector::reference`对象支持执行转换到`bool`的行为,作为转换的一部分,从`features`返回的任然存活的指向`std::vector`的指针被间接引用。这样就在运行的开始避免了未定义行为。索引5然后放置在bits指针的偏移上,然后暴露的`bool`就作为`highPriority`的初始化数值。 - -针对于`Matrix`的例子,显示的类型初始化原则可能会看起来是这样的: - -```cpp -auto sum = static_cast(m1 + m2 + m3 + m4); -``` - -关于这个原则下面的程序并不禁止初始化但是要排除代理类类型。强调你要谨慎地创建一个类型的变量,它和从初始化表达式生成的类型是不同的也是有帮助意义的。举一个例子,假设你有一个函数去计算一些方差: - -```cpp -double calcEpsilon(); // 返回方差 -``` - -`calcEpsilon`明确的返回一个`double`,但是假设你知道你的程序,`float`的精度就够了的时候,而且你要关注`double`和`float`的长度的区别。你可以声明一个`float`变量去存储`calcEpsilon`的结果: - -```cpp -float ep = calcEpsilon(); // 隐式转换double到float -``` - -但是这个会很难表明“我故意减小函数返回值的精度”,一个使用显式的类型初始化原则是这样做的: - -```cpp -auto ep = static_cast(calcEpsilon()); -``` - - - -**总结:** - -- “隐形”的代理型别可以导致auto根据初始化表达式推导出“错误的”型别。 -- 带显式型别的初始化物习惯用法轻质auto推导出你想要的型别。 \ No newline at end of file diff --git a/docs/Effective-Modern-C++/item7.md b/docs/Effective-Modern-C++/item7.md deleted file mode 100644 index eac6c47..0000000 --- a/docs/Effective-Modern-C++/item7.md +++ /dev/null @@ -1,239 +0,0 @@ ---- -sort: 7 ---- -# 条款7:创建对象时使用()和{}的区别 - - - 在C++11中,你可以有多种语法选择用以对象的初始化,这样的语法显得混乱不堪并让人无所适从,括号,等号,大括号均可以用来进行初始化: - ``` - int x(0); //使用()进行初始化 - int y = 0; //使用=进行初始化 - int z{0}; //使用{}进行初始化 - ``` - 在很多情况下,可以同时使用等号和中括号 - ``` - int z = {0}; //使用{}和=进行初始化 - ``` - 对于这一条,我通常的会忽略“等于-大括号”这种语法,因为C++通常认为它只有大括号。 - - 认为这种语法"混乱不堪"的辩护者指出,使用等号用于初始化会误导C++的初学者认为赋值动作已经发生了,而实际上并没有。对于内建类型例如int,这种区别只是理论上的,但是对于用户自定义类型,初始化和赋值的区别很重要,因为两者调用了不同的函数: - - ``` - Widget w1; //调用了构造函数 - Widget w2 = w1; //不是赋值语句,调用了拷贝构造函数 - w1 = w2; //赋值语句,调用了=操作符 - ``` - 尽管拥有多种初始化的语法,C++98在很多情况下无法实现想要的初始化。例如,很难直接判断当一个STL容器包含一些值集合(例如,1,3,5)的时候是否应该创建。 - - 为了解决多种初始化语法冲突的问题,并且它们也无法涵盖所有的初始化场景,C++11引入了统一的初始化:使用单一的初始化语法,理论上可以在任何地方表达所有的初始化。它基于大括号,这也是我倾向于大括号初始化的原因。"统一的初始化"是一个概念,"大括号初始化"是一个语法实现。 - - 大括号初始化使你可以实现以前不能表达的含义。使用大括号,指定容器初始值变得简单: - - ``` - std::vector v{1,3,5};//v的初始值是1,3,5 - ``` - 大括号也可以用来为非静态数据成员指定初始的默认值。C++11中的一项新语法是支持大括号像"="一样初始化,但是括号不行: - ``` - class Widget{ - ... - private: - int x{0}; //正确,x默认值为0 - int y = 0; //正确 - int z(0); //错误 - } - ``` - 另一方法,非拷贝对象(std:atomics-参考[Item40](./item40.md))可以使用大括号和括号初始化,但是"="不行: - ``` - std::atomic ai1{0}; //正确 - std::atomic ai2(0); //正确 - std::atomic ai3 = 0; //错误 - ``` - 因此很容易理解括号初始化被称为标准。在三种为C++初始化设计的表达式中,只有括号可以被用在任何地方。 - 括号初始化的一个新的特性是它禁止在基本类型中使用隐式的数值转换。如果一个括号中的表达式和初始化对象的类型不一致,代码将无法编译: - - ``` - double x,y,z; - ... - int sum1{x + y + z}; //错误,双精度类型数值相加无法表达为整形 - 使用括号和等号进行初始化时不会进行数值转换,因为这样会减少很多冗余的代码: - ``` - ``` - int sum2(x + y + z); //正确,表达式的值为转换成整形 - int sum3 = x + y + z; //如上 - ``` - - - - 括号初始化另外一个值得一提的特点是它摆脱了**C++的令人头疼的歧义原则**。C++原则的一个副作用是任何的可以用来申明的必须被解释成唯一的,经常困扰开发者的一个问题是他们想默认构造一个对象,但是常常自己申明了一个替代它的函数。根本的原因是你想调用一个带参数的构造函数,你可以这样做, - - ``` - Widget w1(10); //调用Widget的构造函数并传递参数10 - ``` - - 但是如果你想使用类似语法调用一个无参的构造函数,实际上你申明了一个函数,而不是对象: - - ``` - Widget w2(); //非常的歧义,定义了一个返回Widget对象的w2函数 - ``` - 函数申明不可能使用大括号传递参数列表,所有使用大括号默认构造函数不会带来这个问题: - ``` - Widget w3{}; //调用Widget无参构造函数 - ``` - 使用大括号进行初始化还有很多值得一提的地方。它的语法可以用于广泛的上下文语境之中,它可以防止隐式值转换,而且不会出现C++的二义性。这真实一举三得的好事。那么为什么这一条标题不是叫"优先使用大括号初始化的语法"呢? - - 大括号的初始化的缺点是它带来的一些令人意外的行为。这些行为来自使用大括号初始化 std::initializer_lists和重载构造函数的异常纠结的关系中。它们之间的相互关系使得代码开起来做了一样的事,实际上并不是。例如,条目2解释了当使用auto申明的变量使用大括号初始化时,它被推断为std::initializer_list类型,尽管其他的申明具有相同初始化变量的方法使得它的类型更为直观。造成的结果就是,你越喜欢使用auto,你就越没有使用大括号初始化的热情。 - - 在构造函数调用中,只要不包含std::initializer_list参数列表,大括号和括号意义是一样: - - ``` - class Widget{ - public: - Widget(int i,bool b); //构造函数没有声明为std::initializer_list的参数 - Widget(int i,double d); - }; - Widget w1(10,true); //调用第一个构造函数 - Widget w2(10,true); //同样调用第一个构造函数 - Widget w3(10,50); //调用第二个构造函数 - Widget w4(10,50); //同样调用第二个构造函数 - ``` - 但是,如果有一个或多个构造函数的参数类型是std::initializer_list,使用大括号初始化语法会优先调用使用了参数类型std::initializer_list的构造函数。更明确的一点是,只要编译器一旦可以把一个使用大括号初始化解释成调用具有std::initializer_list参数的沟通函数,它一定会这么做。如果上面的Widget类具有一个带有std::initializer_list参数的构造函数,如下: - - - ``` - class Widget{ - public: - Widget(int i,bool b); //和上面一样 - Widget(int i,double d); //和上面一样 - Widget(std::initializer_list il); //新加的构造函数 - ... - }; - ``` - w2和w4将会使新的构造函数创建,即使std::initializer_list参数的构造函数看起来比非std::initializer_list构造函数更难匹配,如下: - ``` - Widget w1(10,true); //使用小括号,调用第一个构造函数 - Widget w2{10,true}; //使用大括号构造函数,调用std::initializer_list参数,10和true被转换成long dobule型 - Widget w3(10,5,0); //使用小括号,调用第二个构造函数 - Widget w4{10,5.0}; //使用大括号,调用std::initializer_list参数,10和5.0被转换为long double - ``` - 即使通常复制和移动的构造也会被认为使用std::initializer_list构造函数: - ``` - class Widget{ - public: - Widget(int i,bool b); //同上 - Widget(int i,double d); //同上 - Widget(std::initializer_list il); //同上 - - operator float() const; //转换成float型 - ... - }; - Widget w5(w4); //使用括号,调用拷贝构造函数 - Widget w6{w4}; //使用大括号,调用std::initializer_list参数类型构造函数,w4被转换成float,然后再转换成long double - Widget w7(std::move(w4)); //使用括号,调用move构造函数 - Widget w8{std::move(w4)}; //使用大括号,调用std::initializer_list构造函数,和w6原因一样 - ``` - 编译决定采用std::initializer_lists参数的构造函数意愿强烈,及时这样的调用是不通过的。例如 - ``` - class Widget{ - public: - Widget(int i,bool b); //如上 - Widget(int i,double d); //如上 - - Widget(std::initializer_list il); //元素类型是bool - - ... - }; - Widget w{10,5.0}; //错误,要求窄化型别转换 - ``` - 在这里,编译器会忽略前两个构造函数(第二个还是参数完全匹配的)而是试图调用std::initializer_list参数的构造函数。调用这个构造函数需要将int(10)和double(5.0)转换成bool型。两个转换均会出现类型收窄(bool型不能代表int和double类型),收窄化型别转换在大括号内初始化是被禁止的,所以这个调用非法的,代码编译不通过。 - - **只有当无法使用大括号初始化的参数转换成std::initializer_list的时候,编译器才会回来调用正常的构造函数**,例如,如果我们将std::initializer_list构造函数替换成std::initializer_list,那么非std::initializer_list参数的构造函数称为候选,因为没有办法将int和bool型转换成std::strings: - - ``` - class Widget{ - public: - Widget(int i,bool b); //同上 - Widget(int i,double d); //同上 - - //std::initalizer_list元素类型是std::string - Widget(std::initializer_list il);//没有隐式转换 - ... - }; - - Widget w1(10,true); //使用小括号初始化,调用第一个构造函数 - Widget w2{10,true}; //使用大括号初始化,调用第一个构造函数 - Widget w3(10,5.0); //使用括号初始化,调用第二个构造函数 - Widget w4{10,5.0}; //使用大括号初始化,调用第二个构造函数 - ``` - 我们现在已经接近完成探索大括号初始化和重载构造函数,但是有一个有趣的情形值得一提。假设你使用一个空的大括号构造对象,对象同时支持std::initializer_list作为参数的构造函数。空的大括号参数指什么呢?如果表示空的参数,那么你将调用默认构造函数,如果表示空的std::initializer_list,那么调用无实际传入参数std::initializer_list构造函数。 - 规则是调用默认构造函数。空的大括号意味着无参数,并不是空的std::initializer_list: - ``` - class Widget{ - public: - Widget(); //默认构造函数 - - Widget(std::initializer_list il); //std::initializer_list构造函数 - ... - }; - Widget w1; //调用默认构造函数 - Widget w2{}; //调用默认构造函数 - Widget w3{}; //令人恼火的解析,声明了一个函数! - ``` - 如果你想使用空的initializer_list参数来调用std::initializer_list参数的构造函数,你可以使用空的大括号作为参数--把空的大括号放在小括号之中来标定你传递的内容: - ``` - Widget w4({}); //使用空列表作为参数调用std::initializer_list型的构造函数 - Widget w5({}); //如上 - ``` - 在这一点上,看似神秘的大括号初始化,std::initializer_lists参数初始化,重载构造函数萦绕在你的脑海中,你或许或好奇有多少的信息量会影响到我们平时日常的编程中。这比你想象的更多,因为其中一个被直接影响的类是std::vector。std::vector有一个非std::initializer_list的构造函数允许你指定容器的大小以及每个元素的初始值,它还拥有一个std::initializer_list参数的构造函数允许你指定容器的初始值。如果你创建一个数值类型的(例如:std::vector)的容器并且传递两个参数给构造函数,使用大括号和小括号传递参数将会导致非常明显的区别: - ``` - std::vector v1(10,20) //使用非std::initializer_list参数的构造函数,结果构造了10个元素的std::vector对象,每个对象的值都是20 - std::vector v2{10,20} //使用std::initializer_list参数的构造函数,结果构造了2个元素的std::vector对象,两个元素分别是10和20 - ``` - 让我们从讨论std::vector,以及大括号,括号以及重载构造函数的选择细节中脱离出来。有两点需要指出。 - - 第一,作为一个类的作者,你需要明白如果你有一系列的重载构造函数,其中包括了一个或多个以std::initializer_list作为参数,客户端代码使用大括号初始化将只看到std::initializer_list参数重载构造函数。因此,你最好设计你的构造函数无论客户端代码使用大括号还是小括号初始化重载构造函数的调用不会受到影响。换句话说,在设计std::vector的接口中现在看起来是错误的地方从中学习用来设计自己的类避免同样的错误。 - - - - 一个内在的含义是如果你有一个没有以std::initializer_list作为参数的构造函数的类,你添加一个,客户端代码使用大括号初始化时会从以前被解析成调用非std::initializer_list的构造函数变成现在一个新的函数。当然,这种情况在你添加新的重载函数时经常发生:函数调用从之前调用老的重载函数变成现在新的函数。std::initializer_list参数的构造函数不同的地方在于它不与其他构造函数竞争,它使得其他构造函数变得不再被得到调用。因此添加这样的重载函数需要谨慎考虑。 - - - - 第二个值得学习的地方是作为一个客户端类,你必须谨慎选择大括号或括号创建对象。绝大部门开发者选择使用分隔符作为默认构造函数,除非必要时才会选择其他。使用大括号初始化的开发者被它们广泛的适用性所吸引,它们禁止类型值收缩转换,并且没有c++大多数的二义性。这些开发者知道在一些情况下(例如,在创建std::vector的时候指定大小和初始元素值),要求使用小括号。另一方面,小括号可以被当成默认的参数分隔符。这一点和C++98的语言传统一致,避免了auto推断std::initializer_list的问题,也是吸引人的地方。对象创建的时候调用构造函数不会不经意的被std::initializer_list参数型的构造函数做拦截。他们承认有时候只有大括号能做初始化(例如,使用指定的值创建容器)。关于哪种方式更好没有共识,所以我的建议是选择其中一种方式,并且保持一致。 - - - - 如果你是一个模块作者,使用小括号还是大括号来构造对象带来的不安是令人沮丧的,因为,通常我们并不知道哪一种方式被用到。例如,假设你想使用任意类型任意数量的参数创建一个对象。可变参数类模块是的这变得很简单: - - ``` - template //使用的参数类型 - void doSomeWork(TS&&... params) - { - create local T objcet from params... - ... - } - ``` - 有两种方式可以是的伪代码变成真实的代码(参见[Item25](./item25.md)关于std::forward说明): - ``` - T localObject(std::forward(params)...); //使用小括号 - T localObject{std::forward(params)...}; //使用大括号 - ``` - 考虑如下的调用代码: - ``` - std::vector v; - ... - doSomeWork>(10,20); - ``` - 如果doSomeWork使用小括号创建localObject,结果得到10个元素的std::vector。如果doSomeWork调用大括号,结果得到两个元素的std::vectore。哪一种是对的呢?doSomeWork的作者不知道,只有调用的才知道。 - - 这就是标准库函数std::make_unique和std::make_shared(参见[Item21](./item21.md))面临的问题。这些函数在内部使用小括号并在接口文档中注明以解决这个问题。 - - **总结:** - -- 大括号初始化是广泛使用的初始化语法,可以阻止隐式窄化型别转换,并且不会出现大多数C++的二义性 - -- 在重载构造函数的选择中,大括号初始化会尽量去std::initializer_list型参数的的构造函数,即使其他构造函数看起来更匹配 - -- 一个选择大括号还是小括号具有明显区别的例子是构造具有两个参数的数值类型的std::vector - -- 在模块中选择大括号还是小括号创建对象很具有挑战性 \ No newline at end of file diff --git a/docs/Learn-C-Plus/1.C++开发简介.md b/docs/Learn-C-Plus/1.C++开发简介.md deleted file mode 100644 index 7ea194a..0000000 --- a/docs/Learn-C-Plus/1.C++开发简介.md +++ /dev/null @@ -1,94 +0,0 @@ ---- -sort: 1 ---- - -# C++开发简介 - -在我们编写和执行我们的第一个 C++ 程序之前,我们需要更详细地了解 C++ 程序是如何开发的。这是一个概述简单方法的图形: - -![20220401221353-2022-04-01-22-13-54](https://cdn.jsdelivr.net/gh/ironartisan/picRepo/20220401221353-2022-04-01-22-13-54.png) - -## 第 1 步:定义您要解决的问题 - -这是“什么”步骤,您可以在其中确定要解决的问题。想出你想要编程的最初想法可能是最简单的一步,也可能是最难的一步。但从概念上讲,它是最简单的。您所需要的只是一个可以明确定义的想法,然后您就可以进行下一步了。 - -这里有一些例子: - -“我想编写一个程序,允许我输入许多数字,然后计算平均值。” -“我想编写一个程序来生成二维迷宫并让用户在其中导航。如果他们到达终点,用户就赢了。” -“我想编写一个程序,读取股票价格文件并预测股票是上涨还是下跌。” - -## 第 2 步:确定您将如何解决问题 - -这是“如何”步骤,在此您确定将如何解决您在步骤 1 中提出的问题。这也是软件开发中最被忽视的步骤。问题的症结在于解决问题的方法有很多——然而,其中一些解决方案是好的,而一些是坏的。程序员经常会得到一个想法,坐下来,立即开始编写解决方案。这通常会产生一个属于不良类别的解决方案。 - -通常,好的解决方案具有以下特征: - -它们很简单(不过分复杂或混乱)。 -它们有据可查(尤其是围绕所做的任何假设或限制)。 -它们是模块化构建的,因此以后可以重复使用或更改部分,而不会影响程序的其他部分。 -它们很健壮,可以在发生意外情况时恢复或提供有用的错误消息。 -当您坐下来立即开始编码时,您通常会想“我想做<某事>”,因此您实施的解决方案可以让您以最快的速度到达那里。这可能导致程序脆弱、以后难以更改或扩展,或者有很多错误(技术缺陷)。 - -研究表明,程序员实际上只有 20% 的时间用于编写初始程序。其余 80% 用于维护,其中可能包括调试(消除错误)、应对环境变化的更新(例如,在新的操作系统版本上运行)、增强(改进可用性或功能的小改动),或内部改进(以提高可靠性或可维护性)。 - -因此,值得您花一些额外的时间预先(在开始编码之前)考虑解决问题的最佳方法,您所做的假设以及您可能如何规划未来,以拯救自己很多时间和麻烦。 - -我们将在以后的课程中更多地讨论如何有效地设计问题的解决方案。 - -## 第三步:编写程序 -为了编写程序,我们需要两件事:首先,我们需要编程语言的知识——这就是这些教程的目的!其次,我们需要一个文本编辑器来编写和保存我们编写的程序。我们使用 C++ 指令编写的程序称为源代码(通常简称为代码)。可以使用您想要的任何文本编辑器编写程序,甚至像 Window 的记事本或 Unix 的 vi 或 pico 一样简单。但是,我们强烈建议您使用专为编程而设计的编辑器(称为代码编辑器)。如果您还没有,请不要担心。我们将很快介绍如何安装代码编辑器。 -为编码而设计的典型编辑器具有一些使编程更容易的功能,包括: - -1. 行号。当编译器给我们一个错误时,行号很有用,因为典型的编译器错误会指出:一些错误代码/消息,第 64 行。如果没有显示行号的编辑器,查找第 64 行可能会很麻烦。 -2. 语法高亮和着色。语法高亮和着色会改变程序各个部分的颜色,以便更容易识别程序的不同组件。下面是一个带有行号和语法高亮的 C++ 程序示例: - -``` -#include - -int main() -{ - std::cout << "Colored text!"; - return 0; -} -``` -我们在本教程中展示的示例将始终同时具有行号和语法突出显示,以使它们更易于理解。 - -一种明确的字体。非编程字体通常很难区分数字 0 和字母 O,或者数字 1、字母 l(小写 L)和字母 I(大写 i)。一种好的编程字体将确保这些符号在视觉上有所区别,以确保不会意外使用一个来代替另一个。默认情况下,所有代码编辑器都应启用此功能,但标准文本编辑器可能不会。 -您编写的程序通常会被命名为something.cpp,其中某些内容会替换为您为程序选择的名称(例如计算器、hi-lo 等)。.cpp扩展名告诉编译器(和您)这是一个包含 C++ 指令的 C++ 源代码文件。请注意,有些人使用扩展名 .cc 而不是 .cpp,但我们建议您使用 .cpp。 - -## 第 4 步:编译源代码 - -为了编译 C++ 程序,我们使用 C++ 编译器。C++ 编译器按顺序遍历程序中的每个源代码 (.cpp) 文件并执行两项重要任务: - -首先,它会检查您的代码以确保它遵循 C++ 语言的规则。如果没有,编译器会给你一个错误(和相应的行号)来帮助确定需要修复的地方。编译过程也将中止,直到错误得到修复。 - -其次,它将您的 C++ 源代码翻译成称为目标文件的机器语言文件。对象文件通常命名为name.o或name.obj,其中name与生成它的 .cpp 文件的名称相同。 - -如果您的程序有 3 个 .cpp 文件,编译器将生成 3 个目标文件: - -![20220401224241-2022-04-01-22-42-41](https://cdn.jsdelivr.net/gh/ironartisan/picRepo/20220401224241-2022-04-01-22-42-41.png) - -## 第 5 步:链接目标文件和库 - -在编译器创建一个或多个目标文件之后,另一个名为链接器的程序就会启动。链接器的工作包括三个方面: - -首先,获取编译器生成的所有目标文件,并将它们组合成一个可执行程序。 - -![20220401224252-2022-04-01-22-42-53](https://cdn.jsdelivr.net/gh/ironartisan/picRepo/20220401224252-2022-04-01-22-42-53.png) - -其次,除了能够链接目标文件之外,链接器还能够链接库文件。库文件是已“打包”以在其他程序中重用的预编译代码的集合。 - -C++ 核心语言实际上相当小而简洁(您将在这些教程中学到很多)。但是,C++ 还附带了一个名为C++ 标准库(通常缩写为标准库)的扩展库,它提供了可以在程序中使用的附加功能。C++ 标准库中最常用的部分之一是iostream 库,其中包含在监视器上打印文本和从用户获取键盘输入的功能。几乎每个编写的 C++ 程序都以某种形式使用标准库,因此标准库链接到您的程序中是很常见的。大多数链接器会在您使用标准库的任何部分后立即自动链接到标准库中,因此这通常不是您需要担心的事情。 - -您还可以选择链接其他库。例如,如果您要编写一个播放声音的程序,您可能不想编写自己的代码来从磁盘读取声音文件,检查以确保它们有效,或者弄清楚如何路由声音数据通过扬声器播放到操作系统或硬件——那将是很多工作!相反,您可能会下载一个已经知道如何做这些事情的库,并使用它。我们将在附录中讨论如何在库中链接(并创建您自己的!)。 - -第三,链接器确保正确解析所有跨文件依赖项。例如,如果您在一个 .cpp 文件中定义某些内容,然后在另一个 .cpp 文件中使用它,则链接器会将两者连接在一起。如果链接器无法使用其定义连接对某事物的引用,您将收到链接器错误,并且链接过程将中止。 - -一旦链接器完成链接所有目标文件和库(假设一切顺利),您将拥有一个可执行文件,然后您可以运行它! - -## 第 6 步和第 7 步:测试和调试 - -这是有趣的部分(希望如此)!您可以运行您的可执行文件并查看它是否产生您期望的输出! - -如果您的程序运行但不能正常工作,那么是时候进行一些调试以找出问题所在了。我们将很快讨论如何测试您的程序以及如何更详细地调试它们。 \ No newline at end of file diff --git a/docs/Learn-C-Plus/2.C++基础.md b/docs/Learn-C-Plus/2.C++基础.md deleted file mode 100644 index 9b83eed..0000000 --- a/docs/Learn-C-Plus/2.C++基础.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -sort: 2 ---- - -# C++基础 - -## Statements diff --git a/docs/Learn-C-Plus/README.md b/docs/Learn-C-Plus/README.md deleted file mode 100644 index 30c477d..0000000 --- a/docs/Learn-C-Plus/README.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -sort: 2 ---- - -# Learn C++笔记 - -{% include list.liquid %} - - diff --git a/docs/book/en-us/00-preface.md b/docs/book/en-us/00-preface.md deleted file mode 100644 index b93705a..0000000 --- a/docs/book/en-us/00-preface.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -title: Preface -type: book-en-us -order: 0 ---- - -# Preface - -[TOC] - -## Introduction - -The C++ programming language owns a fairly large user group. From the advent of C++98 to the official finalization of C++11, it has continued to stay relevant. C++14/17 is an important complement and optimization for C++11, and C++20 brings this language to the door of modernization. The extended features of all these new standards are integrated into the C++ language and infuse it with new vitality. -C++ programmers who are still using **traditional C++** (this book refers to C++98 and its previous standards as traditional C++) may even amazed by the fact that they are not using the same language while reading modern C++ code. - -**Modern C++** (this book refers to C++11/14/17/20) introduces many features into traditional C++ which bring the entire language to a new level of modernization. Modern C++ not only enhances the usability of the C++ language itself, but the modification of the `auto` keyword semantics gives us more confidence in manipulating extremely complex template types. At the same time, a lot of enhancements have been made to the language runtime. The emergence of Lambda expressions has given C++ the "closure" feature of "anonymous functions", which are in almost all modern programming languages ​​(such as Python, Swift, etc). It has become commonplace, and the emergence of rvalue references has solved the problem of temporary object efficiency that C++ has long been criticized for. - -C++17 is the direction that has been promoted by the C++ community in the past three years. It also points out an important development direction of **modern C++** programming. Although it does not appear as much as C++11, it contains a large number of small and beautiful languages ​​and features (such as structured binding), and the appearance of these features once again corrects our programming paradigm in C++. - -Modern C++ also adds a lot of tools and methods to its standard library such as `std::thread` at the level of the language itself, which supports concurrent programming and no longer depends on the underlying system on different platforms. The API implements cross-platform support at the language level; `std::regex` provides full regular expression support and more. C++98 has been proven to be a very successful "paradigm", and the emergence of modern C++ further promotes this paradigm, making C++ a better language for system programming and library development. Concepts verify the compile-time of template parameters, further enhancing the usability of the language. - -In conclusion, as an advocate and practitioner of C++, we always maintain an open mind to accept new things, and we can promote the development of C++ faster, making this old and novel language more vibrant. - -## Targets - -- This book assumes that readers are already familiar with traditional C++ (i.e. C++98 or earlier), at least they do not have any difficulty in reading traditional C++ code. In other words, those who have long experience in traditional C++ and people who desire to quickly understand the features of modern C++ in a short period are well suited to read the book; - -- This book introduces to a certain extent of the dark magic of modern C++. However, these magics are very limited, they are not suitable for readers who want to learn advanced C++. The purpose of this book is to offer a quick start for modern C++. Of course, advanced readers can also use this book to review and examine themselves on modern C++. - -## Purpose - -The book claims "On the Fly". It intends to provide a comprehensive introduction to the relevant features regarding modern C++ (before the 2020s). -Readers can choose interesting content according to the following table of contents to learn and quickly familiarize themselves with the new features that are available. -Readers should aware that all of these features are not required. It should be learned when you need it. - -At the same time, instead of grammar-only, the book introduces the historical background as simple as possible of its technical requirements, which provides great help in understanding why these features come out. - -Also, the author would like to encourage that readers should be able to use modern C++ directly in their new projects and migrate their old projects to modern C++ gradually after reading the book. - -## Code - -Each chapter of this book has a lot of code. If you encounter problems when writing your own code with the introductory features of the book, you might as well read the source code attached to the book. You can find the book [here](../../code). All the code is organized by chapter, the folder name is the chapter number. - -## Exercises - -There are few exercises At the end of each chapter of the book. It is for testing whether you can use the knowledge points in the current chapter. You can find the possible answer to the problem from [here](../../exercise). The folder name is the chapter number. - -[Table of Content](./toc.md) | [Next Chapter: Towards Modern C++](./01-intro.md) - -## Licenses - -Creative Commons License
This work was written by [Ou Changkun](https://changkun.de) and licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. The code of this repository is open sourced under the [MIT license](../../LICENSE). diff --git a/docs/book/en-us/01-intro.md b/docs/book/en-us/01-intro.md deleted file mode 100644 index 03ecd88..0000000 --- a/docs/book/en-us/01-intro.md +++ /dev/null @@ -1,149 +0,0 @@ ---- -title: "Chapter 01: Towards Modern C++" -type: book-en-us -order: 1 ---- - -# Chapter 01: Towards Modern C++ - -[TOC] - -**Compilation Environment**: This book will use `clang++` as the only compiler used, -and always use the `-std=c++2a` compilation flag in your code. - -```bash -> clang++ -v -Apple LLVM version 10.0.1 (clang-1001.0.46.4) -Target: x86_64-apple-darwin18.6.0 -Thread model: posix -InstalledDir: /Library/Developer/CommandLineTools/usr/bin -``` - -## 1.1 Deprecated Features - -Before learning modern C++, let's take a look at the main features that have deprecated since C++11: - -> **Note**: Deprecation is not completely unusable, it is only intended to imply that features will disappear from future standards and should be avoided. But, the deprecated features are still part of the standard library, and most of the features are actually "permanently" reserved for compatibility reasons. - -- **The string literal constant is no longer allowed to be assigned to a `char *`. If you need to assign and initialize a `char *` with a string literal constant, you should use `const char *` or `auto`.** - - ```cpp - char *str = "hello world!"; // A deprecation warning will appear - ``` - -- **C++98 exception description, `unexpected_handler`, `set_unexpected()` and other related features are deprecated and should use `noexcept`.** - -- **`auto_ptr` is deprecated and `unique_ptr` should be used.** - -- **`register` keyword is deprecated and can be used but no longer has any practical meaning.** - -- **The `++` operation of the `bool` type is deprecated.** - -- **If a class has a destructor, the properties for which it generates copy constructors and copy assignment operators are deprecated.** - -- **C language style type conversion is deprecated (ie using `(convert_type)`) before variables, and `static_cast`, `reinterpret_cast`, `const_cast` should be used for type conversion.** - -- **In particular, some of the C standard libraries that can be used are deprecated in the latest C++17 standard, such as ``, ``, `` and `` etc.** - -- ... and many more - -There are also other features such as parameter binding (C++11 provides `std::bind` and `std::function`), `export` etc. are also deprecated. These features mentioned above **If you have never used or heard of it, please don't try to understand them. You should move closer to the new standard and learn new features directly**. After all, technology is moving forward. - -## 1.2 Compatibilities with C - -For some force majeure and historical reasons, we had to use some C code (even old C code) in C++, for example, Linux system calls. Before the advent of modern C++, most people talked about "what is the difference between C and C++". Generally speaking, in addition to answering the object-oriented class features and the template features of generic programming, there is no other opinion or even a direct answer. "Almost" is also a lot of people. The Venn diagram in Figure 1.2 roughly answers the C and C++ related compatibility. - -![Figure 1.2: Compatabilities between ISO C and ISO C++](../../assets/figures/comparison.png) - -From now on, you should have the idea that "C++ is **not** a superset of C" in your mind (and not from the beginning, later [References for further reading](#further-readings) The difference between C++98 and C99 is given). When writing C++, you should also avoid using program styles such as `void*` whenever possible. When you have to use C, you should pay attention to the use of `extern "C"`, separate the C language code from the C++ code, and then unify the link, for instance: - -```cpp -// foo.h -#ifdef __cplusplus -extern "C" { -#endif - -int add(int x, int y); - -#ifdef __cplusplus -} -#endif - -// foo.c -int add(int x, int y) { - return x+y; -} - -// 1.1.cpp -#include "foo.h" -#include -#include - -int main() { - [out = std::ref(std::cout << "Result from C code: " << add(1, 2))](){ - out.get() << ".\n"; - }(); - return 0; -} -``` - -You should first compile the C code with `gcc`: - -```bash -gcc -c foo.c -``` - -Compile and output the `foo.o` file, and link the C++ code to the `.o` file using `clang++` (or both compile to `.o` and then link them together): - -```bash -clang++ 1.1.cpp foo.o -std=c++2a -o 1.1 -``` - -Of course, you can use `Makefile` to compile the above code: - -```makefile -C = gcc -CXX = clang++ - -SOURCE_C = foo.c -OBJECTS_C = foo.o - -SOURCE_CXX = 1.1.cpp - -TARGET = 1.1 -LDFLAGS_COMMON = -std=c++2a - -all: - $(C) -c $(SOURCE_C) - $(CXX) $(SOURCE_CXX) $(OBJECTS_C) $(LDFLAGS_COMMON) -o $(TARGET) - -clean: - rm -rf *.o $(TARGET) -``` - -> **Note**: Indentation in `Makefile` is a tab instead of a space character. If you copy this code directly into your editor, the tab may be automatically replaced. Please ensure the indentation in the `Makefile` is done by tabs. -> -> If you don't know the use of `Makefile`, it doesn't matter. In this tutorial, you won't build code that is written too complicated. You can also read this book by simply using `clang++ -std=c++2a` on the command line. - -If you are new to modern C++, you probably still don't understand the following small piece of code above, namely: - -```cpp -[out = std::ref(std::cout << "Result from C code: " << add(1, 2))](){ - out.get() << ".\n"; -}(); -``` - -Don't worry at the moment, we will come to meet them in our later chapters. - -[Table of Content](./toc.md) | [Previous Chapter](./00-preface.md) | [Next Chapter: Language Usability Enhancements](./02-usability.md) - -## Further Readings - -- [A Tour of C++ (2nd Edition) Bjarne Stroustrup](https://www.amazon.com/dp/0134997832/ref=cm_sw_em_r_mt_dp_U_GogjDbHE2H53B) - [History of C++](http://en.cppreference.com/w/cpp/language/history) -- [C++ compiler support](https://en.cppreference.com/w/cpp/compiler_support) -- [Incompatibilities Between ISO C and ISO C++](http://david.tribble.com/text/cdiffs.htm#C99-vs-CPP98) - -## Licenses - -Creative Commons License
This work was written by [Ou Changkun](https://changkun.de) and licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. The code of this repository is open sourced under the [MIT license](../../LICENSE). diff --git a/docs/book/en-us/02-usability.md b/docs/book/en-us/02-usability.md deleted file mode 100644 index eb8a21a..0000000 --- a/docs/book/en-us/02-usability.md +++ /dev/null @@ -1,1120 +0,0 @@ ---- -title: "Chapter 02: Language Usability Enhancements" -type: book-en-us -order: 2 ---- - -# Chapter 02: Language Usability Enhancements - -[TOC] - -When we declare, define a variable or constant, and control the flow of code, -object-oriented functions, template programming, etc., before the runtime, -it may happen when writing code or compiler compiling code. -To this end, we usually talk about **language usability**, -which refers to the language behavior that occurred before the runtime. - -## 2.1 Constants - -### nullptr - -The purpose of `nullptr` appears to replace `NULL`. There are **null pointer constants** in the C and C++ languages, -which can be implicitly converted to null pointer value of any pointer type, -or null member pointer value of any pointer-to-member type in C++. -`NULL` is provided by the standard library implementation and defined as an implementation-defined null pointer constant. -In C, some standard libraries defines `NULL` as `((void*)0)` and some define it as `0`. - -C++ **does not allow** to implicitly convert `void *` to other types, and thus `((void*)0)` is not a valid implementation -of `NULL`. If the standard library tries to define `NULL` as `((void*)0)`, then compilation error would occur in the following code: - -```cpp -char *ch = NULL; -``` - -C++ without the `void *` implicit conversion has to define `NULL` as `0`. -This still creates a new problem. Defining `NULL` to `0` will cause the overloading feature in `C++` to be confusing. -Consider the following two `foo` functions: - -```cpp -void foo(char*); -void foo(int); -``` - -Then the `foo(NULL);` statement will call `foo(int)`, which will cause the code to be counterintuitive. - -To solve this problem, C++11 introduced the `nullptr` keyword, which is specifically used to distinguish null pointers, `0`. The type of `nullptr` is `nullptr_t`, which can be implicitly converted to any pointer or member pointer type, and can be compared equally or unequally with them. - -You can try to compile the following code using clang++: - -```cpp -#include -#include - -void foo(char *); -void foo(int); - -int main() { - if (std::is_same::value) - std::cout << "NULL == 0" << std::endl; - if (std::is_same::value) - std::cout << "NULL == (void *)0" << std::endl; - if (std::is_same::value) - std::cout << "NULL == nullptr" << std::endl; - - foo(0); // will call foo(int) - // foo(NULL); // doesn't compile - foo(nullptr); // will call foo(char*) - return 0; -} - -void foo(char *) { - std::cout << "foo(char*) is called" << std::endl; -} -void foo(int i) { - std::cout << "foo(int) is called" << std::endl; -} -``` - -The outputs are: - -```bash -foo(int) is called -foo(char*) is called -``` - -From the output we can see that `NULL` is different from `0` and `nullptr`. -So, develop the habit of using `nullptr` directly. - -In addition, in the above code, we used `decltype` and -`std::is_same` which are modern C++ syntax. -In simple terms, `decltype` is used for type derivation, -and `std::is_same` is used to compare the equality of the two types. -We will discuss them in detail later in the [decltype](#decltype) section. - -### constexpr - -C++ itself already has the concept of constant expressions, such as 1+2, -3\*4. Such expressions always produce the same result without any side effects. -If the compiler can directly optimize and embed these expressions into the program at -compile-time, it will increase the performance of the program. A very obvious example -is in the definition phase of an array: - -```cpp -#include -#define LEN 10 - -int len_foo() { - int i = 2; - return i; -} -constexpr int len_foo_constexpr() { - return 5; -} - -constexpr int fibonacci(const int n) { - return n == 1 || n == 2 ? 1 : fibonacci(n-1) + fibonacci(n-2); -} - - -int main() { - char arr_1[10]; // legal - char arr_2[LEN]; // legal - - int len = 10; - // char arr_3[len]; // illegal - - const int len_2 = len + 1; - constexpr int len_2_constexpr = 1 + 2 + 3; - // char arr_4[len_2]; // illegal, but ok for most of the compilers - char arr_4[len_2_constexpr]; // legal - - // char arr_5[len_foo()+5]; // illegal - char arr_6[len_foo_constexpr() + 1]; // legal - - // 1, 1, 2, 3, 5, 8, 13, 21, 34, 55 - std::cout << fibonacci(10) << std::endl; - - return 0; -} -``` - -In the above example, `char arr_4[len_2]` may be confusing because `len_2` has been defined as a constant. -Why is `char arr_4[len_2]` still illegal? -This is because the length of the array in the C++ standard must be a constant expression, -and for `len_2`, this is a `const` constant, not a constant expression, -so even if this behavior is supported by most compilers, but it is an illegal behavior, -we need to use the `constexpr` feature introduced in C++11, which will be introduced next, -to solve this problem; for `arr_5`, before C++98 The compiler cannot know that `len_foo()` -actually returns a constant at runtime, which causes illegal production. - -> Note that most compilers now have their compiler optimizations. -> Many illegal behaviors become legal under the compiler's optimization. -> If you need to reproduce the error, you need to use the old version of the compiler. - -C++11 provides `constexpr` to let the user explicitly declare that the function or -object constructor will become a constant expression at compile time. -This keyword explicitly tells the compiler that it should verify that `len_foo` -should be a compile-time constant expression. - -In addition, the function of `constexpr` can use recursion: - -```cpp -constexpr int fibonacci(const int n) { - return n == 1 || n == 2 ? 1 : fibonacci(n-1) + fibonacci(n-2); -} -``` - -Starting with C++14, -the constexpr function can use simple statements such as local variables, -loops, and branches internally. -For example, the following code cannot be compiled under the C++11 standard: - -```cpp -constexpr int fibonacci(const int n) { - if(n == 1) return 1; - if(n == 2) return 1; - return fibonacci(n-1) + fibonacci(n-2); -} -``` - -To do this, we can write a simplified version like this -to make the function available from C++11: - -```cpp -constexpr int fibonacci(const int n) { - return n == 1 || n == 2 ? 1 : fibonacci(n-1) + fibonacci(n-2); -} -``` - -## 2.2 Variables and initialization - -### if-switch - -In traditional C++, the declaration of a variable can declare a temporary variable `int` -even though it can be located anywhere, even within a `for` statement, -but there is always no way to declare a temporary variable in the `if` and `switch` statements. -E.g: - -```cpp -#include -#include -#include - -int main() { - std::vector vec = {1, 2, 3, 4}; - - // since c++17, can be simplified by using `auto` - const std::vector::iterator itr = std::find(vec.begin(), vec.end(), 2); - if (itr != vec.end()) { - *itr = 3; - } - - if (const std::vector::iterator itr = std::find(vec.begin(), vec.end(), 3); - itr != vec.end()) { - *itr = 4; - } - - // should output: 1, 4, 3, 4. can be simplified using `auto` - for (std::vector::iterator element = vec.begin(); element != vec.end(); - ++element) - std::cout << *element << std::endl; -} -``` - -In the above code, we can see that the `itr` variable is defined in the scope of -the entire `main()`, which causes us to rename the other when a variable need to traverse -the entire `std::vector` again. C++17 eliminates this limitation so that -we can do this in if(or switch): - -```cpp -if (const std::vector::iterator itr = std::find(vec.begin(), vec.end(), 3); - itr != vec.end()) { - *itr = 4; -} -``` - -Is it similar to the Go? - -### Initializer list - -Initialization is a very important language feature, -the most common one is when the object is initialized. -In traditional C++, different objects have different initialization methods, -such as ordinary arrays, PODs (**P**lain **O**ld **D**ata, -i.e. classes without constructs, destructors, and virtual functions) -Or struct type can be initialized with `{}`, -which is what we call the initialization list. -For the initialization of the class object, -you need to use the copy construct, -or you need to use `()`. -These different methods are specific to each other and cannot be generic. -E.g: - -```cpp -#include -#include - -class Foo { -public: - int value_a; - int value_b; - Foo(int a, int b) : value_a(a), value_b(b) {} -}; - -int main() { - // before C++11 - int arr[3] = {1, 2, 3}; - Foo foo(1, 2); - std::vector vec = {1, 2, 3, 4, 5}; - - std::cout << "arr[0]: " << arr[0] << std::endl; - std::cout << "foo:" << foo.value_a << ", " << foo.value_b << std::endl; - for (std::vector::iterator it = vec.begin(); it != vec.end(); ++it) { - std::cout << *it << std::endl; - } - return 0; -} -``` - -To solve this problem, -C++11 first binds the concept of the initialization list to the type -and calls it `std::initializer_list`, -allowing the constructor or other function to use the initialization list -like a parameter, which is the initialization of class objects provides -a unified bridge between normal arrays and POD initialization methods, -such as: - -```cpp -#include -#include -#include - -class MagicFoo { -public: - std::vector vec; - MagicFoo(std::initializer_list list) { - for (std::initializer_list::iterator it = list.begin(); - it != list.end(); ++it) - vec.push_back(*it); - } -}; -int main() { - // after C++11 - MagicFoo magicFoo = {1, 2, 3, 4, 5}; - - std::cout << "magicFoo: "; - for (std::vector::iterator it = magicFoo.vec.begin(); - it != magicFoo.vec.end(); ++it) - std::cout << *it << std::endl; -} -``` - -This constructor is called the initialize list constructor, and the type with -this constructor will be specially taken care of during initialization. - -In addition to the object construction, the initialization list can also -be used as a formal parameter of a normal function, for example: - -```Cpp -public: - void foo(std::initializer_list list) { - for (std::initializer_list::iterator it = list.begin(); - it != list.end(); ++it) vec.push_back(*it); - } - -magicFoo.foo({6,7,8,9}); -``` - -Second, C++11 also provides a uniform syntax for initializing arbitrary objects, such as: - -```cpp -Foo foo2 {3, 4}; -``` - -### Structured binding - -Structured bindings provide functionality similar to the multiple return values -provided in other languages. In the chapter on containers, -we will learn that C++11 has added a `std::tuple` container for -constructing a tuple that encloses multiple return values. But the flaw -is that C++11/14 does not provide a simple way to get and define -the elements in the tuple from the tuple, -although we can unpack the tuple using `std::tie` -But we still have to be very clear about how many objects this tuple contains, -what type of each object is, very troublesome. - -C++17 completes this setting, -and the structured bindings let us write code like this: - -```cpp -#include -#include - -std::tuple f() { - return std::make_tuple(1, 2.3, "456"); -} - -int main() { - auto [x, y, z] = f(); - std::cout << x << ", " << y << ", " << z << std::endl; - return 0; -} -``` - -The `auto` type derivation is described in the -[auto type inference](#auto) section. - -## 2.3 Type inference - -In traditional C and C++, the types of parameters must be clearly defined, which does not help us to quickly encode, especially when we are faced with a large number of complex template types, we must indicate the type of variables to proceed. Subsequent coding, which not only slows down our development efficiency but also makes the code stinking and long. - -C++11 introduces the two keywords `auto` and `decltype` to implement type derivation, letting the compiler worry about the type of the variable. This makes C++ the same as other modern programming languages, in a way that provides the habit of not having to worry about variable types. - -### auto - -`auto` has been in C++ for a long time, but it always exists as an indicator of a storage type, coexisting with `register`. In traditional C++, if a variable is not declared as a `register` variable, it is automatically treated as an `auto` variable. And with `register` being deprecated (used as a reserved keyword in C++17 and later used, it doesn't currently make sense), the semantic change to `auto` is very natural. - -One of the most common and notable examples of type derivation using `auto` is the iterator. You should see the lengthy iterative writing in traditional C++ in the previous section: - -```cpp -// before C++11 -// cbegin() returns vector::const_iterator -// and therefore it is type vector::const_iterator -for(vector::const_iterator it = vec.cbegin(); it != vec.cend(); ++it) -``` - -When we have `auto`: - -```cpp -#include -#include -#include - -class MagicFoo { -public: - std::vector vec; - MagicFoo(std::initializer_list list) { - for (auto it = list.begin(); it != list.end(); ++it) { - vec.push_back(*it); - } - } -}; - -int main() { - MagicFoo magicFoo = {1, 2, 3, 4, 5}; - std::cout << "magicFoo: "; - for (auto it = magicFoo.vec.begin(); it != magicFoo.vec.end(); ++it) { - std::cout << *it << ", "; - } - std::cout << std::endl; - return 0; -} -``` - -Some other common usages: - -```cpp -auto i = 5; // i as int -auto arr = new auto(10); // arr as int * -``` - -Since C++ 14, `auto` can even be used as function arguments in generic lambda expressions, -and such functionality is generalized to normal functions in C++ 20. -Consider the following example: - -```cpp -auto add14 = [](auto x, auto y) -> int { - return x+y; -} - -int add20(auto x, auto y) { - return x+y; -} - -auto i = 5; // type int -auto j = 6; // type int -std::cout << add14(i, j) << std::endl; -std::cout << add20(i, j) << std::endl; -``` - -> **Note**: `auto` cannot be used to derive array types yet: -> -> ```cpp -> auto auto_arr2[10] = {arr}; // illegal, can't infer array type -> -> 2.6.auto.cpp:30:19: error: 'auto_arr2' declared as array of 'auto' -> auto auto_arr2[10] = {arr}; -> ``` - -### decltype - -The `decltype` keyword is used to solve the defect that the auto keyword -can only type the variable. Its usage is very similar to `typeof`: - -```cpp -decltype(expression) -``` - -Sometimes we may need to calculate the type of an expression, for example: - -```cpp -auto x = 1; -auto y = 2; -decltype(x+y) z; -``` - -You have seen in the previous example that -`decltype` is used to infer the usage of the type. -The following example is to determine -if the above variables `x, y, z` are of the same type: - -```cpp -if (std::is_same::value) - std::cout << "type x == int" << std::endl; -if (std::is_same::value) - std::cout << "type x == float" << std::endl; -if (std::is_same::value) - std::cout << "type z == type x" << std::endl; -``` - -Among them, `std::is_same` is used to determine whether -the two types `T` and `U` are equal. The output is: - -``` -type x == int -type z == type x -``` - -### tail type inference - -You may think that whether `auto` can be used to deduce the return type of a function. Still consider an example of an add function, which we have to write in traditional C++: - -```cpp -template -R add(T x, U y) { - return x+y; -} -``` - -> Note: There is no difference between typename and class in the template parameter list. Before the keyword typename appears, class is used to define the template parameters. However, when defining a variable with [nested dependency type](https://en.cppreference.com/w/cpp/language/dependent_name#The_typename_disambiguator_for_dependent_names) in the template, you need to use typename to eliminate ambiguity. - -Such code is very ugly because the programmer must explicitly -indicate the return type when using this template function. -But in fact, we don't know what kind of operation -the `add()` function will do, and what kind of return type to get. - -This problem was solved in C++11. Although you may immediately -react to using `decltype` to derive the type of `x+y`, -write something like this: - -```cpp -decltype(x+y) add(T x, U y) -``` - -But in fact, this way of writing can not be compiled. -This is because `x` and `y` have not been defined -when the compiler reads decltype(x+y). -To solve this problem, C++11 also introduces a trailing return type, -which uses the auto keyword to post the return type: - -```cpp -template -auto add2(T x, U y) -> decltype(x+y){ - return x + y; -} -``` - -The good news is that from C++14 it is possible to directly derive the return value of -a normal function, so the following way becomes legal: - -```cpp -template -auto add3(T x, U y){ - return x + y; -} -``` - -You can check if the type derivation is correct: - -```cpp -// after c++11 -auto w = add2(1, 2.0); -if (std::is_same::value) { - std::cout << "w is double: "; -} -std::cout << w << std::endl; - -// after c++14 -auto q = add3(1.0, 2); -std::cout << "q: " << q << std::endl; -``` - -### decltype(auto) - -`decltype(auto)` is a slightly more complicated use of C++14. - -> To understand it you need to know the concept of parameter forwarding -> in C++, which we will cover in detail in the -> [Language Runtime Enhancements](./03-runtime.md) chapter, -> and you can come back to the contents of this section later. - -In simple terms, `decltype(auto)` is mainly used to derive -the return type of a forwarding function or package, -which does not require us to explicitly specify -the parameter expression of `decltype`. -Consider the following example, when we need to wrap the following -two functions: - -```cpp -std::string lookup1(); -std::string& lookup2(); -``` - -In C++11: - -```cpp -std::string look_up_a_string_1() { - return lookup1(); -} -std::string& look_up_a_string_2() { - return lookup2(); -} -``` - -With `decltype(auto)`, we can let the compiler do this annoying parameter forwarding: - -```cpp -decltype(auto) look_up_a_string_1() { - return lookup1(); -} -decltype(auto) look_up_a_string_2() { - return lookup2(); -} -``` - -## 2.4 Control flow - -### if constexpr - -As we saw at the beginning of this chapter, we know that C++11 introduces the `constexpr` keyword, which compiles expressions or functions into constant results. A natural idea is that if we introduce this feature into the conditional judgment, let the code complete the branch judgment at compile-time, can it make the program more efficient? C++17 introduces the `constexpr` keyword into the `if` statement, allowing you to declare the condition of a constant expression in your code. Consider the following code: - -```cpp -#include - -template -auto print_type_info(const T& t) { - if constexpr (std::is_integral::value) { - return t + 1; - } else { - return t + 0.001; - } -} -int main() { - std::cout << print_type_info(5) << std::endl; - std::cout << print_type_info(3.14) << std::endl; -} -``` - -At compile time, the actual code will behave as follows: - -```cpp -int print_type_info(const int& t) { - return t + 1; -} -double print_type_info(const double& t) { - return t + 0.001; -} -int main() { - std::cout << print_type_info(5) << std::endl; - std::cout << print_type_info(3.14) << std::endl; -} -``` - -### Range-based for loop - -Finally, C++11 introduces a range-based iterative method, and we can write loops that are as concise -as Python, and we can further simplify the previous example: - -```cpp -#include -#include -#include - -int main() { - std::vector vec = {1, 2, 3, 4}; - if (auto itr = std::find(vec.begin(), vec.end(), 3); itr != vec.end()) *itr = 4; - for (auto element : vec) - std::cout << element << std::endl; // read only - for (auto &element : vec) { - element += 1; // writeable - } - for (auto element : vec) - std::cout << element << std::endl; // read only -} -``` - -## 2.5 Templates - -C++ templates have always been a special art of the language, and templates can even be used independently as a new language. The philosophy of the template is to throw all the problems that can be processed at compile time into the compile time, and only deal with those core dynamic services at runtime, to greatly optimize the performance of the runtime. Therefore, templates are also regarded by many as one of the black magic of C++. - -### Extern templates - -In traditional C++, templates are instantiated by the compiler only when they are used. In other words, as long as a fully defined template is encountered in the code compiled in each compilation unit (file), it will be instantiated. This results in an increase in compile time due to repeated instantiations. Also, we have no way to tell the compiler not to trigger the instantiation of the template. - -To this end, C++11 introduces an external template that extends the syntax of the original mandatory compiler to instantiate a template at a specific location, allowing us to explicitly tell the compiler when to instantiate the template: - -```cpp -template class std::vector; // force instantiation -extern template class std::vector; // should not instantiation in current file -``` - -### The ">" - -In the traditional C++ compiler, `>>` is always treated as a right shift operator. But actually we can easily write the code for the nested template: - -```cpp -std::vector> matrix; -``` - -This is not compiled under the traditional C++ compiler, -and C++11 starts with continuous right angle brackets that become legal -and can be compiled successfully. -Even the following writing can be compiled by: - -```cpp -template -class MagicType { - bool magic = T; -}; - -// in main function: -std::vector2)>> magic; // legal, but not recommended -``` - -### Type alias templates - -Before you understand the type alias template, you need to understand the difference between "template" and "type". Carefully understand this sentence: **Templates are used to generate types.** In traditional C++, `typedef` can define a new name for the type, but there is no way to define a new name for the template. Because the template is not a type. E.g: - -```cpp -template -class MagicType { -public: - T dark; - U magic; -}; - -// not allowed -template -typedef MagicType, std::string> FakeDarkMagic; -``` - -C++11 uses `using` to introduce the following form of writing, and at the same time supports the same effect as the traditional `typedef`: - -> Usually, we use `typedef` to define the alias syntax: `typedef original name new name; `, but the definition syntax for aliases such as function pointers is different, which usually causes a certain degree of difficulty for direct reading. - -```cpp -typedef int (*process)(void *); -using NewProcess = int(*)(void *); -template -using TrueDarkMagic = MagicType, std::string>; - -int main() { - TrueDarkMagic you; -} -``` - -### Variadic templates - -The template has always been one of C++'s unique **Black Magic**. -In traditional C++, -both a class template and a function template could only accept -a fixed set of template parameters as specified; -C++11 added a new representation, allowing any number, -template parameters of any category, -and there is no need to fix the number of parameters when defining. - -```cpp -template class Magic; -``` - -The template class Magic object can accept an unrestricted number of typename as -a formal parameter of the template, such as the following definition: - -```cpp -class Magic, - std::map>> darkMagic; -``` - -Since it is arbitrary, a template parameter with a number of 0 is also possible: `class Magic<> nothing;`. - -If you do not want to generate 0 template parameters, you can manually define at least one template parameter: - -```cpp -template class Magic; -``` - -The variable length parameter template can also be directly adjusted to the template function. -The `printf` function in the traditional C, although it can also reach the call of an indefinite number of formal parameters, is not class safe. In addition to the variable-length parameter functions that define class safety, C++11 can also make printf-like functions naturally handle objects that are not self-contained. In addition to the use of `...` in the template parameters to indicate the indefinite length of the template parameters, the function parameters also use the same representation to represent the indefinite length parameters, which provides a convenient means for us to simply write variable length parameter functions, such as: - -```cpp -template void printf(const std::string &str, Args... args); -``` - -Then we define variable length template parameters, -how to unpack the parameters? - -First, we can use `sizeof...` to calculate the number of arguments: - -```cpp -#include -template -void magic(Ts... args) { - std::cout << sizeof...(args) << std::endl; -} -``` - -We can pass any number of arguments to the `magic` function: - -```cpp -magic(); // 0 -magic(1); // 1 -magic(1, ""); // 2 -``` - -Second, the parameters are unpacked. So far there is no simple way to process -the parameter package, but there are two classic processing methods: - -**1. Recursive template function** - -Recursion is a very easy way to think of and the most classic approach. This method continually recursively passes template parameters to the function, thereby achieving the purpose of recursively traversing all template parameters: - -```cpp -#include -template -void printf1(T0 value) { - std::cout << value << std::endl; -} -template -void printf1(T value, Ts... args) { - std::cout << value << std::endl; - printf1(args...); -} -int main() { - printf1(1, 2, "123", 1.1); - return 0; -} -``` - -**2. Variable parameter template expansion** - -You should feel that this is very cumbersome. Added support for variable parameter template expansion in C++17, so you can write `printf` in a function: - -```cpp -template -void printf2(T0 t0, T... t) { - std::cout << t0 << std::endl; - if constexpr (sizeof...(t) > 0) printf2(t...); -} -``` - -> In fact, sometimes we use variable parameter templates, but we don't necessarily need to traverse the parameters one by one. We can use the features of `std::bind` and perfect forwarding to achieve the binding of functions and parameters, thus achieving success. The purpose of the call. - -**3. Initialize list expansion** - -Recursive template functions are standard practice, but the obvious drawback is that you must define a function that terminates recursion. - -Here is a description of the black magic that is expanded using the initialization list: - -```cpp -template -auto printf3(T value, Ts... args) { - std::cout << value << std::endl; - (void) std::initializer_list{([&args] { - std::cout << args << std::endl; - }(), value)...}; -} -``` - -In this code, the initialization list provided in C++11 and the properties of the Lambda expression (mentioned in the next section) are additionally used. - -By initializing the list, `(lambda expression, value)...` will be expanded. Due to the appearance of the comma expression, the previous lambda expression is executed first, and the output of the parameter is completed. -To avoid compiler warnings, we can explicitly convert `std::initializer_list` to `void`. - -### Fold expression - -In C++ 17, this feature of the variable length parameter is further brought to the expression, consider the following example: - -```cpp -#include -template -auto sum(T ... t) { - return (t + ...); -} -int main() { - std::cout << sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) << std::endl; -} -``` - -### Non-type template parameter deduction - -What we mainly mentioned above is a form of template parameters: type template parameters. - -```cpp -template -auto add(T t, U u) { - return t+u; -} -``` - -The parameters of the template `T` and `U` are specific types. -But there is also a common form of template parameter that allows different literals -to be template parameters, i.e. non-type template parameters: - -```cpp -template -class buffer_t { -public: - T& alloc(); - void free(T& item); -private: - T data[BufSize]; -} - -buffer_t buf; // 100 as template parameter -``` - -In this form of template parameters, we can pass `100` as a parameter to the template. -After C++11 introduced the feature of type derivation, we will naturally ask, since the template parameters here. -Passing with a specific literal, can the compiler assist us in type derivation, -By using the placeholder `auto`, there is no longer a need to explicitly specify the type? -Fortunately, C++17 introduces this feature, and we can indeed use the `auto` keyword to let the compiler assist in the completion of specific types of derivation. -E.g: - -```cpp -template void foo() { - std::cout << value << std::endl; - return; -} - -int main() { - foo<10>(); // value as int -} -``` - -## 2.6 Object-oriented - -### Delegate constructor - -C++11 introduces the concept of a delegate construct, which allows a constructor to call another constructor -in a constructor in the same class, thus simplifying the code: - -```cpp -#include -class Base { -public: - int value1; - int value2; - Base() { - value1 = 1; - } - Base(int value) : Base() { // delegate Base() constructor - value2 = value; - } -}; - -int main() { - Base b(2); - std::cout << b.value1 << std::endl; - std::cout << b.value2 << std::endl; -} -``` - -### Inheritance constructor - -In traditional C++, constructors need to pass arguments one by one if they need inheritance, which leads to inefficiency. C++11 introduces the concept of inheritance constructors using the keyword using: - -```cpp -#include -class Base { -public: - int value1; - int value2; - Base() { - value1 = 1; - } - Base(int value) : Base() { // delegate Base() constructor - value2 = value; - } -}; -class Subclass : public Base { -public: - using Base::Base; // inheritance constructor -}; -int main() { - Subclass s(3); - std::cout << s.value1 << std::endl; - std::cout << s.value2 << std::endl; -} -``` - -### Explicit virtual function overwrite - -In traditional C++, it is often prone to accidentally overloading virtual functions. E.g: - -```cpp -struct Base { - virtual void foo(); -}; -struct SubClass: Base { - void foo(); -}; -``` - -`SubClass::foo` may not be a programmer trying to overload a virtual function, just adding a function with the same name. Another possible scenario is that when the virtual function of the base class is deleted, the subclass owns the old function and no longer overloads the virtual function and turns it into a normal class method, which has catastrophic consequences. - -C++11 introduces the two keywords `override` and `final` to prevent this from happening. - -### override - -When overriding a virtual function, introducing the `override` keyword will explicitly tell the compiler to overload, and the compiler will check if the base function has such a virtual function with consistent function signature, otherwise it will not compile: - -```cpp -struct Base { - virtual void foo(int); -}; -struct SubClass: Base { - virtual void foo(int) override; // legal - virtual void foo(float) override; // illegal, no virtual function in super class -}; -``` - -### final - -`final` is to prevent the class from being continued to inherit and to terminate -the virtual function to continue to be overloaded. - -```cpp -struct Base { - virtual void foo() final; -}; -struct SubClass1 final: Base { -}; // legal - -struct SubClass2 : SubClass1 { -}; // illegal, SubClass1 has final - -struct SubClass3: Base { - void foo(); // illegal, foo has final -}; -``` - -### Explicit delete default function - -In traditional C++, if the programmer does not provide it, the compiler will default to generating default constructors, copy constructs, assignment operators, and destructors for the object. Besides, C++ also defines operators such as `new` `delete` for all classes. This part of the function can be overridden when the programmer needs it. - -This raises some requirements: the ability to accurately control the generation of default functions cannot be controlled. For example, when copying a class is prohibited, the copy constructor and the assignment operator must be declared as `private`. Trying to use these undefined functions will result in compilation or link errors, which is a very unconventional way. - -Also, the default constructor generated by the compiler cannot exist at the same time as the user-defined constructor. If the user defines any constructor, the compiler will no longer generate the default constructor, but sometimes we want to have both constructors at the same time, which is awkward. - -C++11 provides a solution to the above requirements, allowing explicit declarations to take or reject functions that come with the compiler. E.g: - -```cpp -class Magic { - public: - Magic() = default; // explicit let compiler use default constructor - Magic& operator=(const Magic&) = delete; // explicit declare refuse constructor - Magic(int magic_number); -} -``` - -### Strongly typed enumerations - -In traditional C++, enumerated types are not type-safe, and enumerated types are treated as integers, which allows two completely different enumerated types to be directly compared (although the compiler gives the check, but not all), ** Even the enumeration value names of different enum types in the same namespace cannot be the same**, which is usually not what we want to see. - -C++11 introduces an enumeration class and declares it using the syntax of `enum class`: - -```cpp -enum class new_enum : unsigned int { - value1, - value2, - value3 = 100, - value4 = 100 -}; -``` - -The enumeration thus defined implements type safety. First, it cannot be implicitly converted to an integer, nor can it be compared to integer numbers, and it is even less likely to compare enumerated values of different enumerated types. But if the values specified are the same between the same enumerated values, then you can compare: - -```cpp -if (new_enum::value3 == new_enum::value4) { // true - std::cout << "new_enum::value3 == new_enum::value4" << std::endl; -} -``` - -In this syntax, the enumeration type is followed by a colon and a type keyword to specify the type of the enumeration value in the enumeration, which allows us to assign a value to the enumeration (int is used by default when not specified). - -And we want to get the value of the enumeration value, we will have to explicitly type conversion, but we can overload the `<<` operator to output, you can collect the following code snippet: - -```cpp -#include -template -std::ostream& operator<<( - typename std::enable_if::value, - std::ostream>::type& stream, const T& e) -{ - return stream << static_cast::type>(e); -} -``` - -At this point, the following code will be able to be compiled: - -```cpp -std::cout << new_enum::value3 << std::endl -``` - -## Conclusion - -This section introduces the enhancements to language usability in modern C++, which I believe are the most important features that almost everyone needs to know and use: - -1. Auto type derivation -2. Scope for iteration -3. Initialization list -4. Variable parameter template - -## Exercises - -1. Using structured binding, implement the following functions with just one line of function code: - - ```cpp - #include - #include - #include - - template - void update(std::map& m, F foo) { - // TODO: - } - int main() { - std::map m { - {"a", 1}, - {"b", 2}, - {"c", 3} - }; - update(m, [](std::string key) { - return std::hash{}(key); - }); - for (auto&& [key, value] : m) - std::cout << key << ":" << value << std::endl; - } - ``` - -2. Try to implement a function for calculating the mean with [Fold Expression](#Fold-expression), allowing any arguments to be passed in. - -> Refer to the answer [see this](../../exercises/2). - -[Table of Content](./toc.md) | [Previous Chapter](./01-intro.md) | [Next Chapter: Language Runtime Enhancements](./03-runtime.md) - -## Licenses - -Creative Commons License
This work was written by [Ou Changkun](https://changkun.de) and licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. The code of this repository is open sourced under the [MIT license](../../LICENSE). diff --git a/docs/book/en-us/03-runtime.md b/docs/book/en-us/03-runtime.md deleted file mode 100644 index 8ecd295..0000000 --- a/docs/book/en-us/03-runtime.md +++ /dev/null @@ -1,650 +0,0 @@ ---- -title: "Chapter 03: Language Runtime Enhancements" -type: book-en-us -order: 3 ---- - -# Chapter 03: Language Runtime Enhancements - -[TOC] - -## 3.1 Lambda Expression - -Lambda expressions are one of the most important features in modern C++, and Lambda expressions provide a feature like anonymous functions. -Anonymous functions are used when a function is needed, but you don’t want to use a name to call a function. There are many, many scenes like this. -So anonymous functions are almost standard in modern programming languages. - -### Basics - -The basic syntax of a Lambda expression is as follows: - -``` -[capture list] (parameter list) mutable(optional) exception attribute -> return type { -// function body -} -``` - -The above grammar rules are well understood except for the things in `[capture list]`, -except that the function name of the general function is omitted. -The return value is in the form of a `->` -(we have already mentioned this in the tail return type earlier in the previous section). - -The so-called capture list can be understood as a type of parameter. -The internal function body of a lambda expression cannot use variables outside -the body of the function by default. -At this time, the capture list can serve to transfer external data. -According to the behavior passed, -the capture list is also divided into the following types: - -#### 1. Value capture - -Similar to parameter passing, the value capture is based on the fact that -the variable can be copied, except that the captured variable is copied -when the lambda expression is created, not when it is called: - -```cpp -void lambda_value_capture() { - int value = 1; - auto copy_value = [value] { - return value; - }; - value = 100; - auto stored_value = copy_value(); - std::cout << "stored_value = " << stored_value << std::endl; - // At this moment, stored_value == 1, and value == 100. - // Because copy_value has copied when its was created. -} -``` - -#### 2. Reference capture - -Similar to a reference pass, the reference capture saves the reference and the value changes. - -```cpp -void lambda_reference_capture() { - int value = 1; - auto copy_value = [&value] { - return value; - }; - value = 100; - auto stored_value = copy_value(); - std::cout << "stored_value = " << stored_value << std::endl; - // At this moment, stored_value == 100, value == 100. - // Because copy_value stores reference -} -``` - -#### 3. Implicit capture - -Manually writing a capture list is sometimes very complicated. -This mechanical work can be handled by the compiler. -At this point, you can write a `&` or `=` to the compiler to -declare the reference or value capture. - -To summarize, capture provides the ability for lambda expressions -to use external values. The four most common forms of -capture lists can be: - -- \[\] empty capture list -- \[name1, name2, ...\] captures a series of variables -- \[&\] reference capture, determine the reference capture list from the uses the in function body -- \[=\] value capture, determine the value capture list from the uses in the function body - -#### 4. Expression capture - -> This section needs to understand the rvalue references and smart pointers that -> will be mentioned later. - -The value captures and reference captures mentioned above are variables that have been -declared in the outer scope, so these capture methods capture the lvalue -and not capture the rvalue. - -C++14 gives us the convenience of allowing the captured members to be initialized -with arbitrary expressions, which allows the capture of rvalues. -The type of the captured variable being declared is judged according to the expression, -and the judgment is the same as using `auto`: - -```cpp -#include -#include // std::make_unique -#include // std::move - -void lambda_expression_capture() { - auto important = std::make_unique(1); - auto add = [v1 = 1, v2 = std::move(important)](int x, int y) -> int { - return x+y+v1+(*v2); - }; - std::cout << add(3,4) << std::endl; -} -``` - -In the above code, `important` is an exclusive pointer that cannot be caught by value capture using `=`. -At this time we need to transfer it to the rvalue and -initialize it in the expression. - -### Generic Lambda - -In the previous section, we mentioned that the `auto` keyword cannot be used -in the parameter list because it would conflict with the functionality of the template. -But lambda expressions are not regular functions, without further specification on the typed parameter list, lambda expressions cannot utilize templates. Fortunately, this trouble -only exists in C++11, starting with C++14. The formal parameters of the lambda function -can use the `auto` keyword to utilize template generics: - -```cpp -void lambda_generic() { - auto generic = [](auto x, auto y) { - return x+y; - }; - - std::cout << generic(1, 2) << std::endl; - std::cout << generic(1.1, 2.2) << std::endl; -} -``` - -## 3.2 Function Object Wrapper - -Although the features are part of the standard library and not found in runtime, -it enhances the runtime capabilities of the C++ language. -This part of the content is also very important, so put it here for the introduction. - -### `std::function` - -The essence of a Lambda expression is an object of a class type (called a closure type) -that is similar to a function object type (called a closure object). -When the capture list of a Lambda expression is empty, the closure object -can also be converted to a function pointer value for delivery, for example: - -```cpp -#include -using foo = void(int); // function pointer -void functional(foo f) { - f(1); -} -int main() { - auto f = [](int value) { - std::cout << value << std::endl; - }; - functional(f); // call by function pointer - f(1); // call by lambda expression - return 0; -} -``` - -The above code gives two different forms of invocation, one is to call Lambda -as a function type, and the other is to directly call a Lambda expression. -In C++11, these concepts are unified. -The type of object that can be called is collectively called the callable type. -This type is introduced by `std::function`. - -C++11 `std::function` is a generic, polymorphic function wrapper -whose instances can store, copy, and call any target entity that can be called. -It is also an existing callable to C++. A type-safe package of entities (relatively, -the call to a function pointer is not type-safe), in other words, -a container of functions. When we have a container for functions, -we can more easily handle functions and function pointers as objects. e.g: - -```cpp -#include -#include - -int foo(int para) { - return para; -} - -int main() { - // std::function wraps a function that take int paremeter and returns int value - std::function func = foo; - - int important = 10; - std::function func2 = [&](int value) -> int { - return 1+value+important; - }; - std::cout << func(10) << std::endl; - std::cout << func2(10) << std::endl; -} -``` - -### `std::bind` and `std::placeholder` - -And `std::bind` is used to bind the parameters of the function call. -It solves the requirement that we may not always be able to get all the parameters -of a function at one time. Through this function, we can Part of the call parameters -are bound to the function in advance to become a new object, -and then complete the call after the parameters are complete. e.g: - -```cpp -int foo(int a, int b, int c) { - ; -} -int main() { - // bind parameter 1, 2 on function foo, - // and use std::placeholders::_1 as placeholder for the first parameter. - auto bindFoo = std::bind(foo, std::placeholders::_1, 1,2); - // when call bindFoo, we only need one param left - bindFoo(1); -} -``` - -> **Tip:** Note the magic of the `auto` keyword. Sometimes we may not be familiar -> with the return type of a function, but we can circumvent this problem by using `auto`. - -## 3.3 rvalue Reference - -rvalue references are one of the important features introduced by C++11 -that are synonymous with Lambda expressions. Its introduction solves -a large number of historical issues in C++. -Eliminating extra overhead such as `std::vector`, `std::string`, -and making the function object container `std::function` possible. - -### lvalue, rvalue, prvalue, xvalue - -To understand what the rvalue reference is all about, you must have a clear -understanding of the lvalue and the rvalue. - -**lvalue, left value**, as the name implies, is the value to the left of the assignment -symbol. To be precise, an lvalue is a persistent object that still exists after -an expression (not necessarily an assignment expression). - -**Rvalue, right value**, the value on the right refers to the temporary object -that no longer exists after the expression ends. - -In C++11, in order to introduce powerful rvalue references, -the concept of rvalue values ​​is further divided into: -prvalue, and xvalue. - -**pvalue, pure rvalue**, purely rvalue, either purely literal, -such as `10`, `true`; either the result of the evaluation is equivalent to -a literal or anonymous temporary object, for example `1+2`. -Temporary variables returned by non-references, temporary variables generated -by operation expressions, original literals, and Lambda expressions -are all pure rvalue values. - -Note that a literal (except a string literal) is a prvalue. However, a string -literal is an lvalue with type `const char` array. Consider the following examples: - -```cpp -#include - -int main() { - // Correct. The type of "01234" is const char [6], so it is an lvalue - const char (&left)[6] = "01234"; - - // Assert success. It is a const char [6] indeed. Note that decltype(expr) - // yields lvalue reference if expr is an lvalue and neither an unparenthesized - // id-expression nor an unparenthesized class member access expression. - static_assert(std::is_same::value, ""); - - // Error. "01234" is an lvalue, which cannot be referenced by an rvalue reference - // const char (&&right)[6] = "01234"; -} -``` - -However, an array can be implicitly converted to a corresponding pointer.The result, if not an lvalue reference, is an rvalue (xvalue if the result is an rvalue reference, prvalue otherwise): - -```cpp -const char* p = "01234"; // Correct. "01234" is implicitly converted to const char* -const char*&& pr = "01234"; // Correct. "01234" is implicitly converted to const char*, which is a prvalue. -// const char*& pl = "01234"; // Error. There is no type const char* lvalue -``` - -**xvalue, expiring value** is the concept proposed by C++11 to introduce -rvalue references (so in traditional C++, pure rvalue and rvalue are the same concepts), -a value that is destroyed but can be moved. - -It would be a little hard to understand the xvalue, -let's look at the code like this: - -```cpp -std::vector foo() { - std::vector temp = {1, 2, 3, 4}; - return temp; -} - -std::vector v = foo(); -``` - -In such code, as far as the traditional understanding is concerned, -the return value `temp` of the function `foo` is internally created -and then assigned to `v`, whereas when `v` gets this object, the entire `temp` is copied. -And then destroy `temp`, if this `temp` is very large, this will cause a lot of extra -overhead (this is the problem that traditional C++ has been criticized for). -In the last line, `v` is the lvalue, and the value returned by `foo()` is -the rvalue (which is also a pure rvalue). - -However, `v` can be caught by other variables, and the return value generated by `foo()` -is used as a temporary value. Once copied by `v`, it will be destroyed immediately, and -cannot be obtained or modified. The xvalue defines behavior in which temporary values ​​can be -identified while being able to be moved. - -After C++11, the compiler did some work for us, where the lvalue `temp` -is subjected to this implicit rvalue conversion, -equivalent to `static_cast &&>(temp)`, -where `v` here moves the value returned by `foo` locally. -This is the move semantics we will mention later. - -### rvalue reference and lvalue reference - -To get a xvalue, you need to use the declaration of the rvalue reference: `T &&`, -where `T` is the type. -The statement of the rvalue reference extends the lifecycle of this temporary value, -and as long as the variable is alive, the xvalue will continue to survive. - -C++11 provides the `std::move` method to unconditionally convert -lvalue parameters to rvalues. -With it we can easily get a rvalue temporary object, for example: - -```cpp -#include -#include - -void reference(std::string& str) { - std::cout << "lvalue" << std::endl; -} -void reference(std::string&& str) { - std::cout << "rvalue" << std::endl; -} - -int main() -{ - std::string lv1 = "string,"; // lv1 is a lvalue - // std::string&& r1 = lv1; // illegal, rvalue can't ref to lvalue - std::string&& rv1 = std::move(lv1); // legal, std::move can convert lvalue to rvalue - std::cout << rv1 << std::endl; // string, - - const std::string& lv2 = lv1 + lv1; // legal, const lvalue reference can - // extend temp variable's lifecycle - // lv2 += "Test"; // illegal, const ref can't be modified - std::cout << lv2 << std::endl; // string,string, - - std::string&& rv2 = lv1 + lv2; // legal, rvalue ref extend lifecycle - rv2 += "string"; // legal, non-const reference can be modified - std::cout << rv2 << std::endl; // string,string,string,string - - reference(rv2); // output: lvalue - - return 0; -} -``` - -`rv2` refers to an rvalue, but since it is a reference, -`rv2` is still an lvalue. - -Note that there is a very interesting historical issue here, -let's look at the following code: - -```cpp -#include - -int main() { - // int &a = std::move(1); // illegal, non-const lvalue reference cannot ref rvalue - const int &b = std::move(1); // legal, const lvalue reference can - - std::cout << b << std::endl; -} -``` - -The first question, why not allow non-constant references to bind to non-lvalues? -This is because there is a logic error in this approach: - -```cpp -void increase(int & v) { - v++; -} -void foo() { - double s = 1; - increase(s); -} -``` - -Since `int&` can't reference a parameter of type `double`, -you must generate a temporary value to hold the value of `s`. -Thus, when `increase()` modifies this temporary value, -`s` itself is not modified after the call is completed. - -The second question, why do constant references allow binding to non-lvalues? -The reason is simple because Fortran needs it. - -### Move semantics - -Traditional C++ has designed the concept of copy/copy for class objects -through copy constructors and assignment operators, -but to implement the movement of resources, -The caller must use the method of copying and then destructing first, -otherwise, you need to implement the interface of the mobile object yourself. -Imagine moving your home directly to your new home instead of -copying everything (rebuy) to your new home. -Throwing away (destroying) all the original things is a very anti-human thing. - -Traditional C++ does not distinguish between the concepts of "mobile" and "copy", -resulting in a large amount of data copying, wasting time and space. -The appearance of rvalue references solves the confusion of these two concepts, -for example: - -```cpp -#include -class A { -public: - int *pointer; - A():pointer(new int(1)) { - std::cout << "construct" << pointer << std::endl; - } - A(A& a):pointer(new int(*a.pointer)) { - std::cout << "copy" << pointer << std::endl; - } // meaningless object copy - A(A&& a):pointer(a.pointer) { - a.pointer = nullptr; - std::cout << "move" << pointer << std::endl; - } - ~A(){ - std::cout << "destruct" << pointer << std::endl; - delete pointer; - } -}; -// avoid compiler optimization -A return_rvalue(bool test) { - A a,b; - if(test) return a; // equal to static_cast(a); - else return b; // equal to static_cast(b); -} -int main() { - A obj = return_rvalue(false); - std::cout << "obj:" << std::endl; - std::cout << obj.pointer << std::endl; - std::cout << *obj.pointer << std::endl; - return 0; -} -``` - -In the code above: - -1. First construct two `A` objects inside `return_rvalue`, and get the output of the two constructors; -2. After the function returns, it will generate a xvalue, which is referenced by the moving structure of `A` (`A(A&&)`), thus extending the life cycle, and taking the pointer in the rvalue and saving it to `obj`. In the middle, the pointer to the xvalue is set to `nullptr`, which prevents the memory area from being destroyed. - -This avoids meaningless copy constructs and enhances performance. -Let's take a look at an example involving a standard library: - -```cpp -#include // std::cout -#include // std::move -#include // std::vector -#include // std::string - -int main() { - - std::string str = "Hello world."; - std::vector v; - - // use push_back(const T&), copy - v.push_back(str); - // "str: Hello world." - std::cout << "str: " << str << std::endl; - - // use push_back(const T&&), - // no copy the string will be moved to vector, - // and therefore std::move can reduce copy cost - v.push_back(std::move(str)); - // str is empty now - std::cout << "str: " << str << std::endl; - - return 0; -} -``` - -### Perfect forwarding - -As we mentioned earlier, the rvalue reference of a declaration is actually an lvalue. -This creates problems for us to parameterize (pass): - -```cpp -#include -#include -void reference(int& v) { - std::cout << "lvalue reference" << std::endl; -} -void reference(int&& v) { - std::cout << "rvalue reference" << std::endl; -} -template -void pass(T&& v) { - std::cout << " normal param passing: "; - reference(v); -} -int main() { - std::cout << "rvalue pass:" << std::endl; - pass(1); - - std::cout << "lvalue pass:" << std::endl; - int l = 1; - pass(l); - - return 0; -} -``` - -For `pass(1)`, although the value is the rvalue, since `v` is a reference, it is also an lvalue. -Therefore `reference(v)` will call `reference(int&)` and output lvalue. -For `pass(l)`, `l` is an lvalue, why is it successfully passed to `pass(T&&)`? - -This is based on the **reference collapsing rule**: In traditional C++, we are not able to continue to reference a reference type. -However, -C++ has relaxed this practice with the advent of rvalue references, -resulting in a reference collapse rule that allows us to reference references, -both lvalue and rvalue. But follow the rules below: - -| Function parameter type | Argument parameter type | Post-derivation function parameter type | -| :---------------------: | :---------------------: | :-------------------------------------: | -| T& | lvalue ref | T& | -| T& | rvalue ref | T& | -| T&& | lvalue ref | T& | -| T&& | rvalue ref | T&& | - -Therefore, the use of `T&&` in a template function may not be able to make an rvalue reference, and when a lvalue is passed, a reference to this function will be derived as an lvalue. -More precisely, **no matter what type of reference the template parameter is, the template parameter can be derived as a right reference type** if and only if the argument type is a right reference. -This makes `v` successful delivery of lvalues. - -Perfect forwarding is based on the above rules. The so-called perfect forwarding is to let us pass the parameters, -Keep the original parameter type (lvalue reference keeps lvalue reference, rvalue reference keeps rvalue reference). -To solve this problem, we should use `std::forward` to forward (pass) the parameters: - -```cpp -#include -#include -void reference(int& v) { - std::cout << "lvalue reference" << std::endl; -} -void reference(int&& v) { - std::cout << "rvalue reference" << std::endl; -} -template -void pass(T&& v) { - std::cout << " normal param passing: "; - reference(v); - std::cout << " std::move param passing: "; - reference(std::move(v)); - std::cout << " std::forward param passing: "; - reference(std::forward(v)); - std::cout << "static_cast param passing: "; - reference(static_cast(v)); -} -int main() { - std::cout << "rvalue pass:" << std::endl; - pass(1); - - std::cout << "lvalue pass:" << std::endl; - int l = 1; - pass(l); - - return 0; -} -``` - -The outputs are: - -``` -rvalue pass: - normal param passing: lvalue reference - std::move param passing: rvalue reference - std::forward param passing: rvalue reference -static_cast param passing: rvalue reference -lvalue pass: - normal param passing: lvalue reference - std::move param passing: rvalue reference - std::forward param passing: lvalue reference -static_cast param passing: lvalue reference -``` - -Regardless of whether the pass parameter is an lvalue or an rvalue, the normal pass argument will forward the argument as an lvalue. -So `std::move` will always accept an lvalue, which forwards the call to `reference(int&&)` to output the rvalue reference. - -Only `std::forward` does not cause any extra copies and **perfectly forwards** (passes) the arguments of the function to other functions that are called internally. - -`std::forward` is the same as `std::move`, and nothing is done. `std::move` simply converts the lvalue to the rvalue. -`std::forward` is just a simple conversion of the parameters. From the point of view of the phenomenon, -`std::forward(v)` is the same as `static_cast(v)`. - -Readers may be curious as to why a statement can return values for two types of returns. -Let's take a quick look at the concrete implementation of `std::forward`. `std::forward` contains two overloads: - -```cpp -template -constexpr _Tp&& forward(typename std::remove_reference<_Tp>::type& __t) noexcept -{ return static_cast<_Tp&&>(__t); } - -template -constexpr _Tp&& forward(typename std::remove_reference<_Tp>::type&& __t) noexcept -{ - static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument" - " substituting _Tp is an lvalue reference type"); - return static_cast<_Tp&&>(__t); -} -``` - -In this implementation, the function of `std::remove_reference` is to eliminate references in the type. -And `std::is_lvalue_reference` is used to check if the type derivation is correct, in the second implementation of `std::forward`. -Check that the received value is indeed an lvalue, which in turn reflects the collapse rule. - -When `std::forward` accepts an lvalue, `_Tp` is deduced to the lvalue, so the return value is the lvalue; and when it accepts the rvalue, -`_Tp` is derived as an rvalue reference, and based on the collapse rule, the return value becomes the rvalue of `&& + &&`. -It can be seen that the principle of `std::forward` is to make clever use of the differences in template type derivation. - -At this point, we can answer the question: Why is `auto&&` the safest way to use looping statements? -Because when `auto` is pushed to a different lvalue and rvalue reference, the collapsed combination with `&&` is perfectly forwarded. - -## Conclusion - -This chapter introduces the most important runtime enhancements in modern C++, and I believe that all the features mentioned in this section are worth knowing: - -Lambda expression - -1. Function object container std::function -2. rvalue reference - -[Table of Content](./toc.md) | [Previous Chapter](./02-usability.md) | [Next Chapter: Containers](./04-containers.md) - -## Further Readings - -- [Bjarne Stroustrup, The Design and Evolution of C++](https://www.amazon.com/Design-Evolution-C-Bjarne-Stroustrup/dp/0201543303) - -## Licenses - -Creative Commons License
This work was written by [Ou Changkun](https://changkun.de) and licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. The code of this repository is open sourced under the [MIT license](../../LICENSE). diff --git a/docs/book/en-us/04-containers.md b/docs/book/en-us/04-containers.md deleted file mode 100644 index 36acbb3..0000000 --- a/docs/book/en-us/04-containers.md +++ /dev/null @@ -1,308 +0,0 @@ ---- -title: "Chapter 04 Containers" -type: book-en-us -order: 4 ---- - -# Chapter 04 Containers - -[TOC] - -## 4.1 Linear Container - -### `std::array` - -When you see this container, you will have this problem: - -1. Why introduce `std::array` instead of `std::vector` directly? -2. Already have a traditional array, why use `std::array`? - -First, answer the first question. Unlike `std::vector`, the size of the `std::array` object is fixed. If the container size is fixed, then the `std::array` container can be used first. -Also, since `std::vector` is automatically expanded, when a large amount of data is stored, and the container is deleted, -The container does not automatically return the corresponding memory of the deleted element. In this case, you need to manually run `shrink_to_fit()` to release this part of the memory. - -```cpp -std::vector v; -std::cout << "size:" << v.size() << std::endl; // output 0 -std::cout << "capacity:" << v.capacity() << std::endl; // output 0 - -// As you can see, the storage of std::vector is automatically managed and -// automatically expanded as needed. -// But if there is not enough space, you need to redistribute more memory, -// and reallocating memory is usually a performance-intensive operation. -v.push_back(1); -v.push_back(2); -v.push_back(3); -std::cout << "size:" << v.size() << std::endl; // output 3 -std::cout << "capacity:" << v.capacity() << std::endl; // output 4 - -// The auto-expansion logic here is very similar to Golang's slice. -v.push_back(4); -v.push_back(5); -std::cout << "size:" << v.size() << std::endl; // output 5 -std::cout << "capacity:" << v.capacity() << std::endl; // output 8 - -// As can be seen below, although the container empties the element, -// the memory of the emptied element is not returned. -v.clear(); -std::cout << "size:" << v.size() << std::endl; // output 0 -std::cout << "capacity:" << v.capacity() << std::endl; // output 8 - -// Additional memory can be returned to the system via the shrink_to_fit() call -v.shrink_to_fit(); -std::cout << "size:" << v.size() << std::endl; // output 0 -std::cout << "capacity:" << v.capacity() << std::endl; // output 0 -``` - -The second problem is much simpler. Using `std::array` can make the code more "modern" and encapsulate some manipulation functions, such as getting the array size and checking if it is not empty, and also using the standard friendly. Container algorithms in the library, such as `std::sort`. - -Using `std::array` is as simple as specifying its type and size: - -```cpp -std::array arr = {1, 2, 3, 4}; - -arr.empty(); // check if container is empty -arr.size(); // return the size of the container - -// iterator support -for (auto &i : arr) -{ - // ... -} - -// use lambda expression for sort -std::sort(arr.begin(), arr.end(), [](int a, int b) { - return b < a; -}); - -// array size must be constexpr -constexpr int len = 4; -std::array arr = {1, 2, 3, 4}; - -// illegal, different than C-style array, std::array will not deduce to T* -// int *arr_p = arr; -``` - -When we started using `std::array`, it was inevitable that we would encounter a C-style compatible interface. There are three ways to do this: - -```cpp -void foo(int *p, int len) { - return; -} - -std::array arr = {1,2,3,4}; - -// C-stype parameter passing -// foo(arr, arr.size()); // illegal, cannot convert implicitly -foo(&arr[0], arr.size()); -foo(arr.data(), arr.size()); - -// use `std::sort` -std::sort(arr.begin(), arr.end()); -``` - -### `std::forward_list` - -`std::forward_list` is a list container, and the usage is similar to `std::list`, so we don't spend a lot of time introducing it. - -Need to know is that, unlike the implementation of the doubly linked list of `std::list`, `std::forward_list` is implemented using a singly linked list. -Provides element insertion of `O(1)` complexity, does not support fast random access (this is also a feature of linked lists), -It is also the only container in the standard library container that does not provide the `size()` method. Has a higher space utilization than `std::list` when bidirectional iteration is not required. - -## 4.2 Unordered Container - -We are already familiar with the ordered container `std::map`/`std::set` in traditional C++. These elements are internally implemented by red-black trees. -The average complexity of inserts and searches is `O(log(size))`. When inserting an element, the element size is compared according to the `<` operator and the element is determined to be the same. -And select the appropriate location to insert into the container. When traversing the elements in this container, the output will be traversed one by one in the order of the `<` operator. - -The elements in the unordered container are not sorted, and the internals is implemented by the Hash table. The average complexity of inserting and searching for elements is `O(constant)`, -Significant performance gains can be achieved without concern for the order of the elements inside the container. - -C++11 introduces two unordered containers: `std::unordered_map`/`std::unordered_multimap` and -`std::unordered_set`/`std::unordered_multiset`. - -Their usage is basically similar to the original `std::map`/`std::multimap`/`std::set`/`set::multiset` -Since these containers are already familiar to us, we will not compare them one by one. Let's compare `std::map` and `std::unordered_map` directly: - -```cpp -#include -#include -#include -#include - -int main() { - // initialized in same order - std::unordered_map u = { - {1, "1"}, - {3, "3"}, - {2, "2"} - }; - std::map v = { - {1, "1"}, - {3, "3"}, - {2, "2"} - }; - - // iterates in the same way - std::cout << "std::unordered_map" << std::endl; - for( const auto & n : u) - std::cout << "Key:[" << n.first << "] Value:[" << n.second << "]\n"; - - std::cout << std::endl; - std::cout << "std::map" << std::endl; - for( const auto & n : v) - std::cout << "Key:[" << n.first << "] Value:[" << n.second << "]\n"; -} -``` - -The final output is: - -```txt -std::unordered_map -Key:[2] Value:[2] -Key:[3] Value:[3] -Key:[1] Value:[1] - -std::map -Key:[1] Value:[1] -Key:[2] Value:[2] -Key:[3] Value:[3] -``` - -## 4.3 Tuples - -Programmers who have known Python should be aware of the concept of tuples. Looking at the containers in traditional C++, except for `std::pair` -there seems to be no ready-made structure to store different types of data (usually we will define the structure ourselves). -But the flaw of `std::pair` is obvious, only two elements can be saved. - -### Basic Operations - -There are three core functions for the use of tuples: - -1. `std::make_tuple`: construct tuple -2. `std::get`: Get the value of a position in the tuple -3. `std::tie`: tuple unpacking - -```cpp -#include -#include - -auto get_student(int id) { - if (id == 0) - return std::make_tuple(3.8, 'A', "John"); - if (id == 1) - return std::make_tuple(2.9, 'C', "Jack"); - if (id == 2) - return std::make_tuple(1.7, 'D', "Ive"); - - // it is not allowed to return 0 directly - // return type is std::tuple - return std::make_tuple(0.0, 'D', "null"); -} - -int main() { - auto student = get_student(0); - std::cout << "ID: 0, " - << "GPA: " << std::get<0>(student) << ", " - << "Grade: " << std::get<1>(student) << ", " - << "Name: " << std::get<2>(student) << '\n'; - - double gpa; - char grade; - std::string name; - - // unpack tuples - std::tie(gpa, grade, name) = get_student(1); - std::cout << "ID: 1, " - << "GPA: " << gpa << ", " - << "Grade: " << grade << ", " - << "Name: " << name << '\n'; -} -``` - -`std::get` In addition to using constants to get tuple objects, C++14 adds usage types to get objects in tuples: - -```cpp -std::tuple t("123", 4.5, 6.7, 8); -std::cout << std::get(t) << std::endl; -std::cout << std::get(t) << std::endl; // illegal, runtime error -std::cout << std::get<3>(t) << std::endl; -``` - -### Runtime Indexing - -If you think about it, you might find the problem with the above code. `std::get<>` depends on a compile-time constant, so the following is not legal: - -```cpp -int index = 1; -std::get(t); -``` - -So what do you do? The answer is to use `std::variant<>` (introduced by C++ 17) to provide type template parameters for `variant<>` -You can have a `variant<>` to accommodate several types of variables provided (in other languages, such as Python/JavaScript, etc., as dynamic types): - -```cpp -#include -template -constexpr std::variant _tuple_index(const std::tuple& tpl, size_t i) { - if constexpr (n >= sizeof...(T)) - throw std::out_of_range("越界."); - if (i == n) - return std::variant{ std::in_place_index, std::get(tpl) }; - return _tuple_index<(n < sizeof...(T)-1 ? n+1 : 0)>(tpl, i); -} -template -constexpr std::variant tuple_index(const std::tuple& tpl, size_t i) { - return _tuple_index<0>(tpl, i); -} -template -std::ostream & operator<< (std::ostream & s, std::variant const & v) { - std::visit([&](auto && x){ s << x;}, v); - return s; -} -``` - -So we can: - -```cpp -int i = 1; -std::cout << tuple_index(t, i) << std::endl; -``` - -### Merge and Iteration - -Another common requirement is to merge two tuples, which can be done with `std::tuple_cat`: - -```cpp -auto new_tuple = std::tuple_cat(get_student(1), std::move(t)); -``` - -You can immediately see how quickly you can traverse a tuple? But we just introduced how to index a `tuple` by a very number at runtime, then the traversal becomes simpler. -First, we need to know the length of a tuple, which can: - -```cpp -template -auto tuple_len(T &tpl) { - return std::tuple_size::value; -} -``` - -This will iterate over the tuple: - -```cpp -for(int i = 0; i != tuple_len(new_tuple); ++i) - // runtime indexing - std::cout << tuple_index(new_tuple, i) << std::endl; -``` - -## Conclusion - -This chapter briefly introduces the new containers in modern C++. Their usage is similar to that of the existing containers in C++. It is relatively simple, and you can choose the containers you need to use according to the actual scene, to get better performance. - -Although `std::tuple` is effective, the standard library provides limited functionality and there is no way to meet the requirements of runtime indexing and iteration. Fortunately, we have other methods that we can implement on our own. - -[Table of Content](./toc.md) | [Previous Chapter](./03-runtime.md) | [Next Chapter: Smart Pointers and Memory Management](./05-pointers.md) - -## Licenses - -Creative Commons License
This work was written by [Ou Changkun](https://changkun.de) and licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. The code of this repository is open sourced under the [MIT license](../../LICENSE). diff --git a/docs/book/en-us/05-pointers.md b/docs/book/en-us/05-pointers.md deleted file mode 100644 index dab6df9..0000000 --- a/docs/book/en-us/05-pointers.md +++ /dev/null @@ -1,209 +0,0 @@ ---- -title: "Chapter 05 Smart Pointers and Memory Management" -type: book-en-us -order: 5 ---- - -# Chapter 05 Smart Pointers and Memory Management - -[TOC] - -## 5.1 RAII and Reference Counting - -Programmers who understand `Objective-C`/`Swift`/`JavaScript` should know the concept of reference counting. The reference count is counted to prevent memory leaks. -The basic idea is to count the number of dynamically allocated objects. Whenever you add a reference to the same object, the reference count of the referenced object is incremented once. -Each time a reference is deleted, the reference count is decremented by one. When the reference count of an object is reduced to zero, the pointed heap memory is automatically deleted. - -In traditional C++, "remembering" to manually release resources is not always a best practice. Because we are likely to forget to release resources and lead to leakage. -So the usual practice is that for an object, we apply for space when constructor, and free space when the destructor (called when leaving the scope). -That is, we often say that the RAII resource acquisition is the initialization technology. - -There are exceptions to everything, we always need to allocate objects on free storage. In traditional C++ we have to use `new` and `delete` to "remember" to release resources. C++11 introduces the concept of smart pointers, using the idea of ​​reference counting so that programmers no longer need to care about manually releasing memory. -These smart pointers include `std::shared_ptr`/`std::unique_ptr`/`std::weak_ptr`, which need to include the header file ``. - -> Note: The reference count is not garbage collection. The reference count can recover the objects that are no longer used as soon as possible, and will not cause long waits during the recycling process. -> More clearly and indicate the life cycle of resources. - -## 5.2 `std::shared_ptr` - -`std::shared_ptr` is a smart pointer that records how many `shared_ptr` points to an object, eliminating to call `delete`, which automatically deletes the object when the reference count becomes zero. - -But not enough, because using `std::shared_ptr` still needs to be called with `new`, which makes the code a certain degree of asymmetry. - -`std::make_shared` can be used to eliminate the explicit use of `new`, so `std::make_shared` will allocate the objects in the generated parameters. -And return the `std::shared_ptr` pointer of this object type. For example: - -```cpp -#include -#include -void foo(std::shared_ptr i) { - (*i)++; -} -int main() { - // auto pointer = new int(10); // illegal, no direct assignment - // Constructed a std::shared_ptr - auto pointer = std::make_shared(10); - foo(pointer); - std::cout << *pointer << std::endl; // 11 - // The shared_ptr will be destructed before leaving the scope - return 0; -} -``` - -`std::shared_ptr` can get the raw pointer through the `get()` method and reduce the reference count by `reset()`. -And see the reference count of an object by `use_count()`. E.g: - -```cpp -auto pointer = std::make_shared(10); -auto pointer2 = pointer; // reference count+1 -auto pointer3 = pointer; // reference count+1 -int *p = pointer.get(); // no increase of reference count - -std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 3 -std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 3 -std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 3 - -pointer2.reset(); -std::cout << "reset pointer2:" << std::endl; - -std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 2 -std::cout << "pointer2.use_count() = " - << pointer2.use_count() << std::endl; // pointer2 has reset, 0 -std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 2 - -pointer3.reset(); -std::cout << "reset pointer3:" << std::endl; - -std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 1 -std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 0 -std::cout << "pointer3.use_count() = " - << pointer3.use_count() << std::endl; // pointer3 has reset, 0 -``` - -## 5.3 `std::unique_ptr` - -`std::unique_ptr` is an exclusive smart pointer that prohibits other smart pointers from sharing the same object, thus keeping the code safe: - -```cpp -std::unique_ptr pointer = std::make_unique(10); // make_unique, from C++14 -std::unique_ptr pointer2 = pointer; // illegal -``` - -> `make_unique` is not complicated. C++11 does not provide `std::make_unique`, which can be implemented by itself: -> -> ```cpp -> template -> std::unique_ptr make_unique( Args&& ...args ) { -> return std::unique_ptr( new T( std::forward(args)... ) ); -> } -> ``` -> -> As for why it wasn't provided, Herb Sutter, chairman of the C++ Standards Committee, mentioned in his [blog](https://herbsutter.com/gotw/_102/) that it was because they were forgotten. - -Since it is monopolized, in other words, it cannot be copied. However, we can use `std::move` to transfer it to other `unique_ptr`, for example: - -```cpp -#include -#include - -struct Foo { - Foo() { std::cout << "Foo::Foo" << std::endl; } - ~Foo() { std::cout << "Foo::~Foo" << std::endl; } - void foo() { std::cout << "Foo::foo" << std::endl; } -}; - -void f(const Foo &) { - std::cout << "f(const Foo&)" << std::endl; -} - -int main() { - std::unique_ptr p1(std::make_unique()); - - // p1 is not empty, prints - if (p1) p1->foo(); - { - std::unique_ptr p2(std::move(p1)); - - // p2 is not empty, prints - f(*p2); - - // p2 is not empty, prints - if(p2) p2->foo(); - - // p1 is empty, no prints - if(p1) p1->foo(); - - p1 = std::move(p2); - - // p2 is empty, no prints - if(p2) p2->foo(); - std::cout << "p2 was destroyed" << std::endl; - } - // p1 is not empty, prints - if (p1) p1->foo(); - - // Foo instance will be destroyed when leaving the scope -} -``` - -## 5.4 `std::weak_ptr` - -If you think about `std::shared_ptr` carefully, you will still find that there is still a problem that resources cannot be released. Look at the following example: - -```cpp -#include -#include - -class A; -class B; - -class A { -public: - std::shared_ptr pointer; - ~A() { - std::cout << "A was destroyed" << std::endl; - } -}; -class B { -public: - std::shared_ptr pointer; - ~B() { - std::cout << "B was destroyed" << std::endl; - } -}; -int main() { - std::shared_ptr a = std::make_shared(); - std::shared_ptr b = std::make_shared(); - a->pointer = b; - b->pointer = a; - - return 0; -} -``` - -The result is that A and B will not be destroyed. This is because the pointer inside a, b also references `a, b`, which makes the reference count of `a, b` becomes 2, leaving the scope. When the `a, b` smart pointer is destructed, it can only cause the reference count of this area to be decremented by one. This causes the memory area reference count pointed to by the `a, b` object to be non-zero, but the external has no way to find this area, it also caused a memory leak, as shown in Figure 5.1: - -![Figure 5.1](../../assets/figures/pointers1_en.png) - -The solution to this problem is to use the weak reference pointer `std::weak_ptr`, which is a weak reference (compared to `std::shared_ptr` is a strong reference). A weak reference does not cause an increase in the reference count. When a weak reference is used, the final release process is shown in Figure 5.2: - -![Figure 5.2](../../assets/figures/pointers2.png) - -In the above figure, only B is left in the last step, and B does not have any smart pointers to reference it, so this memory resource will also be released. - -`std::weak_ptr` has no implemented `*` and `->` operators, therefore it cannot operate on resources. `std::weak_ptr` allows us to check if a `std::shared_ptr` exists or not. The `expired()` method of a `std::weak_ptr` returns `false` when the resource is not released; Otherwise, it returns `true`. -Furthermore, it can also be used for the purpose of obtaining `std::shared_ptr`, which points to the original object. The `lock()` method returns a `std::shared_ptr` to the original object when the resource is not released, or `nullptr` otherwise. - -## Conclusion - -The technology of smart pointers is not novel. It is a common technology in many languages. Modern C++ introduces this technology, which eliminates the abuse of `new`/`delete` to a certain extent. It is a more mature technology. Programming paradigm. - -[Table of Content](./toc.md) | [Previous Chapter](./04-containers.md) | [Next Chapter: Regular Expression](./06-regex.md) - -## Further Readings - -- [Why does C++11 have `make_shared` but not `make_unique`](https://stackoverflow.com/questions/12580432/why-does-c11-have-make-shared-but-not-make-unique) - -## Licenses - -Creative Commons License
This work was written by [Ou Changkun](https://changkun.de) and licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. The code of this repository is open sourced under the [MIT license](../../LICENSE). diff --git a/docs/book/en-us/06-regex.md b/docs/book/en-us/06-regex.md deleted file mode 100644 index e8b9854..0000000 --- a/docs/book/en-us/06-regex.md +++ /dev/null @@ -1,241 +0,0 @@ ---- -title: "Chapter 06 Regular Expression" -type: book-en-us -order: 6 ---- - -# Chapter 06 Regular Expression - -[TOC] - -## 6.1 Introduction - -Regular expressions are not part of the C++ language and therefore we only briefly -introduced it here. - -Regular expressions describe a pattern of string matching. -The general use of regular expressions is mainly to achieve -the following three requirements: - -1. Check if a string contains some form of substring; -2. Replace the matching substrings; -3. Take the eligible substring from a string. - -Regular expressions are text patterns consisting of ordinary characters (such as a to z) -and special characters. A pattern describes one or more strings to match when searching for text. -Regular expressions act as a template to match a character pattern to the string being searched. - -### Ordinary characters - -Normal characters include all printable and unprintable characters that are not explicitly specified as metacharacters. This includes all uppercase -and lowercase letters, all numbers, all punctuation, and some other symbols. - -### Special characters - -A special character is a character with special meaning in a regular expression and is also the core matching syntax of a regular expression. See the table below: - -| Symbol | Description | -|:----------------:|:---| -| `$` | Matches the end position of the input string.| -| `(`,`)` | Marks the start and end of a subexpression. Subexpressions can be obtained for later use.| -| `*` | Matches the previous subexpression zero or more times. | -| `+` | Matches the previous subexpression one or more times.| -| `.` | Matches any single character except the newline character `\n`.| -| `[` | Marks the beginning of a bracket expression.| -| `?` | Matches the previous subexpression zero or one time, or indicates a non-greedy qualifier.| -| `\` | Marks the next character as either a special character, or a literal character, or a backward reference, or an octal escape character. For example, `n` Matches the character `n`. `\n` matches newline characters. The sequence `\\` Matches the `'\'` character, while `\(` matches the `'('` character. | -| `^` | Matches the beginning of the input string, unless it is used in a square bracket expression, at which point it indicates that the set of characters is not accepted.| -| `{` | Marks the beginning of a qualifier expression.| -| `\|` | Indicates a choice between the two.| - -### Quantifiers - -The qualifier is used to specify how many times a given component of a regular expression must appear to satisfy the match. See the table below: - -| Symbol | Description | -|:-------:|:-----| -| `*` | matches the previous subexpression zero or more times. For example, `foo*` matches `fo` and `foooo`. `*` is equivalent to `{0,}`.| -| `+` | matches the previous subexpression one or more times. For example, `foo+` matches `foo` and `foooo` but does not match `fo`. `+` is equivalent to `{1,}`.| -| `?` | matches the previous subexpression zero or one time. For example, `Your(s)?` can match `Your` in `Your` or `Yours`. `?` is equivalent to `{0,1}`.| -| `{n}` | `n` is a non-negative integer. Matches the determined `n` times. For example, `o{2}` cannot match `o` in `for`, but can match two `o` in `foo`.| -| `{n,}` | `n` is a non-negative integer. Match at least `n` times. For example, `o{2,}` cannot match `o` in `for`, but matches all `o` in `foooooo`. `o{1,}` is equivalent to `o+`. `o{0,}` is equivalent to `o*`.| -| `{n,m}` | `m` and `n` are non-negative integers, where `n` is less than or equal to `m`. Matches at least `n` times and matches up to `m` times. For example, `o{1,3}` will match the first three `o` in `foooooo`. `o{0,1}` is equivalent to `o?`. Note that there can be no spaces between the comma and the two numbers. | - -With these two tables, we can usually read almost all regular expressions. - -## 6.2 `std::regex` and Its Related - -The most common way to match string content is to use regular expressions. Unfortunately, in traditional C++, regular expressions have not been supported by the language level, and are not included in the standard library. C++ is a high-performance language. In the development of background services, the use of regular expressions is also used when judging URL resource links. The most mature and common practice in the industry. - -The general solution is to use the regular expression library of `boost`. C++11 officially incorporates the processing of regular expressions into the standard library, providing standard support from the language level and no longer relying on third parties. - -The regular expression library provided by C++11 operates on the `std::string` object, and the pattern `std::regex` (essentially `std::basic_regex`) is initialized and matched by `std::regex_match` Produces `std::smatch` (essentially the `std::match_results` object). - -We use a simple example to briefly introduce the use of this library. Consider the following regular expression: - -- `[az]+\.txt`: In this regular expression, `[az]` means matching a lowercase letter, `+` can match the previous expression multiple times, so `[az]+` can Matches a string of lowercase letters. In the regular expression, a `.` means to match any character, and `\.` means to match the character `.`, and the last `txt` means to match `txt` exactly three letters. So the content of this regular expression to match is a text file consisting of pure lowercase letters. - -`std::regex_match` is used to match strings and regular expressions, and there are many different overloaded forms. The simplest form is to pass `std::string` and a `std::regex` to match. When the match is successful, it will return `true`, otherwise, it will return `false`. For example: - -```cpp -#include -#include -#include - -int main() { - std::string fnames[] = {"foo.txt", "bar.txt", "test", "a0.txt", "AAA.txt"}; - // In C++, `\` will be used as an escape character in the string. - // In order for `\.` to be passed as a regular expression, - // it is necessary to perform second escaping of `\`, thus we have `\\.` - std::regex txt_regex("[a-z]+\\.txt"); - for (const auto &fname: fnames) - std::cout << fname << ": " << std::regex_match(fname, txt_regex) << std::endl; -} -``` - -Another common form is to pass in the three arguments `std::string`/`std::smatch`/`std::regex`. -The essence of `std::smatch` is actually `std::match_results`. -In the standard library, `std::smatch` is defined as `std::match_results`, -which means `match_results` of a substring iterator type. -Use `std::smatch` to easily get the matching results, for example: - -```cpp -std::regex base_regex("([a-z]+)\\.txt"); -std::smatch base_match; -for(const auto &fname: fnames) { - if (std::regex_match(fname, base_match, base_regex)) { - // the first element of std::smatch matches the entire string - // the second element of std::smatch matches the first expression - // with brackets - if (base_match.size() == 2) { - std::string base = base_match[1].str(); - std::cout << "sub-match[0]: " << base_match[0].str() << std::endl; - std::cout << fname << " sub-match[1]: " << base << std::endl; - } - } -} -``` - -The output of the above two code snippets is: - -``` -foo.txt: 1 -bar.txt: 1 -test: 0 -a0.txt: 0 -AAA.txt: 0 -sub-match[0]: foo.txt -foo.txt sub-match[1]: foo -sub-match[0]: bar.txt -bar.txt sub-match[1]: bar -``` - -## Conclusion - -This section briefly introduces the regular expression itself, -and then introduces the use of the regular expression library -through a practical example based on the main requirements of -using regular expressions. - -## Exercise - -In web server development, we usually want to serve some routes that satisfy a certain condition. -Regular expressions are one of the tools to accomplish this. -Given the following request structure: - -```cpp -struct Request { - // request method, POST, GET; path; HTTP version - std::string method, path, http_version; - // use smart pointer for reference counting of content - std::shared_ptr content; - // hash container, key-value dict - std::unordered_map header; - // use regular expression for path match - std::smatch path_match; -}; -``` - -Requested resource type: - -```cpp -typedef std::map< - std::string, std::unordered_map< - std::string,std::function>> resource_type; -``` - -And server template: - -```cpp -template -class ServerBase { -public: - resource_type resource; - resource_type default_resource; - - void start() { - // TODO - } -protected: - Request parse_request(std::istream& stream) const { - // TODO - } -} -``` - -Please implement the member functions `start()` and `parse_request`. Enable server template users to specify routes as follows: - -```cpp -template -void start_server(SERVER_TYPE &server) { - - // process GET request for /match/[digit+numbers], - // e.g. GET request is /match/abc123, will return abc123 - server.resource["fill_your_reg_ex"]["GET"] = - [](ostream& response, Request& request) - { - string number=request.path_match[1]; - response << "HTTP/1.1 200 OK\r\nContent-Length: " << number.length() - << "\r\n\r\n" << number; - }; - - // peocess default GET request; - // anonymous function will be called - // if no other matches response files in folder web/ - // default: index.html - server.default_resource["fill_your_reg_ex"]["GET"] = - [](ostream& response, Request& request) - { - string filename = "www/"; - - string path = request.path_match[1]; - - // forbidden use `..` access content outside folder web/ - size_t last_pos = path.rfind("."); - size_t current_pos = 0; - size_t pos; - while((pos=path.find('.', current_pos)) != string::npos && pos != last_pos) { - current_pos = pos; - path.erase(pos, 1); - last_pos--; - } - - // (...) - }; - - server.start(); -} -``` - -An suggested solution can be found [here](../../exercises/6). - -[Table of Content](./toc.md) | [Previous Chapter](./05-pointers.md) | [Next Chapter: Threads and Concurrency](./07-thread.md) - -## Further Readings - -1. [Comments from `std::regex`'s author](https://zhihu.com/question/23070203/answer/84248248) -2. [Library document of Regular Expression](https://en.cppreference.com/w/cpp/regex) - -## Licenses - -Creative Commons License
This work was written by [Ou Changkun](https://changkun.de) and licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. The code of this repository is open sourced under the [MIT license](../../LICENSE). diff --git a/docs/book/en-us/07-thread.md b/docs/book/en-us/07-thread.md deleted file mode 100644 index a9f4116..0000000 --- a/docs/book/en-us/07-thread.md +++ /dev/null @@ -1,559 +0,0 @@ ---- -title: "Chapter 07 Parallelism and Concurrency" -type: book-en-us -order: 7 ---- - -# Chapter 07 Parallelism and Concurrency - -[TOC] - -## 7.1 Basic of Parallelism - -`std::thread` is used to create an execution thread instance, so it is the basis for all concurrent programming. It needs to include the `` header file when using it. -It provides a number of basic thread operations, such as `get_id()` to get the thread ID of the thread being created, use `join()` to join a thread, etc., for example: - -```cpp -#include -#include - -int main() { - std::thread t([](){ - std::cout << "hello world." << std::endl; - }); - t.join(); - return 0; -} -``` - -## 7.2 Mutex and Critical Section - -We have already learned the basics of concurrency technology in the operating system, or the database, and `mutex` is one of the cores. -C++11 introduces a class related to `mutex`, with all related functions in the `` header file. - -`std::mutex` is the most basic mutex class in C++11, and a mutex can be created by constructing a `std::mutex` object. -It can be locked by its member function `lock()`, and `unlock()` can be unlocked. -But in the process of actually writing the code, it is best not to directly call the member function, -Because calling member functions, you need to call `unlock()` at the exit of each critical section, and of course, exceptions. -At this time, C++11 also provides a template class `std::lock_guard` for the RAII mechanism for the mutex. - -RAII guarantees the exceptional security of the code while keeping the simplicity of the code. - -```cpp -#include -#include -#include - -int v = 1; - -void critical_section(int change_v) { - static std::mutex mtx; - std::lock_guard lock(mtx); - - // execute contention works - v = change_v; - - // mtx will be released after leaving the scope -} - -int main() { - std::thread t1(critical_section, 2), t2(critical_section, 3); - t1.join(); - t2.join(); - - std::cout << v << std::endl; - return 0; -} -``` - -Because C++ guarantees that all stack objects will be destroyed at the end of the declaration period, such code is also extremely safe. -Whether `critical_section()` returns normally or if an exception is thrown in the middle, a stack unwinding is thrown, and `unlock()` is automatically called. - -> An exception is thrown and not caught (it is implementation-defined whether any stack unwinding is done in this case). - -`std::unique_lock` is more flexible than `std::lock_guard`. -Objects of `std::unique_lock` manage the locking and unlocking operations on the `mutex` object with exclusive ownership (no other `unique_lock` objects owning the ownership of a `mutex` object). So in concurrent programming, it is recommended to use `std::unique_lock`. - -`std::lock_guard` cannot explicitly call `lock` and `unlock`, and `std::unique_lock` can be called anywhere after the declaration. -It can reduce the scope of the lock and provide higher concurrency. - -If you use the condition variable `std::condition_variable::wait` you must use `std::unique_lock` as a parameter. - -For instance: - -```cpp -#include -#include -#include - -int v = 1; - -void critical_section(int change_v) { - static std::mutex mtx; - std::unique_lock lock(mtx); - // do contention operations - v = change_v; - std::cout << v << std::endl; - // release the lock - lock.unlock(); - - // during this period, - // others are allowed to acquire v - - // start another group of contention operations - // lock again - lock.lock(); - v += 1; - std::cout << v << std::endl; -} - -int main() { - std::thread t1(critical_section, 2), t2(critical_section, 3); - t1.join(); - t2.join(); - return 0; -} -``` - -## 7.3 Future - -The Future is represented by `std::future`, which provides a way to access the results of asynchronous operations. This sentence is very difficult to understand. -To understand this feature, we need to understand the multi-threaded behavior before C++11. - -Imagine if our main thread A wants to open a new thread B to perform some of our expected tasks and return me a result. -At this time, thread A may be busy with other things and have no time to take into account the results of B. -So we naturally hope to get the result of thread B at a certain time. - -Before the introduction of `std::future` in C++11, the usual practice is: -Create a thread A, start task B in thread A, send an event when it is ready, and save the result in a global variable. -The main function thread A is doing other things. When the result is needed, a thread is called to wait for the function to get the result of the execution. - -The `std::future` provided by C++11 simplifies this process and can be used to get the results of asynchronous tasks. -Naturally, we can easily imagine it as a simple means of thread synchronization, namely the barrier. - -To see an example, we use extra `std::packaged_task`, which can be used to wrap any target that can be called for asynchronous calls. For example: - -```cpp -#include -#include -#include - -int main() { - // pack a lambda expression that returns 7 into a std::packaged_task - std::packaged_task task([](){return 7;}); - // get the future of task - std::future result = task.get_future(); // run task in a thread - std::thread(std::move(task)).detach(); - std::cout << "waiting..."; - result.wait(); // block until future has arrived - // output result - std::cout << "done!" << std:: endl << "future result is " - << result.get() << std::endl; - return 0; -} -``` - -After encapsulating the target to be called, you can use `get_future()` to get a `std::future` object to implement thread synchronization later. - -## 7.4 Condition Variable - -The condition variable `std::condition_variable` was born to solve the deadlock and was introduced when the mutex operation was not enough. -For example, a thread may need to wait for a condition to be true to continue execution. -A dead wait loop can cause all other threads to fail to enter the critical section so that when the condition is true, a deadlock occurs. -Therefore, the `condition_variable` object is created primarily to wake up the waiting thread and avoid deadlocks. -`notify_one()` of `std::condition_variable` is used to wake up a thread; -`notify_all()` is to notify all threads. Below is an example of a producer and consumer model: - -```cpp -#include -#include -#include -#include -#include -#include - - -int main() { - std::queue produced_nums; - std::mutex mtx; - std::condition_variable cv; - bool notified = false; // notification sign - - auto producer = [&]() { - for (int i = 0; ; i++) { - std::this_thread::sleep_for(std::chrono::milliseconds(500)); - std::unique_lock lock(mtx); - std::cout << "producing " << i << std::endl; - produced_nums.push(i); - notified = true; - cv.notify_all(); - } - }; - auto consumer = [&]() { - while (true) { - std::unique_lock lock(mtx); - while (!notified) { // avoid spurious wakeup - cv.wait(lock); - } - - // temporal unlock to allow producer produces more rather than - // let consumer hold the lock until its consumed. - lock.unlock(); - // consumer is slower - std::this_thread::sleep_for(std::chrono::milliseconds(1000)); - lock.lock(); - if (!produced_nums.empty()) { - std::cout << "consuming " << produced_nums.front() << std::endl; - produced_nums.pop(); - } - notified = false; - } - }; - - std::thread p(producer); - std::thread cs[2]; - for (int i = 0; i < 2; ++i) { - cs[i] = std::thread(consumer); - } - p.join(); - for (int i = 0; i < 2; ++i) { - cs[i].join(); - } - return 0; -} -``` - -It is worth mentioning that although we can use `notify_one()` in the producer, it is not recommended to use it here. -Because in the case of multiple consumers, our consumer implementation simply gives up the lock holding, which makes it possible for other consumers to compete for this lock, to better utilize the concurrency between multiple consumers. Having said that, but in fact because of the exclusivity of `std::mutex`, -We simply can't expect multiple consumers to be able to produce content in a parallel consumer queue, and we still need a more granular approach. - -## 7.5 Atomic Operation and Memory Model - -Careful readers may be tempted by the fact that the example of the producer-consumer model in the previous section may have compiler optimizations that cause program errors. -For example, the compiler may have optimizations for the variable `notified`, such as the value of a register. -As a result, the consumer thread can never observe the change of this value. This is a good question. To explain this problem, we need to further discuss the concept of the memory model introduced from C++11. Let's first look at a question. What is the output of the following code? - -```cpp -#include -#include - -int main() { - int a = 0; - volatile int flag = 0; - - std::thread t1([&]() { - while (flag != 1); - - int b = a; - std::cout << "b = " << b << std::endl; - }); - - std::thread t2([&]() { - a = 5; - flag = 1; - }); - - t1.join(); - t2.join(); - return 0; -} -``` - -Intuitively, it seems that `a = 5;` in `t2` always executes before `flag = 1;` and `while (flag != 1)` in `t1`. It looks like there is a guarantee the line `std ::cout << "b = " << b << std::endl;` will not be executed before the mark is changed. Logically, it seems that the value of `b` should be equal to 5. -But the actual situation is much more complicated than this, or the code itself is undefined behavior because, for `a` and `flag`, they are read and written in two parallel threads. -There has been competition. Also, even if we ignore competing for reading and writing, it is still possible to receive out-of-order execution of the CPU and the impact of the compiler on the rearrangement of instructions. -Cause `a = 5` to occur after `flag = 1`. Thus `b` may output 0. - -### Atomic Operation - -`std::mutex` can solve the problem of concurrent read and write, but the mutex is an operating system-level function. -This is because the implementation of a mutex usually contains two basic principles: - -1. Provide automatic state transition between threads, that is, "lock" state -2. Ensure that the memory of the manipulated variable is isolated from the critical section during the mutex operation - -This is a very strong set of synchronization conditions, in other words when it is finally compiled into a CPU instruction, it will behave like a lot of instructions (we will look at how to implement a simple mutex later). -This seems too harsh for a variable that requires only atomic operations (no intermediate state). - -The research on synchronization conditions has a very long history, and we will not go into details here. Readers should understand that under the modern CPU architecture, atomic operations at the CPU instruction level are provided. -Therefore, the `std::atomic` template is introduced in C++11 for the topic of multi-threaded shared variable reading and writing, which enables us to instantiate atomic types, -and minimize an atomic read or write operation from a set of instructions to a single CPU instruction. E.g: - -```cpp -std::atomic counter; -``` - -And provides basic numeric member functions for atomic types of integers or floating-point numbers, for example, -Including `fetch_add`, `fetch_sub`, etc., and the corresponding `+`, `-` version is provided by overload. -For example, the following example: - -```cpp -#include -#include -#include - -std::atomic count = {0}; - -int main() { - std::thread t1([](){ - count.fetch_add(1); - }); - std::thread t2([](){ - count++; // identical to fetch_add - count += 1; // identical to fetch_add - }); - t1.join(); - t2.join(); - std::cout << count << std::endl; - return 0; -} -``` - -Of course, not all types provide atomic operations because the feasibility of atomic operations depends on the architecture of the CPU and whether the type structure being instantiated satisfies the memory alignment requirements of the architecture, so we can always pass `std::atomic::is_lock_free` to check if the atom type needs to support atomic operations, for example: - -```cpp -#include -#include - -struct A { - float x; - int y; - long long z; -}; - -int main() { - std::atomic a; - std::cout << std::boolalpha << a.is_lock_free() << std::endl; - return 0; -} -``` - -### Consistency Model - -Multiple threads executing in parallel, discussed at some macro level, can be roughly considered a distributed system. -In a distributed system, any communication or even local operation takes a certain amount of time, and even unreliable communication occurs. - -If we force the operation of a variable `v` between multiple threads to be atomic, that is, any thread after the operation of `v` -Other threads can **synchronize** to perceive changes in `v`, for the variable `v`, which appears as a sequential execution of the program, it does not have any efficiency gains due to the introduction of multithreading. Is there any way to accelerate this properly? The answer is to weaken the synchronization conditions between processes in atomic operations. - -In principle, each thread can correspond to a cluster node, and communication between threads is almost equivalent to communication between cluster nodes. -Weakening the synchronization conditions between processes, usually we will consider four different consistency models: - -1. Linear consistency: Also known as strong consistency or atomic consistency. It requires that any read operation can read the most recent write of a certain data, and the order of operation of all threads is consistent with the order under the global clock. - - ``` - x.store(1) x.load() - T1 ---------+----------------+------> - - - T2 -------------------+-------------> - x.store(2) - ``` - - In this case, thread `T1`, `T2` is twice atomic to `x`, and `x.store(1)` is strictly before `x.store(2)`. `x.store(2)` strictly occurs before `x.load()`. It is worth mentioning that linear consistency requirements for global clocks are difficult to achieve, which is why people continue to study other consistent algorithms under this weaker consistency. - -2. Sequential consistency: It is also required that any read operation can read the last data written by the data, but it is not required to be consistent with the order of the global clock. - - ``` - x.store(1) x.store(3) x.load() - T1 ---------+-----------+----------+-----> - - - T2 ---------------+----------------------> - x.store(2) - - or - - x.store(1) x.store(3) x.load() - T1 ---------+-----------+----------+-----> - - - T2 ------+-------------------------------> - x.store(2) - ``` - - Under the order consistency requirement, `x.load()` must read the last written data, so `x.store(2)` and `x.store(1)` do not have any guarantees, as long as `x.store(2)` of `T2` occurs before `x.store(3)`. - -3. Causal consistency: its requirements are further reduced, only the sequence of causal operations is guaranteed, and the order of non-causal operations is not required. - - ``` - a = 1 b = 2 - T1 ----+-----------+----------------------------> - - - T2 ------+--------------------+--------+--------> - x.store(3) c = a + b y.load() - - or - - a = 1 b = 2 - T1 ----+-----------+----------------------------> - - - T2 ------+--------------------+--------+--------> - x.store(3) y.load() c = a + b - - or - - b = 2 a = 1 - T1 ----+-----------+----------------------------> - - - T2 ------+--------------------+--------+--------> - y.load() c = a + b x.store(3) - ``` - - The three examples given above are all causal consistent because, in the whole process, only `c` has a dependency on `a` and `b`, and `x` and `y` are not related in this example. (But in actual situations we need more detailed information to determine that `x` is not related to `y`) - -4. Final Consistency: It is the weakest consistency requirement. It only guarantees that an operation will be observed at a certain point in the future, but does not require the observed time. So we can even strengthen this condition a bit, for example, to specify that the time observed for an operation is always bounded. Of course, this is no longer within our discussion. - - ``` - x.store(3) x.store(4) - T1 ----+-----------+--------------------------------------------> - - - T2 ---------+------------+--------------------+--------+--------> - x.read() x.read() x.read() x.read() - ``` - - In the above case, if we assume that the initial value of x is 0, then the four times ``x.read()` in `T2` may be but not limited to the following: - - ``` - 3 4 4 4 // The write operation of x was quickly observed - 0 3 3 4 // There is a delay in the observed time of the x write operation - 0 0 0 4 // The last read read the final value of x, - // but the previous changes were not observed. - 0 0 0 0 // The write operation of x is not observed in the current time period, - // but the situation that x is 4 can be observed - // at some point in the future. - ``` - -### Memory Orders - -To achieve the ultimate performance and achieve consistency of various strength requirements, C++11 defines six different memory sequences for atomic operations. The option `std::memory_order` expresses four synchronization models between multiple threads: - -1. Relaxed model: Under this model, atomic operations within a single thread are executed sequentially, and instruction reordering is not allowed, but the order of atomic operations between different threads is arbitrary. The type is specified by `std::memory_order_relaxed`. Let's look at an example: - - ```cpp - std::atomic counter = {0}; - std::vector vt; - for (int i = 0; i < 100; ++i) { - vt.emplace_back([&](){ - counter.fetch_add(1, std::memory_order_relaxed); - }); - } - - for (auto& t : vt) { - t.join(); - } - std::cout << "current counter:" << counter << std::endl; - ``` - -2. Release/consumption model: In this model, we begin to limit the order of operations between processes. If a thread needs to modify a value, but another thread will have a dependency on that operation of the value, that is, the latter depends on the former. Specifically, thread A has completed three writes to `x`, and thread `B` relies only on the third `x` write operation, regardless of the first two write behaviors of `x`, then `A ` When active `x.release()` (ie using `std::memory_order_release`), the option `std::memory_order_consume` ensures that `B` observes `A` when calling `x.load()` Three writes to `x`. Let's look at an example: - - ```cpp - // initialize as nullptr to prevent consumer load a dangling pointer - std::atomic ptr(nullptr); - int v; - std::thread producer([&]() { - int* p = new int(42); - v = 1024; - ptr.store(p, std::memory_order_release); - }); - std::thread consumer([&]() { - int* p; - while(!(p = ptr.load(std::memory_order_consume))); - - std::cout << "p: " << *p << std::endl; - std::cout << "v: " << v << std::endl; - }); - producer.join(); - consumer.join(); - ``` - -3. Release/Acquire model: Under this model, we can further tighten the order of atomic operations between different threads, specifying the timing between releasing `std::memory_order_release` and getting `std::memory_order_acquire`. **All** write operations before the release operation is visible to any other thread, i.e., happens before. - - As you can see, `std::memory_order_release` ensures that a write before a release does not occur after the release operation, which is a **backward barrier**, and `std::memory_order_acquire` ensures that a subsequent read or write after a acquire does not occur before the acquire operation, which is a **forward barrier**. - For the `std::memory_order_acq_rel` option, combines the characteristics of the two barriers and determines a unique memory barrier, such that reads and writes of the current thread will not be rearranged across the barrier. - - Let's check an example: - - ```cpp - std::vector v; - std::atomic flag = {0}; - std::thread release([&]() { - v.push_back(42); - flag.store(1, std::memory_order_release); - }); - std::thread acqrel([&]() { - int expected = 1; // must before compare_exchange_strong - while(!flag.compare_exchange_strong(expected, 2, std::memory_order_acq_rel)) - expected = 1; // must after compare_exchange_strong - // flag has changed to 2 - }); - std::thread acquire([&]() { - while(flag.load(std::memory_order_acquire) < 2); - - std::cout << v.at(0) << std::endl; // must be 42 - }); - release.join(); - acqrel.join(); - acquire.join(); - ``` - - In this case we used `compare_exchange_strong`, which is the Compare-and-swap primitive, which has a weaker version, `compare_exchange_weak`, which allows a failure to be returned even if the exchange is successful. The reason is due to a false failure on some platforms, specifically when the CPU performs a context switch, another thread loads the same address to produce an inconsistency. In addition, the performance of `compare_exchange_strong` may be slightly worse than `compare_exchange_weak`. However, in most cases, `compare_exchange_weak` is discouraged due to the complexity of its usage. - -4. Sequential Consistent Model: Under this model, atomic operations satisfy sequence consistency, which in turn can cause performance loss. It can be specified explicitly by `std::memory_order_seq_cst`. Let's look at a final example: - - ```cpp - std::atomic counter = {0}; - std::vector vt; - for (int i = 0; i < 100; ++i) { - vt.emplace_back([&](){ - counter.fetch_add(1, std::memory_order_seq_cst); - }); - } - - for (auto& t : vt) { - t.join(); - } - std::cout << "current counter:" << counter << std::endl; - ``` - - This example is essentially the same as the first loose model example. Just change the memory order of the atomic operation to `memory_order_seq_cst`. Interested readers can write their own programs to measure the performance difference caused by these two different memory sequences. - -## Conclusion - -The C++11 language layer provides support for concurrent programming. This section briefly introduces `std::thread`/`std::mutex`/`std::future`, an important tool that can't be avoided in concurrent programming. -In addition, we also introduced the "memory model" as one of the most important features of C++11. -They provide a critical foundation for standardized high-performance computing for C++. - -## Exercises - -1. Write a simple thread pool that provides the following features: - - ```cpp - ThreadPool p(4); // specify four work thread - - // enqueue a task, and return a std::future - auto f = pool.enqueue([](int life) { - return meaning; - }, 42); - - // fetch result from future - std::cout << f.get() << std::endl; - ``` - -2. Use `std::atomic` to implement a mutex. - -[Table of Content](./toc.md) | [Previous Chapter](./06-regex.md) | [Next Chapter: File System](./08-filesystem.md) - -## Further Readings - -- [C++ Concurrency in Action](https://www.amazon.com/dp/1617294691/ref=cm_sw_em_r_mt_dp_U_siEmDbRMMF960) -- [Thread document](https://en.cppreference.com/w/cpp/thread) -- Herlihy, M. P., & Wing, J. M. (1990). Linearizability: a correctness condition for concurrent objects. ACM Transactions on Programming Languages and Systems, 12(3), 463–492. https://doi.org/10.1145/78969.78972 - -## Licenses - -Creative Commons License
This work was written by [Ou Changkun](https://changkun.de) and licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. The code of this repository is open sourced under the [MIT license](../../LICENSE).` diff --git a/docs/book/en-us/08-filesystem.md b/docs/book/en-us/08-filesystem.md deleted file mode 100644 index 1008503..0000000 --- a/docs/book/en-us/08-filesystem.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -title: "Chapter 08 File System" -type: book-en-us -order: 8 ---- - -# Chapter 08 File System - -[TOC] - -The file system library provides functions related to -the operation of the file system, path, regular files, directories, and so on. -Similar to the regular expression library, it was one of the first libraries -to be launched by boost and eventually merged into the C++ standard. - -## 8.1 Document and Link - -TODO: - -## 8.2 std::filesystem - -TODO: - -[Table of Content](./toc.md) | [Previous Chapter](./07-thread.md) | [Next Chapter: Minor Features](./09-others.md) - -## Further Readings - -## Licenses - -Creative Commons License
This work was written by [Ou Changkun](https://changkun.de) and licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. The code of this repository is open sourced under the [MIT license](../../LICENSE). \ No newline at end of file diff --git a/docs/book/en-us/09-others.md b/docs/book/en-us/09-others.md deleted file mode 100644 index 773a961..0000000 --- a/docs/book/en-us/09-others.md +++ /dev/null @@ -1,206 +0,0 @@ ---- -title: Chapter 09 Minor Features -type: book-en-us -order: 9 ---- - -# Chapter 09 Minor Features - -[TOC] - -## 9.1 New Type - -### `long long int` - -`long long int` is not the first to be introduced in C++11. -As early as C99, `long long int` has been included in the C standard, -so most compilers already support it. -C++11 now formally incorporate it into the standard library, -specifying a `long long int` type with at least 64 bits. - -## 9.2 `noexcept` and Its Operations - -One of the big advantages of C++ over C is that -C++ itself defines a complete set of exception handling mechanisms. -However, before C++11, almost no one used to write an exception declaration expression after the function name. -Starting from C++11, this mechanism was deprecated, -so we will not discuss or introduce the previous mechanism. -How to work and how to use it, you should not take the initiative to understand it. - -C++11 simplifies exception declarations into two cases: - -1. The function may throw any exceptions -2. The function can't throw any exceptions - -And use `noexcept` to limit these two behaviors, for example: - -```cpp -void may_throw(); // May throw any exception -void no_throw() noexcept; // Cannot throw any exception -``` - -If a function modified with `noexcept` is thrown, -the compiler will use `std::terminate()` to -immediately terminate the program. - -`noexcept` can also be used as an operator to manipulate an expression. -When the expression has no exception, it returns `true`, -otherwise, it returns `false`. - -```cpp -#include -void may_throw() { - throw true; -} -auto non_block_throw = []{ - may_throw(); -}; -void no_throw() noexcept { - return; -} - -auto block_throw = []() noexcept { - no_throw(); -}; -int main() -{ - std::cout << std::boolalpha - << "may_throw() noexcept? " << noexcept(may_throw()) << std::endl - << "no_throw() noexcept? " << noexcept(no_throw()) << std::endl - << "lmay_throw() noexcept? " << noexcept(non_block_throw()) << std::endl - << "lno_throw() noexcept? " << noexcept(block_throw()) << std::endl; - return 0; -} -``` - -`noexcept` can modify the function of blocking exceptions -after modifying a function. If an exception is generated internally, -the external will not trigger. For instance: - -```cpp -try { - may_throw(); -} catch (...) { - std::cout << "exception captured from may_throw()" << std::endl; -} -try { - non_block_throw(); -} catch (...) { - std::cout << "exception captured from non_block_throw()" << std::endl; -} -try { - block_throw(); -} catch (...) { - std::cout << "exception captured from block_throw()" << std::endl; -} -``` - -The final output is: - -``` -exception captured, from may_throw() -exception captured, from non_block_throw() -``` - -## 9.3 Literal - -### Raw String Literal - -In traditional C++, it is very painful to write a string full of -special characters. For example, a string containing HTML ontology -needs to add a large number of escape characters. -For example, a file path on Windows often as: `C:\\Path\\To\\File`. - -C++11 provides the original string literals, -which can be decorated with `R` in front of a string, -and the original string is wrapped in parentheses, for example: - -```cpp -#include -#include - -int main() { - std::string str = R"(C:\Path\To\File)"; - std::cout << str << std::endl; - return 0; -} -``` - -### Custom Literal - -C++11 introduces the ability to customize literals by -overloading the double quotes suffix operator: - -```cpp -// String literal customization must be set to the following parameter list -std::string operator"" _wow1(const char *wow1, size_t len) { - return std::string(wow1)+"woooooooooow, amazing"; -} - -std::string operator"" _wow2 (unsigned long long i) { - return std::to_string(i)+"woooooooooow, amazing"; -} - -int main() { - auto str = "abc"_wow1; - auto num = 1_wow2; - std::cout << str << std::endl; - std::cout << num << std::endl; - return 0; -} -``` - -Custom literals support four literals: - -1. Integer literal: When overloading, you must use `unsigned long long`, `const char *`, and template literal operator parameters. The former is used in the above code; -2. Floating-point literals: You must use `long double`, `const char *`, and template literals when overloading; -3. String literals: A parameter table of the form `(const char *, size_t)` must be used; -4. Character literals: Parameters can only be `char`, `wchar_t`, `char16_t`, `char32_t`. - -## 9.4 Memory Alignment - -C++ 11 introduces two new keywords, `alignof` and `alignas`, to support control of memory alignment. -The `alignof` keyword can get a platform-dependent value of type `std::size_t` to query the alignment of the platform. -Of course, we are sometimes not satisfied with this, and even want to customize the alignment of the structure. Similarly, C++ 11 introduces `alignas`. -To reshape the alignment of a structure. Let's look at two examples: - -```cpp -#include - -struct Storage { - char a; - int b; - double c; - long long d; -}; - -struct alignas(std::max_align_t) AlignasStorage { - char a; - int b; - double c; - long long d; -}; - -int main() { - std::cout << alignof(Storage) << std::endl; - std::cout << alignof(AlignasStorage) << std::endl; - return 0; -} -``` - -where `std::max_align_t` requires the same alignment for each scalar type, so it has almost no difference in maximum scalars. -In turn, the result on most platforms is `long double`, so the alignment requirement for `AlignasStorage` we get here is 8 or 16. - -## Conclusion - -Several of the features introduced in this section are those that -use more frequent features from modern C++ features that -have not yet been introduced. `noexcept` is the most important feature. -One of its features is to prevent the spread of anomalies, -effective Let the compiler optimize our code to the maximum extent possible. - -[Table of Content](./toc.md) | [Previous Chapter](./08-filesystem.md) | [Next Chapter: Outlook: Introduction of C++20](./10-cpp20.md) - -## Licenses - -Creative Commons License
This work was written by [Ou Changkun](https://changkun.de) and licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. The code of this repository is open sourced under the [MIT license](../../LICENSE). diff --git a/docs/book/en-us/10-cpp20.md b/docs/book/en-us/10-cpp20.md deleted file mode 100644 index 04c69ee..0000000 --- a/docs/book/en-us/10-cpp20.md +++ /dev/null @@ -1,102 +0,0 @@ ---- -title: "Chapter 10 Outlook: Introduction of C++20" -type: book-en-us -order: 10 ---- - -# Chapter 10 Outlook: Introduction of C++20 - -[TOC] - -C++20 seems to be an exciting update. -For example, as early as C++11, the `Concept`, -which was eager to call for high-altitude but ultimately lost, is now on the line. -The C++ Organizing Committee decided to vote to finalize C++20 with many proposals, -such as **Concepts**/**Module**/**Coroutine**/**Ranges**/ and so on. -In this chapter, we'll take a look at some of the important features that -C++20 will introduce. - -## Concept - -The concept is a further enhancement to C++ template programming. -In simple terms, the concept is a compile-time feature. -It allows the compiler to evaluate template parameters at compile-time, -greatly enhancing our experience with template programming in C++. -When programming with templates, we often encounter a variety of heinous errors. -This is because we have so far been unable to check and limit template parameters. -For example, the following two lines of code can cause a lot of -almost unreadable compilation errors: - -```cpp -#include -#include -int main() { - std::list l = {1, 2, 3}; - std::sort(l.begin(), l.end()); - return 0; -} -``` - -The root cause of this code error is that `std::sort` must provide -a random iterator for the sorting container, otherwise it will not be used, -and we know that `std::list` does not support random access. -In the conceptual language, the iterator in `std::list` does not satisfy -the constraint of the concept of random iterators in `std::sort`. -After introducing the concept, we can constrain the template parameters -like this: - -```cpp -template -requires Sortable // Sortable is a concept -void sort(T& c); -``` - -abbreviate as: - -```cpp -template // T is a Sortable typename -void sort(T& c) -``` - -Even use it directly as a type: - -```cpp -void sort(Sortable& c); // c is a Sortable type object -``` - -Let's look at a practical example. - -TODO: - -## Module - -TODO: - -## Contract - -TODO: - -## Range - -TODO: - -## Coroutine - -TODO: - -## Conclusion - -In general, I finally saw the exciting features of Concepts/Ranges/Modules in C++20. -This is still full of charm for a programming language that is already in its thirties. - -[Table of Content](./toc.md) | [Previous Chapter](./09-others.md) | [Next Chapter](./appendix1.md) - -## Further Readings - -- [Why Concepts didn't make C++17?](http://honermann.net/blog/2016/03/06/why-concepts-didnt-make-cxx17/) -- [C++11/14/17/20 Compiler Support](https://en.cppreference.com/w/cpp/compiler_support) -- [C++ History](https://en.cppreference.com/w/cpp/language/history) - -## Licenses - -Creative Commons License
This work was written by [Ou Changkun](https://changkun.de) and licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. The code of this repository is open sourced under the [MIT license](../../LICENSE). diff --git a/docs/book/en-us/appendix1.md b/docs/book/en-us/appendix1.md deleted file mode 100644 index cfde041..0000000 --- a/docs/book/en-us/appendix1.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: "Appendix 1: Further Study Materials" -type: book-en-us -order: 11 ---- - -# Appendix 1: Further Study Materials - -First of all, congratulations 🎉 on reading this book! I hope this book has raised your interest in modern C++. - -As mentioned in the introduction to this book, this book is just a book that takes you quickly to the new features of modern C++ 11/14/17/20, rather than the advanced learning practice of C++ "Black Magic". The author of course also thinks about this demand, but the content is very difficult and there are few audiences. Here, the author lists some materials that can help you learn more about modern C++ based on this book. I hope I can help you: - -- [C++ Reference](https://en.cppreference.com/w) -- [CppCon YouTube Channel](https://www.youtube.com/user/CppCon/videos) -- [Ulrich Drepper. What Every Programmer Should Know About Memory. 2007](https://people.freebsd.org/~lstewart/articles/cpumemory.pdf) -- to be added - -[Table of Content](./toc.md) | [Previous Chapter](./10-cpp20.md) | [Next Chapter](./appendix2.md) - -## Licenses - -Creative Commons License
This work was written by [Ou Changkun](https://changkun.de) and licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. The code of this repository is open sourced under the [MIT license](../../LICENSE). \ No newline at end of file diff --git a/docs/book/en-us/appendix2.md b/docs/book/en-us/appendix2.md deleted file mode 100644 index aa1053e..0000000 --- a/docs/book/en-us/appendix2.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -title: "Appendix 2: Modern C++ Best Practices" -type: book-en-us -order: 12 ---- - -# Appendix 2: Modern C++ Best Practices - -In this appendix we will briefly talk about the best practices of modern C++. In general, the author's thoughts on C++'s best practices are mainly absorbed from [Effective Modern C++](https://www.amazon.com/dp/1491903996/ref=cm_sw_em_r_mt_dp_U_-ZgjDb81ERBNP) and [C++ Style Guide](https://google.github.io/styleguide/cppguide.html). In this appendix, we will briefly discuss and use the actual examples to illustrate the methods, and introduce some of **the author's personal**, **non-common**, **non-sensible** best practices, and how to ensure the overall quality of the code. - -## Common Tools - -TODO: - -## Coding Style - -TODO: - -## Overall Performance - -TODO: - -## Code Security - -TODO: - -## Maintainability - -TODO: - -## Portability - -TODO: - -[Table of Content](./toc.md) | [Previous Chapter](./appendix1.md) - -## Licenses - -Creative Commons License
This work was written by [Ou Changkun](https://changkun.de) and licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. The code of this repository is open sourced under the [MIT license](../../LICENSE). \ No newline at end of file diff --git a/docs/book/en-us/toc.md b/docs/book/en-us/toc.md deleted file mode 100644 index 2f03b30..0000000 --- a/docs/book/en-us/toc.md +++ /dev/null @@ -1,112 +0,0 @@ -# C++ 11/14/17/20 On The Fly - -## Table of Contents - -- [**Preface**](./00-preface.md) -- [**Chapter 01 Towards Modern C++**](./01-intro.md) - + 1.1 Deprecated Features - + 1.2 Compatibility with C - + Further Readings -- [**Chapter 02 Language Usability Enhancements**](./02-usability.md) - + 2.1 Constants - - nullptr - - constexpr - + 2.2 Variables & Initialization - - Conditional Statement - - Initializer List - - Structured binding - + 2.3 Type Deduction - - auto - - decltype - - Tail return type - - decltype(auto) - + 2.4 Control Flow - - if constexpr - - Range-based for loop - + 2.5 Templates - - External templates - - The ">" - - Type alias templates - - Default template parameters - - Variadic templates - - Fold expression - - Non-type template parameter deduction - + 2.6 Object-oriented - - Delegate constructor - - Inheritance constructor - - Explicit virtual function overwrite - - override - - final - - Explicit delete default function - - Strongly typed enumerations -- [**Chapter 03 Language Runtime Enhancements**](./03-runtime.md) - + 3.1 Lambda expression - + Basics - + Generics - + 3.2 Function object wrapper - + std::function - + std::bind/std::placeholder - + 3.3 rvalue reference - + lvalue, rvalue, prvalue, xvalue - + rvalue reference and lvalue reference - + Move semantics - + Perfect forwarding -- [**Chapter 04 Containers**](./04-containers.md) - + 4.1 Linear containers - + `std::array` - + `std::forward_list` - + 4.2 Unordered containers - + `std::unordered_set` - + `std::unordered_map` - + 4.3 Tuples `std::tuple` - + basic operation - + runtime indexing `std::variant` - + merge and iteration -- [**Chapter 05 Smart Pointers and Memory Management**](./05-pointers.md) - + 5.1 RAII and reference counting - + 5.2 `std::shared_ptr` - + 5.3 `std::unique_ptr` - + 5.4 `std::weak_ptr` -- [**Chapter 06 Regular Expression**](./06-regex.md) - + 6.1 Introduction - + Ordinary characters - + Special characters - + Quantifiers - + 6.2 `std::regex` and its related - + `std::regex` - + `std::regex_match` - + `std::match_results` -- [**Chapter 07 Parallelism and Concurrency**](./07-thread.md) - + 7.1 Basic of Parallelism - + 7.2 Mutex and Critical Section - + 7.3 Futures - + 7.4 Condition Variable - + 7.5 Atomic Operation and Memory Model - + Atomic Operation - + Consistency Model - + Memory Orders -- [**Chapter 08 File System**](./08-filesystem.md) - + 8.1 Documents and links - + 8.2 `std::filesystem` -- [**Chapter 09 Minor Features**](./09-others.md) - + 9.1 New Types - + `long long int` - + 9.2 `noexcept` and Its Operations - + 9.3 Literal - + Raw String Literal - + Custom String Literal - + 9.4 Memory Alignment -- [**Chapter 10 Outlook: Introduction of C++20**](./10-cpp20.md) - + 10.1 Concept - + 10.2 Range - + 10.3 Module - + 10.4 Coroutine - + 10.5 Transaction Memory -- [**Appendix 1: Further Study Materials**](./appendix1.md) -- [**Appendix 2: Modern C++ Best Practices**](./appendix2.md) - -Table of Content | Last Chapter | [Next Chapter: Preface](./00-preface.md) - -## Licenses - -Creative Commons License
This work was written by [Ou Changkun](https://changkun.de) and licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. The code of this repository is open sourced under the [MIT license](../../LICENSE). \ No newline at end of file diff --git a/docs/book/zh-cn/00-preface.md b/docs/book/zh-cn/00-preface.md deleted file mode 100644 index 5e26f70..0000000 --- a/docs/book/zh-cn/00-preface.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -title: 序言 -type: book-zh-cn -order: 0 ---- - -# 序言 - -[TOC] - -## 引言 - -C++ 是一个用户群体相当大的语言。从 C++98 的出现到 C++11 的正式定稿经历了长达十年多之久的积累。C++14/17 则是作为对 C++11 的重要补充和优化,C++20 则将这门语言领进了现代化的大门,所有这些新标准中扩充的特性,给 C++ 这门语言注入了新的活力。 -那些还在坚持使用**传统 C++** (本书把 C++98 及其之前的 C++ 特性均称之为传统 C++)而未接触过现代 C++ 的 C++ 程序员在见到诸如 Lambda 表达式这类全新特性时,甚至会流露出『学的不是同一门语言』的惊叹之情。 - -**现代 C++** (本书中均指 C++11/14/17/20) 为传统 C++ 注入的大量特性使得整个 C++ 变得更加像一门现代化的语言。现代 C++ 不仅仅增强了 C++ 语言自身的可用性,`auto` 关键字语义的修改使得我们更加有信心来操控极度复杂的模板类型。同时还对语言运行期进行了大量的强化,Lambda 表达式的出现让 C++ 具有了『匿名函数』的『闭包』特性,而这一特性几乎在现代的编程语言(诸如 Python/Swift/... )中已经司空见惯,右值引用的出现解决了 C++ 长期以来被人诟病的临时对象效率问题等等。 - -C++17 则是近三年依赖 C++ 社区一致推进的方向,也指出了 **现代C++** 编程的一个重要发展方向。尽管它的出现并不如 C++11 的分量之重,但它包含了大量小而美的语言与特性(例如结构化绑定),这些特性的出现再一次修正了我们在 C++ 中的编程范式。 - -现代 C++ 还为自身的标准库增加了非常多的工具和方法,诸如在语言自身标准的层面上制定了 `std::thread`,从而支持了并发编程,在不同平台上不再依赖于系统底层的 API,实现了语言层面的跨平台支持;`std::regex` 提供了完整的正则表达式支持等等。C++98 已经被实践证明了是一种非常成功的『范型』,而现代 C++ 的出现,则进一步推动这种范型,让 C++ 成为系统程序设计和库开发更好的语言。Concept 提供了对模板参数编译期的检查,进一步增强了语言整体的可用性。 - -总而言之,我们作为 C++ 的拥护与实践者,始终保持接纳新事物的开放心态,才能更快的推进 C++ 的发展,使得这门古老而又新颖的语言更加充满活力。 - -## 目标读者 - -1. 本书假定读者已经熟悉了传统 C++ ,至少在阅读传统 C++ 代码上不具备任何困难。换句话说,那些长期使用传统 C++ 进行编码的人、渴望在短时间内迅速了解**现代 C++** 特性的人非常适合阅读本书; -2. 本书一定程度上介绍了一些现代 C++ 的**黑魔法**,但这些魔法毕竟有限,不适合希望进阶学习现代 C++ 的读者,本书的定位系**现代 C++ 的快速上手**。当然,希望进阶学习的读者可以使用本书来回顾并检验自己对 **现代 C++** 的熟悉度。 - -## 本书目的 - -本书号称『高速上手』,从内容上对二十一世纪二十年代之前产生 C++ 的相关特性做了非常相对全面的介绍,读者可以自行根据下面的目录选取感兴趣的内容进行学习,快速熟悉需要了解的内容。这些特性并不需要全部掌握,只需针对自己的使用需求和特定的应用场景,学习、查阅最适合自己的新特性即可。 - -同时,本书在介绍这些特性的过程中,尽可能简单明了的介绍了这些特性产生的历史背景和技术需求,这为理解这些特性、运用这些特性提供了很大的帮助。 - -此外,笔者希望读者在阅读本书后,能够努力在新项目中直接使用 C++17,并努力将旧项目逐步迁移到 C++17。也算是笔者为推进现代 C++ 的普及贡献了一些绵薄之力。 - -## 相关代码 - -本书每章中都出现了大量的代码,如果你在跟随本书介绍特性的思路编写自己的代码遇到问题时,不妨读一读随书附上的源码,你可以在[这里](../../code)中找到书中介绍过的全部的源码,所有代码按章节组织,文件夹名称为章节序号。 - -## 随书习题 - -本书每章最后还加入了少量难度极小的习题,仅用于检验你是否能混合运用当前章节中的知识点。你可以在[这里](../../exercises)找到习题的答案,文件夹名称为章节序号。 - -[返回目录](./toc.md) | [下一章 迈向现代 C++](./01-intro.md) - -## 许可 - -知识共享许可协议 - -本书系[欧长坤](https://github.com/changkun)著,采用[知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-nd/4.0/)许可。项目中代码使用 MIT 协议开源,参见[许可](../../LICENSE)。 diff --git a/docs/book/zh-cn/01-intro.md b/docs/book/zh-cn/01-intro.md deleted file mode 100644 index a74a372..0000000 --- a/docs/book/zh-cn/01-intro.md +++ /dev/null @@ -1,149 +0,0 @@ ---- -title: 第 1 章 迈向现代 C++ -type: book-zh-cn -order: 1 ---- - -# 第 1 章 迈向现代 C++ - -[TOC] - -**编译环境**:本书将使用 `clang++` 作为唯一使用的编译器,同时总是在代码中使用 `-std=c++2a` 编译标志。 - -```bash -> clang++ -v -Apple LLVM version 10.0.1 (clang-1001.0.46.4) -Target: x86_64-apple-darwin18.6.0 -Thread model: posix -InstalledDir: /Library/Developer/CommandLineTools/usr/bin -``` - -## 1.1 被弃用的特性 - -在学习现代 C++ 之前,我们先了解一下从 C++11 开始,被弃用的主要特性: - -> **注意**:弃用并非彻底不能用,只是用于暗示程序员这些特性将从未来的标准中消失,应该尽量避免使用。但是,已弃用的特性依然是标准库的一部分,并且出于兼容性的考虑,大部分特性其实会『永久』保留。 - -- **不再允许字符串字面值常量赋值给一个 `char *`。如果需要用字符串字面值常量赋值和初始化一个 `char *`,应该使用 `const char *` 或者 `auto`。** - - ```cpp - char *str = "hello world!"; // 将出现弃用警告 - ``` - -- **C++98 异常说明、 `unexpected_handler`、`set_unexpected()` 等相关特性被弃用,应该使用 `noexcept`。** - -- **`auto_ptr` 被弃用,应使用 `unique_ptr`。** - -- **`register` 关键字被弃用,可以使用但不再具备任何实际含义。** - -- **`bool` 类型的 `++` 操作被弃用。** - -- **如果一个类有析构函数,为其生成拷贝构造函数和拷贝赋值运算符的特性被弃用了。** - -- **C 语言风格的类型转换被弃用(即在变量前使用 `(convert_type)`),应该使用 `static_cast`、`reinterpret_cast`、`const_cast` 来进行类型转换。** - -- **特别地,在最新的 C++17 标准中弃用了一些可以使用的 C 标准库,例如 ``、``、`` 与 `` 等** - -- ……等等 - -还有一些其他诸如参数绑定(C++11 提供了 `std::bind` 和 `std::function`)、`export` 等特性也均被弃用。前面提到的这些特性**如果你从未使用或者听说过,也请不要尝试去了解他们,应该向新标准靠拢**,直接学习新特性。毕竟,技术是向前发展的。 - -## 1.2 与 C 的兼容性 - -出于一些不可抗力、历史原因,我们不得不在 C++ 中使用一些 C 语言代码(甚至古老的 C 语言代码),例如 Linux 系统调用。在现代 C++ 出现之前,大部分人当谈及『C 与 C++ 的区别是什么』时,普遍除了回答面向对象的类特性、泛型编程的模板特性外,就没有其他的看法了,甚至直接回答『差不多』,也是大有人在。图 1.2 中的韦恩图大致上回答了 C 和 C++ 相关的兼容情况。 - -![图 1.2: C 和 C++ 互相兼容情况](../../assets/figures/comparison.png) - -从现在开始,你的脑子里应该树立『**C++ 不是 C 的一个超集**』这个观念(而且从一开始就不是,后面的[进一步阅读的参考文献](#进一步阅读的参考文献)中给出了 C++98 和 C99 之间的区别)。在编写 C++ 时,也应该尽可能的避免使用诸如 `void*` 之类的程序风格。而在不得不使用 C 时,应该注意使用 `extern "C"` 这种特性,将 C 语言的代码与 C++代码进行分离编译,再统一链接这种做法,例如: - -```cpp -// foo.h -#ifdef __cplusplus -extern "C" { -#endif - -int add(int x, int y); - -#ifdef __cplusplus -} -#endif - -// foo.c -int add(int x, int y) { - return x+y; -} - -// 1.1.cpp -#include "foo.h" -#include -#include - -int main() { - [out = std::ref(std::cout << "Result from C code: " << add(1, 2))](){ - out.get() << ".\n"; - }(); - return 0; -} -``` - -应先使用 `gcc` 编译 C 语言的代码: - -```bash -gcc -c foo.c -``` - -编译出 `foo.o` 文件,再使用 `clang++` 将 C++ 代码和 `.o` 文件链接起来(或者都编译为 `.o` 再统一链接): - -```bash -clang++ 1.1.cpp foo.o -std=c++2a -o 1.1 -``` - -当然,你可以使用 `Makefile` 来编译上面的代码: - -```makefile -C = gcc -CXX = clang++ - -SOURCE_C = foo.c -OBJECTS_C = foo.o - -SOURCE_CXX = 1.1.cpp - -TARGET = 1.1 -LDFLAGS_COMMON = -std=c++2a - -all: - $(C) -c $(SOURCE_C) - $(CXX) $(SOURCE_CXX) $(OBJECTS_C) $(LDFLAGS_COMMON) -o $(TARGET) -clean: - rm -rf *.o $(TARGET) -``` - -> 注意:`Makefile` 中的缩进是制表符而不是空格符,如果你直接复制这段代码到你的编辑器中,制表符可能会被自动替换掉,请自行确保在 `Makefile` 中的缩进是由制表符完成的。 -> -> 如果你还不知道 `Makefile` 的使用也没有关系,本教程中不会构建过于复杂的代码,简单的在命令行中使用 `clang++ -std=c++2a` 也可以阅读本书。 - -如果你是首次接触现代 C++,那么你很可能还看不懂上面的那一小段代码,即: - -```cpp -[out = std::ref(std::cout << "Result from C code: " << add(1, 2))](){ - out.get() << ".\n"; -}(); -``` - -不必担心,本书的后续章节将为你介绍这一切。 - -[返回目录](./toc.md) | [上一章](./00-preface.md) | [下一章 语言可用性强化](./02-usability.md) - -## 进一步阅读的参考文献 - -- [C++ 语言导学. Bjarne Stroustrup](https://www.amazon.cn/dp/B00WUBYBYS/ref=sr_1_1?ie=UTF8&qid=1522400738&sr=8-1&keywords=C%2B%2B+%E8%AF%AD%E8%A8%80%E5%AF%BC%E5%AD%A6) -- [C++ 历史](https://en.cppreference.com/w/cpp/language/history) -- [C++ 特性在 GCC/Clang 等编译器中的支持情况](https://en.cppreference.com/w/cpp/compiler_support) -- [C++98 与 C99 之间的区别](http://david.tribble.com/text/cdiffs.htm#C99-vs-CPP98) - -## 许可 - -知识共享许可协议 - -本书系[欧长坤](https://github.com/changkun)著,采用[知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-nd/4.0/)许可。项目中代码使用 MIT 协议开源,参见[许可](../../LICENSE)。 diff --git a/docs/book/zh-cn/02-usability.md b/docs/book/zh-cn/02-usability.md deleted file mode 100644 index e9c84e9..0000000 --- a/docs/book/zh-cn/02-usability.md +++ /dev/null @@ -1,1034 +0,0 @@ ---- -title: 第 2 章 语言可用性的强化 -type: book-zh-cn -order: 2 ---- - -# 第 2 章 语言可用性的强化 - -[TOC] - -当我们声明、定义一个变量或者常量,对代码进行流程控制、面向对象的功能、模板编程等这些都是运行时之前,可能发生在编写代码或编译器编译代码时的行为。为此,我们通常谈及**语言可用性**,是指那些发生在运行时之前的语言行为。 - -## 2.1 常量 - -### nullptr - -`nullptr` 出现的目的是为了替代 `NULL`。 C 与 C++ 语言中有**空指针常量**,它们能被隐式转换成任何指针类型的空指针值,或 C++ 中的任何成员指针类型的空成员指针值。 `NULL` 由标准库实现提供,并被定义为实现定义的空指针常量。在 C 中,有些标准库会把 `NULL` 定义为 `((void*)0)` 而有些将它定义为 `0`。 - -C++ **不允许**直接将 `void *` 隐式转换到其他类型,从而 `((void*)0)` 不是 `NULL` 的合法实现。如果标准库尝试把 `NULL` 定义为 `((void*)0)`,那么下面这句代码中会出现编译错误: - -```cpp -char *ch = NULL; -``` - -没有了 `void *` 隐式转换的 C++ 只好将 `NULL` 定义为 `0`。而这依然会产生新的问题,将 `NULL` 定义成 `0` 将导致 `C++` 中重载特性发生混乱。考虑下面这两个 `foo` 函数: - -```cpp -void foo(char*); -void foo(int); -``` - -那么 `foo(NULL);` 这个语句将会去调用 `foo(int)`,从而导致代码违反直觉。 - -为了解决这个问题,C++11 引入了 `nullptr` 关键字,专门用来区分空指针、`0`。而 `nullptr` 的类型为 `nullptr_t`,能够隐式的转换为任何指针或成员指针的类型,也能和他们进行相等或者不等的比较。 - -你可以尝试使用 `clang++` 编译下面的代码: - -```cpp -#include -#include - -void foo(char *); -void foo(int); - -int main() { - if (std::is_same::value) - std::cout << "NULL == 0" << std::endl; - if (std::is_same::value) - std::cout << "NULL == (void *)0" << std::endl; - if (std::is_same::value) - std::cout << "NULL == nullptr" << std::endl; - - foo(0); // 调用 foo(int) - // foo(NULL); // 该行不能通过编译 - foo(nullptr); // 调用 foo(char*) - return 0; -} - -void foo(char *) { - std::cout << "foo(char*) is called" << std::endl; -} -void foo(int i) { - std::cout << "foo(int) is called" << std::endl; -} -``` - -将输出: - -```bash -foo(int) is called -foo(char*) is called -``` - -从输出中我们可以看出,`NULL` 不同于 `0` 与 `nullptr`。所以,请养成直接使用 `nullptr`的习惯。 - -此外,在上面的代码中,我们使用了 `decltype` 和 `std::is_same` 这两个属于现代 C++ 的语法,简单来说,`decltype` 用于类型推导,而 `std::is_same` 用于比较两个类型是否相同,我们会在后面 [decltype](#decltype) 一节中详细讨论。 - -### constexpr - -C++ 本身已经具备了常量表达式的概念,比如 `1+2`, `3*4` 这种表达式总是会产生相同的结果并且没有任何副作用。如果编译器能够在编译时就把这些表达式直接优化并植入到程序运行时,将能增加程序的性能。一个非常明显的例子就是在数组的定义阶段: - -```cpp -#include -#define LEN 10 - -int len_foo() { - int i = 2; - return i; -} -constexpr int len_foo_constexpr() { - return 5; -} - -constexpr int fibonacci(const int n) { - return n == 1 || n == 2 ? 1 : fibonacci(n-1)+fibonacci(n-2); -} - -int main() { - char arr_1[10]; // 合法 - char arr_2[LEN]; // 合法 - - int len = 10; - // char arr_3[len]; // 非法 - - const int len_2 = len + 1; - constexpr int len_2_constexpr = 1 + 2 + 3; - // char arr_4[len_2]; // 非法 - char arr_4[len_2_constexpr]; // 合法 - - // char arr_5[len_foo()+5]; // 非法 - char arr_6[len_foo_constexpr() + 1]; // 合法 - - std::cout << fibonacci(10) << std::endl; - // 1, 1, 2, 3, 5, 8, 13, 21, 34, 55 - std::cout << fibonacci(10) << std::endl; - return 0; -} -``` - -上面的例子中,`char arr_4[len_2]` 可能比较令人困惑,因为 `len_2` 已经被定义为了常量。为什么 `char arr_4[len_2]` 仍然是非法的呢?这是因为 C++ 标准中数组的长度必须是一个常量表达式,而对于 `len_2` 而言,这是一个 `const` 常数,而不是一个常量表达式,因此(即便这种行为在大部分编译器中都支持,但是)它是一个非法的行为,我们需要使用接下来即将介绍的 C++11 引入的 `constexpr` 特性来解决这个问题;而对于 `arr_5` 来说,C++98 之前的编译器无法得知 `len_foo()` 在运行期实际上是返回一个常数,这也就导致了非法的产生。 - -> 注意,现在大部分编译器其实都带有自身编译优化,很多非法行为在编译器优化的加持下会变得合法,若需重现编译报错的现象需要使用老版本的编译器。 - -C++11 提供了 `constexpr` 让用户显式的声明函数或对象构造函数在编译期会成为常量表达式,这个关键字明确的告诉编译器应该去验证 `len_foo` 在编译期就应该是一个常量表达式。 - -此外,`constexpr` 修饰的函数可以使用递归: - -```cpp -constexpr int fibonacci(const int n) { - return n == 1 || n == 2 ? 1 : fibonacci(n-1)+fibonacci(n-2); -} -``` - -从 C++14 开始,`constexpr` 函数可以在内部使用局部变量、循环和分支等简单语句,例如下面的代码在 C++11 的标准下是不能够通过编译的: - -```cpp -constexpr int fibonacci(const int n) { - if(n == 1) return 1; - if(n == 2) return 1; - return fibonacci(n-1) + fibonacci(n-2); -} -``` - -为此,我们可以写出下面这类简化的版本来使得函数从 C++11 开始即可用: - -```cpp -constexpr int fibonacci(const int n) { - return n == 1 || n == 2 ? 1 : fibonacci(n-1) + fibonacci(n-2); -} -``` - -## 2.2 变量及其初始化 - -### if/switch 变量声明强化 - -在传统 C++ 中,变量的声明虽然能够位于任何位置,甚至于 `for` 语句内能够声明一个临时变量 `int`,但始终没有办法在 `if` 和 `switch` 语句中声明一个临时的变量。例如: - -```cpp -#include -#include -#include - -int main() { - std::vector vec = {1, 2, 3, 4}; - - // 在 c++17 之前 - const std::vector::iterator itr = std::find(vec.begin(), vec.end(), 2); - if (itr != vec.end()) { - *itr = 3; - } - - // 需要重新定义一个新的变量 - const std::vector::iterator itr2 = std::find(vec.begin(), vec.end(), 3); - if (itr2 != vec.end()) { - *itr2 = 4; - } - - // 将输出 1, 4, 3, 4 - for (std::vector::iterator element = vec.begin(); element != vec.end(); - ++element) - std::cout << *element << std::endl; -} -``` - -在上面的代码中,我们可以看到 `itr` 这一变量是定义在整个 `main()` 的作用域内的,这导致当我们需要再次遍历整个 `std::vector` 时,需要重新命名另一个变量。C++17 消除了这一限制,使得我们可以在 `if`(或 `switch`)中完成这一操作: - -```cpp -// 将临时变量放到 if 语句内 -if (const std::vector::iterator itr = std::find(vec.begin(), vec.end(), 3); - itr != vec.end()) { - *itr = 4; -} -``` - -怎么样,是不是和 Go 语言很像? - -### 初始化列表 - -初始化是一个非常重要的语言特性,最常见的就是在对象进行初始化时进行使用。 -在传统 C++ 中,不同的对象有着不同的初始化方法,例如普通数组、 -POD (**P**lain **O**ld **D**ata,即没有构造、析构和虚函数的类或结构体) -类型都可以使用 `{}` 进行初始化,也就是我们所说的初始化列表。 -而对于类对象的初始化,要么需要通过拷贝构造、要么就需要使用 `()` 进行。 -这些不同方法都针对各自对象,不能通用。例如: - -```cpp -#include -#include - -class Foo { -public: - int value_a; - int value_b; - Foo(int a, int b) : value_a(a), value_b(b) {} -}; - -int main() { - // before C++11 - int arr[3] = {1, 2, 3}; - Foo foo(1, 2); - std::vector vec = {1, 2, 3, 4, 5}; - - std::cout << "arr[0]: " << arr[0] << std::endl; - std::cout << "foo:" << foo.value_a << ", " << foo.value_b << std::endl; - for (std::vector::iterator it = vec.begin(); it != vec.end(); ++it) { - std::cout << *it << std::endl; - } - return 0; -} -``` - -为解决这个问题,C++11 首先把初始化列表的概念绑定到类型上,称其为 `std::initializer_list`,允许构造函数或其他函数像参数一样使用初始化列表,这就为类对象的初始化与普通数组和 POD 的初始化方法提供了统一的桥梁,例如: - -```cpp -#include -#include -#include - -class MagicFoo { -public: - std::vector vec; - MagicFoo(std::initializer_list list) { - for (std::initializer_list::iterator it = list.begin(); - it != list.end(); ++it) - vec.push_back(*it); - } -}; -int main() { - // after C++11 - MagicFoo magicFoo = {1, 2, 3, 4, 5}; - - std::cout << "magicFoo: "; - for (std::vector::iterator it = magicFoo.vec.begin(); - it != magicFoo.vec.end(); ++it) - std::cout << *it << std::endl; -} -``` - -这种构造函数被叫做初始化列表构造函数,具有这种构造函数的类型将在初始化时被特殊关照。 - -初始化列表除了用在对象构造上,还能将其作为普通函数的形参,例如: - -```Cpp -public: - void foo(std::initializer_list list) { - for (std::initializer_list::iterator it = list.begin(); - it != list.end(); ++it) vec.push_back(*it); - } - -magicFoo.foo({6,7,8,9}); -``` - -其次,C++11 还提供了统一的语法来初始化任意的对象,例如: - -```cpp -Foo foo2 {3, 4}; -``` - -### 结构化绑定 - -结构化绑定提供了类似其他语言中提供的多返回值的功能。在容器一章中,我们会学到 C++11 新增了 `std::tuple` 容器用于构造一个元组,进而囊括多个返回值。但缺陷是,C++11/14 并没有提供一种简单的方法直接从元组中拿到并定义元组中的元素,尽管我们可以使用 `std::tie` 对元组进行拆包,但我们依然必须非常清楚这个元组包含多少个对象,各个对象是什么类型,非常麻烦。 - -C++17 完善了这一设定,给出的结构化绑定可以让我们写出这样的代码: - -```cpp -#include -#include - -std::tuple f() { - return std::make_tuple(1, 2.3, "456"); -} - -int main() { - auto [x, y, z] = f(); - std::cout << x << ", " << y << ", " << z << std::endl; - return 0; -} -``` - -关于 `auto` 类型推导会在 [auto 类型推导](#auto)一节中进行介绍。 - -## 2.3 类型推导 - -在传统 C 和 C++ 中,参数的类型都必须明确定义,这其实对我们快速进行编码没有任何帮助,尤其是当我们面对一大堆复杂的模板类型时,必须明确的指出变量的类型才能进行后续的编码,这不仅拖慢我们的开发效率,也让代码变得又臭又长。 - -C++11 引入了 `auto` 和 `decltype` 这两个关键字实现了类型推导,让编译器来操心变量的类型。这使得 C++ 也具有了和其他现代编程语言一样,某种意义上提供了无需操心变量类型的使用习惯。 - -### auto - -`auto` 在很早以前就已经进入了 C++,但是他始终作为一个存储类型的指示符存在,与 `register` 并存。在传统 C++ 中,如果一个变量没有声明为 `register` 变量,将自动被视为一个 `auto` 变量。而随着 `register` 被弃用(在 C++17 中作为保留关键字,以后使用,目前不具备实际意义),对 `auto` 的语义变更也就非常自然了。 - -使用 `auto` 进行类型推导的一个最为常见而且显著的例子就是迭代器。你应该在前面的小节里看到了传统 C++ 中冗长的迭代写法: - -```cpp -// 在 C++11 之前 -// 由于 cbegin() 将返回 vector::const_iterator -// 所以 it 也应该是 vector::const_iterator 类型 -for(vector::const_iterator it = vec.cbegin(); it != vec.cend(); ++it) -``` - -而有了 `auto` 之后可以: - -```cpp - -#include -#include -#include - -class MagicFoo { -public: - std::vector vec; - MagicFoo(std::initializer_list list) { - // 从 C++11 起, 使用 auto 关键字进行类型推导 - for (auto it = list.begin(); it != list.end(); ++it) { - vec.push_back(*it); - } - } -}; -int main() { - MagicFoo magicFoo = {1, 2, 3, 4, 5}; - std::cout << "magicFoo: "; - for (auto it = magicFoo.vec.begin(); it != magicFoo.vec.end(); ++it) { - std::cout << *it << ", "; - } - std::cout << std::endl; - return 0; -} -``` - -一些其他的常见用法: - -```cpp -auto i = 5; // i 被推导为 int -auto arr = new auto(10); // arr 被推导为 int * -``` - -从 C++ 14 起,`auto` 能用于 lambda 表达式中的函数传参,而 C++ 20 起该功能推广到了一般的函数。考虑下面的例子: - - -```cpp -auto add14 = [](auto x, auto y) -> int { - return x+y; -} - -int add20(auto x, auto y) { - return x+y; -} - -auto i = 5; // type int -auto j = 6; // type int -std::cout << add14(i, j) << std::endl; -std::cout << add20(i, j) << std::endl; -``` - -> -> **注意**:`auto` 还不能用于推导数组类型: -> -> ```cpp -> auto auto_arr2[10] = {arr}; // 错误, 无法推导数组元素类型 -> -> 2.6.auto.cpp:30:19: error: 'auto_arr2' declared as array of 'auto' -> auto auto_arr2[10] = {arr}; -> ``` - -### decltype - -`decltype` 关键字是为了解决 `auto` 关键字只能对变量进行类型推导的缺陷而出现的。它的用法和 `typeof` 很相似: - -```cpp -decltype(表达式) -``` - -有时候,我们可能需要计算某个表达式的类型,例如: - -```cpp -auto x = 1; -auto y = 2; -decltype(x+y) z; -``` - -你已经在前面的例子中看到 `decltype` 用于推断类型的用法,下面这个例子就是判断上面的变量 `x, y, z` 是否是同一类型: - -```cpp -if (std::is_same::value) - std::cout << "type x == int" << std::endl; -if (std::is_same::value) - std::cout << "type x == float" << std::endl; -if (std::is_same::value) - std::cout << "type z == type x" << std::endl; -``` - -其中,`std::is_same` 用于判断 `T` 和 `U` 这两个类型是否相等。输出结果为: - -``` -type x == int -type z == type x -``` - -### 尾返回类型推导 - -你可能会思考, `auto` 能不能用于推导函数的返回类型呢?还是考虑一个加法函数的例子,在传统 C++ 中我们必须这么写: - -```cpp -template -R add(T x, U y) { - return x+y; -} -``` - -> 注意:typename 和 class 在模板参数列表中没有区别,在 typename 这个关键字出现之前,都是使用 class 来定义模板参数的。但在模板中定义有[嵌套依赖类型](https://en.cppreference.com/w/cpp/language/dependent_name#The_typename_disambiguator_for_dependent_names)的变量时,需要用 typename 消除歧义 - - -这样的代码其实变得很丑陋,因为程序员在使用这个模板函数的时候,必须明确指出返回类型。但事实上我们并不知道 `add()` 这个函数会做什么样的操作,以及获得一个什么样的返回类型。 - -在 C++11 中这个问题得到解决。虽然你可能马上会反应出来使用 `decltype` 推导 `x+y` 的类型,写出这样的代码: - -```cpp -decltype(x+y) add(T x, U y) -``` - -但事实上这样的写法并不能通过编译。这是因为在编译器读到 decltype(x+y) 时,`x` 和 `y` 尚未被定义。为了解决这个问题,C++11 还引入了一个叫做尾返回类型(trailing return type),利用 `auto` 关键字将返回类型后置: - -```cpp -template -auto add2(T x, U y) -> decltype(x+y){ - return x + y; -} -``` - -令人欣慰的是从 C++14 开始是可以直接让普通函数具备返回值推导,因此下面的写法变得合法: - -```cpp -template -auto add3(T x, U y){ - return x + y; -} -``` - -可以检查一下类型推导是否正确: - -```cpp -// after c++11 -auto w = add2(1, 2.0); -if (std::is_same::value) { - std::cout << "w is double: "; -} -std::cout << w << std::endl; - -// after c++14 -auto q = add3(1.0, 2); -std::cout << "q: " << q << std::endl; -``` - -### decltype(auto) - -`decltype(auto)` 是 C++14 开始提供的一个略微复杂的用法。 - -> 要理解它你需要知道 C++ 中参数转发的概念,我们会在[语言运行时强化](./03-runtime.md)一章中详细介绍,你可以到时再回来看这一小节的内容。 - -简单来说,`decltype(auto)` 主要用于对转发函数或封装的返回类型进行推导,它使我们无需显式的指定 `decltype` 的参数表达式。考虑看下面的例子,当我们需要对下面两个函数进行封装时: - -```cpp -std::string lookup1(); -std::string& lookup2(); -``` - -在 C++11 中,封装实现是如下形式: - -```cpp -std::string look_up_a_string_1() { - return lookup1(); -} -std::string& look_up_a_string_2() { - return lookup2(); -} -``` - -而有了 `decltype(auto)`,我们可以让编译器完成这一件烦人的参数转发: - -```cpp -decltype(auto) look_up_a_string_1() { - return lookup1(); -} -decltype(auto) look_up_a_string_2() { - return lookup2(); -} -``` - - -## 2.4 控制流 - -### if constexpr - -正如本章开头出,我们知道了 C++11 引入了 `constexpr` 关键字,它将表达式或函数编译为常量结果。一个很自然的想法是,如果我们把这一特性引入到条件判断中去,让代码在编译时就完成分支判断,岂不是能让程序效率更高?C++17 将 `constexpr` 这个关键字引入到 `if` 语句中,允许在代码中声明常量表达式的判断条件,考虑下面的代码: - -```cpp -#include - -template -auto print_type_info(const T& t) { - if constexpr (std::is_integral::value) { - return t + 1; - } else { - return t + 0.001; - } -} -int main() { - std::cout << print_type_info(5) << std::endl; - std::cout << print_type_info(3.14) << std::endl; -} -``` - -在编译时,实际代码就会表现为如下: - -```cpp -int print_type_info(const int& t) { - return t + 1; -} -double print_type_info(const double& t) { - return t + 0.001; -} -int main() { - std::cout << print_type_info(5) << std::endl; - std::cout << print_type_info(3.14) << std::endl; -} -``` - -### 区间 for 迭代 - -终于,C++11 引入了基于范围的迭代写法,我们拥有了能够写出像 Python 一样简洁的循环语句,我们可以进一步简化前面的例子: - -```cpp -#include -#include -#include - -int main() { - std::vector vec = {1, 2, 3, 4}; - if (auto itr = std::find(vec.begin(), vec.end(), 3); itr != vec.end()) *itr = 4; - for (auto element : vec) - std::cout << element << std::endl; // read only - for (auto &element : vec) { - element += 1; // writeable - } - for (auto element : vec) - std::cout << element << std::endl; // read only -} -``` - -## 2.5 模板 - -C++ 的模板一直是这门语言的一种特殊的艺术,模板甚至可以独立作为一门新的语言来进行使用。模板的哲学在于将一切能够在编译期处理的问题丢到编译期进行处理,仅在运行时处理那些最核心的动态服务,进而大幅优化运行期的性能。因此模板也被很多人视作 C++ 的黑魔法之一。 - -### 外部模板 - -传统 C++ 中,模板只有在使用时才会被编译器实例化。换句话说,只要在每个编译单元(文件)中编译的代码中遇到了被完整定义的模板,都会实例化。这就产生了重复实例化而导致的编译时间的增加。并且,我们没有办法通知编译器不要触发模板的实例化。 - -为此,C++11 引入了外部模板,扩充了原来的强制编译器在特定位置实例化模板的语法,使我们能够显式的通知编译器何时进行模板的实例化: - -```cpp -template class std::vector; // 强行实例化 -extern template class std::vector; // 不在该当前编译文件中实例化模板 -``` - -### 尖括号 ">" - -在传统 C++ 的编译器中,`>>`一律被当做右移运算符来进行处理。但实际上我们很容易就写出了嵌套模板的代码: - -```cpp -std::vector> matrix; -``` - -这在传统 C++ 编译器下是不能够被编译的,而 C++11 开始,连续的右尖括号将变得合法,并且能够顺利通过编译。甚至于像下面这种写法都能够通过编译: - -```cpp -template -class MagicType { - bool magic = T; -}; - -// in main function: -std::vector2)>> magic; // 合法, 但不建议写出这样的代码 -``` - -### 类型别名模板 - -在了解类型别名模板之前,需要理解『模板』和『类型』之间的不同。仔细体会这句话:**模板是用来产生类型的。**在传统 C++ 中,`typedef` 可以为类型定义一个新的名称,但是却没有办法为模板定义一个新的名称。因为,模板不是类型。例如: - -```cpp -template -class MagicType { -public: - T dark; - U magic; -}; - -// 不合法 -template -typedef MagicType, std::string> FakeDarkMagic; -``` - -C++11 使用 `using` 引入了下面这种形式的写法,并且同时支持对传统 `typedef` 相同的功效: - -> 通常我们使用 `typedef` 定义别名的语法是:`typedef 原名称 新名称;`,但是对函数指针等别名的定义语法却不相同,这通常给直接阅读造成了一定程度的困难。 - -```cpp -typedef int (*process)(void *); -using NewProcess = int(*)(void *); -template -using TrueDarkMagic = MagicType, std::string>; - -int main() { - TrueDarkMagic you; -} -``` - -### 变长参数模板 - -模板一直是 C++ 所独有的**黑魔法**(一起念:**Dark Magic**)之一。 -在 C++11 之前,无论是类模板还是函数模板,都只能按其指定的样子, -接受一组固定数量的模板参数;而 C++11 加入了新的表示方法, -允许任意个数、任意类别的模板参数,同时也不需要在定义时将参数的个数固定。 - -```cpp -template class Magic; -``` - -模板类 Magic 的对象,能够接受不受限制个数的 typename 作为模板的形式参数,例如下面的定义: - -```cpp -class Magic, - std::map>> darkMagic; -``` - -既然是任意形式,所以个数为 `0` 的模板参数也是可以的:`class Magic<> nothing;`。 - -如果不希望产生的模板参数个数为 `0`,可以手动的定义至少一个模板参数: - -```cpp -template class Magic; -``` - -变长参数模板也能被直接调整到到模板函数上。传统 C 中的 `printf` 函数, -虽然也能达成不定个数的形参的调用,但其并非类别安全。 -而 C++11 除了能定义类别安全的变长参数函数外, -还可以使类似 `printf` 的函数能自然地处理非自带类别的对象。 -除了在模板参数中能使用 `...` 表示不定长模板参数外, -函数参数也使用同样的表示法代表不定长参数, -这也就为我们简单编写变长参数函数提供了便捷的手段,例如: - -```cpp -template void printf(const std::string &str, Args... args); -``` - -那么我们定义了变长的模板参数,如何对参数进行解包呢? - -首先,我们可以使用 `sizeof...` 来计算参数的个数,: - -```cpp -template -void magic(Ts... args) { - std::cout << sizeof...(args) << std::endl; -} -``` - -我们可以传递任意个参数给 `magic` 函数: - -```cpp -magic(); // 输出0 -magic(1); // 输出1 -magic(1, ""); // 输出2 -``` - -其次,对参数进行解包,到目前为止还没有一种简单的方法能够处理参数包,但有两种经典的处理手法: - -**1. 递归模板函数** - -递归是非常容易想到的一种手段,也是最经典的处理方法。这种方法不断递归地向函数传递模板参数,进而达到递归遍历所有模板参数的目的: - -```cpp -#include -template -void printf1(T0 value) { - std::cout << value << std::endl; -} -template -void printf1(T value, Ts... args) { - std::cout << value << std::endl; - printf1(args...); -} -int main() { - printf1(1, 2, "123", 1.1); - return 0; -} -``` - -**2. 变参模板展开** - -你应该感受到了这很繁琐,在 C++17 中增加了变参模板展开的支持,于是你可以在一个函数中完成 `printf` 的编写: - -```cpp -template -void printf2(T0 t0, T... t) { - std::cout << t0 << std::endl; - if constexpr (sizeof...(t) > 0) printf2(t...); -} -``` - -> 事实上,有时候我们虽然使用了变参模板,却不一定需要对参数做逐个遍历,我们可以利用 `std::bind` 及完美转发等特性实现对函数和参数的绑定,从而达到成功调用的目的。 - -**3. 初始化列表展开** - -递归模板函数是一种标准的做法,但缺点显而易见的在于必须定义一个终止递归的函数。 - -这里介绍一种使用初始化列表展开的黑魔法: - -```cpp -template -auto printf3(T value, Ts... args) { - std::cout << value << std::endl; - (void) std::initializer_list{([&args] { - std::cout << args << std::endl; - }(), value)...}; -} -``` - -在这个代码中,额外使用了 C++11 中提供的初始化列表以及 Lambda 表达式的特性(下一节中将提到)。 - -通过初始化列表,`(lambda 表达式, value)...` 将会被展开。由于逗号表达式的出现,首先会执行前面的 lambda 表达式,完成参数的输出。 -为了避免编译器警告,我们可以将 `std::initializer_list` 显式的转为 `void`。 - -### 折叠表达式 - -C++ 17 中将变长参数这种特性进一步带给了表达式,考虑下面这个例子: - -```cpp -#include -template -auto sum(T ... t) { - return (t + ...); -} -int main() { - std::cout << sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) << std::endl; -} -``` - -### 非类型模板参数推导 - -前面我们主要提及的是模板参数的一种形式:类型模板参数。 - -```cpp -template -auto add(T t, U u) { - return t+u; -} -``` - -其中模板的参数 `T` 和 `U` 为具体的类型。 -但还有一种常见模板参数形式可以让不同字面量成为模板参数,即非类型模板参数: - -```cpp -template -class buffer_t { -public: - T& alloc(); - void free(T& item); -private: - T data[BufSize]; -} - -buffer_t buf; // 100 作为模板参数 -``` - -在这种模板参数形式下,我们可以将 `100` 作为模板的参数进行传递。 -在 C++11 引入了类型推导这一特性后,我们会很自然的问,既然此处的模板参数 -以具体的字面量进行传递,能否让编译器辅助我们进行类型推导, -通过使用占位符 `auto` 从而不再需要明确指明类型? -幸运的是,C++17 引入了这一特性,我们的确可以 `auto` 关键字,让编译器辅助完成具体类型的推导, -例如: - -```cpp -template void foo() { - std::cout << value << std::endl; - return; -} - -int main() { - foo<10>(); // value 被推导为 int 类型 -} -``` - -## 2.6 面向对象 - -### 委托构造 - -C++11 引入了委托构造的概念,这使得构造函数可以在同一个类中一个构造函数调用另一个构造函数,从而达到简化代码的目的: - -```cpp -#include -class Base { -public: - int value1; - int value2; - Base() { - value1 = 1; - } - Base(int value) : Base() { // 委托 Base() 构造函数 - value2 = value; - } -}; - -int main() { - Base b(2); - std::cout << b.value1 << std::endl; - std::cout << b.value2 << std::endl; -} -``` - -### 继承构造 - -在传统 C++ 中,构造函数如果需要继承是需要将参数一一传递的,这将导致效率低下。C++11 利用关键字 `using` 引入了继承构造函数的概念: - -```cpp -#include -class Base { -public: - int value1; - int value2; - Base() { - value1 = 1; - } - Base(int value) : Base() { // 委托 Base() 构造函数 - value2 = value; - } -}; -class Subclass : public Base { -public: - using Base::Base; // 继承构造 -}; -int main() { - Subclass s(3); - std::cout << s.value1 << std::endl; - std::cout << s.value2 << std::endl; -} -``` - -### 显式虚函数重载 - -在传统 C++ 中,经常容易发生意外重载虚函数的事情。例如: - -```cpp -struct Base { - virtual void foo(); -}; -struct SubClass: Base { - void foo(); -}; -``` - -`SubClass::foo` 可能并不是程序员尝试重载虚函数,只是恰好加入了一个具有相同名字的函数。另一个可能的情形是,当基类的虚函数被删除后,子类拥有旧的函数就不再重载该虚拟函数并摇身一变成为了一个普通的类方法,这将造成灾难性的后果。 - -C++11 引入了 `override` 和 `final` 这两个关键字来防止上述情形的发生。 - -#### override - -当重载虚函数时,引入 `override` 关键字将显式的告知编译器进行重载,编译器将检查基函数是否存在这样的其函数签名一致的虚函数,否则将无法通过编译: - -```cpp -struct Base { - virtual void foo(int); -}; -struct SubClass: Base { - virtual void foo(int) override; // 合法 - virtual void foo(float) override; // 非法, 父类没有此虚函数 -}; -``` - -#### final - -`final` 则是为了防止类被继续继承以及终止虚函数继续重载引入的。 - -```cpp -struct Base { - virtual void foo() final; -}; -struct SubClass1 final: Base { -}; // 合法 - -struct SubClass2 : SubClass1 { -}; // 非法, SubClass1 已 final - -struct SubClass3: Base { - void foo(); // 非法, foo 已 final -}; -``` - -### 显式禁用默认函数 - -在传统 C++ 中,如果程序员没有提供,编译器会默认为对象生成默认构造函数、 -复制构造、赋值算符以及析构函数。 -另外,C++ 也为所有类定义了诸如 `new` `delete` 这样的运算符。 -当程序员有需要时,可以重载这部分函数。 - -这就引发了一些需求:无法精确控制默认函数的生成行为。 -例如禁止类的拷贝时,必须将复制构造函数与赋值算符声明为 `private`。 -尝试使用这些未定义的函数将导致编译或链接错误,则是一种非常不优雅的方式。 - -并且,编译器产生的默认构造函数与用户定义的构造函数无法同时存在。 -若用户定义了任何构造函数,编译器将不再生成默认构造函数, -但有时候我们却希望同时拥有这两种构造函数,这就造成了尴尬。 - -C++11 提供了上述需求的解决方案,允许显式的声明采用或拒绝编译器自带的函数。 -例如: - -```cpp -class Magic { - public: - Magic() = default; // 显式声明使用编译器生成的构造 - Magic& operator=(const Magic&) = delete; // 显式声明拒绝编译器生成构造 - Magic(int magic_number); -} -``` - -### 强类型枚举 - -在传统 C++中,枚举类型并非类型安全,枚举类型会被视作整数,则会让两种完全不同的枚举类型可以进行直接的比较(虽然编译器给出了检查,但并非所有),**甚至同一个命名空间中的不同枚举类型的枚举值名字不能相同**,这通常不是我们希望看到的结果。 - -C++11 引入了枚举类(enumeration class),并使用 `enum class` 的语法进行声明: - -```cpp -enum class new_enum : unsigned int { - value1, - value2, - value3 = 100, - value4 = 100 -}; -``` - -这样定义的枚举实现了类型安全,首先他不能够被隐式的转换为整数,同时也不能够将其与整数数字进行比较, -更不可能对不同的枚举类型的枚举值进行比较。但相同枚举值之间如果指定的值相同,那么可以进行比较: - -```cpp -if (new_enum::value3 == new_enum::value4) { - // 会输出true - std::cout << "new_enum::value3 == new_enum::value4" << std::endl; -} -``` - -在这个语法中,枚举类型后面使用了冒号及类型关键字来指定枚举中枚举值的类型,这使得我们能够为枚举赋值(未指定时将默认使用 `int`)。 - -而我们希望获得枚举值的值时,将必须显式的进行类型转换,不过我们可以通过重载 `<<` 这个算符来进行输出,可以收藏下面这个代码段: - -```cpp -#include -template -std::ostream& operator<<( - typename std::enable_if::value, - std::ostream>::type& stream, const T& e) -{ - return stream << static_cast::type>(e); -} -``` - -这时,下面的代码将能够被编译: - -```cpp -std::cout << new_enum::value3 << std::endl -``` - -## 总结 - -本节介绍了现代 C++ 中对语言可用性的增强,其中笔者认为最为重要的几个特性是几乎所有人都需要了解并熟练使用的: - -1. `auto` 类型推导 -2. 范围 `for` 迭代 -3. 初始化列表 -4. 变参模板 - -## 习题 - -1. 使用结构化绑定,仅用一行函数内代码实现如下函数: - - ```cpp - template - void update(std::map& m, F foo) { - // TODO: - } - int main() { - std::map m { - {"a", 1}, - {"b", 2}, - {"c", 3} - }; - update(m, [](std::string key) { - return std::hash{}(key); - }); - for (auto&& [key, value] : m) - std::cout << key << ":" << value << std::endl; - } - ``` - -2. 尝试用[折叠表达式](#折叠表达式)实现用于计算均值的函数,传入允许任意参数。 - -> 参考答案[见此](../../exercises/2)。 - -[返回目录](./toc.md) | [上一章](./01-intro.md) | [下一章 运行时强化](./03-runtime.md) - -## 许可 - -知识共享许可协议 - -本书系[欧长坤](https://github.com/changkun)著,采用[知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-nd/4.0/)许可。项目中代码使用 MIT 协议开源,参见[许可](../../LICENSE)。 diff --git a/docs/book/zh-cn/03-runtime.md b/docs/book/zh-cn/03-runtime.md deleted file mode 100644 index 94517dc..0000000 --- a/docs/book/zh-cn/03-runtime.md +++ /dev/null @@ -1,581 +0,0 @@ ---- -title: 第 3 章 语言运行期的强化 -type: book-zh-cn -order: 3 ---- - -# 第 3 章 语言运行期的强化 - -[TOC] - -## 3.1 Lambda 表达式 - -Lambda 表达式是现代 C++ 中最重要的特性之一,而 Lambda 表达式,实际上就是提供了一个类似匿名函数的特性, -而匿名函数则是在需要一个函数,但是又不想费力去命名一个函数的情况下去使用的。这样的场景其实有很多很多, -所以匿名函数几乎是现代编程语言的标配。 - -### 基础 - -Lambda 表达式的基本语法如下: - -``` -[捕获列表](参数列表) mutable(可选) 异常属性 -> 返回类型 { -// 函数体 -} -``` - -上面的语法规则除了 `[捕获列表]` 内的东西外,其他部分都很好理解,只是一般函数的函数名被略去, -返回值使用了一个 `->` 的形式进行(我们在上一节前面的尾返回类型已经提到过这种写法了)。 - -所谓捕获列表,其实可以理解为参数的一种类型,Lambda 表达式内部函数体在默认情况下是不能够使用函数体外部的变量的, -这时候捕获列表可以起到传递外部数据的作用。根据传递的行为,捕获列表也分为以下几种: - -#### 1. 值捕获 - -与参数传值类似,值捕获的前提是变量可以拷贝,不同之处则在于,被捕获的变量在 Lambda 表达式被创建时拷贝, -而非调用时才拷贝: - -```cpp -void lambda_value_capture() { - int value = 1; - auto copy_value = [value] { - return value; - }; - value = 100; - auto stored_value = copy_value(); - std::cout << "stored_value = " << stored_value << std::endl; - // 这时, stored_value == 1, 而 value == 100. - // 因为 copy_value 在创建时就保存了一份 value 的拷贝 -} -``` - -#### 2. 引用捕获 - -与引用传参类似,引用捕获保存的是引用,值会发生变化。 - -```cpp -void lambda_reference_capture() { - int value = 1; - auto copy_value = [&value] { - return value; - }; - value = 100; - auto stored_value = copy_value(); - std::cout << "stored_value = " << stored_value << std::endl; - // 这时, stored_value == 100, value == 100. - // 因为 copy_value 保存的是引用 -} -``` - -#### 3. 隐式捕获 - -手动书写捕获列表有时候是非常复杂的,这种机械性的工作可以交给编译器来处理,这时候可以在捕获列表中写一个 -`&` 或 `=` 向编译器声明采用引用捕获或者值捕获. - -总结一下,捕获提供了 Lambda 表达式对外部值进行使用的功能,捕获列表的最常用的四种形式可以是: - -- \[\] 空捕获列表 -- \[name1, name2, ...\] 捕获一系列变量 -- \[&\] 引用捕获, 从函数体内的使用确定引用捕获列表 -- \[=\] 值捕获, 从函数体内的使用确定值捕获列表 - -#### 4. 表达式捕获 - -> 这部分内容需要了解后面马上要提到的右值引用以及智能指针 - -上面提到的值捕获、引用捕获都是已经在外层作用域声明的变量,因此这些捕获方式捕获的均为左值,而不能捕获右值。 - -C++14 给与了我们方便,允许捕获的成员用任意的表达式进行初始化,这就允许了右值的捕获, -被声明的捕获变量类型会根据表达式进行判断,判断方式与使用 `auto` 本质上是相同的: - -```cpp -#include -#include // std::make_unique -#include // std::move - -void lambda_expression_capture() { - auto important = std::make_unique(1); - auto add = [v1 = 1, v2 = std::move(important)](int x, int y) -> int { - return x+y+v1+(*v2); - }; - std::cout << add(3,4) << std::endl; -} -``` - -在上面的代码中,important 是一个独占指针,是不能够被 "=" 值捕获到,这时候我们可以将其转移为右值,在表达式中初始化。 - -### 泛型 Lambda - -上一节中我们提到了 `auto` 关键字不能够用在参数表里,这是因为这样的写法会与模板的功能产生冲突。 -但是 Lambda 表达式并不是普通函数,所以在没有明确指明参数表类型的情况下,Lambda 表达式并不能够模板化。 -幸运的是,这种麻烦只存在于 C++11 中,从 C++14 开始,Lambda 函数的形式参数可以使用 `auto` -关键字来产生意义上的泛型: - -```cpp -auto add = [](auto x, auto y) { - return x+y; -}; - -add(1, 2); -add(1.1, 2.2); -``` - -## 3.2 函数对象包装器 - -这部分内容虽然属于标准库的一部分,但是从本质上来看,它却增强了 C++ 语言运行时的能力, -这部分内容也相当重要,所以放到这里来进行介绍。 - -### `std::function` - -Lambda 表达式的本质是一个和函数对象类型相似的类类型(称为闭包类型)的对象(称为闭包对象), -当 Lambda 表达式的捕获列表为空时,闭包对象还能够转换为函数指针值进行传递,例如: - -```cpp -#include - -using foo = void(int); // 定义函数类型, using 的使用见上一节中的别名语法 -void functional(foo f) { // 参数列表中定义的函数类型 foo 被视为退化后的函数指针类型 foo* - f(1); // 通过函数指针调用函数 -} - -int main() { - auto f = [](int value) { - std::cout << value << std::endl; - }; - functional(f); // 传递闭包对象,隐式转换为 foo* 类型的函数指针值 - f(1); // lambda 表达式调用 - return 0; -} -``` - -上面的代码给出了两种不同的调用形式,一种是将 Lambda 作为函数类型传递进行调用, -而另一种则是直接调用 Lambda 表达式,在 C++11 中,统一了这些概念,将能够被调用的对象的类型, -统一称之为可调用类型。而这种类型,便是通过 `std::function` 引入的。 - -C++11 `std::function` 是一种通用、多态的函数封装, -它的实例可以对任何可以调用的目标实体进行存储、复制和调用操作, -它也是对 C++ 中现有的可调用实体的一种类型安全的包裹(相对来说,函数指针的调用不是类型安全的), -换句话说,就是函数的容器。当我们有了函数的容器之后便能够更加方便的将函数、函数指针作为对象进行处理。 -例如: - -```cpp -#include -#include - -int foo(int para) { - return para; -} - -int main() { - // std::function 包装了一个返回值为 int, 参数为 int 的函数 - std::function func = foo; - - int important = 10; - std::function func2 = [&](int value) -> int { - return 1+value+important; - }; - std::cout << func(10) << std::endl; - std::cout << func2(10) << std::endl; -} -``` - -### `std::bind` 和 `std::placeholder` - -而 `std::bind` 则是用来绑定函数调用的参数的, -它解决的需求是我们有时候可能并不一定能够一次性获得调用某个函数的全部参数,通过这个函数, -我们可以将部分调用参数提前绑定到函数身上成为一个新的对象,然后在参数齐全后,完成调用。 -例如: - -```cpp -int foo(int a, int b, int c) { - ; -} -int main() { - // 将参数1,2绑定到函数 foo 上, - // 但使用 std::placeholders::_1 来对第一个参数进行占位 - auto bindFoo = std::bind(foo, std::placeholders::_1, 1,2); - // 这时调用 bindFoo 时,只需要提供第一个参数即可 - bindFoo(1); -} -``` - -> **提示:**注意 `auto` 关键字的妙用。有时候我们可能不太熟悉一个函数的返回值类型, -> 但是我们却可以通过 `auto` 的使用来规避这一问题的出现。 - -## 3.3 右值引用 - -右值引用是 C++11 引入的与 Lambda 表达式齐名的重要特性之一。它的引入解决了 C++ 中大量的历史遗留问题, -消除了诸如 `std::vector`、`std::string` 之类的额外开销, -也才使得函数对象容器 `std::function` 成为了可能。 - -### 左值、右值的纯右值、将亡值、右值 - -要弄明白右值引用到底是怎么一回事,必须要对左值和右值做一个明确的理解。 - -**左值** (lvalue, left value),顾名思义就是赋值符号左边的值。准确来说, -左值是表达式(不一定是赋值表达式)后依然存在的持久对象。 - -**右值** (rvalue, right value),右边的值,是指表达式结束后就不再存在的临时对象。 - -而 C++11 中为了引入强大的右值引用,将右值的概念进行了进一步的划分,分为:纯右值、将亡值。 - -**纯右值** (prvalue, pure rvalue),纯粹的右值,要么是纯粹的字面量,例如 `10`, `true`; -要么是求值结果相当于字面量或匿名临时对象,例如 `1+2`。非引用返回的临时变量、运算表达式产生的临时变量、 -原始字面量、Lambda 表达式都属于纯右值。 - -需要注意的是,字面量除了字符串字面量以外,均为纯右值。而字符串字面量是一个左值,类型为 `const char` 数组。例如: - -```cpp -#include - -int main() { - // 正确,"01234" 类型为 const char [6],因此是左值 - const char (&left)[6] = "01234"; - - // 断言正确,确实是 const char [6] 类型,注意 decltype(expr) 在 expr 是左值 - // 且非无括号包裹的 id 表达式与类成员表达式时,会返回左值引用 - static_assert(std::is_same::value, ""); - - // 错误,"01234" 是左值,不可被右值引用 - // const char (&&right)[6] = "01234"; -} -``` - -但是注意,数组可以被隐式转换成相对应的指针类型,而转换表达式的结果(如果不是左值引用)则一定是个右值(右值引用为将亡值,否则为纯右值)。例如: - -```cpp -const char* p = "01234"; // 正确,"01234" 被隐式转换为 const char* -const char*&& pr = "01234"; // 正确,"01234" 被隐式转换为 const char*,该转换的结果是纯右值 -// const char*& pl = "01234"; // 错误,此处不存在 const char* 类型的左值 -``` - -**将亡值** (xvalue, expiring value),是 C++11 为了引入右值引用而提出的概念(因此在传统 C++ 中, -纯右值和右值是同一个概念),也就是即将被销毁、却能够被移动的值。 - -将亡值可能稍有些难以理解,我们来看这样的代码: - -```cpp -std::vector foo() { - std::vector temp = {1, 2, 3, 4}; - return temp; -} - -std::vector v = foo(); -``` - -在这样的代码中,就传统的理解而言,函数 `foo` 的返回值 `temp` 在内部创建然后被赋值给 `v`, -然而 `v` 获得这个对象时,会将整个 `temp` 拷贝一份,然后把 `temp` 销毁,如果这个 `temp` 非常大, -这将造成大量额外的开销(这也就是传统 C++ 一直被诟病的问题)。在最后一行中,`v` 是左值、 -`foo()` 返回的值就是右值(也是纯右值)。但是,`v` 可以被别的变量捕获到, -而 `foo()` 产生的那个返回值作为一个临时值,一旦被 `v` 复制后,将立即被销毁,无法获取、也不能修改。 -而将亡值就定义了这样一种行为:临时的值能够被识别、同时又能够被移动。 - -在 C++11 之后,编译器为我们做了一些工作,此处的左值 `temp` 会被进行此隐式右值转换, -等价于 `static_cast &&>(temp)`,进而此处的 `v` 会将 `foo` 局部返回的值进行移动。 -也就是后面我们将会提到的移动语义。 - -### 右值引用和左值引用 - -要拿到一个将亡值,就需要用到右值引用:`T &&`,其中 `T` 是类型。 -右值引用的声明让这个临时值的生命周期得以延长、只要变量还活着,那么将亡值将继续存活。 - -C++11 提供了 `std::move` 这个方法将左值参数无条件的转换为右值, -有了它我们就能够方便的获得一个右值临时对象,例如: - -```cpp -#include -#include - -void reference(std::string& str) { - std::cout << "左值" << std::endl; -} -void reference(std::string&& str) { - std::cout << "右值" << std::endl; -} - -int main() -{ - std::string lv1 = "string,"; // lv1 是一个左值 - // std::string&& r1 = lv1; // 非法, 右值引用不能引用左值 - std::string&& rv1 = std::move(lv1); // 合法, std::move可以将左值转移为右值 - std::cout << rv1 << std::endl; // string, - - const std::string& lv2 = lv1 + lv1; // 合法, 常量左值引用能够延长临时变量的生命周期 - // lv2 += "Test"; // 非法, 常量引用无法被修改 - std::cout << lv2 << std::endl; // string,string, - - std::string&& rv2 = lv1 + lv2; // 合法, 右值引用延长临时对象生命周期 - rv2 += "Test"; // 合法, 非常量引用能够修改临时变量 - std::cout << rv2 << std::endl; // string,string,string,Test - - reference(rv2); // 输出左值 - - return 0; -} -``` - -`rv2` 虽然引用了一个右值,但由于它是一个引用,所以 `rv2` 依然是一个左值。 - -注意,这里有一个很有趣的历史遗留问题,我们先看下面的代码: - -```cpp -#include - -int main() { - // int &a = std::move(1); // 不合法,非常量左引用无法引用右值 - const int &b = std::move(1); // 合法, 常量左引用允许引用右值 - - std::cout << a << b << std::endl; -} -``` - -第一个问题,为什么不允许非常量引用绑定到非左值?这是因为这种做法存在逻辑错误: - -```cpp -void increase(int & v) { - v++; -} -void foo() { - double s = 1; - increase(s); -} -``` - -由于 `int&` 不能引用 `double` 类型的参数,因此必须产生一个临时值来保存 `s` 的值, -从而当 `increase()` 修改这个临时值时,调用完成后 `s` 本身并没有被修改。 - -第二个问题,为什么常量引用允许绑定到非左值?原因很简单,因为 Fortran 需要。 - -### 移动语义 - -传统 C++ 通过拷贝构造函数和赋值操作符为类对象设计了拷贝/复制的概念,但为了实现对资源的移动操作, -调用者必须使用先复制、再析构的方式,否则就需要自己实现移动对象的接口。 -试想,搬家的时候是把家里的东西直接搬到新家去,而不是将所有东西复制一份(重买)再放到新家、 -再把原来的东西全部扔掉(销毁),这是非常反人类的一件事情。 - -传统的 C++ 没有区分『移动』和『拷贝』的概念,造成了大量的数据拷贝,浪费时间和空间。 -右值引用的出现恰好就解决了这两个概念的混淆问题,例如: - -```cpp -#include -class A { -public: - int *pointer; - A():pointer(new int(1)) { - std::cout << "构造" << pointer << std::endl; - } - A(A& a):pointer(new int(*a.pointer)) { - std::cout << "拷贝" << pointer << std::endl; - } // 无意义的对象拷贝 - A(A&& a):pointer(a.pointer) { - a.pointer = nullptr; - std::cout << "移动" << pointer << std::endl; - } - ~A(){ - std::cout << "析构" << pointer << std::endl; - delete pointer; - } -}; -// 防止编译器优化 -A return_rvalue(bool test) { - A a,b; - if(test) return a; // 等价于 static_cast(a); - else return b; // 等价于 static_cast(b); -} -int main() { - A obj = return_rvalue(false); - std::cout << "obj:" << std::endl; - std::cout << obj.pointer << std::endl; - std::cout << *obj.pointer << std::endl; - return 0; -} -``` - -在上面的代码中: - -1. 首先会在 `return_rvalue` 内部构造两个 `A` 对象,于是获得两个构造函数的输出; -2. 函数返回后,产生一个将亡值,被 `A` 的移动构造(`A(A&&)`)引用,从而延长生命周期,并将这个右值中的指针拿到,保存到了 `obj` 中,而将亡值的指针被设置为 `nullptr`,防止了这块内存区域被销毁。 - -从而避免了无意义的拷贝构造,加强了性能。再来看看涉及标准库的例子: - -```cpp -#include // std::cout -#include // std::move -#include // std::vector -#include // std::string - -int main() { - - std::string str = "Hello world."; - std::vector v; - - // 将使用 push_back(const T&), 即产生拷贝行为 - v.push_back(str); - // 将输出 "str: Hello world." - std::cout << "str: " << str << std::endl; - - // 将使用 push_back(const T&&), 不会出现拷贝行为 - // 而整个字符串会被移动到 vector 中,所以有时候 std::move 会用来减少拷贝出现的开销 - // 这步操作后, str 中的值会变为空 - v.push_back(std::move(str)); - // 将输出 "str: " - std::cout << "str: " << str << std::endl; - - return 0; -} -``` - -### 完美转发 - -前面我们提到了,一个声明的右值引用其实是一个左值。这就为我们进行参数转发(传递)造成了问题: - -```cpp -void reference(int& v) { - std::cout << "左值" << std::endl; -} -void reference(int&& v) { - std::cout << "右值" << std::endl; -} -template -void pass(T&& v) { - std::cout << "普通传参:"; - reference(v); // 始终调用 reference(int&) -} -int main() { - std::cout << "传递右值:" << std::endl; - pass(1); // 1是右值, 但输出是左值 - - std::cout << "传递左值:" << std::endl; - int l = 1; - pass(l); // l 是左值, 输出左值 - - return 0; -} -``` - -对于 `pass(1)` 来说,虽然传递的是右值,但由于 `v` 是一个引用,所以同时也是左值。 -因此 `reference(v)` 会调用 `reference(int&)`,输出『左值』。 -而对于`pass(l)`而言,`l`是一个左值,为什么会成功传递给 `pass(T&&)` 呢? - -这是基于**引用坍缩规则**的:在传统 C++ 中,我们不能够对一个引用类型继续进行引用, -但 C++ 由于右值引用的出现而放宽了这一做法,从而产生了引用坍缩规则,允许我们对引用进行引用, -既能左引用,又能右引用。但是却遵循如下规则: - -| 函数形参类型 | 实参参数类型 | 推导后函数形参类型 | -| :--------: | :--------: | :-------------: | -| T& | 左引用 | T& | -| T& | 右引用 | T& | -| T&& | 左引用 | T& | -| T&& | 右引用 | T&& | - -因此,模板函数中使用 `T&&` 不一定能进行右值引用,当传入左值时,此函数的引用将被推导为左值。 -更准确的讲,**无论模板参数是什么类型的引用,当且仅当实参类型为右引用时,模板参数才能被推导为右引用类型**。 -这才使得 `v` 作为左值的成功传递。 - -完美转发就是基于上述规律产生的。所谓完美转发,就是为了让我们在传递参数的时候, -保持原来的参数类型(左引用保持左引用,右引用保持右引用)。 -为了解决这个问题,我们应该使用 `std::forward` 来进行参数的转发(传递): - -```cpp -#include -#include -void reference(int& v) { - std::cout << "左值引用" << std::endl; -} -void reference(int&& v) { - std::cout << "右值引用" << std::endl; -} -template -void pass(T&& v) { - std::cout << " 普通传参: "; - reference(v); - std::cout << " std::move 传参: "; - reference(std::move(v)); - std::cout << " std::forward 传参: "; - reference(std::forward(v)); - std::cout << "static_cast 传参: "; - reference(static_cast(v)); -} -int main() { - std::cout << "传递右值:" << std::endl; - pass(1); - - std::cout << "传递左值:" << std::endl; - int v = 1; - pass(v); - - return 0; -} -``` - -输出结果为: - -``` -传递右值: - 普通传参: 左值引用 - std::move 传参: 右值引用 - std::forward 传参: 右值引用 -static_cast 传参: 右值引用 -传递左值: - 普通传参: 左值引用 - std::move 传参: 右值引用 - std::forward 传参: 左值引用 -static_cast 传参: 左值引用 -``` - -无论传递参数为左值还是右值,普通传参都会将参数作为左值进行转发; -由于类似的原因,`std::move` 总会接受到一个左值,从而转发调用了`reference(int&&)` 输出右值引用。 - -唯独 `std::forward` 即没有造成任何多余的拷贝,同时**完美转发**(传递)了函数的实参给了内部调用的其他函数。 - -`std::forward` 和 `std::move` 一样,没有做任何事情,`std::move` 单纯的将左值转化为右值, -`std::forward` 也只是单纯的将参数做了一个类型的转换,从现象上来看, -`std::forward(v)` 和 `static_cast(v)` 是完全一样的。 - -读者可能会好奇,为何一条语句能够针对两种类型的返回对应的值, -我们再简单看一看 `std::forward` 的具体实现机制,`std::forward` 包含两个重载: - -```cpp -template -constexpr _Tp&& forward(typename std::remove_reference<_Tp>::type& __t) noexcept -{ return static_cast<_Tp&&>(__t); } - -template -constexpr _Tp&& forward(typename std::remove_reference<_Tp>::type&& __t) noexcept -{ - static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument" - " substituting _Tp is an lvalue reference type"); - return static_cast<_Tp&&>(__t); -} -``` - -在这份实现中,`std::remove_reference` 的功能是消除类型中的引用, -`std::is_lvalue_reference` 则用于检查类型推导是否正确,在 `std::forward` 的第二个实现中 -检查了接收到的值确实是一个左值,进而体现了坍缩规则。 - -当 `std::forward` 接受左值时,`_Tp` 被推导为左值,所以返回值为左值;而当其接受右值时, -`_Tp` 被推导为 右值引用,则基于坍缩规则,返回值便成为了 `&& + &&` 的右值。 -可见 `std::forward` 的原理在于巧妙的利用了模板类型推导中产生的差异。 - -这时我们能回答这样一个问题:为什么在使用循环语句的过程中,`auto&&` 是最安全的方式? -因为当 `auto` 被推导为不同的左右引用时,与 `&&` 的坍缩组合是完美转发。 - -## 总结 - -本章介绍了现代 C++ 中最为重要的几个语言运行时的增强,其中笔者认为本节中提到的所有特性都是值得掌握的: - -1. Lambda 表达式 -2. 函数对象容器 std::function -3. 右值引用 - -[返回目录](./toc.md) | [上一章](./02-usability.md) | [下一章 容器](./04-containers.md) - -## 进一步阅读的参考文献 - -- [Bjarne Stroustrup, C++ 语言的设计与演化](https://www.amazon.cn/dp/B007JFSCPY) - -## 许可 - -知识共享许可协议 - -本教程由[欧长坤](https://github.com/changkun)撰写,采用[知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-nd/4.0/)许可。项目中代码使用 MIT 协议开源,参见[许可](../../LICENSE)。 diff --git a/docs/book/zh-cn/04-containers.md b/docs/book/zh-cn/04-containers.md deleted file mode 100644 index b884ffd..0000000 --- a/docs/book/zh-cn/04-containers.md +++ /dev/null @@ -1,310 +0,0 @@ ---- -title: 第 4 章 容器 -type: book-zh-cn -order: 4 ---- - -# 第 4 章 容器 - -[TOC] - -## 4.1 线性容器 - -### `std::array` - -看到这个容器的时候肯定会出现这样的问题: - -1. 为什么要引入 `std::array` 而不是直接使用 `std::vector`? -2. 已经有了传统数组,为什么要用 `std::array`? - -先回答第一个问题,与 `std::vector` 不同,`std::array` 对象的大小是固定的,如果容器大小是固定的,那么可以优先考虑使用 `std::array` 容器。 -另外由于 `std::vector` 是自动扩容的,当存入大量的数据后,并且对容器进行了删除操作, -容器并不会自动归还被删除元素相应的内存,这时候就需要手动运行 `shrink_to_fit()` 释放这部分内存。 - -```cpp -std::vector v; -std::cout << "size:" << v.size() << std::endl; // 输出 0 -std::cout << "capacity:" << v.capacity() << std::endl; // 输出 0 - -// 如下可看出 std::vector 的存储是自动管理的,按需自动扩张 -// 但是如果空间不足,需要重新分配更多内存,而重分配内存通常是性能上有开销的操作 -v.push_back(1); -v.push_back(2); -v.push_back(3); -std::cout << "size:" << v.size() << std::endl; // 输出 3 -std::cout << "capacity:" << v.capacity() << std::endl; // 输出 4 - -// 这里的自动扩张逻辑与 Golang 的 slice 很像 -v.push_back(4); -v.push_back(5); -std::cout << "size:" << v.size() << std::endl; // 输出 5 -std::cout << "capacity:" << v.capacity() << std::endl; // 输出 8 - -// 如下可看出容器虽然清空了元素,但是被清空元素的内存并没有归还 -v.clear(); -std::cout << "size:" << v.size() << std::endl; // 输出 0 -std::cout << "capacity:" << v.capacity() << std::endl; // 输出 8 - -// 额外内存可通过 shrink_to_fit() 调用返回给系统 -v.shrink_to_fit(); -std::cout << "size:" << v.size() << std::endl; // 输出 0 -std::cout << "capacity:" << v.capacity() << std::endl; // 输出 0 -``` - -而第二个问题就更加简单,使用 `std::array` 能够让代码变得更加“现代化”,而且封装了一些操作函数,比如获取数组大小以及检查是否非空,同时还能够友好的使用标准库中的容器算法,比如 `std::sort`。 - -使用 `std::array` 很简单,只需指定其类型和大小即可: - -```cpp -std::array arr = {1, 2, 3, 4}; - -arr.empty(); // 检查容器是否为空 -arr.size(); // 返回容纳的元素数 - -// 迭代器支持 -for (auto &i : arr) -{ - // ... -} - -// 用 lambda 表达式排序 -std::sort(arr.begin(), arr.end(), [](int a, int b) { - return b < a; -}); - -// 数组大小参数必须是常量表达式 -constexpr int len = 4; -std::array arr = {1, 2, 3, 4}; - -// 非法,不同于 C 风格数组,std::array 不会自动退化成 T* -// int *arr_p = arr; -``` - -当我们开始用上了 `std::array` 时,难免会遇到要将其兼容 C 风格的接口,这里有三种做法: - -```cpp -void foo(int *p, int len) { - return; -} - -std::array arr = {1,2,3,4}; - -// C 风格接口传参 -// foo(arr, arr.size()); // 非法, 无法隐式转换 -foo(&arr[0], arr.size()); -foo(arr.data(), arr.size()); - -// 使用 `std::sort` -std::sort(arr.begin(), arr.end()); -``` - -### `std::forward_list` - -`std::forward_list` 是一个列表容器,使用方法和 `std::list` 基本类似,因此我们就不花费篇幅进行介绍了。 - -需要知道的是,和 `std::list` 的双向链表的实现不同,`std::forward_list` 使用单向链表进行实现, -提供了 `O(1)` 复杂度的元素插入,不支持快速随机访问(这也是链表的特点), -也是标准库容器中唯一一个不提供 `size()` 方法的容器。当不需要双向迭代时,具有比 `std::list` 更高的空间利用率。 - -## 4.2 无序容器 - -我们已经熟知了传统 C++ 中的有序容器 `std::map`/`std::set`,这些元素内部通过红黑树进行实现, -插入和搜索的平均复杂度均为 `O(log(size))`。在插入元素时候,会根据 `<` 操作符比较元素大小并判断元素是否相同, -并选择合适的位置插入到容器中。当对这个容器中的元素进行遍历时,输出结果会按照 `<` 操作符的顺序来逐个遍历。 - -而无序容器中的元素是不进行排序的,内部通过 Hash 表实现,插入和搜索元素的平均复杂度为 `O(constant)`, -在不关心容器内部元素顺序时,能够获得显著的性能提升。 - -C++11 引入了的两组无序容器分别是:`std::unordered_map`/`std::unordered_multimap` 和 -`std::unordered_set`/`std::unordered_multiset`。 - -它们的用法和原有的 `std::map`/`std::multimap`/`std::set`/`set::multiset` 基本类似, -由于这些容器我们已经很熟悉了,便不一一举例,我们直接来比较一下`std::map`和`std::unordered_map`: - -```cpp -#include -#include -#include -#include - -int main() { - // 两组结构按同样的顺序初始化 - std::unordered_map u = { - {1, "1"}, - {3, "3"}, - {2, "2"} - }; - std::map v = { - {1, "1"}, - {3, "3"}, - {2, "2"} - }; - - // 分别对两组结构进行遍历 - std::cout << "std::unordered_map" << std::endl; - for( const auto & n : u) - std::cout << "Key:[" << n.first << "] Value:[" << n.second << "]\n"; - - std::cout << std::endl; - std::cout << "std::map" << std::endl; - for( const auto & n : v) - std::cout << "Key:[" << n.first << "] Value:[" << n.second << "]\n"; -} -``` - -最终的输出结果为: - -```txt -std::unordered_map -Key:[2] Value:[2] -Key:[3] Value:[3] -Key:[1] Value:[1] - -std::map -Key:[1] Value:[1] -Key:[2] Value:[2] -Key:[3] Value:[3] -``` - -## 4.3 元组 - -了解过 Python 的程序员应该知道元组的概念,纵观传统 C++ 中的容器,除了 `std::pair` 外, -似乎没有现成的结构能够用来存放不同类型的数据(通常我们会自己定义结构)。 -但 `std::pair` 的缺陷是显而易见的,只能保存两个元素。 - -### 元组基本操作 - -关于元组的使用有三个核心的函数: - -1. `std::make_tuple`: 构造元组 -2. `std::get`: 获得元组某个位置的值 -3. `std::tie`: 元组拆包 - -```cpp -#include -#include - -auto get_student(int id) -{ - // 返回类型被推断为 std::tuple - - if (id == 0) - return std::make_tuple(3.8, 'A', "张三"); - if (id == 1) - return std::make_tuple(2.9, 'C', "李四"); - if (id == 2) - return std::make_tuple(1.7, 'D', "王五"); - return std::make_tuple(0.0, 'D', "null"); - // 如果只写 0 会出现推断错误, 编译失败 -} - -int main() -{ - auto student = get_student(0); - std::cout << "ID: 0, " - << "GPA: " << std::get<0>(student) << ", " - << "成绩: " << std::get<1>(student) << ", " - << "姓名: " << std::get<2>(student) << '\n'; - - double gpa; - char grade; - std::string name; - - // 元组进行拆包 - std::tie(gpa, grade, name) = get_student(1); - std::cout << "ID: 1, " - << "GPA: " << gpa << ", " - << "成绩: " << grade << ", " - << "姓名: " << name << '\n'; -} -``` - -`std::get` 除了使用常量获取元组对象外,C++14 增加了使用类型来获取元组中的对象: - -```cpp -std::tuple t("123", 4.5, 6.7, 8); -std::cout << std::get(t) << std::endl; -std::cout << std::get(t) << std::endl; // 非法, 引发编译期错误 -std::cout << std::get<3>(t) << std::endl; -``` - -### 运行期索引 - -如果你仔细思考一下可能就会发现上面代码的问题,`std::get<>` 依赖一个编译期的常量,所以下面的方式是不合法的: - -```cpp -int index = 1; -std::get(t); -``` - -那么要怎么处理?答案是,使用 `std::variant<>`(C++ 17 引入),提供给 `variant<>` 的类型模板参数 -可以让一个 `variant<>` 从而容纳提供的几种类型的变量(在其他语言,例如 Python/JavaScript 等,表现为动态类型): - -```cpp -#include -template -constexpr std::variant _tuple_index(const std::tuple& tpl, size_t i) { - if constexpr (n >= sizeof...(T)) - throw std::out_of_range("越界."); - if (i == n) - return std::variant{ std::in_place_index, std::get(tpl) }; - return _tuple_index<(n < sizeof...(T)-1 ? n+1 : 0)>(tpl, i); -} -template -constexpr std::variant tuple_index(const std::tuple& tpl, size_t i) { - return _tuple_index<0>(tpl, i); -} -template -std::ostream & operator<< (std::ostream & s, std::variant const & v) { - std::visit([&](auto && x){ s << x;}, v); - return s; -} -``` - -这样我们就能: - -```cpp -int i = 1; -std::cout << tuple_index(t, i) << std::endl; -``` - -### 元组合并与遍历 - -还有一个常见的需求就是合并两个元组,这可以通过 `std::tuple_cat` 来实现: - -```cpp -auto new_tuple = std::tuple_cat(get_student(1), std::move(t)); -``` - -马上就能够发现,应该如何快速遍历一个元组?但是我们刚才介绍了如何在运行期通过非常数索引一个 `tuple` 那么遍历就变得简单了, -首先我们需要知道一个元组的长度,可以: - -```cpp -template -auto tuple_len(T &tpl) { - return std::tuple_size::value; -} -``` - -这样就能够对元组进行迭代了: - -```cpp -// 迭代 -for(int i = 0; i != tuple_len(new_tuple); ++i) - // 运行期索引 - std::cout << tuple_index(new_tuple, i) << std::endl; -``` - -## 总结 - -本章简单介绍了现代 C++ 中新增的容器,它们的用法和传统 C++ 中已有的容器类似,相对简单,可以根据实际场景丰富的选择需要使用的容器,从而获得更好的性能。 - -`std::tuple` 虽然有效,但是标准库提供的功能有限,没办法满足运行期索引和迭代的需求,好在我们还有其他的方法可以自行实现。 - -[返回目录](./toc.md) | [上一章](./03-runtime.md) | [下一章 智能指针与内存管理](./05-pointers.md) - -## 许可 - -知识共享许可协议 - -本教程由[欧长坤](https://github.com/changkun)撰写,采用[知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-nd/4.0/)许可。项目中代码使用 MIT 协议开源,参见[许可](../../LICENSE)。 diff --git a/docs/book/zh-cn/05-pointers.md b/docs/book/zh-cn/05-pointers.md deleted file mode 100644 index ef7467f..0000000 --- a/docs/book/zh-cn/05-pointers.md +++ /dev/null @@ -1,194 +0,0 @@ ---- -title: 第 5 章 智能指针与内存管理 -type: book-zh-cn -order: 5 ---- - -# 第 5 章 智能指针与内存管理 - -[TOC] - -## 5.1 RAII 与引用计数 - -了解 `Objective-C`/`Swift` 的程序员应该知道引用计数的概念。引用计数这种计数是为了防止内存泄露而产生的。 -基本想法是对于动态分配的对象,进行引用计数,每当增加一次对同一个对象的引用,那么引用对象的引用计数就会增加一次, -每删除一次引用,引用计数就会减一,当一个对象的引用计数减为零时,就自动删除指向的堆内存。 - -在传统 C++ 中,『记得』手动释放资源,总不是最佳实践。因为我们很有可能就忘记了去释放资源而导致泄露。 -所以通常的做法是对于一个对象而言,我们在构造函数的时候申请空间,而在析构函数(在离开作用域时调用)的时候释放空间, -也就是我们常说的 RAII 资源获取即初始化技术。 - -凡事都有例外,我们总会有需要将对象在自由存储上分配的需求,在传统 C++ 里我们只好使用 `new` 和 `delete` 去 -『记得』对资源进行释放。而 C++11 引入了智能指针的概念,使用了引用计数的想法,让程序员不再需要关心手动释放内存。 -这些智能指针包括 `std::shared_ptr`/`std::unique_ptr`/`std::weak_ptr`,使用它们需要包含头文件 ``。 - -> 注意:引用计数不是垃圾回收,引用计数能够尽快收回不再被使用的对象,同时在回收的过程中也不会造成长时间的等待, -> 更能够清晰明确的表明资源的生命周期。 - -## 5.2 `std::shared_ptr` - -`std::shared_ptr` 是一种智能指针,它能够记录多少个 `shared_ptr` 共同指向一个对象,从而消除显式的调用 -`delete`,当引用计数变为零的时候就会将对象自动删除。 - -但还不够,因为使用 `std::shared_ptr` 仍然需要使用 `new` 来调用,这使得代码出现了某种程度上的不对称。 - -`std::make_shared` 就能够用来消除显式的使用 `new`,所以`std::make_shared` 会分配创建传入参数中的对象, -并返回这个对象类型的`std::shared_ptr`指针。例如: - -```cpp -#include -#include -void foo(std::shared_ptr i) { - (*i)++; -} -int main() { - // auto pointer = new int(10); // illegal, no direct assignment - // Constructed a std::shared_ptr - auto pointer = std::make_shared(10); - foo(pointer); - std::cout << *pointer << std::endl; // 11 - // The shared_ptr will be destructed before leaving the scope - return 0; -} -``` - -`std::shared_ptr` 可以通过 `get()` 方法来获取原始指针,通过 `reset()` 来减少一个引用计数, -并通过`use_count()`来查看一个对象的引用计数。例如: - -```cpp -auto pointer = std::make_shared(10); -auto pointer2 = pointer; // 引用计数+1 -auto pointer3 = pointer; // 引用计数+1 -int *p = pointer.get(); // 这样不会增加引用计数 -std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 3 -std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 3 -std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 3 - -pointer2.reset(); -std::cout << "reset pointer2:" << std::endl; -std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 2 -std::cout << "pointer2.use_count() = " - << pointer2.use_count() << std::endl; // pointer2 已 reset; 0 -std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 2 -pointer3.reset(); -std::cout << "reset pointer3:" << std::endl; -std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 1 -std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 0 -std::cout << "pointer3.use_count() = " - << pointer3.use_count() << std::endl; // pointer3 已 reset; 0 -``` - -## 5.3 `std::unique_ptr` - -`std::unique_ptr` 是一种独占的智能指针,它禁止其他智能指针与其共享同一个对象,从而保证代码的安全: - -```cpp -std::unique_ptr pointer = std::make_unique(10); // make_unique 从 C++14 引入 -std::unique_ptr pointer2 = pointer; // 非法 -``` - -> `make_unique` 并不复杂,C++11 没有提供 `std::make_unique`,可以自行实现: -> -> ```cpp -> template -> std::unique_ptr make_unique( Args&& ...args ) { -> return std::unique_ptr( new T( std::forward(args)... ) ); -> } -> ``` -> -> 至于为什么没有提供,C++ 标准委员会主席 Herb Sutter 在他的[博客](https://herbsutter.com/gotw/_102/)中提到原因是因为『被他们忘记了』。 - -既然是独占,换句话说就是不可复制。但是,我们可以利用 `std::move` 将其转移给其他的 `unique_ptr`,例如: - -```cpp -#include -#include - -struct Foo { - Foo() { std::cout << "Foo::Foo" << std::endl; } - ~Foo() { std::cout << "Foo::~Foo" << std::endl; } - void foo() { std::cout << "Foo::foo" << std::endl; } -}; - -void f(const Foo &) { - std::cout << "f(const Foo&)" << std::endl; -} - -int main() { - std::unique_ptr p1(std::make_unique()); - // p1 不空, 输出 - if (p1) p1->foo(); - { - std::unique_ptr p2(std::move(p1)); - // p2 不空, 输出 - f(*p2); - // p2 不空, 输出 - if(p2) p2->foo(); - // p1 为空, 无输出 - if(p1) p1->foo(); - p1 = std::move(p2); - // p2 为空, 无输出 - if(p2) p2->foo(); - std::cout << "p2 被销毁" << std::endl; - } - // p1 不空, 输出 - if (p1) p1->foo(); - // Foo 的实例会在离开作用域时被销毁 -} -``` - -## 5.4 `std::weak_ptr` - -如果你仔细思考 `std::shared_ptr` 就会发现依然存在着资源无法释放的问题。看下面这个例子: - -```cpp -struct A; -struct B; - -struct A { - std::shared_ptr pointer; - ~A() { - std::cout << "A 被销毁" << std::endl; - } -}; -struct B { - std::shared_ptr pointer; - ~B() { - std::cout << "B 被销毁" << std::endl; - } -}; -int main() { - auto a = std::make_shared(); - auto b = std::make_shared(); - a->pointer = b; - b->pointer = a; -} -``` - -运行结果是 A, B 都不会被销毁,这是因为 a,b 内部的 pointer 同时又引用了 `a,b`,这使得 `a,b` 的引用计数均变为了 2,而离开作用域时,`a,b` 智能指针被析构,却只能造成这块区域的引用计数减一,这样就导致了 `a,b` 对象指向的内存区域引用计数不为零,而外部已经没有办法找到这块区域了,也就造成了内存泄露,如图 5.1: - -![图 5.1](../../assets/figures/pointers1.png) - -解决这个问题的办法就是使用弱引用指针 `std::weak_ptr`,`std::weak_ptr`是一种弱引用(相比较而言 `std::shared_ptr` 就是一种强引用)。弱引用不会引起引用计数增加,当换用弱引用时候,最终的释放流程如图 5.2 所示: - -![图 5.2](../../assets/figures/pointers2.png) - -在上图中,最后一步只剩下 B,而 B 并没有任何智能指针引用它,因此这块内存资源也会被释放。 - -`std::weak_ptr` 没有 `*` 运算符和 `->` 运算符,所以不能够对资源进行操作,它可以用于检查 `std::shared_ptr` 是否存在,其 `expired()` 方法能在资源未被释放时,会返回 `false`,否则返回 `true`;除此之外,它也可以用于获取指向原始对象的 `std::shared_ptr` 指针,其 `lock()` 方法在原始对象未被释放时,返回一个指向原始对象的 `std::shared_ptr` 指针,进而访问原始对象的资源,否则返回`nullptr`。 - -## 总结 - -智能指针这种技术并不新奇,在很多语言中都是一种常见的技术,现代 C++ 将这项技术引进,在一定程度上消除了 `new`/`delete` 的滥用,是一种更加成熟的编程范式。 - -[返回目录](./toc.md) | [上一章](./04-containers.md) | [下一章 正则表达式](./06-regex.md) - -## 进一步阅读的参考资料 - -1. [stackoverflow 上关于『C++11为什么没有 make_unique』的讨论](https://stackoverflow.com/questions/12580432/why-does-c11-have-make-shared-but-not-make-unique) - -## 许可 - -知识共享许可协议 - -本教程由[欧长坤](https://github.com/changkun)撰写,采用[知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-nd/4.0/)许可。项目中代码使用 MIT 协议开源,参见[许可](../../LICENSE)。 diff --git a/docs/book/zh-cn/06-regex.md b/docs/book/zh-cn/06-regex.md deleted file mode 100644 index a4c6597..0000000 --- a/docs/book/zh-cn/06-regex.md +++ /dev/null @@ -1,248 +0,0 @@ ---- -title: 第 6 章 正则表达式 -type: book-zh-cn -order: 6 ---- - -# 第 6 章 正则表达式 - -[TOC] - -## 6.1 正则表达式简介 - -正则表达式不是 C++ 语言的一部分,这里仅做简单的介绍。 - -正则表达式描述了一种字符串匹配的模式。一般使用正则表达式主要是实现下面三个需求: - -1. 检查一个串是否包含某种形式的子串; -2. 将匹配的子串替换; -3. 从某个串中取出符合条件的子串。 - -正则表达式是由普通字符(例如 a 到 z)以及特殊字符组成的文字模式。模式描述在搜索文本时要匹配的一个或多个字符串。 -正则表达式作为一个模板,将某个字符模式与所搜索的字符串进行匹配。 - - -### 普通字符 - -普通字符包括没有显式指定为元字符的所有可打印和不可打印字符。这包括所有大写和小写字母、所有数字、所有标点符号和一些其他符号。 - -### 特殊字符 - -特殊字符是正则表达式里有特殊含义的字符,也是正则表达式的核心匹配语法。参见下表: - -|特别字符|描述| -|:---:|:------------------------------------------------------| -|`$`| 匹配输入字符串的结尾位置。| -|`(`,`)`| 标记一个子表达式的开始和结束位置。子表达式可以获取供以后使用。| -|`*`| 匹配前面的子表达式零次或多次。| -|`+`| 匹配前面的子表达式一次或多次。| -|`.`| 匹配除换行符 `\n` 之外的任何单字符。| -|`[`| 标记一个中括号表达式的开始。| -|`?`| 匹配前面的子表达式零次或一次,或指明一个非贪婪限定符。| -| `\`| 将下一个字符标记为或特殊字符、或原义字符、或向后引用、或八进制转义符。例如, `n` 匹配字符 `n`。`\n` 匹配换行符。序列 `\\` 匹配 `'\'` 字符,而 `\(` 则匹配 `'('` 字符。| -|`^`| 匹配输入字符串的开始位置,除非在方括号表达式中使用,此时它表示不接受该字符集合。| -|`{`| 标记限定符表达式的开始。| -|`\|`| 指明两项之间的一个选择。| - -### 限定符 - -限定符用来指定正则表达式的一个给定的组件必须要出现多少次才能满足匹配。见下表: - - -|字符|描述| -|:-----:|:------------------------------------------------------| -|`*` |匹配前面的子表达式零次或多次。例如,`foo*` 能匹配 `fo` 以及 `foooo`。`*` 等价于`{0,}`。| -|`+` |匹配前面的子表达式一次或多次。例如,`foo+` 能匹配 `foo` 以及 `foooo`,但不能匹配 `fo`。`+` 等价于 `{1,}`。| -|`?` |匹配前面的子表达式零次或一次。例如,`Your(s)?` 可以匹配 `Your` 或 `Yours` 中的`Your` 。`?` 等价于 `{0,1}`。| -|`{n}` | `n` 是一个非负整数。匹配确定的 `n` 次。例如,`o{2}` 不能匹配 `for` 中的 `o`,但是能匹配 `foo` 中的两个 `o`。| -|`{n,}` | `n` 是一个非负整数。至少匹配 `n` 次。例如,`o{2,}` 不能匹配 `for` 中的 `o`,但能匹配 `foooooo` 中的所有 `o`。`o{1,}` 等价于 `o+`。`o{0,}` 则等价于 `o*`。| -|`{n,m}`| `m` 和 `n` 均为非负整数,其中 `n` 小于等于 `m`。最少匹配 `n` 次且最多匹配 `m` 次。例如,`o{1,3}` 将匹配 `foooooo` 中的前三个 `o`。`o{0,1}` 等价于 `o?`。注意,在逗号和两个数之间不能有空格。| - -有了这两张表,我们通常就能够读懂几乎所有的正则表达式了。 - -## 6.2 std::regex 及其相关 - -对字符串内容进行匹配的最常见手段就是使用正则表达式。 -可惜在传统 C++ 中正则表达式一直没有得到语言层面的支持,没有纳入标准库, -而 C++ 作为一门高性能语言,在后台服务的开发中,对 URL 资源链接进行判断时, -使用正则表达式也是工业界最为成熟的普遍做法。 - -一般的解决方案就是使用 `boost` 的正则表达式库。 -而 C++11 正式将正则表达式的的处理方法纳入标准库的行列,从语言级上提供了标准的支持, -不再依赖第三方。 - -C++11 提供的正则表达式库操作 `std::string` 对象, -模式 `std::regex` (本质是 `std::basic_regex`)进行初始化, -通过 `std::regex_match` 进行匹配, -从而产生 `std::smatch` (本质是 `std::match_results` 对象)。 - -我们通过一个简单的例子来简单介绍这个库的使用。考虑下面的正则表达式: - -- `[a-z]+\.txt`: 在这个正则表达式中, `[a-z]` 表示匹配一个小写字母, `+` 可以使前面的表达式匹配多次, -因此 `[a-z]+` 能够匹配一个小写字母组成的字符串。 -在正则表达式中一个 `.` 表示匹配任意字符,而 `\.` 则表示匹配字符 `.`, -最后的 `txt` 表示严格匹配 `txt` 则三个字母。因此这个正则表达式的所要匹配的内容就是由纯小写字母组成的文本文件。 - -`std::regex_match` 用于匹配字符串和正则表达式,有很多不同的重载形式。 -最简单的一个形式就是传入 `std::string` 以及一个 `std::regex` 进行匹配, -当匹配成功时,会返回 `true`,否则返回 `false`。例如: - -```cpp -#include -#include -#include - -int main() { - std::string fnames[] = {"foo.txt", "bar.txt", "test", "a0.txt", "AAA.txt"}; - // 在 C++ 中 \ 会被作为字符串内的转义符, - // 为使 \. 作为正则表达式传递进去生效,需要对 \ 进行二次转义,从而有 \\. - std::regex txt_regex("[a-z]+\\.txt"); - for (const auto &fname: fnames) - std::cout << fname << ": " << std::regex_match(fname, txt_regex) << std::endl; -} -``` - -另一种常用的形式就是依次传入 `std::string`/`std::smatch`/`std::regex` 三个参数, -其中 `std::smatch` 的本质其实是 `std::match_results`。 -故而在标准库的实现中, `std::smatch` 被定义为了 `std::match_results`, -也就是一个子串迭代器类型的 `match_results`。 -使用 `std::smatch` 可以方便的对匹配的结果进行获取,例如: - -```cpp -std::regex base_regex("([a-z]+)\\.txt"); -std::smatch base_match; -for(const auto &fname: fnames) { - if (std::regex_match(fname, base_match, base_regex)) { - // std::smatch 的第一个元素匹配整个字符串 - // std::smatch 的第二个元素匹配了第一个括号表达式 - if (base_match.size() == 2) { - std::string base = base_match[1].str(); - std::cout << "sub-match[0]: " << base_match[0].str() << std::endl; - std::cout << fname << " sub-match[1]: " << base << std::endl; - } - } -} -``` - -以上两个代码段的输出结果为: - -```txt -foo.txt: 1 -bar.txt: 1 -test: 0 -a0.txt: 0 -AAA.txt: 0 -sub-match[0]: foo.txt -foo.txt sub-match[1]: foo -sub-match[0]: bar.txt -bar.txt sub-match[1]: bar -``` - -## 总结 - -本节简单介绍了正则表达式本身,然后根据使用正则表达式的主要需求,通过一个实际的例子介绍了正则表达式库的使用。 - -## 习题 - -1. 在 Web 服务器开发中,我们通常希望服务某些满足某个条件的路由。正则表达式便是完成这一目标的工具之一。 - -给定如下请求结构: - -```cpp -struct Request { - // request method, POST, GET; path; HTTP version - std::string method, path, http_version; - // use smart pointer for reference counting of content - std::shared_ptr content; - // hash container, key-value dict - std::unordered_map header; - // use regular expression for path match - std::smatch path_match; -}; -``` - -请求的资源类型: - -```cpp -typedef std::map< - std::string, std::unordered_map< - std::string,std::function>> resource_type; -``` - -以及服务端模板: - -```cpp -template -class ServerBase { -public: - resource_type resource; - resource_type default_resource; - - void start() { - // TODO - } -protected: - Request parse_request(std::istream& stream) const { - // TODO - } -} -``` - -请实现成员函数 `start()` 与 `parse_request`。使得服务器模板使用者可以如下指定路由: - -```cpp -template -void start_server(SERVER_TYPE &server) { - - // process GET request for /match/[digit+numbers], - // e.g. GET request is /match/abc123, will return abc123 - server.resource["fill_your_reg_ex"]["GET"] = - [](ostream& response, Request& request) - { - string number=request.path_match[1]; - response << "HTTP/1.1 200 OK\r\nContent-Length: " - << number.length() << "\r\n\r\n" << number; - }; - - // peocess default GET request; - // anonymous function will be called - // if no other matches response files in folder web/ - // default: index.html - server.default_resource["fill_your_reg_ex"]["GET"] = - [](ostream& response, Request& request) - { - string filename = "www/"; - - string path = request.path_match[1]; - - // forbidden use `..` access content outside folder web/ - size_t last_pos = path.rfind("."); - size_t current_pos = 0; - size_t pos; - while((pos=path.find('.', current_pos)) != string::npos && pos != last_pos) { - current_pos = pos; - path.erase(pos, 1); - last_pos--; - } - - // (...) - }; - - server.start(); -} -``` - -参考答案[见此](../../exercises/6)。 - -[返回目录](./toc.md) | [上一章](./05-pointers.md) | [下一章 线程与并发](./07-thread.md) - -## 进一步阅读的参考资料 - -1. [知乎『如何评价 GCC 的 C++11 正则表达式?』中原库作者 Tim Shen 的回答](https://zhihu.com/question/23070203/answer/84248248) -2. [正则表达式库文档](https://en.cppreference.com/w/cpp/regex) - -## 许可 - -知识共享许可协议 - -本教程由[欧长坤](https://github.com/changkun)撰写,采用[知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-nd/4.0/)许可。项目中代码使用 MIT 协议开源,参见[许可](../../LICENSE)。 diff --git a/docs/book/zh-cn/07-thread.md b/docs/book/zh-cn/07-thread.md deleted file mode 100644 index 3c59a72..0000000 --- a/docs/book/zh-cn/07-thread.md +++ /dev/null @@ -1,568 +0,0 @@ ---- -title: 第 7 章 并行与并发 -type: book-zh-cn -order: 7 ---- - -# 第 7 章 并行与并发 - -[TOC] - -## 7.1 并行基础 - -`std::thread` 用于创建一个执行的线程实例,所以它是一切并发编程的基础,使用时需要包含 `` 头文件, -它提供了很多基本的线程操作,例如 `get_id()` 来获取所创建线程的线程 ID,使用 `join()` 来等待一个线程结束(与该线程汇合)等等,例如: - -```cpp -#include -#include - -int main() { - std::thread t([](){ - std::cout << "hello world." << std::endl; - }); - t.join(); - return 0; -} -``` - -## 7.2 互斥量与临界区 - -我们在操作系统、亦或是数据库的相关知识中已经了解过了有关并发技术的基本知识,`mutex` 就是其中的核心之一。 -C++11 引入了 `mutex` 相关的类,其所有相关的函数都放在 `` 头文件中。 - -`std::mutex` 是 C++11 中最基本的互斥量类,可以通过构造 `std::mutex` 对象创建互斥量, -而通过其成员函数 `lock()` 可以进行上锁,`unlock()` 可以进行解锁。 -但是在实际编写代码的过程中,最好不去直接调用成员函数, -因为调用成员函数就需要在每个临界区的出口处调用 `unlock()`,当然,还包括异常。 -而 C++11 为互斥量提供了一个 RAII 机制的模板类 `std::lock_guard`。 - -RAII 在不失代码简洁性的同时,很好的保证了代码的异常安全性。 - -在 RAII 用法下,对于临界区的互斥量的创建只需要在作用域的开始部分,例如: - -```cpp -#include -#include -#include - -int v = 1; - -void critical_section(int change_v) { - static std::mutex mtx; - std::lock_guard lock(mtx); - - // 执行竞争操作 - v = change_v; - - // 离开此作用域后 mtx 会被释放 -} - -int main() { - std::thread t1(critical_section, 2), t2(critical_section, 3); - t1.join(); - t2.join(); - - std::cout << v << std::endl; - return 0; -} -``` - -由于 C++ 保证了所有栈对象在生命周期结束时会被销毁,所以这样的代码也是异常安全的。 -无论 `critical_section()` 正常返回、还是在中途抛出异常,都会引发栈回溯,也就自动调用了 `unlock()`。 - -> 没有捕获抛出的异常(此时由实现定义是否进行栈回溯)。 - -而 `std::unique_lock` 则是相对于 `std::lock_guard` 出现的,`std::unique_lock` 更加灵活, -`std::unique_lock` 的对象会以独占所有权(没有其他的 `unique_lock` 对象同时拥有某个 `mutex` 对象的所有权) -的方式管理 `mutex` 对象上的上锁和解锁的操作。所以在并发编程中,推荐使用 `std::unique_lock`。 - -`std::lock_guard` 不能显式的调用 `lock` 和 `unlock`, 而 `std::unique_lock` 可以在声明后的任意位置调用, -可以缩小锁的作用范围,提供更高的并发度。 - -如果你用到了条件变量 `std::condition_variable::wait` 则必须使用 `std::unique_lock` 作为参数。 - -例如: - -```cpp -#include -#include -#include - -int v = 1; - -void critical_section(int change_v) { - static std::mutex mtx; - std::unique_lock lock(mtx); - // 执行竞争操作 - v = change_v; - std::cout << v << std::endl; - // 将锁进行释放 - lock.unlock(); - - // 在此期间,任何人都可以抢夺 v 的持有权 - - // 开始另一组竞争操作,再次加锁 - lock.lock(); - v += 1; - std::cout << v << std::endl; -} - -int main() { - std::thread t1(critical_section, 2), t2(critical_section, 3); - t1.join(); - t2.join(); - return 0; -} -``` - -## 7.3 期物 - -期物(Future)表现为 `std::future`,它提供了一个访问异步操作结果的途径,这句话很不好理解。 -为了理解这个特性,我们需要先理解一下在 C++11 之前的多线程行为。 - -试想,如果我们的主线程 A 希望新开辟一个线程 B 去执行某个我们预期的任务,并返回我一个结果。 -而这时候,线程 A 可能正在忙其他的事情,无暇顾及 B 的结果, -所以我们会很自然的希望能够在某个特定的时间获得线程 B 的结果。 - -在 C++11 的 `std::future` 被引入之前,通常的做法是: -创建一个线程 A,在线程 A 里启动任务 B,当准备完毕后发送一个事件,并将结果保存在全局变量中。 -而主函数线程 A 里正在做其他的事情,当需要结果的时候,调用一个线程等待函数来获得执行的结果。 - -而 C++11 提供的 `std::future` 简化了这个流程,可以用来获取异步任务的结果。 -自然地,我们很容易能够想象到把它作为一种简单的线程同步手段,即屏障(barrier)。 - -为了看一个例子,我们这里额外使用 `std::packaged_task`,它可以用来封装任何可以调用的目标,从而用于实现异步的调用。 -举例来说: - -```cpp -#include -#include -#include - -int main() { - // 将一个返回值为7的 lambda 表达式封装到 task 中 - // std::packaged_task 的模板参数为要封装函数的类型 - std::packaged_task task([](){return 7;}); - // 获得 task 的期物 - std::future result = task.get_future(); // 在一个线程中执行 task - std::thread(std::move(task)).detach(); - std::cout << "waiting..."; - result.wait(); // 在此设置屏障,阻塞到期物的完成 - // 输出执行结果 - std::cout << "done!" << std:: endl << "future result is " - << result.get() << std::endl; - return 0; -} -``` - -在封装好要调用的目标后,可以使用 `get_future()` 来获得一个 `std::future` 对象,以便之后实施线程同步。 - -## 7.4 条件变量 - -条件变量 `std::condition_variable` 是为了解决死锁而生,当互斥操作不够用而引入的。 -比如,线程可能需要等待某个条件为真才能继续执行, -而一个忙等待循环中可能会导致所有其他线程都无法进入临界区使得条件为真时,就会发生死锁。 -所以,`condition_variable` 对象被创建出现主要就是用于唤醒等待线程从而避免死锁。 -`std::condition_variable`的 `notify_one()` 用于唤醒一个线程; -`notify_all()` 则是通知所有线程。下面是一个生产者和消费者模型的例子: - -```cpp -#include -#include -#include -#include -#include -#include - - -int main() { - std::queue produced_nums; - std::mutex mtx; - std::condition_variable cv; - bool notified = false; // 通知信号 - - // 生产者 - auto producer = [&]() { - for (int i = 0; ; i++) { - std::this_thread::sleep_for(std::chrono::milliseconds(900)); - std::unique_lock lock(mtx); - std::cout << "producing " << i << std::endl; - produced_nums.push(i); - notified = true; - cv.notify_all(); // 此处也可以使用 notify_one - } - }; - // 消费者 - auto consumer = [&]() { - while (true) { - std::unique_lock lock(mtx); - while (!notified) { // 避免虚假唤醒 - cv.wait(lock); - } - // 短暂取消锁,使得生产者有机会在消费者消费空前继续生产 - lock.unlock(); - // 消费者慢于生产者 - std::this_thread::sleep_for(std::chrono::milliseconds(1000)); - lock.lock(); - while (!produced_nums.empty()) { - std::cout << "consuming " << produced_nums.front() << std::endl; - produced_nums.pop(); - } - notified = false; - } - }; - - // 分别在不同的线程中运行 - std::thread p(producer); - std::thread cs[2]; - for (int i = 0; i < 2; ++i) { - cs[i] = std::thread(consumer); - } - p.join(); - for (int i = 0; i < 2; ++i) { - cs[i].join(); - } - return 0; -} -``` - -值得一提的是,在生产者中我们虽然可以使用 `notify_one()`,但实际上并不建议在此处使用, -因为在多消费者的情况下,我们的消费者实现中简单放弃了锁的持有,这使得可能让其他消费者 -争夺此锁,从而更好的利用多个消费者之间的并发。话虽如此,但实际上因为 `std::mutex` 的排他性, -我们根本无法期待多个消费者能真正意义上的并行消费队列的中生产的内容,我们仍需要粒度更细的手段。 - -## 7.5 原子操作与内存模型 - -细心的读者可能会对前一小节中生产者消费者模型的例子可能存在编译器优化导致程序出错的情况产生疑惑。 -例如,编译器可能对变量 `notified` 存在优化,例如将其作为一个寄存器的值, -从而导致消费者线程永远无法观察到此值的变化。这是一个好问题,为了解释清楚这个问题,我们需要进一步讨论 -从 C++ 11 起引入的内存模型这一概念。我们首先来看一个问题,下面这段代码输出结果是多少? - -```cpp -#include -#include - -int main() { - int a = 0; - volatile int flag = 0; - - std::thread t1([&]() { - while (flag != 1); - - int b = a; - std::cout << "b = " << b << std::endl; - }); - - std::thread t2([&]() { - a = 5; - flag = 1; - }); - - t1.join(); - t2.join(); - return 0; -} -``` - -从直观上看,`t2` 中 `a = 5;` 这一条语句似乎总在 `flag = 1;` 之前得到执行,而 `t1` 中 `while (flag != 1)` -似乎保证了 `std::cout << "b = " << b << std::endl;` 不会再标记被改变前执行。从逻辑上看,似乎 `b` 的值应该等于 5。 -但实际情况远比此复杂得多,或者说这段代码本身属于未定义的行为,因为对于 `a` 和 `flag` 而言,他们在两个并行的线程中被读写, -出现了竞争。除此之外,即便我们忽略竞争读写,仍然可能受 CPU 的乱序执行,编译器对指令的重排的影响, -导致 `a = 5` 发生在 `flag = 1` 之后。从而 `b` 可能输出 0。 - -### 原子操作 - -`std::mutex` 可以解决上面出现的并发读写的问题,但互斥锁是操作系统级的功能, -这是因为一个互斥锁的实现通常包含两条基本原理: - -1. 提供线程间自动的状态转换,即『锁住』这个状态 -2. 保障在互斥锁操作期间,所操作变量的内存与临界区外进行隔离 - -这是一组非常强的同步条件,换句话说当最终编译为 CPU 指令时会表现为非常多的指令(我们之后再来看如何实现一个简单的互斥锁)。 -这对于一个仅需原子级操作(没有中间态)的变量,似乎太苛刻了。 - -关于同步条件的研究有着非常久远的历史,我们在这里不进行赘述。读者应该明白,现代 CPU 体系结构提供了 CPU 指令级的原子操作, -因此在多线程下共享变量的读写这一问题上, C++11 中还引入了 `std::atomic` 模板,使得我们能实例化原子类型,并将一个原子写操作从一组指令,最小化到单个 CPU 指令。例如: - -```cpp -std::atomic counter; -``` - -并为整数或浮点数的原子类型提供了基本的数值成员函数,举例来说, -包括 `fetch_add`, `fetch_sub` 等,同时通过重载方便的提供了对应的 `+`,`-` 版本。 -比如下面的例子: - -```cpp -#include -#include -#include - -std::atomic count = {0}; - -int main() { - std::thread t1([](){ - count.fetch_add(1); - }); - std::thread t2([](){ - count++; // 等价于 fetch_add - count += 1; // 等价于 fetch_add - }); - t1.join(); - t2.join(); - std::cout << count << std::endl; - return 0; -} -``` - -当然,并非所有的类型都能提供原子操作,这是因为原子操作的可行性取决于具体的 CPU 架构,以及所实例化的类型结构是否能够满足该 CPU 架构对内存对齐 -条件的要求,因而我们总是可以通过 `std::atomic::is_lock_free` 来检查该原子类型是否需支持原子操作,例如: - -```cpp -#include -#include - -struct A { - float x; - int y; - long long z; -}; - -int main() { - std::atomic a; - std::cout << std::boolalpha << a.is_lock_free() << std::endl; - return 0; -} -``` - -### 一致性模型 - -并行执行的多个线程,从某种宏观层面上讨论,可以粗略的视为一种分布式系统。 -在分布式系统中,任何通信乃至本地操作都需要消耗一定时间,甚至出现不可靠的通信。 - -如果我们强行将一个变量 `v` 在多个线程之间的操作设为原子操作,即任何一个线程在操作完 `v` 后, -其他线程均能**同步**感知到 `v` 的变化,则对于变量 `v` 而言,表现为顺序执行的程序,它并没有由于引入多线程 -而得到任何效率上的收益。对此有什么办法能够适当的加速呢?答案便是削弱原子操作的在进程间的同步条件。 - -从原理上看,每个线程可以对应为一个集群节点,而线程间的通信也几乎等价于集群节点间的通信。 -削弱进程间的同步条件,通常我们会考虑四种不同的一致性模型: - -1. 线性一致性:又称强一致性或原子一致性。它要求任何一次读操作都能读到某个数据的最近一次写的数据,并且所有线程的操作顺序与全局时钟下的顺序是一致的。 - - ``` - x.store(1) x.load() - T1 ---------+----------------+------> - - - T2 -------------------+-------------> - x.store(2) - ``` - - 在这种情况下线程 `T1`, `T2` 对 `x` 的两次写操作是原子的,且 `x.store(1)` 是严格的发生在 `x.store(2)` 之前,`x.store(2)` 严格的发生在 `x.load()` 之前。 - 值得一提的是,线性一致性对全局时钟的要求是难以实现的,这也是人们不断研究比这个一致性更弱条件下其他一致性的算法的原因。 - -2. 顺序一致性:同样要求任何一次读操作都能读到数据最近一次写入的数据,但未要求与全局时钟的顺序一致。 - - ``` - x.store(1) x.store(3) x.load() - T1 ---------+-----------+----------+-----> - - - T2 ---------------+----------------------> - x.store(2) - - 或者 - - x.store(1) x.store(3) x.load() - T1 ---------+-----------+----------+-----> - - - T2 ------+-------------------------------> - x.store(2) - ``` - - 在顺序一致性的要求下,`x.load()` 必须读到最近一次写入的数据,因此 `x.store(2)` 与 `x.store(1)` 并无任何先后保障,即 只要 `T2` 的 `x.store(2)` 发生在 `x.store(3)` 之前即可。 - -3. 因果一致性:它的要求进一步降低,只需要有因果关系的操作顺序得到保障,而非因果关系的操作顺序则不做要求。 - - ``` - a = 1 b = 2 - T1 ----+-----------+----------------------------> - - - T2 ------+--------------------+--------+--------> - x.store(3) c = a + b y.load() - - 或者 - - a = 1 b = 2 - T1 ----+-----------+----------------------------> - - - T2 ------+--------------------+--------+--------> - x.store(3) y.load() c = a + b - - 亦或者 - - b = 2 a = 1 - T1 ----+-----------+----------------------------> - - - T2 ------+--------------------+--------+--------> - y.load() c = a + b x.store(3) - ``` - - 上面给出的三种例子都是属于因果一致的,因为整个过程中,只有 `c` 对 `a` 和 `b` 产生依赖,而 `x` 和 `y` - 在此例子中表现为没有关系(但实际情况中我们需要更详细的信息才能确定 `x` 与 `y` 确实无关) - -4. 最终一致性:是最弱的一致性要求,它只保障某个操作在未来的某个时间节点上会被观察到,但并未要求被观察到的时间。因此我们甚至可以对此条件稍作加强,例如规定某个操作被观察到的时间总是有界的。当然这已经不在我们的讨论范围之内了。 - - ``` - x.store(3) x.store(4) - T1 ----+-----------+--------------------------------------------> - - - T2 ---------+------------+--------------------+--------+--------> - x.read x.read() x.read() x.read() - ``` - - 在上面的情况中,如果我们假设 x 的初始值为 0,则 `T2` 中四次 `x.read()` 结果可能但不限于以下情况: - - ``` - 3 4 4 4 // x 的写操作被很快观察到 - 0 3 3 4 // x 的写操作被观察到的时间存在一定延迟 - 0 0 0 4 // 最后一次读操作读到了 x 的最终值,但此前的变化并未观察到 - 0 0 0 0 // 在当前时间段内 x 的写操作均未被观察到, - // 但未来某个时间点上一定能观察到 x 为 4 的情况 - ``` - -### 内存顺序 - -为了追求极致的性能,实现各种强度要求的一致性,C++11 为原子操作定义了六种不同的内存顺序 `std::memory_order` 的选项,表达了四种多线程间的同步模型: - -1. 宽松模型:在此模型下,单个线程内的原子操作都是顺序执行的,不允许指令重排,但不同线程间原子操作的顺序是任意的。类型通过 `std::memory_order_relaxed` 指定。我们来看一个例子: - - ```cpp - std::atomic counter = {0}; - std::vector vt; - for (int i = 0; i < 100; ++i) { - vt.emplace_back([&](){ - counter.fetch_add(1, std::memory_order_relaxed); - }); - } - - for (auto& t : vt) { - t.join(); - } - std::cout << "current counter:" << counter << std::endl; - ``` - -2. 释放/消费模型:在此模型中,我们开始限制进程间的操作顺序,如果某个线程需要修改某个值,但另一个线程会对该值的某次操作产生依赖,即后者依赖前者。具体而言,线程 A 完成了三次对 `x` 的写操作,线程 `B` 仅依赖其中第三次 `x` 的写操作,与 `x` 的前两次写行为无关,则当 `A` 主动 `x.release()` 时候(即使用 `std::memory_order_release`),选项 `std::memory_order_consume` 能够确保 `B` 在调用 `x.load()` 时候观察到 `A` 中第三次对 `x` 的写操作。我们来看一个例子: - - ```cpp - // 初始化为 nullptr 防止 consumer 线程从野指针进行读取 - std::atomic ptr(nullptr); - int v; - std::thread producer([&]() { - int* p = new int(42); - v = 1024; - ptr.store(p, std::memory_order_release); - }); - std::thread consumer([&]() { - int* p; - while(!(p = ptr.load(std::memory_order_consume))); - - std::cout << "p: " << *p << std::endl; - std::cout << "v: " << v << std::endl; - }); - producer.join(); - consumer.join(); - ``` - -3. 释放/获取模型:在此模型下,我们可以进一步加紧对不同线程间原子操作的顺序的限制,在释放 `std::memory_order_release` 和获取 `std::memory_order_acquire` 之间规定时序,即发生在释放(release)操作之前的**所有**写操作,对其他线程的任何获取(acquire)操作都是可见的,亦即发生顺序(happens-before)。 - - 可以看到,`std::memory_order_release` 确保了它之前的写操作不会发生在释放操作之后,是一个向后的屏障(backward),而 `std::memory_order_acquire` 确保了它之前的写行为不会发生在该获取操作之后,是一个向前的屏障(forward)。对于选项 `std::memory_order_acq_rel` 而言,则结合了这两者的特点,唯一确定了一个内存屏障,使得当前线程对内存的读写不会被重排并越过此操作的前后: - - 我们来看一个例子: - - ```cpp - std::vector v; - std::atomic flag = {0}; - std::thread release([&]() { - v.push_back(42); - flag.store(1, std::memory_order_release); - }); - std::thread acqrel([&]() { - int expected = 1; // must before compare_exchange_strong - while(!flag.compare_exchange_strong(expected, 2, std::memory_order_acq_rel)) - expected = 1; // must after compare_exchange_strong - // flag has changed to 2 - }); - std::thread acquire([&]() { - while(flag.load(std::memory_order_acquire) < 2); - - std::cout << v.at(0) << std::endl; // must be 42 - }); - release.join(); - acqrel.join(); - acquire.join(); - ``` - - 在此例中我们使用了 `compare_exchange_strong` 比较交换原语(Compare-and-swap primitive),它有一个更弱的版本,即 `compare_exchange_weak`,它允许即便交换成功,也仍然返回 `false` 失败。其原因是因为在某些平台上虚假故障导致的,具体而言,当 CPU 进行上下文切换时,另一线程加载同一地址产生的不一致。除此之外,`compare_exchange_strong` 的性能可能稍差于 `compare_exchange_weak`,但大部分情况下,鉴于其使用的复杂度而言,`compare_exchange_weak` 应该被有限考虑。 - -4. 顺序一致模型:在此模型下,原子操作满足顺序一致性,进而可能对性能产生损耗。可显式的通过 `std::memory_order_seq_cst` 进行指定。最后来看一个例子: - - ```cpp - std::atomic counter = {0}; - std::vector vt; - for (int i = 0; i < 100; ++i) { - vt.emplace_back([&](){ - counter.fetch_add(1, std::memory_order_seq_cst); - }); - } - - for (auto& t : vt) { - t.join(); - } - std::cout << "current counter:" << counter << std::endl; - ``` - - 这个例子与第一个宽松模型的例子本质上没有区别,仅仅只是将原子操作的内存顺序修改为了 `memory_order_seq_cst`,有兴趣的读者可以自行编写程序测量这两种不同内存顺序导致的性能差异。 - -## 总结 - -C++11 语言层提供了并发编程的相关支持,本节简单的介绍了 `std::thread`, `std::mutex`, `std::future` 这些并发编程中不可回避的重要工具。 -除此之外,我们还介绍了 C++11 最重要的几个特性之一的『内存模型』, -它们为 C++ 在标准化高性能计算中提供了重要的基础。 - -## 习题 - -1. 请编写一个简单的线程池,提供如下功能: - - ```cpp - ThreadPool p(4); // 指定四个工作线程 - - // 将任务在池中入队,并返回一个 std::future - auto f = pool.enqueue([](int life) { - return meaning; - }, 42); - - // 从 future 中获得执行结果 - std::cout << f.get() << std::endl; - ``` - -2. 请使用 `std::atomic` 实现一个互斥锁。 - -[返回目录](./toc.md) | [上一章](./06-regex.md) | [下一章 文件系统](./08-filesystem.md) - -## 进一步阅读的参考资料 - -- [C++ 并发编程\(中文版\)](https://book.douban.com/subject/26386925/) -- [线程支持库文档](https://en.cppreference.com/w/cpp/thread) -- Herlihy, M. P., & Wing, J. M. (1990). Linearizability: a correctness condition for concurrent objects. ACM Transactions on Programming Languages and Systems, 12(3), 463–492. https://doi.org/10.1145/78969.78972 - -## 许可 - -知识共享许可协议 - -本教程由[欧长坤](https://github.com/changkun)撰写,采用[知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-nd/4.0/)许可。项目中代码使用 MIT 协议开源,参见[许可](../../LICENSE)。 diff --git a/docs/book/zh-cn/08-filesystem.md b/docs/book/zh-cn/08-filesystem.md deleted file mode 100644 index be60a5a..0000000 --- a/docs/book/zh-cn/08-filesystem.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: 第 8 章 文件系统 -type: book-zh-cn -order: 8 ---- - -# 第 8 章 文件系统 - -[TOC] - -文件系统库提供了文件系统、路径、常规文件、目录等等相关组件进行操作的相关功能。和正则表达式库类似,他也是最先由 boost 发起,并最终被合并为 C++ 标准的众多库之一。 - -## 8.1 文档与链接 - -TODO: - -## 8.2 std::filesystem - -TODO: - -[返回目录](./toc.md) | [上一章](./07-thread.md) | [下一章 其他杂项](./09-others.md) - -## 许可 - -知识共享许可协议 - -本教程由[欧长坤](https://github.com/changkun)撰写,采用[知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-nd/4.0/)许可。项目中代码使用 MIT 协议开源,参见[许可](../../LICENSE)。 diff --git a/docs/book/zh-cn/09-others.md b/docs/book/zh-cn/09-others.md deleted file mode 100644 index 8dc1861..0000000 --- a/docs/book/zh-cn/09-others.md +++ /dev/null @@ -1,192 +0,0 @@ ---- -title: 第 9 章 其他杂项 -type: book-zh-cn -order: 9 ---- - -# 第 9 章 其他杂项 - -[TOC] - -## 9.1 新类型 - -### `long long int` - -`long long int` 并不是 C++11 最先引入的,其实早在 C99, -`long long int` 就已经被纳入 C 标准中,所以大部分的编译器早已支持。 -C++11 的工作则是正式把它纳入标准库, -规定了一个 `long long int` 类型至少具备 64 位的比特数。 - -## 9.2 noexcept 的修饰和操作 - -C++ 相比于 C 的一大优势就在于 C++ 本身就定义了一套完整的异常处理机制。 -然而在 C++11 之前,几乎没有人去使用在函数名后书写异常声明表达式, -从 C++11 开始,这套机制被弃用,所以我们不去讨论也不去介绍以前这套机制是如何工作如何使用, -你更不应该主动去了解它。 - -C++11 将异常的声明简化为以下两种情况: - -1. 函数可能抛出任何异常 -2. 函数不能抛出任何异常 - -并使用 `noexcept` 对这两种行为进行限制,例如: - -```cpp -void may_throw(); // 可能抛出异常 -void no_throw() noexcept; // 不可能抛出异常 -``` - -使用 `noexcept` 修饰过的函数如果抛出异常,编译器会使用 `std::terminate()` 来立即终止程序运行。 - -`noexcept` 还能够做操作符,用于操作一个表达式,当表达式无异常时,返回 `true`,否则返回 `false`。 - -```cpp -#include -void may_throw() { - throw true; -} -auto non_block_throw = []{ - may_throw(); -}; -void no_throw() noexcept { - return; -} - -auto block_throw = []() noexcept { - no_throw(); -}; -int main() -{ - std::cout << std::boolalpha - << "may_throw() noexcept? " << noexcept(may_throw()) << std::endl - << "no_throw() noexcept? " << noexcept(no_throw()) << std::endl - << "lmay_throw() noexcept? " << noexcept(non_block_throw()) << std::endl - << "lno_throw() noexcept? " << noexcept(block_throw()) << std::endl; - return 0; -} -``` - -`noexcept` 修饰完一个函数之后能够起到封锁异常扩散的功效,如果内部产生异常,外部也不会触发。例如: - -```cpp -try { - may_throw(); -} catch (...) { - std::cout << "捕获异常, 来自 may_throw()" << std::endl; -} -try { - non_block_throw(); -} catch (...) { - std::cout << "捕获异常, 来自 non_block_throw()" << std::endl; -} -try { - block_throw(); -} catch (...) { - std::cout << "捕获异常, 来自 block_throw()" << std::endl; -} -``` - -最终输出为: - -``` -捕获异常, 来自 may_throw() -捕获异常, 来自 non_block_throw() -``` - -## 9.3 字面量 - -### 原始字符串字面量 - -传统 C++ 里面要编写一个充满特殊字符的字符串其实是非常痛苦的一件事情, -比如一个包含 HTML 本体的字符串需要添加大量的转义符, -例如一个Windows 上的文件路径经常会:`C:\\File\\To\\Path`。 - -C++11 提供了原始字符串字面量的写法,可以在一个字符串前方使用 `R` 来修饰这个字符串, -同时,将原始字符串使用括号包裹,例如: - -```cpp -#include -#include - -int main() { - std::string str = R"(C:\File\To\Path)"; - std::cout << str << std::endl; - return 0; -} -``` - -### 自定义字面量 - -C++11 引进了自定义字面量的能力,通过重载双引号后缀运算符实现: - -```cpp -// 字符串字面量自定义必须设置如下的参数列表 -std::string operator"" _wow1(const char *wow1, size_t len) { - return std::string(wow1)+"woooooooooow, amazing"; -} - -std::string operator"" _wow2 (unsigned long long i) { - return std::to_string(i)+"woooooooooow, amazing"; -} - -int main() { - auto str = "abc"_wow1; - auto num = 1_wow2; - std::cout << str << std::endl; - std::cout << num << std::endl; - return 0; -} -``` - -自定义字面量支持四种字面量: - -1. 整型字面量:重载时必须使用 `unsigned long long`、`const char *`、模板字面量算符参数,在上面的代码中使用的是前者; -2. 浮点型字面量:重载时必须使用 `long double`、`const char *`、模板字面量算符; -3. 字符串字面量:必须使用 `(const char *, size_t)` 形式的参数表; -4. 字符字面量:参数只能是 `char`, `wchar_t`, `char16_t`, `char32_t` 这几种类型。 - -## 9.4 内存对齐 - -C++ 11 引入了两个新的关键字 `alignof` 和 `alignas` 来支持对内存对齐进行控制。 -`alignof` 关键字能够获得一个与平台相关的 `std::size_t` 类型的值,用于查询该平台的对齐方式。 -当然我们有时候并不满足于此,甚至希望自定定义结构的对齐方式,同样,C++ 11 还引入了 `alignas` -来重新修饰某个结构的对齐方式。我们来看两个例子: - -```cpp -#include - -struct Storage { - char a; - int b; - double c; - long long d; -}; - -struct alignas(std::max_align_t) AlignasStorage { - char a; - int b; - double c; - long long d; -}; - -int main() { - std::cout << alignof(Storage) << std::endl; - std::cout << alignof(AlignasStorage) << std::endl; - return 0; -} -``` - -其中 `std::max_align_t` 要求每个标量类型的对齐方式严格一样,因此它几乎是最大标量没有差异, -进而大部分平台上得到的结果为 `long double`,因此我们这里得到的 `AlignasStorage` 的对齐要求是 8 或 16。 - -## 总结 - -本节介绍的几个特性是从仍未介绍的现代 C++ 新特性里使用频次较靠前的特性了,`noexcept` 是最为重要的特性,它的一个功能在于能够阻止异常的扩散传播,有效的让编译器最大限度的优化我们的代码。 - -[返回目录](./toc.md) | [上一章](./08-filesystem.md) | [下一章 展望:C++20 简介](./10-cpp20.md) - -## 许可 - -知识共享许可协议 - -本教程由[欧长坤](https://github.com/changkun)撰写,采用[知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-nd/4.0/)许可。项目中代码使用 MIT 协议开源,参见[许可](../../LICENSE)。 diff --git a/docs/book/zh-cn/10-cpp20.md b/docs/book/zh-cn/10-cpp20.md deleted file mode 100644 index c1a4f6a..0000000 --- a/docs/book/zh-cn/10-cpp20.md +++ /dev/null @@ -1,99 +0,0 @@ ---- -title: 第 10 章 展望:C++20 简介 -type: book-zh-cn -order: 10 ---- - -# 第 10 章 展望:C++20 简介 - -[TOC] - -C++20 如同 C++11 一样,似乎能够成为一个振奋人心的更新。例如,早在 C++11 时期就跃跃欲试呼声极高却最终落选的 `Concept`,如今已经箭在弦上。 -C++ 组委会在讨论投票最终确定 C++20 有很多提案,诸如 **Concepts**/**Module**/**Coroutine**/**Ranges**/ 等等。 -本章我们就来一览 C++20 即将引入的那些重要特性。 - -## 概念与约束 - -概念(Concepts)是对 C++ 模板编程的进一步增强扩展。简单来说,概念是一种编译期的特性, -它能够让编译器在编译期时对模板参数进行判断,从而大幅度增强我们在 C++ 中模板编程的体验。 -使用模板进行编程时候我们经常会遇到各种令人发指的错误, -这是因为到目前为止我们始终不能够对模板参数进行检查与限制。 -举例而言,下面简单的两行代码会造成大量的几乎不可读的编译错误: - -```cpp -#include -#include -int main() { - std::list l = {1, 2, 3}; - std::sort(l.begin(), l.end()); - return 0; -} -``` - -而这段代码出现错误的根本原因在于,`std::sort` 对排序容器必须提供随机迭代器,否则就不能使用,而我们知道 `std::list` 是不支持随机访问的。 -用概念的语言来说就是:`std::list` 中的迭代器不满足 `std::sort` 中随机迭代器这个概念的约束(Constraint)。 -在引入概念后,我们就可以这样对模板参数进行约束: - -```cpp -template -requires Sortable // Sortable 是一个概念 -void sort(T& c); -``` - -缩写为: - -```cpp -template // T 是一个 Sortable 的类型名 -void sort(T& c) -``` - -甚至于直接将其作为类型来使用: - -```cpp -void sort(Sortable& c); // c 是一个 Sortable 类型的对象 -``` - -我们现在来看一个实际的例子。 - -TODO: https://godbolt.org/z/9liFPD - -## 模块 - -TODO: - -## 合约 - -TODO: - -## 范围 - -TODO: - -## 协程 - -TODO: - - -## 事务内存 - -TODO: - -## 总结 - -总的来说,终于在 C++20 中看到 Concepts/Ranges/Modules 这些令人兴奋的特性, -这对于一门已经三十多岁『高龄』的编程语言,依然是充满魅力的。 - -[返回目录](./toc.md) | [上一章](./09-others.md) | [下一章 进一步阅读的学习材料](./appendix1.md) - - -## 进一步阅读的参考资料 - -- [Why Concepts didn't make C++17?](http://honermann.net/blog/2016/03/06/why-concepts-didnt-make-cxx17/) -- [C++11/14/17/20 编译器支持情况](https://en.cppreference.com/w/cpp/compiler_support) -- [C++ 历史](https://en.cppreference.com/w/cpp/language/history) - -## 许可 - -知识共享许可协议 - -本教程由[欧长坤](https://github.com/changkun)撰写,采用[知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-nd/4.0/)许可。项目中代码使用 MIT 协议开源,参见[许可](../../LICENSE)。 \ No newline at end of file diff --git a/docs/book/zh-cn/appendix1.md b/docs/book/zh-cn/appendix1.md deleted file mode 100644 index e0a32d6..0000000 --- a/docs/book/zh-cn/appendix1.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: 附录 1:进一步阅读的学习材料 -type: book-zh-cn -order: 11 ---- - -# 附录 1:进一步阅读的学习材料 - -首先,恭喜 🎉 你阅读完本书!笔者希望本书有提起你对现代 C++ 的兴趣。 - -正如本书引言部分提到的,本书只是一本带你快速领略现代 C++ 11/14/17/20 新特性的读物,而非进阶学习实践 C++『黑魔法』的内容。笔者当然也想到了这个需求,只是这样的内容非常艰深,鲜有受众。在此,笔者列出一些能够帮助你在此书基础之上进一步学习现代 C++ 的资料,希望能够祝你一臂之力: - -- [C++ 参考](https://en.cppreference.com/w) -- [CppCon YouTube 频道](https://www.youtube.com/user/CppCon/videos) -- [Ulrich Drepper. 每位程序员都需要知道的内存知识. 2007](https://people.freebsd.org/~lstewart/articles/cpumemory.pdf) -- 待补充 - -[返回目录](./toc.md) | [上一章](./10-cpp20.md) | [下一章 现代 C++ 的最佳实践](./appendix2.md) - -## 许可 - -知识共享许可协议 - -本书系[欧长坤](https://github.com/changkun)著,采用[知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-nd/4.0/)许可。项目中代码使用 MIT 协议开源,参见[许可](../../LICENSE)。 \ No newline at end of file diff --git a/docs/book/zh-cn/appendix2.md b/docs/book/zh-cn/appendix2.md deleted file mode 100644 index bf8e907..0000000 --- a/docs/book/zh-cn/appendix2.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -title: 附录 2:现代 C++ 的最佳实践 -type: book-zh-cn -order: 12 ---- - -# 附录 2:现代 C++ 的最佳实践 - -这篇附录我们来简单谈一谈现代 C++ 的最佳实践。总的来说,笔者关于 C++ 的最佳实践相关的思考主要吸收自[《Effective Modern C++》](https://www.amazon.cn/dp/B016OFO492/ref=sr_1_3?ie=UTF8&qid=1525613457&sr=8-3&keywords=Effective+C%2B%2B)和 [《C++ 风格指南》](http://zh-google-styleguide.readthedocs.io/en/latest/google-cpp-styleguide/contents/)。在这篇附录里将简单讨论、并使用实际例子来阐明的方法,介绍一些笔者**个人的**、**不是随处可见的**、**非常识性**的最佳实践,并如何保证代码的整体质量。 - -## 常用工具 - -TODO: - -## 代码风格 - -TODO: - -## 整体性能 - -TODO: - -## 代码安全 - -TODO: - -## 可维护性 - -TODO: - -## 可移植性 - -TODO: - -[返回目录](./toc.md) | [上一章](./11-appendix1.md) - -## 许可 - -知识共享许可协议 - -本书系[欧长坤](https://github.com/changkun)著,采用[知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-nd/4.0/)许可。项目中代码使用 MIT 协议开源,参见[许可](../../LICENSE)。 \ No newline at end of file diff --git a/docs/book/zh-cn/appendix3.md b/docs/book/zh-cn/appendix3.md deleted file mode 100644 index 4108ea1..0000000 --- a/docs/book/zh-cn/appendix3.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -title: 附录 3:现代 C++ 特性索引 -type: book-zh-cn -order: 13 ---- - -# 附录 3:现代 C++ 特性索引表 - -| 特性概述 | 首次出现章节 | 首次出现版本 | -|:--------|:-----------|:----------| -| 概念(Concept) | 第二章 | C++20 | -| 模板参数推导 | | C++17 | -| 非类型模板参数的 auto 推导 | | C++17 | -| 折叠表达式 | | C++17 | -| 花括号初始化列表的 auto 推导 | | C++17 | -| constexpr lambda | | C++17 | -| lambda 表达式的 this 值捕获 | | C++17 | -| 内联变量 | | C++17 | -| 嵌套 namespace | | C++17 | -| 结构化绑定 | | C++17 | -| 带初始值的 switch 语句 | | C++17 | -| constexpr if | | C++17 | -| UTF-8 字符字面量 | | C++17 | -| 枚举的直接初始化列表 | | C++17 | -| attributes | | C++17 | -| `std::variant` | | C++17 | -| `std::optional` | | C++17 | -| `std::any` | | C++17 | -| `std::string_view` | | C++17 | -| `std::invoke` | | C++17 | -| `std::apply` | | C++17 | -| `std::filesystem` | | C++17 | -| `std::byte` | | C++17 | -| map 和 set 的拼接 | | C++17 | -| 并行算法 | | C++17 | - diff --git a/docs/book/zh-cn/toc.md b/docs/book/zh-cn/toc.md deleted file mode 100644 index fa2cbd9..0000000 --- a/docs/book/zh-cn/toc.md +++ /dev/null @@ -1,114 +0,0 @@ -# 现代 C++ 教程:高速上手 C++ 11/14/17/20 - -## 目录 - -- [**序言**](./00-preface.md) -- [**第 1 章 迈向现代 C++**](./01-intro.md) - + 1.1 被弃用的特性 - + 1.2 与 C 的兼容性 - + 进一步阅读的参考文献 -- [**第 2 章 语言可用性的强化**](./02-usability.md) - + 2.1 常量 - - nullptr - - constexpr - + 2.2 变量及其初始化 - - if/switch 变量声明强化 - - 初始化列表 - - 结构化绑定 - + 2.3 类型推导 - - auto - - decltype - - 尾返回类型 - - decltype(auto) - + 2.4 控制流 - - if constexpr - - 区间 for 迭代 - + 2.5 模板 - - 外部模板 - - 尖括号 ">" - - 类型别名模板 - - 默认模板参数 - - 变长参数模板 - - 折叠表达式 - - 非类型模板参数推导 - + 2.6 面向对象 - - 委托构造 - - 继承构造 - - 显式虚函数重载 - - override - - final - - 显式禁用默认函数 - - 强类型枚举 -- [**第 3 章 语言运行期的强化**](./03-runtime.md) - + 3.1 lambda 表达式 - + 基础 - + 泛型 - + 3.2 函数对象包装器 - + std::function - + std::bind 和 std::placeholder - + 3.3 右值引用 - + 左值、右值的纯右值、将亡值、右值 - + 右值引用和左值引用 - + 移动语义 - + 完美转发 -- [**第 4 章 标准库: 容器**](./04-containers.md) - + 4.1 线性容器 - + `std::array` - + `std::forward_list` - + 4.2 无序容器 - + `std::unordered_set` - + `std::unordered_map` - + 4.3 元组 `std::tuple` - + 基本操作 - + 运行期索引 `std::variant` - + 合并与迭代 -- [**第 5 章 标准库: 指针**](./05-pointers.md) - + 5.1 RAII 与引用计数 - + 5.2 `std::shared_ptr` - + 5.3 `std::unique_ptr` - + 5.4 `std::weak_ptr` -- [**第 6 章 标准库: 正则表达式**](./06-regex.md) - + 6.1 正则表达式简介 - + 普通字符 - + 特殊字符 - + 限定符 - + 6.2 `std::regex` 及其相关 - + `std::regex` - + `std::regex_match` - + `std::match_results` -- [**第 7 章 并行与并发**](./07-thread.md) - + 7.1 并行基础 - + 7.2 互斥量与临界区 - + 7.3 期物 - + 7.4 条件变量 - + 7.5 原子操作与内存模型 - + 原子操作 - + 一致性模型 - + 内存顺序 -- [**第 8 章 文件系统**](./08-filesystem.md) - + 8.1 文档与链接 - + 8.2 `std::filesystem` -- [**第 9 章 其他杂项**](./09-others.md) - + 9.1 新类型 - + `long long int` - + 9.2 `noexcept` 的修饰和操作 - + 9.3 字面量 - + 原始字符串字面量 - + 自定义字面量 - + 9.4 内存对齐 -- [**第 10 章 展望: C++20 简介**](./10-cpp20.md) - + 10.1 Concept - + 10.2 Range - + 10.3 Module - + 10.4 Coroutine - + 10.5 事务内存 -- [**附录 1:进一步阅读的学习材料**](./appendix1.md) -- [**附录 2:现代 C++ 的最佳实践**](./appendix2.md) - -返回目录 | 上一章 | [下一章:序言](./00-preface.md) - -## 许可 - -知识共享许可协议 - -本书系[欧长坤](https://github.com/changkun)著,采用[知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-nd/4.0/)许可。项目中代码使用 MIT 协议开源,参见[许可](../../LICENSE)。 diff --git a/docs/code/1/1.1.c.and.cpp b/docs/code/1/1.1.c.and.cpp deleted file mode 100644 index 825c2e8..0000000 --- a/docs/code/1/1.1.c.and.cpp +++ /dev/null @@ -1,21 +0,0 @@ -// -// 1.1.cpp -// -// chapter 1 introduction -// modern cpp tutorial -// -// created by changkun at changkun.de -// https://github.com/changkun/modern-cpp-tutorial -// - -#include "foo.h" -#include -#include - -int main() { - // use lambda expression - [out = std::ref(std::cout << "Result from C code: " << add(1, 2))](){ - out.get() << ".\n"; - }(); - return 0; -} diff --git a/docs/code/1/Makefile b/docs/code/1/Makefile deleted file mode 100644 index 09cde53..0000000 --- a/docs/code/1/Makefile +++ /dev/null @@ -1,26 +0,0 @@ -# -# 1.1.cpp -# -# chapter 1 introduction -# modern cpp tutorial -# -# created by changkun at changkun.de -# https://github.com/changkun/modern-cpp-tutorial -# - -C = gcc -CXX = clang++ - -SOURCE_C = foo.c -OBJECTS_C = foo.o - -SOURCE_CXX = 1.1.c.and.cpp - -TARGET = 1.1.out -LDFLAGS_COMMON = -std=c++2a - -all: - $(C) -c $(SOURCE_C) - $(CXX) $(SOURCE_CXX) $(OBJECTS_C) $(LDFLAGS_COMMON) -o $(TARGET) -clean: - rm -rf *.o $(TARGET) diff --git a/docs/code/1/foo.c b/docs/code/1/foo.c deleted file mode 100644 index 14b5050..0000000 --- a/docs/code/1/foo.c +++ /dev/null @@ -1,16 +0,0 @@ -// -// foo.c -// -// chapter 1 introduction -// modern cpp tutorial -// -// created by changkun at changkun.de -// https://github.com/changkun/modern-cpp-tutorial -// - -#include "foo.h" - -// C code -int add(int x, int y) { - return x+y; -} diff --git a/docs/code/1/foo.h b/docs/code/1/foo.h deleted file mode 100644 index 9ce74b8..0000000 --- a/docs/code/1/foo.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// foo.h -// -// chapter 1 introduction -// modern cpp tutorial -// -// created by changkun at changkun.de -// https://github.com/changkun/modern-cpp-tutorial -// - -#ifdef __cplusplus -extern "C" { -#endif - -int add(int x, int y); - -#ifdef __cplusplus -} -#endif diff --git a/docs/code/10/10.1.without.concepts.cpp b/docs/code/10/10.1.without.concepts.cpp deleted file mode 100644 index b708a05..0000000 --- a/docs/code/10/10.1.without.concepts.cpp +++ /dev/null @@ -1,18 +0,0 @@ -// -// 10.1.without.concepts.cpp -// chapter 10 cpp20 -// modern c++ tutorial -// -// created by changkun at changkun.de -// https://github.com/changkun/modern-cpp-tutorial -// - - -#include -#include - -int main() { - std::list l = {1, 2, 3}; - // std::sort(l.begin(), l.end()); // tons of compile error - return 0; -} \ No newline at end of file diff --git a/docs/code/10/10.2.concepts.cpp b/docs/code/10/10.2.concepts.cpp deleted file mode 100644 index cb7a1a9..0000000 --- a/docs/code/10/10.2.concepts.cpp +++ /dev/null @@ -1,76 +0,0 @@ -// -// 10.2.concepts.cpp -// chapter 10 cpp20 -// modern c++ tutorial -// -// created by changkun at changkun.de -// https://github.com/changkun/modern-cpp-tutorial -// - - -#include -#include -#include -#include - -using namespace std; - - template -concept bool Stringable = requires(T a){ - {a.to_string()} -> string; -}; - - template -concept bool HasStringFunc = requires(T a){ - { to_string(a) } -> string; -}; - -struct Person { - double height, weight; - Person(double a, double b) : height(a), weight(b) {} - string to_string(){ - return "weight: "+ std::to_string(weight) + ", height: "+ std::to_string(height); - } -}; - - -namespace std { - -string to_string(list l){ - string s = ""; - for(int a : l ){ - s+=(" " + to_string(a) + " "); - } - return s; -} - -} - -string to_string(std::vector v){ - string s = ""; - for(int a : v ){ - s += (" " + to_string(a) + " "); - } - return s; -} - - - void print(Stringable a){ - std::cout << a.to_string() << std::endl; -} - - void print(HasStringFunc a){ - std::cout << to_string(a) << std::endl; -} - - int main() { - std::list l {1, 2, 3}; - Person p(57, 170.0); - std::vector v { 34, 23, 34, 56, 78}; - - print(p); // uses concept Stringable - print(l); - print(3); // uses concept HasStringFunc - // print(v); // error - return 0; -} \ No newline at end of file diff --git a/docs/code/10/Makefile b/docs/code/10/Makefile deleted file mode 100644 index 642b9bc..0000000 --- a/docs/code/10/Makefile +++ /dev/null @@ -1,14 +0,0 @@ -# -# modern cpp tutorial -# -# created by changkun at changkun.de -# https://github.com/changkun/modern-cpp-tutorial -# - -all: $(patsubst %.cpp, %.out, $(wildcard *.cpp)) - -%.out: %.cpp Makefile - clang++ $< -o $@ -std=c++2a -pedantic - -clean: - rm *.out \ No newline at end of file diff --git a/docs/code/2/2.01.nullptr.cpp b/docs/code/2/2.01.nullptr.cpp deleted file mode 100644 index 8fc1763..0000000 --- a/docs/code/2/2.01.nullptr.cpp +++ /dev/null @@ -1,35 +0,0 @@ -// -// 2.1.nullptr.cpp -// chapter 2 language usability -// modern cpp tutorial -// -// created by changkun at changkun.de -// https://github.com/changkun/modern-cpp-tutorial -// - -#include -#include - -void foo(char *); -void foo(int); - -int main() { - if (std::is_same::value) - std::cout << "NULL == 0" << std::endl; - if (std::is_same::value) - std::cout << "NULL == (void *)0" << std::endl; - if (std::is_same::value) - std::cout << "NULL == nullptr" << std::endl; - - foo(0); // will call foo(int) - // foo(NULL); // doesn't compile - foo(nullptr); // will call foo(char*) - return 0; -} - -void foo(char *) { - std::cout << "foo(char*) is called" << std::endl; -} -void foo(int i) { - std::cout << "foo(int) is called" << std::endl; -} diff --git a/docs/code/2/2.02.constexpr.cpp b/docs/code/2/2.02.constexpr.cpp deleted file mode 100644 index 7934ca6..0000000 --- a/docs/code/2/2.02.constexpr.cpp +++ /dev/null @@ -1,53 +0,0 @@ -// -// 2.2.constexpr.cpp -// chapter 2 language usability -// modern cpp tutorial -// -// created by changkun at changkun.de -// https://github.com/changkun/modern-cpp-tutorial -// - -#include -#define LEN 10 - -int len_foo() { - int i = 2; - return i; -} -constexpr int len_foo_constexpr() { - return 5; -} - -// error in c++11 -// constexpr int fibonacci(const int n) { -// if(n == 1) return 1; -// if(n == 2) return 1; -// return fibonacci(n-1) + fibonacci(n-2); -// } - -// ok -constexpr int fibonacci(const int n) { - return n == 1 || n == 2 ? 1 : fibonacci(n-1) + fibonacci(n-2); -} - - -int main() { - char arr_1[10]; // legal - char arr_2[LEN]; // legal - - int len = 10; - // char arr_3[len]; // illegal - - const int len_2 = len + 1; - constexpr int len_2_constexpr = 1 + 2 + 3; - // char arr_4[len_2]; // illegal, but ok for most of the compilers - char arr_4[len_2_constexpr]; // legal - - // char arr_5[len_foo()+5]; // illegal - char arr_6[len_foo_constexpr() + 1]; // legal - - // 1, 1, 2, 3, 5, 8, 13, 21, 34, 55 - std::cout << fibonacci(10) << std::endl; - - return 0; -} diff --git a/docs/code/2/2.03.if.switch.cpp b/docs/code/2/2.03.if.switch.cpp deleted file mode 100644 index e596de1..0000000 --- a/docs/code/2/2.03.if.switch.cpp +++ /dev/null @@ -1,31 +0,0 @@ -// -// 2.3.if.switch.cpp -// chapter 2 language usability -// modern cpp tutorial -// -// created by changkun at changkun.de -// https://github.com/changkun/modern-cpp-tutorial -// - -#include -#include -#include - -int main() { - std::vector vec = {1, 2, 3, 4}; - - // after c++17, can be simplefied by using `auto` - const std::vector::iterator itr = std::find(vec.begin(), vec.end(), 2); - if (itr != vec.end()) { - *itr = 3; - } - - if (const std::vector::iterator itr = std::find(vec.begin(), vec.end(), 3); - itr != vec.end()) { - *itr = 4; - } - - // should output: 1, 4, 3, 4. can be simplefied using `auto` - for (std::vector::iterator element = vec.begin(); element != vec.end(); ++element) - std::cout << *element << std::endl; -} \ No newline at end of file diff --git a/docs/code/2/2.04.initializer.list.cpp b/docs/code/2/2.04.initializer.list.cpp deleted file mode 100644 index 8caeeb9..0000000 --- a/docs/code/2/2.04.initializer.list.cpp +++ /dev/null @@ -1,60 +0,0 @@ -// -// 2.4.initializer.list.cpp -// chapter 2 language usability -// modern cpp tutorial -// -// created by changkun at changkun.de -// https://github.com/changkun/modern-cpp-tutorial -// - -#include -#include -#include - -class Foo { -public: - int value_a; - int value_b; - Foo(int a, int b) : value_a(a), value_b(b) {} -}; - -class MagicFoo { -public: - std::vector vec; - MagicFoo(std::initializer_list list) { - for (std::initializer_list::iterator it = list.begin(); it != list.end(); ++it) { - vec.push_back(*it); - } - } - void foo(std::initializer_list list) { - for (std::initializer_list::iterator it = list.begin(); it != list.end(); ++it) { - vec.push_back(*it); - } - } -}; - -int main() { - // before C++11 - int arr[3] = {1, 2, 3}; - Foo foo(1, 2); - std::vector vec = {1, 2, 3, 4, 5}; - - // after C++11 - MagicFoo magicFoo = {1, 2, 3, 4, 5}; - magicFoo.foo({6,7,8,9}); - Foo foo2 {3, 4}; - - std::cout << "arr[0]: " << arr[0] << std::endl; - std::cout << "foo:" << foo.value_a << ", " << foo.value_b << std::endl; - std::cout << "vec: "; - for (std::vector::iterator it = vec.begin(); it != vec.end(); ++it) { - std::cout << *it << std::endl; - } - std::cout << "magicFoo: "; - for (std::vector::iterator it = magicFoo.vec.begin(); it != magicFoo.vec.end(); ++it) { - std::cout << *it << std::endl; - } - std::cout << "foo2: " << foo2.value_a << ", " << foo2.value_b << std::endl; - - return 0; -} \ No newline at end of file diff --git a/docs/code/2/2.05.structured.binding.cpp b/docs/code/2/2.05.structured.binding.cpp deleted file mode 100644 index 48963f3..0000000 --- a/docs/code/2/2.05.structured.binding.cpp +++ /dev/null @@ -1,22 +0,0 @@ -// -// 2.5.structured.binding.cpp -// chapter 2 language usability -// modern cpp tutorial -// -// created by changkun at changkun.de -// https://github.com/changkun/modern-cpp-tutorial -// - -#include -#include -#include - -std::tuple f() { - return std::make_tuple(1, 2.3, "456"); -} - -int main() { - auto [x, y, z] = f(); - std::cout << x << ", " << y << ", " << z << std::endl; - return 0; -} diff --git a/docs/code/2/2.06.auto.cpp b/docs/code/2/2.06.auto.cpp deleted file mode 100644 index 4922780..0000000 --- a/docs/code/2/2.06.auto.cpp +++ /dev/null @@ -1,43 +0,0 @@ -// -// 2.6.auto.cpp -// chapter 2 language usability -// modern cpp tutorial -// -// created by changkun at changkun.de -// https://github.com/changkun/modern-cpp-tutorial -// - -#include -#include -#include - -class MagicFoo { -public: - std::vector vec; - MagicFoo(std::initializer_list list) { - for (auto it = list.begin(); it != list.end(); ++it) { - vec.push_back(*it); - } - } -}; - -int add(auto x, auto y) { // Supported in C++20 - return x+y; -} - -int main() { - MagicFoo magicFoo = {1, 2, 3, 4, 5}; - std::cout << "magicFoo: "; - for (auto it = magicFoo.vec.begin(); it != magicFoo.vec.end(); ++it) { - std::cout << *it << ", "; - } - std::cout << std::endl; - - auto i = 5; // type int - auto j = 6; // type int - std::cout << add(i, j) << std::endl; - - auto arr = new auto(10); // type int* - // auto auto_arr2[10] = {arr}; // invalid - return 0; -} \ No newline at end of file diff --git a/docs/code/2/2.07.decltype.cpp b/docs/code/2/2.07.decltype.cpp deleted file mode 100644 index 7d48616..0000000 --- a/docs/code/2/2.07.decltype.cpp +++ /dev/null @@ -1,24 +0,0 @@ -// -// 2.7.decltype.cpp -// chapter 2 language usability -// modern cpp tutorial -// -// created by changkun at changkun.de -// https://github.com/changkun/modern-cpp-tutorial -// - -#include -#include - -int main() { - auto x = 1; - auto y = 2; - decltype(x+y) z = 3; - if (std::is_same::value) - std::cout << "type x == int" << std::endl; - if (std::is_same::value) - std::cout << "type z == float" << std::endl; - if (std::is_same::value) - std::cout << "type z == type x" << std::endl; - return 0; -} \ No newline at end of file diff --git a/docs/code/2/2.08.tail.return.type.cpp b/docs/code/2/2.08.tail.return.type.cpp deleted file mode 100644 index c05796a..0000000 --- a/docs/code/2/2.08.tail.return.type.cpp +++ /dev/null @@ -1,47 +0,0 @@ -// -// 2.8.tail.return.type.cpp -// chapter 2 language usability -// modern cpp tutorial -// -// created by changkun at changkun.de -// https://github.com/changkun/modern-cpp-tutorial -// - -#include -#include - -// before c++11 -template -R add(T x, U y) { - return x + y; -} -// after c++11 -template -auto add2(T x, U y) -> decltype(x+y){ - return x + y; -} -// after c++14 -template -auto add3(T x, U y){ - return x + y; -} - -int main() { - - // before c++11 - int z = add(1, 2); - std::cout << z << std::endl; - - // after c++11 - auto w = add2(1, 2.0); - if (std::is_same::value) { - std::cout << "w is double: "; - } - std::cout << w << std::endl; - - // after c++14 - auto q = add3(1.0, 2); - std::cout << "q: " << q << std::endl; - - return 0; -} \ No newline at end of file diff --git a/docs/code/2/2.09.decltype.auto.cpp b/docs/code/2/2.09.decltype.auto.cpp deleted file mode 100644 index d48586a..0000000 --- a/docs/code/2/2.09.decltype.auto.cpp +++ /dev/null @@ -1,22 +0,0 @@ -// -// 2.9.decltype.auto.cpp -// chapter 2 language usability -// modern cpp tutorial -// -// created by changkun at changkun.de -// https://github.com/changkun/modern-cpp-tutorial -// - -template -struct Int {}; - -constexpr auto iter(Int<0>) -> Int<0>; - -template -constexpr auto iter(Int) { - return iter(Int{}); -} - -int main() { - decltype(iter(Int<10>{})) a; -} \ No newline at end of file diff --git a/docs/code/2/2.10.if.constexpr.cpp b/docs/code/2/2.10.if.constexpr.cpp deleted file mode 100644 index 9042f2e..0000000 --- a/docs/code/2/2.10.if.constexpr.cpp +++ /dev/null @@ -1,32 +0,0 @@ -// -// 2.10.if.constexpr.cpp -// chapter 2 language usability -// modern cpp tutorial -// -// created by changkun at changkun.de -// https://github.com/changkun/modern-cpp-tutorial -// - -#include - -template -auto print_type_info(const T& t) { - if constexpr (std::is_integral::value) { - return t + 1; - } else { - return t + 0.001; - } -} - -// at compiling time -// int print_type_info(const int& t) { -// return t + 1; -// } -// double print_type_info(const double& t) { -// return t + 0.001; -// } - -int main() { - std::cout << print_type_info(5) << std::endl; - std::cout << print_type_info(3.14) << std::endl; -} \ No newline at end of file diff --git a/docs/code/2/2.11.for.loop.cpp b/docs/code/2/2.11.for.loop.cpp deleted file mode 100644 index a843c40..0000000 --- a/docs/code/2/2.11.for.loop.cpp +++ /dev/null @@ -1,24 +0,0 @@ -// -// 2.11.for.loop.cpp -// chapter 2 language usability -// modern cpp tutorial -// -// created by changkun at changkun.de -// https://github.com/changkun/modern-cpp-tutorial -// - -#include -#include -#include - -int main() { - std::vector vec = {1, 2, 3, 4}; - if (auto itr = std::find(vec.begin(), vec.end(), 3); itr != vec.end()) *itr = 4; - for (auto element : vec) - std::cout << element << std::endl; // read only - for (auto &element : vec) { - element += 1; // writeable - } - for (auto element : vec) - std::cout << element << std::endl; // read only -} \ No newline at end of file diff --git a/docs/code/2/2.12.external.template.cpp b/docs/code/2/2.12.external.template.cpp deleted file mode 100644 index ad31ff9..0000000 --- a/docs/code/2/2.12.external.template.cpp +++ /dev/null @@ -1,24 +0,0 @@ -// -// 2.12.external.template.cpp -// chapter 2 language usability -// modern cpp tutorial -// -// created by changkun at changkun.de -// https://github.com/changkun/modern-cpp-tutorial -// - -#include -#include - -template class std::vector; // forcely instantiation -extern template class std::vector; // external template for avoiding instantiation in this file - -template class MagicType { - bool magic = T; -}; - -int main() { - // the >> in template - std::vector> matrix; - std::vector2)>> magic; // legal, but not recommended -} \ No newline at end of file diff --git a/docs/code/2/2.13.alias.template.cpp b/docs/code/2/2.13.alias.template.cpp deleted file mode 100644 index a5355d4..0000000 --- a/docs/code/2/2.13.alias.template.cpp +++ /dev/null @@ -1,33 +0,0 @@ -// -// 2.13.alias.template.cpp -// chapter 2 language usability -// modern cpp tutorial -// -// created by changkun at changkun.de -// https://github.com/changkun/modern-cpp-tutorial -// - -#include -#include -#include - -template -class MagicType { -public: - T dark; - U magic; -}; - -// illegal -// template -// typedef MagicType, std::string> FakeDarkMagic; - -typedef int (*process)(void *); -using NewProcess = int(*)(void *); -template -using TrueDarkMagic = MagicType, std::string>; - -int main() { - // FakeDarkMagic me; - TrueDarkMagic you; -} \ No newline at end of file diff --git a/docs/code/2/2.14.default.template.param.cpp b/docs/code/2/2.14.default.template.param.cpp deleted file mode 100644 index 9351649..0000000 --- a/docs/code/2/2.14.default.template.param.cpp +++ /dev/null @@ -1,19 +0,0 @@ -// -// 2.4.default.template.param.cpp -// chapter 2 language usability -// modern cpp tutorial -// -// created by changkun at changkun.de -// https://github.com/changkun/modern-cpp-tutorial -// - -#include - -template -auto add(T x, U y) -> decltype(x+y) { - return x+y; -} - -int main() { - std::cout << add(1, 2) << std::endl; -} \ No newline at end of file diff --git a/docs/code/2/2.15.variadic.template.param.cpp b/docs/code/2/2.15.variadic.template.param.cpp deleted file mode 100644 index e43619f..0000000 --- a/docs/code/2/2.15.variadic.template.param.cpp +++ /dev/null @@ -1,57 +0,0 @@ -// -// 2.15.variadic.template.param.cpp -// chapter 2 language usability -// modern cpp tutorial -// -// created by changkun at changkun.de -// https://github.com/changkun/modern-cpp-tutorial -// - -#include -#include -#include - -// sizeof... -template -void magic(Ts... args) { - std::cout << sizeof...(args) << std::endl; -} - - -// 1. recursive parameter unpack -template -void printf1(T0 value) { - std::cout << value << std::endl; -} -template -void printf1(T value, Ts... args) { - std::cout << value << std::endl; - printf1(args...); -} - -// 2. variadic template parameter unfold -template -void printf2(T0 t0, T... t) { - std::cout << t0 << std::endl; - if constexpr (sizeof...(t) > 0) printf2(t...); -} - -// 3. parameter unpack using initializer_list -template -auto printf3(T value, Ts... args) { - std::cout << value << std::endl; - (void) std::initializer_list{([&args] { - std::cout << args << std::endl; - }(), value)...}; -} - -int main() { - magic(); - magic(1); - magic(1,""); - - printf1(1, 2, "123", 1.1); - printf2(1, 2.3, "abc"); - printf3(111, 123, "alpha", 1.2); - return 0; -} \ No newline at end of file diff --git a/docs/code/2/2.16.fold.expression.cpp b/docs/code/2/2.16.fold.expression.cpp deleted file mode 100644 index cdfbde5..0000000 --- a/docs/code/2/2.16.fold.expression.cpp +++ /dev/null @@ -1,17 +0,0 @@ -// -// 2.16.fold.expression.cpp -// chapter 2 language usability -// modern cpp tutorial -// -// created by changkun at changkun.de -// https://github.com/changkun/modern-cpp-tutorial -// - -#include -template -auto sum(T ... t) { - return (t + ...); -} -int main() { - std::cout << sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) << std::endl; -} \ No newline at end of file diff --git a/docs/code/2/2.18.non.type.template.auto.cpp b/docs/code/2/2.18.non.type.template.auto.cpp deleted file mode 100644 index b2a1d43..0000000 --- a/docs/code/2/2.18.non.type.template.auto.cpp +++ /dev/null @@ -1,19 +0,0 @@ -// -// 2.18.non.type.template.auto.cpp -// chapter 2 language usability -// modern cpp tutorial -// -// created by changkun at changkun.de -// https://github.com/changkun/modern-cpp-tutorial -// - -#include - -template void foo() { - std::cout << value << std::endl; - return; -} - -int main() { - foo<10>(); // value is deduced as type int -} \ No newline at end of file diff --git a/docs/code/2/2.19.delegate.constructor.cpp b/docs/code/2/2.19.delegate.constructor.cpp deleted file mode 100644 index d1748d8..0000000 --- a/docs/code/2/2.19.delegate.constructor.cpp +++ /dev/null @@ -1,66 +0,0 @@ -// -// 2.19.constructor.cpp -// chapter 2 language usability -// modern c++ tutorial -// -// created by changkun at changkun.de -// https://github.com/changkun/modern-cpp-tutorial -// - -#include -#include -class Base { -public: - std::string str; - int value; - Base() = delete; - Base(std::string s) { - str = s; - } - - // delegate constructor - Base(std::string s, int v) : Base(s) { - value = v; - } - - // final constructor - virtual void foo() final { - return; - } - virtual void foo(int v) { - value = v; - } -}; -class Subclass final : public Base { -public: - double floating; - Subclass() = delete; - - // inherit constructor - Subclass(double f, int v, std::string s) : Base(s, v) { - floating = f; - } - - // explifict constructor - virtual void foo(int v) override { - std::cout << v << std::endl; - value = v; - } -}; // legal final - -// class Subclass2 : Subclass { -// }; // illegal, Subclass has final -// class Subclass3 : Base { -// void foo(); // illegal, foo has final -// } - -int main() { - // Subclass oops; // illegal, default constructor has deleted - Subclass s(1.2, 3, "abc"); - - s.foo(1); - - std::cout << s.floating << std::endl; - std::cout << s.value << std::endl; - std::cout << s.str << std::endl; -} diff --git a/docs/code/2/2.20.strong.type.enum.cpp b/docs/code/2/2.20.strong.type.enum.cpp deleted file mode 100644 index ad7e73a..0000000 --- a/docs/code/2/2.20.strong.type.enum.cpp +++ /dev/null @@ -1,51 +0,0 @@ -// -// 2.20.strong.type.enum.cpp -// modern c++ tutorial -// -// created by changkun at changkun.de -// https://github.com/changkun/modern-cpp-tutorial -// - -#include -template -std::ostream& operator<<(typename std::enable_if::value, std::ostream>::type& stream, const T& e) -{ - return stream << static_cast::type>(e); -} - -// there will be compile error if all define value1 和 value2 -enum Left { - left_value1 = 1, - left_value2 -}; -enum Right { - right_value1 = 1, - right_value2 -}; - -enum class new_enum : unsigned int{ - value1, - value2, - value3 = 100, - value4 = 100 -}; - -int main() { - - if (Left::left_value1 == Right::right_value2) { - std::cout << "Left::value1 == Right::value2" << std::endl; - } - - // compile error - // if(new_enum::left_value1 == 1) { - // std::cout << "true!" << std::endl; - // } - if (new_enum::value3 == new_enum::value4) { - std::cout << "new_enum::value3 == new_enum::value4" << std::endl; - } - - std::cout << new_enum::value3 << std::endl; - - - return 0; -} diff --git a/docs/code/2/Makefile b/docs/code/2/Makefile deleted file mode 100644 index 642b9bc..0000000 --- a/docs/code/2/Makefile +++ /dev/null @@ -1,14 +0,0 @@ -# -# modern cpp tutorial -# -# created by changkun at changkun.de -# https://github.com/changkun/modern-cpp-tutorial -# - -all: $(patsubst %.cpp, %.out, $(wildcard *.cpp)) - -%.out: %.cpp Makefile - clang++ $< -o $@ -std=c++2a -pedantic - -clean: - rm *.out \ No newline at end of file diff --git a/docs/code/3/3.1.lambda.basic.cpp b/docs/code/3/3.1.lambda.basic.cpp deleted file mode 100644 index a0b8063..0000000 --- a/docs/code/3/3.1.lambda.basic.cpp +++ /dev/null @@ -1,62 +0,0 @@ -// -// 3.1.lambda.basic.cpp -// chapter 03 runtime enhancement -// modern c++ tutorial -// -// created by changkun at changkun.de -// https://github.com/changkun/modern-cpp-tutorial -// - - -#include -#include // std::make_unique -#include // std::move - -void lambda_value_capture() { - int value = 1; - auto copy_value = [value] { - return value; - }; - value = 100; - auto stored_value = copy_value(); - std::cout << "stored_value = " << stored_value << std::endl; - // At this moment, stored_value == 1, and value == 100. - // Because copy_value has copied when its was created. -} - -void lambda_reference_capture() { - int value = 1; - auto copy_value = [&value] { - return value; - }; - value = 100; - auto stored_value = copy_value(); - std::cout << "stored_value = " << stored_value << std::endl; - // At this moment, stored_value == 100, value == 100. - // Because copy_value stores reference -} - -void lambda_expression_capture() { - auto important = std::make_unique(1); - auto add = [v1 = 1, v2 = std::move(important)](int x, int y) -> int { - return x+y+v1+(*v2); - }; - std::cout << add(3,4) << std::endl; -} - -void lambda_generic() { - auto generic = [](auto x, auto y) { - return x+y; - }; - - std::cout << generic(1, 2) << std::endl; - std::cout << generic(1.1, 2.2) << std::endl; -} - -int main() { - lambda_value_capture(); - lambda_reference_capture(); - lambda_expression_capture(); - lambda_generic(); - return 0; -} diff --git a/docs/code/3/3.2.function.wrap.cpp b/docs/code/3/3.2.function.wrap.cpp deleted file mode 100644 index 93af4cf..0000000 --- a/docs/code/3/3.2.function.wrap.cpp +++ /dev/null @@ -1,51 +0,0 @@ -// -// 3.2.function.wrap.cpp -// chapter 03 runtime enhancement -// modern c++ tutorial -// -// created by changkun at changkun.de -// https://github.com/changkun/modern-cpp-tutorial -// - -#include -#include - -using foo = void(int); // function pointer -void functional(foo f) { - f(1); -} - -int foo2(int para) { - return para; -} - -int foo3(int a, int b, int c) { - return 0; -} - -int main() { - - auto f = [](int value) { - std::cout << value << std::endl; - }; - functional(f); // call by function pointer - f(1); // call by lambda expression - - // std::function wraps a function that take int paremeter and returns int value - std::function func = foo2; - - int important = 10; - std::function func2 = [&](int value) -> int { - return 1+value+important; - }; - std::cout << func(10) << std::endl; - std::cout << func2(10) << std::endl; - - // bind parameter 1, 2 on function foo, and use std::placeholders::_1 as placeholder - // for the first parameter. - auto bindFoo = std::bind(foo3, std::placeholders::_1, 1,2); - // when call bindFoo, we only need one param left - bindFoo(1); - - return 0; -} diff --git a/docs/code/3/3.3.rvalue.cpp b/docs/code/3/3.3.rvalue.cpp deleted file mode 100644 index 20b30ca..0000000 --- a/docs/code/3/3.3.rvalue.cpp +++ /dev/null @@ -1,38 +0,0 @@ -// -// 3.3.rvalue.cpp -// modern c++ tutorial -// -// created by changkun at changkun.de -// https://github.com/changkun/modern-cpp-tutorial -// - - -#include -#include - -void reference(std::string& str) { - std::cout << "lvalue" << std::endl; -} -void reference(std::string&& str) { - std::cout << "rvalue" << std::endl; -} - -int main() -{ - std::string lv1 = "string,"; // lv1 is a lvalue - // std::string&& r1 = lv1; // illegal, rvalue can't ref to lvalue - std::string&& rv1 = std::move(lv1); // legal, std::move can convert lvalue to rvalue - std::cout << rv1 << std::endl; // string, - - const std::string& lv2 = lv1 + lv1; // legal, const lvalue reference can extend temp variable's lifecycle - // lv2 += "Test"; // illegal, const ref can't be modified - std::cout << lv2 << std::endl; // string,string - - std::string&& rv2 = lv1 + lv2; // legal, rvalue ref extend lifecycle - rv2 += "string"; // legal, non-const reference can be modified - std::cout << rv2 << std::endl; // string,string,string, - - reference(rv2); // output: lvalue - - return 0; -} diff --git a/docs/code/3/3.4.historical.cpp b/docs/code/3/3.4.historical.cpp deleted file mode 100644 index d6010c6..0000000 --- a/docs/code/3/3.4.historical.cpp +++ /dev/null @@ -1,16 +0,0 @@ -// -// 3.4.historical.cpp -// modern c++ tutorial -// -// created by changkun at changkun.de -// https://github.com/changkun/modern-cpp-tutorial -// - -#include - -int main() { - // int &a = std::move(1); // illegal, non-const lvalue reference cannot ref rvalue - const int &b = std::move(1); // legal, const lvalue reference can - - std::cout << b << std::endl; -} \ No newline at end of file diff --git a/docs/code/3/3.5.move.semantics.cpp b/docs/code/3/3.5.move.semantics.cpp deleted file mode 100644 index 194af92..0000000 --- a/docs/code/3/3.5.move.semantics.cpp +++ /dev/null @@ -1,40 +0,0 @@ -// -// 3.5.move.semantics.cpp -// modern c++ tutorial -// -// created by changkun at changkun.de -// https://github.com/changkun/modern-cpp-tutorial -// - -#include -class A { -public: - int *pointer; - A():pointer(new int(1)) { - std::cout << "construct" << pointer << std::endl; - } - A(A& a):pointer(new int(*a.pointer)) { - std::cout << "copy" << pointer << std::endl; - } // meaningless object copy - A(A&& a):pointer(a.pointer) { - a.pointer = nullptr; - std::cout << "move" << pointer << std::endl; - } - ~A(){ - std::cout << "destruct" << pointer << std::endl; - delete pointer; - } -}; -// avoid compiler optimization -A return_rvalue(bool test) { - A a,b; - if(test) return a; // equal to static_cast(a); - else return b; // equal to static_cast(b); -} -int main() { - A obj = return_rvalue(false); - std::cout << "obj:" << std::endl; - std::cout << obj.pointer << std::endl; - std::cout << *obj.pointer << std::endl; - return 0; -} \ No newline at end of file diff --git a/docs/code/3/3.6.move.semantics.cpp b/docs/code/3/3.6.move.semantics.cpp deleted file mode 100644 index f849a35..0000000 --- a/docs/code/3/3.6.move.semantics.cpp +++ /dev/null @@ -1,31 +0,0 @@ -// -// 3.5.move.semantics.cpp -// modern c++ tutorial -// -// created by changkun at changkun.de -// https://github.com/changkun/modern-cpp-tutorial -// - -#include // std::cout -#include // std::move -#include // std::vector -#include // std::string - -int main() { - - std::string str = "Hello world."; - std::vector v; - - // use push_back(const T&), copy - v.push_back(str); - // "str: Hello world." - std::cout << "str: " << str << std::endl; - - // use push_back(const T&&), no copy - // the string will be moved to vector, and therefore std::move can reduce copy cost - v.push_back(std::move(str)); - // str is empty now - std::cout << "str: " << str << std::endl; - - return 0; -} diff --git a/docs/code/3/3.7.perfect.forward.cpp b/docs/code/3/3.7.perfect.forward.cpp deleted file mode 100644 index f5e3949..0000000 --- a/docs/code/3/3.7.perfect.forward.cpp +++ /dev/null @@ -1,37 +0,0 @@ -// -// 3.6.perfect.forward.cpp -// modern c++ tutorial -// -// created by changkun at changkun.de -// https://github.com/changkun/modern-cpp-tutorial -// - -#include -#include -void reference(int& v) { - std::cout << "lvalue reference" << std::endl; -} -void reference(int&& v) { - std::cout << "rvalue reference" << std::endl; -} -template -void pass(T&& v) { - std::cout << " normal param passing: "; - reference(v); - std::cout << " std::move param passing: "; - reference(std::move(v)); - std::cout << " std::forward param passing: "; - reference(std::forward(v)); - std::cout << "static_cast param passing: "; - reference(static_cast(v)); -} -int main() { - std::cout << "rvalue pass:" << std::endl; - pass(1); - - std::cout << "lvalue pass:" << std::endl; - int l = 1; - pass(l); - - return 0; -} diff --git a/docs/code/3/Makefile b/docs/code/3/Makefile deleted file mode 100644 index 642b9bc..0000000 --- a/docs/code/3/Makefile +++ /dev/null @@ -1,14 +0,0 @@ -# -# modern cpp tutorial -# -# created by changkun at changkun.de -# https://github.com/changkun/modern-cpp-tutorial -# - -all: $(patsubst %.cpp, %.out, $(wildcard *.cpp)) - -%.out: %.cpp Makefile - clang++ $< -o $@ -std=c++2a -pedantic - -clean: - rm *.out \ No newline at end of file diff --git a/docs/code/4/4.1.linear.container.cpp b/docs/code/4/4.1.linear.container.cpp deleted file mode 100644 index 7370179..0000000 --- a/docs/code/4/4.1.linear.container.cpp +++ /dev/null @@ -1,68 +0,0 @@ -// -// 4.1.linear.container.cpp -// modern c++ tutorial -// -// created by changkun at changkun.de -// https://github.com/changkun/modern-cpp-tutorial -// - -#include -#include -#include - -void foo(int *p, int len) { - for (int i = 0; i != len; ++i) { - std::cout << p[i] << std::endl; - } -} - -int main() { - std::vector v; - std::cout << "size:" << v.size() << std::endl; // output 0 - std::cout << "capacity:" << v.capacity() << std::endl; // output 0 - - // As you can see, the storage of std::vector is automatically managed and - // automatically expanded as needed. - // But if there is not enough space, you need to redistribute more memory, - // and reallocating memory is usually a performance-intensive operation. - v.push_back(1); - v.push_back(2); - v.push_back(3); - std::cout << "size:" << v.size() << std::endl; // output 3 - std::cout << "capacity:" << v.capacity() << std::endl; // output 4 - - // The auto-expansion logic here is very similar to Golang's slice. - v.push_back(4); - v.push_back(5); - std::cout << "size:" << v.size() << std::endl; // output 5 - std::cout << "capacity:" << v.capacity() << std::endl; // output 8 - - // As can be seen below, although the container empties the element, - // the memory of the emptied element is not returned. - v.clear(); - std::cout << "size:" << v.size() << std::endl; // output 0 - std::cout << "capacity:" << v.capacity() << std::endl; // output 8 - - // Additional memory can be returned to the system via the shrink_to_fit() call - v.shrink_to_fit(); - std::cout << "size:" << v.size() << std::endl; // output 0 - std::cout << "capacity:" << v.capacity() << std::endl; // output 0 - - - std::array arr= {1,4,3,2}; - - //int len = 4; - //std::array arr = {1,2,3,4}; // illegal, size of array must be constexpr - - // C style parameter passing - // foo(arr, arr.size()); // illegal, cannot convert implicitly - foo(&arr[0], arr.size()); - foo(arr.data(), arr.size()); - - // more usage - std::sort(arr.begin(), arr.end()); - for(auto &i : arr) - std::cout << i << std::endl; - - return 0; -} diff --git a/docs/code/4/4.2.unordered.map.cpp b/docs/code/4/4.2.unordered.map.cpp deleted file mode 100644 index 564d4bb..0000000 --- a/docs/code/4/4.2.unordered.map.cpp +++ /dev/null @@ -1,37 +0,0 @@ -// -// 4.2.unordered.map.cpp -// chapter 04 containers -// modern c++ tutorial -// -// created by changkun at changkun.de -// https://github.com/changkun/modern-cpp-tutorial -// - -#include -#include -#include -#include - -int main() { - // initialized in same order - std::unordered_map u = { - {1, "1"}, - {3, "3"}, - {2, "2"} - }; - std::map v = { - {1, "1"}, - {3, "3"}, - {2, "2"} - }; - - // iterates in the same way - std::cout << "std::unordered_map" << std::endl; - for( const auto & n : u) - std::cout << "Key:[" << n.first << "] Value:[" << n.second << "]\n"; - - std::cout << std::endl; - std::cout << "std::map" << std::endl; - for( const auto & n : v) - std::cout << "Key:[" << n.first << "] Value:[" << n.second << "]\n"; -} diff --git a/docs/code/4/4.3.tuples.cpp b/docs/code/4/4.3.tuples.cpp deleted file mode 100644 index 8211b19..0000000 --- a/docs/code/4/4.3.tuples.cpp +++ /dev/null @@ -1,83 +0,0 @@ -// -// 4.3.tuples.cpp -// chapter 04 containers -// modern c++ tutorial -// -// created by changkun at changkun.de -// https://github.com/changkun/modern-cpp-tutorial -// - - -#include -#include -#include - -auto get_student(int id) -{ - if (id == 0) - return std::make_tuple(3.8, 'A', "John"); - if (id == 1) - return std::make_tuple(2.9, 'C', "Jack"); - if (id == 2) - return std::make_tuple(1.7, 'D', "Ive"); - // return type is std::tuple - return std::make_tuple(0.0, 'D', "null"); -} - -template -constexpr std::variant _tuple_index(const std::tuple& tpl, size_t i) { - if constexpr (n >= sizeof...(T)) - throw std::out_of_range("out of range."); - if (i == n) - return std::variant{ std::in_place_index, std::get(tpl) }; - return _tuple_index<(n < sizeof...(T)-1 ? n+1 : 0)>(tpl, i); -} -template -constexpr std::variant tuple_index(const std::tuple& tpl, size_t i) { - return _tuple_index<0>(tpl, i); -} - -template -auto tuple_len(T &tpl) { - return std::tuple_size::value; -} - -template -std::ostream & operator<< (std::ostream & s, std::variant const & v) { - std::visit([&](auto && x){ s << x;}, v); - return s; -} - -int main() -{ - auto student = get_student(0); - std::cout << "ID: 0, " - << "GPA: " << std::get<0>(student) << ", " - << "Grade: " << std::get<1>(student) << ", " - << "Name: " << std::get<2>(student) << '\n'; - - double gpa; - char grade; - std::string name; - - // tuple unpack - std::tie(gpa, grade, name) = get_student(1); - std::cout << "ID: 1, " - << "GPA: " << gpa << ", " - << "Grade: " << grade << ", " - << "Name: " << name << '\n'; - - - std::tuple t("123", 4.5, 6.7, 8); - std::cout << std::get(t) << std::endl; - // std::cout << std::get(t) << std::endl; // illegal, runtime error - std::cout << std::get<3>(t) << std::endl; - - // concat - auto new_tuple = std::tuple_cat(get_student(1), std::move(t)); - - // iteration - for(int i = 0; i != tuple_len(new_tuple); ++i) { - std::cout << tuple_index(new_tuple, i) << std::endl; // runtime indexing - } -} diff --git a/docs/code/4/Makefile b/docs/code/4/Makefile deleted file mode 100644 index 642b9bc..0000000 --- a/docs/code/4/Makefile +++ /dev/null @@ -1,14 +0,0 @@ -# -# modern cpp tutorial -# -# created by changkun at changkun.de -# https://github.com/changkun/modern-cpp-tutorial -# - -all: $(patsubst %.cpp, %.out, $(wildcard *.cpp)) - -%.out: %.cpp Makefile - clang++ $< -o $@ -std=c++2a -pedantic - -clean: - rm *.out \ No newline at end of file diff --git a/docs/code/5/5.1.shared.ptr.a.cpp b/docs/code/5/5.1.shared.ptr.a.cpp deleted file mode 100644 index a467767..0000000 --- a/docs/code/5/5.1.shared.ptr.a.cpp +++ /dev/null @@ -1,52 +0,0 @@ -// -// 5.1.shared.ptr.cpp -// chapter 05 start pointers and memory management -// modern c++ tutorial -// -// created by changkun at changkun.de -// https://github.com/changkun/modern-cpp-tutorial -// - -#include -#include - -void foo(std::shared_ptr i) -{ - (*i)++; -} - -int main() -{ - // auto pointer = new int(10); // illegal, no direct assignment - // std::shared_ptr construction - auto pointer = std::make_shared(10); - auto pointer2 = pointer; // reference count + 1 - auto pointer3 = pointer; // reference count + 1 - - - foo(pointer); - std::cout << *pointer << std::endl; // 11 - int *p = pointer.get(); // does not increase reference count - - std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; - std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; - std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; - - pointer2.reset(); - std::cout << "reset pointer2:" << std::endl; - std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; - std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; - std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; - - pointer3.reset(); - std::cout << "reset pointer3:" << std::endl; - std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; - std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; - std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; - // std::cout << *pointer << std::endl; // reference count equals 0, illegal access - - - // Before leaving the scope, the pointer is destructed and - // the reference count is reduced to 0 - return 0; -} diff --git a/docs/code/5/5.2.unique.ptr.cpp b/docs/code/5/5.2.unique.ptr.cpp deleted file mode 100644 index c4db0c9..0000000 --- a/docs/code/5/5.2.unique.ptr.cpp +++ /dev/null @@ -1,50 +0,0 @@ -// -// 5.2.unique.ptr.cpp -// chapter 05 start pointers and memory management -// modern c++ tutorial -// -// created by changkun at changkun.de -// https://github.com/changkun/modern-cpp-tutorial -// - -#include -#include - -struct Foo { - Foo() { std::cout << "Foo::Foo" << std::endl; } - ~Foo() { std::cout << "Foo::~Foo" << std::endl; } - void foo() { std::cout << "Foo::foo" << std::endl; } -}; - -void f(const Foo &) { - std::cout << "f(const Foo&)" << std::endl; -} - -int main() { - std::unique_ptr p1(std::make_unique()); - - // p1 is not empty, prints - if (p1) p1->foo(); - { - std::unique_ptr p2(std::move(p1)); - - // p2 is not empty, prints - f(*p2); - - // p2 is not empty, prints - if(p2) p2->foo(); - - // p1 is empty, no prints - if(p1) p1->foo(); - - p1 = std::move(p2); - - // p2 is empty, no prints - if(p2) p2->foo(); - std::cout << "p2 was destroyed" << std::endl; - } - // p1 is not empty, prints - if (p1) p1->foo(); - - // Foo instance will be destroyed when leaving the scope -} diff --git a/docs/code/5/5.3.weak.ptr.cpp b/docs/code/5/5.3.weak.ptr.cpp deleted file mode 100644 index cd84647..0000000 --- a/docs/code/5/5.3.weak.ptr.cpp +++ /dev/null @@ -1,38 +0,0 @@ -// -// 5.3.weak.ptr.cpp -// chapter 05 start pointers and memory management -// modern c++ tutorial -// -// created by changkun at changkun.de -// https://github.com/changkun/modern-cpp-tutorial -// - - -#include -#include - -class A; -class B; - -class A { -public: - std::shared_ptr pointer; - ~A() { - std::cout << "A was destroyed" << std::endl; - } -}; -class B { -public: - std::shared_ptr pointer; - ~B() { - std::cout << "B was destroyed" << std::endl; - } -}; -int main() { - std::shared_ptr a = std::make_shared(); - std::shared_ptr b = std::make_shared(); - a->pointer = b; - b->pointer = a; - - return 0; -} diff --git a/docs/code/5/Makefile b/docs/code/5/Makefile deleted file mode 100644 index 642b9bc..0000000 --- a/docs/code/5/Makefile +++ /dev/null @@ -1,14 +0,0 @@ -# -# modern cpp tutorial -# -# created by changkun at changkun.de -# https://github.com/changkun/modern-cpp-tutorial -# - -all: $(patsubst %.cpp, %.out, $(wildcard *.cpp)) - -%.out: %.cpp Makefile - clang++ $< -o $@ -std=c++2a -pedantic - -clean: - rm *.out \ No newline at end of file diff --git a/docs/code/6/6.1.regex.cpp b/docs/code/6/6.1.regex.cpp deleted file mode 100644 index 6fc8bcc..0000000 --- a/docs/code/6/6.1.regex.cpp +++ /dev/null @@ -1,38 +0,0 @@ -// -// 6.1.regex.cpp -// chapter 06 regular expression -// modern c++ tutorial -// -// created by changkun at changkun.de -// https://github.com/changkun/modern-cpp-tutorial -// - -#include -#include -#include - -int main() { - std::string fnames[] = {"foo.txt", "bar.txt", "test", "a0.txt", "AAA.txt"}; - // In C++, `\` will be used as an escape character in the string. - // In order for `\.` to be passed as a regular expression, - // it is necessary to perform second escaping of `\`, thus we have `\\.` - std::regex txt_regex("[a-z]+\\.txt"); - for (const auto &fname: fnames) - std::cout << fname << ": " << std::regex_match(fname, txt_regex) << std::endl; - - std::regex base_regex("([a-z]+)\\.txt"); - std::smatch base_match; - for(const auto &fname: fnames) { - if (std::regex_match(fname, base_match, base_regex)) { - // the first element of std::smatch matches the entire string - // the second element of std::smatch matches the first expression with brackets - if (base_match.size() == 2) { - std::string base = base_match[1].str(); - std::cout << "sub-match[0]: " << base_match[0].str() << std::endl; - std::cout << fname << " sub-match[1]: " << base << std::endl; - } - } - } - - return 0; -} diff --git a/docs/code/6/Makefile b/docs/code/6/Makefile deleted file mode 100644 index 642b9bc..0000000 --- a/docs/code/6/Makefile +++ /dev/null @@ -1,14 +0,0 @@ -# -# modern cpp tutorial -# -# created by changkun at changkun.de -# https://github.com/changkun/modern-cpp-tutorial -# - -all: $(patsubst %.cpp, %.out, $(wildcard *.cpp)) - -%.out: %.cpp Makefile - clang++ $< -o $@ -std=c++2a -pedantic - -clean: - rm *.out \ No newline at end of file diff --git a/docs/code/7/7.1.thread.basic.cpp b/docs/code/7/7.1.thread.basic.cpp deleted file mode 100644 index 4f015b9..0000000 --- a/docs/code/7/7.1.thread.basic.cpp +++ /dev/null @@ -1,19 +0,0 @@ -// -// 7.1.thread.basic.cpp -// chapter 7 parallelism and concurrency -// modern c++ tutorial -// -// created by changkun at changkun.de -// https://github.com/changkun/modern-cpp-tutorial -// - -#include -#include - -int main() { - std::thread t([](){ - std::cout << "hello world." << std::endl; - }); - t.join(); - return 0; -} diff --git a/docs/code/7/7.2.critical.section.a.cpp b/docs/code/7/7.2.critical.section.a.cpp deleted file mode 100644 index 168d8c4..0000000 --- a/docs/code/7/7.2.critical.section.a.cpp +++ /dev/null @@ -1,33 +0,0 @@ -// -// 7.2.critical.section.a.cpp -// chapter 7 parallelism and concurrency -// modern c++ tutorial -// -// created by changkun at changkun.de -// https://github.com/changkun/modern-cpp-tutorial -// - -#include -#include -#include - -int v = 1; - -void critical_section(int change_v) { - static std::mutex mtx; - std::lock_guard lock(mtx); - - // do contention operations - v = change_v; - - // mtx will be destructed when exit this region -} - -int main() { - std::thread t1(critical_section, 2), t2(critical_section, 3); - t1.join(); - t2.join(); - - std::cout << v << std::endl; - return 0; -} \ No newline at end of file diff --git a/docs/code/7/7.3.critical.section.b.cpp b/docs/code/7/7.3.critical.section.b.cpp deleted file mode 100644 index 930cd19..0000000 --- a/docs/code/7/7.3.critical.section.b.cpp +++ /dev/null @@ -1,40 +0,0 @@ -// -// 7.3.critical.section.b.cpp -// chapter 7 parallelism and concurrency -// modern c++ tutorial -// -// created by changkun at changkun.de -// https://github.com/changkun/modern-cpp-tutorial -// - -#include -#include -#include - -int v = 1; - -void critical_section(int change_v) { - static std::mutex mtx; - std::unique_lock lock(mtx); - // do contention operations - v = change_v; - std::cout << v << std::endl; - // release the lock - lock.unlock(); - - // during this period, - // others are allowed to acquire v - - // start another group of contention operations - // lock again - lock.lock(); - v += 1; - std::cout << v << std::endl; -} - -int main() { - std::thread t1(critical_section, 2), t2(critical_section, 3); - t1.join(); - t2.join(); - return 0; -} \ No newline at end of file diff --git a/docs/code/7/7.4.futures.cpp b/docs/code/7/7.4.futures.cpp deleted file mode 100644 index 7ab3f0d..0000000 --- a/docs/code/7/7.4.futures.cpp +++ /dev/null @@ -1,25 +0,0 @@ -// -// 7.4.futures.cpp -// chapter 7 parallelism and concurrency -// modern c++ tutorial -// -// created by changkun at changkun.de -// https://github.com/changkun/modern-cpp-tutorial -// - -#include -#include -#include - -int main() { - // pack a lambda expression that returns 7 into a std::packaged_task - std::packaged_task task([](){return 7;}); - // get the future of task - std::future result = task.get_future(); // run task in a thread - std::thread(std::move(task)).detach(); - std::cout << "waiting..."; - result.wait(); // block until future has arrived - // output result - std::cout << "done!" << std:: endl << "future result is " << result.get() << std::endl; - return 0; -} \ No newline at end of file diff --git a/docs/code/7/7.5.producer.consumer.cpp b/docs/code/7/7.5.producer.consumer.cpp deleted file mode 100644 index 24a1a96..0000000 --- a/docs/code/7/7.5.producer.consumer.cpp +++ /dev/null @@ -1,64 +0,0 @@ -// -// 7.5.producer.consumer.cpp -// chapter 7 parallelism and concurrency -// modern c++ tutorial -// -// created by changkun at changkun.de -// https://github.com/changkun/modern-cpp-tutorial -// - -#include -#include -#include -#include -#include -#include - - -int main() { - std::queue produced_nums; - std::mutex mtx; - std::condition_variable cv; - bool notified = false; // notification sign - - auto producer = [&]() { - for (int i = 0; ; i++) { - std::this_thread::sleep_for(std::chrono::milliseconds(500)); - std::unique_lock lock(mtx); - std::cout << "producing " << i << std::endl; - produced_nums.push(i); - notified = true; - cv.notify_all(); - } - }; - auto consumer = [&]() { - while (true) { - std::unique_lock lock(mtx); - while (!notified) { // avoid spurious wakeup - cv.wait(lock); - } - - // temporal unlock to allow producer produces more rather than - // let consumer hold the lock until its consumed. - lock.unlock(); - std::this_thread::sleep_for(std::chrono::milliseconds(1000)); // consumer is slower - lock.lock(); - if (!produced_nums.empty()) { - std::cout << "consuming " << produced_nums.front() << std::endl; - produced_nums.pop(); - } - notified = false; - } - }; - - std::thread p(producer); - std::thread cs[2]; - for (int i = 0; i < 2; ++i) { - cs[i] = std::thread(consumer); - } - p.join(); - for (int i = 0; i < 2; ++i) { - cs[i].join(); - } - return 0; -} diff --git a/docs/code/7/7.6.atomic.cpp b/docs/code/7/7.6.atomic.cpp deleted file mode 100644 index 1b9529b..0000000 --- a/docs/code/7/7.6.atomic.cpp +++ /dev/null @@ -1,28 +0,0 @@ -// -// 7.6.atomic.cpp -// chapter 7 parallelism and concurrency -// modern c++ tutorial -// -// created by changkun at changkun.de -// https://github.com/changkun/modern-cpp-tutorial -// - -#include -#include -#include - -std::atomic count = {0}; - -int main() { - std::thread t1([](){ - count.fetch_add(1); - }); - std::thread t2([](){ - count++; // identical to fetch_add - count += 1; // identical to fetch_add - }); - t1.join(); - t2.join(); - std::cout << count << std::endl; - return 0; -} \ No newline at end of file diff --git a/docs/code/7/7.6.bad.example.cpp b/docs/code/7/7.6.bad.example.cpp deleted file mode 100644 index f16f305..0000000 --- a/docs/code/7/7.6.bad.example.cpp +++ /dev/null @@ -1,32 +0,0 @@ -// -// 7.6.bad.example.cpp -// chapter 7 parallelism and concurrency -// modern c++ tutorial -// -// created by changkun at changkun.de -// https://github.com/changkun/modern-cpp-tutorial -// - -#include -#include - -int main() { - int a = 0; - volatile int flag = 0; - - std::thread t1([&]() { - while (flag != 1); - - int b = a; - std::cout << "b = " << b << std::endl; - }); - - std::thread t2([&]() { - a = 5; - flag = 1; - }); - - t1.join(); - t2.join(); - return 0; -} \ No newline at end of file diff --git a/docs/code/7/7.7.is.lock.free.cpp b/docs/code/7/7.7.is.lock.free.cpp deleted file mode 100644 index 8e437c0..0000000 --- a/docs/code/7/7.7.is.lock.free.cpp +++ /dev/null @@ -1,23 +0,0 @@ -// -// 7.7.is.lock.free.cpp -// chapter 7 parallelism and concurrency -// modern c++ tutorial -// -// created by changkun at changkun.de -// https://github.com/changkun/modern-cpp-tutorial -// - -#include -#include - -struct A { - float x; - int y; - long long z; -}; - -int main() { - std::atomic a; - std::cout << std::boolalpha << a.is_lock_free() << std::endl; - return 0; -} \ No newline at end of file diff --git a/docs/code/7/7.8.memory.order.cpp b/docs/code/7/7.8.memory.order.cpp deleted file mode 100644 index e8b1cc7..0000000 --- a/docs/code/7/7.8.memory.order.cpp +++ /dev/null @@ -1,111 +0,0 @@ -// -// 7.8.memory.order.cpp -// chapter 7 parallelism and concurrency -// modern c++ tutorial -// -// created by changkun at changkun.de -// https://github.com/changkun/modern-cpp-tutorial -// - -#include -#include -#include -#include - -using namespace std; -using namespace std::chrono; - -const int N = 10000; - -void relaxed_order() { - cout << "relaxed_order: " << endl; - - atomic counter = {0}; - vector vt; - for (int i = 0; i < N; ++i) { - vt.emplace_back([&](){ - counter.fetch_add(1, memory_order_relaxed); - }); - } - auto t1 = high_resolution_clock::now(); - for (auto& t : vt) { - t.join(); - } - auto t2 = high_resolution_clock::now(); - auto duration = ( t2 - t1 ).count(); - cout << "relaxed order speed: " << duration / N << "ns" << endl; -} - -void release_consume_order() { - cout << "release_consume_order: " << endl; - - atomic ptr; - int v; - thread producer([&]() { - int* p = new int(42); - v = 1024; - ptr.store(p, memory_order_release); - }); - thread consumer([&]() { - int* p; - while(!(p = ptr.load(memory_order_consume))); - - cout << "p: " << *p << endl; - cout << "v: " << v << endl; - }); - producer.join(); - consumer.join(); -} - -void release_acquire_order() { - cout << "release_acquire_order: " << endl; - - int v; - atomic flag = {0}; - thread release([&]() { - v = 42; - flag.store(1, memory_order_release); - }); - thread acqrel([&]() { - int expected = 1; // must before compare_exchange_strong - while(!flag.compare_exchange_strong(expected, 2, memory_order_acq_rel)) { - expected = 1; // must after compare_exchange_strong - } - // flag has changed to 2 - }); - thread acquire([&]() { - while(flag.load(memory_order_acquire) < 2); - - cout << "v: " << v << endl; // must be 42 - }); - release.join(); - acqrel.join(); - acquire.join(); -} - -void sequential_consistent_order() { - cout << "sequential_consistent_order: " << endl; - - atomic counter = {0}; - vector vt; - for (int i = 0; i < N; ++i) { - vt.emplace_back([&](){ - counter.fetch_add(1, memory_order_seq_cst); - }); - } - auto t1 = high_resolution_clock::now(); - for (auto& t : vt) { - t.join(); - } - auto t2 = high_resolution_clock::now(); - auto duration = ( t2 - t1 ).count(); - cout << "sequential consistent speed: " << duration / N << "ns" << endl; -} - -int main() { - relaxed_order(); - release_consume_order(); - release_acquire_order(); - sequential_consistent_order(); - return 0; -} \ No newline at end of file diff --git a/docs/code/7/Makefile b/docs/code/7/Makefile deleted file mode 100644 index 642b9bc..0000000 --- a/docs/code/7/Makefile +++ /dev/null @@ -1,14 +0,0 @@ -# -# modern cpp tutorial -# -# created by changkun at changkun.de -# https://github.com/changkun/modern-cpp-tutorial -# - -all: $(patsubst %.cpp, %.out, $(wildcard *.cpp)) - -%.out: %.cpp Makefile - clang++ $< -o $@ -std=c++2a -pedantic - -clean: - rm *.out \ No newline at end of file diff --git a/docs/code/9/9.1.noexcept.cpp b/docs/code/9/9.1.noexcept.cpp deleted file mode 100644 index 59ff25e..0000000 --- a/docs/code/9/9.1.noexcept.cpp +++ /dev/null @@ -1,51 +0,0 @@ -// -// 9.1.noexcept.cpp -// chapter 09 others -// modern c++ tutorial -// -// created by changkun at changkun.de -// https://github.com/changkun/modern-cpp-tutorial -// - -#include - -void may_throw() { - throw true; -} -auto non_block_throw = []{ - may_throw(); -}; -void no_throw() noexcept { - return; -} - -auto block_throw = []() noexcept { - no_throw(); -}; - -int main() -{ - std::cout << std::boolalpha - << "may_throw() noexcept? " << noexcept(may_throw()) << std::endl - << "no_throw() noexcept? " << noexcept(no_throw()) << std::endl - << "lmay_throw() noexcept? " << noexcept(non_block_throw()) << std::endl - << "lno_throw() noexcept? " << noexcept(block_throw()) << std::endl; - - try { - may_throw(); - } catch (...) { - std::cout << "exception captured from my_throw()" << std::endl; - } - - try { - non_block_throw(); - } catch (...) { - std::cout << "exception captured from non_block_throw()" << std::endl; - } - - try { - block_throw(); - } catch (...) { - std::cout << "exception captured from block_throw()" << std::endl; - } -} diff --git a/docs/code/9/9.2.literals.cpp b/docs/code/9/9.2.literals.cpp deleted file mode 100644 index 668eb19..0000000 --- a/docs/code/9/9.2.literals.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// -// 9.2.literals.cpp -// chapter 09 others -// modern c++ tutorial -// -// created by changkun at changkun.de -// https://github.com/changkun/modern-cpp-tutorial -// - -#include -#include - -std::string operator"" _wow1(const char *wow1, size_t len) { - return std::string(wow1)+"woooooooooow, amazing"; -} - -std::string operator""_wow2 (unsigned long long i) { - return std::to_string(i)+"woooooooooow, amazing"; -} - -int main() { - std::string str = R"(C:\\File\\To\\Path)"; - std::cout << str << std::endl; - - int value = 0b1001010101010; - std::cout << value << std::endl; - - - auto str2 = "abc"_wow1; - auto num = 1_wow2; - std::cout << str2 << std::endl; - std::cout << num << std::endl; - return 0; -} diff --git a/docs/code/9/9.3.alignment.cpp b/docs/code/9/9.3.alignment.cpp deleted file mode 100644 index 947a522..0000000 --- a/docs/code/9/9.3.alignment.cpp +++ /dev/null @@ -1,31 +0,0 @@ -// -// 9.3.alignment.cpp -// chapter 09 others -// modern c++ tutorial -// -// created by changkun at changkun.de -// https://github.com/changkun/modern-cpp-tutorial -// - - -#include - -struct Storage { - char a; - int b; - double c; - long long d; -}; - -struct alignas(std::max_align_t) AlignasStorage { - char a; - int b; - double c; - long long d; -}; - -int main() { - std::cout << alignof(Storage) << std::endl; - std::cout << alignof(AlignasStorage) << std::endl; - return 0; -} \ No newline at end of file diff --git a/docs/code/9/Makefile b/docs/code/9/Makefile deleted file mode 100644 index 642b9bc..0000000 --- a/docs/code/9/Makefile +++ /dev/null @@ -1,14 +0,0 @@ -# -# modern cpp tutorial -# -# created by changkun at changkun.de -# https://github.com/changkun/modern-cpp-tutorial -# - -all: $(patsubst %.cpp, %.out, $(wildcard *.cpp)) - -%.out: %.cpp Makefile - clang++ $< -o $@ -std=c++2a -pedantic - -clean: - rm *.out \ No newline at end of file diff --git a/docs/images/Effective-Modern-C++.jpg b/docs/images/Effective-Modern-C++.jpg deleted file mode 100644 index 396e547..0000000 Binary files a/docs/images/Effective-Modern-C++.jpg and /dev/null differ diff --git a/docs/images/c-primer-plus.png b/docs/images/c-primer-plus.png deleted file mode 100644 index c92d3a7..0000000 Binary files a/docs/images/c-primer-plus.png and /dev/null differ diff --git a/docs/summary/1.头文件解析.md b/docs/summary/1.头文件解析.md deleted file mode 100644 index cb4536e..0000000 --- a/docs/summary/1.头文件解析.md +++ /dev/null @@ -1,64 +0,0 @@ ---- -sort: 1 ---- -# 头文件解析 - -## 头文件中应该写什么 - -头文件的作用就是被其他的 .cpp 包含进去的。它们本身并不参与编译,但实际上,它们的内容却在多个 .cpp 文件中得到了编译。通过"定义只能有一次"的规则,我们很容易可以得出,头文件中应该只放变量和函数的声明,而不能放它们的定义。因为一个头文件的内容实际上是会被引入到多个不同的 .cpp 文件中的,并且它们都会被编译。放声明当然没事,如果放了定义,那么也就相当于在多个文件中出现了对于一个符号(变量或函数)的定义,纵然这些定义都是相同的,但对于编译器来说,这样做不合法。 - -所以,应该记住的一点就是,.h头文件中,只能存在变量或者函数的声明,而不要放定义。即,只能在头文件中写形如:extern int a; 和 void f(); 的句子。这些才是声明。如果写上 inta;或者 void f() {} 这样的句子,那么一旦这个头文件被两个或两个以上的 .cpp 文件包含的话,编译器会立马报错。 - -但是,这个规则是有三个例外的: - -一,头文件中可以写 const 对象的定义。因为全局的 const 对象默认是没有 extern 的声明的,所以它只在当前文件中有效。把这样的对象写进头文件中,即使它被包含到其他多个 .cpp 文件中,这个对象也都只在包含它的那个文件中有效,对其他文件来说是不可见的,所以便不会导致多重定义。同时,因为这些 .cpp 文件中的该对象都是从一个头文件中包含进去的,这样也就保证了这些 .cpp 文件中的这个 const 对象的值是相同的,可谓一举两得。同理,static 对象的定义也可以放进头文件。 - -二,头文件中可以写内联函数(inline)的定义。因为inline函数是需要编译器在遇到它的地方根据它的定义把它内联展开的,而并非是普通函数那样可以先声明再链接的(内联函数不会链接),所以编译器就需要在编译时看到内联函数的完整定义才行。如果内联函数像普通函数一样只能定义一次的话,这事儿就难办了。因为在一个文件中还好,我可以把内联函数的定义写在最开始,这样可以保证后面使用的时候都可以见到定义;但是,如果我在其他的文件中还使用到了这个函数那怎么办呢?这几乎没什么太好的解决办法,因此 C++ 规定,内联函数可以在程序中定义多次,只要内联函数在一个 .cpp 文件中只出现一次,并且在所有的 .cpp 文件中,这个内联函数的定义是一样的,就能通过编译。那么显然,把内联函数的定义放进一个头文件中是非常明智的做法。 - -三,头文件中可以写类(class)的定义。因为在程序中创建一个类的对象时,编译器只有在这个类的定义完全可见的情况下,才能知道这个类的对象应该如何布局,所以,关于类的定义的要求,跟内联函数是基本一样的。所以把类的定义放进头文件,在使用到这个类的 .cpp 文件中去包含这个头文件,是一个很好的做法。在这里,值得一提的是,类的定义中包含着数据成员和函数成员。数据成员是要等到具体的对象被创建时才会被定义(分配空间),但函数成员却是需要在一开始就被定义的,这也就是我们通常所说的类的实现。一般,我们的做法是,把类的定义放在头文件中,而把函数成员的实现代码放在一个 .cpp 文件中。这是可以的,也是很好的办法。不过,还有另一种办法。那就是直接把函数成员的实现代码也写进类定义里面。在 C++ 的类中,如果函数成员在类的定义体中被定义,那么编译器会视这个函数为内联的。因此,把函数成员的定义写进类定义体,一起放进头文件中,是合法的。注意一下,如果把函数成员的定义写在类定义的头文件中,而没有写进类定义中,这是不合法的,因为这个函数成员此时就不是内联的了。一旦头文件被两个或两个以上的 .cpp 文件包含,这个函数成员就被重定义了。 - - -## C++ 头文件和源文件的区别 - -一、源文件如何根据 #include 来关联头文件 - -1、系统自带的头文件用尖括号括起来,这样编译器会在系统文件目录下查找。 - -2、用户自定义的文件用双引号括起来,编译器首先会在用户目录下查找,然后在到 C++ 安装目录(比如 VC 中可以指定和修改库文件查找路径,Unix 和 Linux 中可以通过环境变量来设定)中查找,最后在系统文件中查找。 - -`#include "xxx.h"`(我一直以为 "" 和 <> 没什么区别,但是 tinyxml.h 是非系统下的都文件,所以要用 "") - -二、头文件如何来关联源文件 - -这个问题实际上是说,已知头文件 "a.h" 声明了一系列函数,"b.cpp" 中实现了这些函数,那么如果我想在 "c.cpp" 中使用 "a.h" 中声明的这些在 "b.cpp"中实现的函数,通常都是在 "c.cpp" 中使用 #include "a.h",那么 c.cpp 是怎样找到 b.cpp 中的实现呢? - -其实 .cpp 和 .h 文件名称没有任何直接关系,很多编译器都可以接受其他扩展名。比如偶现在看到偶们公司的源代码,.cpp 文件由 .cc 文件替代了。 - -在 Turbo C 中,采用命令行方式进行编译,命令行参数为文件的名称,默认的是 .cpp 和 .h,但是也可以自定义为 .xxx 等等。 - -谭浩强老师的《C 程序设计》一书中提到,编译器预处理时,要对 #include 命令进行"文件包含处理":将 file2.c 的全部内容复制到 #include "file2.c" 处。这也正说明了,为什么很多编译器并不 care 到底这个文件的后缀名是什么----因为 #include 预处理就是完成了一个"复制并插入代码"的工作。 - -编译的时候,并不会去找 b.cpp 文件中的函数实现,只有在 link 的时候才进行这个工作。我们在 b.cpp 或 c.cpp 中用 #include "a.h" 实际上是引入相关声明,使得编译可以通过,程序并不关心实现是在哪里,是怎么实现的。源文件编译后成生了目标文件(.o 或 .obj 文件),目标文件中,这些函数和变量就视作一个个符号。在 link 的时候,需要在 makefile 里面说明需要连接哪个 .o 或 .obj 文件(在这里是 b.cpp 生成的 .o 或 .obj 文件),此时,连接器会去这个 .o 或 .obj 文件中找在 b.cpp 中实现的函数,再把他们 build 到 makefile 中指定的那个可以执行文件中。 - -在 Unix下,甚至可以不在源文件中包括头文件,只需要在 makefile 中指名即可(不过这样大大降低了程序可读性,是个不好的习惯哦^_^)。在 VC 中,一帮情况下不需要自己写 makefile,只需要将需要的文件都包括在 project中,VC 会自动帮你把 makefile 写好。 - -通常,C++ 编译器会在每个 .o 或 .obj 文件中都去找一下所需要的符号,而不是只在某个文件中找或者说找到一个就不找了。因此,如果在几个不同文件中实现了同一个函数,或者定义了同一个全局变量,链接的时候就会提示 "redefined"。 - - -.h文件中能包含: - -- 类成员数据的声明,但不能赋值 -- 类静态数据成员的定义和赋值,但不建议,只是个声明就好。 -- 类的成员函数的声明 -- 非类成员函数的声明 -- 常数的定义:如:constint a=5; -- 静态函数的定义 -- 类的内联函数的定义 - -不能包含: - -- 所有非静态变量(不是类的数据成员)的声明 -- 默认命名空间声明不要放在头文件,using namespace std;等应放在.cpp中,在 .h 文件中使用 std::string - -## 参考资料 -- \ No newline at end of file diff --git a/docs/summary/2.顶层 const 和底层 const.md b/docs/summary/2.顶层 const 和底层 const.md deleted file mode 100644 index eb78578..0000000 --- a/docs/summary/2.顶层 const 和底层 const.md +++ /dev/null @@ -1,64 +0,0 @@ ---- -sort: 2 ---- -# 顶层 const 和底层 const - -简单说,顶层const是指“指针本身是常量”,而底层const是指“指针指向的对象是常量”。 - -底层是常量的指针,本身不可改变。(常量指针) -顶层是指针的常量,指向不可改变。(指针常量) - -从 const 指针开始说起。`const int* pInt`; 和 `int *const pInt = &someInt`;,前者是 `*pInt` 不能改变,而后者是 `pInt` 不能改变。因此指针本身是不是常量和指针所指向的对象是不是常量就是两个互相独立的问题。用顶层表示指针本身是个常量,底层表示指针所指向的对象是个常量。 - -更一般的,顶层 `const` 可以表示任意的对象是常量,这一点对任何数据类型都适用;底层 `const` 则与指针和引用等复合类型有关,比较特殊的是,指针类型既可以是顶层 `const` 也可以是底层 `const` 或者二者兼备 - -`const char * p` 与 `char * const p `两种声明的意思分别应该是: -- `p` 是一个指向常量字符的指针,不变的是 `char` 的值,即该字符的值在定义时初始化后就不能再改变。 -- `p` 是一个指向字符的常量指针,不变的是 `p` 的值,即该指针不能再指向别的。 - -一个比较好的记忆方法: -“以`*`分界,把一个声明从右向左读” - -注意语法,`*` 读作 `pointer to` (指向...的指针)`,const` (常量) 是形容词,`char` (变量类型) 和 `p` (变量名) , - -- `const char * p` 读作:`p is a pointer to a const char` - - 译:`p` 是一个指针(变量),它指向一个常量字符(const char)。 -- `char * const p` 读作:`p is a const pointer to a char` - - 译:`p` 是一个常量指针(const p),它指向一个字符(变量)。 -- 另外`const`位置多变, `const char * p` 和 `char const * p`,首先以 `*` 分界,虽然 `const` 的位置改变了,但它都是在修饰 `char`,常量字符。 - -```cpp -int i = 0; -int *const p1 = &i; //p1为顶层const,其值不能改变 -const int ci = 42; //ci为顶层const,其值不能改变 -const int *p2 = &ci; //允许改变p2的值,这是一个底层const -const int *const p3 = p2; //靠右的为顶层const,靠左的为底层const -const int &r = ci; //用于声明引用的都为底层const -``` - -```cpp -i = ci; //正确,ci顶层const不受影响 -p2 = p3;//正确,p2和p3指向的对象都相同,且p3又是一个顶层const其本身也为常量 -int *p = p3; //错误,p3包含底层const定义但是p没有 -p2 = &i;//正确,int i 可以转换为常量 -int &ri = ci;//错误,普通的int &不能绑定到int常量上 -const int &r2 = i;//正确,const int& 可以绑定到一个普通的int上 - -vector::iterator /*可以理解为*/ int* //代表非常量 -vector::const_iterator /*可以理解为*/ const int* //代表底层const -const vector::iterator /*可以理解为*/ int* const //代表顶层const -const vector::iterator /*可以理解为*/ const int* const //左侧的const为底层const,右侧的顶层const - -``` - -const修饰规则: - -`const int *p1`可看作是`const`修饰的类型是`int`,修饰的内容是`*p1`,即`*p1`不允许改变。 -`int const *p2` 可以看作`const`修饰的类型是`int`,修饰的内容是`*p2`,即`*p2`不允许改变。 -`int *const p3` 可以看作`const`修饰的类型是`int *`,修饰的内容是`p3`,即`p3`不允许改变 - -简单来说就是在 *号左边还是右边 - -## 参考资料 -- -- \ No newline at end of file diff --git a/docs/summary/3.值传递、引用传递和指针传递.md b/docs/summary/3.值传递、引用传递和指针传递.md deleted file mode 100644 index b4be12c..0000000 --- a/docs/summary/3.值传递、引用传递和指针传递.md +++ /dev/null @@ -1,109 +0,0 @@ ---- -sort: 3 ---- -# 值传递、引用传递和指针传递 - -## 值传递: - -形参是实参的拷贝,改变形参的值并不会影响外部实参的值。从被调用函数的角度来说,值传递是单向的(实参->形参),参数的值只能传入, - -不能传出。当函数内部需要修改参数,并且不希望这个改变影响调用者时,采用值传递。 - -## 指针传递: - -形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作 - -## 引用传递: - -形参相当于是实参的“别名”,对形参的操作其实就是对实参的操作,在引用传递过程中,被调函数的形式参数虽然也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响了主调函数中的实参变量。 - -## 总结 - -- 值传递过程中,被调函数的形式参数作为被调函数的局部变量处理,即在栈中开辟了内存空间以存放由主调函数放进来的实参的值,从而成为了实参的一个副本。值传递的特点是被调函数对形式参数的任何操作都是作为局部变量进行,不会影响主调函数的实参变量的值。(这里是在说实参指针本身的地址值不会变) -- 指针传递参数本质上是值传递的方式,它所传递的是一个地址值。 -- 而在引用传递过程中,被调函数的形式参数虽然也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响了主调函数中的实参变量。 - -## 示例 - - -```cpp - - -#include - -using namespace std; - -//值传递 -void passByValue(int n) -{ - cout<<"值传递--函数操作地址"<<&n<