拷贝构造时,不用引用的话,按值传参会无限递归调用拷贝构造函数

c++引入引用的原因

引用不是"锦上添花"的语法糖,而是解决了几个 C 语言里很别扭的问题:

1.b 引用在实际代码里用得最多的地方------避免拷贝、允许函数修改外部变量。

1.c 引用&ref声明时就要初始化,且初始化后eg &r=a; 此时r=b是把b值给a了

1. 拷贝构造函数/赋值运算符必须用引用

cpp 复制代码
class String {
public:
    String(const String& other);        // 只能用引用,不能用 String other
    String& operator=(const String& other);
};

如果拷贝构造函数写成 String(String other)(按值传参),那么调用它本身就需要拷贝一份实参------而拷贝一份实参又得调用拷贝构造函数------无限递归。这是引用存在的最根本原因之一。

2. 让运算符重载"看起来像内置类型"

cpp 复制代码
a = b + c;        // 而不是 a = *(b.operator+(&c));

如果不能用引用,运算符重载要么按值传递(有拷贝开销),要么用指针(语法丑陋,调用方要写 operator+(&b, &c))。引用让重载运算符时既保留值语义的书写方式,又避免了不必要的拷贝。

3. 表达"这个参数不可能为空"的语义

指针可以是 nullptr,函数内部理论上都要考虑判空;引用一旦绑定就必然指向一个合法对象(当然你可以用 *(T*)nullptr 强行破坏这个约定,但那是未定义行为,不是正常用法)。这让接口的契约更清晰。

返回 T& vs 返回 T

这确实主要是为了避免拷贝 ,但更准确地说,是在表达这个返回值到底是"新对象"还是"已有对象的别名"。

cpp 复制代码
class Vector {
    std::vector<int> data;
public:
    int& operator[](size_t i) { return data[i]; }   // 返回 T&
    int  at_copy(size_t i)    { return data[i]; }   // 返回 T
};

Vector v;
v[0] = 42;          // 合法!因为 operator[] 返回的是内部元素的引用
v.at_copy(0) = 42;  // 编译错误!右值不能被赋值,返回的是临时拷贝

区别本质:

返回 T 返回 T&
语义 构造/移动出一个新对象给调用者 返回已存在对象的别名
生命周期 调用者获得独立所有权 依赖原对象继续存活,用完即弃、不能延长其生命周期
能否作为左值赋值 不能(是纯右值) 能
开销 现代编译器有 RVO/NRVO + 移动语义,很多情况下拷贝开销已经很小甚至为零 理论上零开销,但前提是原对象必须还活着

关键坑:返回局部变量的引用会导致悬空引用(dangling reference):

cpp 复制代码
int& bad() {
    int x = 10;
    return x;   // x 在函数返回后销毁,返回的引用悬空
}

所以"要不要返回引用"不完全是拷贝开销的权衡,更是一个所有权/生命周期 的设计决策:operator[]、operator*(迭代器解引用)、operator= 返回 *this(支持链式调用 a = b = c)都必须用引用,因为语义上要求返回的就是那个已存在的对象本身,而不是一份拷贝。

用指针能不能实现同样的效果?

技术上完全可以------C 语言几十年就是这么干的:

cpp 复制代码
int* at(std::vector<int>& v, size_t i) { return &v[i]; }
*at(v, 0) = 42;   // 可以工作,但难看

区别在于工程体验,而不是能力上限:

  • 语法 :指针要写 *p、p->,还要考虑空指针检查;引用直接当普通变量用,无需解引用符号。
  • 可重新绑定性 :指针可以指向别的对象(p = &other;),引用一旦初始化就永久绑定同一个对象,不能改指向------这让引用更适合表达"这个名字就是那个对象的别名"这种不可变关系。
  • 不能为空:省去大量判空样板代码,接口契约更清晰(虽然极端情况下引用也能被"悬空",但正常使用下不会是 null)。
  • 运算符重载/模板场景 :for (auto& x : container)、std::sort 内部的比较、STL 容器的 operator[] 等,如果全用指针写,代码会充满 * 和 &,可读性大幅下降。
  • 不能做指针算术 :这既是限制也是优点------引用不会被误用来做 p++ 这种越界风险操作。
  • 不能作为容器元素类型 :std::vector<int&> 不合法(引用不可重新赋值,不满足容器元素要求的可赋值性),这时候就必须退回指针或 std::reference_wrapper。

一句话总结:引用不是指针做不到的能力扩展,而是在指针能做的一部分场景里,提供了更安全、语法更接近值类型的替代方案;底层实现上编译器通常也是用类似指针的机制来实现引用的,但在类型系统和使用契约层面,两者是刻意设计得不同的。

相关推荐
第九序言T3 小时前
Python 数据处理进阶(二十二)用 Pandas 做一次完整的用户行为分析
开发语言·python·数据分析·pandas
云和数据.ChenGuang3 小时前
再论深拷贝和浅拷贝
开发语言·人工智能·python·1024程序员节
晚风予卿云月4 小时前
【C++高并发内存池】 2.ThreadCache设计
开发语言·c++·高并发内存池·tcmalloc
Dovis(誓平步青云)5 小时前
拖动关键帧角色就抽动,动画编辑器怎样补齐中间状态
开发语言·前端·javascript·机器学习·编辑器
行走的陀螺仪5 小时前
Claude Code 从入门到实战(二):Bash工具、Plan模式与后台任务
开发语言·bash
努力努力再努力wz5 小时前
【Lua 入门系列】从执行模型到事件驱动:一文理解 Lua 有栈协程、挂起恢复与协作式调度
开发语言·lua
椒颜皮皮虾྅5 小时前
一套 C# 接口,解锁四类视觉任务:DeploySharp 分类、分割、关键点与旋转框实战
开发语言·分类·c#
2603_965896625 小时前
JavaScript 原型与原型链
开发语言·前端·javascript
朋友圈自动点赞工具5 小时前
Ruby方法查找:对象不存方法
开发语言·前端·ruby·科技资讯
D e re k ZH6 小时前
C++ 模板与泛型编程进阶(No 2)
开发语言·c++