c++ primer 第五版学习笔记-第十章 泛型算法

本文为转载,出处:https://blog.csdn.net/libin1105/article/details/48706281

                                https://blog.csdn.net/xiaoshuaisdl/article/details/79740517

10.1 概述

 

1.大多数算法都定义在头文件algorithm中。标准库还在文件numeric中定义了一组数值泛型算法。

2.泛型算法本身不会执行容器操作,它们只会运行在迭代器之上,执行迭代器的操作.

3.结论:算法永远不会改变底层容器的大小,算法可能改变容器中保存的元素的值或者移动元素,但永远不会直接添加或者删除元素.

4.除了少数以外,标准库算法都对一个范围内的元素进行操作,我们将此元素范围称为“输入范围”。接受输入范围的算法总能使用前两个参数来指定范围,两个参数分别为首元素和尾元素的下一个迭代器

10.2 初识泛型算法

accumulate:定义在头文件numeric中。作用是对范围求和。

euqal:定义在头文件algorithm中。作用是判断给定两个区间是否相等。

fill:定义在头文件algorithm中。作用是对给定区间全部赋予某值。

fill_n:定义在头文件algorithm中。作用是对给定迭代器后的n个元素赋予某值。

back_inserter:定义在头文件iterator中。得到指向容器尾部的迭代器。

sort:定义在头文件algorithm中。对指定区间排序。

unique:定义在头文件algorithm中。“消除”重复项,返回指向不重复值范围末尾的迭代器。

erase:定义在头文件algorithm中。删除指定范围内的元素。

1.只读算法

    ①accucmulate,接受三个参数,前两个是范围,最后一个是和的初值。

    //accumulate(egin, end, initial_value);  
    vectorivec = {1,2,3,4,5,6,7,8,9,0};  
    cout << accumulate(ivec.begin(), ivec.end(), 0) << endl;  
    //字符串必须类型相同,如果accumulate中s换为""就会出错,""是const char*类型的,类型不一致  
    //所以序列中的元素必须和第三个参数相匹配,或者能转换成第三个参数。  
    string s = "";  
    vectorivecs = {"hello", "world", "haha"};  
    cout << accumulate(ivecs.begin(), ivecs.end(), s) << endl; 

      <<1.对于只读不改变的算法我们最好使用a.cbegin( )和a.cend( ),const_iterator类型的迭代器.

      <<2.accumulate的第三个参数决定了函数中使用哪个加法运算符以及返回值类型.

    ②equal 判断两个序列是否保存相同的值,所有元素相同返回true.否则返回false。

       <<1算法实现假设第二个序列至少和第一个序列一样长

       <<2那些只接受单一迭代器来表示第二个序列的算法都假设第二个序列至少和第一个一样长

       <<3元素类型不必完全相同,只要能用==比较就可以。

2.写算法

   ①fill

       fill接受一对迭代器表示一个范围,还接受一个值作为第三个参数,fill将给定的这个值赋予输出序列中的每个元素

       fill_n接受一个单迭代器,一个计数值,和一个值,它将给定值赋予迭代器指向的元素开始的指定个数。

       向目的位置迭代器写入数据的算法假定目的位置足够大,能容纳要写入的元素。

    vectorivec;  
    cout << "size:" << ivec.size() << endl;  
    auto it = back_inserter(ivec);  
    *it = 100;  
    cout << "size:" << ivec.size() << endl;  
    for(const int&i : ivec)  
        cout << i << " ";  
    cout << endl;  
    fill_n(back_inserter(ivec), 10, 9);  
    cout << "size:" << ivec.size() << endl;  
    for(const int&i : ivec)  
        cout << i << " ";  
    cout << endl; 

 

3.拷贝 copy replace

拷贝算法是另一个向目的位置迭代器指向的输出序列中的元素写入数据的算法。

参数是三个迭代器,前两个表示一个输入范围,第三个表示目的序列的起始位置

