IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++_Primer_学习笔记_第九章(顺序容器) -> 正文阅读

[C++知识库]C++_Primer_学习笔记_第九章(顺序容器)

第九章(顺序容器)

本章要点

  • 顺序容器,元素在顺序容器中的位置和其加入容器时的顺序相互一致。
  • 关联容器(分为有序和无序),关联容器中元素的位置由元素相互关联的关键字值决定。后序介绍特有操作
  • 所有的容器都享有公共的接口,不同的容器按不同的方式对它进行扩展。公共接口使得,我们基于某种容器学习的内容也适用于其他的容器。每一种容器,都提供了不同的性能和功能的权衡
  • 容器是什么?容器就是一些特定类型对象的集合。
  • 三中容器适配器,分别为容器操作定义了不同的接口,来与容器的类型适配。

/1.顺序容器概述

1).顺序容器在以下方面中都有不同的性能折中:

  • 容器添加或者删除元素的代价
  • 非顺序访问容器元素的代价
顺序容器类型性能描述
vector可变大小(顺序栈)。支持随机访问。在尾部之外的位置插入或者删除元素可能很慢
deque双向队列(顺序队列)。支持随机访问。在头尾位置插入或者删除速度很快
list双向链表。只支持双向顺序访问。在任何位置插入或者删除元素都很快。
forward_list单向链表。只支持单向顺序访问。在链表的任何位置进行插入或者删除都很快
array固定大小的数组。支持随机访问。不能添加或者删除元素(只能覆盖)
string与vector相似的容器。但是专门保存字符。在尾部插入或者删除很快

2).分析。

  • 除了array是固定大小之外,其他容器都提供了高效,灵活的内存管理。可以添加删除,扩张,收缩容器。
  • 容器对元素的存储策略对容器操作的效率有着固有,重大的影响。以及决定着容器是否支持某一种操作。
容器类型存储方式及其影响
string,vector连续的内存空间。……当添加一个元素,有时候需要分配额外的空间,此时,每一个元素都需要移动到新的存储空间中去。
list,forward_list容器的额外内存开销很大。指针域,头尾指针。
deque其实也是顺序存储的,但是大小可变,实现可能比较困难。

3).注意,forward_listarray是新增的类型。

  • 与内置数组相比较,array是一种更加安全,容易使用的数组类型。
  • forward_list的设计目标是达到和最好的手写单项链表数据结构一样的性能。所以它不支持size。原因就是保存大小会有额外的开销。而对于其他的容器size是一个常量级别的操作。
  • 新标准库的容器性能几乎肯定与精心优化过的同类数据数据结构一样好(同常更好)。现代c++程序应该使用的是标准库容器,而不是原始的数据结构。例如内置数组等。

4).如何选择合适的容器。

  1. 一般使用vector,除非你有更好的理由选择其他的容器。
  2. 程序中有很多小的元素,而且空间的开销很重偶要。不要使用list,或者forward_list
  3. 要求随机访问元素。vector或者deque
  4. 要在中间插入或者删除元素,list,或者forward_list
  5. 只在头尾插入,deque
  6. 如果程序只有在读取数据时才需要在容器中间位置进行插入元素。而随后需要随机访问元素。那么,确定是否需要在中间插入,如果只是排序,直接插入,然后sort()可以避免中间插入。如果确实需要,那么输入时使用list,输入完成再将内容拷贝到vector中。
  7. 如果既需要随机访问,又需要中间插入,取决于哪一种操作占主导地位。需要,进行测试。(forward_list,list,vector,deque
  8. 如果是在不确定使用什么容器,程序中只使用vectorlist的公共操作:
  • 使用迭代器。
  • 不使用下标操作。
  • 避免随机访问。

练习,9.1

  • vectorsort,实现插入排序,等价于list
  • 快速排序频繁地访问元素,需要随机访问比较合理。使用vector

/2.容器库概述

1).容器的操作,

  • 所有容器均适用(本小节介绍的)
  • 仅仅适用于顺序容器(本章接下来小节介绍的),或者关联容器,或者无序容器
  • 只适用于一小部分容器

2).一般来说,一个容器都定义在一个头文件中,文件名称和类型名相同。容器均为模板类,意味着需要提供额外的信息(string??)。对于大多数容器,需要提供额外的元素类型信息list<Sales_data>,deque<double>
3).顺序容器几乎可以保存保存任意类型的数据。但是一些容器操作对元素的数据类型有特殊的要求。例如,一些类如果没有默认构造函数,那么我们不能用只传递一个容器大小的实参来实现构造这个容器(容器的构造函数)。
4).注意,一些容器的操作还对元素类型有限制,以下是部分的容器操作。

