三大顺序容器的使用与对比详细介绍
三大顺序容器的简单使用与对比 前言: 我们在平时做项目的时候都会接触到容器,而我可能还有很多朋友都没有系统的学习过容器,导致里面很多概念容易混淆,在这里,我简单的总结了顺序容器的简单使用。这篇文章起到在做项目的时候检索的作用,以及三种容器中的不同操作,这是重点。讲述如何去开车,而不是去修车,强调的是使用,而不是讲解,更不是深入讲解。如果大家在看完这个想深入研究容器的话,可以去看深入剖析STL这类专业性更强的丛书。 这篇文章介绍了容器的初始化、迭代器操作、容器内部定义的类型别名、插入元素、容器的比较、元素的访问、删除元素最后以vector容器自增长来结束,希望这些内容对你在工作上有所帮助!
正文: 三大顺序容器: 1. vector -> 支持快速随机访问 2. list -> 支持快速插入和删除 3. deque -> 双端队列
容器的初始化: C c; 创建一个名为c的空容器。c是容器类型名。 C c(c2); 将c2容器赋值飞c容器。 C c(begin,end); 创建容器c,并用迭代器begin和end之间的元素进行初始化。 C c(n, t); 用n个值t的元素进行初始化。 C c(n); 创建n个值初始化。 这个地方的内容比较简单,就不列举例子了。
容器内元素的约束类型: 大多数类型都可用做容器的元素类型。容器元素类型必须满足两个约束: 1. 元素类型必须支持赋值运算。 2. 元素类型的对象必须可以复制。 这里要注意的是,引用不支持一般意义的赋值运算,所以没有元素是引用类型的容器。io库类型不支持赋值和赋值,所以也不能创建存放io类型对象的容器。
迭代器的操作,以及vector和deque特有的: 迭代器的作用是指向容器中的元素,但也可以指向容器末端的下一位。迭代器类似指针,可以对指向容器元素的迭代器进行解引操作,还可以进行其他操作。 通用如下: *iter; iter -> mem; ++iter; iter++; --iter; iter--; iter1 == iter2; iter1 != iter2; 下面的迭代器操作是vector与deque特有的,list不可以使用: iter + n; iter - n; iter1 += iter2; iter1-= iter2; iter1 - iter2; < > <= >= list中迭代器不支持大于小于操作符,所以迭代list的时候不要用iter < li.end()这样的操作,要用iter!=li.end()。
容器内定义的别名: 这是容器自己定义的类型,这个地方没什么好说的,把定义的类型别名做个列举吧。 1. size_type 无符号整形,足以存储迭代器类型最大可能容器长度 2. iterator 此容器类型的迭代器 3. const_iterator 元素的只读迭代器类型 4. reverse_iterator 逆序寻址元素的迭代器 5. const_reverse_iterator 元素只读的逆序迭代器 6. difference_type 足够存储两个迭代器差值的有符号整形,可为负数 7. value_type 元素类型 8. reference 元素左值类型,是value_type&的同义词 9. const_reference 元素常量的左值类型,等效于const value_type&
插入元素: 在顺序容器中添加元素有以下几种方法: 1. c.push_back(t); -> 在容器c的尾部添加值为t的元素。返回void,俗称尾插。 2. c.push_front(t); ->与尾插相反,这个是在c容器的头部添加值为t的元素。返回同样是void,俗称头插,但一定要注意,这种方式只使用与list和deque,而不支持vector,在后面我们也会提到删除,vector也同样不支持头删。 3. c.insert(p,t); -> 在迭代器p所指向的元素前面插入值为t的新元素。返回指向新元素的迭代器。 4. c.insert(p, n, t); -> 在迭代器p所指向的元素前面插入n个值为t的新元素。返回void类型。 5. c.insert(p, begin,end); ->在迭代器p所指向的元素前面插入有迭代器begin和end标记的范围内的元素,返回值同样为void。 注意,想插入,删除这样的操作有可能使迭代器失效,只要是元素的位置发生了变化,那么指向它的迭代器就会失效。
关系操作符,从其大小操作: 关系操作符比较简单,容器都支持关系操作符进行容器的比较,包括<,>,!=等操作,但是一定要注意,连个容器的类型一定要相同。 1. c.size(); -> 返回c容器中元素的个数,返回容器内元素的个数。类型是c::size_type 2. c.max_size(); -> 返回容器c可容纳的最多元素个数 3. c.empty(); -> 返回容器是否为空,为空返回true,否则返回false 4. c.resize(n); -> 调整容器的长度,如多n小于size的话,删除多余的元素,否则初始化多出来的新元素。 5. c.resize(n, t); -> 多出来的新元素用t进行初始化。
访问元素操作: 1. c.back(); -> 访问容器最后一个元素 2. c.front(); -> 访问容器第一个元素 如果容器为空,上述操作未定义。 以下是比较方便的访问操作,但是只适用于vector和deque,list不支持。 3. c[n]; ->访问容器中第n个元素。 4. c.at(n); ->访问容器中第n个元素。 下标越界的话,操作未定义。
删除元素操作: 删除元素很容易理解,即删除容器当中指定的元素。 有以下几个操作: 1. c.erase(p); 删除迭代器p所指向的元素,返回一个被删除元素后面的元素的迭代器。不过要注意p并不是c.end();而是真实存在的元素。 2. c.erase(begin,end); 删除迭代器begin和end范围内容的元素,返回迭代器指向删除段的下一个元素。 3. c.clear(); 删除容器全部元素,返回void。 4. c.pop_back(); 删除容器中最后一个元素 只适用于list与deque的 c.pop_front(); 删除容器中的第一个元素。这里面没有vector的事儿,vector不支持前插与前删。前删除与后删除返回值同样是void,如果容器为空的话,这样的操作未定义。
赋值操作与swap操作: 与赋值相关的操作都将作用于整个容器。功能上理解起来非常简单,就是把左操作数容器的内容全部删除,在将右操作数容器插入到左操作数容器,有以下几种方式。 1. c1 = c2 将c1中的内容清除,将c2中的内容插入到c1中。 2. assign函数 这个函数有两个版本 c.assign(begin,end); 将迭代器begin和end中的元素插入到c中,但是这两个迭代器不能是不能是指向c中元素的迭代器。 c.assign(n, t); 将迭代器重置为n个t元素。
需要注意的是容器的类型一定要相同。操作完成迭代器失效。 swap操作可以节省删除元素的成本。 这个操作是将两个容器的内容互换,所以容器类型要相同。右操作容器中原来的内容放于左操作容器中。由于没有增加删除移动容器的元素,所以迭代器不会失效。
vector容器的自增长: 为什么是涉及到vector容器自增长的问题呢?因为vector容器内的元素在内存中是连续存放的,假如我想在后面加入一个元素,不能随便在内存的其他位置开辟一块空间,于是我们不得不开辟比现有容器更大的空间,然后把以前的容器元素复制到新的空间里,在把新元素加到后面,之后释放掉以前容器占用的空间。这样的话我们每插入一个元素的话只开辟原有vector容器再多一个的空间,这样vector的性能会非常非常的大。另外插一句额外的话,list容器使用的是链表的数据结构,新增加元素不用关心在内存什么位置添加,我只要修改结构里节点中指向下一个节点的指针指向即可。这也是为什么vector的随机访问性能优于list,而随机插入元素的性能低于list。 基于上述的原因,为了提高vector的性能,我们应该在vector添加元素的时候要多开辟一些空间,多开辟多少空间根据库的不同而不同。这里面还涉及一个概念叫做容量,容量是指这个vector容器新分配的存储空间能存放多少个元素。他和容器大小的区别是,大小只是容器装的元素的个数。举个例子,容量就好像是水杯的容量,而容器大小就是里面装的水的体积。所以容量只大于等于容器长度,不能小于。 1. capacity函数,返回容器的容量值。 例子程序:当我们耗尽了容器的容量,在插入才进行内存分配。因此是必要的时候才重新分配内存空间。 2. reserve函数,手动设置vector容器应该预留多大的容量。 |