首页 > 编程语言 > 详细

C++ Primer 第十章 泛型算法 笔记

时间:2019-02-26 20:21:50      阅读:184      评论:0      收藏:0      [点我收藏+]

C++ Primer 第十章 泛型算法 练习题

10.1 概述

迭代器令算法不依赖于容器,但算法依赖于元素类型的操作。

10.1

vector<int>vi;
int a;
while(cin>>a)
    vi.push_back(a);
auto result=count(vi.cbegin(),vi.cend(),1);
cout<<result;


10.2
改写类型即可。

10.2 初识泛型算法

除少数例外,标准库算法都对一个范围内(第一个元素和尾后元素的迭代器)的元素进行操作。大多数算法遍历输入范围方式相似,但它们使用范围中元素的方式不同。要了解它们是否读取元素、改变元素或是重排元素顺序。

10.2.1 只读算法

一些算法只会读取其输入范围内的元素,而不改变元素。

  • find,count都是
  • accumulate (定义在头文件numeric中),第三个参数是和的初值。

      string sum=accumulate(v.cbegin(),v.cend(),string(""))  // 将string元素连接
  • equal 第三个元素接受第二个序列的首元素。这种只接受单一迭代器表示第二个序列的算法,都假定第二个序列至少和第一个序列一样长。

    10.3

      vector<int>vi{1,2,3};
      auto result=accumulate(vi.cbegin(),vi.cend(),0);


    10.4
    第三个参数决定函数中使用哪个加法运算符和返回值类型。将初值设定为0,表明返回值为int类型,使用之后,会将double转换为int,损失精度

    10.5
    经验证,一切正常,但是C风格字符串的比较最好还是利用strcmp().

    10.2.2 写容器元素的算法

  • 一些算法将新值赋予序列中的元素,使用时必须确保序列大小至少不小于我们要求算法写入的元素数目。

      vector<int>vec;
      fill_n(back_inserter(vec),10,0); //添加10个元素到vec
      replace_copy(ilst.cbegin(),ilst.cend(),back_inserter(ivec),0,42);
      //ilst没变,ivec包含ivec的拷贝,且其中0被替换成了42

    10.6

      vector<int>vi{1,2,3};
      fill_n(vi.begin(),3,0);

    10.7

    a. lst和vec之间的大小未保证相同.

    b. reserve只预留了空间,该容器内还是没有元素。

    10.8 这只是产生了一个插入迭代器,然后使用这个迭代器进行插入操作。并非算法本身改变大小。

    10.2.3 重排容器元素的算法

    消除重复单词

      sort(words.begin(),words.end());//按字典序排序,默认升序
      auto end_unique=unique(words.begin(),words.end());//返回指向不重复元素末尾的迭代器
      words.erase(end_unique,words.end());

    10.9

    #include
    #include
    #include
    #include
    #include
    using namespace std;
    void elimDups(vector &s)
    {
    cout<<"排序前:";
    for (const auto &a:s)
    {
    cout<<a<<" ";
    }
    cout<<endl<<"sort()排序后:";
    sort(s.begin(),s.end());//sort排序
    for (const auto &a:s)
    {
    cout<<a<<" ";
    }
    cout<<endl<<"unique()排序后:";
    auto str = unique(s.begin(),s.end());//unique排序
    for (const auto &a:s)
    {
    cout<<a<<" ";
    }
    cout<<endl<<"erase()操作后:";
    s.erase(str,s.end());//erase()操作
    for (const auto &a:s)
    {
    cout<<a<<" ";
    }

    }
    int main(int argc, char**argv)
    {
    string a[9] = {"I","Like","Like","C++","very","very","much","SCUEC","NewThread"};
    vector s(a,a+9);
    elimDups(s);
    return 0;

    }

10.10 因为算法始终作用于迭代器,不会直接对容器操作。

10.3 定制操作

10.3.1 向算法传递函数

谓词:是一个可调用的表达式,其返回结果是一个能用做条件的值。元素类型必须能转换为谓词的参数类型。

    bool isShorter(const string &s1,const string &s2)
    {
     return s1.size()<s2.size();
    }
    sort(words.begin(),words.end(),isShorter);//按长度排序

stable_sort:可保持等长元素间的原有顺序

10.11 替换10.9例题参数即可。

10.12 编写谓词即可。

bool compareIsbn(Sales_data s1, Sales_data s2)
{
    return s1.isbn().size() < s2.isbn().size();
}

10.13

bool comp(string &s1)
{return (s1.size() >= 5)}

bool func(string &s1)
{
auto it1=vec.begin();
auto it2=partition(vec.begin(),vec.end(),comp);
if(it1==it2) //若没有符合要求的,则此时返回的迭代器指向首部
return false;
else 
{
    for(;it1!=it2;++it1)
        cout<<*it1<<" ";
    return true;
}
}

10.3.2 lambda表达式