类型别名含义
iterator容器类的迭代器类型
const_iterator可以读取元素,但是不能修改元素的迭代器类型
size_type无符号整数类型,容器的最大容量都可以包含在内
different_type带符号的整数类型,足够保存任意两个迭代器之间的距离
value_type元素类型
reference元素的左值类型,和value_type&含义是一样的
const_reference元素的左值类型,(即const value_type&
构造函数含义
C c;默认构造函数,构造空的容器c
C c1 (c2)构造c2的拷贝c1
C c(b,e)构造c,将迭代器中b(begin)e(end)指定的的范围内的元素拷贝到c中,array不支持)
C c{a,b,c……}列表初始化容器
赋值和swap含义
c1 = c2c1中的元素替换为c2中的元素
c1 = {a,b,c,d……}不适用于array
a.swap(b)交换,容器中的内容
swap(a,b)与上一致
大小含义
c.size()forward_list不支持
c.max_size()c中可以保存的最大元素数目
c.empty
添加或者删除元素(不支持array含义(在不同容器中,这些操作的接口都不同)
c.insert(args)插入
c.emplace(inits)使用inits构造一个c中的元素
c.erase(args)删除args中指定的元素
c.clear()删除c中的所有元素,返回void
关系运算符相关信息
==,!=所有的容器都支持
<,<=,>,>=无序关联容器不支持
获取迭代器相关信息
c.begin(),c.end()
c.cbegin(),c.cend()返回的是const_iterator
反向容器的额外成员(forward_list不支持)描述
reverse_iterator按逆序寻址元素的迭代器
const_reverse_iterator不可修改元素的迭代器
c.rbegin(),c.rend()
c.crbegin(),c.crend()返回的是const_reverse_iterator

//1.迭代器

1).与容器一样,迭代器有着公共的接口,如果一个迭代器提供某一个操作,那么所有提供相同操作的迭代器对这个操作的实现方式都是一样的。例如,所有迭代器都允许访问容器中的元素,并且都是通过解引用运算符号实现的。(或者说运算符号的含义都是一样的,前提是有这个操作)
2).所有容器支持的操作。

操作含义
*iter
iter->men
++iter
–iter对于forward_list不适用
iter1 == iter2
iter1 != iter2

3).只能用于,string,vector,deque,array(原因就是因为它们内存是连续的。)

算术运算含义
iter + nn为整数,并且,iter是指向容器中的元素或者是尾后,否则是无效的
iter-n
iter += n
iter1 -= n
iter1 - iter2
<,<=,=>,>注意,上面的表格没有指出

4).迭代器的范围,两个迭代器之前的范围。代表两个迭代器左闭右开之间的元素。(编译器不会强制左边小于等于右边的要求,但是这是责任)
5).要求左闭右开是因为可安全地解引用。

//2.容器的类型成员

1).表中已经指出。强调几个

类型别名作用
value_type返回的是容器中的元素类型
reference/const_reference得到的是容器元素的引用/常量引用

2).注意,使用时一定要记得前面带上类名。

{
    list<string>::iterator iter;
    vector<int>::difference count;
}

练习9.8

{
    list<string>::value_type value;
    list<string>::reference value;
}

//3.begin和end函数成员

1).列表初始化,list<string> a = {"hello","world"};;善用auto it1 = a.begin();
2).注意,

  • 不以c开头的函数(begin,rbegin,end,rend,)都是被重载的。当非常量对象时,返回的是iterator,当时常量是,返回的是const_iterator
  • c开头的返回的是const_iterator,即便是使用auto
  • 其他使用auto,返回的是否是const依赖于容器的类型。
  • 只读不写,使用cbegin,cend

练习,注意const vector<int>和vector<int> const的区别,前者是vectorconst,不能修改vector;后者是vector里面的成员是const,而vector的大小是可以改变的。

//4.容器定义和初始化

1).初始化的方式。

方式结果
C c;默认构造函数,如果c是一个array那么执行默认的初始化,否则c是一个空的容器
C c1(c2);C c1 = c2;c1初始化为c2的拷贝。c1c2必须是相同的类型,(对于array,它们的大小还必须一样)
C c{a,b,c,……};C c = {a,b,c};类型相容即可。对于array,列表中的元素数量必须小于或者等于array的大小。不择的按值初始化
C c(b,e);c初始化为b和e两个迭代器之间的元素。类型要相容即可。array不适用
只有顺序容器不包括array的构造函数才可以接受大小参数(即关联容器不适用)
C seq(n)进行值初始化,构造函数是explicit,当元素类型是内置类型或者是有默认构造函数的类类型是才可以这样做,如果类类型没有默认构造函数,切记必须显式地指定元素初始值。
C seq(n,t)

2).注意。容器类型和容器元素类型。

  • 拷贝时,容器类型和元素类型都要求可以匹配必须相同)。
  • 使用迭代器范围时,容器类型没有要求相同。元素类型只要可以匹配就可以。
{
    list<string> a = {"hello","world"};
    vector<const char*> b = {"ni","hao"};
    list<string> c(a);//正确
    vector<string> d(a);//错误,一个是链表,一个是顺序表

    vector<string> e(b);//错误,元素类型不匹配

    forward_list<string> f(b.begin(),b.end());//正确。注意这里是迭代器范围,左闭右开
}

