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

结构体当然能当函数的参数、也能当返回值。关键是"怎么交出去"有三种选择:整体传值(复制一份给函数,函数里改不动原件)、传指针(把地址交出去,函数里能改原件)、传引用(写法最干净,也能改原件)。结构体越大,"复制"的代价就越明显——所以实际开发里"传指针/引用"是主流,"传值"只用在"小结构体 + 我保证不改"的场合。

📝 本节和前面几节的关系

这是一次大团圆:6.2.2 的"形参是复印件"、6.3.1 的 swap 换不动、7.6.1 的"传地址能改原件"、7.8.2 的"传引用最干净"——这些在第五章、第六章、第七章反复演练的结论,现在全部套用到结构体上。区别只在于:结构体的"体量"比 int 大得多(32 字节 vs 4 字节),所以"复制"的代价第一次变得值得计较。

一、结构体当参数的三种"交法"

// 交法 ①:整体传值 —— 函数收到的是"复印件"
void printStudent(Student s) {
    cout << s.name << " " << s.age << endl;
}
printStudent(stu);

// 交法 ②:传指针 —— 函数收到的是"地址"
void raiseScore(Student *p) {
    p->score += 5;
}
raiseScore(&stu);

// 交法 ③:传引用 —— 函数收到的是"别名"
void raiseScore2(Student &s) {
    s.score += 5;
}
raiseScore2(stu);

一句话抓住区别:

交法 函数收到什么 能改原件吗 写法代价
传值 一份完整拷贝(32 字节全搬) 不能 调用处最简,但有拷贝开销
传指针 一个地址(4 或 8 字节) 能 调用处要 &,体内要 ->
传引用 原件的别名(本质也是地址) 能 调用处和体内都最干净

二、为什么结构体的"复制"值得计较

回忆 6.2.2:int 传值是复制 4 个字节——几乎不要钱。可结构体不一样:

void f(Student s);          // 每调用一次,复制 32 字节(8.1.5)
void g(Student arr[1000]);  // 如果是个 1000 元素的结构体数组……别想传值

结论: - 只读 + 结构体很小 → 传值(安全,写法简单) - 只读 + 结构体较大 → const Student &s(7.8.2 的 const 引用:不拷贝、还不能改) - 要改原件 → 传指针(Student *p)或传引用(Student &s)

三、和第七章的老结论对一遍

老结论(第七章) 结构体版
void swap(int *a, int *b) + swap(&x, &y)(7.6.1) void f(Student *p) + f(&stu)
void swap(int &a, int &b) + swap(x, y)(7.8.2) void f(Student &s) + f(stu)
传值是"复印件",改不动原件(6.2.2) 一模一样,只是复印件从 4 字节变成 32 字节
const T &x 是只读大对象的首选(7.8.2) 更适合结构体,因为它真的"大"

所以本节其实没有新知识——只是把老知识放到一个"体量变大"的场景里,让你第一次真切感到"复制是有代价的"。

四、结构体当返回值

结构体也能当函数的返回值,返回的是整块复制回去:

Student makeStudent(const char *n, int a, double sc) {
    Student s;
    strcpy(s.name, n);
    s.age   = a;
    s.score = sc;
    return s;                   // 返回一整块(32 字节)
}

Student s1 = makeStudent("张三", 18, 92.5);

注意: - 返回的是局部变量 s 的副本,函数结束后 s 被回收,但副本已经安全地交到调用方手里——这不违反 7.6.3 那条"绝不能返回局部变量地址"。区别在于:返回的是"值"(会复制),返回指针才是"地址"(会悬空)。 - 结构体作为返回值的代价同样是复制,但现代编译器有"返回值优化",多数情况下不会真的多搬一次。

五、本节两篇的分工

小节 讲哪种交法
8.3.1 使用结构体变量做函数参数 交法 ① 传值(含实例02)
8.3.2 使用结构体指针变量做函数参数 交法 ② 传指针(含实例03),顺带对照 ③ 传引用

传引用在 7.8.2 已经讲透了,本节不再单列小节——它的用法和"传 int &"完全一样,只是把 int 换成 Student。

六、动手 Demo:三种交法一次对照

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

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

// ① 传值:收到的是一份拷贝
void tryChangeByValue(Student s) {
    s.score = 100;                  // 改的是拷贝
    cout << "  函数内(传值):score = " << s.score << endl;
}

// ② 传指针:能改原件
void changeByPointer(Student *p) {
    p->score = 100;                 // -> 是"指针的点"
    cout << "  函数内(指针):score = " << p->score << endl;
}

// ③ 传引用:能改原件,写法最干净
void changeByReference(Student &s) {
    s.score = 100;
    cout << "  函数内(引用):score = " << s.score << endl;
}

int main() {
    Student stu = {"张三", 18, 50.0};    // 初始 50 分

    cout << "原始 score = " << stu.score << endl;

    tryChangeByValue(stu);          // 传值
    cout << "调用后 score = " << stu.score << "(没变,32 字节白复制了)" << endl;

    changeByPointer(&stu);          // 传指针(注意 &)
    cout << "调用后 score = " << stu.score << "(变了)" << endl;

    stu.score = 50.0;               // 复位
    changeByReference(stu);         // 传引用(不用 &)
    cout << "调用后 score = " << stu.score << "(也变了,且写法更干净)" << endl;

    cout << "结构体本身占 " << sizeof(Student) << " 字节" << endl;
    return 0;
}

看到什么算成功:

原始 score = 50
  函数内(传值):score = 100
调用后 score = 50(没变,32 字节白复制了)
  函数内(指针):score = 100
调用后 score = 100(变了)
  函数内(引用):score = 100
调用后 score = 100(也变了,且写法更干净)
结构体本身占 32 字节

一句话读结果:传值是"复印了一份 32 字节的卷子给函数改",原件纹丝不动;传指针和传引用是"把原件的位置告诉函数",所以真改了。

30 秒实验:把 tryChangeByValue(Student s) 里的 s 改成 Student &s(只加一个 &),重新运行——"调用后 score"立刻变成 100。一个符号的差别,就是"改不动"和"能改"的分水岭(7.8.2 的老结论)。

七、常见误区

  1. "传值和传指针差不多,反正 C++ 会自动优化"——不一样。传值有明确语义(函数内改不了原件),编译器不能随意改变这个行为。"要不要让函数改原件"是设计决策,不能靠猜测。
  2. "结构体大就一定要传指针"——只读时首选 const Student &s:既不复制(快),又保证改不了(安全),还不用写 & 和 ->(干净)。指针更适合"这个参数可能为空"的场合(nullptr,7.4.4)。
  3. "返回结构体会出安全问题"——不会。返回"值"会复制,是安全的;危险的是返回局部变量的"地址"(7.6.3)。这两个是完全不同的事,别混。
  4. "结构体传值的时候,里面的数组会被复制吗"——会,整块 32 字节全复制,包括 name[20] 那 20 个字符。所以函数里改 s.name 也改不到原件。
自测三问(点开对答案) 1. 结构体当函数参数有哪三种交法?各自能否改原件?——传值(不能)、传指针(能)、传引用(能)。 2. 只读又不希望复制,应该怎么声明参数?——`const Student &s`(const 引用)。 3. 返回"结构体值"和返回"结构体指针"有什么关键差别?——返回值会**复制**,安全;返回**局部变量**的指针会悬空,是 7.6.3 禁止的写法。

相关笔记