C++ 基础与深度分析 Chapter9 序列与关联容器(容器的概述、序列容器)


在这里插入图片描述

容器的概述

在这里插入图片描述

容器

一种特殊的类型,其对象可以放置其它类型的对象(元素)。
需要支持的操作:对象的添加、删除、索引、遍历,并不是有的容器都支持扇面4种操作,有的不支持添加、删除,有的不支持索引。适合添加元素的容器,可能查找就比较慢,适合查找的元素,可能添加就比较慢。

容器分类

序列容器:
其中的对象有序排列,使用整数值进行索引。

关联容器:
其中的对象顺序并不重要,使用键进行索引。
map就是一种关联容器。不同的关联容器,对键的需求不一样。

适配器:
调整原有容器的行为,使得其对外展现出新的类型、接口或返回新的元素。
将原有的容器行为进行调整。

生成器:
构造元素序列。

迭代器

用于指定容器中的一段区间,以执行遍历、删除等操作。

获取迭代器: ©begin/©end ; ©rbegin/©rend

#include <iostream>
#include <vector>
using namespace std;

int main()
{
    std::vector<int> x{1, 2, 3, 4, 5};
    auto b = x.begin();
    auto e = x.end();
    for (auto ptr = b; ptr < e; ++ptr)
    {
        cout << *ptr << endl;
    }
}

如果使用了cbegin和cend,表示迭代器是只读的,不能写入。
rbegin和rend

#include <iostream>
#include <vector>
using namespace std;

int main()
{
    std::vector<int> x{1, 2, 3, 4, 5};
    auto b = x.rbegin(); // 反向迭代器,b指向最后一个位置 
    auto e = x.rend(); // e指向第一个元素前面的一个位置
    for (auto ptr = b; ptr < e; ++ptr)
    {
        cout << *ptr << endl; // 5 4 3 2 1
    }
}

并非所有的迭代器都能完成像数组指针这样的功能。某个迭代器可能不支持某种操作,比如a+3;迭代器是泛化和模拟指针的操作,但是不是所有迭代器都有完全指针的功能。

迭代器分类:分成 5 类( category ),不同的类别支持的操作集合不同
在这里插入图片描述
Input Iterator 输入迭代器
Output Iterator 输出迭代器
Forward Iterator 前向迭代器:只能一点点往前走
Bidirectional Iterator 双向迭代器:既能往前走,又能往后走
Random Access Iterator 随机访问迭代器:不知加1,还能加2,减2等。

支持迭代器的称为range。

序列容器

在这里插入图片描述
array :元素个数固定的序列容器,不支持对象的添加和删除。和数组和相似。
vector :元素连续存储的序列容器,可以添加删除,元素个数可变。在内存中是连续存储的。
forward_list / list :forward_list 基于链表 / list 双向链表的容器,

deque : vector 与 list 的折衷,vector是连续村粗,list是通过链表指针。deque把整个容器分成若干个段,段与段是链表的模式。

basic_string :提供了对字符串专门的支持,是一个模版。

需要使用元素类型来实例化容器模板,从而构造可以保存具体类型的容器。

int main()
{
    std::vector<int> x; // vector这个模版可以包含什么类型的元素
    std::vector<double> y;
}

不同的容器所提供的接口大致相同,但根据容器性质的差异,其内部实现与复杂度不同。只要掌握了一种容器,其他容器也可以照葫芦画瓢了。同样一个操作,对某些容器快,对某些可能比较慢。

对于复杂度过高的操作,提供相对较难使用的接口或者不提供相应的接口。

Array

具有固定长度的容器,其内部维护了一个内建数组,与内建数组相比提供了复制操作。内建数组是不支持复制的,但是Array是支持的。
在这里插入图片描述
成员类型
在这里插入图片描述

#include <iostream>
#include <vector>
#include <array>
using namespace std;

int main()
{
    int a[3]; // 
    // int b[3] = a; // 内建数组不支持复制
    // error: array initializer must be an initializer list
    std::array<int, 3> x; // 提供元素类型和包含元素的个数
    std::array<int, 3> y = x;
    std::array<int, 3>::value_type; // value_type是array中类型

}

成员访问:元素访问: [] , at , front , back , data
在这里插入图片描述
在这里插入图片描述

#include <iostream>
#include <vector>
#include <array>
using namespace std;

int main()
{
    std::array<int, 3> x = {2, 4, 6}; 
    cout << x[4] << endl; // []索引,越界不报错
    // cout << x.at(3) << endl; // 越界,报错
    cout << x.front() << endl; // 2
    cout << x.back() << endl; // 6
    x.data(); // 返回一个int*,指向数组的第一个元素
    cout <<  *(x.data()) << endl; // 2 如果元素是连续保存的,那么一般提供一个data接口

}