4).array的大小也是类型的一部分。当定义一个array时,要包括元素的类型,以及大小

{
    //array不支持普通的构造函数。它的构造函数
    //都会确定容器的大小,显式或者隐式
    //需要用户指定大小,最好情况下也是容易出错的。
    array<int,42>
    array<string,12>

    array<int,42>::size_type s;//正确
    array<int>::size_type s1;//错误,不是一个类型
}

5).与内置数组的比较,

  • 都需要显式地指定大小,这容易出错。
  • 内置数组不支持拷贝,但是array支持
{
    int a[10];
    int b([10] = a;//错误

    array<int,10> a;
    array<int,10> b = a;//正确,类型一样,注意大小也是类型的一部分
}

练习:9.11

  • 如果容器为空,暂未分配空间
  • 迭代器范围(迭代器是const还是普通的都没有区别,只是拷贝而已)和列表初始化,都只要求元素的类型是相容的
  • 而拷贝初始化,必须(容器和元素类型)都是一样的。

//5.赋值和swap

1).赋值方式

赋值方式相关描述
c1 = c2c1,c2必须有一样的类型,注意到array的类型包括大小。
c = {a,b,c……}array不适用,因为大小不一定一样。大小也是类型的一部分。
swap(c1,c2);c1.swap(c2)c1和c2必须有相同的类型,注意容器的类型的含义,元素类型和容器类型甚至还有容器大小。swap通常比拷贝快得多。
assign操作不适用于array和关联容器,原因同上。
seq.assign(b,e)将seq中的元素替换为迭代器b和e所表示范围的元素。注意迭代器不能指向seq
seq.assign(il)将初始化列表li中的值替换seq
seq.assign(n,t)换为n个t。

2).注意,

  • assign只要元素类型是相容的就可以。
  • 因为直接赋值要求类型(容器和元素,甚至大小(array))是一样的。
  • 一样的由于是拷贝而已,assign的迭代器是否是const没有什么区别。
  • 以下是等价的。
{
    a.clear();
    a.insert(a.begin(),10,"hi");

    list<string> a(1);//只有一个空的string元素
    a.assign(10,"hi");
}

  • 除了array外,交换容器的内容操作保证很快,元素本身不动(不对任何元素进行拷贝,删除或者插入操作。),只是交换两个容器内部的数据结构。例如,对两个vector<string>的容器进行交换,它们的元素并没有交换,指向它们的迭代器,引用和指针在交换之后都不会交换。它们指向的元素也没有改变。而是在交换之后,它们成为另一个vector的组成部分。
  • 但是注意,对于string容器,调用swap会导致迭代器,引用,指针失效。
  • 交换两个array会真正地交换它们的元素,因此所需要的时间与它们的数目成正比。
  • 统一使用非成员版本的swap,是一个好习惯。它在泛型编程中非常地重要。

//6.容器大小的操作

1).一个例外,forward_list仅仅不支持size(其他两个是支持的)。其他的容器都支持以下函数。

  • size
  • empty
  • max_size,返回的是一个大于或者等于该类型容器所能容纳的最大最多)元素的值

//7.关系运算符号

1).关系运算符号,两边的运算对象是必须同容器类型,同元素类型的。
2).比较两个容器的大小,其实是对元素进行逐一比较。这一点和string的比较很一样。
3).容器的比较,实际上就是元素的比较,所以如果我们的元素类型没有定义相应的操作,我们是不可以进行比较的。例如,Sales_data是没有定义==和<的,所以我们如果进行比较,那么就是错误的。

练习,9.16,比较vector<int>和list<int>的大小,

  1. 可以逐一比较,
  2. 也可以先将其中一个转换为另一个的容器类型,再利用==进行判断就可以。

9.17,c1 < c2的要求

  1. c1和c2的类型一定要相同。
  2. 它们的元素类型定义了<

/.3顺序容器操作

1).顺序容器和关联容器的不同在于两者组织元素的方式是不一样的。导致元素的存储,访问,添加以及删除也是不一样的。
2).以下介绍,顺序容器特有的操作。以上介绍的是所有的容器都支持的操作。

//1.向顺序容器中添加元素

1).除了array之外的顺序容器的添加操作。

  • forward_list有自己版本的insert和emplace
  • forward_list不支持push_backemplace_back
  • vectorstring不支持push_frontemplace_front
