C++顺序容器

目录

  • 容器选择原则
  • 所有容器都提供的操作
    • 获取迭代器
    • 赋值与swap
    • 大小
    • 添加/删除元素(不支持array)
    • 构造函数
    • 反向容器的额外成员
    • 顺序容器支持的操作
  • forward_list操作
  • 容量管理
  • string操作
  • array
  • 容器适配器
  • 补充

容器选择原则

  • 在意查找速度:散列容器,排序的vector和标准关联容器
  • 容器中的数据的内存布局需要兼容C吗?使用vector
  • 使用typedef代码代替“容器无关代码”
  • 判断容器为空首选empty而不是size()=0,理由很简单:对于所有的标准容器,empty是一个常数时间的操作,但对于一些list实现,size花费线性时间。
  • 尽量使用区间成员函数如assign,区间insert等代替单元素操作如单元素insert:代码量更少,更清晰;效率更高
  • 使用new的指针作为容器元素,要确保在销毁容器前delete指针,可以使用智能指针代替原始指针
  • STL容器的元素需要满足三个要素:可拷贝,可赋值,可析构
  • 去除一个容器中有特定值的所有对象:
    • 如果容器是vector,string,deque,使用erase-remove
    • 如果容器是list,使用list::remove
    • 如果容器是标准关联容器,使用他的erase函数
      添加/删除元素和访问顺序是选择容器类型的标准
容器类型 访问顺序 添加/删除元素顺序 备注
string 随机访问 尾部
vector 随机访问 尾部 1.如无很好的理由,使用vector是最好的选择;2.避免使用vector
deque 双端队列,随机访问 头部,尾部 deque模板是通过链接若干片连续的数据实现的,所以均衡了以上两个容器的特点,支持对所有元素的随机访问,在中间位置添加或删除代价很高,两端插入或删除则很快;在deque容器首部或尾部插入元素不会使任何迭代器失效,而在首部或尾部删除元素则只会使指向该元素的迭代器失效。在deque容器的任何其他位置的插入与删除操作将使指向该容器元素的所有迭代器失效。
list 双向链表,只支持双向顺序访问 任何位置 1.这两个容器的目的是在任何位置添加或删除都很快速;2.作为代价不支持元素的随机访问,为访问一个元素需遍历整个容器;3.删除或插入不会使list的迭代器失效,list和forwar_list的额外内存开销很大
forward_list 单向链表,只支持单向顺序访问 任何位置 没有size操作
array 随机访问 / 1.大小是固定的;所以不支持删除和改变容器大小的操作

类型别名

类型别名 操作 备注
iterator/const_iterator 容器的迭代器类型
size_type 无符号整数类型
difference_type 带符号整数类型,足够保存两个迭代器之间的距离
value_type 元素类型
reference/const_reference 元素的左值类型,与value_type&含义相同

