在 C/C++ 11 之中引入了:std::array 的模板定义,它是静态一维数组,在 C/C++ 面试之中,有些人就会追毛求次的问这些基础上的问题。
但在本文当中,本人不会用面试那帮人不知道哪里找的对照答案来作为参考,我认为一个良好的 C/C++ 开发人员应当知晓,它们之间的实现原理及差异点,而不是拿八股文当经书,这或许会被某些 “人” 认为有些离经叛道。
std::array 是在 C/C++ 11 之中被引入的静态数组模板,它与我们传统在栈上直接分配数组是没有区别,但它提供了相对更好的安全性。
这是因为,std::array 重载了 “C/C++ 索引器” 运算符,并且在每次访问数组时,都会检查数组的索引边界问题,但这可能会降低一定的效能。
例如:
在传统数组中,假设为:int arr[10];,那么我们访问 arr[10],时已经产生了越界问题,是不会报错的,但 std::array<int, 10> arr; 我们访问 arr[10],是会触发 ASSERT 断言的。
不过该断言在 release 编译时会被优化掉,debug 下有效,在 release 下由于被优化掉,会退化成 C/C++ 11 之前的数组定义访问,该存在的潜在索引越界问题,或许仍会存在。
忘了提一嘴:
std::array 跟原生静态数组在汇编上也会有一些细微上的区别,因为 std::array 有 ecx/rcx 寄存器来存储 this 的地址,即每次需要压入 this 地址进去,虽然速度上看不出来什么区别就是了,但你非要找点茬,那就说慢一丝丝也可以。
但实际上在 release 上面会被特别优化的,C/C++ 就喜欢这么玩,标准库好多东西,在最终编译的时候都会有特殊的编译器实现跟优化的,有时候 C/C++ 标准库跟编译器都有点玄学,C语言这块就比较好,不搞那么多令人困惑的东西。
当然:
在 Debug 下通常解决好索引边界问题之后,编译为 Release 通常并不会导致索引边界问题的产生。
std::array 与静态数组最大区别,体现在:new 上面, 当我们欲分配一个名为 class Foo;的对象数组时。
则为:
new Foo[N],与分配 N个长度的 Foo 对象数组 new std::array<Foo, N> 它们在 C/C++ 之中是不同的。
new Foo[N] 会额外多分配一部分长度,用来存储数组的个数,以便于 delete[] 时可以正确的释放数组成员数据,但 new std::array<Foo, N> 分配是不会多出这部分内存占用的。
这个是 std::array 跟 C/C++ 原生静态数组在本质上的区别,至于边界索引检查只在 Debug 下面有效,这不是什么真正意义的重点,但可惜那帮面试的装13的货色,或许有些拎不清楚。
取决于栈上分配跟堆栈分配,它们都是静态数组的一种,但是区别上面很小,基本两者之间是等同。
std::vector 动态矢量数组,与 std::array、原生静态数组之间的差距就比较大了。
1、std::vector 容器本身可分配在栈或堆
2、std::vector 容器元素只能在堆上分配
3、std::vector 容器在没有指定容量及插入元素的情况下,不会在堆上分配元素内存
4、std::vector 容器在不同的平台标准库之中实现是不同的
4.1、在 Windows 平台上面,VC++标准库之中插入元素当容量小于时,则扩大元素一个位置并重新分配内存,在把原内存上的所有元素复制到新的内存上,在释放。
但它一个缺点,当我的容量一直小于当前插入元素个数时,每次插入都需要扩展/复制数组成员的内存,效率上会非常感人。
不过VC++ 标准库之中,对 std::vector 存储更多的元素有一些小优化,取决于算法当前数组容量大小 + 1(前提需要扩容时),但或许会很鸡肋。
_CONSTEXPR20 size_type _Calculate_growth(const size_type _Newsize) const {
// given _Oldcapacity and _Newsize, calculate geometric growth
const size_type _Oldcapacity = capacity();
const auto _Max = max_size();
if (_Oldcapacity > _Max - _Oldcapacity / 2) {
return _Max; // geometric growth would overflow
}
const size_type _Geometric = _Oldcapacity + _Oldcapacity / 2;
if (_Geometric < _Newsize) {
return _Newsize; // geometric growth would be insufficient
}
return _Geometric; // geometric growth is sufficient
}
4.2、在 Linux 平台上面,GCC标准库之中在前面256个元素时为*2扩容,它可以显著的减少插入元素时重复拷贝带来的性能开销。
如:当容量为0时插入一个元素,扩容空间为4,当4满足后扩容为8,但这或会带来更多的内存占用,但好在只有256个大小,即在GCC之中 std::vector 在元素容量 <= 256 时,能效比会比较好。
5、std::vector 可以自定义 allocator 堆内存分配器,但在 std::array、原生静态数组之中不可以
6、std::vector 删除或 clear 元素时,通常不会减少容量大小,但这可能一直占用过多的内存。
通常情况下,std::vector 容器都不应该被,当作一直被持有使用的内存容器,对于可变动元素容器,应首选 std::list 容器来代替。
另外若无必要,不要首选 std::vector 容器,它可能带来一定的性能上面的风险问题,若必须使用 std::vector 容器,那么可以首选自行实现一个适用于业务场景调优的 vector 容器出来,但若 vector 频繁擦写的数量并不多时,可以考虑直接适用它。