操作含义
c.push_back(t);/c.emplace_back(args)emplace是需要构造类的必要信息。**使用(,),以逗号间隔就可以。**返回void
c.push_front(t);/c.emplace_front(args)
c.insert(p,t);/c.emplace(p,args))p可以是begin到end中的任何一个位置,在迭代器p之前插入,返回的是指向新添加的元素的迭代器。
c.insert(p,n,t)插入n个t,返回的所有t中最小的一个迭代器,如果n为0,返回的是p
c.insert(p,b,e)迭代器范围的版本,迭代器范围不能指向该容器。返回的是最小的迭代器,方式就是直接将b到e之间搬到p所指向的元素之前(b在前e在后)。如果b=e为0,返回的是p
c.insert(p,li)li是一个{}括起来的值的列表。返回的是最小的迭代器,也是一样的直接将li顺序不变的整体搬进来。与迭代器范围是一样的。列表为空那么返回的是p

2).向一个vector,或者string或者deque插入元素会使得所有的指向容器的迭代器,引用,指针失效。
3).当我们使用这些操作时,一定要记得,不同是容器使用不同的策略来分配空间,而这些策略直接影响性能。就是顺序存储和链表存储的差异。
4).注意以下。

  • 对于string使用word.push_back('a');等价于word += 'a';.
  • 我们利用一个对象(内置类型)对一个容器进行拷贝初始化,或者将对象插入到一个容器中时,都是对象的副本,与传值调用是一样的,容器里面的修改不会,引起对象本身的变化
  • insert是在元素之前插入,所以即便是不支持push_front,也可以使用insert进行操作。
{
    vector<string> svec;
    list<string> slist;

    slist.insert(slist.begin(),"hello");
    slist.push_front("hello");//这两句话是等价的。

    svec.insert(svec.begin(),"hello");//尽管这样可以做到
    //在前面插入,但是这样的性能是很低的。
}
  • 理解insert的返回值。
{
    list<string> slist;
    auto iter = slist.begin();
    while (cin >> word)
        iter = slist.insert(iter,word);
    //以下是等价的
    //slist.push_front(word);
}

  • 理解emplace的与pushinsert的不一样。emplace是构造而不是拷贝元素。
{
    Sales_data a("书本号码",34,2.97);
    //例如,假定元素类型是Sales_data,
    c.emplace_back("书本号码"342.97);
    //等价于
    c.push_back(a);
    c.push_back(Sales_data("书本号码"342.97));
    //一个是直接传递参数给元素类型的构造函数,
    //在容器管理的内存空间中直接创建对象。
    //一个是直接传递构造好的对象,创建一个局部的对象,并将其压入容器当中去。
}
  • 既然是类的构造函数,我们可以根据类的构造函数进行传参。
{
    c.emplace();//使用的是类Sales_data的默认构造函数
    c.emplace(iter,"书本号码");//使用的是Sales_data(string)构造函数
    c.emplace_front("书本号码",34,2.98);//使用的是三个参数的构造函数。
}

练习,

  • 判断奇偶性使用位与运算,最低位为1则是奇数否则就是偶数,因此,num & 1 == true为奇数,num & 1 == false为偶数。
  • 解引用并进行插入就可。
  • 进行插入vector和string和deque的迭代器会失效,所以,一旦进行了修改,迭代器也要相应地进行修改。例如,利用insert的返回值配合增减操作。

//2.访问元素

1).如果容器中没有元素,那么访问的结果将会是未定义的。

  • at和下标操作值适合string,vector,deque,array
  • back不适用于,forward_list
  • 对一个空的容器调用back或者front将会是一个严重的错误,相当于是下标的越界。
操作含义
c.back()返回c的尾元素的引用,如果c是空的,该函数行为将会是未定义的。
c.front()
s[n]返回的下标为n的的元素的引用,n是一个无符号的整数,如果n >= c.s.size(),函数将会返回未定义
s.at(n)如果下标越界就会抛出out_of_range的异常。

2).注意,

  • 对于forward_list迭代器不可以递减。
  • *(--c.end())c.back()效果是一样的
  • *(c.begin())c.front()效果也是一样的
  • 注意使用back或者front或者对迭代器解引用前,对容器是否为空进行检查。
  • 返回的都是引用。如果容器是const的那么返回的是const的引用。
  • 如果我们希望得到的是引用,而不是拷贝。需要记住加上&
{
    if (!c.empty())
    {
        c.back() = 42;
        auto &i = c.back();
        i = 1024;
        auto k = c.back();
        k = 1000;
        //最终c.back()的值为1024;
    }
}
  • 下标运算符号并不会进行下标是否合法的检查。

练习9.24,注意,如果是at越界,那么程序会异常退出的,因为我们没有设计try处理异常。

//3.删除元素

1).删除操作表。

  • array不适用。
  • forward_list有特殊版本的erase
  • forward_list不支持pop_backvector和string不支持以及pop_front
