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

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

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

相关推荐
君顾13 小时前
上海24小时自助健身房系统开发实战指南:从架构设计到落地部署
java·开发语言·健身房
香菜TTT3 小时前
大模型上下文协议(MCP):AI 应用的“USB-C”接口技术
开发语言·人工智能·经验分享
aramae4 小时前
模拟实现strlen()函数 (C语言)
c语言·开发语言·后端
jimy15 小时前
readelf 查看“派生类”的vtable符号
开发语言·c++
ctlover5 小时前
排序与查找算法详解
开发语言·python
YYYing.5 小时前
【C++进阶系列 (七)】C++ RTTI 深度剖析:从 typeid 到 dynamic_cast 的底层之旅
开发语言·c++·c/c++·rtti
nvvas7 小时前
Java 入门(四):数组——数据的集合管理
java·开发语言
Y3815326627 小时前
SERP 数据清洗实战:字段标准化、日期解析与去重键
开发语言·数据库·python·python数据库
君顾18 小时前
本地城市低空经济管理系统定制:城市级低空运营平台架构与落地实战
java·开发语言·飞手
多多鼠8 小时前
System Prompt 的“版本漂移”问题:从变更管理到 A/B 测试体系
开发语言·网络·人工智能·python·langchain