zoukankan      html  css  js  c++  java
  • 顺序容器的操作

    顺序容器和关联容器的不同之处在于两者组织元素的方式。这些不同之处直接关系的到了元素如何存储、访问、添加以及删除。

    向顺序容器中添加元素

    除了array之外,所有标准库容器都提供灵活的内存管理。在运行时可以动态添加或删除元素来改变容器大小。下表列出了向顺序容器中添加元素的操作。

    向顺序容器添加元素的操作

    操作会改变容器的大小;array不支持这些操作。

    forward_list有自己专有版本的insert和emplace;

    forward_list不支持push_back和emplace_back

    vector和string不支持push_front和emplace_front

    c.push_back(t)        在c的尾部创建一个值为t或由args创建的元素,返回void

    c.emplace_back(args)

    c.push_front(t)        在c的头部创建一个值为t或由args创建的元素,返回void

    c.emplace_front(args)

    c.insert(p,t)           在迭代器p指向的元素之前创建一个值为t或由args创建的元素,返回指向新添加的元素的迭代器

    c.emplace(p,args)

    c.insert(p,n,t)           在迭代器p指向的元素之前插入n个值为t的元素。返回指向新添加的第一个元素的迭代器;若n为0,则返回p

    c.insert(p,b,e)          将迭代器b和e指向的范围内的元素插入到迭代器p指向的元素之前。b和e不能指向c中的元素,返回指向新添加的第一个元素的迭代器;若范围为空,则返回p

    c.insert(p,il)          il是一个花括号包围的元素值列表,将这些给定值插入到迭代器p指向的元素之前。返回指向新添加的第一个元素的迭代器:若列表为空,则返回p

    向一个vector、string或deque插入元素会使所有指向容器的迭代器、引用和指针失效。

    当我们使用这些操作时,必须记得不同容器使用不同的策略来分配元素空间,而这些策略直接影响性能。在一个vector或string的尾部之外的任何位置,或是一个deque的首尾之外的任何位置添加元素,都需要移动元素。而且,向一个vector或string添加元素可能引起整个对象存储空间的重新分配。重新分配一个对象的存储空间需要分配新的内存,并将元素从旧的空间移动到新的空间中。

    使用push_back

    我们看到push_back将一个元素追加到一个vector的尾部。除array和forward_list之外,每个顺序容器(包括string类型)都支持push_back

    例如,下面的循环每次读取一个string到word中,然后追加到容器尾部:

    //从标准输入读取数据,将每个单词放到容器末尾

    string word;

    while(cin>>word)

      container.push_back(word);

    对push_back的调用在container尾部创建了一个新的元素,将container的size增大了1。该元素的值为word的一个拷贝,container的类型可以是list、vector或deque。

    关键概念:当我们用一个对象来初始化容器时,或将一个对象插入到容器中时,实际上放入到容器中的是对象值的一个拷贝,而不是对象本身。就像我们将一个对象传递给非引用参数一样,容器中的元素与提供值的对象之间没有任何关联。随后对容器中元素的任何改变都不会影响到原始对象,反之亦然

    使用push_front

    除了push_back,list、forward_list和deque容器还支持名为push_front的类似操作。此操作将元素插入到容器头部:

    list<int> ilist;

    //将元素添加到Ilist开头

    for(size_t ix=0;ix!=4;++ix)

      ilist.push_front(ix);

    此循环将元素0、1、2、3添加到ilist头部。每个元素都插入到list的新的开始位置。即,当我们插入1时,它会被放置在0之前,2被放置在1之前,依次类推。因此,在循环中以这种方式将元素添加到容器中,最终会形成逆序。

    注意:deque像vector一样提供了随机访问元素的能力,但它提供了vector所不支持的push_front。deque保证在容器首部进行插入和删除元素的操作都只花费常数时间。与vector一样,在deque首尾之外的位置插入元素会很耗时。

    在容器中的特定位置添加元素

    push_back和push_front操作提供了一种方便地在顺序容器尾部或头部插入单个元素的方法。insert成员提供了更一般的添加功能,它允许我们在容器中任意位置插入0个或多个元素。vector、deque、list和string都支持insert成员。forward_list提供了特殊版本的insert成员

    每个insert函数都接受一个迭代器作为其一个参数。迭代器指出了在容器中什么位置放置新元素。它可以指向容器中任何位置,包括容器尾部之后的下一个位置。由于迭代器可能指向容器尾部之后不存在的元素的位置,而且在容器开始位置插入元素是很有用的功能,所有insert函数将元素插入到迭代器所指定的位置之前。例如,下面的语句

    slist.insert(iter,"Hello!");  //将hello添加到iter之前的位置

    虽然某些容器不支持push_front操作,但他们对于insert操作并无类似的限制(插入开始位置)。因此我们可以将元素插入到容器的开始位置,而不必担心容器是否支持push_front:

    vector<string> svec;

    list<string> slist;

    //等价于调用slist.push_front("Hello!");

    slist.insert(slist.begin(),"Hello!");

    //vector不支持push_front,但我们可以插入到begin()之前

    //警告:插入到vector末尾之外的任何位置都可能很慢

    svec.insert(svec.begin(),"Hello!");

    将元素插入到vector、deque和string中的任何位置都是合法的。然而,这样做可能很耗时。

    插入范围内元素

    除了第一个迭代器参数之外,insert函数还可以接受更多的参数,这与容器构造函数类似。其中一个版本接受一个元素数目和一个值,它将指定数量的元素添加到指定位置之前,这些元素够按给定值初始化:

    svec.insert(svec.end(),10,"Anna");

    这行代码将10个元素插入到svec的末尾,并将所有元素都初始化为string“Anna”。

    接受一对迭代器或一个初始化列表的insert版本将给定范围中的元素插入到指定位置之前:

    vector<string> v={"quasi","simba","frollo","scar"};

    //将v的最后两个元素添加到slist的开始位置

    slist.insert(slist.begin(),v.end()-2,v.end());

    slist.insert(slist.end(),{"these","words","will","go","at","the","end"});

    //运行时错误:迭代器表示要拷贝的范围,不能指向与目的位置相同的容器

    slist.insert(slist.begin(),slist.begin(),slist.end());

    如果我们传递给insert一对迭代器,它们不能指向添加元素的目标容器

    使用insert的返回值

    通过使用insert的返回值,可以容器中一个特定的位置反复插入元素:

    list<string> lst;

    auto iter=lst.begin();

    while(cin>>word)

      iter=lst.insert(iter,word);  //等价于调用push_front

    在循环之前,我们将iter初始化为lst.begin()。第一次调用insert会将我们刚刚读入的string插入到iter所指向的元素之前的位置。insert返回的迭代器恰好指向这个新元素。我们将此迭代器赋予iter并重复循环,读取下一个单词。

    使用emplace操作

    新标准引入了三个成员——emplace_front、emplace和emplace_back,这些操作构造而不是拷贝元素。这些操作分别对应push_front、insert和push_back,允许我们将元素放置在容器头部、一个指定的位置之前或容器尾部。

    调用push或insert成员函数时,我们将元素类型对象传递给它们,这些对象被拷贝到容器中。而当我们调用一个emplace成员函数时,则是将参数传递给元素类型的构造函数。emplace成员使用这些参数在容器管理的内存空间中直接构造元素。例如,假定c保存Sales_data元素:

    //在c的末尾构造一个Sales_data对象

    //使用三个参数的Sales_data的构造函数

    c.emplace_back("978-0590353403",25,15.99);

    //错误:没有接受三个参数的push_back版本

    c.push_back("978-0590353403",25,15.99);

    //正确:创建一个临时的Sales_data对象传递给push_back

    c.push_back(Sales_data(("978-0590353403",25,15.99));

    其中对emplace_back的调用和第二个push_back调用都会创建新的Sales_data对象。在调用emplace_back时,会在容器管理的内存空间中直接创建对象。而调用push_back则会创建一个局部临时对象,并将其压入容器中

    emplace函数的参数根据元素类型而变化,参数必须与元素类型的构造函数相匹配:

    //iter指向c中一个元素,其中保存了Sales_data元素

    c.emplace_back();//使用Sales_data的默认构造函数

    c.emplace(iter,"999-999999999"); //使用Sales_data(string)

    //使用Sales_data的接受一个ISBN、一个count和一个price的构造函数

    c.emplace_front("978-0590353403",25,15.99);

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

    访问元素

    下表列出了我们可以用来在顺序容器值访问元素的操作。如果容器中没有元素,访问操作的结果是未定义的。

    包括array在内的每个顺序容器都有一个front成员函数,而除了forward_list之外的所以顺序容器都有一个back成员函数。这两个操作分别返回首元素和尾元素的引用

    //在解引用一个迭代器或调用front或back之前检查是否有元素

    if(!c.empty()){

      //val和val2是c中第一个元素值的拷贝

      auto val=*c.begin(),val2=c.front();

      //val3和val4是c中最后一个元素值的拷贝

      auto last=c.end();

      auto val3=*(--last);  //不能递减forward_list迭代器

      auto val4=c.back();  //forward_list不支持

    此程序用两种不同方式来获取c中的首元素和尾元素的引用。直接的方式hit调用front和back。而间接的方法是通过解引用begin返回的迭代器来获得首元素的引用,以及通过递减然后解引用end返回的迭代器来获取尾元素的引用。

    在顺序容器中访问元素的操作

    at和下标操作是适用于string、vector和array

    back不适用于forward_list。

    c.back()           返回c中尾元素的引用。若c为空,函数行为未定义

    c.front()           返回c中首元素的引用。若c为空,函数行为未定义

    c[n]             返回c中下标为n的元素的引用,n是一个无符号整数。若n>c.size(),则函数的行为未定义

    c.at[n]            返回下标为n的元素的引用。如果下标越界,则抛出一个out_of_range异常

    对一个空容器调用front和back,就像使用一个越界的下标一样

    访问成员函数返回的是引用

    在容器中访问元素的成员函数(即,front、back、下标和at)返回的都是引用。如果容器是一个const对象,则返回值是const的引用。如果容器不是const的,则返回值是普通引用,我们可以用来改变元素的值。

    if(!c.empty()){

      c.front()=42;  //将42赋予c中的第一个元素

      auto &v=c.back();   //获得指向最后一个元素的引用

      v=1024;    //改变c中的元素

      auto v2=c.back();    //v2不是一个引用,它是c.back()的一个拷贝

      v2=0;      //未改变c中的元素

    }

    与往常一样,如果我们使用auto变量来保存这些还是的返回值,并且希望使用此变量来改变元素的值,必须记得将变量定义为引用类型

    下标操作和安全的随机访问

    提供快速随机访问的容器(string、vector、deque和array)也都提供下标运算符。就像我们已经看到的那样,下标运算符接受一个下标参数,返回容器中该位置的元素的引用。

    我们希望确保下标是合法的,可以使用at成员函数。at成员函数类似下标运算符,但如果下标越界,at会抛出一个out_of_range异常:

    vector<string> svec;  //空vector

    cout<<svec[0];   //运行时错误:svec中没有元素

    cout<<svec.at[0];   //抛出一个out_of_range异常

    删除元素

    与添加元素的多种方式类似,(非array)容器也有多种删除元素的方式。如下表所示:

    顺序容器的删除操作

    这些操作会改变容器的大小,所以不适用于array

    forward_list有特殊版本的erase

    forward_list不支持pop_back;vector和string不支持pop_front

    c.pop_back()       删除c中尾元素,若c为空,则函数行为未定义,函数返回void

    c.pop_front()      删除c中首元素,若c为空,则函数行为未定义,函数返回void

    c.erase(p)        删除迭代器p所指的元素,返回以指向被删除元素之后的迭代器,若p指向尾元素,则返回尾后迭代器。若p是尾后迭代器,则函数的行为未定义

    c.erase(b,e)       删除迭代器b和e所指定范围内的元素,返回一个指向最后一个被删除元素之后元素的迭代器,若e本身就是尾后迭代器,则函数也返回尾后迭代器

    c.clear()         删除c中的所以元素,返回void

    删除deque中除首位元素之外的任何元素都会使所有迭代器、引用和指针失效。指向vector或string中删除点之后位置的迭代器、引用和指针都会失效。

    pop_front和pop_back成员函数

    pop_front和pop_back成员函数分别删除首元素和尾元素。与vector和string不支持push_front一样,这些类型也不支持pop_front。类似的,forward_list不支持pop_back。与元素访问成员函数类似,不能对一个空容器执行弹出操作。

    这些操作返回void,如果你需要弹出的元素的值,就必须在执行弹出操作之前保存它:

    while(!ilist.empty()){

      process(ilist.front());  //对ilist的首元素进行一些处理

      ilist.pop_front();  //完成处理后删除首元素

    }

    从容器内部删除一个元素

    成员函数erase从容器中指定位置删除元素,我们可以删除由一个迭代器指定的单个元素,也可以删除由一对迭代器指定的范围内的所有元素。两种形式的erase都返回指向删除的(最后一个)元素之后位置的迭代器。即,若j是i之后的元素,那么erase(i)将返回指向j的迭代器。

    例如,下面的循环删除一个list中的所有奇数元素:

    list<int> lst=(0,1,2,3,4,5,6,7,8,9};

    auto it=lst.begin();

    while(it!=lst.end())

      if(*it%2)

        it=lst.erase(it);  //删除此元素

      else

        ++it;

    每个循环步中,首先检查当前元素是否是奇数,如果是,就删除该元素,并将it设置为我们所删除的元素之后的元素。如果*it为偶数,我们将it递增,从而在下一步循环检查下一个元素。

    删除多个元素

    接受一对迭代器的erase版本允许我们删除一个范围内的元素:

    //删除两个迭代器表示的范围内的元素

    //返回指向最后一个被删除元素之后位置的迭代器

    elem1=slist.erase(elem1,elem2);  //调用后,elem1==elem2

    迭代器elem1指向我们要删除的第一个元素,elem2指向我们要删除的最后一个元素之后的位置。

    为了删除一个容器中的所有元素,我们既可以调用clear,也可以用begin和end获得的迭代器作为参数调用erase:

    slist.clear() ;//删除容器中的所有元素

    slist.erase(slist.begin(),slist.end());   //等价调用

  • 相关阅读:
    javascript小记
    好看的echart的词云效果(wordCloud)
    工作中经常用到的git的简单操作记录
    积累就是提升之浅谈分时函数
    有意思的面试小试题
    分享张鑫旭大神的,纯css打字小技巧,我顺便收藏一下
    模仿也是提高,纯css小技巧实现头部进度条
    推荐好用的css调试工具,两个
    There appears to be trouble with your network connection. Retrying
    Enter passphrase for key ‘/root/.ssh/id_rsa’ : git push 重复输入密码的问题
  • 原文地址:https://www.cnblogs.com/wuchanming/p/3915563.html
Copyright © 2011-2022 走看看