操作相关描述
c.pop_back()返回void,如果c为空函数的行为将会是未定义的。
c.pop_front()
c.erase§删除p所指向的元素,返回被删除元素之后的迭代器的位置(这样总是合法的),如果p是尾后迭代器函数的行为将会是未定义的
c.erase(b,e)迭代器范围,左闭右开。两个迭代器相等,或者都指向尾后迭代器也是合法的。返回的是e
c.clear()删除所有的元素,返回的是void
  • 删除deque中除了首位位置之外的元素都会使得迭代器,引用,指针失效
  • 指向vectorstring中的删除点之后的迭代器,引用和指针会失效。
  • 删除元素的成员函数不会检查,参数的合法性,所以程序员一定要保证它们是存在的。

练习,

  • 对于将数组进行进行拷贝到容器的问题,我们可以将指针看成是迭代器,例如我们可以将,arrarr+10作为迭代器范围进行assign

//4.特殊的forward_list操作

1).forward_list有特殊版本的添加和删除操作。它是一个单向的链表,所以进行操作时需要它的前驱,因为它的前驱的后继是需要改变的。但是一个单向链表中获取一个前驱是不可能的。所以需要,因此添加或者删除一个元素我们是通过改变后继这个元素来实现的。(数据结构考题)

{
    //大致是这样的,c语言版本。
    struct link{
        dataType data;
        struct link* next;
    };
    //p为需要删除的元素。
    p->data = p->next->data;
    q = p->next;
    p->next = q->next;
    free(q);
}

2).由于实现单向链表的操作与其他的实现不一样。所以forward_list定义了自己的删除和插入操作。

名称相关描述
lst.before_begin();/lst.cbefore_begin()返回首元素之前的位置的迭代器。不可以解引用,和尾后的迭代器是一个性质的。
lst.insert_after(p,t)在迭代器p之后插入元素。
lst.insert_after(p,n,t)n表示数量
lst.insert_after(p,b,e)迭代器范围,也是不能执行它本身
lst.insert_after(p,il)il是一个花括号的列表。如果p为尾后迭代器,那么是函数行为未定义的,为空则返回p。返回的是指向最后一个插入元素的迭代器。
emplace_after(p,args)如果p是尾后迭代器,那么函数的行为就是未定义的。p之后插入,返回的是新插入的元素的迭代器。
lst.erase_after§删除的是p之后的那个元素,返回的是执行被删元素之后的元素的迭代器,如果不存在这样的元素,返回的是尾后迭代器。如果p指向的是尾元素或者尾后迭代器,这样的函数将会是未定义的。
lst.erase_after(b,e)**b之后一直到e之前的元素。**返回的就是e
{
    forward_list<int> flst = {0,1,2,3,4,5,6};
    auto prev = flst.before_begin();
    auto curr = flst.begin();
    while (curr != flst.end())//flst的end不会变化。
    {
        if(*curr % 2)
            curr = flst.erase(prev);//prev还是指向curr的前一个位置
        else
        {
            prev = curr;
            ++curr;
        }
    }
}

//5.改变容器的大小

1).array不支持resize

操作名称相关描述
c.resize(n)改变c的大小为n。如果n < c.size(),多的元素被丢弃。反之不足的元素进行值初始化
c.resize(n,t)系添加的元素都是t
{
    list<int> ilist(10,42);
    ilist.resize(15);//末尾多了5个为0的元素
    ilist.resize(25,-1);//末尾继续加10个为-1的元素
    ilist。resize(5);//只剩下5个位42的元素
}

2).注意。

  • 如果容器中的类型是类类型,我们要么保证类类型有默认构造函数,或者我们为它提供初始值。
  • 如果resize是缩小容器的大小,那么指向被删除元素的指针,迭代器,引用都会失效
  • 对于vector,string,deque进行resize,可能导致迭代器,指针,引用失效。

//6.容器操作可能会导致迭代器失效

1).添加元素的情况。
|容器类型|插入位置|指针,引用,迭代器的有效性|
|vector或者是string|存储空间被重新分配|全部失效|
|vector或者是string|储存空间没有被重新分配|指向插入位置之前的元素的指针,迭代器,引用仍然有效,但是指向插入位置之后元素的指针,引用,迭代器将会失效。|
|deque|插入到处首尾位置之外的任何位置,|全部失效|
|deque|插入到首位位置|迭代器会失效。但是指向存在元素的指针,引用不会失效。|
|list或者forward_list|任何位置|全部有效,包括尾后和首前|

2).删除元素的情况。
|容器类型|删除位置|指针,引用,迭代器的有效性|
|vector或者是string|任何位置|指向删除位置之前的元素的指针,迭代器,引用仍然有效,但是指向删除位置之后元素的指针,引用,迭代器将会失效。|
|deque|首尾位置之外的任何位置,|全部失效,意味着,位置全部发生变化,具体是如何实现的?|
|deque|首位位置|如果是尾元素,尾后迭代器会失效。但是指向其他元素的迭代器,指针,引用不会失效。如果是首元素,则全部有效(包括尾后)。|
|list或者forward_list|任何位置|全部有效,包括尾后和首前|

