C/C++ 标准库 std::vector、std::array、原生静态数组 的区别有哪些?

本文讨论了C++11中std::array的特点,包括其与原生数组的边界检查差异、内存分配策略以及与std::vector的对比,强调开发者应理解原理而非死记硬背面试题。
摘要由CSDN通过智能技术生成

在 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 频繁擦写的数量并不多时,可以考虑直接适用它。

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值