栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

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

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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

文章目录
  • 容器的概述
    • 容器
    • 容器分类
    • 迭代器
  • 序列容器
    • Array
    • Vector
    • list/forward_list
    • deque/basic_string

容器的概述

容器

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

容器分类

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

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

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

生成器:
构造元素序列。

迭代器

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

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

#include 
#include 
using namespace std;

int main()
{
    std::vector 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 
#include 
using namespace std;

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

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

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

Array

具有固定长度的容器,其内部维护了一个内建数组,与内建数组相比提供了复制操作。内建数组是不支持复制的,但是Array是支持的。

成员类型

#include 
#include 
#include 
using namespace std;

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

}

成员访问:元素访问: [] , at , front , back , data

#include 
#include 
#include 
using namespace std;

int main()
{
    std::array 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 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 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 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 a;
    for (int i = 0; i < 1024; ++i)
    {
        a.emplace_back(i);
    }
}

两者区别:

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

list

#include 
#include 
#include 
using namespace std;

int main()
{
    std::list 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 
#include 
#include 
#include 
using namespace std;

int main()
{
    std::forward_list 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

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/992164.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号