C++序列式容器(数据线性排列)

序列式容器介绍

  所谓的序列容器就是以线性排列(类似于数组的存储方式)来存储某一指定类型的数据(int、double···),该类容器不会自动对存储的元素按照值得大小进行排序。
  序列式容器大致包含下面几类:

  • array< T,N >(数组容器):表示可存储N个T类型的元素,是C++自身所提供的一种元素,此类容器一旦建立,其长度固定不变,意味着不能增加或删除元素,只能改变元素的值;
    在这里插入图片描述
  • vector< T >(向量容器):用来存放T类型的元素,是一个长度可变的序列容器,即在存储空间不足时会自动申请内存,使用此容器在尾部增加或删除元素得效率最高(时间复杂度为O(1)),但在其他位置插入或删除元素效率较差(时间复杂度为O(n),n为容器中元素个数);
    在这里插入图片描述
  • deque< T >(双端队列容器):和vector相似,区别在于使用该容器不仅尾部插入和删除元素高效,在头部插入或删除元素同样高效,时间复杂度都为O(1),但其他位置插入或删除时间复杂度同样为O(n);
    在这里插入图片描述
  • list< T >(链表容器):是一个长度可变的、由 T类型元素组成的序列,它以双向链表的形式组织元素,在这个序列的任何地方都和搞笑的增加或删除元素(时间复杂度为O(1)),但访问容器中任意元素的速度比前三种容器慢,和链表一样都需要从头开始遍历;
    在这里插入图片描述
  • forward_list< T >(正向链表容器):和list容器类似,它是以单链表的形式组织元素,它内部的元素只能从第一个元素开始访问,是一类比链表容器快,更节省内存的容器;
    在这里插入图片描述

其实除此之外,stack 和 queue 本质上也属于序列容器,只不过它们都是在 deque 容器的基础上改头换面而成,通常更习惯称它们为容器适配器。

array数组容器

  前面有介绍说array<T,N>就是一种相当于标准数组得容器类型,他是一个有N个T类型元素的固定序列,除了需要指定元素的类型和个数外,和常规数组没有太大差别,使用array容器类型时需要在源文件中包含头文件array,下面是一个创建具有10个int类型元素的对象:

std::array<int,10> data;

  若定义了一个array容器没有对其初始化,其不会自动初始化,下面的语句可将对象初始化为0:

std::array<int, 10> data {};

 可通过调用数组对象得成员函数fill()将所有元素设成给定值:

data.fill(20);

  可通过方括号 [ ] 索引方式访问和使用数组容器中的元素,例如:

data[2] = data[1] + data[0];

  这样的使用索引时不会做边界检查,所以若发生越界访问就不会被检测,如果检查越界索引值应该使用成员函数at(),若产生越界访问则会抛出std::out_of_rang 异常:

data.at(2) = data.at(1) + data.at(0);

  数组对象的size()函数能够返回size_t类型的元素个数值,所以能够像下面这样计算容器内元素的和:

int total {};
for(size_t i {} ; i < data.size() ; ++i)
{
    total += data[i];
}

  此外还可通过成员函数empty()来判断数组容器中是否为空。

扫描二维码关注公众号,回复: 11387642 查看本文章

vector向量容器

  vector与array容器最大的区别就是其可以自动增长容器的大小,从而可包含任意数量的元素,因此类型参数T不需要传入数量参数N,需要注意的是该容器只能在尾部高效地删除或添加元素。大多数情况下向量容器可方便、灵活地代替数组容器,其头文件为vector;该容器常用的成员函数如下:

成员函数 作用
vector() 无参构造函数,将容器初始化为空
vector(int n) 将容器初始化为有 n 个元素
vector(int n, const T & val) 假定元素的类型是 T,此构造函数将容器初始化为有 n 个元素,每 个元素的值都是 val
vector(iterator first, iterator last) first 和 last 可以是其他容器的迭代器。一般来说,本构造函数初始化的结果就是将 vector 容器的内容变成与其他容器上的区间 [first, last) —致
void clear() 删除所有元素
bool empty() 判断容器是否为空
void pop_back() 删除容器末尾的元素
void push_back( const T & val) 将 val 添加到容器末尾
int size() 返回容器中元素的个数
T & front() 返回容器中第一个元素的引用
T & back() 返回容器中最后一个元素的引用
iterator insert(iterator i, const T & val) 将 val 插入迭代器 i 指向的位置,返回 i
iterator insert( iterator i, iterator first, iterator last) 将其他容器上的区间 [first, last) 中的元素插入迭代器 i 指向的位置
iterator erase(iterator i) 删除迭代器 i 指向的元素,返回值是被删元素后面的元素的迭代器
iterator erase(iterator first, iterator last) 删除容器中的区间 [first, last)
void swap( vector & v) 将容器自身的内容和另一个同类型的容器 v 互换

  示例代码如下:

