乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • C++ 学习路径
  • 第1章 C++概述与开发环境

    • C++ 语言概述与编译模型
    • 第一个 C++ 程序与规范
    • 开发环境配置
    • 预处理指令详解
  • 第2章 基本语法与类型系统

    • 变量与基本类型
    • 类型转换
    • 枚举类型
    • 引用与指针
    • 数组与 stdarray
    • 字符串与原始字符串字面量
    • const 与 constexpr
    • nullptr 与空指针
    • 类型推导 auto 与 decltype
    • 基于范围的 for 循环
    • 值类别全面解析
    • char16_t 与 char32_t
    • static_assert 编译期断言
  • 第3章 函数与重载

    • 函数声明与定义
    • 函数重载
    • 默认参数与内联函数
    • Lambda 表达式
    • 函数对象与 stdfunction
    • 后置返回类型与 noexcept
  • 第4章 类与对象

    • 类的基本定义
    • 构造函数与析构函数
    • 拷贝控制
    • 移动构造函数与移动赋值
    • 列表初始化与类内初始化器
    • 静态成员与嵌套类
    • 友元
    • =default 与 =delete
  • 第5章 继承与多态

    • 继承基础
    • 虚函数与多态
    • 虚函数表与动态绑定原理
    • 虚析构函数
    • 抽象类与纯虚函数
    • 多重继承与虚继承
    • 继承构造函数
  • 第6章 运算符重载

    • 运算符重载基础
    • 算术与关系运算符重载
    • 赋值与移动运算符重载
    • 特殊运算符重载
  • 第7章 模板与泛型编程

    • 函数模板
    • 类模板
    • 模板特化与偏特化
    • 可变参数模板
    • 别名模板与模板模板参数
    • SFINAE 与类型萃取
    • 依赖名与 typename/template 关键字
  • 第8章 异常处理

    • 异常处理机制
    • noexcept 与异常安全
    • RAII 与异常安全实践
  • 第9章 内存管理与智能指针

    • 动态内存与内存分区
    • RAII 惯用法
    • unique_ptr
    • shared_ptr 与 weak_ptr
    • 内存管理最佳实践
  • 第10章 右值引用与移动语义

    • 右值引用与值类别深度解析
    • std::move 原理与使用
    • 完美转发与 std::forward
    • 移动语义性能对比与最佳实践
  • 第11章 STL容器

    • vector 深度剖析
    • deque 内部机制
    • list 与 forward_list
    • map 与 set 深度解析
    • unordered 容器与哈希原理
    • array 与 tuple
    • 容器适配器
    • 容器选择全景指南
  • 第12章 STL算法与迭代器

    • 迭代器体系全解
    • 非变异算法
    • 变异算法
    • 排序与二分算法
    • Lambda 与算法组合
    • std::random 随机数库
    • 自定义迭代器开发
  • 第13章 IO流与文件

    • 标准 IO 流
    • 格式化输出控制
    • 文件流操作
    • 字符串流
    • std::regex 正则表达式
  • 第14章 并发与多线程

    • thread 基础与线程管理
    • mutex 与 lock_guard
    • unique_lock 与 condition_variable
    • thread_local 线程局部存储
    • atomic 与内存序
    • future 与 async 异步编程
    • std::chrono 时间库
  • 第15章 现代C++新特性

    • 从 C++11 到 C++20 演进路线
    • C++14 关键新特性
    • C++17 关键新特性
    • C++20 核心特性速览
  • 第16章 面试考点与最佳实践

    • C++ 综合最佳实践清单
    • 高频面试题精讲
    • 多线程面试题与实战
    • 内存管理常见陷阱与排查
  • 附录

    • C++ 核心知识点
    • C++ 专业术语

数组与 std::array

定义与作用

C++ 提供两种固定大小数组:

类型来源特点
T[N] 原生数组C 语言会退化为指针,丢失大小信息
std::array<T, N>C++11封装类,保留大小,支持 STL 接口

原生数组的最大问题是退化(decay)——当数组作为函数参数时,它退化为首元素指针,长度信息完全丢失。std::array 通过封装彻底解决了这一问题,同时保持了与原生数组相同的性能(零开销抽象)。

核心原理

数组退化示意

内存布局对比

两者内存布局完全一致——连续存储,无额外开销。std::array 的 .size() 和 .begin()/.end() 在编译期确定,零运行时开销。

完整示例

示例一:原生数组退化陷阱与修复

场景说明:演示原生数组作为函数参数时的退化问题,以及 std::array 如何解决。

