《C++ Primer》笔记 第9章 顺序容器

  1. 顺序容器类型
类型 解释
vector 可变大小数组。支持快速随机访问。在尾部以外的位置插入或删除元素可能很慢
deque 双端队列。支持快速随机访问。在头尾位置插入、删除速度很快
list 双向链表。只支持双向顺序访问。在list中任何位置进行插入、删除操做速度都很快
forward_list 单向链表。只支持单向顺序访问。在链表任何位置进行插入、删除操做速度都很快
array 固定大小数组。支持快速随机访问。不能添加或删除元素
string 与vector类似的容器,但专门用于保存字符。随机访问快。在尾部插入、删除速度快。
  1. stringvector将元素保存在连续的内存空间中。由元素的下标来计算其地址是很是快速的,但在中间位置添加或删除元素就会很是耗时。listforward_list两个容器的设计目的是令容器任何位置的添加和删除操做都很快速,做为代价,这两个容器不支持元素的随机访问,而且相比vector、deque、array,这两个容器的额外内存开销也很大。deque支持快速的随机访问,在deque的两端添加或删除元素都是很快的,与list或forward_list添加删除元素的速度至关。array对象的大小是固定的。所以,array不支持添加和删除元素以及改变容器大小的操做。forward_list的设计目标是达到与最好的手写的单向链表数据结构至关的性能。所以,forward_list没有size操做,由于保存或计算其大小就会比手写链表多出额外的开销。对其余容器而言,size保证是一个快速的常量时间的操做。
  2. 如下是一些选择容器的基本原则:
    • 除非你有很好的理由选择其余容器,不然应使用vector。
    • 若是你的程序有不少小的元素,且空间的额外开销很重要,则不要使用list或forward_list。
    • 若是程序要求随机访问元素,应使用vector或deque。
    • 若是程序要求在容器的中间插入或删除元素,应使用list或forward_list。
    • 若是程序须要在头尾位置插入或删除元素,但不会在中间位置进行插入或删除操做,则使用deque。
    • 若是程序只有在读取输入时才须要在容器中间位置插入元素,随后须要随机访问元素,则
      • 首先,肯定是否真的须要在容器中间位置添加元素。当处理输入数据时,一般能够很容易地向vector追加数据,而后再调用标准库的sort函数来重排容器中的元素,从而避免在中间位置添加元素。
      • 若是必须在中间位置插入元素,考虑在输入阶段使用list,一旦输入完成,将list中的内容拷贝到一个vector中。
  3. 若是你不肯定应该使用哪一种容器,那么能够在程序中只使用vector和list公共的操做:使用迭代器,不使用下标操做,避免随机访问。这样,在必要时选择使用vector或list都很方便。
  4. 容器类型上的操做造成了一种层次:
    • 某些操做是全部容器类型都提供的。
    • 另一些操做仅针对顺序容器、关联容器或无序容器。
    • 还有一些操做只适用于一小部分容器。
  5. 顺序容器构造函数的一个版本接受容器大小参数,它使用了元素类型的默认构造函数。但某些类没有默认构造函数。咱们能够定义一个保存这种类型对象的容器,但咱们在构造这种容器时不能只传递给它一个元素数目参数:
    // 假定noDefault是一个没有默认构造函数的类型
      vector<noDefault> v1(10, init); // 正确:提供了元素初始化器
      vector<noDefault> v2(10); // 错误:必须提供一个元素初始化器
  6. 容器操做
