C++ Primer记录_第十章

本文详细介绍了泛型算法的概念,包括只读和写容器元素的算法,如find、accumulate、fill、replace等。重点讲解了重排元素的算法,如sort和unique,并展示了如何通过lambda表达式和参数绑定定制操作。此外,还探讨了不同类型的迭代器,如插入迭代器、iostream迭代器和反向迭代器,以及它们在泛型算法中的作用。最后,强调了迭代器类别、算法命名规范以及特定容器如list和forward_list的成员函数算法。

第十章 泛型算法

  • 泛型算法称为“算法”,是因为它们实现了一些经典算法的公共接口,如排序和搜索。
  • 泛型算法称为“泛型”,是因为它们可以用于不同类型的元素和多种容器类型。

10.1 概述

  • 大多数算法都定义在头文件algorithm中。
  • 标准库还在头文件numeric中定义了一组数值泛型算法。
  • 这些算法通常遍历两个迭代器指定的一个元素范围来进行操作。
  • 最好的例子是标准库算法find:传递给find的前两个参数是表示元素范围的迭代器,第三个参数是一个值。
  • 由于find操作的是迭代器,因此我们可以用同样的find函数在任何容器中查找值。
int val = 42; //我们将查找的值
//如果在vec中找到想要的元素,则返回结果指向它,否则返回结果为vec.cend()
auto result = find(vec.cbegin(), vec.cend(), val);
string val = : "a value"; //我们要查找的值
//此调用在list中查找string元素
auto result = find(lst.cbegin(), lst.cend(), val);
int ia[] = {27, 210, 12, 47, 109, 83); 
int val= 83; 
int* result= find(begin(ia), end(ia), val); 
  • 算法工作步骤(以find为例):
    1. 访问序列中的首元素。
    2. 比较此元素与我们要查找的值。
    3. 如果此元素与我们要查找的值匹配,find返回标识此元素的值。
    4. 否则,find前进到下一个元素,重复执行步骤2和3。
    5. 如果到达序列尾,find应停止。
    6. 如果find到达序列末尾,它应该返回一个指出元素未找到的值。此值和步骤3返回的值必须具有相容的类型。
  • 迭代器令算法不依赖于容器,但算法依赖于元素类型的操作。

10.2 初识泛型算法

  • 标准库提供了超过100个算法,与容器类似,这些算法有一致的结构。

10.2.1 只读算法

  • 一些算法只会读取其输入范围内的元素,而从不改变元素,被称为只读算法
  • 除find算法,在头文件numeric中,accumulate函数也是一个只读算法。
//对vec中的元素求和,和的初值是0
int sum = accumualte(vec.cbegin(), vec.cend(), 0);
  • accumulate将第三个参数作为求和起点,这蕴含若一个编程假定:将元素类型加到和的类型上的操作必须是可行的。
string sum = accumulate(v.cbegin(), v.cend(), string(""));
//错误:const char*上没有定义+运算符
string sum = accumulate(v.cbegin(), v.cend(), "");
  • 只读算法equal,用于确定两个序列是否保存相同的值。
// roster2中的元素数目应该至少与roster1一样多
equal(roster1.cbegin(), roster1.cend(), roster2.cbegin());
  • 那些只接受一个单一迭代器来表示第二个序列的算法,都假定第二个序列至少与第一个序列一样长。

10.2.2 写容器元素的算法

  • 一些算法将新值赋予序列中的元素,必须注意确保序列原大小至少不小于我们要求算法写入的元素数目。
  • 算法不会执行容器操作,因此它们自身不可能改变容器的大小。
fill(vec.begin(), vec.end(), 0); //将每个元素重置为0
//将容器的一个子序列设置为10
fill(vec.begin(), vec.begin() + vec.size() / 2, 10);
  • fill_n算法,不能在空容器上调用fill_n。
vector<int> vec; //空vector
//先使用vec,赋予它不同值
fill_n(vec.begin(), vec.size(), 0); //将所有元素重置为0
  • 一种保证算法有足够元素空间来容纳输出数据的方法是使用插入迭代器
  • back_inserter定义在头文件iterator中的一个函数,它接受一个指向容器的引用,返回一个与容器绑定的插入迭代器。
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算法是另一个向目的位置迭代器指向的输出序列中的元素写入数据的算法。
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
  • replace算法读入一个序列,并将其中所有等于给定值的元素都改为另一个值。
