C++ STL 容器 -- 道与术 篇(三) array

概述 

"通于一而万事毕" ——《庄子·天地》

        std::array 是C风格数组的替代品,增强版的固定大小的数组。其"道"在于编译时确定的连续内存布局和零开销抽象;其"术"在于丰富的STL接口和类型安全操作。

道法篇

        在《C++ STL 容器 -- 道与术 篇(一) vector》中,详细介绍了顺序列表vector的原理以及接口。array 跟vector类似,也是顺序列表,但与vector不同的是,array的大小是固定的,在编译时就决定了大小,不能在运行过程中进行修改,与C的数组类似。

vector  两个重点:

(1)在内存布局上是连续的--> 支持随机访问。

(2)固定大小。

系列特性说明:

基于这两个"道法"原理,决定了其一系列特性:

(1)编译时大小确定
template<typename T, std::size_t N>
class array {
    T _M_elems[N];  // 内嵌固定大小数组
    // 没有动态内存管理开销
};

     上图为一个大小为16 的aray的简单示例。

(2)性能优势
  • 访问复杂度:O(1) 随机访问

  • 无动态分配:编译期确定大小,无堆分配开销

  • 缓存友好:连续内存布局,提高缓存命中率

(3)内存开销分析
#include <iostream>
#include <array>
#include <stdint.h>

struct Test {
    uint64_t a, b, c;  // 24字节
};

int main() {
    std::array<Test, 3> arr;
    std::cout << "array大小: " << sizeof(arr) << "字节\n";  // 72字节
    std::cout << "元素大小: " << sizeof(Test) << "字节\n";  // 24字节
    // 总大小 = 3 × 24 = 72字节,无额外开销
}
/*
输出:
array大小: 72字节
元素大小: 24字节
*/
(4)与vector的内存对比
std::array<int, 100> fixed_arr;    // 栈上分配,400字节
std::vector<int> dynamic_vec(100); // 堆上分配,额外管理开销

// array内存布局:
// [栈帧]...[array数据]...[其他栈变量]

// vector内存布局:  
// [栈帧]...[vector控制块]...[堆上的数据]

编译时能力

#include <array>

// 编译时计算
constexpr std::array<int, 5> create_array() {
    std::array<int, 5> arr{};
    for (int i = 0; i < 5; ++i) {
        arr[i] = i * i;
    }
    return arr;
}

constexpr auto squares = create_array();
// 编译期就已经计算完成:{0, 1, 4, 9, 16}

使用 std::array 而不是C数组的主要理由:

  • ✅ 安全性:提供边界检查选项

  • ✅ 现代化:完整的STL容器接口

  • ✅ 便利性:内置有用成员函数,支持赋值操作

  • ✅ 类型安全:保持大小信息,不会意外退化为指针

  • ✅ 零开销:与C数组相同的性能和内存布局

  • ✅ 可维护性:更清晰、更少错误的代码

术法篇

        前面通过"道法"篇详细介绍了array的底层原理和内存布局。至此,你应该理解其零开销抽象的本质和性能优势。下面全面呈现std::array的完整API。

📋 完整API

