既然有指针了,为什么 C++ 还搞个引用出来?

本文介绍了C++中的对象、指针和引用的概念及其区别。对象存储数据,指针保存地址,而引用是变量的别名,不占用额外内存。引用在定义时必须初始化,且不可变,增强了安全性,避免了空指针、野指针和意外修改。引用在函数参数传递和返回值中使用,提供了效率和清晰度,同时常引用可以防止数据在函数中被修改。引用在处理临时对象和运算符重载时有独特优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 对象的定义:

对象是指一块能存储数据并具有某种类型的内存空间;

一个对象a,它有值和地址&a,运行程序时,计算机会为该对象分配存储空间,来存储该对象的值,我们通过该对象的地址,来访问存储空间中的值;

2. 指针的定义

指针:它保存一个值(或 null)的地址,可以检索指向该地址处的值 。

指针定义时,可以不用初始化;

指针可以初始化为NULL,

指针的在初始化后, 其地址值仍可以改变, 用于存储另外一个地址;

指针p也是对象,它同样有地址&p和存储的值p,只不过,p存储的数据类型是数据的地址。

如果我们要以p中存储的数据为地址,来访问对象的值,则要在p前加解引用操作符"",即p。

3. 指针的常量和变量之分

对象有常量(const)变量之分,

既然指针本身是对象,那么指针所存储的地址也有常量和变量之分;

指针常量是指,指针这个对象所存储的地址是不可以改变的,

而指向常量的指针的意思是,不能通过该指针来改变这个指针所指向的对象。

4.引用

引用用于在程序的不同的部分使用两个以上的变量名指向同一块地址,

使得对其中任何一个变量的操作实际上都是对同一地址单元进行的。

引用的特点:

  1. 引用仅是变量的别名,而不是实实在在地定义了一个变量,因此引用本身并不占用内存,引用,表明上是为变量创建了第二个名字, 它的本质是一个指针,和目标变量的是同一个内存地址声明引用时,目标的存储状态不会改变;

  2. 引用在定义时,必须初始化, 且不能初始化为空;只有在引用申明时, “&” 才称作引用符,且放在类型名后面;其他时候出现的 “&”, 都是指 取地址操作符;。

  3. 引用只能在初始化的时候引用一次 ,不能更改为转而引用其他变量。

  4. 对引用进行操作,实际上就是对被引用的变量进行操作;

5. C++ 中为什么要有 “引用”

C++ 中引用的出现,最初是为了解决, 运算符重载的 问题;

假设没有引用,那么,用指针来operator overloading操作。 
 A operator +(const A *a, const A *_a);
那么使用的时候,&a + &b, 这样看起来不是那样简单明了。
引用解决C++运算符重载后代码美观的问题。

并且引用的 出现带来了 一个指针无法替代的特性: 引用临时对象;

引用在定义的时候必须就赋值,并且此后再也无法更改;

5.1 引用的特性

并且在实践中,使用指针,经常出现一下情况:

  1. 操作空指针
  2. 操作野指针
  3. 不知不觉中, 手动改变了指针的值,随后以为该指针正常,关键本人不知道;

为了设立一种机制, 保证上述三种情况的不发生, “ 引用”这个机制保证避免发生上述三种情况

引用特性:

  1. 引用不允许为空 (从而避免了操作空指针)
  2. 引用在定义的时候,就必须初始化(避免了操作 野指针)
  3. 一个引用 ,始终从一而终的指向他 初始化的那个对象 ( 避免了指针的值不会被修改)

5.2 引用作为函数参数

C++ 支持把引用作为参数传给函数, 这比传一般的参数更安全

(1)传递引用给函数与传递指针的效果是一样的。这时,被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就是对其相应的目标对象(在主调函数中)的操作。

(2)使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作:

  1. 而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本;
  2. 如果传递的是对象,还将调用拷贝构造函数。
  3. 因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好。

(3)使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用"*指针变量名"的形式进行运算,这很容易产生错误且程序的阅读性较差;

另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰。

如果既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,就应使用常引用。

5.3 引用作为函数的返回值