#include <iostream>
#include <vector>  //使用vector需要包含此头文件
using namespace std;
template <class T>
void PrintVector(const vector <T> & v)
{  //用于输出vector容器的全部元素的函数模板
    typename vector <T>::const_iterator i;
    //typename 用来说明 vector <T>::const_iterator 是一个类型,在 Visual Studio 中不写也可以
    for (i = v.begin(); i != v.end(); ++i)
        cout << *i << " ";
    cout << endl;
}
int main()
{
    int a[5] = { 1, 2, 3, 4, 5 };
    vector <int> v(a, a + 5);  //将数组a的内容放入v
    cout << "1) " << v.end() - v.begin() << endl;  //两个随机迭代器可以相减,输出:1)5
    cout << "2)"; PrintVector(v);  //输出:2)1 2 3 4 5
    v.insert(v.begin() + 2, 13);  //在 begin()+2 位置插人 13
    cout << "3)"; PrintVector(v);  //输出:3)1 2 13 3 4 5
    v.erase(v.begin() + 2);  //删除位于 begin()+2 位置的元素
    cout << "4)"; PrintVector(v);  //输出:4)1 2 3 4 5
    vector<int> v2(4, 100);  //v2 有 4 个元素,都是 100
    v2.insert(v2.begin(), v.begin() + 1, v.begin() + 3);  //将v的一段插入v2开头
    cout << "5)v2:"; PrintVector(v2);  //输出:5)v2:2 3 100 100 100 100
    v.erase(v.begin() + 1, v.begin() + 3);  //删除 v 上的一个区间,即 [2,3)
    cout << "6)"; PrintVector(v);  //输出:6)1 4 5
    return 0;
}

  运行结果:

1) 5
2)1 2 3 4 5
3)1 2 13 3 4 5
4)1 2 3 4 5
5)v2:2 3 100 100 100 100
6)1 4 5

deque双向队列

  deque也是顺序容器中的一种,使用前需要包含头文件deque它和vector很相似,因此所有适用于vector的操作同样适用于deque;它优于vector的是vector 在头部删除或添加元素的速度很慢,在尾部添加元素的性能较好,而 deque 在头尾增删元素都具有较好的性能,其有两个vector没有的成员函数:

void push_front (const T & val);  //将 val 插入容器的头部
void pop_front();  //删除容器头部的元素

list双向链表

  前面的一些结构都类似于数据结构中的数组,下面的list是双向链表的结构,使用前需要包含文件list,其内部组织元素中每个元素都有一个指针指向它的前驱(前一个元素),还有一个指针指向它的后继(后一个元素)如下图所示:
在这里插入图片描述
  list容器不支持随机访问元素,其有许多成员函数用法和vector类似,此外还有些独有的成员函数:

成员函数或模板 作用
void push_front(const T & val) 将 val 插入链表最前面
void pop_front() 删除链表最前面的元素
void sort() 将链表从小到大排序
void remove (const T & val) 删除和 val 相等的元素
void unique() 删除所有和前一个元素相等的元素
void merge(list & x) 将链表 x 合并进来并清空 x。要求链表自身和 x 都是有序的

  STL中的sort算法可对vector和deque排序,他需要随机访问迭代器支持,因为list不支持随机访问,所以引入了sort成员函数完成sort。list示例程序如下:

