C++primer第10章 泛型算法
标准库并未给每一个容器添加大量的功能,而是提供了一组算法,这些算法中的大多数都独立于任何特定的容器。这些算法是通用的:可用于不同类型的容器和不同类型的元素(不仅包含标准库类型,如vector或list,还包含内置的数组类型)。
10.1概述
大多数算法都定义在头文件algorithm中。标准库还在头文件numeric中定义了一组数值泛型算法。
通常情况下,算法遍历由两个迭代器指定的一个元素范围,对其中每个元素进行一些处理。例如我们有一个int的vector,希望知道此vector中是否包含一个特定值。那我们就可以调用标准库算法find,代码如下:
int val = 42;
auto result = find(vec.cbegin(), vec.cend(), val);
传递给find的前两个参数是表示元素范围的迭代器,第三个参数是一个值。find将范围内的每一个元素与给定值进行比较。返回指向第一个等于给定值的元素的迭代器。如果范围内无匹配元素,则find返回第二个参数来表示搜索失败。
由于find操作的是迭代器,所以,我们可以用同样的find函数在任何容器中查找值。例如我们可以用find在一个string的list中查找一个给定值。
string val = "value";
auto result = find(lst.cbegin(), lst.cend(), val);
应用于内置数组也是一样的:
int ia[] = { 1,2,3,4,5 };
int val = 3;
int* result = find(begin(ia), end(ia), val);
注:迭代器令算法不依赖于容器,但算法依赖于元素类型的操作(比如上面的使用的find就要求vector和list中的元素支持==元素符)。不过我们也可以用自定义的操作来替代默认的运算符。
算法可以在容器内移动元素,也可以改变容器内元素的值,但不可以直接添加或删除元素,改变容器的大小。
10.2初识泛型算法
标准库提供了很多的算法,都有一致的结构。除了少数的例外,标准库算法都对一个范围内的元素进行操作。接受范围的算法总是使用前两个参数来表示此范围,两个参数分别指向要处理的第一个元素和尾元素之后位置的迭代器。
算法使用范围内的元素的方式不同,可分为是否读取元素、改变元素或是重排元素顺序。
10.2.1只读算法
一些算法只会读取输入范围内的元素,而不改变元素。find算法就是其中一个。count算法也是。在头文件中numeric的accumulate算法也是。
(1) accumulate算法接受三个参数,前两个指出了需要求和的元素的范围,第三个参数是和的初值。
//对vec中的元素求和,和的初始值为0
auto sum = accumulate(vec.cbegin(), vec.cend(), 0);
accumulate的第三个参数的类型决定了函数中使用哪个加法运算符以及返回值的类型。
由于string定义了+运算符,所以我们可以通过调用accumulate来将vector中的所有的string元素连接起来:
auto sum = accumulate(vec.cbegin(), vec.cend(), string(""));
此调用将vec中的每个元素连接到一个string上,该string的初始时为空串。但如下的写法是不正确的。
string sum = accumulate(vec.cbegin(), vec.cend(), "");
因为字符串字面值是const char*类型,并没有定义+运算符。
对于只读取而不改变元素的算法,通常最好使用cbegin()和cend()。但如果计划使用算法返回的迭代器来改变相应元素的值,就需要使用begin()和end()来作为参数。
(2) 另外一个只读算法是equal,用于确定两个序列是否保存相同的值。equal算法将第一个序列中的每个元素与第二个序列中的对应元素进行比较。如果所有的对应元素都相等,则返回true,否则返回false。此算法接受三个迭代器:前两个表示一个序列中元素范围,第三个表示第二个序列的首元素。
//roster2中的元素的数目至少和roster1一样多
equal(roster1.cbegin(), roster1.cend(), roster2.cbegin());
只要roster1中的元素类型能用==与roster2中的元素类型进行比较即可,两个容器中的元素类型可以不相同。容器也可以不相同。例如roster1可以是一个vector<string>,而roster2可以是一个list<const char*>。equal还基于一个假设,那就是roster2的长度至少大于等于roster1的长度。
10.2.2写容器元素的算法
一些算法将新值赋值给序列中的元素。例如算法fill接受一对迭代器表示一个范围,还接受一个值作为第三个参数。
(1) fill将给定的这个值赋予输入序列中的每个元素。
//将vec中的每个元素置为0
fill(vec.begin(), vec.end(), 0);
(2) 算法fill_n接受一个单迭代器、一个计数值和一个值。该算法将给定值赋予迭代器指向的元素开始的指定个元素。
//将vec中的每个元素置为0
fill_n(vec.begin(), vec.size(), 0);
该算法假定容器至少拥有第二个参数这个大的大小。
其中使用算法fill_n比较容易出错的一个就是第二个参数大于了容器的大小,比如现在vec容器大小只有3,而写出了如下的代码:
fill_n(vec.begin(), 10, 0);
程序执行到此时就会报错。对于算法fill也是相同。
注:向目的位置迭代器写入数据的算法假定目的位置足够大,能容纳要写入的元素。
一种保证算法有足够元素空间来容纳输出数据的方法是使用插入迭代器。插入迭代器是一种向容器中添加元素的迭代器。
(3) 函数back_inserter在头文件iterator中,接受一个指向容器的引用,返回一个与该容器绑定的插入迭代器。当通过返回的迭代器赋值时,赋值运算符会调用push_back将一个具有给定值的元素添加到容器中。
vector<int> vec;
auto it = back_inserter(vec);
*it = 42; //vec中现在有一个元素,值为42
我们常常使用back_inserter来创建一个迭代器,作为算法的目的位置来使用。例如:
fill_n(back_inserter(vec), 10, 0);
以上代码即便vec的大小不到10个,还是不会出错。在每一部迭代中,fill_n向给定序列的一个元素赋值。由于我们传递的参数是back_inserter返回的迭代器,因此每次赋值都会在vec中调用push_back方法。最终,这条fill_n调用语句向vec的末尾加了10个元素,每个元素的值都是0。
(4) copy算法接受三个迭代器,前两个表示一个输入范围,第三个表示目的序列的起始位置。此算法将输入范围内的元素拷贝到目的序列中。传递给copy的目的序列至少要包含与输入序列一样多的元素。
auto ret = copy(src.begin(), src.end(), dst.begin());
copy返回的是其目的位置迭代器的值,即dst的尾元素之后的位置。
(5) replace算法读入一个序列,并将其中所有等于给定值的元素都改为另外一个值。此算法接受4个参数:前两个是迭代器,表示输入序列,后两个一个是要搜索的值,另一个是新值。
replace(ilist.begin(), ilist.end(), 0, 42);
此调用将将序列中所有的0替换为42。
(6) replace_copy
replace_copy(ilist.begin(), ilist.end(),back_inserter(ivec) 0, 42);
此调用后,ilist并未改变,ivec包含ilist的一份拷贝,不过在ilist中值为0的元素在ivec中都变为42。
10.2.3重排容器元素的算法
一些算法会重排容器中元素的顺序。
(1) sort算法会重排输入序列中的元素,使之有序,默认是利用元素类型的<运算符来实现排序的。
sort(vec.begin(), vec.end());
(2) 如果希望将排完序的vec中的相同的元素只保留一份,可以使用unique算法
auto end_unique = unique(vec.begin(), vec.end()); //vec已经排好序
返回一个指向不重复值范围末尾的迭代器。
然后我们可以调用erase删除重复的元素。
vec.erase(end_unique, vec.end());
注:标准库算法对迭代器而不是对容器进行操作。因此,算法不能(直接)添加或删除元素。
10.3定制操作
标准库允许我们提供自己定义的操作来代替默认运算符。
10.3.1向算法传递函数
现在有一个vector<string>类型的strVec,我们希望按照单词的长度进行排序,如果长度相同,再按照字典序进行排序。我们依然使用sort算法,但需要输入第三个参数,是一个谓词。
谓词是一个可调用的表达式,其返回的结果是一个能作为条件的值。谓词分为一元谓词(接受一个参数)和二元谓词(接受两个参数)。接受谓词参数的算法,对输入序列中的元素调用调用谓词。因此,元素类型必须可以转换为谓词的参数类型。
//定义比较函数充当谓词:
bool isShorter(const string &s1, const string &s2)
{
return s1.size() < s2.size();
}
//按照单词长度进行排序
sort(strVec.begin(), strVec.end(), isShorter);
调用完成后,strVec中的元素按照元素的长度进行排序。
stable_sort(strVec.begin(), strVec.end(), isShorter);
调用stable_sort完成后,strVec中的元素不但按照元素长度排序完成,而且相同长度的元素会按照字典顺序排序完成。
10.3.2lambda表达式
现在有一个vector<string>类型的strVec,strVec中的元素已经根据元素的长度由短到长排好序了,相同长度的元素也根据字典顺序排好序了。现在我们要在strVec中找到第一个大于等于给定长度的元素。
我们可以使用find_if算法来完成以上的需求,find_if算法接受三个参数,前两个表示一个序列范围,第三个接受一个一元谓词。那这样的话我们定义以下的函数当作谓词传递给find_if的第三个参数就不好使了。因为这个函数可以充当二元谓词,而不能充当一元谓词。
bool biggerSize(const string &str, int size );
那接下来我们就需要介绍lambda表达式来完成以上的需求。
lambda表达式表示一个可调用的代码单元。我们可以将其理解为一个未命名的内联函数。lambda表达式具有以下形式。
[capture list](parameter list) -> return type {function body}
我们可以忽略参数列表和返回类型,但必须永远包含捕获列表和函数体。
auto f = [] {return 42;};
cout<<f()<<endl;
编写一个与isShorter函数功能相同的lambda表达式。
[](const string &strA, const string &strB)
{
return strA.size() < strB.size();
}
空的捕获列表表明此lambda不使用它所在函数任何局部变量。stable_sort的另外一种写法如下:
stable_sort(strVec.begin(), strVec.end(), [](const string& a, const string& b)
{
return a.size() < b.size();
}
);
我们可以使用捕获列表捕获函数中的变量,比如现在函数中有个变量名为sz,则我们书写lambda表达式如下:
[sz](const string &a)
{
return a.size() >= sz;
}
现在我们可以使用find_if算法了。
auto wc = find_if(strVec.begin(), strVec.end(), [sz](const string &a)
{
return a.size() >= sz;
}
);
find_if的调用返回一个迭代器,指向第一个长度不小于给定参数sz的元素。如果这样的元素不存在,则返回strVec.end()的有个拷贝。
现在我们想把strVec中元素长度大于sz的元素全部打印出来,就调用了for_each算法。
for_each(wc, strVec.end(), [](const string& s)
{
cout<<s<<” ”;
}
);
捕获列表只用于局部非static变量,lambda可以直接使用局部static变量和它所在函数之外声明的名字。
10.3.3lambda捕获和返回
类似于参数传递,变量捕获的方式也可以是值或引用。采用值传递的前提是变量支持拷贝。与参数不同,被捕获的变量的值在lambda创建时拷贝,而不是在调用时拷贝。
auto f = [v1]{return v1;} ; //值捕获
auto f2 = [&v1]{return v1;}; //引用捕获
如果我们采用引用方式捕获一个变量,就必须确保被引用的对象在lambda执行的时候时存在的。
一般来说,我们应该尽量减少捕获的数据量,来避免潜在的捕获导致的问题。而且,如果可以的话,应该避免捕获指针或引用。
隐式捕获:捕获列表中还可以写一个&或者=。&告诉编译器采用捕获引用方式,=表示采用值捕获方式。编译器会自行推到lambda中用到的函数中的变量。
如果我们希望对一部分变量采用值捕获,对其他变量采用引用捕获,可以混合使用隐式捕获和显式捕获。
//os隐式捕获,引用捕获方式,c显式捕获,值捕获方式
for_each(strVec.begin(), strVec.end(), [&, c](const string &s)
{
os<<s<<c;
}
);
//os显式捕获,引用捕获方式;c隐式捕获,值捕获方式。
for_each(strVec.begin(), strVec.end(), [=, &os](const string &s)
{
os<<s<<c;
}
);
我们混用隐式捕获和显式捕获时,捕获列表中的第一个元素必须时一个&或者=。此符号指定了默认捕获方式为引用或值。
默认情况下,对于一个值被拷贝的变量,lambda不会改变其值。如果我们希望能改变一个被捕获的变量的值,就必须在参数列表中首加上关键字mutable。因此,可变lambda能省略参数列表。
auto f = [v1]()mutable{return ++val;};
一个引用捕获的变量是否可以修改依赖于此引用指向的是一个const类型还是一个非const类型。
auto f2 = [&v1]{return ++v1;};
如果v1是一个const对象,则以上代码编译出错。如果v1是一个非const对象,则以上代码正常运行。
默认情况下,如果一个lambda包含return之外的任何语句,则编译器假定此lambda返回void。与其返回void的函数类似,被推断返回void的lambda不能返回值。
举例:
transform(vi.begin(), vi.end(), vi.begin(), [](int i)
{
return i<0?-i:i;
}
);
算法transform接受三个迭代器和一个可调用对象。前两个迭代器表示输入序列,第三个迭代器表示目的位置。当输入迭代器和目的迭代器相同时,transform将输入序列中每个元素替换为可调用对象操作该元素得到的结果。
上面的代码中,lambda中只含有一句return语句,所以编译器会自动推断返回结果类型。如果有return之外的代码,且无指定返回类型,编译器会默认返回void类型。如下面所示:
//错误:不能推断lambda的返回类型
transform(vi.begin(), vi.end(), vi.begin(),[](int i)
{
if(i<0)
return -i;
else
return i;
}
);
我们必须指定返回值类型
transform(vi.begin(), vi.end(), vi.begin(),[](int i) -> int
{
if(i<0)
return -i;
else
return i;
}
);
10.3.4参数绑定
使用lambda表达式的场景:对于那种只在一两个地方使用的简单操作,lambda表达式是最有用的。如果我们需要在很多地方使用相同的操作,通常应该定义一个函数,而不是多次编写相同的lambda表达式。类型的,如果一个操作很多语句才能完成,通常使用函数更好。
如上文所示,find_if接受一个一元谓词。因此传递给find_if的可调用对象必须接受单一参数。如果我们想传递一个两个参数的函数给find_if来代替lambda表达式,就需要用到bind函数。
bind标准库函数,它定义在functional中。我们可以将bind函数看作一个通用的函数适配器,他接受一个可调用对象,生成一个可调用对象来“适应”原对象的参数列表。
auto newCallable = bind(callable, arg_list);
其中newCallable本身是一个可调用对象,arg_list是一个逗号分割的参数列表,对应给定的callable的参数。当我们调用newCallable时,newCallable会调用callable,并传递给它arg_list中的参数。
arg_list中的参数可能包含形如_n的名字,其中n是一个整数。_1为newCallable的第一个参数,_2为第二个参数,一次类推。
现在有如下的定义:
bool check_size(const string &s, string::size_type sz)
{
return s.size() >= sz;
}
auto check6 = bind(check_size, _1, 6);
此bind调用只有一个占位符,表示check6只接受单一的参数。占位符出现在arg_list的第一个位置,表示check6的此参数对应check_size的第一个参数。此参数是一个const string&。因此,调用check6必须传递给它一个string类型的参数,check6会将此参数传递给check_size。
string s = “hello”;
bool b1 = check6(s); //check6(s)会调用check_size(s, 6)
ok,现在我们就可以不使用lambda表达式就可以调用find_if了。
auto wc = find_if(strVec.begin(), strVec.end(), bind(check_size, _1, sz));
此bind调用生成一个可调用对象,将check_size的第二个参数绑定到sz的值(sz是一个外部变量名)。当find_if对strVec中的string调用这个对象时,这些对象会调用check_size,将给定的string和sz传递给它。因此find_if可以有效的对输入序列中的每个string调用check_size,实现string的大小与sz的比较。
名字_1,_2都定义在一个名为placeholders的命名空间中,而这个命令空间本身定义在std命名空间。使用时为方便需要添加如下代码:
using namespace std::placeholders;
我们可以用bind修正参数的值,也可以用bind绑定给定可调用对象中的参数或重新安排其顺序。
auto g = bind(f,a,b,_2,c,_1);
生成一个新的可调用对象,他有两个参数,分别用占位符_2和_1表示。新的可调用对象将它自己的参数作为第三个和第五个参数传递给f。f的第一个、第二个和第四个参数分别绑定在给定值a、b、c上,传递给g的第一个参数绑定到_1,也是f的第五个参数,第二个参数绑定到_2上,也是f的第三个参数。
这样我们就可以写出以下的代码:
//按单词的长度由短到长排序
sort(strVec.begin(), strVec.end(), isShorter);
//按照单词长度由长至短排序
sort(strVec.begin(), strVec.end(), bind(isShorter, _2, _1));
有时候我们希望绑定的参数以引用方式传递,或者是要绑定参数的类型无法拷贝。
比如以下代码:
ostream& print(ostream& os, const string &s, char c)
{
return os<<s<<c;
}
//错误,因为os不支持拷贝
for_each(strVec.begin(), strVec.end(), bind(print, os, _1, ‘ ’));
我们可以写如下的代码就可以实现参数引用传递了。
for_each(strVec.begin(), strVec.end(), bind(print, ref(os), _1, ‘ ’));
ref定义在functional中。
10.4再探迭代器
标准库在头文件iterator中还定义了额外的几种迭代器。插入迭代器,流迭代器,反向迭代器,移动迭代器。
10.4.1插入迭代器
插入器是一种迭代器适配器,它接受一个容器,生成一个迭代器,能实现向给定容器中添加元素。
插入迭代器操作 |
|
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 |
插入迭代器有三种类型:
back_inserter创建一个使用push_back的迭代器。
front_inserter创建一个使用push_front的迭代器。
inserter创建一个使用insert的迭代器。此函数接受第二个参数,参数必须指定一个容器的迭代器。
只有容器在支持相应的操作下,我们才可以使用相应的插入器。
理解插入器的工作过程很重要。当调用insterter(c, iter)时,我们得到一个迭代器,接下来我们使用这个迭代器时,会将元素插入到iter原来指向的元素之前的位置。如果it是由inserter生成的迭代器。即下面的赋值语句:
*it = val;
相当于
it = c.insert(it, val); //it会指向新加入的元素。
++it;
front_inserter生成的迭代器的行为与inserter生成的迭代器的行为完全不一样。使用front_inserter时,元素总是插入到容器的第一个元素之前。
list<int> lst = {1,2,3,4};
list<int> lst2,lst3;
//拷贝完成之后,lst2包含4 3 2 1
copy(lst.cbegin(), lst.cend(), front_inserter(lst2));
//拷贝完成之后 lst3包含1 2 3 4
copy(lst.cbegin(), lst.cend(), inserter(lst3, lst3.begin()));
10.4.2iostream迭代器
istream_iterator读取输入流,ostream_iterator向一个输出流写数据
当创建一个流迭代器时,必须指定迭代器将要读写的对象类型。
istream_iterator<int> in_iter(cin); //从cin读取int
istream_iterator<int> eof; //istream的尾后迭代器
while(in_iter != eof)
{
vec.push_back(*in_iter++);
}
此循环从cin中读取int值,保存在vec中,与下面的代码等价。
istream_iterator<int> in_iter(cin), eof; //从cin中读取int
vector<int> vec(in_iter, eof); //从迭代器范围构造vec
使用算法操作流迭代器:
istream_iterator<int> in(cin), eof;
cout<<accumulate(in, eof, 0)<<endl;
此调用会计算从标准输入读取的值的和。
一个istream_iterator绑定到一个流时,标准库并不保证迭代器立即从流读取数据。具体实现可以推迟从流中读取数据,直到我们使用迭代器时才真正读取。标准库中的实现保证的是,在我们第一次解引用迭代器之前,从流中读取数据的操作已经完成。
我们可以对任何具有输出运算符(<<运算符)的类型定义ostream_iterator。创建一个ostream_iterator时,提供可选的第二个参数,是一个C风格的字符串,在输出每个元素后都会打印此字符串。
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 |
ostream_iterator<int> out_iter(cout, “ ”);
for(auto e : vec)
{
*out_iter++ = e;
}
此程序将vec中的每个元素写入到cout,每个元素后加一个空格。每次向out_iter赋值时,写操作就会被提交。虽然*out_iter和out_iter++的没有任何操作,但相较于out_iter = e,还是推荐第一种写法。
可以通过拷贝来打印vec中的元素:
copy(vec.begin(), vec.end(), out_iter);
10.4.3反向迭代器
反向迭代器就是在容器中从尾元素向首元素反向移动的迭代器。对于反向迭代器,递增和递减的操作会颠倒过来。递增一个反向迭代器(++it)会移动到前一个元素;递减一个迭代器(--it)会移动到下一个元素。
除了forword_list之外,其他容器都支持反向迭代器。我们可以通过rbegin(),rend(),crbegin(),crend()来获得反向迭代器。
//反向打印vec中的元素
vector<int> vec = {0,1,2,3,4,5,6,7,8,9};
for(auto r_iter = vec.crbegin(); r_iter = vec.crend(); ++r_iter)
{
cout<<*r_iter<<endl;
}
//按逆序排列,将最小的元素放在vec的末尾
sort(vec.rbegin(), vec.rend());
不能从一个forword_list或者一个流迭代器创建反向迭代器。
有一个名为line的string,保存着一个逗号分割的单词列表,我们希望打印line中的第一个单词。
auto comma = find(line.cbegin(), line.cend(), ‘,’);
cout<<string(line.cbegin(), comma)<<endl;
希望打印最后一个单词:
auto rcomma = find(line.crbegin(), line.crend(), ‘,’);
cout<<string(line.rbegin(), rcomma)<<endl;
上面的代码如果line是one,two,three的话是打印不出three的,只能打印出
eerht。
以下代码才是正确的:
cout<<string(rcomma.base(), line.cend())<<endl;
rcomma.base()得到一个正向迭代器。
注:当我们从一个普通迭代器初始化一个反向迭代器,或是给一个反向迭代器赋值时, 结果迭代器与原迭代器指向的并不是相同的元素。
10.5泛型算法结构
算法所要求的迭代器操作可以分为5个迭代器类别。
迭代器类别 |
|
输入迭代器 |
只读,不写;单遍扫描,只能递增。 |
输出迭代器 |
只写,不读;单遍扫描,只能递增。 |
前向迭代器 |
可读写,多遍扫描,只能递增。 |
双向迭代器 |
可读写,多遍扫描,可递增递减。 |
随机访问迭代器 |
可读写,多遍扫描,支持全部迭代器运算。 |
10.5.1 5类迭代器
一个高层次的迭代器支持低层类别迭代器的所有操作。find算法在一个序列上进行一遍扫描,对元素进行只读操作,因此至少需要输入迭代器。replace函数需要一对迭代器,至少是前向迭代器。replace_copy的前两个迭代器参数也要求至少是前向迭代器,第三个参数表示目的位置,必须至少是输出迭代器。
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);
10.5.3 算法命名规范
一些算法使用重载形式传递一个谓词。
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;});
10.6 特定容器算法
list和forward_list定义了几个成员函数形式的算法。sort、merge、remove、reverse和unique。通用版本的sort用于随机访问迭代器,因此不能用于list和forword_list。list应该支持的是双向访问迭代器,forword_list支持的是前向迭代器。因为链表数据结构的原因,使用自身支持的这些算法比通用算法效率要高很多。
splice算法,链表特有,没有通用版本。
list和forward_list的splice成员函数的参数 |
|
lst.splice(args)或flst.splice_after(args) |
|
(p, lst2) |
p指向一个lst中元素的迭代器,或一个指向flst首前位置的迭代器。函数将lst2的所有元素移动到lst中的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不能指向给定范围中的元素。 |
链表特有的操作会改变容器。remove的链表版本会删除指定的元素。remove的通用版不会改变容器的大小,unique的链表版会删除第二个和后继的重复元素,而unique的通用版也不会直接改变容器的大小。
merge和splice会销毁其参数。通用版本的merge会将合并的序列存放在第三个容器中,原来的两个容器的数据保持不变。而链表的merge是将一个链表合并到零个链表中,一个链表被销毁了,另一个链表的大小发生了改变。
posted on 2022-02-16 08:20 xcxfury001 阅读(37) 评论(0) 编辑 收藏 举报
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战