🔷 第八章 结构体与共用体
📄 一句话概括

把结构体变量直接当参数传给函数(void f(Student s)),函数收到的是一份完整的复印件——整个 32 字节都拷一份进去。好处是函数内部怎么改都影响不到原件(天然安全);代价是每调用一次就复制一次。所以这种写法适合"只读 + 结构体不大"的场合,读着最直观。

📝 本节和前面几节的关系

这是 6.2.2"形参是复印件"在结构体上的重演——规则一个字都没变,变的是"复印件有多大"。上一节 8.3 已经给了三种交法的对照,这一节把交法①(传值)掰开讲清楚:它到底安全在哪、贵在哪、什么时候该用它。教材实例02就在这里。

一、语法:和普通变量当参数没区别

void printStudent(Student s) {          // 形参就是普通的"结构体变量"
    cout << "姓名:" << s.name  << endl;
    cout << "年龄:" << s.age   << endl;
    cout << "成绩:" << s.score << endl;
}

int main() {
    Student stu = {"张三", 18, 92.5};
    printStudent(stu);                  // 直接写变量名,不用 & 
    return 0;
}

三件事要看清:

  1. 形参写 Student s——就是一个普通的结构体变量声明,和 int n 是同一种写法;
  2. 调用处直接写 stu——不用 &(这是传值的标志);
  3. 函数体里用 . 访问成员——因为 s 是个变量,不是指针(-> 是 8.3.2 的事)。

二、"复印件"到底复制了什么

Student stu = {"张三", 18, 92.5};
printStudent(stu);

调用发生的那一瞬间,内存里是这样的:

位置 内容
main 里的 stu 张三 / 18 / 92.5(原件,完好无损)
printStudent 的形参 s 张三 / 18 / 92.5(新开的 32 字节,逐字节拷过来的)

关键点:s 和 stu 是两块完全独立的内存。函数里改 s,stu 毫发无伤;函数结束 s 被回收,stu 照样活着。

注意 name[20] 也在复制范围内——20 个字符一并拷过去,不是"共用同一个数组"。所以函数里 strcpy(s.name, "李四") 也改不到原件。

三、好处:天然安全

void damage(Student s) {
    strcpy(s.name, "改坏了");
    s.age = -1;
    s.score = -999;
}

int main() {
    Student stu = {"张三", 18, 92.5};
    damage(stu);
    cout << stu.name << " " << stu.age << endl;   // 还是 张三 18
    return 0;
}

无论函数里怎么折腾,调用方的 stu 都安然无恙——因为动的是复印件。

这带来一个实际的编程好处:你不需要读函数体,就能确定"这个函数不会改我的数据"。这在维护大程序时非常值钱。

四、代价:每调用一次复制一次

for (int i = 0; i < 100000; i++) {
    printStudent(stu);              // 每次调用都复制 32 字节 → 共 320 万字节
}

所以有一条实用的判断标准:

结构体大小 建议
很小(几个 int,≤ 16 字节) 传值就行,写法最直观,安全
中等或较大(含大数组、很多成员) 只读用 const Student &s;要改用指针或引用

五、动手 Demo(实例02 使用结构体变量做函数参数)

教材实例02的做法是"把结构体变量交给函数处理"。我们把它做成一个成绩处理小工具,把"传值只读"和"传值改不动"两件事都验证到:

#include <iostream>
#include <cstring>
using namespace std;

struct Student {
    char   name[20];
    int    age;
    double score;
};

// ① 只读展示:传值最合适
void printStudent(Student s) {
    cout << "  " << s.name << "  年龄 " << s.age
         << "  成绩 " << s.score << endl;
}

// ② 计算总分:传值也可以(只读业务)
double calcBonus(Student s) {
    if (s.score >= 90) return 10.0;      // 4.2.3 的 if 阶梯
    if (s.score >= 80) return 5.0;
    return 0.0;
}

// ③ 试图修改:改的是复印件,原件不动
void tryAddScore(Student s) {
    s.score += 10;
    cout << "  函数内改完是 " << s.score << "(这是复印件)" << endl;
}

int main() {
    Student s1 = {"张三", 18, 92.5};
    Student s2 = {"李四", 19, 78.0};
    Student s3 = {"王五", 20, 55.5};

    Student cls[3] = {s1, s2, s3};       // 8.4 的结构体数组

    cout << "=== 成绩单 ===" << endl;
    for (int i = 0; i < 3; i++) printStudent(cls[i]);

    cout << "=== 加分资格 ===" << endl;
    for (int i = 0; i < 3; i++)
        cout << "  " << cls[i].name << " 可加 " << calcBonus(cls[i]) << " 分" << endl;

    cout << "=== 验证传值改不动原件 ===" << endl;
    cout << "调用前:" << cls[0].score << endl;
    tryAddScore(cls[0]);
    cout << "调用后:" << cls[0].score << "(原件没变)" << endl;

    cout << "sizeof(Student) = " << sizeof(Student) << " 字节,每次传值都拷这么多" << endl;
    return 0;
}

看到什么算成功:

=== 成绩单 ===
  张三  年龄 18  成绩 92.5
  李四  年龄 19  成绩 78
  王五  年龄 20  成绩 55.5
=== 加分资格 ===
  张三 可加 10 分
  李四 可加 0 分
  王五 可加 0 分
=== 验证传值改不动原件 ===
调用前:92.5
  函数内改完是 102.5(这是复印件)
调用后:92.5(原件没变)
sizeof(Student) = 32 字节,每次传值都拷这么多

三个观察点: - printStudent 里用 . 访问成员(因为 s 是变量); - tryAddScore 里改得热火朝天,调用方的 92.5 一点没动; - 最后一行点题:这 32 字节,每传一次就拷一次。

30 秒实验:把 printStudent(Student s) 改成 printStudent(Student &s),同时把调用处保持 printStudent(cls[i]) 不变。输出完全一样,但一个字节都没复制——这就是 8.3 里说的"只读大对象首选 const 引用"的原因。(更严谨的写法是 const Student &s,明确告诉读者"我不改它"。)

六、常见误区

  1. "函数里改了 s.score,外面的也会变吧"——不会。传值就是复印件(6.2.2 的铁律,结构体也照旧)。
  2. "结构体传值只复制指针,不复制内容"——完全错。它是整块逐字节复制,char name[20] 那 20 个字符也一并复制。只有指针变量本身才会"只复制地址"(那是 8.3.2)。
  3. "传值安全的代价可以忽略"——小结构体可以,大结构体不行。养成习惯:看到自己写的函数参数是"大的结构体值",先问一句"能不能改成 const &"。
  4. "传值的时候调用处要写 &"——不用。写了 & 那是传地址(8.3.2),语义完全不同。
  5. "形参名字必须和实参一样"——名字随便起(printStudent(Student stu) 也行)。形参和实参是两个独立的变量,只是内容一样。
自测三问(点开对答案) 1. `void f(Student s)` 里修改 `s.score`,会影响调用方的变量吗?为什么?——不会。形参 `s` 是调用时**复制出来的独立一份**(6.2.2)。 2. 传结构体值的时候,成员里的 `char name[20]` 会被复制吗?——会,**整块 32 字节全部复制**,包括那 20 个字符。 3. 什么情况下该把"传值"换成 `const Student &s`?——结构体比较大、**而且函数不需要修改它**的时候:既省掉复制,又通过 `const` 保证改不了。

相关笔记