#include <list>  //使用 list 需要包含此头文件
#include <iostream>
#include <algorithm>  //使用STL中的算法需要包含此头文件
using namespace std;
class A {
private: int n;
public:
    A(int n_) { n = n_; }
    friend bool operator < (const A & a1, const A & a2);
    friend bool operator == (const A & a1, const A & a2);
    friend ostream & operator << (ostream & o, const A & a);
};
bool operator < (const A & a1, const A & a2) {
    return a1.n < a2.n;
}
bool operator == (const A & a1, const A & a2) {
    return a1.n == a2.n;
}
ostream & operator << (ostream & o, const A & a) {
    o << a.n;
    return o;
}
template <class T>
void Print(T first, T last)
{
    for (; first != last; ++first)
        cout << *first << " ";
    cout << endl;
}
int main()
{
    A a[5] = { 1, 3, 2, 4, 2 };
    A b[7] = { 10, 30, 20, 30, 30, 40, 40 };
    list<A> lst1(a, a + 5), lst2(b, b + 7);
    lst1.sort();
    cout << "1)"; Print(lst1.begin(), lst1.end());  //输出:1)1 2 2 3 4
    lst1.remove(2);  //删除所有和A(2)相等的元素
    cout << "2)"; Print(lst1.begin(), lst1.end());  //输出:2)1 3 4
    lst2.pop_front();  //删除第一个元素
    cout << "3)"; Print(lst2.begin(), lst2.end());  //输出:3)30 20 30 30 40 40
    lst2.unique();  //删除所有和前一个元素相等的元素
    cout << "4)"; Print(lst2.begin(), lst2.end());  //输出:4)30 20 30 40
    lst2.sort();
    lst1.merge(lst2);  //合并 lst2 到 lst1 并清空 lst2
    cout << "5)"; Print(lst1.begin(), lst1.end());  //输出:5)1 3 4 20 30 30 40
    cout << "6)"; Print(lst2.begin(), lst2.end());  //lst2是空的,输出:6)
    lst1.reverse();  //将 lst1 前后颠倒
    cout << "7)"; Print(lst1.begin(), lst1.end());  //输出 7)40 30 30 20 4 3 1
    lst2.insert(lst2.begin(), a + 1, a + 4);  //在 lst2 中插入 3,2,4 三个元素
    list <A>::iterator p1, p2, p3;
    p1 = find(lst1.begin(), lst1.end(), 30);
    p2 = find(lst2.begin(), lst2.end(), 2);
    p3 = find(lst2.begin(), lst2.end(), 4);
    lst1.splice(p1, lst2, p2, p3);  //将[p2, p3)插入p1之前,并从 lst2 中删除[p2,p3)
    cout << "8)"; Print(lst1.begin(), lst1.end());  //输出:8)40 2 30 30 20 4 3 1
    cout << "9)"; Print(lst2.begin(), lst2.end());  //输出:9)3 4
    return 0;
}

运行结果:

1)1 2 2 3 4
2)1 3 4
3)30 20 30 30 40 40
4)30 20 30 40
5)1 3 4 20 30 30 40
6)
7)40 30 30 20 4 3 1
8)40 2 30 30 20 4 3 1
9)3 4

forward_list单向链表

  forward_list是以单链表的形式存储元素,其模板定义在forward_list中,他和list最大的区别就是不能反向遍历元素,只能按顺序遍历,但也因此有一些特性:

  1. L没有可以返回最后一个元素引用的成员函数back();只有成员函数front();
  2. 因为只能通过自增前面元素的迭代器来到达序列的终点,所以push_back()、pop_back()、emplace_back()也无法使用;
  3. 无法使用反向迭代器。只能从它得到const或non-const前向迭代器,这些迭代器都不能解引用,只能自增。

  forward_list 的操作比 list 容器还要快,且占用内存少,forward_list 容器的构造函数的使用方式和 list 容器相同。但**没有成员函数size()**可通过定义在头文件中的iterator中的distance()函数得到元素的个数:

std::forward_list<std::string> my_words {"three", "six", "eight"};
auto count = std::distance(std::begin(my_words),std::end(my_words));
// Result is 3

distance()第一个参数为开始迭代器,第二个参数是结束迭代器,指定了元素范围,当需要将前向迭代器移动多个位置时,可以使用advance()函数:

std::forward_list<int> data {10, 21, 43, 87, 175, 351};
auto iter = std::begin(data);
size_t n {3};
std::advance(iter, n);
std::cout << "The " << n+1 << "th element is n << *iter << std::endl;
// Outputs 87

  advance() 函数会将前向迭代器自增需要的次数。这使我们不必去循环自增迭代器。其会自增第一个参数的迭代器但是不会返回它,因此其返回类型为void。

参考文章:STL序列式容器

猜你喜欢

转载自blog.csdn.net/weixin_42647166/article/details/105705767
今日推荐