类型别名 解释
iterator 此容器类型的迭代器类型
const_iterator 能够读取元素,但不能修改元素的迭代器类型
size_type 无符号整数类型,足够保存此种容器类型最大可能容器的大小
difference_type 带符号整数类型,足够保存两个迭代器之间的距离
value_type 元素类型
reference 元素的左值类型;与value_type&含义相同
const_reference 元素的const左值类型(即,const value_type&
构造函数 解释
C c; 默认构造函数,构造空容器
C c1(c2); 构造c2的拷贝c1
C c(b,e); 构造c,将迭代器b和e指定的范围内的元素内的元素拷贝到c
C c{a,b,c...}; 列表初始化c
赋值与swap 解释
c1=c2 将c1中的元素替换为c2中元素
c1={a,b,c...} 将c1中的元素替换为列表中元素
a.swap(b) 交换a和b的元素
swap(a,b) 与a.swap(b)等价
大小 解释
c.size() c中元素的数目
c.max_size() c可保存的最大元素数目
c.empty() 若c中储存了元素,返回false,不然返回true
添加、删除元素 解释
c.insert(args) 将args中的元素拷贝进c
c.emplace(inits) 使用inits构造c中的一个元素
c.erase(args) 删除args指定的元素
c.clear() 删除c中的全部元素,返回void
关系运算符 解释
==,!= 全部容器都支持相等(不等)运算符
<,<=,>,>= 删除c中的全部元素,返回void
获取迭代器 解释
c.begin(),c.end() 返回指向c的首元素和尾元素以后位置的迭代器
c.cbegin(),c.cend() 返回const_iterator
反向容器的额外成员(不支持forward_list) 解释
reverse_iterator 按逆序寻址元素的迭代器
const_reverse_iterator 不能修改元素的逆序迭代器
c.rbegin(),c.rend() 返回指向c的尾元素和首元素以前位置的迭代器
c.crbegin(),c.crend() 返回const_reverse_iterator
  1. forward_list迭代器不支持递减运算符(--)。ios

  2. 一个迭代器范围由一对迭代器表示,两个迭代器分别指向同一个容器中的元素或者是尾元素以后的位置。c++

  3. 迭代器范围中的元素包含first所表示的元素以及从first开始直至last(但不包含last)之间的全部元素。左闭合区间:[left, right)。git

  4. 若是知足以下条件,两个迭代器begin和end构成一个迭代器范围:程序员

    • 它们指向同一个容器中的元素,或者是容器最后一个元素以后的位置,且
    • 咱们能够经过反复递增begin来到达end。换句话说,end不在begin以前。
  5. 标准库使用左闭合范围是由于这种范围有三种方便的性质。假定begin和end构成一个合法的迭代器范围,则编程

    • 若是begin与end相等,则范围为空
    • 若是begin与end不等,则范围至少包含一个元素,且begin指向该范围中的第一个元素
    • 咱们能够对begin递增若干次,使得begin==end
  6. 这些性质意味着咱们能够用循环来处理一个元素范围。数组

    while (begin != end)
    {
        *begin = val; // 正确:范围非空,所以begin指向一个元素
        // 在while循环中,能够安全地解引用begin,由于begin必然指向一个元素。
        ++begin; // 移动迭代器,获取下一个元素
    }
  7. 反向迭代器就是一种反向遍历容器的迭代器,与正向迭代器相比,各类操做的含义也都发生了颠倒。例如,对一个反向迭代器执行++操做,会获得上一个元素。安全

  8. begin和end有多个版本:带r的版本返回反向迭代器;以c开头的版本则返回const迭代器;不以c开头的函数都是被重载过的。一个是const成员,返回容器的const_iterator类型;另外一个是很是量成员,返回容器的iterator类型。当咱们对一个很是量对象调用这些成员时,获得的是返回iterator的版本。只有在对一个const对象调用这些函数时,才会获得一个const版本。与const指针和引用相似,能够将一个普通的iterator转换为对应的const_iterator,但反之不行。数据结构

  9. 当auto与begin或end结合使用时,得到的迭代器类型依赖于容器类型,与咱们想要如何使用迭代器绝不相干。但以c开头的版本仍是能够得到const_iterator的,而无论容器的类型是什么。当不须要写访问时,应该使用cbegin和cend。app

  10. 容器定义和初始化函数

方法 解释
C c; 默认构造函数。若是c是一个array,则c中元素按默认方式初始化;不然c为空
C c1(c2) c1初始化为c2的拷贝。c1和c2必须是相同类型(即,它们必须是相同的容器类型,且保存的是相同的元素类型;对于array类型,二者还必须具备相同大小)
C c{a,b,c...}或C c={a,b,c...} c初始化为初始化列表中元素的拷贝。列表中元素的类型必须与c的元素类型相容。对于array类型,列表中元素数目必须等于或小于array的大小,任何遗漏的元素都进行值初始化
C c(b,e) c初始化为迭代器b和e指定范围中的元素的拷贝。范围中元素的类型必须与C的元素的类型相容(array不适用)
—— 只有顺序容器(不包括array)的构造函数才能接受大小参数
C seq(n) seq包含n个元素,这些元素进行了值初始化;此构造函数是explicit的。(string不适用)
C seq(n,t) seq包含n个初始化为值t的元素
  1. 当将一个容器初始化为另外一个容器的拷贝时,两个容器的容器类型和元素类型都必须相同。但当传递迭代器参数来拷贝一个范围时,就不要求容器类型是相同的了。并且,新容器和原容器中的元素类型也能够不一样,只要能将要拷贝的元素转换为要初始化的容器的元素类型便可(构造函数只是读取范围中的元素并进行拷贝)。

    // 每一个容器有三个元素,用给定的初始化器进行初始化
    list<string> authors = {"Milton", "Shakespeare", "Austen"};
    vector<const char*> articles = {"a", "an", "the"};
    
    list<string> list2(authors); // 正确:类型匹配
    deque<string> authList(authors); // 错误:容器类型不匹配
    vector<string> words(articles); // 错误:容器类型必须匹配
    // 正确:能够将const char*元素转换为string
    forward_list<string> words(articles.begin(),articles.end());
  2. 若是元素类型是内置类型或者是具备默认构造函数的类类型,能够只为构造函数提供一个容器大小参数。若是元素类型没有默认构造函数,除了大小参数外,还必须指定一个显示的元素初始值。

    #include <iostream>
    #include <vector>
    using namespace std;
    
    class A
    {
    private:
        vector<int> data;
    
    public:
        A(vector<int>::iterator beg, vector<int>::iterator end)
        {
            data.assign(beg, end);
        }
        void display()
        {
            for (auto i : data)
                cout << i << " ";
            cout << endl;
        }
    };
    
    int main()
    {
        vector<int> iv{1, 2, 3};
        vector<A> one(5, A(iv.begin(), iv.end()));
        for (auto i : one)
            i.display();
        cout << endl;
        return 0;
    }
  3. 只有顺序容器的构造函数才接受大小参数,关联容器并不支持。

  4. 标准库array的大小也是类型的一部分。当定义一个array时,除了指定元素类型,咱们必须同时指定元素类型和大小。

    array<int, 42> // 类型为:保存42个int的数组
    array<string, 10> // 类型为:保存10个string的数组
    
    array<int, 10>::size_type i; // 数组类型包括元素类型和大小
    array<int>::size_type j; // 错误:array<int>不是一个类型
  5. 一个默认构造的array是非空的:它包含了于其大小同样多的元素。这些元素都被默认初始化,就像一个内置数组中的元素那样。若是咱们对array进行列表初始化,初始值的数目必须等于或小于array的大小。若是初始值数目小于array的大小,则它们被用来初始化array中靠前的元素,全部剩余元素都会进行值初始化。在这两种状况下,若是元素类型是一个类类型,那么该类必须有一个默认构造函数,以使值初始化可以进行。

    array<int, 10> ia1; // 10个默认初始化的int
    array<int, 10> ia2 = {0,1,2,3,4,5,6,7,8,9}; // 列表初始化
    array<int, 10> ia3 = {42}; // ia3[0]为42,剩余元素为0
    
    int digs[10] = {0,1,2,3,4,5,6,7,8,9};
    int cpy[10] = digs; // 错误:内置数组不支持拷贝或赋值
    array<int, 10> digits = {0,1,2,3,4,5,6,7,8,9};
    array<int, 10> copy = digits; // 正确:只要数组类型匹配即合法
  6. 与其余容器同样,array也要求初始值的类型必须与要建立的容器类型相同。此外,array还要求元素类型和大小也都同样,由于大小是array类型的一部分。

  7. 与赋值相关的运算符可用于全部容器。赋值运算符将其左边容器中的所有元素替换为右边容器中元素的拷贝。若是两个容器原来大小不一样,赋值运算后二者的大小都与右边容器的原大小相同。

    c1 = c2; // 将c1的内容替换为c2中元素的拷贝
    c1 = {a,b,c}; // 赋值后,c1大小为3
  8. 与内置数组不一样,标准库array类型容许赋值。赋值号左右两边的运算对象必须具备相同的类型。因为右边运算对象的大小可能与左边运算对象的大小不一样,所以array类型不支持assign,也不容许用花括号包围的值列表进行赋值。

    array<int, 10> a1 = {0,1,2,3,4,5,6,7,8,9};
    array<int, 10> a2 = {0}; // 全部元素值均为0
    a1 = a2; // 替换a1中的元素
    a2 = {0}; // 错误:不能将一个花括号列表赋予数组
容器赋值运算 解释
c1=c2 将c1中的元素替换为c2中元素的拷贝。c1和c2必须具备相同的类型
c={a,b,c...} 将c1中元素替换为初始化列表中元素的拷贝(array不适用)
swap(c1,c2) 交换c1和c2中的元素。c1和c2必须具备相同的类型。swap一般比从c2向c1拷贝元素快得多
—— assign操做不适用于关联容器和array
seq.assign(b,e) 将seq中的元素替换为迭代器b和e所表示的范围中的元素。迭代器b和e不能指向seq中的元素
seq.assign(il) 将seq中的元素替换为初始化列表il(initializer_list)中的元素
seq.assign(n,t) 将seq中的元素替换为n个值为t的元素
  1. 赋值相关运算会致使指向左边容器内部的迭代器、引用和指针失效。而swap操做将容器内容交换不会致使指向容器的迭代器、引用和指针失效(容器类型为array和string的状况除外)。

  2. 顺序容器的assign成员容许咱们从一个不一样但相容的类型赋值,或者从容器的一个子序列赋值。assign操做用参数所指定的元素(的拷贝)替换左边容器中的全部元素。

    list<string> names;
    vector<const char*> oldstyle;
    names = oldstyle; // 错误:容器类型不匹配
    // 正确:能够将const char*转换为string
    names.assign(oldstyle.cbegin(), oldstyle.cend());
  3. 因为其旧元素被替换,所以传递给assign的迭代器不能指向调用assign的容器。

  4. 除array外,swap不对任何元素进行拷贝、删除或插入操做,所以能够保证在常数时间内完成。

  5. 除string外,指向容器的迭代器、引用和指针在swap操做以后都不会失效。它们仍指向swap操做以前所指向的那些元素。可是,在swap以后,这些元素已经属于不一样的容器了。对一个string调用swap会致使迭代器、引用和指针失效。

  6. swap两个array会真正交换它们的元素。所以,交换两个array所需的时间与array中元素的数目成正比。

  7. 对于array,在swap操做以后,指针、引用和迭代器所绑定的元素保持不变,但元素值已经与另外一个array中对应元素进行了交换。

  8. 在标准库中,容器既提供成员函数版本的swap,也提供非成员版本的swap。非成员版本的swap在泛型编程中是很是重要的。统一使用非成员版本的swap是一个好习惯。

  9. 成员函数size返回容器中元素的数目;empty当size为0时返回布尔值true,不然返回false;max_size返回一个大于或等于该类型容器所能容纳的最大元素数的值。forward_list支持max_size和empty,但不支持size。

  10. 每一个容器类型都支持相等运算符(==和!=);除了无序关联容器都支持关系运算符(>、>=、<、<=)。关联运算符左右两边的运算对象必须是相同类型的容器,且必须保存相同类型的元素。

  11. 比较两个容器其实是进行元素的逐对比较。这些运算符的工做方式与string的关系运算相似。

    • 若是两个容器具备相同大小且全部元素都两两对应相等,则这两个容器相等;不然两个容器不等。
    • 若是两个容器大小不一样,但较小容器中每一个元素都等于较大容器中的对应元素,则较小容器小于较大容器。
    • 若是两个容器都不是另外一个容器的前缀子序列,则它们的比较结果取决于第一个不相等的元素的比较结果。
  12. 只有当其元素类型也定义了相应的比较运算符时,咱们才可使用关系运算符来比较两个容器。

  13. 容器的相等运算符其实是使用元素的==运算符实现比较的,而其余关系运算符是使用元素的<运算符。若是元素类型不支持所需运算符,那么保存这种元素的容器就不能使用相应的关系运算。

向顺序容器添加元素的操做 解释
—— 这些操做会改变容器的大小;array不支持这些操做
—— forward_list有本身专有版本的insert和emplace
—— forward_list不支持push_back和emplace_back
—— vector和string不支持push_front和emplace_front
c.push_back(t)或c.emplace_back(args) 在c的尾部建立一个值为t或由args建立的元素。返回void
c.push_front(t)或c.emplace_front(args) 在c的头部建立一个值为t或由args建立的元素。返回void
c.insert(p,t)或c.emplace(p,args) 在迭代器p指向的元素以前建立一个值为t或由args建立的元素。返回指向新添加的元素的迭代器
c.insert(p,n,t) 在迭代器p指向的元素以前插入n个值为t的元素。返回指向新添加的第一个元素的迭代器;若n为0,则返回p
c.insert(p,b,e) 将迭代器b和e指定的范围内的元素插入到迭代器p指向的元素以前。b和e不能指向c中的元素(insert会破坏迭代器)。返回指向新添加的第一个元素的迭代器;若范围为空,则返回p
c.insert(p,il) il是一个花括号包围的元素值列表(initializer_list)。将这些给定值插入到迭代器p指向的元素以前。返回指向新添加的第一个元素的迭代器;若列表为空,则返回p
  1. 向一个vector、string或deque插入元素会使全部指向容器的迭代器、引用和指针失效。

  2. 不一样容器使用不一样的策略来分配元素空间,而这些策略直接影响性能。

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

  4. 经过使用insert的返回值,能够在容器中一个特定位置反复插入元素:

    list<string> lst;
    auto iter = lst.begin();
    while (cin >> word)
    	iter = lst.insert(iter, word); // 等价于调用push_front
  5. emplace_front、emplace和emplace_back这些操做构造而不是拷贝元素。

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

    #include <iostream>
    #include <vector>
    using namespace std;
    
    class A
    {
    private:
        vector<int> data;
    
    public:
        A(vector<int>::iterator beg, vector<int>::iterator end)
        {
            data.assign(beg, end);
        }
        void display()
        {
            for (auto i : data)
                cout << i << " ";
            cout << endl;
        }
    };
    
    int main()
    {
        vector<int> iv{1, 2, 3};
        vector<A> one(5, A(iv.begin(), iv.end()));
        for (auto i : one)
            i.display();
        cout << endl;
        
        vector<int> ivv{2, 3, 6};
        one.emplace_back(ivv.begin(), ivv.end());
        one.back().display();
        return 0;
    }
  7. 包括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不支持
    }
  8. 在调用front和back(或解引用begin和end返回的迭代器)以前,要确保c非空。若是容器为空,if中操做的行为将是未定义的。

