《C++ Primer》笔记 第10章 泛型算法
迭代器令算法不依赖于容器,但算法依赖于元素类型的操作。
算法永远不会执行容器的操作。算法永远不会改变底层容器的大小。
accumulate定义在头文件numeric中,接受三个参数,前两个指出需要求和的元素的范围,第三个参数是和的初值。accumulate的第三个参数的类型决定了函数中使用哪个加法运算符以及返回值的类型。
// 对vec中的元素求和,和的初值是0 int sum = accumulate(vec.cbegin(), vec.cend(), 0); // 将vector中所有string元素连接起来 string sum = accumulate(v.cbegin(), v.cend(), string("")); // 错误:const char*上没有定义+运算符 string sum = accumulate(v.cbegin(), v.cend(), "");
对于只读取而不改变元素的算法,通常最好使用cbegin()和cend()。但是,如果你计划使用算法返回的迭代器来改变元素的值,就需要使用begin()和end()的结果作为参数。
equal用于确定两个序列是否保存相同的值。它将第一个序列中的每个元素与第二个序列中的对应元素进行比较。如果所有对应元素都相等,则返回true,否则返回false。此算法接受三个迭代器:前两个表示第一个序列中的元素范围,第三个表示第二个序列的首元素。
那些只接受一个单一迭代器来表示第二个序列的算法,都假定第二个序列至少与第一个序列一样长。
算法fill接受一对迭代器表示一个范围,还接受一个值作为第三个参数。fill将给定的这个值赋予输入序列中的每个元素。
一些算法从两个序列中读取元素。构成这两个序列的元素可以来自于不同类型的容器。而且,两个序列中元素的类型也不要求严格匹配。算法要求的只是能够比较两个序列中的元素。
函数fill_n接受一个单迭代器、一个计数值和一个值。它将给定值赋予迭代器指向的元素开始的指定个元素。示例:
fill_n(dest, n, val)
。fill_n假定dest指向一个元素,而从dest开始的序列至少包含n个元素。向目的位置迭代器写入数据的算法假定目的位置足够大,能容纳要写入的元素。
插入迭代器是一种像容器中添加元素的迭代器。通常情况,当我们通过一个迭代器向容器元素赋值时,值被赋予迭代器指向的元素。而当我们通过一个插入迭代器赋值时,一个与赋值号右侧值相等的元素被添加到容器中。
back_inserter定义在头文件iterator中,接受一个指向容器的引用,返回一个与该容器绑定的插入迭代器。当我们通过此迭代器赋值时,赋值运算符会调用push_back将一个具有给定值的元素添加到容器中:
vector<int> vec; // 空向量 auto it = back_inserter(vec); // 通过它赋值会将元素添加到vec中 *it = 42; // vec中现在有一个元素,值为42 // 我们常常使用back_inserter来创建一个迭代器,作为算法的目的位置来使用 vector<int> vec; // 空向量 // 正确:back_inserter创建一个插入迭代器,可用来向vec添加元素 fill_n(back_inserter(vec), 10, 0); // 添加10个元素到vec
拷贝算法copy接受三个迭代器,前两个表示一个输入范围,第三个表示目的序列的起始位置。此算法将输入范围中的元素拷贝到目的序列中。传递给copy的目的序列至少要包含与输入序列一样多的元素。
// 利用copy实现内置数组的拷贝 int a1[] = {0,1,2,3,4,5,6,7,8,9}; int a2[sizeof(a1)/sizeof(*a1)]; // a2与a1大小一样 // ret指向拷贝到a2的尾元素之后的位置 auto ret = copy(begin(a1), end(a1), a2); // 把a1的内容拷贝给a2
copy返回的是其目的位置迭代器(递增后)的值。即,ret恰好指向拷贝到a2的尾元素之后的位置。
replace算法读入一个序列,并将其中所有等于给定值的元素都改为另一个值。此算法接受4个参数:前两个是迭代器,表示输入序列,后两个一个是要搜索的值,另一个是新值。它将所有等于第一个值的元素替换为第二个值:
// 将所有值为0的元素改为42 replace(ilst.begin(), ilst.end(), 0, 42);
如果我们希望保留原序列不变,可以调用replace_copy。此算法接受额外第三个迭代器参数,指出调整后序列的保存位置:
// 使用back_inserter按需要增长目的序列 replace_copy(ilst.cbegin(), ilst.cend(), back_inserter(ivec), 0, 42);
sort算法接受两个迭代器,表示要排序的元素范围。
unique算法重排输入序列,将相邻的重复项“消除”,并返回一个指向不重复值范围的末尾的迭代器。unique返回的迭代器指向最后一个不重复元素之后的位置,此位置之后的元素仍然存在,但我们不知道它们的值是什么。
标准库算法对迭代器而不是容器进行操作。因此,算法不能(直接)添加或删除元素。
void elimDups(vector<string> &words) { // 按字典序排序words,以便查找重复单词 sort(words.begin(), words.end()); // unique重排输入范围,使得每个单词只出现一次 // 排列在范围的前部,返回指向不重复区域之后一个位置的迭代器 auto end_unique = unique(words.begin(), words.end()); // 使用向量操作erase删除重复单词 words.erase(end_unique, words.end()); }
谓词是一个可调用的表达式,其返回结果是一个能用作条件的值。标准库算法所使用的谓词分为两类:一元谓词(意味着它们只接受单一参数)和二元谓词(意味着它们有两个参数)。接受谓词参数的算法对输入序列中的元素调用谓词。因此,元素类型必须能转换为谓词的参数类型。
// 比较函数,用来按长度排序单词 bool isShorter(const string &s1, const string &s2) { return s1.size() < s2.size(); }
稳定排序算法stable_sort维持相等元素的原有顺序。假定在此调用前words是按字典序排列的,则调用之后,words会按元素大小排序,而长度相同的单词会保持字典序:
elimDups(words); // 将words按字典序重排,并消除重复单词 // 按长度重新排序,长度相同的单词维持字典序 stable_sort(words.begin(), words.end(), isShorter); for(const auto &s : words) // 无需拷贝字符串 cout << s << " "; // 打印每个元素,以空格分隔 cout << endl;
标准库定义了名为partition的算法,它接受一个谓词,对容器内容进行划分,使得谓词为true的值会排在容器的前半部分,而使谓词为false的值会排在后半部分。算法返回一个迭代器,指向最后一个使谓词为true的元素之后的位置。
find_if算法接受一对迭代器,表示一个范围,第三个参数是一个谓词。find_if算法对输入序列中的每个元素调用给定的这个谓词。它返回第一个使谓词返回非0值的元素,如果不存在这样的元素,则返回尾后迭代器。
// 获取一个迭代器,指向第一个满足size() >= sz的元素 auto wc = find_if( words.begin(), words.end(), [sz](const string &a){ return a.size() >= sz; });
对于一个对象或一个表达式,如果可以对其使用调用运算符,则称它为可调用的。包括函数、函数指针、重载了函数调用运算符的类和lambda表达式。
一个lambda表达式表示一个可调用的代码单元。我们可以将其理解为一个未命名的内联函数。lambda可能定义在函数内部。lambda必须使用尾置返回来指定返回类型:
[capture list](parameter list) -> return type { function body }
我们可以忽略参数列表和返回类型,但必须永远包含捕获列表和函数体。在lambda中忽略括号和参数列表等价于指定一个空参数列表。如果忽略返回类型,lambda根据函数体中的代码推断出返回类型。如果函数体只是一个return语句,则返回类型从返回的表达式的类型推断而来。否则返回类型为void。
lambda的调用方式与普通函数的调用方式相同,都是使用调用运算符。
lambda不能有默认参数。因此,一个lambda调用的实参数目永远与形参数目相等。
一个lambda只有在其捕获列表中捕获一个它所在函数中的局部变量,才能在函数体中使用该变量。
for_each算法接受一个可调用对象,并对输入序列中每个元素调用此对象:
// 打印长度大于等于给定值的单词,每个单词后面接一个空格 for_each(wc, words.end(), [](const string &s){ cout << s << " "; }); cout << endl;
捕获列表只用于局部非static变量,lambda可以直接使用局部static变量和在它所在函数之外声明的名字。
当定义一个lambda时,编译器生成一个与lambda对应的新的(未命名的)类类型。默认情况下,从lambda生成的类都包含一个对应该lambda所捕获的变量的数据成员。类似任何普通类的数据成员,lambda的数据成员也在lambda对象创建时被初始化。
类似参数传递,变量的捕获方式也可以是值或引用。
采用值捕获的前提是变量可以拷贝。与参数不同,被捕获的变量的值是在lambda创建时拷贝,而不是调用时拷贝:
void fcn1() { size_t v1 = 42; // 局部变量 // 将v1拷贝到名为f的可调用对象 auto f = [v1] { return v1; }; v1 = 0; auto j = f(); // j为42;f保存了我们创建它时v1的拷贝 }
采用引用方式捕获变量
void fcn2() { size_t v1 = 42; // 局部变量 // 对象f2包含v1的引用 auto f2 = [&v1]{ return v1; }; v1 = 0; auto j = f2(); // j为0;f2保存v1的引用,而非拷贝 }
- 引用有时是必要的:
void biggies(vector<string> &words, vector<string>::size_type sz, ostream &os = cout, char c = ' ') { // 与之前例子一样的重排words的代码 // 打印count的语句改为打印到os for_each(words.begin(), words.end(), [&os, c](const string &s){ os << s << c; }); }
- 如果函数返回一个lambda,则与函数不能返回一个局部变量的引用类似,此lambda也不能包含引用捕获。
- 当以引用方式捕获一个变量时,必须保证在lambda执行时变量是存在的。
隐式捕获:为了指示编译器推断捕获列表,应在捕获列表中写一个&或=。&告诉编译器采用捕获引用方式,=则表示采用值捕获方式。
// sz为隐式捕获,值捕获方式 wc = find_if(words.begin(), words.end(), [=](const string &s){ return s.size() >= sz; });
可以混合使用隐式捕获和显式捕获:
void biggies(vector<string> &words, vector<string>::size_type sz, ostream &os = cout, char c = ' ') { // 其他处理与前例一样 // os隐式捕获,引用捕获方式;c显示捕获,值捕获方式 for_each(words.begin(), words.end(), [&, c](const string &s){ os << s << c; }); // os显式捕获,引用捕获方式;c隐式捕获,值捕获方式 for_each(words.begin(), words.end(), [=, &os](const string &s){ os << s << c; }); }
lambda捕获列表 解释 [] 空捕获列表。lambda不能使用所在函数中的变量。一个lambda只有捕获变量后才能使用它们 [names] names是一个逗号分隔的名字列表,这些名字都是lambda所在函数的局部变量。默认情况下,捕获列表中的变量都被拷贝。名字前如果使用了&,则采用引用捕获方式 [&] 隐式捕获列表,采用引用捕获方式。lambda体中所使用的来自所在函数的实体都采用引用方式使用 [=] 隐式捕获列表,采用值捕获方式。lambda体将拷贝所使用的来自所在函数的实体的值 [&, identifier_list] identifier_list是一个逗号分隔的列表,包含0个或多个来自所在函数的变量。这些变量采用值捕获方式,而任何隐式捕获的变量都采用引用的方式捕获。identifier_list中的名字前面不能使用& [=, identifier_list] identifier_list中的变量都采用引用方式捕获,而任何隐式捕获的变量都采用值方式捕获。identifier_list中的名字不能包括 this
,且这些名字之前必须使用&默认情况下,对于一个值被拷贝的变量,lambda不会改变其值。如果我们希望能改变一个被捕获的变量的值,就必须在参数列表后加上关键字
mutable
。因此,可变lambda不能省略参数列表:void fcn3() { size_t v1 = 42; // 局部变量 // f可以改变它所捕获的变量的值 auto f = [v1] () mutable { return ++v1; }; // 不会影响外层v1的值 v1 = 0; auto j = f(); // j为43 // 多次调用f,lambda中v1的值会累加 // 在lambda中如果不加mutable,则v1是read-only的 }
一个引用捕获的变量是否(如往常一样)可以修改依赖于此引用指向的是一个const类型还是一个非const类型:
void fcn4() { size_t v1 = 42; // 局部变量 // v1是一个非const变量的引用 // 可以通过f2中的引用来改变它 auto f2 = [&v1]{ return ++v1; }; // 外层v1值改变 v1 = 0; auto j = f2(); }
transform接受三个迭代器和一个可调用对象。前两个迭代器表示输入序列,第三个迭代器表示目的位置。算法对输入序列中每个元素调用可调用对象,并将结果写到目的位置。目的位置迭代器与表示输入序列开始位置的迭代器可以是相同的。当输入迭代器和目的迭代器相同时,transform将输入序列中每个元素替换为可调用对象操作该元素得到的结果。
transform(vi.begin(), vi.end(), vi.begin(), [](int i){ return i < 0 ? -i : i; });
count_if算法接受一对迭代器,表示一个输入范围,还接受一个谓词,会对输入范围中每个元素执行。count_if返回一个计数值,表示谓词有多少次为真。
在很多地方使用相同的操作,或者一个操作需要很多语句才能完成,通常使用函数比lambda表达式更好。
bind标准库函数定义在头文件functional中。可以将bind函数看作一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表。
调用bind的一般形式为:
auto newCallable = bind(callable, arg_list);
其中,newCallable本身是一个可调用对象,arg_list是一个逗号分隔的参数列表,对应给定的callable的参数。即,当我们调用newCallable时,newCallable会调用callable,并传递给它arg_list中的参数。arg_list中的参数可能包含形如_n的名字,其中n是一个整数。这些参数是”占位符“,表示newCallable的参数,它们占据了传递给newCallable的参数的”位置“。数值n表示生成的可调用对象中参数的位置:_1为newCallable的第一个参数,_2为第二个参数,以此类推。
// g是一个有两个参数的可调用对象 auto g = bind(f, a, b, _2, c, _1); // 调用g(X,Y)会调用 f(a, b, Y, c, X) // 可用bind重排参数顺序
名字_n都定义在一个名为placeholders的命名空间中,而这个命名空间本身定义在std命名空间中。
using namespace std::placeholders;
默认情况下,bind的那些不是占位符的参数被拷贝到bind返回的可调用对象中,但是,与lambda类似,有时对有些绑定的参数我们希望以引用方式传递,或是要绑定参数的类型无法拷贝,就必须使用标准库ref函数:
// 1 for_each(words.begin(), words.end(), [&os, c](const string &s){ os << s << c; }); // 2 ostream &print(ostream &os, const string &s, char c) { return os << s << c; } for_each(words.begin(), words.end(), bind(print, ref(os), _1, ' '));
函数ref返回一个对象,包含给定的引用,此对象是可以拷贝的。标准库中还有一个cref函数,生成一个保存const引用的类。与bind一样,函数ref和cref也定义在头文件functional中。
插入迭代器操作 | 解释 |
---|---|
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(所以*it++ = t; 等价于it = t; ) |
插入器有三种类型,差异在于元素插入的位置:
- back_inserter创建一个使用push_back的迭代器
- front_inserter创建一个使用push_front的迭代器
- inserter创建一个使用insert的迭代器。此函数接受第二个参数,这个参数必须是一个指向给定容器的迭代器。元素将被插入到给定迭代器所表示的元素之前。
只有在容器支持push_front的情况下,我们才可以使用front_inserter。类似的,只有在容器支持push_back的情况下,我们才能使用back_inserter。
当调用inserter(c, iter)时,我们得到一个迭代器,接下来使用它时,会将元素插入到iter原来所指向的元素之前的位置。即,如果it是由inserter生成的迭代器,则下面这样的赋值语句
*it = val;
其效果与下面代码一样:
it = c.insert(it, val); // it指向新加入的元素 ++it; // 递增it使它指向原来的元素
unique_copy函数接受第三个迭代器,表示拷贝不重复元素的目的位置。与unique一样,要求重复元素相邻存放。
对于一个绑定到流的迭代器,一旦其关联的流遇到文件尾或遇到IO错误,迭代器的值就与尾后迭代器相等。
istream_iterator<int> in_iter(cin), eof; // 从cin读取int vector<int> vec(in_iter, eof); // 从迭代器范围构造vec
istream_iterator操作 | 解释 |
---|---|
istream_iterator in(is); | in从输入流is读取类型为T的值 |
istream_iterator end; | 读取类型为T的值的istream_iterator迭代器,表示尾后位置 |
in1 == in2 | in1和in2必须读取相同类型。如果它们都是尾后迭代器,或绑定到相同的输入,则两者相等 |
*in | 返回从流中读取的值 |
in->mem | 与(*in).mem的含义相同 |
++in, in++ | 使用元素类型所定义的>>运算符从输入流中读取下一个值。与以往一样,前置版本返回一个指向递增后迭代器的引用,后置版本返回旧值 |
- istream_iterator允许使用懒惰求值:标准库并不保证迭代器立即从流读取数据。具体实现可以推迟从流中读取数据,直到我们使用迭代器时才真正读取。标准库中的实现所保证的是,在我们第一次解引用迭代器之前,从流中读取数据的操作已经完成了。
ostream_iterator操作 | 解释 |
---|---|
ostream_iterator out(os); | out将类型为T的值写到输出流os中 |
ostream_iterator out(os, d); | out将类型为T的值写到输出流os中,每个值后面都输出一个d。d指向一个空字符结尾的字符数组 |
out = val | 用<<运算符将val写入到out所绑定的ostream中。val的类型必须与out可写的类型兼容 |
*out, ++out, out++ | 这些运算符时存在的,但不对out做任何事情。每个运算符都返回out |
我们可以用ostream_iterator来输出值的序列:
ostream_iterator<int> out_iter(cout, " "); for(auto e : vec) *out_iter++ = e; // 赋值语句实际上将元素写到cout cout << endl;
当我们向out_iter赋值时,可以忽略解引用和递增运算。
for(auto e : vec) out_iter = e; // 赋值语句将元素写到cout cout << endl;
通过调用copy来打印vec中的元素,这比编写循环更为简单:
copy(vec.begin(), vec.end(), out_iter); cout << endl;
我们可以为任何定义了输入运算符(>>)的类型创建istream_iterator对象。类似的,只要类型有输出运算符(<<),我们就可以为其定义ostream_iterator。
除了forward_list之外,其他容器都支持反向迭代器。我们可以通过调用rbegin、rend、crbegin、crend成员函数来获得反向迭代器。这些成员函数返回指向容器尾元素和首元素之前一个位置的迭代器。与普通迭代器一样,反向迭代器也有const和非const版本。
通过向sort传递一对反向迭代器来将vector整理为递减序:
sort(vec.begin(), vec.end()); // 按“正常序”排序vec // 按逆序排序:将最小元素放在vec的末尾 sort(vec.rbegin(), vec.rend());
只能从即支持++也支持--的迭代器来定义反向迭代器。流迭代器不支持递减运算,因为不可能在一个流中反向移动。因此,不可能从一个forward_list或一个流迭代器创建反向迭代器。
reverse_iterator的base成员函数将反向迭代器转换并返回一个普通迭代器。
// 在一个逗号分隔的列表中查找第一个元素 auto comma = find(line.cbegin(), line.cend(), ','); cout << string(line.cbegin(), comma) << endl; // 在一个逗号分隔的列表中查找最后一个元素 auto rcomma = find(line.crbegin(), line.crend(), ','); // 错误:将逆序输出单词的字符 cout << string(line.crbegin(), rcomma) << endl; // 正确:得到一个正向迭代器,从逗号后开始读取字符直到line末尾 cout << string(rcomma.base(), line.cend()) << endl;
反向迭代器的目的是表示元素范围,而这些范围是不对称的,这导致一个重要的结果:当我们从一个普通迭代器初始化一个反向迭代器,或是给一个反向迭代器赋值时,结果迭代器与原迭代器指向的并不是相同的元素。
迭代器类别:
- 输入迭代器:只读,不写;单边扫描,只能递增(++);还支持相等性判定运算符(==、!=)、解引用运算符(*)(只出现在赋值运算符右侧)和箭头运算符(->)。
- 输入迭代器只用于顺序访问。
- 对于一个输入迭代器,*it++保证是有效的,但递增它可能导致所有其他指向流的迭代器失效。其结果就是,不能保证输入迭代器的状态可以保存下来并用来访问元素。因此,输入迭代器只能用于单边扫描算法。
- 算法find和accumulate要求输入迭代器;而istream_iterator是一种输入迭代器。
- 输出迭代器:只写,不读;单边扫描,只能递增(++),支持解引用运算符(*)(只出现在赋值运算符左侧)。
- 我们只能向一个输出迭代器赋值一次。
- 只能用于单边扫描算法。
- copy函数的第三个参数就是输出迭代器。ostream_iterator类型也是输出迭代器。
- 前向迭代器:可读写;多遍扫描,只能递增(++),支持所有输入、输出迭代器的操作。
- 双向迭代器:可读写;多遍扫描,可递增递减(++、--),支持所有前向迭代器操作。
- 随机访问迭代器:可读写,多遍扫描,支持全部迭代器运算,除了上述迭代器类别支持的操作外,还有比较两个迭代器相对位置的关系运算符(<、<=、>、>=)、迭代器和一个整数值的加减运算(+、+=、-、-=)令迭代器在序列中前进或后退给定整数个元素、两个迭代器上的减法运算符(-)得到其距离以及下标运算符(iter[n]与*(iter+n)等价)。
- 提供在常量时间内访问序列中任意元素的能力。
- 输入迭代器:只读,不写;单边扫描,只能递增(++);还支持相等性判定运算符(==、!=)、解引用运算符(*)(只出现在赋值运算符右侧)和箭头运算符(->)。
除了输出迭代器之外,一个高层类别的迭代器支持底层类别迭代器的所有操作。C++标准指明了泛型和数值算法的每个得到其参数的最小迭代器类别。对每个迭代器参数来说,其能力必须与规定的最小类别至少相当。向算法传递一个能力更差的迭代器会产生错误。
算法形参模式:
- alg(beg, end, other args);
- alg(beg, end, dest, other args);
- alg(beg, end, beg2, other args);
- alg(beg, end, beg2, end2, other args);
beg和end表示算法所操作的输入范围
dest参数是一个表示算法可以写入的目的位置的迭代器
接受单独的beg2或是接受beg2和end2的算法用这些迭代器表示第二个输入范围
other args表示一些算法还接受额外的、非迭代器的特定参数
算法命名规范:
一些算法使用重载形式传递一个谓词
unique(beg, end); // 使用==运算符比较元素 unique(beg, end, comp); // 使用comp比较元素
_if版本的算法
find(beg, end, val); // 查找输入范围中val第一次出现的位置 find_if(beg, end, pred); // 查找第一个令pred为真的元素
_copy区分拷贝元素的版本和不拷贝的版本
reverse(beg, end); // 反转输入范围中元素的顺序 reverse_copy(beg, end, dest); // 将元素按逆序拷贝到dest
一些算法同时提供_copy和_if版本
// 从v1中删除奇数元素 remove_if(v1.begin(), v1.end(), [](int i){ return i % 2; }); // 将偶数元素从v1拷贝到v2;v1不变 remove_copy_if(v1.begin(), v1.end(), back_inserter(v2), [](int i){ return i % 2; });
链表版本的算法性能比对应的通用版本好得多,对于list和forward_list,应该优先使用成员函数版本的算法而不是通用算法。
list和forward_list成员函数版本的算法 | 解释 |
---|---|
—— | 这些操作都返回void |
lst.merge(lst2)或lst.merge(lst2, comp) | 将来自lst2的元素合并入lst。lst和lst2都必须是有序的。元素将从lst2中删除。在合并之后,lst2变为空。第一个版本使用<运算符;第二个版本使用给定的比较操作 |
lst.remove(val)或lst.remove_if(pred) | 调用erase删除掉与给定值相等(==)或令一元谓词为真的每个元素 |
lst.reverse() | 反转lst中元素的顺序 |
lst.sort()或lst.sort(comp) | 使用<或给定比较操作顺序排序元素 |
lst.unique()或lst.unique(pred) | 调用erase删除同一个值的连续拷贝。第一个版本使用==;第二个版本使用给定的二元谓词 |
list和forward_list的splice成员函数的参数 | 解释 |
---|---|
—— | lst.splice(args)或flst.splice_after(args) |
(p, lst2) | p是一个指向lst中元素的迭代器,或一个指向flst首前位置的迭代器。函数将lst2的所有元素移动到lst中p之前的位置或者是flst中p之后的位置。将元素从lst2中删除。lst2的类型必须与lst或flst相同,且不能是同一个链表 |
(p, lst2, p2) | p2是一个指向lst2中位值的有效的迭代器。将p2指向的元素移动到lst中,或将p2之后的元素移动到flst中。lst2可以是与lst或flst相同的链表 |
(p, lst2, b, e) | b和e必须表示lst2中的合法范围。将给定范围中的元素从lst2移动到lst或flst。lst2与lst(或flst)可以是相同的链表,但p不能指向给定范围中元素。 |
- 链表特有版本与通用版本间的一个至关重要的区别是链表版本会改变底层的容器。