介绍lambda

  • 一个lambda表达式表示一个可调用的代码单元,可理解成一个未命名的内联函数。与函数不同的是,lambda可能定义在函数内部。
  • [捕获列表](参数列表)->返回类型{函数体}

    捕获列表是一个lambda所在函数中定义的局部变量的列表,通常为空
  • lambda必须使用尾置返回
  • 可忽略参数列表和返回类型,但永远包含捕获列表和函数体

      auto f = [] {return 42;}; //定义可调用对象f,它不接受参数,返回42
      cout << f() << endl; //打印42

    向lambda传递参数

  • lambda不能有默认参数,因此,一个lambda调用的实参数目永远和实参数目相等。

      stable_sort(words.begin(),words.end(),
                 [](const string &a,const string &b)
                      {return a.size()<b.size();});

    使用捕获列表

    /查找第一个长度大于sz的元素/
    auto wc=find_if(words.begin(),words.end(),
    sz
    {return a.size()>sz;});//返回迭代器

    for_each算法

  • lambda可直接调用当前函数之外的名字
  • for_each算法接受一个可调用对象,并对输入序列中每个元素调用此对象。

      for_each(wc,words.end(),
                    [](const string &s) {cout<<s<<" ";});

10.14

    [](int &a,int &b){cout<<a+b;};

10.15

    [value](int a){return (value+a);};

10.16 参考示例biggies。

10.17

    stable_sort(vec1.begin(),vec1.end(),
                [](Sales_data s1, Sales_data s2)
                {return s1.isbn().size() < s2.isbn().size();});


10.18

    void biggies(vector<string> &s, vector<string>::size_type sz)
{
elimDups(s);//字典排序、删除重复
stable_sort(s.begin(),s.end(),
        [](const string &a,const string &b)
                {return a.size()<b.size();});//按长度排序
auto it1 = partition(s.begin(),s.end(),
                [sz](const string &s){return s.size()<=sz;});
for (it1; it1 != s.end(); ++it1)
{
    cout<<*it1<<" ";
}
}


10.19 换成stable_partition 即可

