CC++ & Algorithm

array固定数组与C风格数组

较难5
语言版本:通用
概述:像一排位置固定的储物柜,array是大小固定、更安全的C++数组,比传统C数组更友好。

固定大小的数组:就像你的储物柜——不多不少刚刚好

你是不是也有过这样的经历:老师让你统计一周七天的天气,按理说一周就是7天,不会多也不会少;或者你有一张身份证号,固定18位,不能多也不能少。在编程中,我们常常会遇到这种“数量固定不变”的数据。C++ 里有两个工具可以做这件事:一个是古老的 C风格数组(像没有管理员的储物柜),另一个是 C++ 标准库提供的 std::array(像有管理员、有规则的新式储物柜)。这篇文章就带你好好认识它们。

什么是固定数组?

固定数组 就是一块连续的内存空间,里面分成了很多个小格子(元素),每个格子有一个编号(索引),而且这个格子总数在你创建的时候就定死了,以后永远不能增加也不能减少。

  • 图书馆的储物柜:20个柜子,编号 0~19,你不能塞进第21个,也不能把柜子扔掉两个。
  • 一周的天数:7天,你不能让一周变成8天。
  • 学生的学号:很多学校学号是固定长度,比如10位,不会因为转学就变长。

C 语言中我们这样写:int lockers[20]; —— 这就是C风格数组。C++ 中推荐这样写:std::array<int, 20> lockers; —— 这就是 std::array

两者在内存里完全一样:都是连续的一排格子,访问速度飞快(O(1))。但 std::array 更加安全、方便,就像储物柜有了管理员:

  • 管理员会告诉你柜子编号对不对(越界检查)
  • 管理员提供统一的方法开门(成员函数)
  • 你可以把整个储物柜复制给另一个(赋值操作)

新手最容易犯的错误

1. 越界访问(最危险)

int scores[5] = {90, 85, 100, 70, 88};
cout << scores[5];  // 糟糕!索引从0开始,最大是4,scores[5]越界

C风格数组不会检查,程序可能悄悄读到别的内存,或者崩溃。std::array 如果使用 at() 会抛出异常,但用 [] 也照样不检查。

生活比喻:你的储物柜编号只有0~19,你非要用钥匙捅20号门——那可能捅到墙里,或者别人的柜子。

2. 把C数组传给函数后,以为还能知道大小

void printArray(int arr[]) {
    cout << sizeof(arr);  // 糟糕!arr退化为指针,sizeof(arr)是8(指针大小)
}

C风格数组作为参数时,数组名变成了指针,你再也拿不到元素个数了。std::array 传引用则能保持大小。

3. 混淆 array 和 vector

std::array 大小固定,不能动态扩展;std::vector 大小可以变。新手容易拿 array 当 vector 用,比如 push_back。

分模块讲解:从入门到会用

1. 创建和初始化

#include <iostream>
#include <array>   // 使用 std::array 必须包含
using namespace std;

int main() {
    // 创建一个存放 5 个整数的固定数组,默认不初始化(局部变量值是随机的)
    array<int, 5> nums;
    // 如果一个都没赋值,就输出它可能得到奇奇怪怪的数(垃圾值)
    cout << nums[0] << endl;  // 不确定,可能是 0 也可能是别的

    // 创建并初始化:可以全部写,也可以只写前几个,后面的自动归零
    array<int, 5> initialized = {10, 20, 30};  // 前三个是10,20,30,后两个是0

    // 或者用统一的 fill 方法全部填同一个值
    array<int, 5> allFive;
    allFive.fill(5);  // 全部变成 5

    return 0;
}

生活场景:你有一个新文具盒,里面有5个格子。你可以先不放任何笔(未初始化),也可以放几支(部分初始化),剩下的格子空着(自动置0),也可以全放同一款笔(fill)。

2. 访问元素

  • []操作符:最快,但不检查越界。
  • at()成员函数:检查越界,越界时抛出异常。
array<int, 7> weekTemps = {32, 35, 28, 30, 31, 29, 33};
cout << "周一温度: " << weekTemps[0] << endl;   // 32,不检查
cout << "周三温度: " << weekTemps.at(2) << endl; // 28,检查越界