//将所有值为0的元素改为42
replace(ilst.begin(), ilst.end(), 0, 42);
  • replace_copy算法调用后,ilst并未改变,ivec包含ilst的一份拷贝,不过原来在ilst中值为0的元素在ivec中都变为42。
//使用back_inserter按需要增长目标序列
replace_copy(ilst.cbegin(), ilst.cend(), back_inserter(ivec), 0, 42);

10.2.3 重排容器元素的算法

  • 某些算法会重排容器中元素的顺序,一个明显的例子是sort
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());
}
the quick red fox jumps over the slow red turtle
//sort(words.begin(), words.end());
fox,jumps,over,quick,red,red,slow,the,the,turtle
//auto end_unique = unique(words.begin(), words.end());
fox,jumps,over,quick,red,slow,the,turtle,???,???
//words.erase(end_unique, words.end());
fox,jumps,over,quick,red,slow,the,turtle

10.3 定制操作

  • 很多算法都会比较输入序列中的元素,标准库还为这些算法定义了额外的版本,允许我们提供自己定义的操作来代替默认运算符。

10.3.1 向算法传递函数

  • 此外希望单词按其长度排序,大小相同的再按字典序排列。将使用sort的第二个版本,此版本是重载过的,它接受第三个参数,此参数是个谓词
  • 谓词是一个可调用的表达式,其返回结果是一个能用作条件的值。
  • 标准库算法所使用的谓词分为两类:一元谓词(只接受一个参数)和二元谓词(接受两个参数)。
//比较函数,用来按长度排序单词
bool isShorter(const string &s1,const string &s2)
{
    return s1.size()<s2.size();
}
//按长度由短至长排序words
sort(words.begin(),words.end(),isShorter);
  • stable_sort算法可以保持等长元素间的字典序。

10.3.2 lambda表达式

  • 有时希望进行的操作需要更多参数,超出了算法对谓词的限制。
  • 可以向一个算法传递任何类别的可调用对象,对于一个对象或一个表达式,如果可以对其使用调用运算符,则称它为可调用的。
  • 一个lambda表达式表示一个可调用的代码单元,可以理解为一个未命名的内联函数。
  • 一个lambda表达式具有如下形式:
[capture list](parameter list) -> return type { function body }
  • capture list (捕获列表)是一个lambda所在函数中定义的局部变量的列表(通常为空)。
  • return typeparameter listfunction body与任何普通函数一样,分别表示返回类型、参数列表和函数体。
  • 与普通函数不同,lambda必须使用尾置返回来指定返回类型。
  • 可以忽略参数列表和返回类型,但必须永远包含捕获列表和函数体。
auto f = [] { return 42; }
  • 忽略括号和参数列表等价于指定一个空参数列表。
  • 如果忽略返回类型,lambda根据函数体中的代码推断出返回类型。
cout << f() << endl; //打印42
  • 编写一个与isShorter函数完成相同功能的lambda。
[](const string &a, const string &b) { return a.size() < b.size(); }
sort(words.begin(), words.end(), 
    [](const string &a, const string &b) 
    { return a.size() < b.size(); });
  • 关于求大于等于一个给定长度的单词有多少,还会修改输出,使程序只打印大于等于给定长度的单词。
void biggies(vector<string> &words, vector<string>::size_type sz)
{
    elimDups(woreds); //将words按宇典序排序,删除重复单词
    //按长度排序,长度相同的单词维持字典序
    stable_sort(words.begin(), words.end(), isShorter);
    //获取一个迭代器,指向笫一个满足size() >= sz的元素
    //计算满足size >= sz的元素的数目
    //打印长度大于等于给定值的单词,每个单词后面接一个空格
}
  • 标准库find_if算法来查找第一个具有特定大小的元素。
  • find_if接受一元谓词,但是没有任何办法能传递给它第二个参数来表示长度。
  • 所以使用捕获列表来捕获局部变量。
[sz](const string &a)
{ return a.size() >= sz; };
  • 完整的biggies。