在顺序容器中访问元素的操做 解释
—— at和下标操做只适用于string、vector、deque和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异常
  1. 对一个空容器调用front和back,就像使用一个越界的下标同样,是一种严重的程序设计错误。

  2. 在容器中访问元素的成员函数(即,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变量来保存这些函数的返回值,而且但愿使用此变量来改变元素的值,必须记得将变量定义为引用类型
    }
顺序容器的删除操做 解释
—— 这些操做会改变容器的大小,因此不适用于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自己就是尾后迭代器,则函数也返回尾后迭代器(总之返回e)
c.clear() 删除c中的全部元素。返回void
  1. 删除deque中除首尾位置以外的任何元素都会使全部迭代器、引用和指针失效。指向vector或string中删除点以后位置的迭代器、引用和指针都会失效。
  2. 删除元素的成员函数并不检查其参数。在删除元素以前,程序员必须确保它(们)是存在的。
  3. 在一个单向链表中,没有简单的办法来获取一个元素的前驱。所以,在一个forward_list中添加或删除元素的操做是经过改变给定元素以后的元素来完成的。为了支持这些操做,forward_list也定义了before_begin,它返回一个首前迭代器(容许咱们添加删除链表首元素)
在forward_list中插入或删除元素的操做 解释
lst.before_begin()或lst.cbefore_begin() 返回指向链表首元素以前不存在的元素的迭代器。此迭代器不能解引用。cbefore_begin()返回一个const_iterator
lst.insert_after(p,t)或lst.insert_after(p,n,t)或lst.insert_after(p,b,e)或lst.insert_after(p,il) 在迭代器p以后的位置插入元素。t是一个对象,n是数量,b和e是表示范围的一对迭代器(b和e不能指向lst内),il是一个花括号列表(initializer_list)。返回一个指向最后一个插入元素的迭代器。若是范围为空,则返回p。若p为尾后迭代器,则函数行为未定义
emplace_after(p,args) 使用args在p指定的位置以后建立一个元素。返回一个指向这个新元素的迭代器。若p为尾后迭代器,则函数行为未定义
lst.erase_after(p)或lst.erase_after(b,e) 删除p指向的位置以后的元素,或删除从b以后直到(但不包含)e之间的元素。返回一个指向被删元素以后元素的迭代器,若不存在这样的元素,则返回尾后迭代器。若是p指向lst的尾元素或者是一个尾后迭代器,则函数行为未定义
// 示例代码:
forward_list<int> flst = {0,1,2,3,4,5,6,7,8,9};
auto prev = flst.before_begin(); // 表示flst的“首前元素”
auto curr = flst.begin(); // 表示flst中的第一个元素
while (curr != flst.end()) // 仍有元素要处理
{
	if (*curr % 2) // 若元素为奇数,也能够用*curr & 1
		curr = flst.erase_after(prev); // 删除它并移动curr
	else
	{
		prev = curr; // 移动迭代器curr,指向下一个元素,prev指向
		++curr; // curr以前的元素
	}
}
顺序容器大小操做 解释
—— resize不适用于array
c.resize(n) 调整c的大小为n个元素。若n<c.size(),则多出的元素被丢弃。若必须添加新元素(n>c.size()),对新元素进行值初始化。
c.resize(n,t) 调整c的大小为n个元素。任何新添加的元素都初始化为值t
  1. 若是resize缩小容器,则指向被删除元素的迭代器、引用和指针都会失效;对vector、string或deque进行resize可能致使迭代器、指针和引用失效。

  2. 容器操做可能使迭代器失效。使用失效的迭代器、指针或引用是严重的运行时错误。

    • 在向容器添加元素后:
      • 若是容器是vector或string,且存储空间被从新分配,则指向容器的迭代器、指针和引用都会失效。若是存储空间未从新分配,指向插入位置以前的元素的迭代器、指针和引用仍有效,但指向插入位置以后元素的迭代器、指针和引用将会失效。
      • 对于deque,插入到除首尾位置以外的任何位置都会致使迭代器、指针和引用失效。若是在首尾位置添加元素,迭代器会失效,但指向存在的元素的引用和指针不会失效。
      • 对于list和forward_list,指向容器的迭代器(包括尾后迭代器和首前迭代器)、指针和引用仍有效。
    • 当咱们从一个容器中删除元素后,指向被删除元素的迭代器、指针和引用必定会失效,毕竟,这些元素都已经被销毁了。当咱们删除一个元素后:
      • 对于list和forward_list,指向容器其余位置的迭代器(包括尾后迭代器和首前迭代器)、引用和指针仍有效。
      • 对于deque,若是在首尾以外的任何位置删除元素,那么指向被删除元素外其余元素的迭代器、引用和指针也会失效。若是是删除deque的尾元素,则尾后迭代器也会失效,但其余迭代器、引用和指针不受影响;若是是删除首元素,这些也不会受影响。
      • 对于vector和string,指向被删元素以前元素的迭代器、引用和指针仍有效。注意:当咱们删除元素时,尾后迭代器老是会失效。
  3. 当你使用迭代器(或指向容器元素的引用或指针)时,最小化要求迭代器必须保持有效的程序片断是一个好的办法。因为向迭代器添加元素和从迭代器删除元素的代码可能会使迭代器失效,所以必须保证每次改变容器的操做以后都正确地从新定位迭代器。这个建议对vector、string和deque尤其重要。

  4. 程序必须保证每一个循环布中都更新迭代器、引用和指针:

    // 傻瓜循环,删除偶数元素,复制每一个奇数元素
    vector<int> vi = {0,1,2,3,4,5,6,7,8,9};
    auto iter = vi.begin(); // 调用begin而不是cbegin,由于咱们要改变vi
    while (iter != vi.end())
    {
    	if (*iter % 2)
    	{
    		iter = vi.insert(iter, *iter); // 复制当前元素
    		iter += 2; // 向前移动迭代器,跳过插入到它以前的元素以及当前元素
    	}
    	else
    	{
    		iter = vi.erase(iter); // 删除偶数元素
    		// 不该向前移动迭代器,iter指向咱们删除的元素以后的元素
    	}
    }
  5. 不要保存end返回的迭代器。当咱们添加、删除vector或string的元素后,或在deque中首元素以外任何位置添加、删除元素后,原来end返回的迭代器老是会失效。所以,添加或删除元素的循环程序必须反复调用end,而不能在循环以前保存end返回的迭代器,一直看成容器末尾使用。

