结构体当然能当函数的参数、也能当返回值。关键是"怎么交出去"有三种选择:整体传值(复制一份给函数,函数里改不动原件)、传指针(把地址交出去,函数里能改原件)、传引用(写法最干净,也能改原件)。结构体越大,"复制"的代价就越明显——所以实际开发里"传指针/引用"是主流,"传值"只用在"小结构体 + 我保证不改"的场合。
这是一次大团圆: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 元素的结构体数组……别想传值
Student32 字节,一次调用复制 32 字节——还行;- 但如果结构体里有
char info[1024],就是 1KB 一次; - 8.4 的结构体数组更夸张:
Student cls[1000]是 32000 字节,传值等于每次调用都搬 32KB。
结论:
- 只读 + 结构体很小 → 传值(安全,写法简单)
- 只读 + 结构体较大 → 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 的老结论)。
七、常见误区
- "传值和传指针差不多,反正 C++ 会自动优化"——不一样。传值有明确语义(函数内改不了原件),编译器不能随意改变这个行为。"要不要让函数改原件"是设计决策,不能靠猜测。
- "结构体大就一定要传指针"——只读时首选
const Student &s:既不复制(快),又保证改不了(安全),还不用写&和->(干净)。指针更适合"这个参数可能为空"的场合(nullptr,7.4.4)。 - "返回结构体会出安全问题"——不会。返回"值"会复制,是安全的;危险的是返回局部变量的"地址"(7.6.3)。这两个是完全不同的事,别混。
- "结构体传值的时候,里面的数组会被复制吗"——会,整块 32 字节全复制,包括
name[20]那 20 个字符。所以函数里改s.name也改不到原件。
自测三问(点开对答案)
1. 结构体当函数参数有哪三种交法?各自能否改原件?——传值(不能)、传指针(能)、传引用(能)。 2. 只读又不希望复制,应该怎么声明参数?——`const Student &s`(const 引用)。 3. 返回"结构体值"和返回"结构体指针"有什么关键差别?——返回值会**复制**,安全;返回**局部变量**的指针会悬空,是 7.6.3 禁止的写法。- ☐ 8.3.1 使用结构体变量做函数参数(下一节)
相关笔记
- 结构体的大小 —— 复制代价有多大
- 使用结构体变量做函数参数 —— 下一节:传值细节(含实例02)
- 形参与实参 —— "形参是复印件"的出处
- 使用引用传递参数 —— const 引用为什么是大对象首选
- 🔷 C++基础 · 索引 —— 全课路线图