注意:传递给copy的目的序列至少要包含与输出序列一样多的元素。如要改变容器的大小,应该第三个参数用back_insert

    copy(ivec.begin(), ivec.end(), ivec2.begin());    //copy  
    for(const int&i : ivec2)                          //copy后ivec2全是1  
        cout << i << " ";  
    cout << endl;  
    replace(ivec.begin(), ivec.end(), 1, 100);        //replace 将第三个参数的值用第四个参数代 
                                                         替,用100代替1 
    for(const int&i : ivec)  
        cout << i << " ";  
    cout << endl;  
    listil(10, 0);  
    //将ivec先copy一份到il的末尾,然后拷贝过去的数中100全部替换为42。  
    replace_copy(ivec.begin(), ivec.end(), back_inserter(il), 100, 42);   //copy and replace  

注意:算法不会改变容器的大小,有算法移动数据等类似操作是确保对方容器有足够的大小。

          c.reserve改变的是容器的容量

          c.resize改变的是容器的大小

          容器的容量不为0但是容器的size为0,意味着容器的此时没有存储空间,不能用类似copy函数等等来像容器里面赋值

4.重排容器元素的算法

          sort(c.begin( ), c.end( ));    :排序

          uniq_iter = unique(c.begin( ), c.end( ));   :去重,重复的元素,并不改变容器的大小,重复的元素移动到了“不重复元素”的末尾。返回不重复元素的下一个位置的迭代器,或者说是重复元素中的第一个元素。

10.3 定制操作

 

1.谓词是一个可调用的表达式,其返回结果是一个能用作条件的值。谓词分为:一元谓词和二元谓词。几元谓词接受几元参数。

接受谓词参数的算法对输出序列中的元素调用谓词,因此元素类型必须能转换成谓词的参数类型

stable_sort( c.begin( ), c.end( ), 谓词函数); //按长度排序,长度相同,看字典顺序。

2.可调用对象定义;对于一个对象或一个表达式,如果可以对其使用调用运算符,则称为可调用的;可调用的对象有:函数、函数指针、重载函数调用运算符的类和lambda表达式。

和任何函数类似,一个lambda具有一个返回类型,一个参数列表和一个函数体,但和函数不同,lambda可能定义在函数的内部。

形式:[捕获列表](参数列表) -> 返回类型 { 函数体 }

捕获列表:是一个lambda所在函数中定义的局部变量的列表(通常为空)。返回类型使用尾置返回类型,其他和普通函数一样。

可以忽略参数列表和返回类型(等价指定一个空的),但是必须永远包含捕获列表和函数体。

auto f = [ ] { return 42; } 

<1.向lambda传递参数

[](const string&s1, const string&s2) { return s1.size( ) >= s2.size( ); }  

lambda不能含有默认参数,空捕获列表表示不用所在函数里面的任何局部变量

<2.使用捕获列表

一个lambda可以出现在一个函数中,使用其局部变量,但他只能使用那些明确指名的变量。

一个lambda通过将局部变量包含在其捕获列表中指出将来会使用这些变量。

捕获列表指引lambda在其内部包含访问局部变量所需的信息

[sz] (const string&s) { s.size() >= sz; }; 

lambda以一对[ ]开始,我们可以在其中提供一个","分割的名字列表,这些名字都是来自它所在的函数内。

auto wc = find_if(ivec.begin(), ivec.end ,
                              [sz] (const string&s)   
                              { return s.size() >= sz; });
// find_if前两个参数是迭代器,第三个是一个谓词,返回满足条件条件的第一个迭代器。 

捕获列表只用于局部非static变量,lambda可以直接使用局部static变量和在它所在函数之外声明的名字。

3.lambda捕获和返回

捕获的两种方式:

       <1值捕获:采用值捕获的前提是变量可以拷贝,和参数不同,被捕获的变量的值是在lambda创建时拷贝,而不是调用时拷贝