void biggies(vector<string> &words, vector<string>::size_type sz)
{
    elimDups(woreds); //将words按宇典序排序,删除重复单词
    //按长度排序,长度相同的单词维持字典序
    stable_sort(words.begin(), words.end(), [](const string &a, const string &b)
                { return a.size() < b.size(); });
    //荻取一个迭代器,指向笫一个满足size() >= sz的元素
    auto wc = find_if(words.begin(), word.end(), [sz](const string &a)
                      { return a.size() >= sz; });
    //计算满足size >= sz的元素的数目
    auto count = words.end() - wc;
    cout << count << " " << make_plural(count, "word", "s") << " of length " << sz << " or longer " << endl;
    //打印长度大于等于给定值的单词,每个单词后面接一个空格
    for_each(wc, words.end(), [](const string &s)
             { cout << s << " "; });
    cout << endl;
}

10.3.3 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的拷贝
}
  • 引用捕获。当以引用方式捕获一个变量时,必须保证在lambda执行时变量是存在的。
void fcn2()
{
    size_t v1 = 42; //局部变量
    //对象f2包含v1的引用
    auto f2 = [&v1]
    { return v1; };
    v1 = 0;
    auto j = f2(); // j为0;f2保存v1的引用,而非拷贝
}
  • 引用捕获有时是必要的,如不能拷贝ostream对象,但是希望biggies函数接受一个ostream。
void biggies(vector<string> &words, vector<string>::size_type sz, ostream &os = cout, char c = ' ')
{
    //与之前例子一样的重排words的代码
    //打印count的语句改为打印到os
    for_each(words.begin(), word.end(), [&os, c](const string &s)
             { os << s << c; });
}
  • 隐式捕获。可以让编译器根据lambda体中的代码来推断我们要使用哪些变量
  • 告诉编译器采用捕获引用方式,则表示采用值捕获方式。
// sz为隐式捕获,值捕获方式
wc = find_if(words.begin(), word.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(), word.end(), [&, c](const string &s)
             { os << s << c; });
    // os显式捕获,引用捕获方式;c隐式捕获,值捕获方式
    for_each(words.begin(), word.end(), [=, &oc](const string &s)
             { os << s << c; });
}
lambda捕获列表描述
[]空捕获列表
[names]names是一个逗号分隔的名字列表,这些名字都是lambda所在函数的局部变量。
[&]隐式捕获列表,采用引用捕获方式。
[=]隐式捕获列表,采用值捕获方式。
[ & , identifer_list]identifer_list是一个逗号分隔的列表, 包含0个或多个来着所在函数的变量,这些变量采用值捕获,而任何隐式捕获的变量都采用引用捕获。
[ = , identifer_list]identifer_list中的值都是采用引用捕获,而任何隐式捕获的变量都采用值捕获
  • 对于一个值被拷贝的变量,lambda不会改变其值。
  • 希望能改变一个被捕获的变量的值,就必须在参数列表首加上关键字mutable。
  • 可变lambda能省略参数列表。
void fcn3()
{
    size_t v1 = 42; //局部变量
    // f可以改变它所捕获的变量的值
    auto f = [v1]() mutable
    { return ++v1; };
    v1 = 0;
    auto j = f(); // j为43
}
void fcn4()
{
    size_t v1 = 42; //局部变量
    // v1是一个非const变量的引用
    //可以通过f2中的引用来改变它
    auto f2 = [&v1]
    { return ++v1; };
    v1 = 0;
    auto j = f2(); // j为1
}
  • 如果一个lambda体包含return之外的任何语句,则编译器假定此lambda返回void。
transform(vi.begin(), vi.end(), vi.begin(), [](int i)
          { return i < 0 ? -i : i });
  • 以下if版本,编译器推断这个版本的lambda返回类型为void。
//错误:不能推断lambda的返回类型
transform(vi.begin(), vi.end(), vi.begin(), [](int i)
          { if(i<0) return i < 0 ? -i : i });
  • 当我们需要为一个lambda定义返回类型时,必须尾置返回类型。
transform(vi.begin(), vi.end(), vi.begin(), [](int i) -> int
          { if(i<0) return i < 0 ? -i : i });

10.3.4 参数绑定

  • 对于捕获局部变量的lambda,用函数来替换它不是那么容易。
  • 如用在find_if调用中的lambda比较一个string和一个给定大小。
