♦️第10章 泛型算法
- 泛型算法是提供一个算法,对于不同类型的容器和不同类型的元素。因此叫做泛化。
🐬10.1 概述
大多数算法定义在头文件 algorithm 中,部分在 numeric 中
- 这些算法不直接操作容器,而是操作迭代器
- 算法不会改变容器的大小。永远不会执行容器操作
- 大多数算法是通过遍历两个迭代器标记的一段元素来实现其功能。
- 算法永远不会改变底层容器的大小。算法可能改变容器中保存的元素的值,也可能在容器内移动元素,但不能直接添加或者删除元素。
🐬10.2 初始泛型算法
- 标准库算法都对一个范围内的元素进行操作,此元素范围称为“输入范围”
- 理解算法的最基本的方法是了解它们是否读取元素、改变元素、重排元素顺序。
部分算法从两个序列中读取元素,两个序列不必相同(如vector和list),序列中的元素也不必相同(如double和int),要求是只要能比较两种元素即可。
俩种假设
- 当算法只接受
三个迭代器
时,前俩个迭代器表示第一个序列范围,第三个迭代器表示第二个序列元素范围。这种情况假定第二个序列至少与第一个一样长。
- 向目的位置迭代器写入 n 个数据的算法假定目的位置足够大(大于等于 n)
🐾10.2.1 只读算法
- 对于只读而不改变元素的算法,通常使用
cbegin()
和cend()
//对输入范围内的元素在 val 的基础上求和。可以对字符串“+”。注意元素是加在val上,如果元素是double,val是int,和会被转换成int
int sum = accumulate(vec.cbegin(),vec.cend(),val);
//比较两个序列的元素是否全部相等(假定第二个序列至少与第一个序列一样长
bool F = equal(vec1.cbegin(),vec1,end(),vec2.cbegin());
//查找符合某条件的元素,返回迭代器,如果没有就返回尾迭代器
auto iter = find_if(vec.begin(),vec.end(),'一元谓词');
🐾10.2.2 写容器元素算法
- 写元素算法,必须注意确保原序列大小至少不小于要求算法写入元素的数目。
fill(vec.begin(),vec.end(),val);//将 val 赋予输入序列中的每一个元素
fill(vec.begin(),vec.begin() + vec.size()/2,10);//将一个容器的前一半的值写为10
fill_n(dest,n,val);//将 val 赋予从 dest 开始的连续 n 个元素。假定dest开始的序列有至少 n 个元素
fill_n(vec.begin(),vec.size(),0);//将 vec 的所有元素重置为0
for_each(vec.begin(),vec.end(),'可调用对象');
//对输入范围内的每一个元素执行可调用对象的内容。注意:可调用对象是一元谓词,参数类型是迭代器指向的类型
插入迭代器
- 是一种向容器中添加元素的迭代器。
- 函数
back_inserter
定义在头文件iterator
中。 back_inserter
接收一个指向容器的引用,返回一个与该容器绑定的插入迭代器。
vector<int> vec;
auto it = back_inserter(vec);//it 即为vec新加的插入迭代器
*it = 24;//给 it 赋值后,vec 现在有一个元素为 24
back_inserter() 函数的返回值作为算法的目的位置
fill_n(back_inserter(vec),10,0);//添加 10 个元素到 vec
拷贝算法
- 输入:前两个参数指定输入范围,第三个指向目标序列。
copy时必须保证目标目的序列至少要包含与输入序列一样多的元素。
auto ret = copy(begin(a1),end(a1),begin(a2));//把数组 a1 的内容拷贝给 a2,返回的是目的位置迭代器(递增后)的值
replace(ilst.begin(),ilst.end(),0,42);//把输入范围内所有值为 0 的元素改为 42
replace_copy(ilst.begin(),ilst.end(),back_inserter(ivec),0,42);//把输入范围内所有值为 0 的元素改为 42 并拷给 dest 开头的序列。
- replace:只是
不保留原序列
,将原序列的值全部换成新的 - replace_copy:
保留原序列
,ivec包含ilst的一份拷贝,原来的ilst中的值为0的元素在ivec中都变为42.
🐾10.2.3 重排容器元素的算法
//默认按字典序从小到达排列输入范围内的元素。重复的元素会相邻
sort(words.begin(),words.end());
//将输入范围内的元素重排,将重复的元素里不是第一次出现的元素都放到序列的后端。返回指向序列后端重复元素中的第一个重复元素的迭代器。
auto end_unique = unique(words.begin(),words.end());
//删除重复的元素。erase()函数可以接受两个相等的迭代器作为参数
words.erase(end_unique,words.end());
🐬10.3 定制操作
- 默认情况下,这类算法使用元素类型的<或=运算符完成比较。但现在允许我们
自定义操作来代替默认运算符
🐾10.3.1 向算法传递函数
- 谓词是
一个可调用的表达式,返回结果是一个可以作为条件的值,如返回一个 bool 值。
- 谓词分为
一元谓词
和二元谓词
,分别接收一个参数
和俩个参数
元素类型必须能转换为谓词参数的类型
。接收谓词参数的算法对输入序列中的元素调用谓词
'sort'
bool isShorter(const string &s1, const string &s2)
{
return s1.size() < s2.size();
}
sort(words.begin(), words.end(), isShorter);
'stable_sort 稳定排序'
//会按照元素长度大小排序,而长度相同的单词会保持字典序
stable_sort(words.begin(), words.end(), isShort);
🐾10.3.2 lambda表达式
- 不止一元谓词和二元谓词,我们希望操作可以接受更多的参数。(不止上面sort接收的二元。)
- 可以向一个算法传递任何类别的可调用对象。
四种可调用对象:函数、函数指针、重载了函数调用运算符的类、lambda表达式
形式:
[capture list](parameter list) -> return type {function body}。
capture list
捕获列表是一个lambda
所在函数定义的局部变量的列表(通常为空)。不可忽略。return type
是返回类型。可忽略。parameter
是参数列表。可忽略。
function body
是函数体。不可忽略。
注意
- 与函数不同,lambda可能定义在
函数内部
- lambda必须使用
尾置返回
来指定返回类型 - 捕获列表的内容为 lambda所在函数中定义的局部变量(直接写局部变量的名字即可,通常为空)。
捕获列表只用于局部非 static 变量。
参数列表和返回类型都可以省略
。如果忽略返回类型,则根据 return 语句推断返回类型(此时函数体必须只有 return 语句)。- lambda 没有默认参数。
- lambda表达式的调用方式也是使用调用运算符,内含实参。
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 << " ";})
🐾10.3.3 lambda捕获和返回
- 当向一个函数传递一个 lambda 时,同时定义lambda时会生成一个
新的类类型
和该类型的一个对象
。(传递的参数实际上就是此编译器生成的类类型的未命名对象。)
auto f = [ ]{ return 42; } 实际上定义了一个类类型的对象。
- 默认情况下,从 lambda 生成的类都包含一个对应该 lambda 所捕获的变量的数据成员。
- lambda 捕获变量的方式分为
值捕获
和引用捕获
,类似参数传递。
值捕获
- 值捕获的前提是变量可以拷贝
被值捕获的变量的值是在 lambda 创建时拷贝,而非调用时拷贝。因此在 lambda 创建后改变被捕获的变量不会影响 lambda 中对应的值。
void func()
{
size_t v1 = 42 //局部变量
auto f = [v1]{return v1;};//将v1拷贝到名为f的可调用对象
v1 = 0;
auto j = f(); //j为42;f保存了创建它时v1的拷贝
}
不能拷贝ostream对象
,因此想要捕获os对象唯一方法就是捕获其引用(或指向os的指针)
引用捕获
- 注意采用引用捕获必须
确保被引用的对象在 lambda 执行时是存在的。
- lambda 捕获的都是
上层函数的局部变量
,在函数结束后就都不复存在了。 引用捕获可以用于捕获变量是 IO 类型时。
void func()
{
size_t v1 = 42 //局部变量
auto f2 = [&v1]{return v1;};//将v1拷贝到名为f的可调用对象
v1 = 0;
auto j = f2(); //j为0;f2保存了v1的引用,而非拷贝
}
隐式捕获
- 编译器会根据 lambda 中的代码来推断要使用的变量。
- 让编译器推断捕获列表,在捕获列表中写一个
&(引用方式)
或=(值方式)
。auto f3 = [=] {return v1;}
wc = find_if(words.begin(), words.end(), [=](const string& s){ return s.size()>=sz; });
混合显示捕获与隐式捕获
- 对一部分采用
值捕获
,另一部分采用隐式捕获
。 采用混合捕获,捕获列表中的第一个元素必须是一个&或=
- 如果是 &,表示采用隐式的引用捕获,此时后面显式捕获的变量必须都采用值捕获。
- 如果是 =,表示采用隐式的值捕获,此时后面显式捕获的变量必须都采用引用捕获。
for_each(words.begin(), words.end(), [&,c](const string& s){ os<<s<<c; }); // 这里的 os 是通过隐式的引用捕获得到的。
for_each(words.begin(), words.end(), [=, &os](cosnt string& s){ os<<s<<c;}); // 这里的 c 是通过隐式的值捕获得到的。
lambda捕获列表
[],[参数序列] // 空列表与显式捕获
[=],[&] // 隐式值捕获与隐式引用捕获
[=,参数列表],[&,参数列表] // 混合捕获
可变lambda
- 默认情况对于一个值拷贝的变量,lambda不会改变其值。如果希望改变一个被捕获变量的值,可以在参数列表首加上
关键字muitable
- 引用捕获的变量是否可以修改依赖于引用指向的是 const 还是非 const 类型。
void func()
{
size_t v1 = 42 //局部变量
//f可以改变他所捕获的变量的值
auto f = [v1]() mutable{return ++v1;};
v1 = 0;
auto j = f(); //j为43
}
void func()
{
size_t v1 = 42 //局部变量
//v1是一个非const变量的引用
//可以通过v2中的引用改变它
auto f = [&v1]() mutable{return ++v1;};
v1 = 0;
auto j = f(); //j为1
}
指定lambda返回类型
- lambda 中只包含一个单一的 return 语句,可以省略返回类型。
lambda 中除了 return 还有其他语句,此时应该指明返回类型。
lambda定义返回类型时,必须使用尾置返回类型
🐾10.3.4 参数绑定
lambda表达式更适合在一两个地方使用的简单操作
。如果是很多地方使用相同的操作,还是需要定义函数。对于捕获列表为空的 lambda,通常可以用函数来代替
。对于捕获列表不为空的 lambda,不容易使用函数替换。
标准库bind函数
- 定义在头文件functional中,可以看做为一个通用的函数适配器。
bind 函数接受一个可调用对象,生成一个新的调用对象来“适应”原对象的参数列表。
auto newCallable = bind(allable, arg_list);
- newCallable本身是一个可调用对象。
- arg_list是一个逗号分隔的参数列表,对应给定的callable的参数。
我们再调用newCallable的时候,newCallable会调用callable并传递给它arg_list中的参数。
bind占位符
_1,_2,_n 等占位符分别表示新可调用对象的第 1,2,n 个参数
,将 _n 放在 bind 中不同的参数位置,表示将新可调用对象的第 n 个参数和旧可调用对象在该位置的参数绑定在了一起。- _1,_2 等定义在命名空间
placeholders
中,placeholders 这个名字定义在 std 中。placeholders 的实现定义在functional
头文件中。
using namespace std::placeholders::_1;//使用 _1,_2 前要先声明使用命名空间 placeholders。
bind的参数
bind绑定的主要功能有俩个
可以减少参数数目。(减少掉的参数被设为一个固定值)
- 改变参数的顺序
auto g = bind(f, a, b, _2, c, _1); // g 只有两个参数,两个参数分别传递给 f 的第 5 个和第 3 个参数
调用g(X, Y),会调用f(a, b, Y, c, X);
将 bind 用于算法的谓词
sort(words.beegin(), words.end(), isShorter); //长度由短到长排序
sort(words.begin(), word.end(), isShorter(, _2, _1));//长度由长到短排序
绑定引用参数
- 默认情况下,bind 的那些不是占位符的参数是
值传递
,被拷贝到 bind 返回的可调用对象中。 - 有时需要像lambda一样需要传递引用或者参数类型不能进行拷贝(如os),此时需要使用
ref函数
和cref函数
[&os, c](const string &s){os << s << c;}; //正确
bind(print, os, _1, ' '); // 错误,os 不能拷贝
bind(print, ref(os), _1, ' '); // 正确
🐬10.4 再探迭代器
- 除了为每个容器定义的迭代器之外,标准库在头文件iterator中还定义了额外几种迭代器。
- 插入迭代器:
绑定到容器上
,可以来向容器插入元素。 - 流迭代器:
绑定到输入或输出流上
,可以用来遍历所关联的 IO 流。 - 反向迭代器:
这些迭代器向后移动
,不能向前移动除了 forward_list 外的标准库容器都有反向迭代器。 - 移动迭代器:移动迭代器
不拷贝其中的元素,而是移动他们。
🐾10.4.1 插入迭代器
- 插入器是一种迭代器适配器,
接受一个容器,生成一个迭代器
,能实现向给定容器添加元素。
注意
back_inserter
是插入器,back_insert_iterator<vector<int>>
是插入迭代器类型。back_inserter(v)
返回绑定到容器 v 的 back_insert_iterator,并实现其自增。
插入迭代器的三种类型
- 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 |
插入迭代器的定义
- 有两种方式,一种是直接定义,另一种是通过插入器生成
vector<int> v;
'使用插入器生成'
auto bIn = back_inserter(v); // bIn 是一个绑定到 v 的插入迭代器。
'直接定义'
back_insert_iterator<vector<int>> bIn(v); // bIn 是一个绑定到 v 的插入迭代器。
insert_iterator<vector<int>> iIn(v, v.begin()); // iIn 是一个绑定到 v 的插入迭代器
插入迭代器的操作
插入迭代器操作只有一种,就是赋值操作。
- 当通过插入迭代器赋值时,插入迭代器调用容器操作来向给定容器的指定位置插入一个元素。
*bIn = 30;//在 bIn 所绑定的容器的尾元素之后插入一个 30
*iIn = 10;//在 iIn 所绑定的容器的相应元素之前插入一个 10
插入器 inserter 与函数 insert 的不同
- 插入迭代器是
恒定指向同一个元素的
。而 insert 返回的是指向所插入元素的迭代器
'1'
auto f1 = inserter(v, v.begin());
*f1 = 10;
'2'
iter = v.insert(v.begin(), 10);
++iter;
插入迭代器的使用
vector<int> v1,v2;
*back_inserter(v1) = 2; // 在 v1 的尾元素之后插入一个 2
*inserter(v1, v1.begin()) = 6; // 在 v1 的首元素之前插入一个 6
copy(v1.begin(),v1.end(),back_inserter(v2)); // 将 v1 的所有元素按顺序拷贝到 v2 的尾元素之后。
copy(v1.begin(),v1.end(),inserter(v2,v2.begin())); // 将 v1 的所有元素按顺序拷贝到 v2的首元素之前
🐾10.4.2 iostream迭代器
虽然iostream类型不是迭代器,但定义了可以使用这些IO类型对象的迭代器。iostream有俩类
istream_iterator
:读取输入流。ostream_iterator
:向输出流写数据。
流迭代器作用
- 迭代器可与输入或输出流绑定在一起,用于迭代遍历所关联的 IO 流。
- 通过使用流迭代器,
我们可以用泛型算法从流对象中读取数据以及向其写入数据
。创建流迭代器时,必须指定迭代器将要读写的对象类型。 流迭代器可以绑定到 iostream,fstream,stringstream。它将对应的流当作一个元素序列来处理。
可以为任何具有输入运算符的(>>)类型定义istream_iterator,即所有内置类型和重置了 >> 的类。
创建流迭代器
1. 绑定到一个流
默认初始化:采用这种方式创建的迭代器可以当作尾后值使用,可以在 for 循环中作为终止条件。
istream_iterator<int> int_it(cin);//创建了一个流迭代器 iInt,iInt 从 cin 读取 int
istream_iterator<int> int_eof;//尾后迭代器。当关联的流遇到文件尾或遇到错误,迭代器的值就与尾后迭代器相等。
while(in_it != eof)
{
vec.push_back(*in_iter ++);
}
istream_iterator使用
- 使用算法操作流迭代器
istream_iterator<T> in(is); //in从输入流is读取类型为T的值
istream_iterator<T> end; //读取类型是T的值的istream_iterator迭代器,表示尾后位置
in1 == in2; in1 != int2; //首先 in1 和 in2 必须读取相同类型。如果 in1 和 in2 都是尾后迭代器或绑定到相同的输入,则两者相等。
*in; //返回从流中读取的值
++in;in++; //递增操作
'1'
istream_iterator<int> inInt(cin),eof;
while(inInt != eof)
vec.push_back(*inInt++);
'2'
vector<int> vec(inInt,eof);//和上面效果相同,当遇到文件尾或遇到第一个不是 int 的数据停止。
'3'
istream_iterator<int> in(cin), eof;
cout << accumulate(in, eof, 0) << endl;
ostream_iterator 定义
可以为任何具有输出运算符的(<<)类型定义 ostream_iterator,即所有内置类型和重置了 << 的类。
ostream_iterator<int> out(os);//out 将类型为 int 的值写到输出流 os 中
ostream_iterator<int> out(os,str);//out 将类型为 int 的值写到 os 中,每个值后面都跟着一个C风格字符串 str。(字符数组)
ostream_iterator操作
ostream_iterator 的赋值操作等价于输出流的输出操作。每赋一次值,输出一次。
ostream_iterator<T> out(os); //out将类型为T的值写到输出流os中
ostream_iterator<T> out(os, d); //out将类型为T的值写到输出流os中,每个值后面都输出一个d。d指向一个空字符结尾的字符数组。
out = 6; //等价于 os<<6。将 6 写入到 out 绑定的输出流中。
*out; ++out; out++; //这些运算符存在但没有意义。
ostream_iterator 的使用
for(auto e:vec)
out = e; //赋值语句将元素写到 cout,每赋一次值,写操作就会被提交一次。
'* 和 ++可以省略'
for(auto e:vec)
*out++ = e;//与上面的语句实际效果相同,看起来更清晰。
'使用 copy 来打印 vec 中的元素,更为简单。'
copy(vec.begin(),vec.end(),out);//将 vec 中的元素写入到了 out 绑定的输出流中
🐾10.4.3 反向迭代器
- 反向迭代器就是在容器中从尾元素向首元素反向移动的迭代器。
- 对于反向迭代器,递增和递减的
操作含义会颠倒
。 除了 forward_list 外
,其他容器都支持反向迭代器。- 反向迭代器支持递增和递减操作。
流迭代器不支持递减操作。
- 实现向后遍历,配合
rbegin
和rend
。
函数 riter.base() 返回相应的正向迭代器,正向迭代器指向靠后一个元素。
auto riter = string.rbegin()//反向迭代器 riter 指向 string 的尾元素
auto iter = riter.base();//正向迭代器 iter 指向 string 的尾后元素
🐬10.5 泛型算法结构
🐾10.5.1 5类迭代器
迭代器类别 | 解释 | 支持的操作 |
---|---|---|
输入迭代器 | 只读,不写;单遍扫描,只能递增 |
== ,!= ,++ ,* ,-> |
输出迭代器 | 只写,不读;单遍扫描,只能递增 |
++ ,* |
前向迭代器 | 可读写;多遍扫描,只能递增 |
== ,!= ,++ ,* ,-> |
双向迭代器 | 可读写;多遍扫描,可递增递减 |
== ,!= ,++ ,-- ,* ,-> |
随机访问迭代器 | 可读写,多遍扫描,支持全部迭代器运算 |
== ,!= ,< ,<= ,> ,>= ,++ ,-- ,+ ,+= ,- ,-= ,* ,-> ,iter[n] ==*(iter[n]) |
输入迭代器
输入迭代器只用于顺序访问,只能用于单遍扫描算法,如算法 find 和 accumulate。
Istream_iterator 是一种输入迭代器。
输出迭代器
只能向一个输出迭代器赋值一次,只能用于顺序访问的单遍扫描算法。如 copy 函数的第三个参数。
ostream_iterator 是一种输出迭代器。
前向迭代器
可以读写元素。只能在序列中沿一个方向移动,可以多次读写同一个元素。
可以保存前向迭代器的状态,使用前向迭代器的算法可以对序列多遍扫描。
算法 replace 要求前向迭代器,forward_list 的迭代器是前向迭代器。
双向迭代器
可以正向/反向读写序列中的元素。支持递增递减运算符。
算法 reverse 要求双向迭代器。list 的迭代器是双向迭代器。
随机访问迭代器
提供在常量时间内访问序列中任意元素
的能力。
支持以下操作:
支持 <, <=, >, >= 等关系运算符。
支持迭代器与整数的加减。
支持两个迭代器之间的相减。
支持下标运算符。iter[n] 和 *(iter[n]) 含义相同。
算法 sort 要求随机访问迭代器。array, deque, vector, string 的迭代器是随机访问迭代器。
🐾10.5.2 算法的形参模式
大多数算法一般有以下几种形式
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都是迭代器参数,是否使用要依赖于执行的操作。
dest 经常是一个插入迭代器或一个 ostream_iterator,他们都能确保不管写多少元素都肯定是安全的。
🐾10.5.3 算法命名规范
- 算法遵循一同命名和重载规范。
- 一些算法使用重载形式传递一个谓词,
来代替 < 或 ==。
unique(beg, end); // 使用 == 比较元素
unique(beg, end, comp); // 使用 comp 比较元素
_if版本算法
接受一个元素值的算法
通常有一个不同名的版本:加_if,接受一个谓词代替元素值。
find(beg, end, val); // 查找范围内 val 第一次出现的版本。
find_if(beg, end, pred); // 查找第一个令 pred 为真的元素。
拷贝版本算法
- 区分拷贝元素的版本和不拷贝的版本:拷贝版本通常加_copy。
- 默认情况下,重排元素的算法会将重排后的元素写回给定的输入序列中。拷贝版本则将元素写到一个给定的位置。
reverse(beg, end); // 反转序列中的元素。
reverse_copy(beg, end, dest); // 将元素按逆序拷贝到 dest。
remove_if(v1.begin(), v1.end(), [](int i){return i%2;}); // 从 v1 中删除奇数元素。
remove_copy_if(v1.begin(), v1.end(), back_inserter(v2), [](int i){return i%2;}); // 将去掉了奇数元素的 v1 序列拷到 v2 中。v1当中的元素不变
🐬10.6 特定容器算法
- 链表类型 list 和 forward_list 定义了几个成员函数形式的算法。它们定义了独有的
sort, merge, remove, reverse 和 unique。
通用版本的 sort 要求随机访问迭代器
,而 list 和 forward_list 分别提供双向迭代器和前向迭代器
,因此不能用于 list 和 forward_list。- 其他链表类型定义的算法的通用版本可以用于链表,但是性能差很多,应该
优先使用成员函数版本的算法。
lst.merge(lst2); // 将 lst2 的元素合并入 lst。两者必须都是有序的,合并后 lst2 将变为空。使用 < 比较元素。
lst.merge(lst2,comp); // 上面的 merge 的重载版本,用给定的 comp 代替 <
lst.remove(val); // 调用 erase 删除掉与给定值相等的每个元素。
lst.remove_if(pred); // pred 是个一元谓词,删除掉 lst 中使 pred 为真的每个元素。
lst.reverse(); // 反转 lst 中元素的顺序。
lst.sort(); // 使用 < 给元素排序
lst.sord(comp); // 重载版本
lst.unique(); // 调用 erase 删除同一个值的连续拷贝。
lst.unique(pred); // pred 是个二元谓词,删除使 pred 为真的连续拷贝。
- 上面的操作都返回
void
list和forward_list的splice成员函数版本的参数:
链表类型定义了 splice 算法,此算法是链表特有的,没有通用版本。
- splice 算法用来在
两个链表间移动元素
或在一个链表中移动元素的位置
。
lst.splice(p, lst2); flst.splice_after(p, lst2); // p 是一个指向 lst 中元素的迭代器,或一个指向 flst 首前位置的迭代器。
// 函数将 lst2 中的所有元素移动到 lst 中 p 之前的位置或 flst 中 p 之后的位置。
lst.splice(p, lst2, p2); flst.splice_after(p, lst2, p2); // p2 是一个指向 lst2 中位置的迭代器。
// 函数将 p2 指向的元素移动到 lst 中,或将 p2 之后的元素移动到 flst 中,lst2 可以是与 lst 或 flst 相同的链表。
lst.splice(p, lst2, b, e); flst.splice_after (p, lst2, b, e); // b 和 e 表示 lst2 中的合法范围。
// 将给定范围中的元素从 lst2 移动到 lst 中。lst2 可以是与 lst 或 flst 相同的链表,但是 p 不能指向 b 和 e 之间的元素。
- 使用
lst.splice(args)
或flst.splice_after(args)