#include <iostream>
#include <array>
#include <algorithm>
#include <numeric>

// ---- 原生数组的问题 ----
// 这三个函数声明是等价的!都退化为 int*
void process_1(int arr[10]) {   // 10 被忽略
    std::cout << "sizeof(arr) in process_1: " << sizeof(arr) << std::endl;
}

void process_2(int arr[]) {     // 完全等价
    std::cout << "sizeof(arr) in process_2: " << sizeof(arr) << std::endl;
}

void process_3(int* arr) {      // 同上
    std::cout << "sizeof(arr) in process_3: " << sizeof(arr) << std::endl;
}

// 需要额外传递长度参数
double average(const int* arr, size_t size) {
    return std::accumulate(arr, arr + size, 0.0) / size;
}

// ---- std::array 的优势 ----
template <size_t N>
double average_array(const std::array<int, N>& arr) {
    if (N == 0) return 0.0;
    return std::accumulate(arr.begin(), arr.end(), 0.0) / N;
}

// 或者用 std::array 的方法直接访问大小
double average_v2(const std::array<int, 5>& arr) {
    return std::accumulate(arr.begin(), arr.end(), 0.0) / arr.size();
}

int main() {
    // 原生数组
    int raw_arr[5] = {10, 20, 30, 40, 50};
    std::cout << "===== 原生数组 =====" << std::endl;
    std::cout << "sizeof(raw_arr) in main: " << sizeof(raw_arr) << std::endl;
    process_1(raw_arr);  // 打印指针大小(8 字节)
    std::cout << "平均分: " << average(raw_arr, 5) << std::endl;

    // std::array
    std::array<int, 5> std_arr = {10, 20, 30, 40, 50};
    std::cout << "\n===== std::array =====" << std::endl;
    std::cout << "sizeof(std_arr) in main: " << sizeof(std_arr) << std::endl;
    std::cout << "std_arr.size(): " << std_arr.size() << std::endl;
    std::cout << "平均分: " << average_array(std_arr) << std::endl;

    // STL 兼容性:range-based for
    std::cout << "\n遍历: ";
    for (const auto& score : std_arr) {
        std::cout << score << " ";
    }
    std::cout << std::endl;

    // STL 兼容性:算法
    auto [min_it, max_it] = std::minmax_element(std_arr.begin(), std_arr.end());
    std::cout << "最低分: " << *min_it << ", 最高分: " << *max_it << std::endl;

    // 边界检查(at 抛出异常,operator[] 不检查)
    std::cout << "std_arr.at(2) = " << std_arr.at(2) << std::endl;
    try {
        std::cout << std_arr.at(10);  // 抛出 std::out_of_range
    } catch (const std::out_of_range& e) {
        std::cout << "越界访问被捕获: " << e.what() << std::endl;
    }

    return 0;
}

预期输出:

===== 原生数组 =====
sizeof(raw_arr) in main: 20
sizeof(arr) in process_1: 8
sizeof(arr) in process_2: 8
sizeof(arr) in process_3: 8
平均分: 30

===== std::array =====
sizeof(std_arr) in main: 20
std_arr.size(): 5
平均分: 30

遍历: 10 20 30 40 50 
最低分: 10, 最高分: 50
std_arr.at(2) = 30
越界访问被捕获: array::at: __n (which is 10) >= _Nm (which is 5)

逐段分析:

  • sizeof(raw_arr) 在 main 中是 20(5 个 int),在函数内部变为 8(64 位指针大小)——退化已发生
  • process_1/process_2/process_3 三个函数签名完全等价,都只是 int*
  • std::array<int, 5> 在函数间传递时保留大小信息,可通过模板参数 N 或 .size() 获取
  • std::array 完全兼容 STL 算法(begin/end、minmax_element 等)
  • .at() 提供带边界检查的访问,越界抛出 std::out_of_range;operator[] 与原生数组一样无检查

示例二:二维数组与 std::array 嵌套

场景说明:实现一个固定大小的矩阵类,对比原生二维数组和 std::array 嵌套。

#include <iostream>
#include <array>
#include <iomanip>

// 使用 std::array 实现 3x3 矩阵
using Matrix3 = std::array<std::array<double, 3>, 3>;

// 原生数组版本:退化严重,无法传递
void print_matrix_raw(const double matrix[3][3]) {
    // matrix 是 double (*)[3],不是 double[3][3]
    // 但第一维的 3 仍被忽略,实际是 double (*)[3]
    for (int i = 0; i < 3; ++i) {
        for (int j = 0; j < 3; ++j) {
            std::cout << std::setw(8) << matrix[i][j];
        }
        std::cout << std::endl;
    }
}