string::size_type sz =42;  
auto f = [sz] { return sz; };  
sz = 0;  
cout << f() << endl;

       注意:由于被捕获的变量的值是在lambda创建时的拷贝,因此随后对其修改不会影响到lambda对应的那个值。

       <2引用捕获:

string::size_type sz =42;  
auto f = [&sz] { return sz; };  
sz = 0;  
cout << f() << endl; 

和我们普通传引用一样,传的参数被绑定了。所以修改后lambda捕获也会被修改。

“ 采用引用方式捕获一个变量,就必须确保被引用的对象在lambda执行的时候是存在的 ”,lambda捕获的都是局部变量,这些变量在函数结束后就不复存在了

如果lambda可能在函数结束后执行,捕获的引用指向的局部变量已经消失。

引用捕获有时是必要的,比如说参数包含流,流是不能被拷贝的。

 

注意:尽量保持lambda的变量捕获简单化

捕获一个变量如int,string或其他的非指针类型,通常可以采用简单的值捕获方式,在此情况下,只需要关注变量在捕获时是否包含我们所需要的值就行了。

如果我们捕获一个指针或引用,就必须保证在lambda执行时,绑定到迭代器,指针或引用的对象存在,有可能在捕获时,绑定的对象是我们所期望的,

但是在执行时,对象的值可能已经改变的。

我们应该尽可能减少捕获的数据量,来避免潜在的捕获导致的问题,而且,如果可能,应该避免捕获指针或引用。

       <3隐式捕获:

       除了显示列出我们希望使用的所在函数的变量外,还可以让编译器根据lambda体中的代码来推断我们要使用哪些变量,

在捕获列表写一个&或=,&告诉编译器采用引用方式捕获,=告诉编译器采用值传递方式捕获。

当我们混用隐式捕获和显示捕获时,捕获列表的第一个元素必须是&或=。

混合捕获:

[=, &os] { os << s << endl; }  
[&,   s] { os << s << endl; }

捕获列表的几种形式

[ ]             空列表

[ names]  逗号分隔的名字列表

[&]           隐式引用捕获

[=]           隐式值捕获

[&, identifier_list]     隐式变量采用引用捕获,值捕获采用列表形式

[=, identifier_list]     隐式变量采用值捕获,引用捕获采用列表形式

<4可变的lambda

默认情况下,对于一个被值拷贝的变量,lambda不会改变其值。

值捕获时,如果想要修改sz就需要在参数列表后面加上mutable关键字。引用捕获可以直接修改.

size_t sz = 43;  
auto f = [sz]()mutable { return ++sz; };//值传递,是创建时所拷贝的。

<5指定lambda的返回类型,必须使用尾置返回类型。

有时默认推到类型可能是错误的。

transform(ivec.begin(), ivec.end(), ivec.begin(),   
   [](int i)->int { return i < 0 ? -i : i; });//默认是void

 

4.标准库bind函数

头文件:functional ;它接受一个可调用对象,生成一个新的可调用对象来适应原对象的参数列表。
调用bind的形式:
auto newCallable=bind(callable, arg_list);
newCallable本身是一个可调用对象,arg_list是一个逗号分隔的参数列表,对应给定的callable的参数。arg_list中的参数可能包含形如_n的名字,_1是newCallable的第一个参数,_2为第二个参数,依次类推。

    listil = {1, 2, 3, 4};  
    listils;  
    copy(il.begin(), il.end(), back_inserter(ils));  
    for(const int i : ils)  
        cout << i << " ";  
    cout << endl;  
    listils2;  
    copy(il.begin(), il.end(), front_inserter(ils2));  
    for(const int i : ils2)  
        cout << i << " ";  
    cout << endl;  
    listils3;  
    copy(il.begin(), il.end(), inserter(ils3, ils3.begin()));    //写ils3.begin()和ils3.end()结果不变  
    for(const int i : ils3)  
        cout << i << " ";  
    cout << endl; 
    listil = {1, 2, 3, 4};  
    listils;  
    copy(il.begin(), il.end(), back_inserter(ils));  
    for(const int i : ils)  
        cout << i << " ";  
    cout << endl;  
    listils2;  
    copy(il.begin(), il.end(), front_inserter(ils2));  
    for(const int i : ils2)  
        cout << i << " ";  
    cout << endl;  
    listils3;  
    copy(il.begin(), il.end(), inserter(ils3, ils3.begin()));    //写ils3.begin()和ils3.end()结果不变  
    for(const int i : ils3)  
        cout << i << " ";  
    cout << endl; 