容器大小管理操做 解释
—— shrink_to_fit只适用于vector、string和deque
—— capacity和reserve只适用于vector和string
c.shrink_to_fit() 请将capacity()减小为与size()相同大小(具体的实现能够选择忽略此请求,即,调用shrink_to_fit并不保证必定退回内存空间)
c.capacity() 不从新分配内存空间的话,c能够保存多少元素
c.reserve(n) 分配至少能容纳n个元素的内存空间
  1. reserve并不改变容器中元素的数量,它仅影响vector预先分配多大的内存空间。只有当须要的内存空间超过当前容量时,reserve调用才会改变vector的容量,若是需求大小大于当前容量,reserve至少分配与需求同样大的内存空间(可能更大)。若是需求大小小于或等于当前容量,reserve什么也不作。特别是,当需求大小小于当前容量时,容器不会退回内存空间。所以,在调用reserve以后,capacity将会大于或等于传递给reserve的参数。这样,调用reserve永远也不会减小容器占用的内存空间。相似的,resize成员函数只能改变容器中元素的数目,而不是容器的容量。咱们一样不能使用resize来减小容器预留的内存空间。

  2. 每一个vector实现均可以选择本身的内存分配策略。可是必须遵照的一条原则是:只有当无可奈何(在执行insert操做时size与capacity相等,或者调用resize或reserve时给定的大小超过当前capacity)时才能够分配新的内存空间。