void print_matrix(const Matrix3& matrix) {
    for (const auto& row : matrix) {
        for (double val : row) {
            std::cout << std::setw(8) << std::setprecision(2)
                      << std::fixed << val;
        }
        std::cout << std::endl;
    }
}

Matrix3 add_matrices(const Matrix3& a, const Matrix3& b) {
    Matrix3 result{};
    for (size_t i = 0; i < result.size(); ++i) {
        for (size_t j = 0; j < result[i].size(); ++j) {
            result[i][j] = a[i][j] + b[i][j];
        }
    }
    return result;
}

int main() {
    Matrix3 m1 = {{{1.0, 2.0, 3.0},
                   {4.0, 5.0, 6.0},
                   {7.0, 8.0, 9.0}}};

    Matrix3 m2 = {{{9.0, 8.0, 7.0},
                   {6.0, 5.0, 4.0},
                   {3.0, 2.0, 1.0}}};

    std::cout << "矩阵 m1:" << std::endl;
    print_matrix(m1);

    std::cout << "\n矩阵 m2:" << std::endl;
    print_matrix(m2);

    auto m3 = add_matrices(m1, m2);
    std::cout << "\nm1 + m2:" << std::endl;
    print_matrix(m3);

    // 属性查询
    std::cout << "\n矩阵属性:" << std::endl;
    std::cout << "行数: " << m3.size() << std::endl;
    std::cout << "列数: " << m3[0].size() << std::endl;
    std::cout << "总元素: " << m3.size() * m3[0].size() << std::endl;

    return 0;
}

预期输出:

矩阵 m1:
    1.00    2.00    3.00
    4.00    5.00    6.00
    7.00    8.00    9.00

矩阵 m2:
    9.00    8.00    7.00
    6.00    5.00    4.00
    3.00    2.00    1.00

m1 + m2:
   10.00   10.00   10.00
   10.00   10.00   10.00
   10.00   10.00   10.00

矩阵属性:
行数: 3
列数: 3
总元素: 9

逐段分析:

  • std::array<std::array<T, Cols>, Rows> 是固定大小矩阵的零开销实现
  • add_matrices 按值返回——由于 std::array 是固定大小,编译器可以优化掉拷贝(RVO)
  • .size() 在每一层返回对应维度的大小,编译期确定
  • using Matrix3 = std::array<...> 类型别名提高可读性

易错场景与面试考点

易错场景

场景错误表现正确做法
函数参数写 int arr[N]N 被忽略,实际是 int*用 int (&arr)[N](数组引用)或 std::array<int,N>&
sizeof(arr) 在函数内使用返回指针大小而非数组大小用 std::array 或显式传递长度
std::array 越界用 []未定义行为,无错误提示调试期用 .at(),发布期用 [](性能优先)
大数组放在栈上栈溢出大数组用 std::vector 或堆分配
试图用 auto 推导数组auto arr2 = arr 得到 int*用 auto& arr2 = arr 或 std::array

常见面试问题

  1. 原生数组作为函数参数时发生了什么?——退化为指针,第一维的长度信息丢失。void f(int arr[10]) 等价于 void f(int* arr)。

  2. 如何防止数组退化?——使用数组引用 int (&arr)[N] 或 std::array<int, N>。后者更推荐。

  3. std::array 和 std::vector 的区别?什么场景用哪个?——array 固定大小、栈分配、无运行时开销;vector 动态大小、堆分配、支持扩容。编译期已知大小且不大时用 array。

  4. std::array 的 .size() 有运行时开销吗?——无。size() 返回模板参数 N,编译期即可确定,通常被优化为常量。

  5. 多维 std::array 和原生多维数组的内存布局是否相同?——完全相同,都是连续的行优先(row-major)布局。std::array<std::array<T,Cols>, Rows> 保证与 T[Rows][Cols] 相同的内存排布。

小结

  • 原生数组会退化为指针,丢失长度信息;用 std::array 彻底解决
  • std::array 零开销抽象——内存布局与原生数组一致,.size() 编译期求值
  • 支持 STL 接口:迭代器、begin()/end()、at() 边界检查
  • 函数参数尽量用 std::array 或数组引用 T (&)[N]
  • 编译期未知大小或需要动态扩容时用 std::vector
上一页
引用与指针
下一页
字符串与原始字符串字面量