STL之list和vector

list 容器

list 简介

list是C++标准模版库(STL,Standard Template Library)中的部分内容。实际上,list容器就是一个双向链表,可以高效地进行插入删除元素。

使用list容器之前必须加上头文件:#include<list>;

list属于std命名域的内容,因此需要通过命名限定:using std::list;也可以直接使用全局的命名空间方式:using namespace std;

构造函数

   list<int> c0; //空链表
   list<int> c1(3); //建一个含三个默认值是0的元素的链表
   list<int> c2(5,2); //建一个含五个元素的链表,值都是2  
   list<int> c4(c2); //建一个c2的copy链表
   list<int> c5(c1.begin(),c1.end()); //c5含c1一个区域的元素[_First, _Last]。

成员函数

  • c.begin() 返回指向链表第一个元素的迭代器。
  • c.end() 返回指向链表最后一个元素之后的迭代器。
    list<int> a1{1,2,3,4,5};
    list<int>::iterator it;
    for(it = a1.begin();it!=a1.end();it++)
    {
        cout << *it << "\t";
    }
    cout << endl;
  • c.rbegin() 返回逆向链表的第一个元素,即c链表的最后一个数据。
  • c.rend() 返回逆向链表的最后一个元素的下一个位置,即c链表的第一个数据再往前的位置。
  • operator= 重载赋值运算符。
  • c.assign(n,num) 将n个num拷贝赋值给链表c。
  • c.assign(beg,end) 将[beg,end]区间的元素拷贝赋值给链表c。
    int a[5] = {1,2,3,4,5};
    list<int> a1;
    list<int>::iterator it;
    a1.assign(2,10);
    for(it = a1.begin();it!=a1.end();it++){
        cout << *it << " ";
    }
    cout << endl;
    a1.assign(a,a+5);
    for(it = a1.begin();it!=a1.end();it++){
        cout << *it << " ";
    }
    cout << endl;
  • c.front() 返回链表c的第一个元素。
  • c.back() 返回链表c的最后一个元素。
  • c.empty() 判断链表是否为空。
  • c.size() 返回链表c中实际元素的个数。
  • c.max_size() 返回链表c可能容纳的最大元素数量。
  • c.clear() 清除链表c中的所有元素。
  • c.insert(pos,num) 在pos位置插入元素num。
  • c.insert(pos,n,num) 在pos位置插入n个元素num。
  • c.insert(pos,beg,end) 在pos位置插入区间为[beg,end]的元素。
  • c.erase(pos)    删除pos位置的元素。
  • c.push_back(num) 在末尾增加一个元素
  • c.pop_back() 删除末尾的元素。
  • c.push_front(num) 在开始位置增加一个元素。
  • c.pop_front() 删除第一个元素。
  • resize(n) 重新定义链表的长度,超出原始长度部分用0代替,小于原始部分删除。
  • resize(n,num) 重新定义链表的长度,超出原始长度部分用num代替。
  • c1.swap(c2); 将c1和c2交换。
  • swap(c1,c2); 同上。
  • c1.merge(c2) 合并2个有序的链表并使之有序,从新放到c1里,释放c2。
  • c1.merge(c2,comp) 合并2个有序的链表并使之按照自定义规则排序之后从新放到c1中,释放c2。
  • c1.splice(c1.beg,c2) 将c2连接在c1的beg位置,释放c2
  • c1.splice(c1.beg,c2,c2.beg) 将c2的beg位置的元素连接到c1的beg位置,并且在c2中施放掉beg位置的元素
  • c1.splice(c1.beg,c2,c2.beg,c2.end) 将c2的[beg,end)位置的元素连接到c1的beg位置并且释放c2的[beg,end)位置的元素
  • remove(num) 删除链表中匹配num的元素。
  • remove_if(comp) 删除条件满足的元素,参数为自定义的回调函数。
    list<int> a1{1,2,3,4,5};
    a1.remove_if([](int n){return n<3;});
    list<int>::iterator it;
    cout << "remove_if():";
    for(it = a1.begin();it!=a1.end();it++){
        cout << *it << " ";
    }
    cout << endl;
  • reverse() 反转链表
  • unique() 删除相邻的元素
  • c.sort() 将链表排序,默认升序
  • c.sort(comp) 自定义回调函数实现自定义排序
    list<int> a1{1,3,2,5,4};
    a1.sort();
    list<int>::iterator it;
    cout << "sort():";
    for(it = a1.begin();it!=a1.end();it++){
        cout << *it << " ";
    }
    cout << endl;
    
    a1.sort([](int n1,int n2){return n1>n2;});
    cout << "sort(function point):";
    for(it = a1.begin();it!=a1.end();it++){
        cout << *it << " ";
    }
    cout << endl;
  • operator==
  • operator!=
  • operator<
  • operator<=
  • operator>
  • operator>=