构造string的其余方法 解释
—— n、len2和pos2都是无符号值
string s(cp,n) s是cp指向的数组中前n个字符的拷贝。此数组至少应该包含n个字符
string s(s2,pos2) s是string s2从下标pos2开始的字符的拷贝。若pos2>s2.size(),构造函数的行为未定义
string s(s2,pos2,len2) s是string s2从下标pos2开始len2个字符的拷贝。若pos2>s2.size(),构造函数的行为未定义。无论len2的值是多少,构造函数至多拷贝s2.size()-pos2个字符。
子字符串操做 解释
s.substr(pos,n) 返回一个string,包含s中从pos开始的n个字符的拷贝。pos的默认值为0.n的默认值为s.size()-pos,即拷贝从pos开始的全部字符
  1. 在c++11中,vector 增长了data()的用法,它返回内置vecotr所指的数组内存的第一个元素的指针。
修改string的操做 解释
s.insert(pos,args) 在pos以前插入args指定的字符。pos能够是一个下标或一个迭代器。接受下标的版本返回一个指向s的引用;接受迭代器的版本返回指向第一个插入字符的迭代器
s.erase(pos,len) 删除从位置pos开始的len个字符。若是len被省略,则删除从pos开始直至s末尾的全部字符。返回一个指向s的引用
s.assign(args) 将s中的字符替换为args指定的字符。返回一个指向s的引用
s.append(args) args追加到s。返回一个指向s的引用
s.replace(range,args) 删除s中范围range内的字符,替换为args指定的字符。range或者是一个下标和一个长度,或者是一对指向s的迭代器。返回一个指向s的引用
—— args能够是下列形式之一;append和assign可使用全部形式
—— str不能与s相同,迭代器b和e不能指向s
str 字符串str
str,pos,len str中从pos开始最多len个字
cp,len 从cp(char pointer)指向的字符数组的前(最多)len个字符
n,c n个字符c
b,e 迭代器b和e指定的范围内的字符
初始化列表 花括号包围的,以逗号分隔的字符列表
—— replace和insert所容许的args形式依赖于rangepos是如何指定的
replace(pos,len,args) replace(b,e,args) insert(pos,args) insert(iter,args) args能够是
str
str,pos,len
cp,len
cp
n,c
b2,e2
初始化列表
  1. string搜索函数返回string::size_type值,该类型是一个unsigned类型。所以,用一个int或其余带符号类型来保存这些函数的返回值不是一个好主意。