3).几点说明。

  • 使用失效的指针,引用,迭代器很可能跟使用未初始化的指针发生一样的后果。
  • 删除deque的尾元素,删除vector和string任何位置的元素,都会导致尾后迭代器失效。
  • 对于list和forward_list,迭代器,指针和引用时刻保证有效。
  • 程序设计时,一定要注意,对于string,vector,deque重新定位迭代器,指针,引用。
  • 删除时,对于被删除的元素,指针,引用,迭代器,必然会失效。因为元素被销毁了。

4).程序设计时要注意的问题。

  • 标准库中end()操作是很快的,因为vector,string以及deque的操作经常会导致尾后迭代器失效。
  • 很多标准库实现上,如果我们使用失效的end来做判断,while (begin != end);将会导致无线的循环。修改为,while (begin != c.end());即可。

练习9.31

  • list,forward_list支持自增自减运算,只是不支持算术运算。
  • 支持大小的比较。

练习9.32

  • 函数实参的求值顺序是不确定的。

/4.vector对象是如何增长的

1).vector的内存分配测略使得,它的扩张操作,通常比listdeque更加地快。
2).vectorstring类型提供一些允许我们和它实现中内存分配部分互动的成员函数。

容器大小管理操作相关描述
shrink_to_fit只适用于vector,string,deque
capacity和reserve只适用于vector,string
c.shrink_to_fit()capacity()缩小为和size()一样的大小
c.capacity()不重新分配内存的话,c可以保存多少的元素
c.reserve(n)分配至少可以容纳n个元素的存储空间

3).几点说明。

  • reserve并不会改变容器中元素的数量,他仅仅影响vector预先分配多大的内存空间。
  • 只有当需要的内存空间超过当前的容量时,reserve调用才会改变vector的容量。
  • 并且,如果需求大小大于当前容量时,reserve至少会分配与需求一样的内存空间(可能会更大)。
  • 如果内存的需求小于等于当前容量时,reserve什么也不做,它并不会退回内存空间
  • 值得注意的时,resize修改的是容器中的元素的数目,而不是容器的容量,更不可能使用resize来缩小容器预留的存储空间。
  • 尽管新标准中,我们可以调用shrink_to_fit成员函数来退回不需要的空间,但是具体的实现可以选择忽略这一请求。调用它,也并不能保证一定退回不需要的空间。

4).关于,capacitysize

  • vectorsize为0,标准库实现的时候,capacity也为0;
  • 当我们添加元素时,size等于元素的数目,capacity至少为size大小。具体大小看标准库的实现方式。
  • 实际上,如果我们没有超过vector的容量,vector就不能重新分配空间while (c.size() != c.capacity());
  • vector的实现采用的是,每一次需要分配新的内存空间时。将当前的容量翻倍

5).每一个vector实现都可以选择自己的内存分配策略,但是必须遵守的是,只有不得已的情况下,才能重新分配空间。

  • 只有在执行insert操作时size才会和capacity相等。
  • 调用resize或者reserve时给定的大小超过当前的capacityvector才可能会重新配分内存空间。额外多少空间,具体看实现方式。
  • 不管是什么策略,在一个为空的vector中连续进行n此的push_back后,所需要的时间不能超过n的常倍。

练习,9.37

  • 对于listsizecapacity总是相等的。
  • 对于arraycapacity是不可变的。

练习,9.40

  • 关于resize是否会改变capacity取决于编译器。

/5.额外的string操作

1).用途。

  • string和c风格字符数组之间的转换
  • 增加了用下标代替迭代器的版本

//1.构造string的其他方法

1).从string对象看。

方式相关信息
string s1;空字符串
string s1(s2)副本,s2可以是c风格字符数组的指针,可以是cons,因为只是拷贝
string s1 = s2副本
string s1(“value”)不包括字面值后面的空字符
string s = “value”同上
string s(n,‘c’)

2).从顺序容器角度看。(实际上string不是容器)

方式相关描述
string s{‘a’,‘b’,‘c’}
string s = {‘a’,‘b’,‘c’}可以有等号。
string s(b,e)迭代器范围
string s(n)???

3).其他构造函数。

  • n,len2,pos2,都是无符号的。
方式相关描述
string s(cp,n)将cp指向的数组的前n个字符进行拷贝。数组至少有n个字符
string s(s2,pos2)s是string s2从下标pos2开始的字符拷贝。如果pos2 > s2.size(),函数的行为将会是未定义的
string s(s2,pos2,len2)pos2的规定同上,但是不管len2的值为多少,构造函数至多拷贝到s.size()

4).几点说明。

  • 当我们从一个const char*创建一个string时,指针指向的数组必须以空字符结束,拷贝操作遇到空字符时停止。
  • 如果我们还传递一个计数值,可选的,数组就不必以空字符结束。
  • 如果我们没有计数值也没有空字符结尾,未定义?。或者超过范围,会抛出out_of_range的错误,异常终止。
  • 注意,(cp+6,5)代表从cp[6]开始拷贝5个元素,cp这里转化为指针了。
  • string为参数时,如果pos > size一样的,会抛出out_of_range的错误。

