引用必须初始化且不可重绑定,是所引用类型的别名;指针可延迟初始化、可空、可重指向,sizeof引用返回类型大小,指针返回地址字节数。

引用必须初始化,指针可以不初始化
声明一个引用时,int& r 必须绑定到一个已存在的对象,比如 int x = 42; int& r = x;。不初始化会直接编译失败,错误类似 error: declaration of reference variable 'r' requires an initializer。而指针可以先声明后赋值:int* p; 合法,之后再 p = &x;。
这背后是语义差异:引用是别名,从诞生起就必须“是某个东西”;指针是地址的容器,可以为空、可以改指向、可以悬空。
- 引用一旦绑定,不能重新绑定到另一个对象(
int y = 10; r = y;是赋值,不是重绑定) - 指针可多次赋值:
p = &y;是合法的重指向 -
nullptr只对指针有意义,引用没有“空引用”概念
sizeof 引用和指针返回的值不同
sizeof 作用于引用时,返回的是所引用类型的大小,不是引用本身的开销;而作用于指针时,返回的是该平台下地址的字节数(通常 4 或 8 字节)。
例如:
立即学习“C++免费学习笔记(深入)”;
int x = 100; int& r = x; int* p = &x; std::cout << sizeof(r) << " " << sizeof(p); // 输出可能是 "4 8"(64 位系统)
这说明编译器通常把引用实现为隐式地址传递,但语法上它不占独立存储空间——你无法取引用的地址(&r 实际上是 &x),也无法定义引用数组(int& arr[5]; 非法)。
- 引用没有自己的内存地址,
&r永远等价于&x - 指针有明确的地址,
&p是指针变量自身的地址 - 引用不能做算术运算(
r++是对原值操作,r + 1是值运算),指针支持++、+、-等
函数参数传参时,引用避免拷贝且不可空,指针更灵活但需检查
当函数需要修改实参或传大对象时,常用 void func(const std::string& s) 避免拷贝;而如果允许“不传”(即可选参数),就得用指针:void func(const std::string* s),调用方可以传 nullptr。
这是实际编码中最容易踩坑的地方:误用引用导致无法表达“无值”语义。
- 引用形参强制要求调用方提供有效对象,否则编译不过(如
func(some_string)) - 指针形参调用方可能传
nullptr,函数内部必须检查:if (s != nullptr) { ... } - 现代 C++ 倾向用
std::optional或重载来替代裸指针表达可选性,但底层接口仍常见指针
const 引用能绑定临时对象,const 指针不能自动获得临时对象生命周期
const int& r = 42; 合法,编译器会延长临时整数的生命周期至 r 作用域结束;但 const int* p = &42; 编译失败——你不能取字面量的地址。
这个特性让 const 引用成为安全绑定临时值的首选,尤其在模板和泛型编程中:
templatevoid f(const T& arg); // 可接受字面量、右值、左值
而指针版本做不到这点,除非显式创建变量再取地址。
-
const T&是万能转发引用的基石(配合模板推导) -
T*无法绑定到纯右值,const T*也不能绕过取地址限制 - 非 const 引用(
T&)也不能绑定临时对象,只有 const 引用有此特权
引用和指针在底层常常被编译器生成相似的汇编,但语言规则截然不同。最容易忽略的是:引用不是“轻量级指针”,它根本不是一个对象;而所有对引用的“操作”,本质上都是对目标对象的操作——包括 typeid、sizeof、甚至调试器里看到的值。混淆这一点,就会在模板特化、类型推导或调试内存布局时掉进深坑。









