OSDN Git Service

Add docs for iterator
authorJacob Zhong <cmpute@qq.com>
Thu, 5 Sep 2019 01:34:28 +0000 (21:34 -0400)
committerJacob Zhong <cmpute@qq.com>
Thu, 5 Sep 2019 01:34:28 +0000 (21:34 -0400)
docs/lang/csl/index.md
docs/lang/csl/iterator.md
docs/lang/csl/sequence-container.md
docs/lang/op.md

index 55fb0ab..adb4bf4 100644 (file)
@@ -1,11 +1,11 @@
 
 ## C++ 标准
 
-首先需要介绍的是 C++ 本身的版本。由于 C++ 本身只是一门语言,而不同的编译器对 C++ 的实现方法各不一致,因此需要标准化来约束编译器的实现,使得 C++ 代码在不同的编译器下表现一致。C++ 自1985年诞生以来,一共由国际标准化组织( ISO )发布了 5 个正式的 C++ 标准,分别为 C++ 98、C++ 03、C++ 11 (亦称 C++ 0x)、C++ 14 (亦称 C++1y)、C++ 17 (亦称 C++ 1z),最新的标准 C++ 20 仍在制定中。此外还有一些补充标准,例如 C++ TR1。
+首先需要介绍的是 C++ 本身的版本。由于 C++ 本身只是一门语言,而不同的编译器对 C++ 的实现方法各不一致,因此需要标准化来约束编译器的实现,使得 C++ 代码在不同的编译器下表现一致。C++ 自1985年诞生以来,一共由国际标准化组织( ISO )发布了 5 个正式的 C++ 标准,依次为 C++98、C++03、C++11 (亦称 C++0x)、C++14 (亦称 C++1y)、C++17 (亦称 C++1z),最新的标准 C++20 仍在制定中。此外还有一些补充标准,例如 C++ TR1。
 
 每一个版本的 C++ 标准不仅规定了 C++ 的语法、语言特性,还规定了一套 C++ 内置库的实现规范,这个库便是 C++ 标准库。C++ 标准库中包含大量常用代码的实现,如输入输出、基本数据结构、内存管理、多线程支持等。掌握 C++ 标准库是编写更现代的 C++ 代码必要的一步。C++ 标准库的详细文档在 [cppreference](https://zh.cppreference.com/) 网站上,文档对标准库中的类型函数的用法、效率、注意事项等都有介绍,请善用。
 
-需要指出的是,不同的 OJ 平台对 C++ 版本均不相同,例如[最新的 ACM 比赛规则](https://icpc.baylor.edu/worldfinals/programming-environment)支持 C++ 14 标准,而 [NOI 现行规则](http://www.noi.cn/newsview.html?id=559&hash=E4E249) 中指定的 g++ 4.8 [默认支持标准](https://gcc.gnu.org/onlinedocs/gcc-4.8.5/gcc/Standards.html#Standards)是 C++ 98。因此在学习 C++ 时要注意比赛支持的标准,避免在赛场上时编译报错。
+需要指出的是,不同的 OJ 平台对 C++ 版本均不相同,例如[最新的 ACM 比赛规则](https://icpc.baylor.edu/worldfinals/programming-environment)支持 C++14 标准,而 [NOI 现行规则](http://www.noi.cn/newsview.html?id=559&hash=E4E249) 中指定的 g++ 4.8 [默认支持标准](https://gcc.gnu.org/onlinedocs/gcc-4.8.5/gcc/Standards.html#Standards)是 C++98。因此在学习 C++ 时要注意比赛支持的标准,避免在赛场上时编译报错。
 
 ## 标准模板库( STL )
 
@@ -16,7 +16,7 @@ STL 即标准模板库(Standard Template Library),是 C++ 标准库的一
 
 ## Boost 库
 
-[Boost](https://www.boost.org/) 是除了标准库外,另一个可移植的、高质量、高性能、高可靠性的 C++ 工具库。Boost 中的模块数量非常之大,并且拥有完备的跨平台支持,因此被看作 C++ 的准标准库。C++ 标准中的不少特性也都来自于 Boost,如智能指针、元编程、日期和时间等。尽管在 OI 中无法使用 Boost ,但是 Boost 中有不少轮子可以用来验证算法或者对拍,如 Boost.Geometry 有 R 树的实现, Boost.Graph 有图的相关算法, Boost.Intrusive 则提供了一套与 STL 容器用法相似的侵入式容器
+[Boost](https://www.boost.org/) 是除了标准库外,另一个久副盛名的开源 C++ 工具库,其代码具有可移植、高质量、高性能、高可靠性等特点。Boost 中的模块数量非常之大,功能全面,并且拥有完备的跨平台支持,因此被看作 C++ 的准标准库。C++ 标准中的不少特性也都来自于 Boost,如智能指针、元编程、日期和时间等。尽管在 OI 中无法使用 Boost ,但是 Boost 中有不少轮子可以用来验证算法或者对拍,如 Boost.Geometry 有 R 树的实现, Boost.Graph 有图的相关算法, Boost.Intrusive 则提供了一套与 STL 容器用法相似的侵入式容器。有兴趣的读者可以自行在网络搜索教程
 
 ## 参考资料
 
index 8b13789..620a126 100644 (file)
@@ -1 +1,35 @@
 
+在 STL 中,迭代器( Iterator )是用来访问和检查 STL 容器中元素的对象,它的行为模式和指针类似,甚至有不少容器的迭代器类型就是指针。但是它封装了一些有效性检查,并且提供了统一的访问格式。迭代器尽管在使用上与常规的下标访问效果相似,但是由于部分 STL 容器(如 `std::list` )的下标访问有很大的开销,这时在顺序访问的情况下迭代器会比下标访问更加高效。类似的概念在其他很多高级语言中都存在,如 Python 的 `__iter__` 函数, C# 的 `IEnumerator`。
+
+## 使用方法
+
+迭代器听起来比较晦涩,其实迭代器本身可以看作一个数据指针。迭代器主要有两个操作:自增和解引用( 单目 `*` [运算符](../op.md)),其中自增用来移动迭代器,解引用可以获取或修改它指向的元素。
+最常用的使用方法是用迭代器替换普通的 `for` 循环,例如下列代码中两个循环的效果是一致的。(假设已经引用了 `std` 空间中的相关类型)
+
+```cpp
+vector<int> data(10);
+
+for(int i = 0; i < 10; i++)
+    cout << data[i] << endl; // 使用下标访问元素
+
+for(vector<int>::iterator iter = data.begin();
+    iter != data.end(); iter++)
+    cout << *iter << endl; // 使用迭代器访问元素
+// 在C++11后可以使用 auto iter = data.begin() 来简化上述代码
+```
+
+## 分类
+
+在 STL 的定义中,迭代器根据其支持的操作依次分为以下几类:
+
+- InputIterator:只要求支持拷贝、自增和解引访问
+- OutputIterator:只要求支持拷贝、自增和解引赋值
+- ForwardIterator:即同时满足 InputIterator 和 OutputIterator 的要求
+- Bidirectional:在 ForwardIterator 的基础上支持自减(即反向访问)
+- RandomAccess:在 Bidirectional 的基础上支持加减运算和比较运算(即随机访问)
+
+不同的 STL 容器支持的迭代器类型不同,在使用时需要留意。
+
+## 获取途径
+
+STL 容器一般支持从一端或两端开始的访问,以及对 const 修饰符的支持。例如容器的 `begin()` 函数可以获得指向容器头部的迭代器,而 `rend()` 函数可以获得指向容器头部,并且支持反向访问的迭代器。
index 2502cc8..ea68d31 100644 (file)
@@ -44,7 +44,7 @@ vector<int> v3(3, 1, v2.get_allocator());
 vector<int> v4(v2);
 // 6. 创建一个v4的拷贝vector v5,其内容是{v4[1], v4[2]}; 线性复杂度
 vector<int> v5(v4.begin() + 1, v4.begin() + 3);
-// 7. 移动v2到新创建的vector v6,不发生拷贝; 常数复杂度; 需要 C++ 11
+// 7. 移动v2到新创建的vector v6,不发生拷贝; 常数复杂度; 需要 C++11
 vector<int> v6(std::move(v2));  // 或者 v6 = std::move(v2);
 ```
 
@@ -148,19 +148,19 @@ vector 提供了如下几种 [迭代器](./iterator.md)
 ###  `vector` 的实现细节
 
  `vector` 的底层其实仍然是定长数组,它能够实现动态扩容的原因是增加了避免数量溢出的操作。首先需要指明的是 `vector` 中元素的数量(长度) $n$ 与它已分配内存最多能包含元素的数量(容量) $N$ 是不一致的, `vector` 会分开存储这两个量。当向 `vector` 中添加元素时,如发现 $n>N$ ,那么容器会分配一个尺寸为 $2N$ 的数组,然后将旧数据从原本的位置拷贝到新的数组中,再将原来的内存释放。尽管这个操作的渐进复杂度是 $O(n)$ ,但是可以证明其均摊复杂度为 $O(1)$ 。而在末尾删除元素和访问元素则都仍然是 $O(1)$ 的开销。
-因此,只要对 `vector` 的尺寸估计得当并善用 `reserve()` 和 `shrink_to_fit()` 函数(C++ 11),就能使得 `vector` 的效率与定长数组不会有太大差距。
+因此,只要对 `vector` 的尺寸估计得当并善用 `resize()` 和 `reserve()` ,就能使得 `vector` 的效率与定长数组不会有太大差距。
 
 ###  `vector<bool>` 
 
 标准库特别提供了对 `bool` 的 `vector` 特化,每个“ `bool` ”只占 1 bit,且支持动态增长。但是其 `operator[]` 的返回值的类型不是 `bool&` 而是 `vector<bool>::reference` 。因此,使用 `vector<bool>` 使需谨慎,可以考虑使用 `deque<bool>` 或 `vector<char>` 替代。而如果你需要节省空间,请直接使用 [ `bitset` ](./bitset.md) 。
 
-##  `array` (C++ 11)
+##  `array` (C++11)
 
  `std::array` 是 STL 提供的 **内存连续的** 、 **固定长度** 的数组数据结构。其本质是对原生数组的直接封装。
 
 ### 为什么要用 `array` 
 
- `array` 实际上是 STL 对数组的封装。它相比 `vector` 牺牲了动态扩容的特性,但是换来了与原生数组几乎一致的性能(在开满优化的前提下)。因此如果能使用 C++ 11 特性的情况下,能够使用原生数组的地方几乎都可以直接把定长数组都换成 `array` ,而动态分配的数组可以替换为 `vector` 。
+ `array` 实际上是 STL 对数组的封装。它相比 `vector` 牺牲了动态扩容的特性,但是换来了与原生数组几乎一致的性能(在开满优化的前提下)。因此如果能使用 C++11 特性的情况下,能够使用原生数组的地方几乎都可以直接把定长数组都换成 `array` ,而动态分配的数组可以替换为 `vector` 。
 
 ###  `array` 的使用方法
 
@@ -201,7 +201,7 @@ deque<int> v2(10, 1);
 deque<int> v3(v1);
 // 5. 创建一个v2的拷贝deque v4,其内容是v4[0]至v4[2]; 线性复杂度
 deque<int> v4(v2.begin(), v2.begin() + 3);
-// 6. 移动v2到新创建的deque v5,不发生拷贝; 常数复杂度; 需要 C++ 11
+// 6. 移动v2到新创建的deque v5,不发生拷贝; 常数复杂度; 需要 C++11
 deque<int> v5(std::move(v2));
 ```
 
@@ -258,7 +258,7 @@ deque<int> v5(std::move(v2));
 
 `list` 类型还提供了一些针对其特性实现的 STL 算法函数。由于这些算法需要[随机访问迭代器](./iterator.md),因此`list`提供了特别的实现以便于使用。这些算法有`splice()`、`remove()`、`sort()`、`unique()`、`merge()`等。
 
-##  `forward_list` (C++ 11)
+##  `forward_list` (C++11)
 
  `std::forward_list` 是 STL 提供的 [单向链表](../../ds/linked-list.md) 数据结构,相比于 `std::list` 减小了空间开销。
 
index 8bbc4fb..cb0069f 100644 (file)
@@ -4,13 +4,17 @@ author: Ir1d, aofall
 
 | 运算符   | 功能  |
 | ----- | --- |
-|  `+`  | 单目正 |
-|  `-`  | 单目负 |
-|  `*`  | 乘法  |
+|  `+` (单目) | 正 |
+|  `-` (单目) | 负 |
+|  `*` (双目) | 乘法  |
 |  `/`  | 除法  |
 |  `%`  | 取模  |
-|  `+`  | 加法  |
-|  `-`  | 减法  |
+|  `+` (双目) | 加法  |
+|  `-` (双目) | 减法  |
+
+
+??? note “单目与双目运算符”
+    单目运算符(又称一元运算符)指被操作对象只有一个的运算符,而双目运算符(又称二元运算符)的被操作对象有两个。例如 `1+2` 中加号就是双目运算符,它有`1`和`2`两个被操作数。此外 C++ 中还有唯一的一个三目运算符`?:`。
 
 算术运算符中有两个单目运算符(正、负)以及五个双目运算符(乘法、除法、取模、加法、减法),其中单目运算符的优先级最高。
 
@@ -24,6 +28,19 @@ author: Ir1d, aofall
 
 得到的 `op` 的运算值遵循数学中加减乘除的优先规律,首先进行优先级高的运算,同优先级自左向右运算,括号提高优先级。
 
+## 位运算符
+
+| 运算符   | 功能  |
+| ----- | --- |
+|  `~`  | 逐位非 |
+|  `&` (双目) | 逐位与 |
+|  `|`  | 逐位或 |
+|  `^`  | 逐位异或 |
+|  `<<`  | 逐位左移 |
+|  `>>`  | 逐位右移 |
+
+注意,位运算符的优先级低于普通的算数运算符。
+
 ## 自增/自减 运算符
 
 有时我们需要让变量进行增加 1(自增)或者减少 1(自减),这时自增运算符 `++` 和自减运算符 `--` 就派上用场了。
@@ -105,3 +122,27 @@ Result = (1 + 2, 3 + 4, 5 + 6);
 // 若要让 Result 的值得到逗号运算的结果则应将整个表达式用括号提高优先级,此时
 // Result 的值才为 11。
 ```
+
+## 成员访问运算符
+
+| 运算符    | 功能   |
+| ------ | ---- |
+|  `[]`  |  数组下标  |
+|  `.`  | 对象成员 |
+|  `&` (单目) | 取地址/获取引用 |
+|  `*` (单目) | 间接寻址/解引用 |
+|  `->`  | 指针成员   |
+
+这些运算符用来访问对象的成员或者内存,除了最后一个运算符外上述运算符都可被重载。与`&`, `*`和 `->` 相关的内容请阅读[指针](./pointer.md)和[引用](./reference.md)教程。这里还省略了两个很少用到的运算符`.*`和`->*`,其具体用法可以参见 [C++ 语言手册](https://zh.cppreference.com/w/cpp/language/operator_member_access)。
+
+```cpp
+auto result = v[1]; // 获取v中下标为2的对象
+
+auto result = p.q; // 获取p对象的q成员
+
+auto result = p->q; // 获取p指针指向对象的q成员
+
+auto result = &v // 获取v的指针或引用
+
+auto result = *v // 获取v指针指向的对象
+```