// 越界演示(危险!不要在实际代码中这样写)
// cout << weekTemps[7] << endl;  // 可能输出垃圾值或崩溃
try {
    cout << weekTemps.at(7) << endl;  // 抛出 out_of_range 异常
} catch (const out_of_range& e) {
    cout << "越界了!" << e.what() << endl;
}

3. 首尾元素、大小、填充

cout << "本周最低温度(首元素): " << weekTemps.front() << endl;  // 32
cout << "本周最高温度(尾元素): " << weekTemps.back() << endl;   // 33
cout << "一周天数: " << weekTemps.size() << endl;  // 7

weekTemps.fill(25);  // 全部变成25度

4. 复制整个数组

std::array 支持直接赋值,这在C风格数组里是做不到的。

array<int, 5> scores = {90, 85, 100, 70, 88};
array<int, 5> copyScores;
copyScores = scores;  // 直接复制,方便!
cout << "成绩复制后的第一个: " << copyScores[0] << endl;  // 90

对比C风格数组:

int cScores[5] = {90, 85, 100, 70, 88};
int cCopy[5];
// cCopy = cScores;  // 错误!不能直接赋值
// 必须手动循环或 memcpy
for (int i = 0; i < 5; ++i) cCopy[i] = cScores[i];

5. 获取原始指针(与旧C代码交互)

有时需要把 std::array 传给期望 int* 的C函数(比如某些系统调用),可以用 data() 方法得到指向第一个元素的指针。

array<int, 5> numbers = {1,2,3,4,5};
int* ptr = numbers.data();   // 获取指针
ptr[0] = 100;                // 通过指针修改第一个元素
cout << numbers[0] << endl;  // 100,已经改了

6. 配合STL算法

std::array 提供了 begin()end() 迭代器,可以用于任何STL算法。

#include <algorithm>
array<int, 5> scores = {90, 85, 100, 70, 88};
sort(scores.begin(), scores.end());   // 升序排序
cout << "排序后的成绩: ";
for (int s : scores) cout << s << " ";
cout << endl;  // 输出: 70 85 88 90 100

7. 遍历的多种方式

// 方法1:范围for (最推荐,最简洁)
for (int temp : weekTemps) {
    cout << temp << " ";
}

// 方法2:传统for循环 + size()
for (size_t i = 0; i < weekTemps.size(); ++i) {
    cout << weekTemps[i] << " ";
}

// 方法3:迭代器
for (auto it = weekTemps.begin(); it != weekTemps.end(); ++it) {
    cout << *it << " ";
}

C风格数组 vs std::array 详细对比

特性C风格数组std::array
声明int arr[5];array<int,5> arr;
初始化int arr[5] = {1,2};array<int,5> arr = {1,2};
大小固定固定,但大小是类型的一部分
越界检查无,全靠自觉at() 会抛出异常<br>[] 同样无检查
赋值不支持,需循环或memcpy支持 arr2 = arr1;
传参退化为指针,丢失大小可以传引用,保持大小
使用STL算法需要指针或手动计算长度直接支持迭代器
内存布局连续连续,与C数组完全相同
性能无开销无额外开销(编译时展开)
获取指针arr 就是指针arr.data()
获取大小sizeof(arr)/sizeof(int) 只在定义处有效arr.size() 总是正确
空数组int arr[0]; 标准不允许array<int,0> arr; 允许

结论:在纯C++代码中,总是优先使用 std::array。只有在你需要和C语言库交互,或者为了极致的底层性能控制,才考虑C风格数组。

完整可运行代码示例

#include <iostream>
#include <array>       // std::array
#include <algorithm>   // std::sort
using namespace std;