10.3.3 lambda捕获和返回

  • 被捕获变量值是在lambda创建时拷贝,因此其后对其修改不会影响到lambda内对应的值。

    引用捕获

    当用引用捕获时修改值就能影响到lambda内对应值啦。但必须确保被引用对象在lambda执行时存在。

      void fun(ostream &os=cout,char c=' ')
      {
      for_each(xxx,xxx,
              [&os,c](const string &s)
                      {os<<s<<C;});
      }

    隐式捕获

  • 在捕获列表中写一个&或=。
  • 当混合使用显隐时,第一个必须是&或=,且方式不同(比如隐式用了引用,那么显示就得用传值)

    可变lambda

    如果希望改变一个被捕获的变量值,就必须在参数列表首加上关键字mutable,因此,可变lambda能省略参数列表。

      auto f = [v1]() mutable {return ++v1};

    指定lambda返回类型

    当需要为一个lambda定义返回类型时,要用尾置返回类型!

      transform(v.begin(),v.end(),v.begin(),
                          [](int i)->int
                                  {if(i<0) return -i;else return i;};

    10.20

      count_if(vi.begin(),vi.end(),
              [](const string &a) {a.size()>6;})


    10.21

    int v=42;
    auto f = &v->bool{while (v>0) --v; return !v; } ;

    标准库bind函数

  • 那种一两个地方需要用的简单操作,lambda表达式最好用,否则还是定义一个函数
  • bind定义在functional中,可将其看作一个函数适配器。

    auto New可调用对象 =bind(调用对象,参数列表)
    其中参数列表中可用占位符_n,表示占用第n个参数。定义在placeholders命名空间

      bool check_size(const string &s,string::size_type sz)
      {return s.size()>=sz;}
      auto wc=find_if(words.begin(),words.end(),
                                      bind(check_size,_1,sz);  
      bool b1=wc("hello"); //用定值6和占位的const string&调用check_size函数
  • 可用bind重新安排对象中参数位置

      auto g=bind(f,a,b,_2,c,_1);
      g(x,y);//即f(a,b,y,c,x)
      sort(xx,xx,bind(isShorter,_2,_1));//变成由长到短排序
  • 若希望传递给bind一个对象而又不拷贝他,可用ref函数

      ostream &print(ostream &os,const string &s,char c)
      {return os<<s<<c};
      for_each(xx,xx,bind(print,ref(os),_1,' '));//传递ostream

    10.22

      bool func(string &s,string::size()_type sz)
      {
      return s.size()<=sz;
      }
      count_if(vec1.begin(),vec1.end(),bind(func,_1,6));

    10.23 可调用对象形参数+1(自己)

    10.24

      bool check_size(const string &s,string::size_type sz)
      {return s.size()>sz;}
      string::size_type length=xxstring.size();
      auto wc=find_if(vi.begin(),vi.end(),
                                      bind(check_size,_1,length);  


    10.25

      auto it1 = partition(s.begin(),s.end(),
              bind(check_size,_1,sz));  

    10.4 再探迭代器

    插入迭代器

    插入器是一种适配器,接受一个容器,生成一个迭代器,能实现向给定容器添加元素。

      list<int> lst={1,2,3,4};
      list<int> lst2,lst3;
      copy(lst.cbegin(),lst.cend(),front_inserter(lst2);//4321 
      copy(lst.cbegin(),lst.cend(),inserter(lst3,lst3.begin());//1234

    10.26

    back_inserter:使用push_back;

    front_inserter:使用push_front,每次总把元素插在首位置

    inserter:接受第二个参数,参数是指向容器插入位置的迭代器。插在给定元素前面


10.27

sort(vec1.begin(),vec1.end());
unique_copy(vec1.cbegin(),vec1.cend(),back_inserter(vec2));


10.28 见上述示例代码。注意vector不支持push_front操作。

iostream迭代器

istream_iterator<int> in_iter(cin),eof;
vector<int> vec(in_iter,eof);//迭代器范围构造
vec.push_back(*in_iter++);//后置递增构造


istream_iterator<int> in(cin),eof;
cout<< accumulate(in,eof,0) <<endl;//使用算法操作六迭代器


ostream_iterator<int> out_iter(cout," ");
for(auto e:vec)
    *out_iter++=e;//循环输出
copy(vec.begin(),vec.end(),out_iter);//更简单的循环输出

10.29

ifstream in1("1.txt");
istream_iterator<string> str(in1),end;
copy(str,end,back_inserter(vec1));

10.30

istream_iterator<int> str(in1),end;
ostream_iterator<int> out_iter(cout," ");
copy(str,end,back_inserter(vec1));
sort(vec1.begin(),vec1.end());
copy(vec1.begin(),vec1.end(),out_iter);

10.31

unique_copy(str,end,back_inserter(vec1));//记得先sort

10.32 我用的find_if。

istream_iterator<Sales_item> item_iter(cin),eof;
vector<Sales_item> vs(item_iter,eof);
sort( vs.begin(), vs.end(), compareIsbn );
auto currt=vs.begin();
auto temp=currt;
while(currt!=vs.end())
{
temp=find_if(vs.begin(),vs.end(),
            [currt.isbn()](Sales_ietm b) {return (currt.isbn()!=b.isbn());});
cout<< acumulate(currt,temp,Sales_item())<< endl;
currt=temp;
}

10.33

ifstream in("1.txt");
istream_iterator<int> it1(in),end;
vector<int> vec1;
copy(it1,end,back_inserter(vec1));

ofstream out("2.txt");
ofstream out2("3.txt");
ostream_iterator<int> it2(out," ");
ostream_iterator<int> it3(out2,"\n");
for (auto it=vec1.begin();it!=vec1.end();++it)
{
    if ((*it)%2 == 0)//偶数
        it2=it;
    else
        it3 = it;
}

10.4.3 反向迭代器

  • 反向迭代器就是在容器中从尾元素向首元素反向移动的迭代器。++会移动到前面,--会移动到下一个元素。
  • 除了forward_list之外,都支持反向迭代器。可用rbegin,rend等使用。
  • 可用a.base()将其转换为普通迭代器,输出需求内容。(否则输出的是反的)
  • 普通迭代器和反向迭代器的关系反映了左闭合区间。

    10.34

      for (auto it1 = v1.rbegin(); it1 != v1.rend() ; ++it1)
      cout<<*it1<<" ";


    10.35

      for (auto it1 = v1.end(); it1 != v1.begin() ;--it1)
      cout<<*(it1-1)<<" ";


    10.36

      find(list1.rbegin(),list1.rend(),0);


    10.37

      copy(vec1.rbegin()+3,vec1.rend()-2,back_inserter(list1));

    10.5 泛型算法结构

    10.5.1 5类迭代器

    10.38

输入迭代器:可以读取序列中的元素,只用于顺序访问,*it++必须有效

输出迭代器,看作是输入迭代器的补集,可写,只赋值一次

前向迭代器:单向,支持输入输出,只沿一个方向移动

双向迭代器:双向,支持读写,还支持递增递减运算符。

随机访问迭代器:基本支持所有功能。


10.39

list:双向迭代器。

vector:随机访问迭代器


10.40

copy的三个参数,第一二个数输入迭代器,第三个是输出迭代器

reverse:双向迭代器

unique是前向迭代器

10.5.2 算法形参模式

10.41

(a):遍历beg到end,找到oldVal就用newVal替换

(b):遍历beg到end,找到满足pred条件的就用newVal替换

(c):遍历beg到end,找到oldVal就用newVal替换,并将其拷贝至dest

(d):遍历beg到end,找到满足pred条件的就用newVal替换,并将其拷贝至dest

10.6 特定容器算法

  • 链表类型list\forward_list定义了独有的sort\merge\remove\reverse\unique操作。所以建议,对于这两个容器,优先使用成员函数版本的算法而不是通用算法。
  • 链表类型还定义了splice算法。
  • 链表版本会改变底层容器。

10.42

list1.sort();
list1.unique();

2/26/2019 7:41:55 PM

C++ Primer 第十章 泛型算法 笔记

原文:https://www.cnblogs.com/Just-LYK/p/10439569.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!