(1)以引用返回函数值,定义函数时需要在函数名前加&

(2)用引用返回一个函数值的最大好处是,在内存中不产生被返回值的副本。

引用作为返回值,必须遵守以下规则:

1.不能返回局部变量的引用。

主要原因是局部变量会在函数返回后被销毁,因此被返回的引用就成为了"无所指"的引用,程序会进入未知状态。

2.不能返回函数内部new分配的内存的引用。

例如,被函数返回的引用只是作为一个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memory leak。

3.可以返回类成员的引用,但最好是const。

这条原则可以参照Effective C++[1]的Item 30。主要原因是当对象的属性是与某种业务规则(business rule)相关联的时候,其赋值常常与某些其它属性或者对象的状态有关,因此有必要将赋值操作封装在一个业务规则当中。如果其它对象可以获得该属性的非常量引用(或指针),那么对该属性的单纯赋值就会破坏业务规则的完整性。

4.引用与一些操作符的重载

流操作符<<和>>,这两个操作符常常希望被连续使用,例如:cout << “hello” << endl; 因此这两个操作符的返回值应该是一个仍然支持这两个操作符的流引用。

<think>好的,用户问为什么shared_ptr需要保存资源指针。首先,我得回忆一下shared_ptr的基本结构。shared_ptr是C++中的智能指针,用于自动管理动态分配的内存,防止内存泄漏。它通过引用计数来实现多个指针共享同一资源的所有权。 用户可能已经知道shared_ptr的基本用法,但可能对内部机制不太清楚。他们的问题核心在于为什么需要直接保存资源指针,而不是仅仅通过控制块来管理。可能用户认为控制块已经保存了资源指针,所以shared_ptr对象本身保存指针似乎是多余的。 首先,我需要确认shared_ptr的结构。每个shared_ptr包含两个指针:一个指向所管理的对象(资源指针),另一个指向控制块。控制块包含引用计数、弱引用计数以及删除器等。控制块里的确存储了资源指针,但shared_ptr本身也保存了这个指针。 接下来,思考用户可能的疑惑点:既然控制块已经有了资源指针为什么shared_ptr对象还要存一份?这可能导致用户觉得是重复存储,浪费空间。需要解释这两个指针的作用以及为什么都需要存在。 可能的解释方向包括: 1. **访问效率**:如果每次访问资源都要通过控制块,会增加间接访问的开销,而直接存储资源指针可以提高访问速度。 2. **线程安全**:引用计数的增减需要原子操作,但资源指针本身不需要同步,分开存储可以优化性能。 3. **解耦设计**:控制块和资源指针的分离使得shared_ptr可以更灵活地处理不同的删除器和分配器,同时保持资源访问的高效。 4. **生命周期管理**:当引用计数降为零时,控制块负责释放资源,但shared_ptr对象在生存期内需要快速访问资源,因此直接存储指针是必要的。 5. **历史兼容性**:可能考虑到与旧代码或原始指针的兼容性,直接存储指针便于与现有API交互。 同时,用户可能没有考虑到当使用别名构造函数(aliasing constructor)时,shared_ptr可能指向一个对象的子对象,而控制块中的指针指向父对象。这种情况下,shared_ptr本身的指针和控制块的指针不同,但共享同一个控制块。这说明资源指针的存储是灵活的,允许这种别名用法。 还需要考虑用户可能的误解,例如认为控制块中的指针和shared_ptr中的指针总是一致的,但实际上在别名构造的情况下它们是不同的。因此,shared_ptr必须保存自己的资源指针,以正确指向实际管理的对象,而控制块中的指针可能用于正确释放内存。 此外,用户可能对移动语义或拷贝shared_ptr时的行为有疑问,比如为什么拷贝时引用计数增加,但移动时不会。不过这可能超出了当前问题的范围,但需要明确shared_ptr保存资源指针与控制块的关系。 最后,总结时需要强调,尽管控制块保存了原始指针用于释放资源,但shared_ptr自身保存指针是为了高效访问,同时支持灵活的用法如别名构造,这些都需要直接存储资源指针。</think>在C++的智能指针 `shared_ptr` 中,**资源指针的保存**是设计中的关键环节,其必要性主要体现在以下几个方面: --- ### 1. **访问效率的直接性** - **直接访问对象**: `shared_ptr` 内部直接存储资源指针(`T*`),使得通过 `operator->` 或 `operator*` 访问对象时无需通过控制块间接寻址。例如: ```cpp std::shared_ptr<MyClass> ptr = ...; ptr->doSomething(); // 直接通过 ptr 内部的指针调用,无需查询控制块 ``` 如果每次访问都需通过控制块获取指针,会引入额外开销(尤其是高频操作时)。 --- ### 2. **控制块与资源指针的解耦** - **控制块的职责**: 控制块(包含引用计数、删除器等)和资源指针是**逻辑分离**的: - 控制块负责管理资源的生命周期(如引用计数增减、资源释放)。 - `shared_ptr` 自身存储的指针负责快速访问资源。 - **允许别名构造(Aliasing)**: `shared_ptr` 支持一种特殊用法:某个 `shared_ptr` 可以指向资源的子对象,但通过控制块共享父对象的所有权。例如: ```cpp struct Parent { int child; }; auto parent_ptr = std::make_shared<Parent>(); // 创建指向 parent_ptr 的子成员 child 的 shared_ptr,但所有权仍属于 parent_ptr std::shared_ptr<int> child_ptr(parent_ptr, &parent_ptr->child); ``` 此时 `child_ptr` 内部存储的指针是 `&parent_ptr->child`,但控制块中的指针指向 `Parent` 对象。 **若 `shared_ptr` 不保存资源指针,这种灵活用法无法实现**。 --- ### 3. **与原始指针的兼容性** - **无缝对接传统代码**: `shared_ptr` 需要与接受原始指针的 API 兼容。例如: ```cpp void legacy_api(MyClass* raw_ptr); std::shared_ptr<MyClass> ptr = ...; legacy_api(ptr.get()); // 直接通过内部存储的指针获取原始指针 ``` 如果资源指针仅存在控制块中,`ptr.get()` 需额外查询控制块,降低效率。 --- ### 4. **性能优化** - **减少间接访问**: 控制块通常通过动态内存分配存储,而 `shared_ptr` 对象本身可能位于栈上。直接存储资源指针避免了每次访问对象时访问堆内存中的控制块,这对缓存局部性(Cache Locality)更友好。 --- ### 5. **资源释放的精确性** - **控制块中的指针用于释放资源**: 当引用计数归零时,控制块需要通过其内部保存的原始指针调用删除器释放资源。但此指针可能与 `shared_ptr` 自身存储的指针不同(例如别名构造时)。 **示例**: ```cpp // 假设控制块保存父对象指针,shared_ptr 保存子对象指针 parent_ptr.reset(); // 父对象引用计数归零,触发释放 // child_ptr 仍指向已释放内存的子对象(但此时已悬空,需谨慎使用!) ``` 这种设计允许资源释放的精确性,同时 `shared_ptr` 仍可指向子对象。 --- ### 总结:为什么必须保存资源指针? | 设计目标 | 实现方式 | |-------------------------|--------------------------------------------------------------------------| | **高效访问对象** | 直接存储指针,避免间接查询控制块 | | **支持别名构造等灵活用法** | 允许 `shared_ptr` 指向子对象,同时通过控制块管理父对象所有权 | | **兼容传统代码** | 快速获取原始指针(`ptr.get()`) | | **性能优化** | 减少堆内存访问,提升缓存效率 | | **精确资源释放** | 控制块保存释放所需的指针,`shared_ptr` 保存实际访问的指针(可能不同) | --- ### 额外思考:为什么不直接通过控制块访问? 如果强制所有访问都通过控制块,会导致: 1. **性能下降**:每次 `ptr->method()` 需多一次指针引用。 2. **无法支持别名构造**:子对象指针无法与父对象所有权绑定。 3. **代码复杂性增加**:控制块需额外记录多种指针类型。 因此,`shared_ptr` 存储资源指针是效率与功能平衡后的最优设计。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Unknown To Known

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值