C/C++ 入门:数组
变量一次只能保存一个值,要存”一组同类型的数据”,最简单的办法就是数组(array)。
📚 基本概念速读
| 名称 | 定义 | 省流 |
|---|---|---|
| 数组 | 一段连续内存里存放的多个同类型元素 | 同类型数据排队 |
| 元素(element) | 数组里的每一个数据 | 队伍中的一员 |
| 下标(index) | 元素在数组里的位置编号,从 0 开始 | 第几个 |
| 数组长度 | 数组能容纳的元素个数 | 队伍长度 |
| 多维数组 | 数组的元素还是数组,比如二维数组 | 表格 |
| 数组名 | 数组的名字,使用时通常退化为首元素地址 | 队伍的队首 |
🧩 数组的基本模型
数组把多个同类型变量放进一段连续的内存里,通过下标访问:
1 | int scores[5]; // 5 个 int 排成一行 |
可以这样理解:
1 | 下标: [0] [1] [2] [3] [4] |
flowchart LR
A[数组 scores] --> B[下标 0]
A --> C[下标 1]
A --> D[下标 2]
A --> E[...]
A --> F[下标 n-1]
数组的两个基本事实:
- 同类型:所有元素类型一致,占用内存大小相同;
- 连续内存:元素紧挨着存放,所以按下标访问是 O(1) 的,非常快。
🧮 一维数组
定义与初始化
1 | int a[5]; // 定义:5 个 int,未初始化(值不确定) |
数组长度必须是编译期能确定的常量(普通写法里不能是运行时变量):
1 | int n = 5; |
需要”运行时才知道大小”的数组,用第 9 章的
new[]或vector,不要用变长数组写法。
下标访问
下标从 0 开始,最后一个元素的下标是长度减一:
1 | int scores[5] = {92, 88, 95, 70, 85}; |
遍历
1 | for (int i = 0; i < 5; i++) { |
也可以结合数组长度求法:
1 | int n = sizeof(scores) / sizeof(scores[0]); // 5:总字节数 / 单个元素字节数 |
📦 数组与函数
数组传给函数时,传的是首元素地址(数组名退化),函数里拿不到数组长度,需要额外传:
1 |
|
因为传的是地址,函数里修改数组会直接影响原数组:
1 | void addOne(int arr[], int size) |
🗺️ 多维数组
二维数组可以理解成”数组的数组”,适合表示表格、矩阵:
1 | int matrix[3][4] = { // 3 行 4 列 |
双重循环遍历:
1 | for (int row = 0; row < 3; row++) { |
flowchart TD
A[matrix 3 行 4 列] --> B[第 1 行: 1 2 3 4]
A --> C[第 2 行: 5 6 7 8]
A --> D[第 3 行: 9 10 11 12]
三维及以上同理,只是现实中用得较少。
⚠️ 越界与常见错误
| 错误 | 后果 |
|---|---|
arr[5](长度 5 的数组) |
越界访问,未定义行为,可能读到脏数据或崩溃 |
| 忘记初始化就用 | 读到不确定的值 |
| 数组长度用运行时变量 | 变长数组写法不可移植 |
| 函数里只传数组名不传长度 | 函数不知道数组多大,容易越界 |
C/C++
不检查数组越界,编译期通常不报错,运行时才会出问题。这是数组和
vector 最大的差别之一——vector 有
size(),还能用 at() 检查越界。
现代 C++ 中,普通场景优先用
std::vector(第 9 章讲过);数组仍然常见于固定大小、追求极简的场景,以及和 C 代码打交道时。
⚠️ 常见误区
| 误区 | 正解 |
|---|---|
| 下标从 1 开始 | C/C++ 下标从 0 开始 |
| 数组越界会报错 | 不检查越界,是未定义行为 |
| 传数组给函数是拷贝 | 传的是首元素地址,函数内修改影响原数组 |
| 数组名可以随便赋值 | 数组名是常量地址,不能整体赋值 |
sizeof(arr) 在函数里也能拿到长度 |
函数里 arr 是指针,sizeof
得到的是指针大小 |
✅ 总结
数组把同类型数据放进连续内存,按下标 O(1) 访问;下标从 0 开始、不检查越界、传参时退化为指针,这三个特性决定了它的用法和风险。
学完数组,下一步的指针、字符串会大量建立在”数组名是首元素地址”这个事实上;而需要动态大小、自动管理内存时,记得有
vector 这个更省心的选择。
Happy Hacking! 🎉