vector容器

vector简介

vector是C++标准模版库(STL,Standard Template Library)中的部分内容。之所以认为是一个容器,是因为它能够像容器一样存放各种类型的对象,简单的说:vector是一个能够存放任意类型的动态数组,能够增加和压缩数据

使用vector容器之前必须加上<vector>头文件:#include<vector>;

vector属于std命名域的内容,因此需要通过命名限定:using std::vector;也可以直接使用全局的命名空间方式:using namespace std;

vector构造函数

  • vector<type>c;创建一个空的vector容器。
  • vector<type> c1(c2);复制一个vector。
  • vector<type> c(n);创建一个vector,含有n个数据,数据均以缺省构造产生,即全0;
  • vector<type> c(n,elem)创建一个vector,含有n个elem的拷贝数据。
  • vector<type> c(beg,end)创建一个以[beg,end)区间的vector。
  • ~vector<type>() 销毁所有数据,施放内存。

vector成员函数

  • c.push_back(elem)在尾部插入一个elem数据。
vector<int> v;
v.push_back(1);
  • c.pop_back()删除末尾的数据。
  • c.assign(beg,end)将[beg,end)一个左闭右开区间的数据赋值给c。
  • c.assign (n,elem)将n个elem的拷贝赋值给c。
  • c.at(int index)传回索引为index的数据,如果index越界,抛出out_of_range异常。
  • c.begin()返回指向第一个数据的迭代器。
  • c.end()返回指向最后一个数据之后的迭代器。
  • c.rbegin()返回逆向队列的第一个数据,即c容器的最后一个数据。
  • c.rend()返回逆向队列的最后一个数据的下一个位置,即c容器的第一个数据再往前的一个位置。
  • c.capacity()返回容器中数据个数,翻倍增长
vector<int> v;
v.push_back(1);
cout << v.capacity() << endl;  // 1
v.push_back(2);
cout << v.capacity() << endl;  // 2
v.push_back(3);
cout << v.capacity() << endl; // 4
  • c.clear()移除容器中的所有数据。
  • c.empty()判断容器是否为空。
  • c.erase(pos)删除pos位置的数据,传回下一个数据的位置。
  • c.erase(beg,end)删除[beg,end)区间的数据,传回下一个数据的位置。
  • c.front()返回第一个数据。
  • c.back()传回最后一个数据,不检查这个数据是否存在。
  • c.insert(pos,elem) 在pos位置插入一个elem的拷贝,返回插入的值的迭代器。
  • c.insert(pos,n,elem)在pos位置插入n个elem的数据,无返回值。
  • c.insert(pos,beg,end)在pos位置插入在[beg,end)区间的数据,无返回值。
  • c.size()返回容器中实际数据的个数。
  • c.resize(num)重新指定队列的长度。(往往用来增加vector的长度,小->大 ok 大->小 没用!)
  • c.reserve()保留适当的容量。

** 针对resize()和reserver()做一点分析:**
  reserve是容器预留空间,但并不真正创建元素对象,在创建对象之前,不能引用容器内的元素,因此当加入新的元素时,需要用push_back()/insert()函数。
  resize是改变容器的大小,并且创建对象,因此,调用这个函数之后,就可以引用容器内的对象了,因此当加入新的元素时,用operator[]操作符,或者用迭代器来引用元素对象。
  再者,两个函数的形式是有区别的,reserve函数之后一个参数,即需要预留的容器的空间;resize函数可以有两个参数,第一个参数是容器新的大小,第二个参数是要加入容器中的新元素,如果这个参数被省略,那么就调用元素对象的默认构造函数。
  reserve只是保证vector的空间大小(capacity)最少达到它的参数所指定的大小n。在区间[0, n)范围内,如果下标是index,vector[index]这种访问有可能是合法的,也有可能是非法的,视具体情况而定。
