C++primer(第五版)第九章 顺序容器(容器的运用及其部分习题解答,C++11特性总结,重点章节内容较多)

                   顺序容器:为程序员提供了控制元素存储和访问顺序的能力。(无序容器)

          1.顺序容器的概述

          A.顺序容器的类型

          vector:可变大小数组。支持快速随机访问。在尾部之外的位置插入或删除元素可能很慢。

         deque:双端队列。支持快速随机访问。在头尾位置插入/删除速度很快。

          list:双向链表。只支持双向顺序访问。在list中任何位置进行插入/删除操作速度都很快。

         forword_list:单向链表。只支持单向顺序访问。在链表的任何位置进行插入/删除操作速度都很快。(无size())

         array:固定大小数组。支持快速随机访问。不能添加和删除元素。

         string:与vector相似的容器,但专门用于保存字符。随机访问快。在尾部插入/删除速度快。

          根据以上顺序容器的类型介绍,从而习题9.1答案就很简单了:list(字母序是任何位置插入)、deque(头部尾部插入/删除)、vector(可变容器)。

           2.容器库

            A.迭代器

           迭代器中beigin是指容器首元素,end是指容器的最后一个元素的后一个位置;

           B.容器的拷贝

           容器的拷贝有两种操作方法:一是直接拷贝整个容器,二是拷贝一个迭代器对指定的元素范围。前者需要两个容器的类型和元素类型必须匹配;后者不要求容器类型相同元素类型也可不同,但需要元素转换。

            例子:

            list<string> authors ={"Milton","Shakespeare","Austen"};

            vector<const char*> atricles={"a","an","the"};

            list<string> list2(authors);    //正确,类型与元素都匹配;

            forward_list<string> words(articles.begin(), articles.end());// 正确, 元素转换

           C.赋值和swap

           赋值相关运算会导致指向左边容器内部的迭代器、引用和指针失效。而swap操作容器的内容交换不会导致指向容器的迭代器、引用和指针失效并且高效(array、string类型的除外)。

           D.容器大小比较

           容器大小的比较与前面章节讲的字符串string的比较一样,首先是字符大小比较(出现第一个不同字符大的容器大,都相同则容器大小相同),然后是容器长度(元素前部分都相同,即一个容器的元素都在另一个容器的前半部分,元素多容器大)。

           3.顺序容器的操作

           顺序容器与关联容器的不同之处在于两者组织元素的方式。例如:元素的存储、访问、添加和删除。

           A.容器元素添加的操作

            push_back:将元素添加到容器的尾部(array和forward_list除外)

            push_front:将元素添加到容器的头部(list、forward_list 和deque容器支持)

            insert:将元素添加到容器的特定位置(insert(a,b)将b元素插入到a元素指定位置的前面)

            插入单个元素例子:

             vector<string> svec;

              list<string> slist;

              slist.insert(slist.begin(),"hello!"); //在list链表首元素之前插入hello!这个元素;

             svec.insert(svec.begin(),"hello!");//在vector首元素之前插入hello!这个元素;

            插入范围内元素例子:

            svec.insert(svec.end(),10,"Anna");//将10个Anna元素插入到svec的末尾;

           注意:end表示的是svec最有一个元素后一个位置,从而也就是插入到末尾;

            习题9.18、9.19、9.20程序解答:

#include <iostream>