string搜索操做 解释
—— 搜索操做返回指定字符出现的下标,若是未找到则返回npos
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中的字符
—— args必须是如下形式之一
c,pos 从s中位置pos开始查找字符c。pos默认为0
s2,pos 从s中位置pos开始查找字符串s2。pos默认为0
cp,pos 从s中位置pos开始查找指针cp指向的以空字符结尾的C风格字符串
cp,pos,n 从s中位置pos开始查找指针cp指向的数组的前n个字符。pos和n无默认值
  1. 指定在哪里开始搜索:

    string numbers("0123456789"), name("r2d2");
    string::size_type pos = 0;
    // 每步循环查找name中下一个数
    while ((pos = name.find_first_of(numbers, pos)) != string::npos)
    {
    	cout << "found number at index: " << pos
    		<< " element is " << name[pos] << endl;
    	++pos; // 移动到下一个字符
    }
s.compare的几种参数形式 解释
s2 比较s和s2
pos1,n1,s2 将s中从pos1开始的n1个字符与s2进行比较
pos1,n1,s2,pos2,n2 将s中从pos1开始的n1个字符与s2中从pos2开始的n2个字符进行比较
cp 比较s与cp指向的以空字符结尾的字符数组
pos1,n1,cp 将s中从pos1开始的n1个字符与cp指向的以空字符结尾的字符数组进行比较
pos1,n1,cp,n2 将s中从pos1开始的n1个字符与指针cp指向的地址开始的n2个字符进行比较
string和数值之间的转换 解释
to_string(val) 一组重载函数,返回数值val的string表示。val能够是任何算术类型。对每一个浮点类型和int或更大的整型,都有相应版本的to_string。与往常同样,小整型会被提高
stoi(s,p,b)或stol(s,p,b)或stoul(s,p,b)或stoll(s,p,b)或stoull(s,p,b) 返回s的起始子串(表示整数内容)的数值,返回值类型分别是int、long、unsigned long、long long、unsigned long long。b表示转换所用的基数,默认值为10。p是size_t指针,用来保存s中第一个非数值字符的下标,p默认为0,即,函数不保存下标
stof(s,p)或stod(s,p)或stold(s,p) 返回s的起始子串(表示浮点数内容)的数值,返回值类型分别是float、double、或long double。参数p的做用与整数转换函数中同样
—— string参数中第一个非空白符必须是符号(+或-)或数字。它能够以0x或0X开头来表示十六进制数。对那些将字符串转换为浮点值的函数,string参数也能够以小数点(.)开头,并能够包含e或E来表示指数部分。对于那些将字符串转换为整型值的函数,根据基数不一样,string参数能够包含字母字符,对应大于数字9的数。
—— 若是string不能转换为一个数值,这些函数抛出一个invalid_argument异常。若是转换获得的数值没法用任何类型来表示,则抛出一个out_of_range异常。
  1. 适配器是标准库中的一个通用概念。本质上,一个适配器是一种机制,能使某种事物的行为看起来像另一种事物同样。一个容器适配器接受一种已有的容器类型,是其行为看起来像一种不一样的类型。
