1.核心原理
指针:内存中拿出一小块空间来存储指向变量的地址,从而实现对这个变量的间接访问。
引用:当你声明一个引用时,编译器实际上是在内部为这个引用创建了一个指向原始对象的指针(或者更精确地说,是一种对原始对象的直接访问机制)。但是,这种指针对用户是隐藏的,你不能直接访问或操作它。在C++中,你不能获取引用的地址(即你不能对引用使用&
操作符),也不能让引用重新引用另一个对象。(文心一言)
简单来说,我感觉引用就是一个“别名”,在帮助程序员直接访问这个变量,同时提醒程序员这个变量的特性(比如能不能修改)
2.优缺点
使用引用作为函数参数
优点:
- 避免复制:当使用引用传递对象时,实际上是在传递对象的别名,而不是对象的副本。这避免了复制大型对象时可能产生的性能开销。
- 简洁性:在函数内部,你可以直接使用引用名来访问对象,无需使用解引用操作符(
*
)。 - 更清晰的语义:引用通常用于表示“你期望传入的对象不会被修改”或“你期望传入的对象可能会被修改”。
例:
表示不会被修改的对象:
当你想表示一个函数不应该修改其接收到的参数时,你可以使用const
引用。这样做的好处是,它向调用者明确传达了函数的意图,并且编译器会帮助你确保这一点。如果函数试图修改一个const
引用所引用的对象,编译器会报错。
示例:
void printInfo(const LargeClass& obj) {
// 在这里,你不能修改obj的任何成员,因为它是const引用
std::cout << obj.getSomeData() << std::endl;
}
调用者看到const
引用时,会知道他们的对象不会被这个函数修改。
表示可能会被修改的对象:
如果你不使用const
,那么引用表示的是一个可能会被修改的对象。调用者应该意识到,他们传入的对象在函数内部可能会被修改。
示例:
void modifyData(LargeClass& obj) {
obj.setData(42); // 修改obj的数据
}
在这个例子中,调用者应该意识到他们传入的对象obj
在modifyData
函数内部可能会被修改。
与指针相比,使用引用可以更清晰地表达这些意图。虽然你也可以使用指向const
对象的指针来表示不可修改的对象,但指针的语义通常与动态内存管理、所有权转移和可空性更相关。
此外,由于引用在语法上更接近于直接操作对象(无需使用*
解引用),因此代码通常更易读和维护。当然,这也取决于具体的上下文和团队的编码风格。在某些情况下,使用指针可能是更合适的选择。
缺点:
- 必须初始化:引用在声明时必须被初始化,这意味着你不能声明一个未初始化的引用参数。
- 不能为空:引用不能是空引用(即没有指向任何对象),这可能会在某些情况下限制其使用。
- 可能导致意外的修改:如果函数内部修改了引用的对象,这可能会影响到原始对象,这有时可能不是期望的行为。
使用指针作为函数参数
优点:
- 灵活性:指针可以被初始化为
nullptr
(空指针),这允许函数处理没有传入对象的情况。 - 可控性:你可以检查指针是否为空,这增加了代码的健壮性。
- 可以修改指针本身:虽然你不能通过指针改变它所指向的对象的内容(除非它是一个指向非常量的指针),但你可以改变指针本身的值,使其指向不同的对象。
缺点:
- 需要解引用:在函数内部,你需要使用解引用操作符(
*
)来访问指针指向的对象,这可能会使代码稍显繁琐。 - 可能的空指针解引用:如果指针未被正确初始化或意外地设置为
nullptr
,解引用该指针将导致未定义行为,这通常是一个严重的错误。 - 语义可能不清晰:指针的语义可能不如引用那么直接。例如,一个指向常量的指针(
const T*
)和一个指向非常量的指针(T*
)在语义上有很大的区别,这可能会增加代码的复杂性。
总结
- 性能:在传递大型对象时,引用通常比指针更高效,因为它避免了复制。
- 安全性:指针提供了更多的灵活性,但也增加了出错的可能性(如空指针解引用)。
- 易用性:引用在函数内部的使用更加直观和简洁,而指针需要更多的注意和小心处理。
在C++中,通常推荐在以下情况下使用引用:
- 当你知道参数不会被设置为
nullptr
时。 - 当你希望确保参数不会被修改时(使用
const
引用)。 - 当你不希望参数被复制时(特别是对于大型对象)。
而在其他情况下,使用指针可能更加合适。(文心一言)
总体上,感觉引用就是程序员希望能够做到一种“直接访问的机制”,希望这个“引用”能够作为一个标签一样的存在提醒自己这个变量是否要修改。(即语义更加清晰)