注:

  • vector和string重新分配内存有以下四个部分:
    • 分配新的内存
    • 把所有的元素从容器的旧内存拷贝到新内存中
    • 销毁旧内存中的对象
    • 回收旧内存
  • 避免使用vector,因为vector是一个伪容器,并不保存真正的bool,而是打包bool以节省空间,在一个典型的实现中,每个保存在vector中的bool占用一个bit,一个byte可以容纳8个bool。替代品:
    • deque:保存真正的bool
    • 使用bitset(#include)代替
  • 收缩到合适:假如有一个vector v的capacity()大于size():vector(v).swap(v)会使v的capacity大小同size()一样大:因为拷贝构造只分配拷贝的元素需要的内存,所以这个临时的vector没有多余的空间,然后再交换,就可以达到收缩到合适大小;同样的技巧可以用到string,这个技巧也可以用来清除vector和string
  • 在使用vector前先判断empty
  • 当你需要一个指向vector内部数据的指针时不该使用begin(),begin返回的时iterator而不是指针,虽然vector的迭代器是指针但不能依赖于此,最好&v[i];类似于vector上获取指向内部数据的指针,对string也是不可靠的

所有容器都提供的操作

获取迭代器

  • c.begin(),c.end():返回指向c的首元素和尾元素之后位置的迭代器,解引用迭代器返回所指元素的引用;如果对象是常量则返回const_iterator
  • c.cbegin(),c.cend():返回const_iterator,当不需要写访问 时应当使用这两个迭代器;不管对象是否是常量,这两个函数返回的都是const_iterator

赋值与swap

  • c1=c2;
  • c1={a,b,c,… …}:将c1中的元素替换为列表中的元素——不适用于array
  • a.swap(b):交换容器a和b的元素:元素本身并未交换,只是交换了两个容器的内部数据结构,但swap两个array会真正交换他们的元素
  • swap(a,b):同上,应统一使用非成员版本的
  • 注解:除array外swap不对任何元素进行拷贝,删除或插入操作;与其他容器不同,对一个string调用swap会导致迭代器,引用和指针失效,为什么?

大小

  • c.size():c中元素的数目;—— forward_list 不支持size,但支持max_size()和empty()
  • c.max_size():返回一个大于或等于该类型容器所能容纳的最大元素数的值;
  • c.empty():判断c是否为空,若为空返回true,否则返回false

添加/删除元素(不支持array)

  • c.insert(args):
  • list中的insert支持以下:
    • l.insert(pos, elem):在迭代器pos之前插入元素elem
    • l.insert(pos, n, elem):在迭代器pos之前插入n个elem,并返回第一个新元素的位置
    • l.insert(pos, beg, end): 在迭代器pos之前插入区间[beg, end)的一份拷贝,并返回第一个新元素的位置
    • l.insert(pos, initlist)
    • c.emplace(inits):会通过参数直接构造容器中的元素,而push则是先构造元素,再调用拷贝构造将参数拷贝到容器中
    • c.erase(args):删除参数指定的元素;返回迭代器
    • c.clear():删除c中所有元素,返回void:空间会不会删除?

构造函数

  • C c:默认构造函数
  • C c(n):n为数字,表示容器c的大小,默认初始化(如果是int则为0)
  • 拷贝初始化:C c1(c2)/C c1=c2:c1和c2必须是相同类型(相同的容器类型以及相同的元素类型;对array来说还具有相同的大小)
  • 列表初始化:C c{a,b,c, … …}/C c={a,b,c, … …}:
  • C c(b,e):初始化为迭代器b和e指定范围中的元素的拷贝,范围中元素的类型与C的元素类型必须相容(容器类型可以不一样)(也可用数组指针表示的范围初始化)——不支持array
  • 顺序容器支持(不包括array):C seq(n)/C seq(n,t),接受一个容器大小和一个(可选的)元素初始值

反向容器的额外成员

反向遍历容器的迭代器不支持forward_list

  • reverse_iterator:按逆序寻址元素的迭代器
  • const_reverse_iterator:不能修改元素的逆序迭代器
  • c.rbegin(), c.rend():返回指向c尾元素和首元素之前位置的迭代器
  • c.crbegin(), c.crend():返回const_reverse_iterator

顺序容器支持的操作

assign操作不适用于关联容器和array;传递给assign的迭代器不能指向调用assign的容器; 添加/插入/删除 操作不支持array

  • seq.assign(b,e):将seq中的元素替换为迭代器b和e表示的范围中的元素,这两个迭代器不能指向seq中的元素;b和e可以是表示数组范围的指针
  • seq.assign(il):将seq中的元素替换为初始化列表i1中的元素
  • seq.assign(n,t):将seq中的元素替换为n个值为t的元素
  • c.push_back(t):返回void,添加到对象尾端(不支持forward_list)
  • c.emplace_back(args):返回void(不支持forward_list)
  • c.push_front(t):返回void(不支持string和vecto,最好只用于deque)
  • c.emplace_front(t)(不支持string和vecto,最好只用于deque)
  • 在迭代器之前位置之前插入(forward_list有自己版本的insert):都返回新添加的第一个元素的迭代器;最好只用于list和forward_list
    • c.insert(p,t):迭代器p指向的元素之前插入t或args创建的元素,返回新添加的元素的迭代器
    • c.emplace(p,args):通过参数直接构造实例而不是通过拷贝或移动
    • c.insert(p,n,t):迭代器p指向的元素之前插入n个置位t的元素,返回新添加的第一个元素的迭代器
    • c.insert(p,b,e):将迭代器b和e指定的范围内的元素插入到迭代器p指向的元素之前
    • c.insert(p,i1):在p之前插入i1,i1是花括号包围的元素值列表
    • 注解:
      • 前面几种出现了emplace_xxxx,与push_back不同点:push_xx是将构造好的对象拷贝到容器中,而emplace则是根据提供的参数直接在容器中构造元素而不是拷贝;当调用push或insert时是将元素的对象传递给它们,这些对象被拷贝到容器中,当调用emplace时则是将参数传递给元素类型的构造函数,emplace成员使用这些参数在容器管理的内存空间中直接构造元素。(C++ Primer P346)
      • emplace函数的参数必须与元素类型的构造函数相匹配
  • 删除元素:这些操作都会改变容器的大小,所以不适用于array;在删除元素前必须确保是存在的
    • c.pop_back():删除尾元素,返回void;(forward_list不支持)
    • c.pop_front():删除首元素,返回void;(vector和string不支持 )
    • c.erase§:删除迭代器p指定的元素,返回被删除元素之后元素的迭代器【fordlist_list有特殊版本的erase】
    • c.erase(b,e):删除[b,e)范围的元素,返回指向最后一个被删元素之后的元素迭代器,若e本身就是尾后迭代器,则函数也返回尾后迭代器
    • c.remove: list使用这个更高效——是否支持其他?
    • c.remove_if
    • c.clear():删除c中所有元素,返回void
  • 访问元素:在容器中访问成员函数返回的都是引用,如果容器是const对象,返回的是const的引用;如果容器中没有元素,访问的结果是未定义的,所以在调用back和front之前要确保c为非空
    • c.back():返回尾元素的引用(除forward_list)
    • c.front():返回首元素的引用 (所有顺序容器)
    • c[n]:返回下标为n的元素的引用,n的类型为size_type
    • c.at(n):返回下标为n的元素的引用,若下标越界,抛出out_of_range异常,这大概 是与c[n]的唯一区别;c[n]和c.at(n)的区别还有哪些?如果元素是sting的话返回const char*, []返回string
  • 四个成员函数,只有vector和string提供了这些函数:
    • 如果resize缩小容器,则指向被删除元素的迭代器,引用和指针都会失效;对vector,string或deque进行resize可能导致迭代器,指针,引用失效
    • array不支持
    • c.resize(n):调整容器大小,增大或缩小容器,使容器大小改变为n——vector的内存应该有移动吧?
    • c.resize(n,t):调整c的大小为n,多出的空间用t初始化
    • capacity():容量大小
    • size():容器中有多少元素
  • list独有的操作
    • 排序:l.sort():以operator<为准则(从小到大)对元素进行排序
      链表:list和forward_list
  • 总说
    • 这两个容器的设计目的是令容器任何位置的添加和删除操作很快速,作为代价,不支持元素的随机访问:为了访问一个元素只能遍历整个容器
    • 与vector,deque和array相比,这两个容器的额外内存开销很大

list

  • 相比较array,vector,deque的不同:
    • 不支持随机访问,如果要访问第5个元素,就得沿着link航行前4个元素
    • 任何位置安插和删除元素很快(确定这个位置的过程是否很慢?)
    • 安插和删除动作不会造成指向其他元素的pointer,reference和iterator失效
  • list提供的成员函数反映出其与vector,array,deque的不同:
    • list提供front,push_front,pop_front,back,push_back,pop_back等操作函数
    • 由于不支持随机访问,所以不提供下标运算符,也不支持at方法
  • list不提供容量,空间重新分配操作函数
  • 迭代器:
    • lst.before_begin( )/lst.cbefore_begin( ):返回指向链表首元素之前不存在的元素的迭代器,不能解引用;
  • 插入操作:
    • lst.insert_after(p,t):在迭代器p之后插入元素,t是一个对象;返回一个指向最后一个插入元素的迭代器,如果范围为空则返回p;
    • lst.insert_after(p,n,t):n是数量
    • lst.insert_after(p,b,e):b和e是表示范围的一对迭代器(b和e不能指向lst内)
    • lst.insert_after(p.i1):i1是一个花括号列表。
  • 删除操作:
    • lst.erase_after§:删除p指向的位置之后的元素,返回一个被删除元素之后元素的迭代器,若不存在这个元素则返回尾后迭代器
    • lst.erase_after(b,e):删除b之后直到e之间(b,e)的元素

forward_list操作

单向链表,通过改变给定元素之后的元素完成

  • 相比较list,forward_list有以下约束:
    • 只提供前向迭代器,不支持双向迭代器,也不支持反向迭代器,所以诸如rbegin等方法是不支持的
    • 没有指向最末元素的锚点,所以用以处理末元素的方法诸如back,push_back,pop_back等是不支持的
  • 除了以上,其他和list相同:
    • 不提供随机访问,如要访问第5个元素,必须沿着link航行前4个元素,因此在forward_list中访问任意元素会非常慢
    • 在任意位置安插和移除元素会速度会非常快,前提是指出了那个位置(确定这个位置的过程是不是又非常慢?)
    • 安插或移除元素不会造成指向其他节点的指针,引用,迭代器失效
  • 非更易性操作:
    • c.empty():判断容器是否为空
    • c.max_size()
    • 6个比较操作

为什么说链表在插入和删除上效率要高于数组?
插入删除操作时间复杂度对比:
在只知道小标时:
• 数组:O(n):需要移动其后所有项的位置
• 链表:O(n):需要遍历前面所有项以得到下标对应的项
在拥有操作项的引用(或指针,迭代器等)时:
• 数组:O(n)
• 链表:O(1)
结论:
• 若线性表需要频繁查找,很少进行插入和删除操作时,应该采用顺序存储结构;
• 频繁插入和删除需要采用链表结构
• 当不确定线性表中元素个数变化或者大小时采用单链表结构

容量管理

  • c.capacity( ):(适用于vector和string)在容器不扩张内存的情况下可以容纳元素的个数
  • c.reserve(n):(适用于vector和string)分配至少能容纳n个元素的内存空间(可以比n更多),如果n比实际容器中的个数小则不做任何改变,但一般会强迫进行一次重新分配;reserve之后迭代器会失效;避免使用重新分配的关键是使用reserve尽快把容器容量;通常有两种情况使用reserve来避免不必要的重新分配:
    • 大约知道有多少元素出现在容器中
    • 第二:保留你可能需要的最大空间
    • c.shrink_to_fit():(适用于vector,string,deque):请求将capacity()减少为与size()相同大小,只是一个请求,标准库并不保证退还内存

string操作

  • 构造string:接收string 或const char* 参数
    ○ string s(cp,n):s是cp指向的数组 中前n个字符的拷贝
    ○ string s(s2,pos2):s是string s2从下标pos2开始的字符的拷贝
    ○ string s(s2,pos2,len2):string s2从下标pos2开始的len2个字符的拷贝
    • 转换为字符数组或C数组
    ○ data() / c_str():返回一个以’\0’结尾的C数组
    ○ copy(char* const buf, size_t count, const size_t off):将string内容复制到调用者提供的字符数组中,末尾不添加’\0’字符
    • 大小和容量
    ○ empty():用来检验字符串是否为空,应该优先使用该函数
    ○ size() / length():返回string的现有字符,两个函数等效
    ○ max_size():string最多能包含的字符数
    ○ capacity():重新分配内存前,string所能包含的最大字符数
    ○ reserve():预留容量,避免重新分配;与vector不同,string使用reserve可以缩减容量;默认参数为0
    ○ shrink_to_fit():非强制性缩减容量,不一定缩减成功
    • 元素访问:
    ○ operator[] / at():[]不会检查索引是否有效,at会检查,如果调用at时指定的索引无效会抛出out_of_range
    ○ front()相当于[0]
    ○ back():返回最后一个字符,相当于str[str.size()-1],返回的是个引用
    • 比较:
    />=是将比较的两个字符串的各个字符ascii码进行比较
    ○ compare:作用同上
    • 更改内容:
    ○ swap
    ○ clear() / erase():令string为空
    • 搜索和查找:
    ○ 每个函数有4个重载版本
    ○ 查找失败返回一个特殊值:npos,定义于string中,如 if(idx == string::npos),npos被定义为一个const string::size_type类型,并初始化为-1;
    ○ 检验查找函数的返回值时使用string::size_type类型而不是int或unsigned,表示匹配发生的下标位置,否则与npos的比较动作无法有效运行
    ○ 可以查找字符,字符串
    ○ 成员函数:
    § find() / rfind():查找第一个/最后一个 与value相等的字符,如果成功就返回字符索引,否则返回string::npos
    § find_first_of() / find_last_of():查找第一个/最后一个 与value中的某值相等的字符
    § find_first_not_of() / find_last_not_of():查找第一个 / 最后一个 与value中的任何值都不相等的字符
    § 第一参数永远是被查找对象
    • 函数而非成员数值转换:
    ○ stoi(str, idexRet=nullptr, base=10):将str转换成int,同样的函数还有stol等,具体参考C++标准库13.2.13
    ○ to_string(val):将val转换成一个string,val可以是int,long等
    • 支持迭代器操作:
    ○ 迭代器类型:string::iterator
    ○ begin,end
    2. s.substr(pos,n):
    1. 参数:传递一个可选的开始位置和计数值:
    1. pos:开始位置,默认值为0;
    2. n:为从pos开始要取得字符的个数,默认为从pos开始的所有字符,若n>s.size(),只取从pos开始到结束的部分。
    2. 返回值:返回一个string,它是原始string的一部分或全部的拷贝。[ )

array

定义时指定元素类型和大小
array不允许用花括号包围的列表赋值但可以列表初始化

容器适配器

标准库定义了三个顺序容器适配器:stack(栈适配器),queue(队列适配器),priority_queue(优先队列),所有的适配器都要求容器具有添加,删除,以及访问尾元素的能力

  • 所有容器都支持的类型:
    • size_type:表示当前类型对象的大小
    • value_type:元素类型
    • container_type:实现适配器的底层容器类型
    • A a/A a©:创建一个名为a的空适配器/带有容器C的一个拷贝;
    • 关系运算符:支持所有关系运算符:==,!=,<,<=,>,>=
    • a.empty():为空则返回true,否则返回false
    • a.size():返回a中元素数目
    • a.swap(a,b):交换a和b的内容,a和b必须有相同类型,包括底层容器类型也必须相同
    • swap(a,b):
  • 栈适配器stack:定义在头文件stack中,默认基于deque实现,也可以在list或vector之上实现,适用场合?
    • s.pop():删除栈顶元素,但不返回该元素值
    • s.push(item)/s.emplace(args):创建一个新元素压入栈顶,该元素通过拷贝或移动item而来,或者由args构造
    • s.top():返回栈顶元素,但不删除元素
    • 注意:除了最顶端外,没有任何其他方法可以存取stack的其他元素。
  • 队列适配器:queue和priority_queue(优先队列)适配器定义在queue头文件中,queue是基于deque 实现,priority_queue默认基于vector 实现,主要接口为:
    • q.push()/q.emplace(args):将一个元素放入到queue尾
    • q.front():返回队列queue的第一个元素(最先插入的),但不删除此元素
    • q.back():只适用于queue,返回队列最后一个元素的引用
    • q.pop():移除最前端(最先进队列)的元素或priority_queue的最高优先级的元素,不返回值
    • q.top():返回最高优先级元素,但不删除该元素,只适用于priority_queue
  • 其他
    • stack:可以使用除array和forward_list之外的任何容器类型来构造stack
    • queue:可以构造于list或deque之上,但不能基于vector构造
    • priority_queue:可以构造于vector或deque之上,但不能基于list

补充

  • 可以使用数组或数组的一部分初始化vector,必须使用指针
  • 有成员版本的swap,也有非成员版本的swap,在泛型编程中使用非成员版本的swap;swap除array外不对任何元素进行拷贝,删除,或插入操作,因此,除string外,指向容器的迭代器,引用和指针在swap操作之后都不会失效,仍指向swap操作之前所指向的那些元素,但swap之后这些元素已经属于不同的容器了。对string调用swap会导致迭代器,引用和指针失效。swap两个array 会真正交换它们的元素。

你可能感兴趣的:(C++编程,c++,开发语言)