🏗️ 成员类型
类型定义说明
value_typeT容器中存储的元素类型
size_typestd::size_t无符号整数类型,用于表示大小
difference_typestd::ptrdiff_t有符号整数类型,用于表示距离
referencevalue_type&元素的引用类型
const_referenceconst value_type&元素的常量引用类型
pointervalue_type*指向元素的指针类型
const_pointerconst value_type*指向常量元素的指针类型
iterator随机访问迭代器指向元素的迭代器
const_iterator常量随机访问迭代器指向常量元素的迭代器
reverse_iteratorstd::reverse_iterator<iterator>反向迭代器
const_reverse_iteratorstd::reverse_iterator<const_iterator>常量反向迭代器
🏗️ 构造函数
构造函数说明示例
array()默认构造(值初始化)std::array<int, 3> arr;
array(const array& other)拷贝构造std::array<int, 3> arr2(arr1);
array(array&& other) noexcept移动构造std::array<int, 3> arr3(std::move(arr1));
array(std::initializer_list<T> init)初始化列表构造std::array<int, 3> arr4{1,2,3};
🗑️ 析构函数
函数说明示例
~array()销毁所有元素自动调用
🔄 赋值操作
函数说明示例时间复杂度
array& operator=(const array& other)拷贝赋值arr2 = arr1;O(n)
array& operator=(array&& other) noexcept移动赋值arr3 = std::move(arr1);O(n)
array& operator=(std::initializer_list<T> ilist)初始化列表赋值arr4 = {4,5,6};O(n)
🔍 元素访问
函数说明示例异常安全
reference operator[](size_type pos)访问指定元素arr[0] = 1;无检查
const_reference operator[](size_type pos) constconst版本int x = arr[0];无检查
reference at(size_type pos)边界检查访问arr.at(0) = 1;越界抛出异常
const_reference at(size_type pos) constconst版本int x = arr.at(0);同上
reference front()访问第一个元素arr.front() = 1;空数组未定义
const_reference front() constconst版本int x = arr.front();同上
reference back()访问最后一个元素arr.back() = 1;空数组未定义
const_reference back() constconst版本int x = arr.back();同上
T* data() noexcept访问底层数组int* p = arr.data();
const T* data() const noexceptconst版本const int* p = arr.data();
🔄 迭代器
函数说明示例
iterator begin() noexcept指向首元素的迭代器auto it = arr.begin();
const_iterator begin() const noexceptconst版本auto it = arr.begin();
const_iterator cbegin() const noexcept指向首元素的const迭代器auto it = arr.cbegin();
iterator end() noexcept指向尾后位置的迭代器auto it = arr.end();
const_iterator end() const noexceptconst版本auto it = arr.end();
const_iterator cend() const noexcept指向尾后位置的const迭代器auto it = arr.cend();
reverse_iterator rbegin() noexcept指向反向首元素的迭代器auto it = arr.rbegin();
const_reverse_iterator rbegin() const noexceptconst版本auto it = arr.rbegin();
const_reverse_iterator crbegin() const noexcept指向反向首元素的const迭代器auto it = arr.crbegin();
reverse_iterator rend() noexcept指向反向尾后位置的迭代器auto it = arr.rend();
const_reverse_iterator rend() const noexceptconst版本auto it = arr.rend();
const_reverse_iterator crend() const noexcept指向反向尾后位置的const迭代器auto it = arr.crend();
📦 容量操作
函数说明示例时间复杂度
bool empty() const noexcept检查是否为空if(arr.empty())O(1)
size_type size() const noexcept返回元素数量size_t s = arr.size();O(1)
size_type max_size() const noexcept返回可容纳的最大元素数size_t m = arr.max_size();O(1)
🛠️ 修改器
函数说明示例时间复杂度
void fill(const T& value)用value填充所有元素arr.fill(42);O(n)
void swap(array& other) noexcept交换内容arr1.swap(arr2);O(n)
🔗 非成员函数
比较操作
函数说明示例
bool operator==(const array& lhs, const array& rhs)相等比较if(arr1 == arr2)
bool operator!=(const array& lhs, const array& rhs)不等比较if(arr1 != arr2)
bool operator<(const array& lhs, const array& rhs)小于比较if(arr1 < arr2)
bool operator<=(const array& lhs, const array& rhs)小于等于比较if(arr1 <= arr2)
bool operator>(const array& lhs, const array& rhs)大于比较if(arr1 > arr2)
bool operator>=(const array& lhs, const array& rhs)大于等于比较if(arr1 >= arr2)
C++20 三路比较
函数说明示例
template<class T, size_t N> auto operator<=>(const array<T,N>& lhs, const array<T,N>& rhs)三路比较auto cmp = arr1 <=> arr2;
交换操作
函数说明示例
void swap(array& lhs, array& rhs) noexcept特化swap算法std::swap(arr1, arr2);
元函数 (C++17)
函数说明示例
template<class T, size_t N> struct tuple_size<array<T,N>>获取array的大小auto size = std::tuple_size<arr_type>::value;
template<size_t I, class T, size_t N> struct tuple_element<I, array<T,N>>获取array指定元素的类型using type = std::tuple_element<0, arr_type>::type;
元素访问 (C++17)
函数说明示例
template<size_t I, class T, size_t N> T& get(array<T,N>& a) noexcept访问指定位置的元素int x = std::get<0>(arr);
template<size_t I, class T, size_t N> T&& get(array<T,N>&& a) noexcept访问指定位置的元素(右值)int x = std::get<0>(std::move(arr));
template<size_t I, class T, size_t N> const T& get(const array<T,N>& a) noexceptconst版本int x = std::get<0>(arr);
擦除操作 (C++20)
函数说明示例
template<class T, size_t N, class U> size_t erase(array<T,N>& c, const U& value)从array中擦除所有等于value的元素std::erase(arr, 10);
template<class T, size_t N, class Pred> size_t erase_if(array<T,N>& c, Pred pred)从array中擦除所有满足pred的元素std::erase_if(arr, [](int x){return x%2==0;});

💻 完整代码示例

#include <iostream>
#include <array>
#include <algorithm>
#include <tuple>