bool check_size(const string &s, string::size_type sz)
{
    return s.size() >= sz;
}
  • 但是不能用这个函数作为find_if的一个参数。
  • 为了解决check_size传递一个长度参数的问题,是使用bind的标准库函数,定义在头文件functional中。
  • 可以将bind函数看作一个通用的函数适配器,接受一个可调用对象,生成一个新的可调用对象来适应原对象的参数列表。
  • bind的一般形式为:当调用newCallable时,newCallable会调用callable,并传递给它arg_list中的参数。
    • newCallable本身是一个可调用对象。
    • arg_list是一个逗号分隔的参数列表,对应给定的callable的参数。
    • arg_list中的参数可能包含形如_n的名字,这些参数是”占位符",
auto newCallable = bind(callable, arg_list);
  • 绑定check_size的sz参数
bool check_size(const string &s, string::size_type sz)
{
    return s.size() >= sz;
}

// check6是一个可调用对象,接受一个string类型的参数
//并用此string和值6来调用check_size
auto check6 = bind(check_size, _1, 6);

string s = "hello";
bool b1 = check6(s); // check6(s)会check_size(s,6)
  • 以下lambda和bind的比较。
auto wc = find_if(words.begin(), word.end(), [sz](const string &a)
                  { return a.size() >= sz; });

auto wc = find_if(words.begin(), word.end(), bind(check_size, _1, sz));
  • 用bind重排参数顺序可以颠倒含义
//按单词长度由短至长排序
sort(words.begin(), words.end(), isShorter);
//按单词长度由长至短排序
sort(words.begin(), words.end(), bind(isShorter, _2, _1));
  • 默认情况下,bind的那些不是占位符的参数被拷贝到bind返回的可调用对象中,对有些绑定的参数希望以引用方式传递,或是要绑定参数的类型无法拷贝。
// os是一个局部变量,引用一个输出流
// c是一个局部变量,类型为char
for_each(words.begin(), words.end(), [&os, c](const string &s)
         { os << s << c; });
  • 编写一个函数,完成相同的工作
ostream &print(ostream &os, cost string &s, char c)
{
    reutrn os << s << c;
}
  • 不能直接用bind来代替对os的捕获。
//错误:不能拷贝OS
for_each(words.begin(), words.end(), bind(print, os, _l, ' '));
  • 使用标准库ref函数解决。
for_each(words.begin(), words.end(), bind(print, ref(os), _l, ' '));

10.4 再探迭代器

  • 除了为每个容器定义的迭代器之外,标准库在头文件iterator中还定义了额外几种迭代器。
  • 以下几种:
    • 插入迭代器:这些迭代器被绑定到一个容器上,可用来向容器插入元素。
    • 流迭代器:这些迭代器被绑定到输入或输出流上,可用来遍历所有关联的IO流。
    • 反向迭代器:这些迭代器向后而不是向前移动,除了forward_list之外的标准库容器都有反向迭代器。
    • 移动迭代器:这些专用的迭代器不是拷贝其中的元素,而是移动它们。

10.4.1 插入迭代器

  • back_inserter创建一个使用push_back的迭代器。
  • front_inserter创建一个使用push_front的迭代器。
  • inserter创建一个使用insert的迭代器。此函数接受第二个参数,这个参数必须是一个指向给定容器的迭代器。元素将被插入到给定迭代器所表示的元素之前。

10.4.2 iostream迭代器

  • istream_iterator读取输入流。
istream_iterator操作操作含义
istream_iterator in(is);in从输入流is读取类型为T的值
istream_iterator end;读取类型为T的值的istream_iterator迭代器,表示尾后位置
in1 == in2in1和in2必须读取相同类型。如果它们都是尾后迭代器,或绑定到相同的输入,则两者相等
in1 != in2同上不相等
*in返回从流中读取的值
in->mem与(*in).mem的含义相同
++in, in++读取下一个值,前置版本返回后值,后置版本返回旧值
  • ostream_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