容量相关(平凡实现): empty , size , max_size

int main()
{
    std::array<int, 3> x = {2, 4, 6}; 
    cout << x.empty() << endl; // 0 非空
    cout << x.size() << endl; // 3
    cout << x.max_size() << endl; // 3 最多包含的元素个数
}

填充与交换: fill , swap

int main()
{
    std::array<int, 3> x;
    x.fill(100);
    cout << x[0] << ' ' << 
            x[1] << ' ' <<
            x[2] << ' ' << endl;
}

swap复制成本是非常高的
在这里插入图片描述
比较操作: <=> c++20,字典序比较
在这里插入图片描述
在这里插入图片描述
迭代器
在这里插入图片描述

Vector

在这里插入图片描述

vector 容器模板:元素个数可变
在这里插入图片描述
可以看到,vector如果用swap方法,只改变指针就可以。所以比array快。

size元素数量,cap目前vector容量是多少。
empty、size、max_size都不是平凡实现了。

容量相关接口: capacity / reserve / shrink_to_fit


int main()
{
    std::vector<int> a;
    cout << a.capacity() << endl; // 1
    a.reserve(1024); // 刚开始就开辟了1024个buffer
    for (int i = 0; i < 1024; ++i)
    {
        a.push_back(i);
    }

}

当vector不会再填充元素,那么用shrink_to_fit,释放原有buffer,拷贝一个大小正好的内存过来。这样没有浪费更多的内存资源。

附加元素接口: push_back / emplace_back

int main()
{
    std::vector<int> a;
    for (int i = 0; i < 1024; ++i)
    {
        a.emplace_back(i);
    }
}

两者区别:

int main()
{
    std::vector<std::string> a;
    a.push_back("hello"); //先构造一个string,再将hello添加
    a.emplace_back("hello"); // 直接生成hello string
}

元素插入接口: insert / emplace
一般是在vector的中间插入,涉及到其他元素的后移,性能要比push_back和emplace_back低
在这里插入图片描述
在这里插入图片描述
元素删除接口: pop_back / erase / clear
pop_back删除最后一个元素,erase是指定一个位置元素进行删除,后面元素要往前挪动。clear删除所有元素。

vector 不提供 push_front / pop_front ,可以使用 insert / erase 模拟,但效率不高。

Vector的swap 效率较高

写操作可能会导致迭代器失效
迭代器失效的操作
在这里插入图片描述

list/forward_list

在这里插入图片描述
vecotr:

int main()
{
    std::vector<int> a{1, 2, 3};
    for (auto ptr = a.begin(); ptr < a.end(); ++ptr)
    {
        cout << *ptr << endl;
    }
}

list

#include <iostream>
#include <vector>
#include <list>
using namespace std;

int main()
{
    std::list<int> a{1, 2, 3};
    for (auto ptr = a.begin(); ptr != a.end(); ++ptr)
    {
        cout << *ptr << endl;
    }
}

list 容器模板:双向链表
list的插入和删除成本低,随机访问成本高。vector相反,插入和删除成本低,但是随机访问成本低。索引就是ptr+索引数,因为是连续存储的,所以可以直接知道地址为多少。

一些操作:
在这里插入图片描述
pop_front, push_front开头删除和插入元素。
splice:可以把list,移到另外一个list
在这里插入图片描述

forward_list 容器模板:单向链表
目标:一个成本较低的线性表实现。

#include <iostream>
#include <vector>
#include <list>
#include <forward_list>
using namespace std;

int main()
{
    std::forward_list<int> a{1, 2, 3};
    for (auto ptr = a.begin(); ptr != a.end(); ++ptr)
    {
        cout << *ptr << endl;
    }
}

forward_list只支持递增操作,只能往后走,获取它的后继,不能获取它的前驱。没有rbegin和rend来反向遍历。list就可以,因为list是双向链表。
list是支持size的,但是forward_list不支持size。因为list虽然是双向链表,但是内部有一块空间用来保存第几个元素。但是forward_list因为是一个成本比较低的数据结构,所以没有开辟这样的额外空间。

不支持 pop_back / push_back。

在这里插入图片描述
为什么都是after操作,因为单向链表只知道后面的地址,不知道之前的。

deque/basic_string

在这里插入图片描述
deque双端队列
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
push_back / push_front 速度较快
deque是支持中括号随机访问的。知道左边每个格子包含几个灰色的格子,就可以通过中括号去找到相应的位置。但是和vector的速度是没法比的,但比list多了中括号的计算。deque也可以在序列中间添加和删除元素,但是效率很慢,比list慢很多。

basic_string: 容器模板:实现了字符串相关的接口
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
提供了数值与字符串转换的接口
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
针对短字符串的优化Short String Optimization SSO
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值