#include<deque>
#include<list>
using namespace std;
int main()
{
    deque<string> string1={"wangjin","wangjin1","wangjing2"};
    for(auto it=string1.begin();it!=string1.end();++it)     //打印deque容器;
        cout<<*it<<endl;
   list<string> string2(string1.begin(),string1.end());
   for(auto it1=string2.begin();it1!=string2.end();++it1)  //打印list容器;
        cout<<*it1<<endl;
        //上面是9.18和9.19习题程序;
   list<int> oe={1,2,3,4,5,6,7,8,9,0};
   deque<int> odd,even;
  for(auto it2=oe.begin();it2!=oe.end();++it2)
  {
      if((*it2)%2==0)
         even.insert(even.end(),*it2);
      else
        odd.insert(odd.end(),*it2);
  }
  cout<<"deque偶数元素为:"<<endl;
  for(auto it3=even.begin();it3!=even.end();++it3)  //打印deque偶数容器;
  {
      cout<<*it3<<",";
  }
  cout<<endl<<"deque奇数元素为:"<<endl;
  for(auto it4=odd.begin();it4!=odd.end();++it4) //打印deque奇数容器;
  {
      cout<<*it4<<",";
  }
  cout<<endl;  //上面为练习9.20习题
    return 0;
}

            显示结果:

          技术分享

           B.emplace操作

            C++11引入的新的三个标准:emplace_front、emplace和emplace_back,这些操作是直接构造,而不是拷贝元素;分别对应push_front、push和push_back;

            例子:

            c.emplace_back("978-56",25,15.99);//直接构造对象,压入容器;

            c.push_back(Sales_data("978-56",25,15.99));//创建临时对象,再压入容器;

            emplace函数在容器中直接构造元素。传递给emplace函数的参数必须与元素类型的构造函数相匹配。

           C.删除元素

            删除元素的操作:

             c.pop_back():删除c中尾元素。c为空,则未定义。

             c.pop_front():删除c中首元素。c为空,则未定义。

             c.erase(p)   :删除迭代器p所指定的元素,返回一个指向被删元素之后的迭代器;若p是尾元素,则返回尾后迭代器。

             c.erase(b,e):删除迭代器b和e所指定范围内的元素。返回一个指向被删元素之后的迭代器;

             c.clear():删除c中的所有元素。返回void;

              注意:迭代器删除元素,都是返回删除元素的后一个位置的迭代器;

           有关特殊的forward_list操作:

            学过链表就能很快理解它删除和添加元素对整个链表的影响,书本中做了很详细的讲解,引用了before_begin()、insert_after();

           D.改变容器的大小

            resize(a):a表示整个容器数据的大小,它可以通过a的大小来增大和缩小容器;

             c.resize(n):调整c的大小为n个元素。若n<c.size,则多出的元素被丢弃。

             c.resize(n,t):调整c的大小为n个元素。任何新添加的元素的初始化值为t。

             例子:

            list<int> ilist(10,42) ; //10个int:每个的值为42;即list初始化有10个42的容器;

            ilist.resize(15);   //本来有10个元素,现在扩大了容器:增加了5个为0的元素;

            ilist.resize(25,-1);   //继续扩大容器:增加了(25-10)10个为-1的元素;

            ilist.resize(5)  ;  //从ilist末尾删除20个元素,最终容器只有:5个为42的元素;

            E.不要保存end返回的迭代器

              原因:当我们删除/添加vector或string的元素后,或在deque中首元素之外的任何位置添加/删除元素后,原来end返回的迭代器总是会失效。因此,添加或删除元素的循环程序必须反复调用end,而不是在循环之前保存end返回的迭代器,一直当做容器末尾使用。

              4.vector对象是如何增长的

             结果:容器通过预留空间作为备用,可用来保存更多的新元素。标准库采用了这种可以减少容器空间重新分配次数的策略。

              A.管理容量的成员函数

                容器大小管理操作:

              c.shrink_to_fit() :将capacity()减少为与size()相同大小;

                  它的适用范围:vector、string和deque;

              c.capacity()      :不重新分配内存空间的话,c可以保存多少元素;

              c.reserve()        :分配至少能容纳n个元素的内存空间;

                  它们两个的适用范围:vector、string;

                注意:reserve并不改变容器中元素的数量,它仅影响vector预先分配多大的内存。

            B.capacity和size

             它们的区别:capacity>=size;capacity-size=预留空间的大小;根据这两个总结的公式就能够更好的理解它们。  

             例子习题9.39和9.38的题目程序: 

#include <iostream>
#include<vector>
#include<string>
using namespace std;