int main() {
    // ----- std::array 的完整用法 -----
    // 1. 创建并初始化一个存放5个整数的固定数组
    array<int, 5> scores = {90, 85, 100, 70, 88};  // 成绩

    // 2. 访问元素
    cout << "第一个成绩([]): " << scores[0] << endl;
    cout << "第三个成绩(at()): " << scores.at(2) << endl;

    // 3. 使用 front 和 back
    cout << "最低成绩(首): " << scores.front() << endl;
    cout << "最高成绩(尾): " << scores.back() << endl;

    // 4. 获取大小
    cout << "成绩个数: " << scores.size() << endl;

    // 5. 填充所有元素为同一个值
    array<int, 5> allZero;
    allZero.fill(0);
    cout << "全部填0后: ";
    for (int v : allZero) cout << v << " ";
    cout << endl;

    // 6. 获取原始指针并修改
    int* ptr = scores.data();
    ptr[0] = 100;   // 把第一个成绩改成100
    cout << "修改后第一个成绩: " << scores[0] << endl;

    // 7. 使用STL算法排序
    sort(scores.begin(), scores.end());
    cout << "排序后的成绩: ";
    for (int s : scores) cout << s << " ";
    cout << endl;

    // 8. 复制整个数组
    array<int, 5> copyScores;
    copyScores = scores;
    cout << "复制后的第一个成绩(已排序): " << copyScores[0] << endl;

    // ----- 对比C风格数组 -----
    int cArr[5] = {1, 2, 3, 4, 5};
    // cArr2 = cArr;  // 错误!不能赋值
    int cArr2[5];
    for (int i = 0; i < 5; ++i) cArr2[i] = cArr[i];
    cout << "C数组复制成功" << endl;

    // 注意:C数组作为参数时大小丢失
    cout << "当前函数内cArr的大小: " << sizeof(cArr) / sizeof(int) << endl;  // 5
    // 但如果传给函数,就得不到正确的值了

    return 0;
}

如果我要用 Python 怎么办?

Python 里没有直接对应 std::array 的东西,但我们可以用下面几种方式来模拟:

1. 只读固定数组 → 用元组 (tuple)

元组创建后就不能改变元素了,适合存储一周天数、坐标点等不允许修改的数据。

# 一周的温度,只读
week_temps = (32, 35, 28, 30, 31, 29, 33)
print("周一温度:", week_temps[0])
# week_temps[0] = 40   # 会报错,元组不可修改
print("总天数:", len(week_temps))

2. 可变但固定大小的数组 → 用列表 (list) 加自律

你可以创建一个列表,然后自我约束不调用 appendpop 等方法。

scores = [90, 85, 100, 70, 88]   # 固定5个成绩
scores[0] = 95                    # 可以修改
# 但不要调用 scores.append(80)  # 这会破坏固定大小
print("成绩单:", scores)

如果想完全避免意外变长,可以自己写一个简单的类封装,但通常不需要。

3. 类型固定但长度可变的数组 → array 模块

array.array 只能存放同一类型的元素(比如全部是整数),但它可以 append 和 pop,所以长度不固定。

from array import array
arr = array('i', [1, 2, 3, 4, 5])  # 'i' 表示 signed int
arr.append(6)    # 长度变成6
print(arr)       # array('i', [1, 2, 3, 4, 5, 6])
arr.pop()        # 长度回到5
print(arr)       # array('i', [1, 2, 3, 4, 5])

如果你真的需要编译时固定大小,Python 做不到,但实际编程中很少遇到这种强需求。

总结:什么时候用固定数组?

  • 数据量在编译时就已知且不会再变:比如一周天数、一年月份、学号长度、班级人数固定时。
  • 追求最高性能,且不需要扩容:固定数组比 vector 更轻量(没有扩容开销)。
  • 与C代码交互:可能需要C风格数组或 std::array::data()

优先选择 std::array,它让你少犯错、代码更清晰。如果遇到需要动态大小的场景,就去学习 std::vector(动态数组)。下一篇文章我们就聊聊 vector 这位能伸缩的好朋友!

例题精讲

1单选题

关于std::array<int, 5>和C风格数组int arr[5],以下哪个说法是正确的?

Astd::array的大小可以在运行时指定
BC风格数组作为函数参数时,其大小信息会传递给函数
Cstd::array的operator[]会进行边界检查
Dstd::array提供了at()成员函数用于边界检查
2判断题

std::array<int, 10> arr; 和 int arr[10]; 在内存布局上是完全相同的。

3填空题
给定 std::array<int, 5> arr = {1, 2, 3, 4, 5}; 要获取数组的元素个数,应使用 arr.___() 。
4单选题

关于C风格数组作为函数参数,下列哪项说法是正确的?

A数组的大小会通过参数传递给函数
B数组名会退化为指向首元素的指针
C数组可以按值传递给函数
D在函数内部使用sizeof(arr)可以得到原始数组的字节大小
5填空题
使用 std::array<int, 5> arr; 若要安全地访问索引为9的元素(越界),应使用 arr.___(9) 。