1. 程式人生 > >C++ Primer 第十章 泛型算法 筆記

C++ Primer 第十章 泛型算法 筆記

size_t string 引用捕獲 list 字典序排序 變量 字符串 space ifstream

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 第十章 泛型算法 筆記