• C++ Primer学习笔记


    第9章 顺序容器

    容器是指什么?
    一个容器指的是一些特定类型对象的集合。

    9.1 顺序容器概述

    顺序容器,sequential container,提供了控制元素存储何访问顺序的能力,顺序取决于元素加入容器时的位置,与元素值无关。
    标准库提供了3种容器适配器,分别为容器操作定义了不同的接口,来与容器适配。

    顺序容器类型

    vector 可变大小数组,随机访问快,一般在尾部插入/删除元素。在外部之外的插入/删除操作可能很慢
    deque 双端队列,随机访问快,头尾插入/删除快
    list 双向链表,只支持双向顺序访问。在list中任何位置进行插入/删除操作速度都很快
    forward_list 单向链表,只支持单向顺序访问。在链表任何位置进行插入/删除操作速度都很快
    array 固定大小数组,支持快速随机访问。不能添加或删除元素
    string 与vector相似的容器,但专门用于保存字符。

    确定使用哪种顺序容器
    通常,选择使用vector,除非有明确地使用其他容器的理由。
    如果不确定使用哪种容器,可以只使用vector,list的公共操作:使用迭代器,不要用下标进行操作,避免随机访问。

    容器选择基本原则:

    • 通常选择vector,除非有很好的理由使用其他容器。
    • 如果程序有很多小元素,且空间额外开销很重要,就不要使用list或者forward_list。
    • 如果程序要求随机访问元素,使用vector或deque。
    • 如果程序要求在容器中间插入/删除元素,使用list, forward_list。
    • 如果程序要求在头尾位置插入/删除元素,但不会在中间插入/删除元素,使用deque。
    • 如果程序只有在读取输入时,才需要在容器中间位置插入元素,随后需要随机访问元素,则
    1. 如果需要在容器中间位置添加元素,可以先用vector追加数据,然后调用sort进行排序。
    2. 可以考虑输入阶段用list,输入完成后将list拷贝到一个vector。

    9.2 容器库概述

    容器类型有3类操作:

    1. 所有容器都提供的;
    2. 仅针对顺序容器、关联容器、无序容器的;
    3. 只适用于小部分容器的;

    本节介绍第1类 适用于所有容器的。

    容器保存元素类型限制
    顺序容器几乎可以保存任意类型元素,某些容器操作对元素类型有自己特殊要求。
    例如,vector可以保存vector类型元素

    vector<vector<string>> lines; // 老版本编译器可能要求2个尖括号之间有空格,即vector<vector<string> >
    

    顺序容器构造的一个函数版本,接受一个容器大小参数,使用了元素默认构造函数,但又的类型没有默认构造函数。定义保存这种类型对象容器时,不能只传递一个元素数目参数。

    // 假定noDefault是一个没有默认构造函数的类型
    vector<noDefault> v1(10, init); // 正确,init是元素初始化器
    vector<noDefault> v2(10); // 错误,因为noDefault没有默认构造函数,必须提供一个元素初始化器
    

    容器操作
    类型别名

    iterator 容器类型的迭代器类型
    const_iterator 可以读取元素,但不能修改元素的迭代器类型
    size_type 无符号整型,保存容器大小
    difference_type 带符号整型,2个迭代器之间的距离
    value_type 元素类型
    const_reference 元素的const左值类型(const value_type&)
    

    构造函数

    C c; 默认构造函数,构造空容器
    C c1(c2); 构造c2的拷贝c1
    C c(b,e); 构造c,将迭代器b和e指定范围内的元素拷贝到e(array不支持)
    C c{a,b,c...}; 列表初始化c
    

    赋值与swap

    c1 = c2 将c1中元素替换为c2中元素
    c1 = {a,b,c...} 将c1中的元素替换为列表中元素(不适应于array)
    a.swap(b) 交换a和b的元素
    swap(a,b) <=>a.swap(b)
    

    大小

    c.size() c中元素数目(forward_list不支持)
    c.max_size() c可保存的最大元素数目
    c.empty() 判断c中是否存储了元素。c为空,返回false;否则,返回true
    

    添加/删除元素(不适用于array,不同容器中,这些操作的接口不同)

    c.insert(args); 将args中元素拷贝进c
    c.emplace(inits); 使用inits构造c中的一个元素
    c.erase(args); 删除args指定的元素
    c.clear(); 删除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
    

    9.2.1 迭代器

    迭代器是访问容器元素的对象,俗称光标(cursor),提供遍历访问容器元素的接口,而不用关心容器对象的内存分配细节。
    C++迭代器有如下特点:

    1. 如果一个迭代器提供某个操作,那么所有提供相同操作的实现方式都相同。例如,标准容器类型提供容器元素访问操作,而所有迭代器都是通过解引用运算符实现该操作。
    2. forward_list迭代器不支持递减运算符(--)。

    迭代器范围
    一个迭代器范围由一对迭代器表示,两个迭代器分别指向同一个容器中的元素或尾元素之后的位置。这两个迭代器通常称为begin, end,或者first, last。
    不管end还是last,指向尾元素下一位置,而非容器最后一个元素,即元素位置范围:左闭合区间[begin, end)。

    注意:编译器不会强制要求begin和end的合理位置,需要程序员保证。

    使用左闭合范围蕴含的编程假定

    // 利用循环+迭代器,遍历容器元素
    while(begin != end) {
      *begin = val; // 通过解引用方式,访问begin指向的元素并赋值
      ++begin; // 移动迭代器到下一位置
    }
    

    9.2.2 容器类型成员

    size_type, iterator, const_iterator,
    反向迭代器:提供反向遍历容器的迭代器;
    类型别名:value_type 表示元素类型,reference或const_reference表示元素类型的一个引用,在泛型编程中会比较有用(第16章介绍);

    // iter是通过list<string>定义的一个迭代器类型
    list<string>::iterator iter;
    
    // count是通过vector<int>定义的difference_type类型
    vector<int>::difference_type count;
    

    9.2.3 begin和end成员

    begin,end生成指向容器的第一个元素和尾元素之后位置的迭代器。常用于包含容器所有元素的迭代器范围。
    begin,end多个版本:
    普通版本,不带任何前缀,返回普通迭代器(常用);
    带r版本返回反向迭代器;
    带c版本返回const迭代器; (用以支持auto)

    list<string> a = {"Milton", "Sharkspeare", "Austen"};
    auto it1 = a.begin(); // list<string>::iterator
    auto it2 = a.rbegin(); // list<string>::reverse_iterator
    auto it3 = a.cbegin(); // list<string>::const_iterator
    auto it4 = a.crbegin(); // list<string>::const_reverse_iterator
    

    注意:当不需要写访问时,应该使用cbegin和cend。

    9.2.4 容器定义和初始化

    除array外,其他容器的默认构造函数都会创建一个指定类型的空容器,且都可以接受指定容器大小和元素初始值的参数。
    两个容器的元素类型必须相同或者可以类型转换,才能用一个容器初始化另外一个。

    C c; // 默认构造函数。如果C是array,则c中元素按默认方式初始化;否则c为空
    C c1(c2); // c1初始化为c2的拷贝。c1和c2必须是相同类型(容器类型相同 and 元素类型相同)。如果C是array,c1和c2大小必须相同
    C c1 = c2; // <=> C c1(c2)
    
    C c{a,b,c...}; // c初始化为初始化列表中元素的拷贝。列表元素类型必须与c的元素类型相同
    C c = {a,b,c...}; // <=> C c{a,b,c...}
    
    C c(b,e); // c初始化为迭代器b和e指定范围中的元素的拷贝。范围中元素的类型必须与C的元素类型相容(不适用于array)。要拷贝的元素包括b指向的元素,但不包括e指向的元素。
    
    // 只有顺序容器(除array外,因为array有专门指定大小的方法)的构造函数才能接受大小参数
    C seq(n); // seq包含n个元素,对这些元素进行值初始化;此构造函数是explicit的。(不适用于string)
    C seq(n,t); // seq包含n个初始化为值t的元素
    

    将一个容器初始化为另一个容器的拷贝
    2种方法:

    1. 直接拷贝整个容器;
    2. 拷贝由迭代器对指定的元素范围(array除外);

    第1种方法,要求2个容器类型 + 元素类型必须匹配。第2种方法,不要钱容器类型相同,元素类型也不一定相同,但是要能将元素转换为要初始化的容器的元素类型。

    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); // 错误:容器类型不匹配
    
    forward_list<string> words(articles.begin(), articles.end()); // 正确:参数是迭代器对,元素类型可以转换为要初始化的类型即可。const char* 可以转换为string
    // 注意:要拷贝的元素包括articles.begin()指向的元素,但不包括articles.end()指向的元素
    

    列表初始化

    • 列表初始化指定容器每个元素值
    • 指示了容器大小
    list<string> authors = {"Milton", "Shakespeare", "Austen"};
    vector<const char*> articles = {"a", "an", "the"};
    

    与容器大小相关的构造函数
    除上面提到与关联容器相同的构造函数外,顺序容器(除array外)还提供另一个构造函数,指定容器大小和初值(可省略,前提是元素类型有默认的初始化方式或者默认构造函数)。

    vector<int> ivec(10, -1); // 10个int元素,每个初值-1
    list<string> svec(10, "hi!");  // 10个string元素,每个初值"hi!"
    forward_list<int> ivec(10); // 10个int元素,每个初值0
    deque<string> svec(10);  // 10个string元素,每个初值为空
    

    标准库array具有固定大小
    大小是array类型的一部分,所以array不支持普通的容器构造函数,这些构造函数都会确定容器的大小,或隐式或显式。注意区分与其他顺序容器,在指定容器大小方式上的区别。

    // array类型
    array<int, 42> // 类型为:保存42个int的数组
    array<string, 10> // 类型为:保存10个string的数组
    
    // 使用array类型,需要同时指定元素类型和数组大小
    array<int, 10>::size_type i; // 数组类型要包括元素类型和大小
    array<int>::size_type j;; / 错误:因为array<int>不是一个类型
    
    // 定义array对象
    array<int, 10> ia1; // 10个默认初始化(为0)的int
    array<int, 10> ia2 = {0,1,2,3,4,5,6,7,8}; // 列表初始化
    array<int, 10> ia3 = {42}; // ia3[0] 为42,剩余元素为0
    

    内置数组与标准库容器数组array
    不能对内置数组进行拷贝或对象赋值,但array均可以

    int digs[10] = {0,1,2,3,4,5,6,7,8};
    int cpy[10] = digs; // 错误:内置数组不能拷贝或赋值
    
    array<int, 10> digitis = {0,1,2,3,4,5,6,7,8};
    array<int, 10> copy = digits; // 正确:array类型匹配,即可进行拷贝
    

    9.2.5 赋值和swap

    除array外,容器允许直接拷贝或赋值。array对象间赋值,如果2个array大小不相等,赋值后,= 左边array大小变为右边array大小。

    c1 = c2; // c1, c2是除array外的容器对象
    c1 = {a,b,c};  // c1是除array外的容器对象
    
    array<int, 10> a1 = {0,1,2,3,4,5,6,7,8};
    array<int, 10> a2 = {0}; // 10个元素均为0
    a1 = a2; // 正确:用a2元素替换a1元素,拷贝后a1大小由10变为1
    a2 = {0}; // 错误,不能将花括号列表赋值给array , 只有在初始化时才可以
    

    容器赋值运算

    c1 = c2;
    c = {a,b,c,...}; // c不能是array
    swap(c1, c2); // 交换c1和c2的元素。 要求c1, c2具有相同的类型。swap通常比拷贝元素(c1 = c2)快
    
    // assign操作不适用于关联容器和array
    seq.assign(b, e); // 将seq元素替换为迭代器b和e所表示的范围中的元素。迭代器b和e不能指向seq的元素
    seq.assign(il); // 将seq元素替换为初始化列表il中的元素
    seq.assign(n, t); // 将seq中的元素替换为n个值为t的元素
    

    注意:赋值运算会导致指向左边容器内部迭代器、引用和指针失效。而swap操作将容器内容交换,不会导致指向容器的迭代器、引用和指针失效(除容器类型为array和string外)

    使用assign(仅顺序容器)
    assign要求容器的元素类型相容即可,类似于用一个容器的迭代器对初始化另外一个容器。
    assign第二个版本,接受一个整型值和一个元素值。用指定数目的相同元素值,替换容器中原有元素。

    // assign支持容器类型不匹配,但是元素类型相容的赋值
    list<string> name;
    vector<const char*> oldstyle;
    names = oldstyle; // 错误:容器类型不匹配,不能直接拷贝
    
    names.assign(oldstyle.begin(), oldstyle.end); // 正确:可以将const char* 转换为string,也就是说元素类型相容
    
    // assign支持用指定数目元素值,替换容器内容
    list<string> slist1(1); // 1个元素,空string
    slist1.assign(10, "Hiya!"); // 10个元素,每个都是"Hiya!"
    // <=> 等价于下面的语句
    slist1.clear();
    slist.insert(slist1.begin(), 10, "Hiya!");
    

    使用swap
    交换2个相同容器类型的元素。除array外,swap不对任何元素进行拷贝、删除或插入,可以保证在常数时间内完成。个人理解,可以简单成,交换的只是2个容器的指针/引用,没有发生元素交换。
    而对array,swap操作会发生真正的元素交换,交换时间与array元素个数成正比。

    // 交换svec1和svec2,交换后svec1大小为24,svec2大小为10
    vector<string> svec1(10); // 10个元素的vector
    vector<string> svec2(24); // 24个元素的vector
    swap(svec1, svec2);
    

    9.2.6 容器大小操作

    1. size(), 返回容器元素数目,除forward_list外的容器都支持;
    2. empty(), size为0是返回true,否则返回false;
    3. max_size(), 容器所能容纳的最大元素的值;

    9.2.7 关系运算符

    按顺序逐个比较元素。
    == 所有元素都相等(容器大小相同)
    < 能比较的元素都相等,但左边容器小于右边;或者,左边不等于右边的第一个元素,小于右边对应位置元素

    类同"<"

    注意:容器的关系运算,需要元素支持对应的运算符

    vector<int> v1 = {1,3,5,7,9};
    vector<int> v2 = {1,3,9};
    vector<int> v3 = {1,3,5,7};
    vector<int> v4 = {1,3,5,7,9};
    
    v1 < v2 // true: 因为v1[2] = 5 < v[2] = 9 
    v1 < v3 // false: 所有元素都相等,但v3元素个数更少
    v1 == v4 // true: 所有元素都相等,且元素个数相等
    v1 == v2 // false: 元素数目不一样,肯定不相等
    

    9.3 顺序容器操作

    9.3.1 向顺序容器添加元素

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

    // 操作会改变容器大小;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_back(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
    

    使用push_back
    除array和forward_list外,其他顺序容器都支持push_back,将元素插入到容器末尾。
    array和forward_list不支持push_back,因为array是固定大小,而forward_list是单向链表,插入元素到末尾需要遍历整个链表。

    注意:通过push_back插入到容器的元素是拷贝,而不是对象本身。也就是说,对容器内容的修改,不会影响用来初始化容器元素的对象。

    例,在vector末尾追加string

    vector<string> container;
    string word;
    while(cin >> word) {
      container.push_back(word);
    }
    

    push_front
    push_front将元素插入到容器开头,用法类似于push_back。
    vector不支持push_front;list, forward_list, deque支持push_front

    在容器特定位置添加元素
    insert提供一般的添加功能,可以指定在容器中插入位置。
    vector, deque, list, string都支持insert。forward_list提供特殊版本的insert

    vector<string> svec;
    list<string> slist;
    
    slist.insert(slist.begin(), "Hello!"); // 将元素插入到指定位置之前 <=> slist.push_front("Hello!");
    
    // vector不支持push_front,但可以insert到begin()
    svec.insert(svec.begin(), "Hello!"); // 插入到vector末尾以外位置,速度可能很慢
    

    插入范围内元素
    注意:要插入的范围不能是容器自身迭代器范围

    // 1. 插入多个相同元素
    svec.insert(svec.end(), 10, "Anna"); // 将10个string元素"Anna"插入到svec末尾
    
    vector<string> v = {"quasi", "simba", "frollo", "scar"}; 
    // 2. 插入另外一个容器迭代器对指定范围元素
    slist.insert(slist.begin(), v.end() - 2, v.end()); // 将容器v最后2个元素插入到slist开头
    // 3. 插入元素列表
    slist.insert(slist.end(), {"these", "words", "will", "go", "at", "the", "end"}); // 将花括号括起来的元素列表插入到slist末尾
    

    使用insert返回值
    insert返回指向第一个新加入元素的迭代器。

    list<string> lst;
    
    auto iter = lst.begin();
    while (cin >> word) {
      iter = lst.insert(iter, word); // <=> lst.push_front(word);
    }
    

    使用emplace操作
    emplace_front, emplace, emplace_back用于构造而非拷贝元素,插入元素位置分别与push_front, insert, push_back相同。
    何为构造元素?
    传入emplace的参数,会被直接当作容器元素的构造参数,用来构造新元素。

    class Sales_data{
    public:
      Sales_data(string &id, double price) {
        this->id = id;
        this->price = price;
      }
      ~Sales_data();
    
    private:
      string id;
      double price;
    }
    
    int main() {
      vector<Sales_data> vec;
      vec.push_back(new Sales_data("123-456789", 12.5)); 
      vec.emplace_back("123-456888", 15.6); // 等价于上面的push_back语句
    
      return 0;
    }
    

    9.3.2 访问元素

    注意front, back和begin, end的区别,前者得到的是容器元素的首元素和末尾元素,要求容器不能为空;后者得到的是容器的迭代器,begin指向首元素位置,end指向最后一个元素的下一个位置。

    // 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异常
    

    安全访问的随机访问
    访问容器元素时,一定要先确保存在,否则可能导致未定义行为。
    如下面的例子,

    vector<string> svec; // 空vector
    cout << svec[0]; // 运行时错误:svec为空,没有元素
    cout << svec.at(0); // 下标越界,抛出out_of_range异常
    

    9.3.3 删除元素

    顺序容器 的删除操作。
    注意:删除元素的成员函数并不检查参数,删除元素之前,程序员必须确保它们(待删除元素)存在。

    // 操作会改变容器大小,不适用于array
    // forward_list有特殊版本erase
    // forward_list不支持pop_back; vector, string不支持pop_front
    c.pop_back(); // 删除c尾元素。若c为空,函数未定义,返回void
    c.pop_front(); // 删除c首元素。...
    c.erase(p); // 删除迭代器p所指元素,返回删除元素后的元素的迭代器。
    c.clear(); // 删除c所有元素。返回void
    

    pop_front和pop_back成员函数
    pop_front , pop_back分别删除首元素,尾元素。
    vector, string不支持push_front, pop_front。forward_list不支持pop_back。
    不能对一个空容器,执行弹出操作

    while (!ilist.empty()) {
      process(ilist.front()); // 对ilist首元素进行自定义处理
      ilist.pop_front(); // 删除首元素
    }
    

    从容器内部删除一个元素
    erase从容器指定位置或位置范围删除元素。用法类似于insert。

    // 1. 删除指定位置元素
    list<int> lst = {0,1,2,3,4,5,6};
    auto it = lst.begin();
    while(it != lst.end()) {
      if (*it % 2) {
        it = lst.erase(it); // 删除指定位置元素,返回删除元素下一位置
      }
      else ++it;
    }
    
    // 2. 删除指定范围元素
    elem1 = lst.erase(lst.begin(), lst.end()); // 调用后elem1 == lst.end()
    
    // 3. 删除所有元素
    lst.clear();
    

    9.3.4 特殊的forward_list操作

    lst.before_begin(); // 返回指向链表首元素之前不存在的元素的迭代器。该迭代器不能解引用
    lst.cbefore_begin(); // 同before_begin,不过得到的是const_iterator
    
    // 注意:插入元素的迭代器,不能是待插入容器的迭代器
    lst.insert_after(p, t); // 在迭代器p之后的位置插入元素。t是一个对象,n是数量,b, e是表示范围的一对迭代器
    lst.insert_after(p, n, t); // p之后,插入n个重复的t
    lst.insert_after(p, b, e); // p之后插入迭代器对[b, e)之间的元素
    lst.insert_after(p, il); // il是花括号列表
    
    emplace_after(p, args); // p之后用args构造的对象元素
    
    lst.erase_after(p); // 删除p指向的位置之后的元素
    lst.erase_after(b, e); // 删除迭代器对(b, e)的元素,返回被删除元素之后后的元素的迭代器
    

    9.3.5 改变容器大小

    resize用来增大或缩小容器。array不支持resize
    如果当前size < 增大后的size,则新元素添加到容器后部;
    如果当前size > 增大后的siez,则容器后部元素删除;

    list<int> ilist(10, 42); // 10个int,值都是42
    ilist.resize(15);  // 增加5个int到ilist末尾,值为0
    ilist.resize(25, -1);  // 增加10个int到ilist末尾,值-1
    ilist.resize(5); // 减少到5个int,也就是删除末尾20个元素
    

    9.4 vector操作是如何增长的

    原则:只有迫不得已的时候,才可以分配新的内存空间。
    每个vector实现可能不一样,不过,都是在分配新内存时,分配足够内存空间用于存储,而不是一个一个元素增加存储空间。

    管理容量的成员函数

    // 容器大小管理操作
    // shrink_to_fit只适用于vector/string和deque
    // capacity和reserve只适用于vector和string
    c.shrink_to_fit(); // 将capacity()减少为与size()相同大小
    c.capacity(); // 不重新分配内存空间的话,c可以保存多少元素
    c.reserve(n); // 分配至少能容纳n个元素的内存空间
    

    capacity和size
    size, 容器已经保存的元素的数目;
    capacity, 在不分配新的内存空间的前提下它最多可以保存多少元素;

    vector<int> ivec;
    cout << "ivec: size: " << ivec.size()
    		<< " capacity: " << ivec.capacity() << endl;
    
    for (vector<int>::size_type ix = 0; ix != 24; ++ix) {
    	ivec.push_back(ix);
    }
    
    // size为24,capacity应该 >= 24,具体是多少取决于具体的标准库实现
    cout << "ivec: size: " << ivec.size()
    			<< " capacity: " << ivec.capacity() << endl;
    
    ivec.reserve(50); // 会让capacity至少为50
    

    输出

    ivec: size: 0 capacity: 0
    ivec: size: 24 capacity: 32
    

    9.5 额外的string操作

    // 除了字符串字面量构造string外,构造string的其他方法
    // n, len2, pos2都是无符号值
    string s(cp, n); // s是cp指向的数组中前n个字符的拷贝,此数组至少应该包含n个字符
    string s(s2, pos2); // s是string s2从下标pos2开始的字符的拷贝。若pos2 > s2.size(),该构造函数行为未定义
    sring s(s2, pos2, len2); // s是string s2从下标pos2开始len2个字符的拷贝。...
    

    substr操作
    求子字符串。
    如果开始位置超过string大小,抛出out_of_range异常。

    string s("hello world");    // 11个元素(字符)
    string s2 = s.substr(0, 5); // s2 = hello = s[0..5), s[0]开始5个字符
    string s3 = s.substr(6);    // s3 = world = s[6..end)
    string s4 = s.substr(6, 11); // s4 = world = s[6..end), s[0]开始11个字符
    string s5 = s.substr(12);   // 12 > 11,抛出out_of_range异常
    

    9.5.2 改变string的其他方法

    string支持顺序容器的赋值运算,assign, insert和erase操作。
    其中,insert, erase除了接受迭代器参数的版本,还有接受下标的版本。insert, assign还有接受C风格字符串版本。

    // 接受下标版本insert,erase
    s.insert(s.size(), 5, '!'); // 在s末尾插入5个感叹号
    s.erase(s.size()-5, 5); // 从s删除末尾5个字符
    
    // 接受C风格字符串版本insert, assign
    const char *cp = "Stately, plump Buck";
    s.assign(cp, 7); // s == "Stately"
    s.insert(s.size(), cp + 7); // s == "Stately, plump Buck", s末尾插入cp[7]到cp末尾字符()
    

    append和replace函数
    append,在string末尾追加新元素;
    replace,在指定位置删除指定长度的元素;
    assign,替换string的所有内容;

    // append
    string s("C++ Primer"), s2 = 5;
    s.append(" 4th Ed."); 
    // <=> 
    s.insert(s.size(), " 4th Ed."); // s == "C++ Primer 4th Ed."
    
    // replace
    s2.replace(11, 3, "5th"); // 将s2[11]开始3个字符替换为"5th"
    // <=>
    s2.erase(11, 3);
    s2.insert(11, "5th"); // s == "C++ Primer 5th Ed."
    
    // 替换的文本内容不一定跟删除的文本内容一样长
    s2.replace(11, 3, "Fifth"); // s == "C++ Primer Fifth Ed."
    

    9.5.3 string搜索操作

    搜索成功返回string::size_type值,表示匹配发生位置的下标。搜索失败,则返回名为string::npos的static成员(-1)。
    注意:string位置下标用size_type表示,用int表示不是很适合。

    string name("AnnaBelle");
    auto pos1 = name.find("Anna"); // pos1 == 0
    
    // 查找失败,返回npos
    string lowercase("annabelle");
    pos1 = lowercase.find("Anna"); // pos1 == npos
    
    // 查找给定字符串中任何一个字符匹配的位置
    string numbers("0123456789"), name("r2d2");
    auto pos = name.find_first_of(numbers); // 在name中,查找numbers任意一字符出现在name中的第一个位置
    
    string dept("03714p3");
    auto pos = dept.find_first_not_of(numbers); // dept中查找第一个不在numbers中的字符的位置
    

    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_frist_not_of(args); // 在s中查找第一个不在args中的字符
    s.find_last_not_of(args);  // 在s中查找最后一个不在args中的字符
    
    args 必须是以下形式之一
    c, pos // 从s中位置pos开始查找字符c。pos默认为0
    s2, pos // ...查找字符串s2.pos默认为0
    cp, pos // ...查找指针cp指向的以空字符结尾的C风格字符串。pos默认0
    cp, pos, n // ...查找cp指向的数组的前n个字符。pos和n无默认值
    

    9.5.4 compare函数

    类似于C标准库strcmp函数,用来比较当前string与指定string对象的大小。可以指定字符串的起始位置、字符数。
    也支持string与C风格字符串进行比较。

    string s1, s2;
    s1.compare(s2);
    s1.compare(pos1, n1, s2); // 比较s[pos1..pos1 + n1 - 1]与s2
    s1.compare(pos1, n1, s2, pos2, n2); // 比较s[pos1..pos1 + n1 - 1]与s2[pos2..pos2 + n2 - 1]
    

    数值转换

    类似于Java中的Integer.parseInt("123"),C++也提供了将string转换成数值类型(int/double等)的库函数,也提供了数值转换为string转换函数。
    to_string, 数值 => string
    stod,string => double
    stoi,string => int

    应用示例,

    int i = 42;
    string s = to_string(i); // 将整数i转换为字符表示形式
    double d = stod(s); // 将字符串s转换为浮点数
    

    *** string和数值之间的转换***

    to_string(val); // 任意算术类型(int/short/long/double/float) => string
    /* p是size_t指针,用来保存s中第一个非数值字符的下标,默认为0,即函数不报存下标。b表示转换所用的基数,默认10 */
    stoi(s, p, b);  // string => int
    stol(s, p, b); // string => long
    stoul(s, p, b); // string => unsigned long
    stoll(s, p, b); // string => long long
    stoull(s, p, b); // string => unsigned long long
    stof(s, p); // string => float
    stod(s, p); // string => double
    stold(s, p); // string => long double
    

    9.6 容器适配器

    标准库定义了3个顺序容器适配器:stack, queue, priority_queue。
    适配器(adaptor)是标准库一个通用概念,容器、迭代器和函数都有迭代器。

    // 所有容器适配器都支持的操作和类型
    size_type 一种类型,足以保存当前类型的最大对象的大小
    value_type 元素类型
    container_type 实现适配器的底层容器类型
    A a; 创建一个名为a的空适配器
    A a(c); 创建名为a适配器,带有容器c的拷贝
    关系运算符==, !=, <, <=, >, >= 每个适配器都支持的
    a.empty(); 判断容器是否为空
    a.size(); 返回a中元素数目
    swap(a, b); 交换a和b的内容,a,b必须具有相同类型,底层容器类型也必须相同
    a.swap(b); 
    

    定义一个适配器
    对于适配器能建造在哪些容器之上,是有规定的。不过,所有的适配器都要求容器有添加和删除元素、访问尾元素的能力,因此适配器不能建造在array和forward_list上。
    默认情况下,stack和queue是基于deque实现的,priority_queue是在vector基础上实现的。
    stack可以构造于除array和forward_list之外的任何容器类型;
    queue可以构造于list/deque之上;
    deque可以构造于vector/deque之上,但不能基于list;

    // 定义一个stack
    deque<int> deq; // deq是一个deque(双端队列)
    stack<int> stk(deq); // 从deq拷贝元素到stk
    
    // 在vector基础上实现空栈
    stack<string, vector<string>> str_stk;
    // str_stk2在vector基础上构造,初始化时保存svec的拷贝
    stack<string, vector<string>> str_stk2(svec); // svec是vector<string> 容器类型对象
    
    // 栈适配器的用法
    stack<int> intStack; 
    // 填满栈
    for (size_t ix = 0; ix != 10; ++ix)
      intStack.push(ix);
    while (!intStack.empty()) {
      int value = intStack.top();
      // 使用栈顶值的代码
      intStack.pop(); // 弹出栈顶值
    }
    

    栈操作

    // 栈默认基于deque,也可以构建在list/vector之上
    s.pop(); //注意跟vector区别,没有_back, _front后缀
    s.push(item); 
    s.emplace(args); 
    s.top(); 
    

    队列操作

    // 队列默认基于deque,也可以基于list/vector
    // priority_queue默认基于vector,也可以基于deque
    q.pop();  // 返回queue首元素,或priority_queue最高优先级元素
    q.front();
    q.back(); // 只适用于queue
    q.top();  // 返回最高优先级元素,不删除。只适用于priority_queue
    q.push(item);
    q.emplace(args); 
    

    小结

  • 相关阅读:
    通过URL地址将图片保存到本地
    Sql批量提交方法
    MySql 批量提交方法
    【Sqlserver】查看所有数据库的大小 创建日期 名称 版本级别 状态
    【asp.net】滑块验证码(分享一个从github上下载的源码)
    【winform】 WeifenLuo.WinFormsUI.Docking.dll 组件学习
    【C#】初次使用webapi的体会
    【C#】 封装的异步HttpRequest
    Android实现双时间选择器
    Android的金额3位数加逗号分隔
  • 原文地址:https://www.cnblogs.com/fortunely/p/14445949.html
Copyright © 2020-2023  润新知