istream_iterator<int> int_it(cin); //从cin读取int
istream_itreator<int> int_eof;     //尾后迭代器
ifstream in("afile");
istream_iterator<string> str_it(in); //从“afile”读取字符串
istream_iterator<int> int_iter(cin); //从cin读取int
istream_iterator<int> eof;           //尾后迭代器
while (in_iter != eof)               //当有数据可供读取时
{
    //后置递增运算读取流,返回迭代器的旧值
    //解引用迭代器,获得从流读取的前一个值
    vec.push_back(*in_iter++);
}
  • 程序重写。从流中读取的数据被用来构造vec。
istream_iterator<int> in_iter(cin), eof; //从cin读取int
vector<int> vec(in_iter, eof); //从迭代器范围构造vec
  • 用ostream_iterator来输出值的序列。
  • 因为*out++不对out做任何事情,每个运算符都返回out,所以可以省略。但是推荐第一种形式,循环的行为更为清晰。
ostream_iterator<int> out_iter(cout, " ");
for (auto e : vec)
{
    *out_iter++ = e; //赋值语句实际上将元素写道cout
}
cout << endl;
  • 使用流迭代器处理类类型
istream_iterator<Sales_item> item_iter(cin), eof;
ostream_iterator<Sales_item> out_iter(cout, "\n");
//将第一笔交易记录存在sum中,并读取吓一跳记录
Sales_item sum = *item_iter++;
while (item_iter != eof)
{
    //如果当前交易记录(存在item_iter中)有着相同的ISBN号
    if (item_iter->isbn() == sum.isbn())
    {
        sum += *item_iter++; //将其加到sum上并读取下一条记录
    }
    else
    {
        out_iter = sum;     //输出sum当前值
        sum = *item_iter++; //读取下一条记录
    }
}
out_iter = sum; //记得打印最后一组记录的和

10.4.3 反向迭代器

  • 反向迭代器就是在容器中从尾元素向首元素反向移动的迭代器。
  • 可以通过调用rbegin、rend、crbegin和crend成员函数来获得反向迭代器。
    在这里插入图片描述
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) //将r_iter绑定到尾元素,crend指向首元素之前的位置,实际是递减,移动到前一个元素
{
    cout << *r_iter << endl; //打印9,8,7,...0
}
sort(vec.begin(), vec.end());   //按正序排序
sort(vec.rbegin(), vec.rend()); //按逆序排序
  • 反向迭代器有递减运算符,但流迭代器不支持。
  • 如果要打印line中的第一个单词。
//在一个逗号分隔的列表中查找笫一个元素
auto comma = find(line.cbegin(), line.cend(), ',');
cout << string(line.cbegin(), comma) << endl;
  • 如果要打印line中最后第一个单词,反转反向迭代器。
    在这里插入图片描述
//在一个逗号分隔的列表中查找最后一个元素
auto rcomma = find(line.crbegin(), line.crend(), ',');
//错误:将逆序捡出单词的宇符
cout << string(line.crbegin(), rcomma) << endl;
//正确:得到一个正向迭代器,从逗号开始读取字符直到line末尾
cout << string(rcomma.base(), line.cend()) << endl; 

10.5 泛型算法结构

  • 任何算法的最基本的特性是它要求其迭代器提供哪些操作。
  • 算法所要求的迭代器操作可以分为5个迭代器类别
迭代器类别操作含义
输入迭代器只读,不写,单遍扫描,只能递增
输出迭代器只写,不读,单遍扫描,只能递增
前向迭代器可读写,多遍扫描,只能递增
双向迭代器可读写,多遍扫描,可递增递减
随机访问迭代器可读写,多遍扫描,支待全部迭代器运算

