值传递 vs 引用传递
更新时间:2026-08-27。本文是
languages/cpp/主题入门层第 14 篇。上一篇认识了引用,这一篇把它用在函数参数上:按值传会复制一份,按引用传是同一块内存。"想改实参用引用,大对象用 const 引用,小对象按值"——这条经验法则背后是性能和语义的双重考量。
本文要回答的问题
- 按值传和按引用传,内存里到底发生了什么?
const 引用为什么是"只读不拷贝"的黄金参数?- 什么时候按值、什么时候按引用?
一、按值:复制一份,改不动外面
void inc_by_value(int n) {
n += 1; // 改的是副本
}
int x = 10;
inc_by_value(x);
std::cout << x; // 还是 10inc_by_value(x) 把 x 的值复制一份给形参 n,函数里改 n 跟外面的 x 毫无关系。这跟 C 完全一样——C 里想改实参只能传指针。
二、按引用:同一块内存,直接改
void inc_by_ref(int& n) {
n += 1; // 改的就是实参
}
int x = 10;
inc_by_ref(x);
std::cout << x; // 11int& n 是 x 的引用,函数里 n += 1 直接写进 x 的内存。这就是上一篇说的"引用是别名"在函数参数上的应用——C 的 int* n + 手动解引用,C++ 的 int& n 自动完成:
// C:传指针
void inc(int* n) { *n += 1; } // 调用:inc(&x);// C++:传引用
void inc(int& n) { n += 1; } // 调用:inc(x);结果一样,C++ 少写 * 和 &,调用点也更干净。
三、const 引用:只读不拷贝
函数只是"借用"参数来读,不想改、也不想复制,就用 const T&:
void print_msg(const std::string& msg) {
std::cout << msg; // 只读,OK
// msg += "!"; // 错误:const 引用不能改
}
print_msg("hello"); // 字符串字面量也能直接传!三个好处:
- 不拷贝:
std::string按值传会复制整个字符串(底层还涉及堆分配),const&只传一个地址; - 能读不能改:
const是编译器帮你上的锁,误写直接编译报错; - 能接收临时值:字面量、函数返回的临时对象都能绑定到
const&(非 const 引用不行)。
第三条很实用:print_msg("hello") 里 "hello" 是个临时字符串,传 const std::string& 可以,传 std::string& 就编译失败。
四、怎么选:一条经验法则
| 场景 | 参数写法 | 理由 |
|---|---|---|
| 想改实参 | T& | 直接改原对象 |
| 大对象只读 | const T& | 不拷贝 + 安全 |
| 小对象(int/double/指针) | T 按值 | 拷贝便宜,还少一次间接 |
| 返回新对象 | 返回值(别返回局部引用!) | 见下方警告 |
经验法则:按值传 int、double 这种"拷贝跟没拷一样便宜"的类型;按值传 std::string、std::vector 这种要拷贝内部数据的,一律 const&。这也是为什么前面 11 篇里 print_log(const std::string& msg) 一直这么写——现在明白了吧。

五、警告:别返回局部变量的引用
int& bad() {
int local = 42;
return local; // 危险!函数结束 local 就没了
}返回局部变量的引用 = 返回一个悬垂引用(dangling reference),函数一结束内存就被回收,用它是未定义行为。能返回值就返回值,现代编译器有 RVO(返回值优化)帮你省拷贝,见高手层拷贝省略与 RVO。
六、与本站主线衔接
- 引用传递的底层就是传地址,与 C 的指针模型互通,见 C 值传递与传地址;
const引用、拷贝开销与对象生命周期,专家层对象内存布局与移动语义与右值引用会深挖;- 下一篇讲 inline 函数——编译器层面的"免调用开销",是函数主题的收尾。
一句话总结
按值传参复制一份(改不动实参),按引用传参共用内存(直接改实参),const T& 是"只读不拷贝"的黄金参数;小对象按值、大对象 const&、要改实参用 T&;千万别返回局部变量的引用,能返回值就返回值(编译器有 RVO 兜底)。