数组作函数参数时,传的不是整个数组,而是它的首地址——也就是说,数组一直是"按地址传"的(跟你想的"复制一份"完全不同)。两个直接后果:① 函数里改数组 = 改原件;② 函数里的 sizeof(a) 会失效(得到的是指针大小),所以长度必须另外传一个参数。二维数组作参数时,列数不能省。
这一节把三条线拧成一股:7.1.3 的"数组不能整体赋值"(所以根本没法"按值传")、7.5 的"数组名就是首地址"(所以传进去的是地址)、3.1.7 的 sizeof(所以它会失效)。学完它,你写遍历数组的函数时就不会再被"传进去之后长度算不出来"卡住。实例15 还打开了本章最后一扇门:命令行参数。
一、数组参数的真相:它"退化"成指针
void printArr(int a[], int n) { ... } // 写法 A
void printArr(int *a, int n) { ... } // 写法 B
这两种写法完全等价——编译器看到 int a[] 就当成 int *a。这个行为叫"数组退化为指针"。
为什么会退化?因为 7.1.3 说过:数组不能整体赋值、不能整体复制。既然没法整个搬进函数,那就只能把首地址交出去(7.5)。
所以传数组的真相是:
调用方 函数内
┌──────────┐ ┌──────────┐
│ a[0] a[1]│ ◄──────────│ 指针 a │ 只交了一个地址
│ a[2] a[3]│ └──────────┘
└──────────┘
两个直接后果:
| 后果 | 说明 |
|---|---|
| 函数里改数组 = 改原件 | 因为操作的就是调用方那块内存(不需要 &,也不需要 *) |
sizeof(a) 在函数里失效 |
a 已经变成指针,sizeof 得到 4 或 8,不是数组总字节 |
二、因此:长度必须另传
void printArr(int a[], int n) { // n 是"额外带来的长度"
for (int i = 0; i < n; i++) cout << a[i] << " ";
}
为什么不能在函数里自己算长度?
void bad(int a[]) {
int n = sizeof(a) / sizeof(a[0]); // ❌ 4/4 = 1,永远算出 1!
}
这是本节最实用的一条经验:凡是"接受数组"的函数,都要多接一个"长度"参数。你在 7.1.1 学的那招 sizeof(a)/sizeof(a[0]) 只在"数组还在自己家里"时有效,一旦传进函数就失效了。
三、二维数组作参数:列数不能省
void print2D(int a[][4], int rows) { // ✅ 列数 4 必须写
for (int i = 0; i < rows; i++) {
for (int j = 0; j < 4; j++) cout << a[i][j] << "\t";
cout << endl;
}
}
void print2D(int a[3][], int rows) { ... } // ❌ 列数不能省
原因还是 7.2.1 那条:编译器必须知道"一行多长"(每行 16 字节),才能算出 a[i][j] 在哪。行数可以由参数给,列数必须在类型里写死。
四、实例15:获取命令行参数
这是数组参数最经典的应用:main 本身也可以收参数。
int main(int argc, char *argv[]) {
...
}
| 参数 | 类型 | 含义 |
|---|---|---|
argc |
int |
参数个数(含程序名本身,所以至少是 1) |
argv |
char *argv[] |
参数内容:一串字符串(指针数组,7.7!) |
读法:argv 就是"一个指针数组"——每一格是一个 char *,指向一个参数字符串。而 argv[0] 永远是程序自己的名字。
#include <iostream>
using namespace std;
int main(int argc, char *argv[]) {
cout << "一共收到 " << argc << " 个参数:" << endl;
for (int i = 0; i < argc; i++)
cout << " argv[" << i << "] = " << argv[i] << endl;
return 0;
}
怎么给它传参数?
| 方式 | 做法 |
|---|---|
| Dev-C++ | 菜单「运行」→「参数」,在对话框里填写(如 apple banana),然后正常运行 |
| PowerShell | 先编译出 .exe,然后 .\程序.exe apple banana |
看到什么算成功(假设传了 apple banana):
一共收到 3 个参数:
argv[0] = C:\...\程序.exe
argv[1] = apple
argv[2] = banana
注意 argc 是 3 不是 2——因为程序名自己算一个。这是新手最常见的差一错误(off-by-one)。
五、动手 Demo:三个例子一次看完
#include <iostream>
using namespace std;
void printArr(int a[], int n) { // 数组 + 长度
for (int i = 0; i < n; i++) cout << a[i] << " ";
cout << endl;
}
void doubleAll(int a[], int n) { // 直接在原数组上改
for (int i = 0; i < n; i++) a[i] *= 2;
}
void sumArr(int a[], int n, int &result) { // 数组 + 长度 + 引用"出参"
result = 0;
for (int i = 0; i < n; i++) result += a[i];
}
int main() {
int arr[5] = {1, 2, 3, 4, 5};
cout << "main 里的 sizeof(arr) = " << sizeof(arr) << endl; // 20
printArr(arr, 5); // 1 2 3 4 5(注意:不用写 &)
int total;
sumArr(arr, 5, total); // 用引用把"结果"带回来(7.8.2)
cout << "总和 = " << total << endl; // 15
doubleAll(arr, 5); // 函数里改,外面跟着变
printArr(arr, 5); // 2 4 6 8 10
return 0;
}
看到什么算成功:main 里的 sizeof(arr) = 20、1 2 3 4 5、总和 = 15、2 4 6 8 10。
三个要点一次看完:
| 现象 | 说明 |
|---|---|
调用时没写 & |
因为 arr 本身就是地址(7.5),传数组天然就是传地址 |
doubleAll 改了外面 |
数组传参 = 传地址,函数里改的就是原件 |
用 int &result 带回总和 |
数组装不下"额外的一个结果",正好用 7.8.2 的引用出参 |
30 秒实验:在 printArr 函数内部加一句 cout << sizeof(a) << endl;,重跑——打印出 4(或 8),而不是 20。这一行就是"数组在函数里退化成指针"的铁证,也解释了为什么必须多传一个 n。
六、常见误区
- "数组是值传递,函数里改不了外面的数组"——反了。数组退化成指针,传的是地址,函数里一改,外面就变。想"保护"数组不被改,得在参数上加
const(const int a[])。 - "
int a[]和int *a是两种不同的参数"——完全等价,编译器一视同仁(这也解释了 7.6.1 的swap为什么能用&arr[0])。 - "函数里能用
sizeof(a)/sizeof(a[0])算出个数"——算不出来(得到 1)。必须另传长度参数。 - "二维数组传参时可以省列数"——列数必须写(
int a[][4]),因为编译器要靠它算"一行多长"。 - "
argc等于参数的个数"——严格说是"参数个数 + 1",因为程序名自己也算一个。
自测三问(点开对答案)
1. 数组作函数参数,传的是整个数组还是地址?——**地址**(数组退化成指针)。所以函数里改数组 = 改原件。 2. 为什么函数里 `sizeof(a)` 算不出数组长度?——因为形参 `a` 已经是指针了,`sizeof` 得到的是指针大小(4 或 8)。**必须另传长度。** 3. 例15 里 `argc` 和 `argv` 各是什么?——`argc` 是参数个数(含程序名);`argv` 是"指针数组",每格指向一个参数字符串,`argv[0]` 是程序名。- ☐ 7.9 小结(下一节)
相关笔记
- 一维数组的初始化 —— 数组为什么不能整体复制
- 指针与数组 —— 数组名就是首地址
- sizeof运算符 —— 为什么在函数里失效
- 指针数组 ——
char *argv[]就是指针数组 - 🔷 C++基础 · 索引 —— 全课路线图