全部容器适配器都支持的操做和类型 解释
size_type 一种类型,足以保存当前类型的最大对象的大小
value_type 元素类型
container_type 实现适配器的底层容器类型
A a; 建立一个名为a的空适配器
A a(c); 建立一个名为a的适配器,带有容器c的一个拷贝
关系运算符 每一个适配器都支持全部关系运算符:==、!=、<、<=、>、>=。这些运算符返回底层容器的比较结果
a.empty() 若a包含任何元素,返回false,不然返回true
a.size() 返回a中的元素数目
swap(a,b) 交换a和b的内容,a和b必须有相同类型,包括底层容器类型也必须相同
  1. 默认状况下,stack和queue是基于deque实现的,priority_queue是在vector之上实现的。咱们能够在建立一个适配器时将一个命名的顺序容器做为第二个类型参数,来重载默认容器类型。

    // 从deq拷贝元素到stk
    stack<int> stk(deq);
    // 在vector上实现的空栈
    stack<string, vector<string>> str_stk;
    // str_stk2在vector上实现,初始化时保存svec的拷贝
    stack<string, vector<string>> str_stk2(svec); // svec是vector<string>类型
额外的栈操做 解释
—— 栈默认基于deque实现,也能够在list或vector之上实现。
s.pop() 删除栈顶元素,但不返回该元素值
s.push(item) 建立一个新元素压入栈顶,该元素经过拷贝或移动item而来,或者由args构造
s.top() 返回栈顶元素,但不将元素弹出栈
额外的queue和priority_queue操做 解释
—— queue默认基于deque实现,priority_queue默认基于vector实现
—— queue也能够用list或vector实现,priority_queue也能够用deque实现
q.pop() 弹出queue的首元素或priority_queue的最高优先级的元素,但不返回此元素
q.front() 返回首元素或尾元素,但不删除此元素
q.back() 只适用于queue
q.top() 返回最高优先级元素,但不删除该元素
q.push(item)或q.emplace(args) 在queue末尾或priority_queue中恰当的位置建立一个元素,其值为item,或者由args构造
  1. deque支持在容器头尾位置的快速插入和删除,并且在两端插入或删除元素都不会致使从新分配空间
相关文章
相关标签/搜索