三大顺序容器的简单使用与对比

        前言:

        我们在平时做项目的时候都会接触到容器,而我可能还有很多朋友都没有系统的学习过容器,导致里面很多概念容易混淆,在这里,我简单的总结了顺序容器的简单使用。这篇文章起到在做项目的时候检索的作用,以及三种容器中的不同操作,这是重点。讲述如何去开车,而不是去修车,强调的是使用,而不是讲解,更不是深入讲解。如果大家在看完这个想深入研究容器的话,可以去看深入剖析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容器应该预留多大的容量。