C++ 中的值传递和地址传递
在 C++ 中,函数参数传递主要有两种方式:值传递和地址传递(指针传递和引用传递都属于地址传递的变体)。
1. 值传递
特点
- 函数接收的是实参的副本
- 对形参的修改不会影响原始变量
- 适用于小型数据(基本类型、小型结构体)
void increment(int x) {
x++; // 只修改局部副本
}
int main() {
int a = 5;
increment(a);
cout << a; // 输出 5,原始值未改变
return 0;
}
值传递优缺点
优点:
- 安全,原始数据不会被意外修改
- 实现简单直观
缺点:
- 对于大型对象,复制开销大
- 无法通过参数返回修改结果
2. 地址传递
2.1 指针传递
void increment(int* x) {
(*x)++; // 解引用并修改原始值
}
int main() {
int a = 5;
increment(&a); // 传递地址
cout << a; // 输出 6,原始值被修改
return 0;
}
2.2 引用传递
void increment(int& x) {
x++; // 直接修改原始值
}
int main() {
int a = 5;
increment(a); // 看起来像值传递,实际是引用传递
cout << a; // 输出 6
return 0;
}
地址传递的优缺点
优点:
- 避免大型对象的复制开销(减少内存拷贝,提升性能)
- 可以在函数中修改实参的值
#include <iostream>
using namespace std;
void func(int a, int b, int c, int* max, int* min)
{
// 先假设a是最大值,b是最小值
*max = a;
*min = b;
if (b > a) {
*max = b;
*min = a;
}
// 检查c是否比当前最大值大
if (c > *max) {
*max = c;
}
// 检查c是否比当前最小值小
else if (c < *min) {
*min = c;
}
}
int main()
{
int a = 5;
int b = 8;
int c = 1;
int max, min; // 声明max和min变量
func(a, b, c, &max, &min); // 传递max和min的地址
cout << "max = " << max << ", " << "min = " << min << endl;
return 0;
}
缺点:
- 可能意外修改原始数据(指针传递)
- 指针可能为 nullptr(需要检查)
- 引用不能重新绑定(有时是优点)
3. 对比表格
特性 | 值传递 | 指针传递 | 引用传递 |
---|---|---|---|
传递内容 | 值的副本 | 内存地址 | 别名(隐式指针) |
语法 | func(int x) | func(int* x) | func(int& x) |
调用方式 | func(a) | func(&a) | func(a) |
修改原始值 | 不能 | 能 | 能 |
空值检查 | 不需要 | 需要 | 不需要 |
性能 | 小型数据高效 | 大型数据高效 | 大型数据高效 |
安全性 | 高 | 中(可能空指针) | 高 |
可读性 | 直观 | 需要理解指针 | 直观 |
4. 现代C++最佳实践:
- 优先使用const引用传递大型只读对象
void printBigObject(const BigObject& obj);
- 使用智能指针代替裸指针进行资源管理
void process(std::shared_ptr<Resource> res);
5. 示例:三种方式实现swap函数
// 值传递(无法实现swap)
void swap_fail(int a, int b) {
int temp = a;
a = b;
b = temp;
}
// 指针传递
void swap_ptr(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
// 引用传递(推荐)
void swap_ref(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
int main() {
int x = 1, y = 2;
swap_fail(x, y);
cout << x << " " << y; // 1 2(未交换)
swap_ptr(&x, &y);
cout << x << " " << y; // 2 1(已交换)
swap_ref(x, y);
cout << x << " " << y; // 1 2(再次交换)
return 0;
}
为什么函数内部不能直接修改指针本身
在 C++ 中,函数参数传递默认是值传递,包括指针参数。
1. 指针参数的本质
当你传递指针给函数时:
void func(int* ptr) {
ptr = new int(10); // 这只会修改局部副本
}
int main() {
int* p = nullptr;
func(p);
// p 仍然是 nullptr!
}
这是因为:
- 函数接收到的是指针的副本(指针值的拷贝)
- 修改这个副本(
ptr = ...
)不会影响原始指针 - 这就像值传递一个
int
参数一样
2. 可以修改指针指向的内容
虽然不能直接修改指针本身,但可以修改指针指向的内容:
void modifyContent(int* ptr) {
*ptr = 10; // 这会修改指针指向的内存
}
int main() {
int x = 5;
int* p = &x;
modifyContent(p);
// x 现在是 10
}
3. 如何真正修改指针本身
如果需要修改指针本身(而不仅是指向的内容),你需要:
方法1:使用指针的指针(二级指针)
void modifyPointer(int** ptrPtr) {
*ptrPtr = new int(20); // 修改原始指针
}
int main() {
int* p = nullptr;
modifyPointer(&p);
// p 现在指向新分配的 int
delete p; // 记得释放内存
}
方法2:使用指针的引用(更推荐)
void modifyPointer(int*& ptrRef) {
ptrRef = new int(30); // 直接修改原始指针
}
int main() {
int* p = nullptr;
modifyPointer(p);
// p 现在指向新分配的 int
delete p;
}
4. 原始代码问题分析
void func(..., int* max, int* min) {
max = &a; // 这只修改了局部副本
min = &b; // 这只修改了局部副本
}
这些赋值操作只修改了函数内部的指针副本,不会影响 main()
中的原始指针。
5. 解决方案对比
方法 | 示例 | 优点 | 缺点 |
---|---|---|---|
返回值 | int* func() | 简单 | 只能返回一个值 |
二级指针 | func(int** max) | 可以修改多个指针 | 语法复杂 |
指针引用 | func(int*& max) | 语法简洁 | 需要C++引用支持 |
修改指向的内容 | func(int* max) | 最简单 | 需要预先分配内存 |
6. 最佳实践建议
- 如果只需要修改指针指向的内容,使用一级指针
- 如果需要修改指针本身,优先使用指针引用 (
int*&
) - 在C代码中或需要兼容C时,使用二级指针 (
int**
) - 考虑使用智能指针 (
std::unique_ptr&
) 来避免内存管理问题