C++ 泛型编程范式的基础:容器

容器,就是能够“容纳”“存放”元素的一些数据结构。“算法 + 数据结构 = 程序。”在 C++ 里,容器就是这个公式里面的“数据结构”。

1. 容器

所谓的数据结构,就是数据在计算机里的存储和组织形式,比如堆、数组、链表、二叉树、B+ 树、哈希表,等等。有一种数据结构是万能的、可以应用于任何场景。毕竟,不同的数据结构存储数据的形式不一样,效率也就不一样。有的是连续存放,有的是分散存放,有的存储效率高,有的查找效率高,我们必须要依据具体的应用场合来进行取舍。

对于最基本、最经典的那些数据结构,C++ 标准库里的容器就已经把它们给实现了。

容器,其实就是 C++ 对数据结构的抽象和封装。而且,因为标准库开发者的功力很深,对编译器的了解程度更是远超你我,所以,容器的性能和优化水平要比我们自己写的好上几十倍,这一点你绝对不用质疑。

容器的通用特性

所有容器都具有的一个基本特性:它保存元素采用的是“值”(value)语义,也就是说,容器里存储的是元素的拷贝、副本,而不是引用。从这个基本特性可以得出一个推论,容器操作元素的很大一块成本就是值的拷贝。所以,如果元素比较大,或者非常多,那么操作时的拷贝开销就会很高,性能也就不会太好。

一个解决办法是,尽量为元素实现转移构造和转移赋值函数,在加入容器的时候使用 std::move() 来“转移”,减少元素复制的成本:

Point p;                        // 一个拷贝成本很高的对象

v.push_back(p);                // 存储对象,拷贝构造,成本很高
v.push_back(std::move(p));    // 定义转移构造后就可以转移存储,降低成本

也可以使用 C++11 为容器新增加的 emplace 操作函数,它可以“就地”构造元素,免去了构造后再拷贝、转移的成本,不但高效,而且用起来也很方便:

v.emplace_back(...);            // 直接在容器里构造元素,不需要拷贝或者转移

在容器里存放元素的指针,来间接保存元素,但不建议采用这种方案。虽然指针的开销很低,但因为它是“间接”持有,就不能利用容器自动销毁元素的特性了,你必须要自己手动管理元素的生命周期,麻烦而且非常容易出错,有内存泄漏的隐患。如果真的有这种需求,可以考虑使用智能指针 unique_ptr/shared_ptr,让它们帮你自动管理元素。弄清楚这两个智能指针之间的差异,区分“独占语义”和“共享语义”。

一般情况下,shared_ptr 是一个更好的选择,它的共享语义与容器的值语义基本一致。使用 unique_ptr 就要当心,它不能被拷贝,只能被转移,用起来就比较“微妙”。

容器的具体特性

所有容器的“共性”,再来看看具体容器的“个性”。C++ 里的容器很多,但可以按照不同的标准进行分类,常见的一种分类是依据元素的访问方式,分成顺序容器,有序容器和无序容器三大类别,先看一下最容易使用的顺序容器。

2. 顺序容器

顺序容器就是数据结构里的线性表,一共有 5 种:array、vector、deque、list、forward_list。按照存储结构,这 5 种容器又可以再细分成两组。

  • 连续存储的数组:array、vector 和 deque。
  • 指针结构的链表:list 和 forward_list。

array 和 vector 直接对应 C 的内置数组,内存布局与 C 完全兼容,所以是开销最低、速度最快的容器。它们两个的区别在于容量能否动态增长。array 是静态数组,大小在初始化的时候就固定了,不能再容纳更多的元素。而 vector 是动态数组,虽然初始化的时候设定了大小,但可以在后面随需增长,容纳任意数量的元素。

array<int, 2> arr;                // 初始一个array,长度是2
assert(arr.size() == 2);        // 静态数组的长度总是2

vector<int> v
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值