数值n表示生成的可调用对象中参数的位置

int add1(int a, int b, int c, int d, int f)  
{  
    return a+b+c+d+f;  
}  
  
int main()  
{  
    int a,b,d;  
    cin >> a >> b >> d;  
    //bind将add1绑定到f上面,除了_1和_2需要我们传参。  
    auto f = bind(add1, a, b, std::placeholders::_1, d, std::placeholders::_2);   
    //调用f(),参数是(1,1)传递给_1和_2。  //bind和placeholders命名空间都在functional里面
    //等价于调用add1(a,b,_1,d,_2);  
    cout << f(1, 1) << endl;  
 }  
//参数_1和_2可以颠倒,那么传参时也需要颠倒。

5.bind拷贝参数而不能拷贝ostream。我们可以使用ref函数。

函数ref返回一个对象,包含给定引用,此对象是可以拷贝的。标准库中海油一个cref函数,生成一个保存const引用的类。与bind一样,函数ref和cref也定义在头文件functional 中。

bool length(const string&s, string::size_type sz)  
{  
    return s.size() <= sz;  
}  
int count(vector&ivec, string::size_type sz)  
{  
    auto wc = count_if(ivec.begin(), ivec.end(), bind(length, std::placeholders::_1, sz));  
}  

10.4 再探迭代器

 

1.

插入迭代器:这些迭代器被绑定到一个容器上,可用来向容器插入元素。

流迭代器:这些迭代器被绑定到输入或输出流上,可用来遍历所有关联的IO流。

反向迭代器:这些迭代器向后而不是向前移动。除了forward_list之外的标准库容器都有反向迭代器。

移动迭代器:这些专用的迭代器不是拷贝其中的元素,而是移动它们。

 

2.

back_inserter            创建一个使用push_back的迭代器

front_inserter            创建一个使用push_front的迭代器

inserter                      创建一个使用insert的迭代器。此函数接受第二个参数,这个参数必须是一个指向给定容器的

                                  迭代器。元素将插入到给定迭代器所表示的元素之前。

 

3.

istream_iterator操作

istream_iterator in(is)           in从输入流is读取类型为T的值

istream_iterator end;           读取类型为T的值的istream_iterator迭代器,表示尾后位置

in1 == in2                                   in1和in2必须读取相同类型。如果它们都是尾后迭代器,或绑定到相同

in1 != in2                                    的输入,则两者相等

*in                                               返回从流中读取的值

in->mem                                     与(*in).mem的含义相同

++in,in++                                   使用元素类型所定义的>>运算符从输入流中读取下一值。与以往一样,前置版本返回一个指向递增后迭代器的引用,后置版本返回旧值

 

4.

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

 

10.5 泛型算法结构

 

1.迭代器类别

输入迭代器                只读,不写;单遍扫描,只能递增

输出迭代器                只写,不读;单遍扫描,只能递增

前向迭代器                可读写,多遍扫描,只能递增

双向迭代器                可读写,多遍扫描,可递增递减

随机访问迭代器         可读写,多遍扫描,支持全部迭代器运算

 

10.6 特定容器算法

 

1.list和forward_list成员函数版本的算法

lst.merga(lst2)            

lst.megra(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删除统一个值的连续拷贝。第一个版本使用==;第二个版本使用给定的二元谓词

 

2.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不能指向给定范围中元素。

你可能感兴趣的:(c++)