前情回顾
在前面的课程中,我们已经学习了两种向函数传递实参的方式:
- 值传递(《函数形参与实参简介》)
- 引用传递(《通过左值引用传参》)
下面通过一段示例代码比较三者(值传递、引用传递、地址传递):
#include <iostream>
#include <string>
void printByValue(std::string val) // 形参为实参的副本
{
std::cout << val << '\n';
}
void printByReference(const std::string& ref) // 形参为实参的引用
{
std::cout << ref << '\n';
}
void printByAddress(const std::string* ptr) // 形参为保存实参地址的指针
{
std::cout << *ptr << '\n';
}
int main()
{
std::string str{ "Hello, world!" };
printByValue(str); // 值传递:拷贝 str
printByReference(str); // 引用传递:无拷贝
printByAddress(&str); // 地址传递:无拷贝
return 0;
}
地址传递(pass by address)
地址传递通过指针完成:
- 调用者把对象的地址(指针)传给函数。
- 形参为指针类型,保存该地址。
- 函数通过解引用指针访问原对象。
关键点
- 无对象拷贝:仅拷贝地址(4 或 8 字节),速度极快。
- 可修改实参:若指针指向非 const 对象,函数可修改原对象。
- 需显式取址:调用时用
&
获取地址,或先存于指针变量再传。
示例:修改实参
void changeValue(int* ptr) { *ptr = 6; }
int main() {
int x{ 5 };
changeValue(&x); // x 变为 6
}
为什么使用地址传递
- 避免昂贵拷贝(如
std::string
)。 - 需要修改实参。
- 接口必须与 C 兼容 或需要指针语义(如动态数组)。
const 指针形参
- 只读:形参声明为
const T*
防止修改实参。 - 可写:形参为
T*
允许修改。
不推荐将指针形参本身声明为 const
(如 T* const
),除非确有必要;否则会增加视觉噪音,掩盖“是否可改对象”这一关键信息。
void foo(const char* src, char* dst); // 简洁清晰
void foo(const char* const src, char* const dst); // 噪音大
空指针检查
地址传递需防止解引用空指针:
void print(int* ptr) {
if (!ptr) return; // 空指针直接返回
std::cout << *ptr << '\n';
}
更严谨:用 assert
断言非空(调试期)并提前返回(发布期)。
#include <cassert>
void print(const int* ptr) {
assert(ptr); // 调试期断言
if (!ptr) return;
std::cout << *ptr << '\n';
}
地址传递 vs 引用传递
特性 | 地址传递 | 引用传递 |
---|---|---|
语法 | &obj / ptr | 直接传对象 |
可空 | 是(需检查) | 否 |
可接受右值 | 否 | 是(const 引用) |
可重新指向 | 是 | 否 |
语义直观性 | 低(需 & 和 *) | 高 |
现代 C++ 准则:
- 能引用就引用,必须指针才指针。
- 字符串形参优先
std::string_view
(值传递),次选const std::string&
。
结论
地址传递通过指针实现,既避免拷贝又能修改实参,但需手动处理空指针与语法噪音。除非接口需要,否则优先使用引用传递。