把结构体变量直接当参数传给函数(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;
}
三件事要看清:
- 形参写
Student s——就是一个普通的结构体变量声明,和int n是同一种写法; - 调用处直接写
stu——不用&(这是传值的标志); - 函数体里用
.访问成员——因为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 万字节
}
Student32 字节,10 万次调用 = 复制 3.2 MB——还能接受;- 如果结构体里有个
char content[4096](比如一篇文章),一次复制 4KB,10 万次就是 400 MB——性能直接崩。
所以有一条实用的判断标准:
| 结构体大小 | 建议 |
|---|---|
很小(几个 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,明确告诉读者"我不改它"。)
六、常见误区
- "函数里改了
s.score,外面的也会变吧"——不会。传值就是复印件(6.2.2 的铁律,结构体也照旧)。 - "结构体传值只复制指针,不复制内容"——完全错。它是整块逐字节复制,
char name[20]那 20 个字符也一并复制。只有指针变量本身才会"只复制地址"(那是 8.3.2)。 - "传值安全的代价可以忽略"——小结构体可以,大结构体不行。养成习惯:看到自己写的函数参数是"大的结构体值",先问一句"能不能改成
const &"。 - "传值的时候调用处要写
&"——不用。写了&那是传地址(8.3.2),语义完全不同。 - "形参名字必须和实参一样"——名字随便起(
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` 保证改不了。- ☐ 8.3.2 使用结构体指针变量做函数参数(下一节)
相关笔记
- 结构体与函数 —— 上一节:三种交法总览
- 使用结构体指针变量做函数参数 —— 下一节:交法②传指针(含实例03)
- 形参与实参 —— 复印件逻辑的原版
- 使用引用传递参数 —— const 引用
- 🔷 C++基础 · 索引 —— 全课路线图