10.5.1 5类迭代器

  • 迭代器也定义了一组公共操作。一些操作所有迭代器都支持,另外一些只有特定类别的迭代器才支持。
  • 对于向一个算法传递错误类别的迭代器的问题,很多编译器不会给出任何警告或提示。
  • 输入迭代器:可以读取序列中的元素。
    • 用于比较两个迭代器的相等和不相等运算符(=、!=)。
    • 用于推进迭代器的前置和后置递增运算(++)。
    • 用于读取元素的解引用运算符(*);解引用只会出现在赋值运算符的右侧。
    • 箭头运算符(->),等价于(*t).member,即,解引用迭代器,并提取对象的成员。
    • 算法find和accumulate要求输入迭代器,而istream_iterator是一种输入迭代器。
  • 输出迭代器:可以看作输入迭代器功能上的补集,只写而不读元素。
    • 用于推进迭代器的前置和后置递增运算(++)。
    • 解引用运算符(*),只出现在赋值运算符的左侧。
    • copy函数的第三个参数就是输出迭代器。ostream_iterator类型也是输出迭代器。
  • 前向迭代器:可以读写元素。
    • 这类迭代器只能在序列中沿一个方向移动。
    • 前向迭代器支持所有输入和输出迭代器的操作,而且可以多次读写同一个元素。
    • 算法replace要求前向迭代器,forward_list上的迭代器是前向迭代器。
  • 双向迭代器:可以正向/反向读写序列中的元素。
    • 除了支持所有前向迭代器的操作之外双向迭代器还支持前置和后置递减运算符(–)。
    • 算法reverse要求双向迭代器,除了forward_list之外,其他标准库都提供符合双向迭代器要求的迭代器。
  • 随机访问迭代器:提供在常量时间内访问序列中任意元素的能力。
    • 此类迭代器支持双向迭代器的所有功能
    • 用于比较两个迭代器相对位置的关系运算符(<、<=、>和>=)。
    • 迭代器和一个整数值的加减运算(+、+=、-和-=),计算结果是迭代器在序列中前进(或后退)给定整数个元素后的位置。
    • 用于两个迭代器上的减法运算符(-),得到两个迭代器的距离。
    • 下标运算符(iter[n]), 与*(iter[n])等价。
    • 算法sort要求随机访问迭代器。array、deque、string和vector的迭代器都是随机访问迭代器,用于访问内置数组元素的指针也是,原理如此

10.5.2 算法形参模式

  • 在任何其他算法分类之上,还有一组参数规范。
  • 大多数算法具有如下4种形式之一:
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表示第二个范围。
  • 除了这些迭代器参数,一些算法还接受额外的、非迭代器的特定参数。
  • 向输出迭代器写入数据的算法都假定目标空间足够容纳写入的数据。

10.5.3 算法命名规范

  • 除了参数规范,算法还遵循一套命名和重载规范。
  • 一些算法使用重载形式传递一个谓词。
unique(beg, end);       //使用==运算符比较元素
unique(beg, end, comp); //使用comp比较元素
  • _if版本的算法
find(beg, end, val);     //查找输入范围中val笫一次出现的位置
find_if(beg, end, pred); //查找第一个令pred为真的元素
  • 区分拷贝元素的版本和不拷贝的版本
reverse(beg, end);            //反转输入范围中元素的顺序
reverse_copy(beg, end, dest); //将元素按逆序拷贝到dest

//从v1中删除奇数元素
remove_if(vl.begin(), vl.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定义了几个成员函数形式的算法。
list和forward_list成员函数版本的算法算法含义
lst.merge(lst2)将来自lst2的元素合并入lst,lst1和lst2都必须是有序的,元素将从lst2中删除,在合并之后,lst2变为空。使用<运算符操作
lst.merge(lst2,comp)同上,使用给定比较操作
lst.remove(val)调用erase删除掉与给定值相等(==)
lst.remove_if(pred)调用erase删除掉令一元谓词为真的每个元素
lst.reverse()反转lst中元素的顺序
lst.sort()使用<操作排序元素
lst.sort(comp)使用给定比较操作排序元素
lst.unique()调用erase删除同一个值的连续拷贝,使用==
lst.unique(pred)调用erase删除同一个值的连续拷贝,使用给定的二元谓词
  • 链表类型还定义了splice算法,此算法是链表数据结构所特有的,因此不需要通用版本。
list和forward_list的splice成员函数的参数算法含义
lst.splice(args)或flst.splice_after(args)
(p, lst2)p是一个指向lst中元素的迭代器,或一个指向flst首前位置的迭代器。函数将lst2的所有元素移动到lst中p之前的位置或是flst中p之后的位置。将元素从lst2中删除。lst2的类型必须与1st或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的链表版本会删除指定的元素。
    • unique的链表版本会删除第二个和后继的重复元素。
    • merge和splice会销毁其参数。
    • 链表版本的merge函数会销毁给定的链表。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Flame老唐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值