5).substr操作。

  • s.substr(pos,n),返回一个string,包含s中从pos开始的n个字符的拷贝。pos的默认值是0,n的默认值是size-pos,即是从pos开始的全部字符。
  • 如果开始的pos是不合法的,会抛出out_of_range的异常。
  • 如果n大于范围,一样的会进行调整,得到从pos开始的全部字符串。

练习,9.41关于string的拷贝等。

  • 如果是c风格的字符串。可以使用
  1. s = cp;/s(cp);
  2. s(cp,n);要求就是一定要以空字符结尾。
  • 如果是string
  1. s = s1;/s(s1)
  2. s = (s1,pos,n);
  • 如果是其他的容器。
  1. s = (b,e);
  • vector提供了一个data成员函数,返回的是容器内存空间的首地址。那么对于vector<char>string的转换
  1. 可以这么做,s = (v.data(),v.size());
  2. 是把这个看成c风格的字符串还是?

//2.改变string的其他方法(insert,assign,erase)

1).除了接受迭代器版本的insert以及erase之外,string的还有,下标版本的。

{
    s.insert(s.size(),5,'!');
    //在s的末尾插入5个!
    //注意这里依然是,在前插入
    s.erase(s.size() - 5,5);
    //删除s最后的五个元素
}

2).标准库string类型还定义提供了接受c风格字符串数组的insertassign版本。

{
    const char *cp = "hello world!";
    s.assign(cp,5);
    //s == "hello";
    s.insert(s.size(),cp+5);
    //s == "hello world!"

}

3).几点说明。

  • assign的要求拷贝的字符一定要小于数组中的字符数。不包括结尾的空字符
  • 这里的字符串需要以空字符结尾?

4).我们还可以,指定来自其他的string或者子字符串插入到当前的string或者赋予当前的string

{
    string s = "some string";
    string s1 = "some other string";
    s.insert(0,s1);
    //将s1插入到s的0位置之前的地方
    s.insert(0,s1,0,s1.size());
    //在s[0]位置之前插入s1中从s2[0]开始的s1.size()个字符。
}

5).小节。

  • inserterase加入和下标的方式
  • assign则是加入和c风格字符串的方式。

6).appendreplace函数

  • append是在末尾进行插入操作的一种简写形式。
{
    string s("hello world!"),sl = s;
    s.insert(s.size(),"abc");
    sl.append("abc");
    //以上是等价的
}
  • replace是调用eraseinsert的一种简写形式。
  • replace的第三个参数并不要与第二个参数是一样长的。
{
    s.erase(12,3);
    //从当前下标位置开始删除3个内容。注意下标从0开始。
    s.insert(12,"def");
    //可以是字符串。
    //插入方式就是直接整体插入。搬
    //等价于
    s.replace(12,3,"def");
    //含义就是,从下标13开始删除3个元素,然后再从13位置之前插入'def'。
}

7).操作表。

修改string的操作相关的描述返回值
s.insert(pos,args)pos之前插入args,请注意,pos是下标,而且注意,下标从零开始。返回的是s的引用。注意args的4个形式引用,下同
s.erase(pos,len)pos位置开始,依次删除pos+1到pos+len-1这些元素。如果被省略,删除从pos开始(包括pos)直到末尾的所有字符。
s.assign(args)替换指定字符。
s.append(args)
s.replace(range,args)range的组织形式有,迭代器范围或者下标加上长度。

8).args的组织形式。

  • args可以是以下形式之一,appendassign可以使用任何的形式。
  • str不可以和s相同。迭代器不可以指向s
    |形式|相关描述|
    |-|-|
    |str|字符串|
    |str,pos,len|str中,从pos开始至多len个字符|
    |cp,len|从cp指向的字符数组前最多len个字符。|
    |cp|cp必须以空字符结尾|
    |n,c|n个字符c|
    |b,e|迭代器b和c指定的范围内的字符。|
    |{}|以逗号分隔|

9).replaceinsert所允许的形式,与range以及pos的形式有关。(以下列出的是不可以的)

replacereplaceinsertinsert
(pos,len,args)(b,e,args)(pos,args)(iter,args)
b,estr,pos,lencpstr
{}b,estr,pos,len
{}cp,len
cp

10).接口就是函数。有共同的接口->重载函数。
11).字符串来源可以是

  • string
  • 字符串字面量
  • 字符串指针
  • {}
  • 字符加个数

12).对于string和c风格的字符串还可以限定字符串的范围。

//3.string的搜索操作

1).搜索操作。

  • 如果搜索得到,返回的是一个string::size_type的无符号整型数。并且指向的是字符串的第一个字符的位置。
  • 如果没有匹配到,返回的是一个string::nposstatic成员。标准库将npos定义为一个const string::size_type类型,并且初始化未-1。我们知道upos是一个无符号的整数。所以此初始值代表着,nops代表着,任何string对象可能的最大的大小。
  • 用一个int或者其他带符号的整型来接受find的返回值都是不恰当的。
  • 搜索是对下标敏感的。