int main()
{
    vector<string> svec;
    svec.reserve(1024);
    cout << "capacity1:" <<svec.capacity()<< endl;  //之前的capacity;
    string word={"0"};
    for(auto it=0;it<256;it++)   //将it=256,512,1000,1024分别运行程序
        svec.push_back(word);
    svec.resize(svec.size()+svec.size()/2);
    cout << "capacity2:" <<svec.capacity()<< endl; //插入数据后的capacity;
    return 0;
}             

            显示结果:

    分别为(插入256,512,1000,1024)对应的capacity:1024,1024,2000,2048;

          5.额外的string操作

           A.构造string的其他方法

            string s(cp,n)  : s是cp指向数组中前n个字符的拷贝。数组长度须不小于n;

            string s(s2,pos2):s是string s2从下标pos2开始的字符的拷贝。

            string s(s2,pos2,len2):s是string s2从下标pos2开始len2个字符的拷贝。若pos2>s2.size(),构造函数的行为未定义。不管len2多大,构造函数最多拷贝s2.size()-pos2个字符。

              例子:

          const char* cp="Hello world!!!" ;

          char nonull[]={"H","i"};

          string s1(cp)  ;//拷贝cp中的字符直到遇到空字符;s1=="Hello world!!!";

          string s2(cp+6,5);// 从cp[6]开始拷贝5个字符;s2="world";

          string s3(s1,6,20);//只拷贝到s1的末尾,s3="world!!!";

          string s4(s1,16);//s1的下标小于16,从而抛出一个out_of_range异常;

           B.substr操作

         s.substr(pos,n):返回一个string,包含s中从pos开始的n个字符的拷贝。pos的默认值为0,n的默认值为s.size()-pos,即拷贝从pos开始的所有字符;

           与上面string的操作相似,就不在举例说明;

           C.append和replace函数

           s.append(args) :  将args追加到s。返回一个指向s的引用。

           s.replace(range,args): 删除s中范围range内的字符,替换为args指定的字符。range或者是一个下标和一个长度,或者是一个对指向s的迭代器。返回一个指向s的引用。

           例子:

           string s("C++ primer"),s2=s;//将s和s2初始化为"C++ primer";

           s.insert(s.size(),"4th Ed.");

           s2.append(" 4th Ed.");//等价s.insert(s.size(),"4th Ed.");s2==s,s=="C++ primer 4th Ed."

          replace操作是调用erase和insert的一种简单形式:

           s.erase(11,3);      //s=="C++ primer  Ed."

           s.insert(11,"5th");  //s=="C++ primer 5th Ed."

          s2.replace(11,3,"5th"); //s==s2;等价于上面两个语句;

            D.string 搜素操作

            s.find(args)  :查找s中args第一次出现的位置;

            s.rfind(args) :查找s中args最后一次出现的位置;

            s.find_first_of(args):在s中查找args中任何一个字符第一次出现的位置;

            s.find_last_of(args):在s中查找args中任何一个字符最后一次出现的位置;

            s.find_first_not_of(args):在s中查找第一个不在args中的字符;

            s.find_last_not_of(args):在s中查找最后一个不在args中的字符;

            例子:

            string river("Mississippi");

            auto first_pos=river.find("is"); //返回1

            auto last_pos=river.rfind("is");// 返回4

            E.数值转换

            string和数值之间的转换:

            to_string(val)  :一组重载函数,返回数值val的string表示。val可以是任何算术类型。

           stoi(l,ul,ll,ull)(s,p,b):返回s的起始字串的数值,(红色标记的)返回值类型分别是int,long,unsigned long,long long,unsigned long long。b表示转换所用的基数,默认值为10。p是size_t指针,用来保存s中第一个非数值字符的下标,p默认为0,即函数不保存下标。

           stof(d,ld)(s,p):  返回s的起始子串的数值,返回类型分别是float,double,long double。p参数的作用与整数转换函数中一样;

          由于我用的codeblacks不支持stoi从而,我编写的程序编译不通过;下次换个支持C++11特性的软件试试;

          总结C++11特性:

               1.forward_list和array

               array是固定大小的数组,因此,它不支持添加/删除以及改变容器大小的操作。而forward_list的设计目标是达到与最好的手写的单向链表数据结构相当的性能。它没有size操作,因为保存和计算需要额外的开销。前面也做了介绍。

                2.cbegin和cend

                他们和begin,end类似,只不过前面加了const ;它们返回const迭代器;当不需要写访问时,就用cbegin和end比较好。

                3.列表初始化

               容器列表的初始化在前面的程序中都用到了,前面章节也讲解过;

                 list<string> aythors={"Mislton","Shakespeare"};

                4.容器的非成员版本swap

               本章节对swap和赋值进行了比较分析,swap操作将容器的内容交换不会导致指向容器的迭代器、引用和指针失效。这是它的一个很大的优点。

                 5.insert函数

                 本章做了重点讲解,它在新的标准下,接受元素个数或范围的insert版本返回指向第一个新加入元素的迭代器。

                 6.emplace操作

                 本章也做了详细的讲解,通过前面的例子能够更好的理解和运用它。

                 7.shrink_to_fit

                 管理容量成员的函数,它在新标准下,它可要求deque、vector和string退回不需要的内存空间。(简单来讲,就是将预留空间去掉和size()大小相同)

                8.string的数值转换

                上面有详细的讲解,本想通过程序更好的理解它,我用的codeblacks不支持stoi等函数,只能下次尝试其他支持c++11特性的软件。














郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。