Skip to content

Commit

Permalink
添加"泛型算法"
Browse files Browse the repository at this point in the history
  • Loading branch information
arkingc committed Jun 5, 2019
1 parent fce9a5a commit 048d8d1
Showing 1 changed file with 268 additions and 1 deletion.
269 changes: 268 additions & 1 deletion C++/C++Primer.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

|**常见类型**|**函数**|**面向对象**|**容器**|**模板与泛型编程**|**内存管理**|
|:--:|:--:|:--:|:--:|:--:|:--:|
|[变量](#ch1)<br>[字符串与数组](#ch4)|[函数](#ch5)|[](#ch6)<br>[重载运算与类型转换](#ch7)<br>[继承体系](#ch8)|[容器](#ch9)<br>[容器适配器](#ch10)|[模板与泛型编程](#ch2)<br>|[内存管理](#ch3)<br>|
|[变量](#ch1)<br>[字符串与数组](#ch4)|[函数](#ch5)|[](#ch6)<br>[重载运算与类型转换](#ch7)<br>[继承体系](#ch8)|[容器](#ch9)<br>[容器适配器](#ch10)<br>[泛型算法](#ch11)|[模板与泛型编程](#ch2)<br>|[内存管理](#ch3)<br>|

<br>
<br>
Expand Down Expand Up @@ -149,6 +149,22 @@
+ [2.2 queue](#22-queue)
+ [2.3 priority_queue](#23-priority_queue)

<h2 id="ch11"></h2>

* [泛型算法](#泛型算法)
- [1.常用算法](#1常用算法)
+ [1.1 读容器(元素)算法](#11-读容器元素算法)
+ [1.2 写容器(元素)算法](#12-写容器元素算法)
+ [1.3 for_each算法](#13-for_each算法)
- [2.迭代器](#2迭代器)
+ [2.1 按类型划分](#21-按类型划分)
+ [2.2 按操作级别划分](#22-按操作级别划分)
- [3.调用对象](#3调用对象)
+ [3.1 谓词](#31-谓词)
+ [3.2 lambda](#32-lambda)
+ [3.3 bind参数绑定](#33-bind参数绑定)
- [4.链表的算法](#4链表的算法)

<h2 id="ch2"></h2>

* [模板与泛型编程](#模板与泛型编程)
Expand Down Expand Up @@ -2449,6 +2465,257 @@ priority_queue操作:
<br>
<br>
# 泛型算法
2个头文件:`<algorithm>`(大部分)、`numeric`
主要包括以下几种形式:
* `alg(beg,end,other args)`
* `alg(beg,end,dest,other args)`
* `alg(beg,end,beg2,other args)`
* `alg(beg,end,beg2,end2,other args)`
## 1.常用算法
### 1.1 读容器(元素)算法
* **元素查找**
- `find(b,e,v)`:在序列中查找`v`。查找成功则返回指向它的迭代器,否则返回`e`
- `find_if(b,e,f)`:在序列`[b,e)`中查找满足调用对象f的第一个元素,返回其迭代器,不存在则返回`e`
* **序列累加**
- `accumulate(b,e,v)`:位于`<numeric>`头文件。第3个参数是和的初值,其类型决定了如何进行`+`,类型必须与序列中元素类型匹配。如果使用字符串字面值,由于类型为`const char*`没有`+`运算,所以不能用于`string`相加;​
* **序列比较**
- `equal(b1,e1,b2)`:由于第二个序列只指定了开头位置,所以假定第二个序列至少和第一个序列一样长,否则会发生错误。可以用于比较不同类型的容器中的元素。元素类型也不必一样,能用`==`进行比较即可​​​
### 1.2 写容器(元素)算法
* **序列赋值**(容易犯的错误是对空容器调用序列赋值函数,序列赋值隐含了大小指定,空容器元素为0。如果不使用插入迭代器,则结果会是未定义的)
- `fill(b,e,v)`:要求传入的序列有效
- `fill_n(dest,n,val)`:向`dest`指向位置开始的`n`个元素赋值为`val`
* **序列拷贝**
- `copy(b1,e1,b2)`:将序列`[b1,e1)`的元素拷贝至`b2`开始的位置,如果b2不是插入迭代器,则需保证`b2`后有足够的空间。返回指向最后一个拷贝后一个元素的迭代器​
* **元素替换**(前两个参数指定了序列范围,`ov`是旧值,`nv`是新值)
- `replace(b,e,ov,nv)`:这个版本直接在序列`[b,e)`中修改
- `replace_copy(b1,e1,b2,ov,nv)`:这个版本将`[b1,e1)`修改的结果保存到`b2`指向的位置,因此不会改变原序列
* **消除重复**
- `unique(b,e)`:将`[b,e)`范围中的重复元素移至末尾,返回第一个重复元素的迭代器。范围中的相同元素必须相邻,也就是说先要排好序。不删除元素,只是移到后面。可以根据返回的迭代器调用容器的`erase()`操作配合,删除重复元素;​​
* **元素排序**
- `sort(b,e)`
- `sort(b,e,up)`:`up`是谓词
- `stable_sort(b,e)`:`stable_sort`可以维持相等元素的有序性。如:谓词是比较string长度的函数,则`stable_sort`不会改变相同长度string的原始序列
- `stable_sort(b,e,up)`
### 1.3 for_each算法
`for_each(b,e,f)`:算法遍历序列,对序列中的每一个元素调用f指定的操作
<br>
## 2.迭代器
头文件:`<iterator>`。泛型算法中,通常需要传递几个迭代器来表示序列范围​;
由于不是传递容器作为泛型算法的参数,使得泛型算法不依赖于具体容器​
### 2.1 按类型划分
#### 1)插入迭代器
- 特点
+ 一种迭代器适配器,故类型包含容器类型
+ 赋值时会调用容器的操作添加元素
+ 如果泛型算法会添加元素,则应该使用插入迭代器
+ 只支持递增(但是不做任何事),不支持递减
- `back_insert_iterator`
+ 使用`push_back`(支持的容器才能使用)
+ `back_inserter(c)`
+ 始终在尾部插入
- `front_insert_iterator`
+ 使用`push_front`(只有支持的容器才使用)
+ `front_inserter(c)`
+ 始终在首部插入
- `insert_iterator`
+ 使用`insert`
+ `inserter(c,iter)`
+ 插入`iter`前,返回`iter`
#### 2)流迭代器
将对应的流当作一个特定的元素序列来处理
- 特点
+ 只支持递增不支持递减,`ostream_iterator`递增不做任何事
+ 一次性访问
- `istream_iterator`
+ 绑定输入流,读取数据
+ `istream_iterator<T> it(cin)`
+ 尾后迭代器:`istream_iterator<T> eof`(空的`istream_iterator`)
+ 使用输入迭代器读取输入初始化vector:`vector<int> vec(it,eof)`
+ 懒惰求值:绑定到输入时不会理解从流数据,使用时才真正读取
- `ostream_iterator`
+ 绑定输出流,输出数据
+ `ostream_iterator<T> it(cout)` 或 `ostream_iterator<T> it(cout,c_str)`:可以提供一个c风格字符串,每次输出一个元素都会打印这个字符串
+ 没有尾后迭代器(没有空的`ostream_iterator`)
+ 使用输出迭代器:`copy(vec.begin(),vec.end(),it)`
#### 3)容器迭代器
* 普通迭代器
* 反向迭代器
- `reverse_iterator`
- `c.rbegin()`,`c.rend()`,`c.crbegin()`,`c.crend()`都会返回反向迭代器
- 容器必须支持`++`和`--`(除了forward_list外)
- 注意反向迭代器的范围不对称
+ 输出时,结果反过来
+ 使用普通迭代器初始化(或赋值)时,增减序列相反
#### 4)移动迭代器
* 特点
- 一种迭代器适配器
- 移动迭代器的解引用运算符生成一个右值:一般迭代器的解引用返回一个指向元素的左值
- `make_move_iterator()`函数将一个普通迭代器转换为一个移动迭代器:原迭代器的所有其它操作在移动迭代器中都照常工作,因此,可以将一对移动迭代器传递给算法。但是标准库并不保证哪些算法适用移动迭代器,哪些不适用;
### 2.2 按操作级别划分
泛型算法通常对迭代器操作级别有要求,必须使用大于等于要求级别的迭代器
高层​类别的迭代器支持低层类别的迭代器,越往下越高(支持的操作越多)
* **输入迭代器**:`istream_iterator`
- 只读,不写;单遍扫描,只能递增
* **输出迭代器**:`ostream_iterator`
- 只写,不读;单遍扫描,只能递增
- 与输入迭代器互为功能上的补集
* **前向迭代器**:forward_list的迭代器
- 可读写,多遍扫描;只能递增
* **双向迭代器**:list的迭代器(普通与反向)
- 可读写,多遍扫描;可递增递减
* **随机访问迭代器**:array,deque,string,vector的迭代器
- 可读写,多遍扫描,支持全部迭代运算;
- 随机说明可以迭代器支持`+n`, `-n`;​
<br>
## 3.调用对象
### 3.1 谓词
谓词是一个可调用的表达式(如函数),返回结果是一个能用作条件的值
* 一元谓词:只接受一个参数
* 二元谓词:只接受两个参数
### 3.2 lambda
> lambda是一种可调用对象,可理解为一种未命名内联函数。除此之外,可调用对象还有:函数、函数指针、函数对象、bind绑定的对象
头文件:`<functional>`
**用途**:
* 某些泛型算法只能接受某种谓词,谓词限制了参数个数
* 为了对序列执行某一操作,可能需要的参数大于谓词的限制
`[capture list] (parameter list) -> return type {function body}`
* **捕获列表**(**必须有**)
- 捕获列表就是用于向lambda传递我们原本想使用的参数。这些参数在函数体中会用到
- 捕获列表中可以包含lambda所在函数的局部变量
- lambda对静态局部变量和所在函数体外的名字可见。非静态局部变量传入捕获列表使得lambda可见
* **参数列表**(**可省略**)
- 和函数的参数不同,lambda不能使用默认实参
* **箭头**(**可省略**)
- 当函数体不只return一条语句时,默认返回`void`,如果想返回其它类型必须指明返回类型。当指明返回类型时,箭头不省略,即必须使用尾置返回类型
* **返回类型**(**可省略**)
- 如果省略了返回类型,则通过下列方式判断返回类型
+ 1)函数体内的返回语句
+ 2)返回的表达式的类型
+ 3)如果​lambda的函数体包含任何单一return语句之外的内容,且未指定返回类型,则返回void
* **函数体**(**必须有**)
**捕获**
* **显示捕获**
- **值捕获**
+ 前提是变量可拷贝
+ 创建lambda时拷贝,而不是调用时
+ 随后修改不会影响lambda内对应的值
- **引用捕获**(不推荐。引用可扩展开,包括指针,迭代器)
+ `&局部变量名`
+ lambda使用时实际是使用引用绑定的对象
+ 随后的修改会影响lambda内对应的值(如果引用的是一个const对象,则不能修改)
+ 捕获的是局部变量,必须确保引用的对象在lambda执行时存在(如果lambda可能在函数结束后执行,捕获的引用指向的局部变量已经消失)
+ 捕获某些对象必须使用引用捕获(I/O)
- **修改捕获对象**
+ 通过值拷贝时,在参数列表后加mutable 可以改变其值。默认情况下,对于一个值被拷贝的变量,lambda不会改变其值。改变的只是lambda拷贝的对象,与原对象无关
* **隐式捕获**
- 让编译器推断捕获列表
- `&`:告诉编译器采用引用捕获的方式
- `=`:告诉编译器采用值捕获的方式
* **显隐混合捕获**
- 捕获列表第一个元素必须是 `&` 或 `=`
- 显示捕获的变量必须使用与隐式捕获不同的方式
lambda对象可以作为返回值,如果函数返回一个lambda,则与函数不能返回一个局部变量的引用类似,此lambda也不能包含引用捕获
### 3.3 bind参数绑定
> 标准库中的一个函数。可以看作一个通用的函数适配器
头文件:`<functional>`
**用途**:
* 与lambda相同
* 解决谓词参数限制的另一种途径
`auto newCallable = bind(callable,arg_list)`
* `callable`为参数受限(受泛型算法限制)的谓词(谓词为可调用表达式,如函数)
当调用`newCallable`时,`newCallable`会将`arg_list`传递给`callable`,调用`callable`(`callable`函数的参数顺序与`arg_list`一致)
参数列表(`arg_list`)
* **占位符**:占位符定义在名为`placeholders`的命名空间,此命名空间又定义在`std`命名空间,所以使用时两个命名空间都要写上:`using namespace std::placeholders;`通常是泛型算法对参数的限制来决定有多少个占位符
- `_n`,`n`为整数,表示调用`newCallable`时的第`n`个参数。可以无序,即_2在_1前
* **变量**
- 有些变量不能传值(I/O),所以需要绑定引用
+ ref(os):返回绑定对象的引用
+ cref(os):返回绑定对象的const引用
<br>
## 4.链表的算法
通用泛型算法的问题:
* 通用sort要求随机访问,链表不支持
* 其它通用算法对链表来说代价太高
- 链表指针修改更快;
- 因此链表优先选择成员函数版本的算法​
* **与泛型算法对应的算法**
- `ls.merge(ls2)`
- `ls.merge(ls2,comp)`
- `ls.remove(val)`
- `ls.remove_if(pred)`
- `ls.reverse()`
- `ls.sort()`
- `ls.sort(comp)`
- `ls.unique()`
- `ls.unique(pred)`
* **链表独有的算法**
- `ls.splice(args)`
+ `args`:([代码](https://github.com/arkingc/llc/blob/master/cpp/container/list/splice.cpp))
* `(p,ls2)`:将链表`ls2`插入`ls`中`p`指向的元素前
* `((p,ls2,p2)`:将`ls2`中`p2`指向的元素插入`ls`中`p`指向的元素前
* `((p,ls2,b,e)`:​​​将`ls2`中`[b,e)`范围的元素插入`ls`中`p`指向的元素前
<br>
<br>
# 模板与泛型编程
## 1.模板函数
Expand Down

0 comments on commit 048d8d1

Please sign in to comment.