int main() {
    // 1. 创建和初始化
    std::array<int, 5> arr = {1, 2, 3, 4, 5};
    std::array<int, 5> arr2 = {6, 7, 8, 9, 10};
    
    // 2. 元素访问
    std::cout << "第一个元素: " << arr.front() << std::endl;
    std::cout << "最后一个元素: " << arr.back() << std::endl;
    std::cout << "索引为2的元素: " << arr[2] << std::endl;
    std::cout << "索引为3的元素(使用at): " << arr.at(3) << std::endl;
    std::cout << "数据指针: " << arr.data() << std::endl;
    
    // 3. 容量操作
    std::cout << "大小: " << arr.size() << std::endl;
    std::cout << "最大大小: " << arr.max_size() << std::endl;
    std::cout << "是否为空: " << (arr.empty() ? "是" : "否") << std::endl;
    
    // 4. 迭代器遍历
    std::cout << "正向遍历: ";
    for (auto it = arr.begin(); it != arr.end(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;
    
    std::cout << "反向遍历: ";
    for (auto it = arr.rbegin(); it != arr.rend(); ++it) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;
    
    // 5. 范围for循环
    std::cout << "范围for循环: ";
    for (const auto& element : arr) {
        std::cout << element << " ";
    }
    std::cout << std::endl;
    
    // 6. 修改操作
    arr.fill(42);  // 填充
    std::cout << "填充后: ";
    for (const auto& element : arr) {
        std::cout << element << " ";
    }
    std::cout << std::endl;
    
    // 7. 交换操作
    arr.swap(arr2);
    std::cout << "交换后arr: ";
    for (const auto& element : arr) {
        std::cout << element << " ";
    }
    std::cout << std::endl;
    
    // 8. 使用STL算法
    std::sort(arr.begin(), arr.end());
    std::cout << "排序后arr: ";
    for (const auto& element : arr) {
        std::cout << element << " ";
    }
    std::cout << std::endl;
    
    // 9. 使用get访问元素
    std::cout << "第一个元素(get): " << std::get<0>(arr) << std::endl;
    
    // 10. 结构化绑定 (C++17)
    auto [a, b, c, d, e] = arr;
    std::cout << "结构化绑定: " << a << " " << b << " " << c << " " << d << " " << e << std::endl;
    
    // 11. 编译时计算
    constexpr std::array<int, 4> compile_time_arr = []{
        std::array<int, 4> result{};
        for (int i = 0; i < 4; ++i) {
            result[i] = i * i;
        }
        return result;
    }();
    
    std::cout << "编译时数组: ";
    for (const auto& elem : compile_time_arr) {
        std::cout << elem << " ";
    }
    std::cout << std::endl;
    
    // 12. 与C风格数组交互
    void c_style_function(int* arr, size_t size);
    c_style_function(arr.data(), arr.size());
    
    
    return 0;
}

// 模拟C风格函数
void c_style_function(int* arr, size_t size) {
    std::cout << "C风格函数接收: ";
    for (size_t i = 0; i < size; ++i) {
        std::cout << arr[i] << " ";
    }
    std::cout << std::endl;
}
第一个元素: 1
最后一个元素: 5
索引为2的元素: 3
索引为3的元素(使用at): 4
数据指针: 0x7fff5f773d60
大小: 5
最大大小: 5
是否为空: 否
正向遍历: 1 2 3 4 5 
反向遍历: 5 4 3 2 1 
范围for循环: 1 2 3 4 5 
填充后: 42 42 42 42 42 
交换后arr: 6 7 8 9 10 
排序后arr: 6 7 8 9 10 
第一个元素(get): 6
结构化绑定: 6 7 8 9 10
编译时数组: 0 1 4 9 
C风格函数接收: 6 7 8 9 10

实际应用场景

1. 数学计算 - 向量和矩阵

using Vector3 = std::array<float, 3>;
using Matrix3x3 = std::array<std::array<float, 3>, 3>;

Vector3 crossProduct(const Vector3& a, const Vector3& b) {
    return {
        a[1]*b[2] - a[2]*b[1],
        a[2]*b[0] - a[0]*b[2], 
        a[0]*b[1] - a[1]*b[0]
    };
}

2. 游戏开发 - 固定大小数据

struct Transform {
    std::array<float, 3> position;
    std::array<float, 4> rotation;  // 四元数
    std::array<float, 3> scale;
};

class GameObject {
private:
    std::array<Transform, 100> transforms;  // 固定对象池
};

3. 嵌入式系统 - 内存确定

class SensorReader {
private:
    std::array<uint16_t, 256> sensor_buffer;  // 固定缓冲区
public:
    void readData() {
        // 无动态分配,适合嵌入式环境
        std::fill(sensor_buffer.begin(), sensor_buffer.end(), 0);
    }
};

        至此,std::array的所有API已完整呈现。从基础构造到高级特性,从元素访问到编译时计算,每一招每一式都已涵盖。掌握这些"术法",配合对连续内存布局"道法"的理解,你已能充分发挥std::array的性能优势,在需要固定大小容器的场景中游刃有余。

        上一篇:《C++ STL 容器 -- 道与术 篇(二) list》

        相近篇:《C++ STL 容器 -- 道与术 篇(一) vector》

        下一篇:《》

        总则篇:《C++容器的介绍和使用》--> 怎么选择容器

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值