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

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;   // 可以工作,但难看

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

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

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

相关推荐
LccKyI20 分钟前
# C# 基础编程 面试题汇总(一)
开发语言·c#
CODER030423 分钟前
win11系统编译安装cuda版llama-cpp-python(踩完所有的坑)
开发语言·python·llama
神仙别闹28 分钟前
基于C语言+SQLite 实现(控制台+图形界面)图书管理系统
c语言·开发语言·sqlite
何以解忧,唯有..40 分钟前
LangChain 模型调用方法总结:invoke、batch、stream 的入参与出参
开发语言·langchain·batch
SamChan901 小时前
Python+ReportLab自动生成PDF翻译质量审计报告:从数据到可视化的完整方案
开发语言·python·ai·pdf·wpf
深念Y1 小时前
Makefile vs go run:Go 项目构建方式对比
java·开发语言·golang·k8s·编译·流水线·cicd
whcyhhh1 小时前
头歌实践教学平台:数据科学与大数据技术导论(十八2)
大数据·开发语言·python
一晌小贪欢1 小时前
Python办公18:PDF 转 Word——利用 OCR 技术批量提取不可编辑的文档内容
开发语言·python·pdf·word·excel·数据可视化·python办公
我命由我123451 小时前
Android Camera - 获取当前设备屏幕的旋转角度、保存帧到外部存储的私有空间
android·java·开发语言·java-ee·android jetpack·android-studio·android runtime