docs(book): 添加现代 C++教程及相关代码
- 新增现代 C++ 教程的 Preface 章节,包括英文和中文版本 - 添加 C++ Primer 练习代码 - 新增 Learn C++ 教程的 C++ 开发简介章节 - 添加头文件解析文档 - 更新 mkdocs.yml,包含新教程的目录结构 - 修改项目设置,使用 Python 3.10环境
This commit is contained in:
parent
b6d97a5353
commit
b5160be045
225
docs/Cpp-Notes-main/Cpp-Primer/10.泛型算法.md
Normal file
225
docs/Cpp-Notes-main/Cpp-Primer/10.泛型算法.md
Normal file
@ -0,0 +1,225 @@
|
||||
# 第十章 泛型算法
|
||||
|
||||
## 泛型算法
|
||||
|
||||
- 因为它们实现共同的操作,所以称之为“**算法**”;而“**泛型**”、指的是它们可以操作在多种容器类型上。
|
||||
- 泛型算法本身不执行容器操作,只是单独依赖迭代器和迭代器操作实现。
|
||||
- 头文件: `#include <algorithm>`或者 `#include <numeric>`(算数相关)
|
||||
- 大多数算法是通过遍历两个迭代器标记的一段元素来实现其功能。
|
||||
- 必要的编程假定:算法永远不会改变底层容器的大小。算法可能改变容器中保存的元素的值,也可能在容器内移动元素,但不能直接添加或者删除元素。
|
||||
|
||||
### find
|
||||
|
||||
- `vector<int>::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 <iterator>`
|
||||
- `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<T> in(is);` | `in`从输入流`is`读取类型为`T`的值 |
|
||||
|`istream_iterator<T> end;` | 读取类型是`T`的值的`istream_iterator`迭代器,表示尾后位置 |
|
||||
| `in1 == in2` | `in1`和`in2`必须读取相同类型。如果他们都是尾后迭代器,或绑定到相同的输入,则两者相等。 |
|
||||
| `in1 != in2` | 类似上条 |
|
||||
| `*in` | 返回从流中读取的值 |
|
||||
| `in->mem` | 与`*(in).mem`含义相同 |
|
||||
| `++in, in++` | 使用元素类型所定义的`>>`运算符从流中读取下一个值。前置版本返回一个指向递增后迭代器的引用,后置版本返回旧值。 |
|
||||
|
||||
**ostream_iterator的操作**:
|
||||
|
||||
| 操作 | 解释 |
|
||||
|-----|-----|
|
||||
| `ostream_iterator<T> out(os);` | `out`将类型为`T`的值写到输出流`os`中 |
|
||||
| `ostream_iterator<T> 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)`
|
||||
144
docs/Cpp-Notes-main/Cpp-Primer/11.关联容器.md
Normal file
144
docs/Cpp-Notes-main/Cpp-Primer/11.关联容器.md
Normal file
@ -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<string, int> word_count = {{"a", 1}, {"b", 2}};`
|
||||
- `set`:`set<string> exclude = {"the", "a"};`
|
||||
|
||||
### 关键字类型的要求
|
||||
|
||||
- 对于有序容器,关键字类型必须定义元素比较的方法。默认是`<`。
|
||||
- 如果想传递一个比较的函数,可以这样定义:`multiset<Sales_data, decltype(compareIsbn)*> bookstore(compareIsbn);`
|
||||
|
||||
### pair
|
||||
|
||||
- 在`utility`头文件中定义。
|
||||
- 一个`pair`保存两个数据成员,两个类型不要求一样。
|
||||
|
||||
**pair的操作**:
|
||||
|
||||
| 操作 | 解释 |
|
||||
|-----|-----|
|
||||
| `pair<T1, T2> p;` | `p`是一个`pair`,两个类型分别是`T1`和`T2`的成员都进行了值初始化。 |
|
||||
| `pair<T1, T2> p(v1, v2);` | `first`和`second`分别用`v1`和`v2`进行初始化。 |
|
||||
| `pair<T1, T2>p = {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<const key_type, mapped_type>`; 对于`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<string, size_t>(word, 1));`
|
||||
- `word_count.insert(map<string, size_t>::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`。 |
|
||||
176
docs/Cpp-Notes-main/Cpp-Primer/12.动态内存.md
Normal file
176
docs/Cpp-Notes-main/Cpp-Primer/12.动态内存.md
Normal file
@ -0,0 +1,176 @@
|
||||
# 第十二章 动态内存
|
||||
|
||||
- 对象的生命周期:
|
||||
- 全局对象在程序启动时分配,结束时销毁。
|
||||
- 局部对象在进入程序块时创建,离开块时销毁。
|
||||
- 局部`static`对象在第一次使用前分配,在程序结束时销毁。
|
||||
- 动态分配对象:只能显式地被释放。
|
||||
|
||||
- 对象的内存位置:
|
||||
- **静态内存**用来保存局部`static`对象、类`static`对象、定义在任何函数之外的变量。
|
||||
- **栈内存**用来保存定义在函数内的非`static`对象。
|
||||
- **堆内存**,又称自由空间,用来存储**动态分配**的对象。
|
||||
|
||||
## 动态内存与智能指针
|
||||
|
||||
- 动态内存管理:
|
||||
- `new`:在动态内存中为对象分配空间并返回一个指向该对象的指针。
|
||||
- `delete`:接受一个动态对象的指针,销毁该对象,并释放与之关联的内存。
|
||||
- 智能指针:
|
||||
- 管理动态对象。
|
||||
- 行为类似常规指针。
|
||||
- 负责自动释放所指向的对象。
|
||||
- 智能指针也是模板。
|
||||
|
||||
### shared_ptr类
|
||||
|
||||
**shared_ptr和unique_ptr都支持的操作**:
|
||||
|
||||
| 操作 | 解释 |
|
||||
|-----|-----|
|
||||
| `shared_ptr<T> sp` `unique_ptr<T> 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<T>(args)` | 返回一个`shared_ptr`,指向一个动态分配的类型为`T`的对象。使用`args`初始化此对象。 |
|
||||
| `shared_ptr<T>p(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<T> p(q)` | `p`管理内置指针`q`所指向的对象;`q`必须指向`new`分配的内存,且能够转换为`T*`类型 |
|
||||
| `shared_ptr<T> p(u)` | `p`从`unique_ptr u`那里接管了对象的所有权;将`u`置为空 |
|
||||
| `shared_ptr<T> p(q, d)` | `p`接管了内置指针`q`所指向的对象的所有权。`q`必须能转换为`T*`类型。`p`将使用可调用对象`d`来代替`delete`。 |
|
||||
| `shared_ptr<T> 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<T> u1` | 空`unique_ptr`,可以指向类型是`T`的对象。`u1`会使用`delete`来是释放它的指针。 |
|
||||
| `unique_ptr<T, D> u2` | `u2`会使用一个类型为`D`的可调用对象来释放它的指针。 |
|
||||
| `unique_ptr<T, D> 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<T> w` | 空`weak_ptr`可以指向类型为`T`的对象 |
|
||||
| `weak_ptr<T> 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<T[]> u` | `u`可以指向一个动态分配的数组,整数元素类型为`T` |
|
||||
| `unique_ptr<T[]> u(p)` | `u`指向内置指针`p`所指向的动态分配的数组。`p`必须能转换为类型`T*`。 |
|
||||
| `u[i]` | 返回`u`拥有的数组中位置`i`处的对象。`u`必须指向一个数组。 |
|
||||
|
||||
### allocator类
|
||||
|
||||
- 标准库`allocator`类定义在头文件`memory`中,帮助我们将内存分配和对象构造分离开。
|
||||
- 分配的是原始的、未构造的内存。
|
||||
- `allocator`是一个模板。
|
||||
- `allocator<string> alloc;`
|
||||
|
||||
**标准库allocator类及其算法**:
|
||||
|
||||
| 操作 | 解释 |
|
||||
|-----|-----|
|
||||
| `allocator<T> 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`中。
|
||||
- 在给定目的位置创建元素,而不是由系统分配内存给他们。
|
||||
123
docs/Cpp-Notes-main/Cpp-Primer/13.拷贝控制.md
Normal file
123
docs/Cpp-Notes-main/Cpp-Primer/13.拷贝控制.md
Normal file
@ -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&&`。
|
||||
- 引用限定符:
|
||||
- 在参数列表后面防止一个`&`,限定只能向可修改的左值赋值而不能向右值赋值。
|
||||
|
||||
147
docs/Cpp-Notes-main/Cpp-Primer/14.重载运算与类型转换.md
Normal file
147
docs/Cpp-Notes-main/Cpp-Primer/14.重载运算与类型转换.md
Normal file
@ -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<Type>` | `equal_to<Type>` | `logical_and<Type>` |
|
||||
| `minus<Type>` | `not_equal_to<Type>` | `logical_or<Type>` |
|
||||
| `multiplies<Type>` | `greater<Type>` | `logical_not<Type>` |
|
||||
| `divides<Type>` | `greater_equal<Type>` | |
|
||||
| `modulus<Type>` | `less<Type>` | |
|
||||
| `negate<Type>` | `less_equal<Type>` | |
|
||||
|
||||
- 可以在算法中使用标准库函数对象。
|
||||
|
||||
### 可调用对象与function
|
||||
|
||||
**标准库function类型**:
|
||||
|
||||
| 操作 | 解释 |
|
||||
|-----|-----|
|
||||
| `function<T> f;` | `f`是一个用来存储可调用对象的空`function`,这些可调用对象的调用形式应该与类型`T`相同。 |
|
||||
| `function<T> f(nullptr);` | 显式地构造一个空`function` |
|
||||
| `function<T> f(obj)` | 在`f`中存储可调用对象`obj`的副本 |
|
||||
| `f` | 将`f`作为条件:当`f`含有一个可调用对象时为真;否则为假。 |
|
||||
| 定义为`function<T>`的成员的类型 | |
|
||||
| `result_type` | 该`function`类型的可调用对象返回的类型 |
|
||||
| `argument_type` | 当`T`有一个或两个实参时定义的类型。如果`T`只有一个实参,则`argument_type` |
|
||||
| `first_argument_type` | 第一个实参的类型 |
|
||||
| `second_argument_type` | 第二个实参的类型 |
|
||||
|
||||
- 例如:声明一个`function`类型,它可以表示接受两个`int`,返回一个`int`的可调用对象。`function<int(int, int)>`
|
||||
|
||||
## 重载、类型转换、运算符
|
||||
|
||||
### 类型转换运算符
|
||||
|
||||
- 类型转换运算符是类的一种特殊成员函数,它负责将一个类类型的值转换成其他类型。类型转换函数的一般形式如下:`operator type() const;`
|
||||
- 一个类型转换函数必须是类的成员函数;它不能声明返回类型,形参列表也必须为空。类型转换函数通常应该是`const`。
|
||||
- 避免过度使用类型转换函数。
|
||||
- C++11引入了显式的类型转换运算符。
|
||||
- 向`bool`的类型转换通常用在条件部分,因此`operator bool`一般定义成`explicit`的。
|
||||
|
||||
### 避免有二义性的类型转换
|
||||
|
||||
- 通常,不要为类第几个亿相同的类型转换,也不要在类中定义两个及以上转换源或转换目标是算术类型的转换。
|
||||
- 在调用重载函数时,如果需要额外的标准类型转换,则该转换的级别只有当所有可行函数都请求同一个用户定义的类型转换时才有用。如果所需的用户定义的类型转换不止一个,则该调用具有二义性。
|
||||
|
||||
### 函数匹配与重载运算符
|
||||
|
||||
- 如果`a`是一种类型,则表达式`a sym b`可能是:
|
||||
- `a.operatorsym(b);`
|
||||
- `operatorsym(a,b);`
|
||||
- 如果我们队同一个类既提供了转换目标是算术类型的类型转换,也提供了重载的运算符,则将会遇到重载运算符与内置运算符的二义性问题。
|
||||
148
docs/Cpp-Notes-main/Cpp-Primer/15.面向对象程序设计.md
Normal file
148
docs/Cpp-Notes-main/Cpp-Primer/15.面向对象程序设计.md
Normal file
@ -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`的派生类,返回它的两个运算对象分别出现的行的交集 |
|
||||
182
docs/Cpp-Notes-main/Cpp-Primer/16.模板和泛型编程.md
Normal file
182
docs/Cpp-Notes-main/Cpp-Primer/16.模板和泛型编程.md
Normal file
@ -0,0 +1,182 @@
|
||||
# 第十六章 模板和泛型编程
|
||||
|
||||
- 面向对象编程和泛型编程都能处理在编写程序时不知道类型的情况。
|
||||
- OOP能处理类型在程序运行之前都未知的情况;
|
||||
- 泛型编程中,在编译时就可以获知类型。
|
||||
|
||||
## 定义模板
|
||||
|
||||
- **模板**:模板是泛型编程的基础。一个模板就是一个创建类或函数的蓝图或者说公式。
|
||||
|
||||
### 函数模板
|
||||
|
||||
- `template <typename T> int compare(const T &v1, const T &v2){}`
|
||||
- 模板定义以关键字 `template`开始,后接**模板形参表**,模板形参表是用**尖括号**`<>`括住的一个或多个**模板形参**的列表,用逗号分隔,**不能为空**。
|
||||
- 使用模板时,我们显式或隐式地指定模板实参,将其绑定到模板参数上。
|
||||
- 模板类型参数:类型参数前必须使用关键字`class`或者`typename`,这两个关键字含义相同,可以互换使用。旧的程序只能使用`class`。
|
||||
- 非类型模板参数:表示一个值而非一个类型。实参必须是常量表达式。`template <class T, size_t N> void array_init(T (&parm)[N]){}`
|
||||
- 内联函数模板: `template <typename T> inline T min(const T&, const T&);`
|
||||
- 模板程序应该尽量减少对实参类型的要求。
|
||||
- 函数模板和类模板成员函数的定义通常放在头文件中。
|
||||
|
||||
### 类模板
|
||||
|
||||
- 类模板用于生成类的蓝图。
|
||||
- 不同于函数模板,编译器不能推断模板参数类型。
|
||||
- **定义类模板**:
|
||||
- `template <class Type> class Queue {};`
|
||||
- 实例化类模板:提供显式模板实参列表,来实例化出特定的类。
|
||||
- 一个类模板中所有的实例都形成一个独立的类。
|
||||
- **模板形参作用域**:模板形参的名字可以在声明为模板形参之后直到模板声明或定义的末尾处使用。
|
||||
- 类模板的成员函数:
|
||||
- `template <typename T> ret-type Blob::member-name(parm-list)`
|
||||
- 默认情况下,对于一个实例化了的类模板,其成员只有在使用时才被实例化。
|
||||
- 新标准允许模板将自己的类型参数成为友元。`template <typename T> class Bar{friend T;};`。
|
||||
- 模板类型别名:因为模板不是一个类型,因此无法定义一个`typedef`引用一个模板,但是新标准允许我们为类模板定义一个类型别名:`template<typename T> using twin = pair<T, T>;`
|
||||
|
||||
### 模板参数
|
||||
|
||||
- 模板参数与作用域:一个模板参数名的可用范围是在声明之后,至模板声明或定义结束前。
|
||||
- 一个特定文件所需要的所有模板的声明通常一起放置在文件开始位置。
|
||||
- 当我们希望通知编译器一个名字表示类型时,必须使用关键字`typename`,而不能使用`class`。
|
||||
- 默认模板实参:`template <class T = int> class Numbers{}`
|
||||
|
||||
### 成员模板
|
||||
|
||||
- 成员模板(member template):本身是模板的函数成员。
|
||||
- 普通(非模板)类的成员模板。
|
||||
- 类模板的成员模板。
|
||||
|
||||
### 控制实例化
|
||||
|
||||
- 动机:在多个文件中实例化相同模板的额外开销可能非常严重。
|
||||
- 显式实例化:
|
||||
- `extern template declaration; // 实例化声明`
|
||||
- `template declaration; // 实例化定义`
|
||||
|
||||
### 效率与灵活性
|
||||
|
||||
|
||||
## 模板实参推断
|
||||
|
||||
- 对函数模板,编译器利用调用中的函数实参来确定其模板参数,这个过程叫**模板实参推断**。
|
||||
|
||||
### 类型转换与模板类型参数
|
||||
|
||||
- 能够自动转换类型的只有:
|
||||
- 和其他函数一样,顶层`const`会被忽略。
|
||||
- 数组实参或函数实参转换为指针。
|
||||
|
||||
### 函数模板显式实参
|
||||
|
||||
- 某些情况下,编译器无法推断出模板实参的类型。
|
||||
- 定义:`template <typename T1, typename T2, typename T3> T1 sum(T2, T3);`
|
||||
- 使用函数显式实参调用:`auto val3 = sum<long long>(i, lng); // T1是显式指定,T2和T3都是从函数实参类型推断而来`
|
||||
- **注意**:正常类型转换可以应用于显式指定的实参。
|
||||
|
||||
### 尾置返回类型与类型转换
|
||||
|
||||
- 使用场景:并不清楚返回结果的准确类型,但知道所需类型是和参数相关的。
|
||||
- `template <typename It> auto fcn(It beg, It end) -> decltype(*beg)`
|
||||
- 尾置返回允许我们在参数列表之后声明返回类型。
|
||||
|
||||
标准库的**类型转换**模板:
|
||||
|
||||
- 定义在头文件`type_traits`中。
|
||||
|
||||
| 对`Mod<T>`,其中`Mod`是: | 若`T`是: | 则`Mod<T>::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 T>
|
||||
typename remove_reference<T>::type&& move(T&& t)
|
||||
{
|
||||
return static_cast<typename remove_reference<T>::type&&>(t);
|
||||
}
|
||||
```
|
||||
|
||||
### 转发
|
||||
|
||||
- 使用一个名为`forward`的新标准库设施来传递参数,它能够保持原始实参的类型。
|
||||
- 定义在头文件`utility`中。
|
||||
- 必须通过显式模板实参来调用。
|
||||
- `forward`返回显式实参类型的右值引用。即,`forward<T>`的返回类型是`T&&`。
|
||||
|
||||
## 重载与模板
|
||||
|
||||
- 多个可行模板:当有多个重载模板对一个调用提供同样好的匹配时,会选择最特例化的版本。
|
||||
- 非模板和模板重载:对于一个调用,如果一个非函数模板与一个函数模板提供同样好的匹配,则选择非模板版本。
|
||||
|
||||
## 可变参数模板
|
||||
|
||||
**可变参数模板**就是一个接受可变数目参数的模板函数或模板类。
|
||||
- 可变数目的参数被称为参数包。
|
||||
- 模板参数包:标识另个或多个模板参数。
|
||||
- 函数参数包:标识另个或者多个函数参数。
|
||||
- 用一个省略号来指出一个模板参数或函数参数,表示一个包。
|
||||
- `template <typename T, typename... Args>`,`Args`第一个模板参数包。
|
||||
- `void foo(const T &t, const Args& ... rest);`,`rest`是一个函数参数包。
|
||||
- `sizeof...`运算符,返回参数的数目。
|
||||
|
||||
### 编写可变参数函数模板
|
||||
|
||||
- 可变参数函数通常是递归的:第一步调用处理包中的第一个实参,然后用剩余实参调用自身。
|
||||
|
||||
### 包扩展
|
||||
|
||||
- 对于一个参数包,除了获取它的大小,唯一能做的事情就是**扩展**(expand)。
|
||||
- 扩展一个包时,还要提供用于每个扩展元素的**模式**(pattern)。
|
||||
|
||||
### 转发参数包
|
||||
|
||||
- 新标准下可以组合使用可变参数模板和`forward`机制,实现将实参不变地传递给其他函数。
|
||||
|
||||
## 模板特例化(Specializations)
|
||||
|
||||
- 定义函数模板特例化:关键字`template`后面跟一个空尖括号对(`<>`)。
|
||||
- 特例化的本质是实例化一个模板,而不是重载它。特例化不影响函数匹配。
|
||||
- 模板及其特例化版本应该声明在同一个头文件中。所有同名模板的声明应该放在前面,然后是特例化版本。
|
||||
- 我们可以部分特例化类模板,但不能部分特例化函数模板。
|
||||
367
docs/Cpp-Notes-main/Cpp-Primer/17.标准库特殊设施.md
Normal file
367
docs/Cpp-Notes-main/Cpp-Primer/17.标准库特殊设施.md
Normal file
@ -0,0 +1,367 @@
|
||||
# 第十七章 标准库特殊设施
|
||||
|
||||
## tuple类型
|
||||
|
||||
- `tuple`是类似`pair`的模板,每个成员类型都可以不同,但`tuple`可以有任意数量的成员。
|
||||
- 但每个确定的`tuple`类型的成员数目是固定的。
|
||||
- 我们可以将`tuple`看做一个“快速而随意”的数据结构。
|
||||
|
||||
**tuple支持的操作**:
|
||||
|
||||
| 操作 | 解释 |
|
||||
|-----|-----|
|
||||
| `tuple<T1, T2, ..., Tn> t;` | `t`是一个`tuple`,成员数为`n`,第`i`个成员的类型是`Ti`所有成员都进行值初始化。 |
|
||||
| `tuple<T1, T2, ..., Tn> t(v1, v2, ..., vn);` | 每个成员用对应的初始值`vi`进行初始化。此构造函数是`explicit`的。 |
|
||||
| `make_tuple(v1, v2, ..., vn)` | 返回一个用给定初始值初始化的`tuple`。`tuple`的类型从初始值的类型**推断**。 |
|
||||
| `t1 == t2` | 当两个`tuple`具有相同数量的成员且成员对应相等时,两个`tuple`相等。 |
|
||||
| `t1 relop t2` | `tuple`的关系运算使用**字典序**。两个`tuple`必须具有相同数量的成员。 |
|
||||
| `get<i>(t)` | 返回`t`的第`i`个数据成员的引用:如果`t`是一个左值,结果是一个左值引用;否则,结果是一个右值引用。`tuple`的所有成员都是`public`的。 |
|
||||
| `tuple_size<tupleType>::value` | 一个类模板,可以通过一个`tuple`类型来初始化。它有一个名为`value`的`public constexpr static`数据成员,类型为`size_t`,表示给定`tuple`类型中成员的数量。 |
|
||||
| `tuple_element<i, tupleType>::type` | 一个类模板,可以通过一个整型常量和一个`tuple`类型来初始化。它有一个名为`type`的`public`成员,表示给定`tuple`类型中指定成员的类型。 |
|
||||
|
||||
### 定义和初始化tuple
|
||||
|
||||
定义和初始化示例:
|
||||
|
||||
- `tuple<size_t, size_t, size_t> threeD;`
|
||||
- `tuple<size_t, size_t, size_t> 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<n> b;` | `b`有`n`位;每一位均是0.此构造函数是一个`constexpr`。 |
|
||||
| `bitset<n> b(u);` | `b`是`unsigned long long`值`u`的低`n`位的拷贝。如果`n`大于`unsigned long long`的大小,则`b`中超出`unsigned long long`的高位被置为0。此构造函数是一个`constexpr`。 |
|
||||
| `bitset<n> 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<n> 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<unsigned> 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`,偏移量相对于流结尾位置。 |
|
||||
200
docs/Cpp-Notes-main/Cpp-Primer/18.用于大型程序的工具.md
Normal file
200
docs/Cpp-Notes-main/Cpp-Primer/18.用于大型程序的工具.md
Normal file
@ -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 <typename T>
|
||||
Blob<T>::Blob(std::initializer_list<T> il) try:
|
||||
data(std::make_shared<std::vector<T> >(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含有虚基类的对象的构造顺序与一般的顺序稍有**区别**:首先使用提供给最底层派生类构造函数的初始值初始化该对象的虚基类子部分,接下来按照直接基类在派生列表中出现的次序对其进行初始化。
|
||||
* 虚基类总是先于非虚基类构造,与它们在继承体系中的次序和位置无关。
|
||||
377
docs/Cpp-Notes-main/Cpp-Primer/19.特殊工具与技术.md
Normal file
377
docs/Cpp-Notes-main/Cpp-Primer/19.特殊工具与技术.md
Normal file
@ -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 <cstdlib>
|
||||
|
||||
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<type*>(e) // e必须是一个有效的指针
|
||||
dynamic_cast<type&>(e) // e必须是一个左值
|
||||
dynamic_cast<type&&>(e) // e不能是左值
|
||||
// 以上,type类型必须时一个类类型,并且通常情况下该类型应该含有虚函数。
|
||||
// e的类型必须符合三个条件中的任意一个,它们是:
|
||||
// 1. e的类型是目标type的公有派生类;
|
||||
// 2. e的类型是目标type的共有基类;
|
||||
// 3. e的类型就是目标type的类型;
|
||||
|
||||
// 指针类型的dynamic_cast
|
||||
// 假设Base类至少含有一个虚函数,Derived是Base的共有派生类。
|
||||
if (Derived *dp = dynamic_cast<Derived*>(bp)) {
|
||||
// 使用dp指向的Derived对象
|
||||
} else { // bp指向一个Base对象
|
||||
// 使用dp指向的Base对象
|
||||
}
|
||||
|
||||
// 引用类型的dynamic_cast
|
||||
void f(const Base &b) {
|
||||
try {
|
||||
const Derived &d = dynamic_cast<const Derived&>(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<const Derived&>(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*);
|
||||
```
|
||||
@ -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 <iostream>
|
||||
|
||||
int main()
|
||||
{
|
||||
std::cout << "Hello,World" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## 练习1.4
|
||||
我们的程序使用加法运算符`+`来将两个数相加。编写程序使用乘法运算符`*`,来打印两个数的积。
|
||||
|
||||
解:
|
||||
```cpp
|
||||
#include <iostream>
|
||||
|
||||
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 <iostream>
|
||||
|
||||
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 <iostream>
|
||||
|
||||
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 <iostream>
|
||||
|
||||
int main() {
|
||||
int i = 10;
|
||||
while (i >= 0) {
|
||||
std::cout << i << std::endl;
|
||||
i--;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 练习1.11
|
||||
编写程序,提示用户输入两个整数,打印出这两个整数所指定的范围内的所有整数。
|
||||
|
||||
解:
|
||||
```cpp
|
||||
#include <iostream>
|
||||
|
||||
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 <iostream>
|
||||
// 修改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 <iostream>
|
||||
|
||||
int main() {
|
||||
for (int i = 10; i >= 0; i--) {
|
||||
std::cout << i << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
```
|
||||
|
||||
### 练习1.11
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
|
||||
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循环,两种形式的优缺点各是什么?
|
||||
|
||||
解:
|
||||
- <https://stackoverflow.com/questions/2950931/for-vs-while-in-c-programming>
|
||||
-
|
||||
```
|
||||
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 <iostream>
|
||||
|
||||
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
|
||||
编译并运行本节的程序,给它输入全都相等的值。再次运行程序,输入没有重复的值。
|
||||
|
||||
解:
|
||||

|
||||
|
||||
|
||||

|
||||
|
||||
## 练习1.19
|
||||
修改你为1.4.1节练习1.11(第11页)所编写的程序(打印一个范围内的数),使其能处理用户输入的第一个数比第二个数小的情况。
|
||||
|
||||
解:
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
|
||||
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 <iostream>
|
||||
#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 <iostream>
|
||||
#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 <iostream>
|
||||
#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 <iostream>
|
||||
#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`头文件,编译并运行本节给出的书店程序。
|
||||
@ -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 <iostream>
|
||||
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 <iostream>
|
||||
|
||||
|
||||
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 <iostream>
|
||||
#include <string>
|
||||
|
||||
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 <iostream>
|
||||
#include <string>
|
||||
|
||||
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 <iostream>
|
||||
#include <string>
|
||||
|
||||
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页)的练习。
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -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 <iostream>
|
||||
#include <vector>
|
||||
#include <ctime>
|
||||
#include <cstdlib>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main() {
|
||||
vector<int> 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 <<endl;
|
||||
|
||||
for(auto &val : vInt) {
|
||||
val = (val % 2 == 0) ? val : val * 2;
|
||||
}
|
||||
cout <<"最终数组为" << endl;
|
||||
|
||||
for(auto it = vInt.cbegin(); it != vInt.cend(); ++it) {
|
||||
cout << *it << " ";
|
||||
}
|
||||
cout <<endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## 练习4.22
|
||||
|
||||
本节的示例程序将成绩划分为`high pass`、`pass` 和 `fail` 三种,扩展该程序使其进一步将 60 分到 75 分之间的成绩设定为`low pass`。要求程序包含两个版本:一个版本只使用条件运算符;另一个版本使用1个或多个`if`语句。哪个版本的程序更容易理解呢?为什么?
|
||||
|
||||
解:
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <ctime>
|
||||
#include <cstdlib>
|
||||
|
||||
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 <iostream>
|
||||
|
||||
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<b`所占的内存空间,应该改为`sizeof(a<b)`。
|
||||
|
||||
(d)的含义是求函数f()返回值所占内存空间的大小,因为函数调用运算符的优先级高于sizeof的优先级,所以本例无须添加括号。
|
||||
|
||||
## 练习4.31
|
||||
|
||||
本节的程序使用了前置版本的递增运算符和递减运算符,解释为什么要用前置版本而不用后置版本。要想使用后置版本的递增递减运算符需要做哪些改动?使用后置版本重写本节的程序。
|
||||
|
||||
解:
|
||||
|
||||
在4.5节(132页)已经说过了,除非必须,否则不用递增递减运算符的后置版本。在这里要使用后者版本的递增递减运算符不需要任何改动。
|
||||
|
||||
## 练习4.32
|
||||
解释下面这个循环的含义。
|
||||
|
||||
```cpp
|
||||
constexpr int size = 5;
|
||||
int ia[size] = { 1, 2, 3, 4, 5 };
|
||||
for (int *ptr = ia, ix = 0;
|
||||
ix != size && ptr != ia+size;
|
||||
++ix, ++ptr) { /* ... */ }
|
||||
```
|
||||
|
||||
解:
|
||||
|
||||
首先定义一个常量表达式size,它的的值是5;接着以size作为维度创建一个整型数组ia,5个元素分别是1~5。
|
||||
|
||||
for语句头包括三部分:第一部分定义整型指针指向数组ia的首元素,并且定义了一个整数ix,赋给它初值0;第二部了分判断循环终止的条件,当ix没有达到 size同时指针ptr没有指向数组最后一个元素的下一位置时,执行循环体;第三部分令变量ix和指针ptr分别执行递增操作。
|
||||
|
||||
## 练习4.33
|
||||
|
||||
根据4.12节中的表说明下面这条表达式的含义。
|
||||
|
||||
```cpp
|
||||
someValue ? ++x, ++y : --x, --y
|
||||
```
|
||||
|
||||
解:
|
||||
|
||||
逗号表达式的优先级是最低的。因此这条表达式也等于:
|
||||
```cpp
|
||||
(someValue ? ++x, ++y : --x), --y
|
||||
```
|
||||
如果`someValue`的值为真,`x` 和 `y` 的值都自增并返回 `y` 值,然后丢弃`y`值,`y`递减并返回`y`值。如果`someValue`的值为假,`x` 递减并返回`x` 值,然后丢弃`x`值,`y`递减并返回`y`值。
|
||||
|
||||
|
||||
|
||||
## 练习4.34
|
||||
|
||||
根据本节给出的变量定义,说明在下面的表达式中将发生什么样的类型转换:
|
||||
|
||||
```cpp
|
||||
(a) if (fval)
|
||||
(b) dval = fval + ival;
|
||||
(c) dval + ival * cval;
|
||||
```
|
||||
|
||||
需要注意每种运算符遵循的是左结合律还是右结合律。
|
||||
|
||||
解:
|
||||
|
||||
```cpp
|
||||
(a) fval 转换为 `bool` 类型,非0值转换为 `true`,0转换成 `false`
|
||||
(b) ival 转换为 `float` ,相加的结果转换为 `double`
|
||||
(c) cval 转换为 `int` ,然后相乘的结果转换为 `double`
|
||||
```
|
||||
|
||||
## 练习4.35
|
||||
|
||||
假设有如下的定义:
|
||||
|
||||
```cpp
|
||||
char cval;
|
||||
int ival;
|
||||
unsigned int ui;
|
||||
float fval;
|
||||
double dval;
|
||||
```
|
||||
|
||||
请回答在下面的表达式中发生了隐式类型转换吗?如果有,指出来。
|
||||
|
||||
```cpp
|
||||
(a) cval = 'a' + 3;
|
||||
(b) fval = ui - ival * 1.0;
|
||||
(c) dval = ui * fval;
|
||||
(d) cval = ival + fval + dval;
|
||||
```
|
||||
|
||||
解:
|
||||
|
||||
- (a) `'a'` 转换为 `int` ,然后与 `3` 相加的结果转换为 `char`
|
||||
- (b) `ival` 转换为 `double`,`ui` 转换为 `double`,结果转换为 `float`
|
||||
- (c) `ui` 转换为 `float`,结果转换为 `double`
|
||||
- (d) `ival` 转换为 `float`,与`fval`相加后的结果转换为 `double`,最后的结果转换为`char`
|
||||
|
||||
## 练习4.36
|
||||
|
||||
假设 `i` 是`int`类型,`d` 是`double`类型,书写表达式 `i*=d` 使其执行整数类型的乘法而非浮点类型的乘法。
|
||||
|
||||
解:
|
||||
|
||||
```cpp
|
||||
i *= static_cast<int>(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<void*>(const_cast<string*>(ps));
|
||||
(b) i = static_cast<int>(*pc);
|
||||
(c) pv = static_cast<void*>(&d);
|
||||
(d) pc = static_cast<char*>(pv);
|
||||
```
|
||||
|
||||
## 练习4.38
|
||||
|
||||
说明下面这条表达式的含义。
|
||||
|
||||
```cpp
|
||||
double slope = static_cast<double>(j/i);
|
||||
```
|
||||
|
||||
解:
|
||||
|
||||
将`j/i`的结果值转换为`double`,然后赋值给`slope`。
|
||||
|
||||
@ -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 <iostream>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
using namespace std;
|
||||
|
||||
int main()
|
||||
{
|
||||
int g;
|
||||
|
||||
cout <<"请输入成绩:"<<endl;
|
||||
|
||||
cin >> g;
|
||||
|
||||
if (g < 0 || g > 100) {
|
||||
cout << "该成绩不合法" <<endl;
|
||||
return -1;
|
||||
}
|
||||
if(g == 100) {
|
||||
cout << "等级成绩是:" << "A++" << endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(g < 60) {
|
||||
cout << "等级成绩是:" << "F" << endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int i = g /10;
|
||||
int j = g % 10;
|
||||
string score, level, finalGrade;
|
||||
|
||||
if(i == 9)
|
||||
score = "A";
|
||||
else if (i == 8)
|
||||
score = "B";
|
||||
else if (i == 7)
|
||||
score = "C";
|
||||
else
|
||||
score = "D";
|
||||
|
||||
if (j > 7) {
|
||||
level = "+";
|
||||
}else if (j < 3) {
|
||||
level = "-";
|
||||
}else {
|
||||
level = "";
|
||||
}
|
||||
finalGrade = score + level;
|
||||
|
||||
cout << "等级成绩是:" << finalGrade << endl;
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## 练习5.6
|
||||
|
||||
改写上一题的程序,使用条件运算符代替`if else`语句。
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
using namespace std;
|
||||
|
||||
int main()
|
||||
{
|
||||
|
||||
int g;
|
||||
|
||||
cout <<"请输入成绩:"<<endl;
|
||||
|
||||
cin >> g;
|
||||
|
||||
if (g < 0 || g > 100) {
|
||||
cout << "该成绩不合法" <<endl;
|
||||
return -1;
|
||||
}
|
||||
if(g == 100) {
|
||||
cout << "等级成绩是:" << "A++" << endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(g < 60) {
|
||||
cout << "等级成绩是:" << "F" << endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int i = g /10;
|
||||
int j = g % 10;
|
||||
string score, level, finalGrade;
|
||||
|
||||
score = (i == 9) ? "A" :(i == 8) ? "B": (i == 7)? "C":"D";
|
||||
level = (j > 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 <iostream>
|
||||
|
||||
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 <iostream>
|
||||
|
||||
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 <iostream>
|
||||
|
||||
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 <iostream>
|
||||
|
||||
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<int>(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 <iostream>
|
||||
|
||||
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 <iostream>
|
||||
#include <vector>
|
||||
|
||||
using std::cout; using std::vector;
|
||||
|
||||
bool is_prefix(vector<int> const& lhs, vector<int> 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<int> l{ 0, 1, 1, 2 };
|
||||
vector<int> 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 <iostream>
|
||||
#include <string>
|
||||
|
||||
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 <iostream>
|
||||
#include <string>
|
||||
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 <iostream>
|
||||
using std::cin; using std::cout; using std::endl;
|
||||
#include <string>
|
||||
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 <iostream>
|
||||
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 <iostream>
|
||||
#include <stdexcept>
|
||||
|
||||
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 <iostream>
|
||||
#include <stdexcept>
|
||||
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;
|
||||
}
|
||||
```
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -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 <iostream>
|
||||
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<string>& 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<string>& vec)
|
||||
{
|
||||
ifstream ifs(fileName);
|
||||
if (ifs)
|
||||
{
|
||||
string buf;
|
||||
while (ifs >> buf)
|
||||
vec.push_back(buf);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 练习8.6
|
||||
> 重写7.1.1节的书店程序,从一个文件中读取交易记录。将文件名作为一个参数传递给`main`。
|
||||
|
||||
解:
|
||||
|
||||
```cpp
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
#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 <fstream>
|
||||
#include <iostream>
|
||||
|
||||
#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 <fstream>
|
||||
#include <iostream>
|
||||
|
||||
#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 <iostream>
|
||||
#include <sstream>
|
||||
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 <iostream>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
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<string> 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 <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
using std::vector; using std::string; using std::cin; using std::istringstream;
|
||||
|
||||
struct PersonInfo {
|
||||
string name;
|
||||
vector<string> phones;
|
||||
};
|
||||
|
||||
int main()
|
||||
{
|
||||
string line, word;
|
||||
vector<PersonInfo> 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 <iostream>
|
||||
#include <sstream>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
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<string> 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<PersonInfo> 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`。
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,9 +0,0 @@
|
||||
---
|
||||
sort: 2
|
||||
---
|
||||
|
||||
# C++ Primer 第5版练习题解答
|
||||
|
||||
{% include list.liquid %}
|
||||
|
||||
|
||||
@ -1,4 +0,0 @@
|
||||
int main() {
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
int i = 10;
|
||||
while (i >= 0) {
|
||||
std::cout << i << std::endl;
|
||||
i--;
|
||||
}
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
#include <iostream>
|
||||
|
||||
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,9 +0,0 @@
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
int sum = 0;
|
||||
for (int i = -100; i <= 100; ++i) {
|
||||
sum += i;
|
||||
}
|
||||
std::cout << "sum is " << sum << std::endl;
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
#include <iostream>
|
||||
// 修改1.9
|
||||
int main() {
|
||||
int sum = 0;
|
||||
for (int i = 50; i <= 100; i++) {
|
||||
sum += i;
|
||||
}
|
||||
std::cout << "sum is " << sum << std::endl;
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
for (int i = 10; i >= 0; i--) {
|
||||
std::cout << i << std::endl;
|
||||
}
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
#include <iostream>
|
||||
|
||||
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,10 +0,0 @@
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
int sum = 0, value = 0;
|
||||
while(std::cin >> value) {
|
||||
sum += value;
|
||||
}
|
||||
std::cout << "sum is " << sum << std::endl;
|
||||
return 0;
|
||||
}
|
||||
@ -1,26 +0,0 @@
|
||||
#include <iostream>
|
||||
|
||||
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;
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
#include <iostream>
|
||||
|
||||
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;
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
return -1;
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
#include <iostream>
|
||||
#include "Sales_item.h"
|
||||
|
||||
int main() {
|
||||
|
||||
for(Sales_item item;std::cin >> item; std::cout << item << std::endl);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -1,16 +0,0 @@
|
||||
#include <iostream>
|
||||
#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;
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
#include <iostream>
|
||||
#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,28 +0,0 @@
|
||||
#include <iostream>
|
||||
#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 << "没有数据" <<std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
std::cout<< "Hello, World" <<std::endl;
|
||||
return 0;
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
#include <iostream>
|
||||
|
||||
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,15 +0,0 @@
|
||||
#include <iostream>
|
||||
|
||||
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,10 +0,0 @@
|
||||
#include <iostream>
|
||||
|
||||
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;
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
/* 正常注释 /* 嵌套注释 */ 正常注释*/
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
std::cout << "/*";
|
||||
std::cout << "*/";
|
||||
std::cout << /* "*/ " */";
|
||||
std::cout << /* "*/" /* "/*" */;
|
||||
}
|
||||
@ -1,10 +0,0 @@
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
int i = 50, sum = 0;
|
||||
while (i <= 100) {
|
||||
sum += i;
|
||||
i += 1;
|
||||
}
|
||||
std::cout << "sum is " << sum << std::endl;
|
||||
}
|
||||
@ -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 <iostream>
|
||||
#include <string>
|
||||
|
||||
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
|
||||
@ -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<T> 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
|
||||
@ -1,15 +0,0 @@
|
||||
#include <iostream>
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
#include <iostream>
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
#include <iostream>
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
#include <iostream>
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
#include <iostream>
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
#include <iostream>
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
@ -1,29 +0,0 @@
|
||||
#include <iostream>
|
||||
#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 << "没有数据" <<std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1,14 +0,0 @@
|
||||
#include <iostream>
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
#include <iostream>
|
||||
|
||||
|
||||
int main() {
|
||||
std::cout << "2\x4d\012";
|
||||
std::cout <<"2\tM\n";
|
||||
return 0;
|
||||
}
|
||||
@ -1,72 +0,0 @@
|
||||
//
|
||||
// Created by ChenYL on 2022/5/14.
|
||||
//
|
||||
|
||||
#ifndef PAT_SALES_DATA_H
|
||||
#define PAT_SALES_DATA_H
|
||||
# include <iostream>
|
||||
# include <string>
|
||||
|
||||
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
|
||||
@ -1,11 +0,0 @@
|
||||
#include <iostream>
|
||||
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;
|
||||
}
|
||||
@ -1,16 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main() {
|
||||
string s;
|
||||
cout << "请输入一个字符串" <<endl;
|
||||
getline(cin, s);
|
||||
for (auto c : s) {
|
||||
if (!ispunct(c))
|
||||
cout << c
|
||||
}
|
||||
cout << endl;
|
||||
return 0;
|
||||
}
|
||||
@ -1,26 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
using std::vector;
|
||||
|
||||
int main() {
|
||||
vector<int> 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 << "输入的整数为" <<m << endl;
|
||||
return 0;
|
||||
}
|
||||
@ -1,27 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
using std::vector;
|
||||
|
||||
int main() {
|
||||
vector<string> 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;
|
||||
}
|
||||
@ -1,74 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main()
|
||||
{
|
||||
vector<int> v1; // size:0, no values.
|
||||
vector<int> v2(10); // size:10, value:0
|
||||
vector<int> v3(10, 42); // size:10, value:42
|
||||
vector<int> v4{ 10 }; // size:1, value:10
|
||||
vector<int> v5{ 10, 42 }; // size:2, value:10, 42
|
||||
vector<string> v6{ 10 }; // size:10, value:""
|
||||
vector<string> 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;
|
||||
}
|
||||
@ -1,31 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
using std::vector;
|
||||
|
||||
int main() {
|
||||
vector<string> 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;
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main() {
|
||||
// string line;
|
||||
// cout << "请输入字符串" <<endl;
|
||||
// while (getline(cin, line)) { // 读入一整行
|
||||
// cout << line << endl;
|
||||
// }
|
||||
string word;
|
||||
while (cin >> word) { // 读入一个词
|
||||
cout << word << endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -1,37 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
using std::vector;
|
||||
|
||||
int main() {
|
||||
vector<int> v;
|
||||
int w;
|
||||
cout << "请输入数字" << endl;
|
||||
while (cin >> w) {
|
||||
v.push_back(w);
|
||||
}
|
||||
if (v.size() == 0) {
|
||||
cout << "没有任何元素" <<endl;
|
||||
return -1;
|
||||
}
|
||||
cout << "相邻一组数的和为:" <<endl;
|
||||
for(decltype((v.size())) i = 0; i < v.size() - 1; i += 2)
|
||||
cout << v[i] + v[i + 1] << " ";
|
||||
|
||||
if (v.size() % 2 != 0)
|
||||
cout << v[v.size() - 1];
|
||||
cout <<endl;
|
||||
|
||||
cout << "首尾两项数的和为:" <<endl;
|
||||
decltype(v.size()) i = 0, j = v.size() - 1;
|
||||
while (i < j) {
|
||||
cout << v[i] +v[j] << " ";
|
||||
++i,--j;
|
||||
}
|
||||
if (v.size() % 2 != 0)
|
||||
cout << v[v.size() / 2];
|
||||
cout <<endl;
|
||||
return 0;
|
||||
}
|
||||
@ -1,85 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main()
|
||||
{
|
||||
vector<int> v1; // size:0, no values.
|
||||
vector<int> v2(10); // size:10, value:0
|
||||
vector<int> v3(10, 42); // size:10, value:42
|
||||
vector<int> v4{ 10 }; // size:1, value:10
|
||||
vector<int> v5{ 10, 42 }; // size:2, value:10, 42
|
||||
vector<string> v6{ 10 }; // size:10, value:""
|
||||
vector<string> 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;
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
using std::vector;
|
||||
|
||||
int main() {
|
||||
vector<string> 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;
|
||||
}
|
||||
@ -1,27 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
using std::vector;
|
||||
|
||||
int main() {
|
||||
|
||||
vector<int> words;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
words.push_back(rand() % 1000);
|
||||
}
|
||||
cout << "随机生成的数字是:" <<endl;
|
||||
for(auto it = words.begin(); it != words.end() && !words.empty(); it++) {
|
||||
cout << *it << " ";
|
||||
}
|
||||
cout <<endl;
|
||||
|
||||
cout << "翻倍后的数字是:" <<endl;
|
||||
|
||||
for(auto it = words.begin(); it != words.end() && !words.empty(); it++) {
|
||||
*it = *it * 2;
|
||||
cout << *it << " ";
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@ -1,31 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
using std::vector;
|
||||
|
||||
int main() {
|
||||
vector<int> v;
|
||||
int w;
|
||||
cout << "请输入数字" << endl;
|
||||
while (cin >> w) {
|
||||
v.push_back(w);
|
||||
}
|
||||
if (v.cbegin() == v.cend()) {
|
||||
cout << "没有任何元素" <<endl;
|
||||
return -1;
|
||||
}
|
||||
cout << "首尾两项的和为:" <<endl;
|
||||
auto begin = v.begin();
|
||||
auto end = v.end();
|
||||
for(auto it = begin; it != begin + (end - begin) / 2; it ++){
|
||||
cout << (*it + *(begin + (end - it) - 1))<< " ";
|
||||
|
||||
if (v.size() % 2 != 0)
|
||||
cout << *(begin + (end - it + 1)/ 2);
|
||||
cout <<endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -1,24 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
using std::vector;
|
||||
|
||||
int main() {
|
||||
vector<unsigned> v(11);
|
||||
auto it = v.begin();
|
||||
int i;
|
||||
cout << "请输入一组成绩"<<endl;
|
||||
while(cin >> i) {
|
||||
if (i < 101) {
|
||||
++*(it + i / 10);
|
||||
}
|
||||
}
|
||||
cout << "各分数段的人数分布式" << endl;
|
||||
for (it = v.begin(); it!= v.end();it++) {
|
||||
cout << *it << " ";
|
||||
}
|
||||
cout << endl;
|
||||
return 0;
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
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;
|
||||
}
|
||||
@ -1,38 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
using std::vector;
|
||||
|
||||
int main() {
|
||||
const int sz = 10;
|
||||
vector<int> num3;
|
||||
vector<int> 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;
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
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;
|
||||
}
|
||||
@ -1,42 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <iterator>
|
||||
|
||||
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<int> vec1 = { 0, 1, 2 };
|
||||
vector<int> 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;
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main()
|
||||
{
|
||||
string str1, str2;
|
||||
cout << "请输入两个字符串" <<endl;
|
||||
cin >> str1 >> str2;
|
||||
|
||||
if (str1 > str2)
|
||||
cout << "第一个字符串大于第二个字符串" << endl;
|
||||
else if (str1 < str2)
|
||||
cout << "第一个字符串小于第二个字符串" << endl;
|
||||
else
|
||||
cout << "第一个字符串等于第二个字符串" << endl;
|
||||
return 0;
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main()
|
||||
{
|
||||
char str1[80], str2[80];
|
||||
|
||||
cout << "请输入两个字符串" <<endl;
|
||||
cin >> 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;
|
||||
}
|
||||
@ -1,31 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
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 <<endl;
|
||||
}
|
||||
auto len1 = str1.size();
|
||||
auto len2 = str2.size();
|
||||
|
||||
if (len1 == len2) {
|
||||
cout << str1 << "和" << str2 << "的长度是" << len2 << endl;
|
||||
}else if (len1 > len2)
|
||||
cout << str1 << "比" << str2 << "的长度多" << len1 - len2 << endl;
|
||||
else
|
||||
cout << str1 << "比" << str2 << "的长度少" << len2 - len1 << endl;
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -1,21 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
|
||||
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 <<endl;
|
||||
cout << "第二个字符为" << str2 <<endl;
|
||||
cout << "拼接后的字符为" << result <<endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main()
|
||||
{
|
||||
|
||||
int nums[] = {1, 2, 3, 4} ;
|
||||
|
||||
vector<int> words(begin(nums), end(nums));
|
||||
|
||||
for (auto val : words) {
|
||||
cout << val << " ";
|
||||
}
|
||||
cout << endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main()
|
||||
{
|
||||
|
||||
|
||||
const int sz = 10;
|
||||
vector<int> 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;
|
||||
}
|
||||
@ -1,31 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
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;
|
||||
}
|
||||
@ -1,32 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
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;
|
||||
}
|
||||
@ -1,32 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
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;
|
||||
}
|
||||
@ -1,27 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
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 << "拼接后的字符为" <<result << endl;
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main() {
|
||||
string s;
|
||||
cout << "请输入一个字符串" <<endl;
|
||||
getline(cin, s);
|
||||
for (auto &c : s) {
|
||||
c = 'X';
|
||||
}
|
||||
cout << s << endl;
|
||||
return 0;
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main() {
|
||||
string s;
|
||||
cout << "请输入一个字符串" <<endl;
|
||||
getline(cin, s);
|
||||
for (char &c : s) {
|
||||
c = 'X';
|
||||
}
|
||||
cout << s << endl;
|
||||
return 0;
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
using namespace std;
|
||||
// while
|
||||
int main() {
|
||||
string s;
|
||||
cout << "请输入一个字符串" <<endl;
|
||||
getline(cin, s);
|
||||
int i = 0;
|
||||
while (s[i] !='\0') {
|
||||
s[i] = 'X';
|
||||
++i;
|
||||
}
|
||||
cout << s << endl;
|
||||
return 0;
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main() {
|
||||
string s;
|
||||
cout << "请输入一个字符串" <<endl;
|
||||
getline(cin, s);
|
||||
for (unsigned int i = 0; i < s.size(); i++) {
|
||||
s[i] = 'X';
|
||||
}
|
||||
cout << s << endl;
|
||||
return 0;
|
||||
}
|
||||
@ -1,31 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <ctime>
|
||||
#include <cstdlib>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main() {
|
||||
vector<int> 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 <<endl;
|
||||
|
||||
for(auto &val : vInt) {
|
||||
val = (val % 2 == 0) ? val : val * 2;
|
||||
}
|
||||
cout <<"最终数组为" << endl;
|
||||
|
||||
for(auto it = vInt.cbegin(); it != vInt.cend(); ++it) {
|
||||
cout << *it << " ";
|
||||
}
|
||||
cout <<endl;
|
||||
return 0;
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <ctime>
|
||||
#include <cstdlib>
|
||||
|
||||
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;
|
||||
}
|
||||
@ -1,24 +0,0 @@
|
||||
#include <iostream>
|
||||
|
||||
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;
|
||||
}
|
||||
@ -1,27 +0,0 @@
|
||||
#include <iostream>
|
||||
|
||||
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;
|
||||
}
|
||||
@ -1,58 +0,0 @@
|
||||
#include <iostream>
|
||||
|
||||
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;
|
||||
}
|
||||
@ -1,39 +0,0 @@
|
||||
#include <iostream>
|
||||
|
||||
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;
|
||||
}
|
||||
@ -1,32 +0,0 @@
|
||||
#include <iostream>
|
||||
|
||||
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;
|
||||
}
|
||||
@ -1,53 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
using namespace std;
|
||||
|
||||
int main()
|
||||
{
|
||||
|
||||
int g;
|
||||
|
||||
cout <<"请输入成绩:"<<endl;
|
||||
|
||||
cin >> g;
|
||||
|
||||
if (g < 0 || g > 100) {
|
||||
cout << "该成绩不合法" <<endl;
|
||||
return -1;
|
||||
}
|
||||
if(g == 100) {
|
||||
cout << "等级成绩是:" << "A++" << endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(g < 60) {
|
||||
cout << "等级成绩是:" << "F" << endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int i = g /10;
|
||||
int j = g % 10;
|
||||
string score, level, finalGrade;
|
||||
|
||||
if(i == 9)
|
||||
score = "A";
|
||||
else if (i == 8)
|
||||
score = "B";
|
||||
else if (i == 7)
|
||||
score = "C";
|
||||
else
|
||||
score = "D";
|
||||
|
||||
if (j > 7) {
|
||||
level = "+";
|
||||
}else if (j < 3) {
|
||||
level = "-";
|
||||
}else {
|
||||
level = "";
|
||||
}
|
||||
finalGrade = score + level;
|
||||
|
||||
cout << "等级成绩是:" << finalGrade << endl;
|
||||
return 0;
|
||||
}
|
||||
@ -1,40 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
using namespace std;
|
||||
|
||||
int main()
|
||||
{
|
||||
|
||||
int g;
|
||||
|
||||
cout <<"请输入成绩:"<<endl;
|
||||
|
||||
cin >> g;
|
||||
|
||||
if (g < 0 || g > 100) {
|
||||
cout << "该成绩不合法" <<endl;
|
||||
return -1;
|
||||
}
|
||||
if(g == 100) {
|
||||
cout << "等级成绩是:" << "A++" << endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(g < 60) {
|
||||
cout << "等级成绩是:" << "F" << endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int i = g /10;
|
||||
int j = g % 10;
|
||||
string score, level, finalGrade;
|
||||
|
||||
score = (i == 9) ? "A" :(i == 8) ? "B": (i == 7)? "C":"D";
|
||||
level = (j > 7) ? "+" : (j < 3)? "-": "";
|
||||
|
||||
finalGrade = score + level;
|
||||
|
||||
cout << "等级成绩是:" << finalGrade << endl;
|
||||
return 0;
|
||||
}
|
||||
@ -1,27 +0,0 @@
|
||||
#include <iostream>
|
||||
|
||||
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;
|
||||
}
|
||||
@ -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=<lib> Display the full path to library <lib>
|
||||
-print-prog-name=<prog> Display the full path to compiler component <prog>
|
||||
-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,<options> Pass comma-separated <options> on to the assembler
|
||||
-Wp,<options> Pass comma-separated <options> on to the preprocessor
|
||||
-Wl,<options> Pass comma-separated <options> on to the linker
|
||||
-Xassembler <arg> Pass <arg> on to the assembler
|
||||
-Xpreprocessor <arg> Pass <arg> on to the preprocessor
|
||||
-Xlinker <arg> Pass <arg> on to the linker
|
||||
-save-temps Do not delete intermediate files
|
||||
-save-temps=<arg> 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=<file> Override built-in specs with the contents of <file>
|
||||
-std=<standard> Assume that the input sources are for <standard>
|
||||
--sysroot=<directory> Use <directory> as the root directory for headers
|
||||
and libraries
|
||||
-B <directory> Add <directory> 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 <file> Place the output into <file>
|
||||
-pie Create a position independent executable
|
||||
-shared Create a shared library
|
||||
-x <language> 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 <iostream>```
|
||||
- ```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 <infile >outfile``
|
||||
|
||||
|
||||
|
||||
@ -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" <<std::endl;
|
||||
```
|
||||
- 转义序列。`\n`、`\t`等。
|
||||
- 布尔字面值。`true`,`false`。
|
||||
- 指针字面值。`nullptr`
|
||||
|
||||
> 字符串型实际上时常量字符构成的数组,结尾处以`'\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
|
||||
```
|
||||
@ -1,242 +0,0 @@
|
||||
---
|
||||
sort: 3
|
||||
---
|
||||
|
||||
# 字符串、向量和数组
|
||||
|
||||
## using声明
|
||||
- 使用某个命名空间:例如 `using std::cin`表示使用命名空间`std`中的名字`cin`。
|
||||
- 头文件中不应该包含`using`声明。这样使用了该头文件的源码也会使用这个声明,会带来风险。
|
||||
|
||||
## string
|
||||
- 标准库类型`string`表示可变长的字符序列。
|
||||
- `#include <string>`,然后 `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 <vector>` 然后 `using std::vector;`
|
||||
- 容器:包含其他对象。
|
||||
- 类模板:本身不是类,但可以**实例化instantiation**出一个类。 `vector`是一个模板, `vector<int>`是一个类型。
|
||||
- 通过将类型放在类模板名称后面的**尖括号**中来指定**类型**,如`vector<int> ivec`。
|
||||
|
||||
### 定义和初始化vector对象
|
||||
|
||||
初始化`vector`对象的方法
|
||||
|
||||
| 方法 | 解释 |
|
||||
| --------------------------- | ------------------------------------------------------------- |
|
||||
| `vector<T> v1` | `v1`是一个空`vector`,它潜在的元素是`T`类型的,执行默认初始化 |
|
||||
| `vector<T> v2(v1)` | `v2`中包含有`v1`所有元素的副本 |
|
||||
| `vector<T> v2 = v1` | 等价于`v2(v1)`,`v2`中包含`v1`所有元素的副本 |
|
||||
| `vector<T> v3(n, val)` | `v3`包含了n个重复的元素,每个元素的值都是`val` |
|
||||
| `vector<T> v4(n)` | `v4`包含了n个重复地执行了值初始化的对象 |
|
||||
| `vector<T> v5{a, b, c...}` | `v5`包含了初始值个数的元素,每个元素被赋予相应的初始值 |
|
||||
| `vector<T> v5={a, b, c...}` | 等价于`v5{a, b, c...}` |
|
||||
|
||||
- 列表初始化: `vector<string> 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<int>::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<int> v(begin(a), end(a));` 。
|
||||
|
||||
### 数组和指针
|
||||
|
||||
- 使用数组时,编译器一般会把它转换成指针。
|
||||
- 标准库类型限定使用的下标必须是无符号类型,而内置的下标可以处理负值。
|
||||
- **指针访问数组**:在表达式中使用数组名时,名字会自动转换成指向数组的第一个元素的指针。
|
||||
|
||||
## C风格字符串
|
||||
|
||||
- 从C继承来的字符串。
|
||||
- 用空字符结束(`\0`)。
|
||||
- 对大多数应用来说,使用标准库 `string`比使用C风格字符串更安全、更高效。
|
||||
- 获取 `string` 中的 `cstring` : `const char *str = s.c_str();` 。
|
||||
|
||||
C标准库String函数,定义在`<cstring>` 中:
|
||||
|
||||
| 函数 | 介绍 |
|
||||
| ---------------- | --------------------------------------------------------------------------------------------------- |
|
||||
| `strlen(p)` | 返回`p`的长度,空字符不计算在内 |
|
||||
| `strcmp(p1, p2)` | 比较`p1`和`p2`的相等性。如果`p1==p2`,返回0;如果`p1>p2`,返回一个正值;如果`p1<p2`,返回一个负值。 |
|
||||
| `strcat(p1, p2)` | 将`p2`附加到`p1`之后,返回`p1` |
|
||||
| `strcpy(p1, p2)` | 将`p2`拷贝给`p1`,返回`p1` |
|
||||
|
||||
**尽量使用vector和迭代器,少用内置数组和指针;应尽量使用string,避免使用C风格的基于数组的字符串**
|
||||
|
||||
## 多维数组
|
||||
|
||||
- **多维数组的初始化**:
|
||||
|
||||
- 使用范围for语句时,除了最内层的循环外,其他所有循环的控制变量都应该是**引用**类型。
|
||||
|
||||
## 指针vs引用
|
||||
|
||||
- 引用总是指向某个对象,定义引用时没有初始化是错的。
|
||||
- 给引用赋值,修改的是该引用所关联的对象的值,而不是让引用和另一个对象相关联。
|
||||
|
||||
## 指向指针的指针
|
||||
|
||||
- 定义: `int **ppi = π`
|
||||
- 解引用:`**ppi`
|
||||
|
||||
## 动态数组
|
||||
|
||||
- 使用 `new`和 `delete`表达和c中`malloc`和`free`类似的功能,即在堆(自由存储区)中分配存储空间。
|
||||
- 定义: `int *pia = new int[10];` 10可以被一个变量替代。
|
||||
- 释放: `delete [] pia;`,注意不要忘记`[]`。
|
||||
|
||||
|
||||
|
||||
|
||||
@ -1,207 +0,0 @@
|
||||
---
|
||||
sort: 4
|
||||
---
|
||||
|
||||
# 表达式
|
||||
|
||||
## 表达式基础
|
||||
|
||||
- **运算对象转换**:小整数类型会被提升为较大的整数类型
|
||||
- **重载运算符**:当运算符作用在类类型的运算对象时,用户可以自行定义其含义。
|
||||
- **左值和右值**:
|
||||
- C中原意:左值**可以**在表达式左边,右值不能。
|
||||
- `C++`:当一个对象被用作**右值**的时候,用的是对象的**值**(内容);
|
||||
- 被用做**左值**时,用的是对象的**身份**(在内存中的位置)。
|
||||
- **求值顺序**:`int i = f1() + f2()`
|
||||
- 先计算`f1() + f2()`,再计算`int i = f1() + f2()`。但是f1和f2的计算**先后不确定**
|
||||
- 但是,如果f1、f2都对同一对象进行了修改,因为顺序不确定,所以会编译出错,显示未定义
|
||||
- 有4种运算符明确规定了运算对象的求值顺序。
|
||||
- 逻辑与`&&`
|
||||
- 逻辑或`||`
|
||||
- 条件运算符`?:`
|
||||
- 逗号运算符`,`
|
||||
|
||||
|
||||
## 算术运算符
|
||||
|
||||
- **溢出**:当计算的结果超出该类型所能表示的范围时就会产生溢出。
|
||||
- **bool类型不应该参与计算**
|
||||
```cpp
|
||||
bool b=true;
|
||||
bool b2=-b; //仍然为true
|
||||
//b为true,提升为对应int=1,-b=-1
|
||||
//b2=-1≠0,所以b2仍未true
|
||||
```
|
||||
- 取余运算m%n,结果符号与m相同
|
||||
|
||||
## 逻辑运算符
|
||||
|
||||
- **短路求值**:逻辑与运算符和逻辑或运算符都是先求左侧运算对象的值再求右侧运算对象的值,当且仅当左侧运算对象无法确定表达式的结果时才会计算右侧运算对象的值。**先左再右**
|
||||
- 对于逻辑与运算符来说,当且仅当左侧运算对象为真时才对右侧运算对象求值。
|
||||
- 对于逻辑或运算符来说,当且仅当左侧运算对象为假时才对右侧运算对象求值。
|
||||
- 小技巧,声明为引用类型可以避免对元素的拷贝,如下,如string特别大时可以节省大量时间。
|
||||
|
||||
```cpp
|
||||
vector<string> text;
|
||||
for(const auto &s: text){
|
||||
cout<<s;
|
||||
}
|
||||
```
|
||||
## 相等性测试
|
||||
|
||||
- 测试一个算法对象或指针对象的真值,最直接的方法就是将其作为if语句的条件;
|
||||
- ```if(val) {} // 如果val是任意的非0值,条件为真```
|
||||
- ```if(!val) {} // 如果val是0,条件为真```
|
||||
|
||||
## 赋值运算符
|
||||
|
||||
- 赋值运算的**返回结果时它的左侧运算对象**,且是一个左值。类型也就是左侧对象的类型。
|
||||
- 如果赋值运算的左右侧运算对象类型不同,则右侧运算对象将转换成左侧运算对象的类型。
|
||||
- 赋值运算符满足**右结合律**,这点和其他二元运算符不一样。 `ival = jval = 0;`等价于`ival = (jval = 0);`
|
||||
- 赋值运算优先级比较低,使用其当条件时应该加括号。
|
||||
- 复合赋值运算符,**复合运算符只求值一次**,普通运算符求值两次。
|
||||
任意复合运算符op等价于`a = a op b;`
|
||||
|
||||
## 递增递减运算符
|
||||
|
||||
* 前置版本`j = ++i`,先加一后赋值
|
||||
* 后置版本`j = i++`,先赋值后加一
|
||||
|
||||
**优先使用前置**版本,后置多一步储存原始值。(除非需要变化前的值)
|
||||
|
||||
### 混用解引用和递增运算符
|
||||
|
||||
`*iter++`等价于`*(iter++)`,递增优先级较高
|
||||
|
||||
```c++
|
||||
auto iter = vi.begin();
|
||||
while (iter!=vi.end()&&*iter>=0)
|
||||
cout<<*iter++<<endl; // 输出当前值,指针向前移1
|
||||
```
|
||||
|
||||
> **简介是一种美德**,追求简洁能降低程序出错可能性
|
||||
|
||||
## 成员访问运算符
|
||||
|
||||
`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<double>(j);`
|
||||
|
||||
- **dynamic_cast**:支持运行时类型识别。
|
||||
|
||||
- **const_cast**:只能改变运算对象的底层const,一般可用于去除const性质。 `const char *pc; char *p = const_cast<char*>(pc)`
|
||||
|
||||
> 只有其可以改变常量属性
|
||||
|
||||
- **reinterpret_cast**:通常为运算对象的位模式提供低层次上的重新解释。
|
||||
|
||||
#### 旧式强制类型转换
|
||||
|
||||
`type expr`
|
||||
|
||||
## 运算符优先级表
|
||||
|
||||

|
||||
|
||||

|
||||
@ -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`子句之间传递异常的具体信息。
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user