指针可为空、可重赋值,引用必须初始化且绑定后不可更改目标;指针大小依赖平台,引用大小等于所引类型;返回局部变量时用引用会导致悬垂引用,需谨慎管理生命周期。

指针可以为空,引用必须绑定有效对象
这是最常踩坑的一点:声明 int* p 后不初始化,p 是野指针;而 int& r 声明时就必须初始化,且不能是 nullptr 或临时变量(除非是 const 引用绑定右值)。编译器会直接报错:error: declaration of reference variable 'r' requires an initializer。
实操建议:
- 需要表示“可能不存在”时,用指针(比如函数返回查找结果,没找到就返回
nullptr) - 想强制约束接口必须传入有效对象时,优先用引用(比如
void process(const std::string& s)) - 不要试图写
int& r = *p;而p恰好是nullptr—— 这是未定义行为,运行时大概率崩溃
指针可重新赋值,引用绑定后不可更改目标
int a = 1, b = 2; int* p = &a; p = &b; 合法;但 int& r = a; r = &b; 是错的 —— 这里 r = &b 实际执行的是 a = *(&b),也就是把 b 的值赋给 a,r 依然绑在 a 上。
常见误解场景:
立即学习“C++免费学习笔记(深入)”;
- 循环中想让引用“切换”对象?不行。只能用指针
- 函数参数用引用是为了避免拷贝,不是为了后期改指向 —— 它的绑定在初始化那一刻就固定了
-
std::vector的operator[]返回引用,所以能写v[0] = 42;但它返回的不是“可重绑定的引用”,只是对元素的别名
sizeof 和 typeid 行为不同
sizeof(int*) 在 64 位系统上通常是 8,和平台相关;sizeof(int&) 等于 sizeof(int)(通常为 4),因为引用本身不占额外存储 —— 它只是别名,编译器通常用指针实现,但语义上不暴露这个细节。
类型识别也不同:
-
typeid(p).name()返回指针类型(如"Pi"表示int*) -
typeid(r).name()返回被引用的类型(如"i"表示int) - 这意味着模板推导、
auto类型判断也会有差异:auto x = r;推出int,auto y = &r;才推出int*
函数返回值该用指针还是引用
关键看生命周期:如果返回的是局部变量,用引用就是悬垂引用(dangling reference),比悬垂指针更隐蔽,因为编译器不一定警告。
实操建议:
- 返回栈上临时对象?禁止返回非 const 引用,const 引用也不安全(C++11 后延长生命周期仅限于纯右值,且只限于直接绑定)
- 返回堆内存或静态存储期对象?可用指针,也可用引用(如单例的
getInstance()常返回MyClass&) - 返回容器元素?
std::vector::at()返回T&,前提是确保索引有效;越界时抛异常,而不是静默返回垃圾引用 - 不确定所有权或生命周期?优先返回
std::optional<t></t>或智能指针,而不是裸指针/引用
真正难的不是语法区别,而是每次写 & 或 * 时,你得在脑子里确认:这个变量的生命期归谁管?它会不会在别处被销毁?编译器不会替你检查所有绑定路径,尤其涉及跨作用域、多线程或 RAII 对象析构顺序时。










