概述
1.C++ STL的容器分为三种,序列式容器,关联式容器,无序式容器,这里先说说常用的序列式容器。
2.array,vector,deque,list,forward_list这几种都是序列式容器,序列容器是以线性序列的方式存储元素,也就是说,在内存,是连续分配一块内存来存放元素的,是可以经过下标迭代的方式遍历元素。
3.C++的容器是不能用来装引用和函数的,容器的设计原则是以效率为先,安全为次的,所以容器本身并不带有异常处理。
一.基本用法
所有的序列式容器都有一些共同的属性和用法,这里我拿最学常用的std::vecot来举例子,这些属性array,vector,deque,list,forward_lis的使用方法是一样的。
构造:
using T = std::vector<float>;T a;T b(a);//拷贝构造T d = a;//拷贝构造,调用了operator =T e(std::move(b));//右值引用,执行完之后b里面为空值,但b所占的内存不变T c{a };//C++ 11之后支持的拷贝方法T g(a.begin(), a.end());//区间拷贝
属性:
T t;int i = t.size();//返回容器大小,为了空间上的极致效率,forward_list没有这个属性bool tf = t.empty();//容器是否为空,等价于 t.size() != 0,但效率上要高一些auto max = t.max_size();//当前环境下最多还存放多少个该元素t.swap(a);//交换两个容器,但类型必须相同,实现是交换两个容器的指针(array除外)swap(t, a);//等价于上面t.begin();//返回容器头部迭代器位置t.end();//返回容器末尾部的迭代器合法位置的下一个位置,如果要解引用 *(t.end()-1)t.cbegin();//跟上面两不一样的是,这个返回的是const迭代器t.cend();t.rbegin();//反过来的迭代器t.rend();t.front();//返回头部元素的引用t.back();//返回尾部元素的引用t.clear();//清空容器,调用类的析构函数
二.std::array
1.概念
1.1 std::array是在C++11中才引入的容器,其实就是C++11对内置数组的重新封装,array对象在实例化的时候大小是固定的,并不支持动态添加或者删除元素,所以array默认是构造一个非空的实例。array的内存是分配在栈上的,对应的拷贝和赋值都要花掉一定的空间,绝不会重新分配内存。
1.2 在知道容器的大小情况,或者要与C进行交互时可以考虑使用std::array,毕竟array比内置的数组增加了一些常用的接口和可以对下标进行检查。
2. std::array的构造:
std::array<float, 100> a;//类型是float,有100个元素std::array<float, 100> b = {};//全部按float的默认值初始化std::array<float, 5> d = {1.0f,33.0f,2.1f,4.3f,7.0f };//具体值初始化std::array<float, 5> c = {2.0f};//第一个元素初始化为2.0,其余按类型默认值初始化
3.std::array的元素访问
遍历方法:
//传统方法遍历for (size_t i = 0; i < d.size(); ++ i){std::cout << d[i] << std::endl;//中括号是不检查下标是否合法std::cout << d.at(i) << std::endl;//.at()会检查下标,如果不合法则抛出异常}//迭代器遍历for (auto iter = d.begin(); iter != d.end(); ++iter){std::cout << *iter << std::endl;//解引用}//auto 方式遍历for (auto v : d){std::cout << v << std::endl;}
与C接口互用
std::array<char, 10> c_str;strcpy(c_str.data(), "array char\n");//在vs中头文件要加#include <cstring>,项目属性/C++/预处理器加入:_CRT_SECURE_NO_WARNINGSprintf("%s", c_str.data());
5.交换函数:
t.swap(a);//交换两个容器,array是交换两个容器里面的值,而不是容器指针,这样容器一大效率就会降低swap(t, a);//等价于上面
三.std::vector
概念1.1 std::vector 是C++ 98就引入的动态数组,vector会在需要自动调整所占内存的大小,vector的这个特性让它比起静态数组所占用的内 存更多一些,因为它还分配了额外的内存以应对将来可能的内存扩张,在使用过程中,不用因为每次插入新的元素而重新分配内存,除非预留分配的那块内存用完,这样有效率会有很大的提升。
1.2 std::vector的特点是能随机访问容器内的元素,在容器末端添加或者删除元素效率比较高,前端和中间则效率相对会低一些。构造
std::vector<float> d(10);//创建容器里面全部初始化为类型默认值(0.0)std::vector<float> e(10, 1.0f);// 创建容器里面全部初始化为1.0std::vector<float> c(e.begin(), e.end());//迭代器方式拷贝//C++ 11 std::vector<float> h({1.01, 1.1, 4.0, 3.0, 5.5 });std::vector<float> d{1.2, 3.7, 0.2, 10.4, 3.6 };
属性
auto mun = h.capacity(); //返回容器当前能够容纳的元素数量h.reserve(10); //为容器预先分配内存,避免重新分配内存。h.assign(8, 1.1f);//压缩或者放大内存并指定默认值h.assign(e.begin(), e.end());//等价于 h = eh.assign({2.0,1.5,3.9});//压缩并指定初始化h.pop_back();//删掉最后一个元素,这里要确定容器不能为空h.push_back(11.12);//在容器尾部插入一个元素auto iterAfter = h.erase(h.begin());//删掉指定元素,删掉后后面的元素要向前填补,//返回要删除元素的下一个元素的引用,如果全部//删完,则返回end(),使用该方法之后,迭代器有//可能会失效(pop_back(),push_back()也有这个现象)//因为vector有可能重新分配内存h.erase(h.begin(), h.end());//区间删掉元素,要确定区间值是闭合的auto iter = h.insert(h.end(),12.7f);//在指定位置插入一个新的元素,返回迭代器位置iter = h.insert(h.end(),10,12.1f);//在指定位置插入10元素,并初始化为指定值h.insert(h.end(), e.begin(), e.end());//在指定位置插入另一个容器区间h.resize(10);//缩小或者放大容器h.resize(20, 1.2f);//放大容器,如果之前不满20个,则插入的元素初始化为1.2,//如果原本的容器大于20个,则忽略掉。h.clear();//清空掉容器里面所有的值,但容器的所占的内存不会变小h.shrink_to_fit();//请求容器降低其容量与当前所要存的元素匹配,//但主动权在编译器,它决定是否真正释放多余的内存,//这个方法只是提出请求,是否要实现由编译器说了算//目前大部分的实现是内存能降下来t.swap(a);//交换两个容器,vector是交换两个容器的指针swap(t, a);//等价于上面
元素访问与容器遍历与array一样
//传统方法遍历for (size_t i = 0; i < h.size(); ++i){std::cout << h[i] << std::endl;//中括号是不检查下标是否合法std::cout << h.at(i) << std::endl;//.at()会检查下标,如果不合法则抛出异常}//迭代器遍历for (auto iter = h.begin(); iter != h.end(); ++iter){std::cout << *iter << std::endl;//解引用}//auto 方式遍历for (auto v : h){std::cout << v << std::endl;}
与C接口互用
std::vector<char> c_str(20);strcpy(c_str.data(), "vector char\n");//在vs中头文件要加#include <cstring>,项目属性/C++/预处理器加入:_CRT_SECURE_NO_WARNINGSprintf("%s", c_str.data());
四.std::deque
1.概念
1.1 std::deque是双端队列,头尾两端插入和删除元素比较高效,中间删除和添加元素效率低,在std::deque两端插入和删除并不会使其它元素的指针或引用失效。
1.2 元素的访问和迭代比vector慢,因为迭代器是智能指针而不是普通指针,内存分配不连续。
1.3 std::deque的存储空间会自动按需扩大和缩小,扩大std::deque比扩大std::vector的效率和开销会低很多,因为它不涉及到现有元素复制到新的内存位置。
2.定义方法和定义vecot的方法一样,没有不一样的地方。
3.属性
std::deque不提供 capacity(),reserve()这两个方法,因为deque分配的内存是一块一块的,不需要知道预分配多少内存。
std::deque<float> d{1.2,3.7,0.2,10.4,3.6 };d.pop_back();//删除最后一个元素d.pop_front();//删除头元素;//在头部添加元素d.push_front(2.9f);d.emplace_front(3.7f);//在尾部添加元素d.push_back(3.3f);d.emplace_back(1.1f);d.emplace(d.end(),10.0f);//插入元素,指定初始化
4.元素访问与容器遍历与vector一样。
5.deque不能和C接口交互。
6.交换两个容器方法和效率跟vector一样。
五.std::list
1.概述
1.1 list是一个双向链表,不支持随机访问元素,访问头部和尾部元素速度快。
1.2 在list中任何位置进行插入/删除操作速度都很快,常量时间内完成,插入和删除不会造成迭代器失效。
1.3 对异常支持较好,出现异常对list而言,要么成功,要么什么影响都没有。
1.4 list在空间的成本上要比vector,deque高很多,因为list是一个双向列表,每个元素要有一个往前指针和往后的指针,list本身内部的元素是通过new的方式来生成的,比如用list存一个char元素时,在64位系统下,要开支33个字节左右(往前指针8个字节 + 往后指针8个字+new的16个字节 + char本身的一个字节)。
2.构造方法与vector一样。
3.属性。
list是一个个分配内存的,所以不提供capacity(),reserve(),shrink_to_fit() 这三个方法。
std::list<float> d{1.2, 3.7, 0.2, 10.4, 3.6 };d.pop_back();//删除最后一个元素d.pop_front();//删除头元素//在头部添加元素d.push_front(2.9f);d.emplace_front(3.7f);//在尾部添加元素d.push_back(3.3f);d.emplace_back(1.1f);d.emplace(d.end(), 10.0f);//插入元素,指定初始化
4.元素访问与遍历
list不支持[]和at访问元素。
//访问遍历元素auto iter_begin = d.begin();for (int i = 0; i < d.size()-1; ++i){std::cout << *(++iter_begin) << std::endl;}auto iter = std::next(iter_begin,2);//访问第三个元素,返回迭代器引用std::cout << *iter << std::endl;//auto遍历元素for (auto v : d){std::cout << v << std::endl;}
5.算法
d.remove(1.2);//删除掉容器里面所有等于1.2的值d.remove_if([](auto v) {return v > 5.0f; });//条件删除d.reverse();//翻转整个容器d.sort();//排序,默认是从小到大//std::sort(d.begin(), d.end());//不能这么用h.sort();d.merge(h);//把两个排好序的容器合并成一个容器,之后h里面没有元素d.unique();//把两个排好序的容器里面重复的元素删掉d.splice(d.begin(),c);//把C容器安插到d的指定位置
5.list不能和C接口交互。