resize和reserve接口的共同点是它们都保证了vector的空间大小(capacity)最少达到它的参数所指定的大小。

c.max_size()返回容器能容量的最大数量。
c1.swap(c2)将c1和c2交换。
swap(c1,c2)同上。

压缩一个臃肿的vector

很多时候大量的删除数据,或者通过使用reserver(),结果vector的空间远远大于实际的需要。所以需要压缩vector到它的实际大小。resize()能增加vector的大小。clear()仅仅移除容器内的数据,不能改变capacity()的大小,所以对vector进行压缩非常重要。

C++11中已经提供了shrink_to_fit()函数实现vector的压缩。

#include <iostream>
#include <vector>
int main()
{
    std::vector<int> v;
    std::cout << "Default-constructed capacity is " << v.capacity() << '\n';
    v.resize(100);
    std::cout << "Capacity of a 100-element vector is " << v.capacity() << '\n';
    v.clear();
    std::cout << "Capacity after clear() is " << v.capacity() << '\n';
    v.shrink_to_fit();
    std::cout << "Capacity after shrink_to_fit() is " << v.capacity() << '\n';
}

结果

Default-constructed capacity is 0
Capacity of a 100-element vector is 100
Capacity after clear() is 100
Capacity after shrink_to_fit() is 0

vector和list区别

stl提供了三个最基本的容器:vector,list,deque。

vector和built-in数组类似,它拥有一段连续的内存空间,并且起始地址不变,因此它能非常好的支持随即存取,即[]操作符,但由于它的内存空间是连续的,所以在中间进行插入和删除会造成内存块的拷贝,另外,当该数组后的内存空间不够时,需要重新申请一块足够大的内存并进行内存的拷贝。这些都大大影响了vector的效率。

list就是双向链表(根据sgi stl源代码),因此它的内存空间可以是不连续的,通过指针来进行数据的访问,这个特点使得它的随即存取变的非常没有效率,因此它没有提供[]操作符的重载。但由于链表的特点,它可以以很好的效率支持任意地方的删除和插入。

deque是一个double-ended queue,它的具体实现不太清楚,但知道它具有以下两个特点:它支持[]操作符,也就是支持随即存取,并且和vector的效率相差无几,它支持在两端的操作:push_back,push_front,pop_back,pop_front等,并且在两端操作上与list的效率也差不多。

因此在实际使用时,如何选择这三个容器中哪一个,应根据你的需要而定,一般应遵循下面的原则:

  1. 如果你需要高效的随即存取,而不在乎插入和删除的效率,使用vector
  2. 如果你需要大量的插入和删除,而不关心随即存取,则应使用list
  3. 如果你需要随即存取,而且关心两端数据的插入和删除,则应使用deque。

vector为存储的对象分配一块连续的地址空间,因此对vector中的元素随机访问效率很高。在vecotor中插入或者删除某个元素,需要将现有元素进行复制,移动。如果vector中存储的对象很大,或者构造函数复杂,则在对现有元素进行拷贝时开销较大,因为拷贝对象要调用拷贝构造函数。对于简单的小对象,vector的效率优于list。vector在每次扩张容量的时候,将容量扩展2倍,这样对于小对象来说,效率是很高的。

list中的对象是离散存储的,随机访问某个元素需要遍历list。在list中插入元素,尤其是在首尾插入元素,效率很高,只需要改变元素的指针。

综上所述:

vector适用:对象数量变化少,简单对象,随机访问元素频繁;

list适用:对象数量变化大,对象复杂,插入和删除频繁。

参考文献:

推荐阅读更多精彩内容

  • 前言: 详细介绍: List:元素有放入顺序,元素可重复Map:元素按键值对存储,无放入顺序Set:元素无放入顺序...
    YBshone阅读 7,962评论 0 17
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 30,757评论 18 399
  • 标签(空格分隔): STL 运用STL,可以充分利用该库的设计,让我为简单而直接的问题设计出简单而直接的解决方案,...
    认真学计算机阅读 1,325评论 0 10
  • 总结:侯老师的这门stl课,只看视频和ppt是不太够的。应该结合侯老师的《stl源码剖析》和Nicolai M J...
    诵之思之阅读 184评论 3 0
  • 一. Java基础部分.................................................
    wy_sure阅读 3,458评论 0 11