操作名称相关描述
s.find(args)查找args第一次出现的位置
s.rfind(args)查找args最后一次出现的位置
s.find_first_of(args)查找args中任意一个字符第一次出现的位置
s.find_last_of(args)最后一次出现的位置
s.find_first_not_of(args)第一个args中没有的s中的字符。
s.find_last_not_of(args)查找最后一个args中没有的s中的字符

2).args的形式。

形式相关的描述
c,pospos指定了从哪一个位置开始搜索,默认为零,下同
s,pos
cp.poscp指向的字符串需要以空字符结尾
cp,pos,n从s中的pos开始查找指针cp指向的数组的前n个字符posn是没有默认值的。

//4.compare函数

1).比较结果。

比较结果返回值
s > 指定字符串正数
<负数
==0

2).参数形式。

  • s.compare(args)
    |args|相关描述|
    |-|-|
    |sl||
    |pos,n,sl|将s从pos开始的n个字符和sl进行比较|
    |pos1,n1,sl,pos2,n2||
    |cp||
    |pos,n,cp|cp指向的的是以空字符结尾的字符串|
    |pos,n1,cp,n2|从cp指向的地址开始进行比较。|

//5.数值转换

1).标准库中引入很多的函数,实现数值数据和标准库string之间的转换。

  • 要转换数值的string中的第一个非空白字符必须是,数值中可能出现的字符。例如,d = stod(s.substr(s.find_find_of("+-.1234567890")));
  • string参数中,第一个非空字符必须是+,-,或者数字。
  1. 可以是以0x、0X开头的十六进制数
  2. 转换为浮点值的函数,可以是**小数点.**开头,并且以e或者E来表示指数部分。
  3. 对于转换为整型的字符串。根据进制的不同,例如十六进制的。
  • 如果string不可以转换为一个数值,那么函数将会抛出一个invalid_argument的异常。如果转换得到的数值无法用任何类型类表示,那么将会抛出一个out_of_range的异常。

2).转换函数。

操作相关描述
to_string(val)是一组重载的函数。val可以是任何算术类型。每个浮点型或者int或者更大的类型都有相应的版本。小整型会被提升
stoi(s,p,b)返回int类型的数值。b表示的是进制。默认是10。p是size_t下标指针用来保存第一个s中非数值字符的下标。p默认是0。即,函数不保存下标。
stol,stoll,stoul,stoull,
stof(s,p)p的作用是一样的
stod,stold
{
    string str = "1010";
    int a = stoi(str, 0, 2);//p表示位置,2表示的是进行时str中的进制。
    cout << a << endl;
    return 0;
}

/6.容器适配器

1).三种容器适配器,以及它们可以作为基础的顺序容器。
|适配器类型|可以作为基础的顺序容器|原因|
|stack|除了arrayforward_list|push_back,pop_back,back|
|queue|list,deque|push_back,pop_front,back,front|
|priority_queue|vectordeque|front,push_back,pop_back,随机访问|

2).所有适配器都支持的操作。

操作名称相关描述
size_type一种类型,表示适配器的大小
value_type元素类型
container实现适配器的底层容器
A a创建空的适配器
A a?带有容器c的拷贝的适配器
关系运算符全支持,返回的是底层容器的比较结果
a.empty()
a.size()
swap(a,b)交换适配器的内容,要求,类型包括底层容器类型,全部一样。
a.swap(b)

3).我们可以重载默认的底层容器。

  • stackqueue的默认值是deque
  • priority的默认值是vector
  • 重载形式。stack<int,vector>

4).几点说明。

  • 因为所有适配器要求,可以添加或者删除,访问尾元素的能力,所以array以及forward_list是不可以作为底层容器的
  • 每一个栈都有自己的操作。虽然是底层实现,但是我们不可以在适配器中使用底层实现的操作。例如在stack中使用push_back是错误的。

5).stack适配器。

操作相关描述
s.pop()删除栈顶元素,但是不会返回值
s.push(item)压入栈顶。通过拷贝或者移动item而来
s.emplace(args)args进行构造
s.top()返回栈顶

6).queuepriority适配器。

操作相关描述
q.pop()返回的是queue的首元素,或者priority——queue的最高优先级元素。但是不会删除该元素。
q.front()只适用于queue,返回的是首元素,但是不会删除该元素
q.back()只适用于queue,返回尾元素。
q.top()返回最高优先级的元素,但是不会删除该元素。只适用于priority_queue
q.push(item)priority_queue的适当的位置或者queue的末尾创建一个元素
q.push(args)
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-07-13 17:16:52  更:2021-07-13 17:17:23 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年4日历 -2024/4/28 13:08:30-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码