🔷 第七章 数组、指针和引用
📄 一句话概括

数组作函数参数时,传的不是整个数组,而是它的首地址——也就是说,数组一直是"按地址传"的(跟你想的"复制一份"完全不同)。两个直接后果:① 函数里改数组 = 改原件;② 函数里的 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。

六、常见误区

  1. "数组是值传递,函数里改不了外面的数组"——反了。数组退化成指针,传的是地址,函数里一改,外面就变。想"保护"数组不被改,得在参数上加 const(const int a[])。
  2. "int a[] 和 int *a 是两种不同的参数"——完全等价,编译器一视同仁(这也解释了 7.6.1 的 swap 为什么能用 &arr[0])。
  3. "函数里能用 sizeof(a)/sizeof(a[0]) 算出个数"——算不出来(得到 1)。必须另传长度参数。
  4. "二维数组传参时可以省列数"——列数必须写(int a[][4]),因为编译器要靠它算"一行多长"。
  5. "argc 等于参数的个数"——严格说是"参数个数 + 1",因为程序名自己也算一个。
自测三问(点开对答案) 1. 数组作函数参数,传的是整个数组还是地址?——**地址**(数组退化成指针)。所以函数里改数组 = 改原件。 2. 为什么函数里 `sizeof(a)` 算不出数组长度?——因为形参 `a` 已经是指针了,`sizeof` 得到的是指针大小(4 或 8)。**必须另传长度。** 3. 例15 里 `argc` 和 `argv` 各是什么?——`argc` 是参数个数(含程序名);`argv` 是"指针数组",每格指向一个参数字符串,`argv[0]` 是程序名。

相关笔记