当前位置: 首页 > news >正文

成员访问运算符重载(详解)

目录

成员访问运算符

两层结构下的使用

三层结构下的使用(难点)

内存分析


成员访问运算符

成员访问运算符包括箭头访问运算符 -> 和解引用运算符 * ,它们是指针操作最常用的两个运算符。我们先来看箭头运算符 ->

箭头运算符只能以成员函数的形式重载,其返回值必须是一个指针或者重载了箭头运算符的对象。来看下例子:

两层结构下的使用

例子:建立一个双层的结构,MiddleLayer含有一个Data*型的数据成员

image-20231127173105700

image-20231127173356110

栈帧结束,栈帧结束,ml会调用析构函数,会delete _pdata 空间所以 ml 管理的对象至少应该在堆上的空间

Data*原生指针的用法如下,需要关注堆空间资源的回收

Data * p = new Data();
p->getData();
(*p).getData();
delete p;
p = nullptr;

如果用这种方式创建MiddleLayer对象,我们发现不需要手动delet pdata,并没有发生内存泄露,反而手动delet pdata后会有double free的问题

 

因为MiddleLayer对象实际上对堆上Data对象形成了接管。当函数栈帧结束会自动调用析构函数将堆上的空间回收,所以如果再次手动调用delete pdata 会出现double free的问题。

当然,也可以让Middlelayer对象自己管理一个Data对象,这样就不会出现那种情况了。

image-20240314112042932

image-20240314112053390

需求:希望实现一个这样的效果,创建MiddleLayer对象ml,让ml对象可以使用箭头运算符去调用Data类的成员函数getData

MiddleLayer ml(new Data);
cout << ml->getData() << endl;

箭头运算符无法应对MiddleLayer对象,那么可以定义箭头运算符重载函数。

  • 首先不用考虑重载形式,箭头运算符必须以成员函数形式重载;

  • 然后考虑返回类型,返回值需要使用箭头运算符调用getData函数,而原生的用法只有Data* 才能这么用,所以返回值应该是一个Data* ,此时应该直接返回 _pdata;

  • 同时考虑到一个问题:MiddleLayer的数据成员是一个Data*,创建MiddleLayer对象时初始化这个指针,让其指向了堆上的Data对象,那么还应该补充析构函数使MiddleLayer对象销毁时能够回收这片堆上的资源。

Data* operator->(){return _pdata;
}

思考,解引用运算符应该如何重载能够实现同样的效果呢?直接使用MiddleLayer对象模仿Data*指针去访问getData函数

image-20240314113617206

image-20240314113636052

image-20240314113714656

 

当我们完成了以上的需求后,还有一件“神奇”的事情,使用的语句中有new没有delete,但是检查发现并没有内存泄漏

原因:ml本身是一个局部对象,因为重载了箭头运算符和解引用运算符,所以看起来像个指针,也可以像指针一样进行使用,但是这个对象在栈帧结束时会自动销毁,自动调用析构函数回收了它的数据成员所申请的堆空间

实际上,这就是智能指针的雏形:其思想就是通过对象的生命周期来管理资源。

下面为测试代码,可以自行进行测试

#include<iostream>
using namespace std;
class Data {
public ://构造函数Data(){cout << "Data()" << endl;}Data(int x):_data(x){cout << "Data(int)" << endl;}int get()const { return _data; }~Data() { cout << "~Data()" << endl; }
private:int _data = 10;
};class middlelayer {
public:middlelayer(Data* p):_pdata(p){cout << "middlelayer(Data*)" << endl;}~middlelayer() {if (_pdata) {delete _pdata;_pdata = nullptr;}}Data * operator->(){return _pdata;}//要返回一个data对象,才可以用点来Data& operator* (){return *_pdata;}
private:Data* _pdata;
};void test()
{//下面两行代码会出错,因为栈帧结束,ml会调用析构函数,所以ml管理的对象至少应该在堆上的空间/*Data d1;middlelayer ml( &d1);*///不建议这样使用,//因为如果手动的通过p1指针来回收堆上的Data对象空间//会出现double free的问题Data* p1 = new Data();cout << p1->get() << endl;cout << (*p1).get() << endl;middlelayer ml(p1);//delete p1;
}
void test1()
{Data* p1 = new Data();cout << p1->get() << endl;cout << (*p1).get() << endl;delete p1;//智能指针的雏形//利用局部对象的生命周期管理堆上的资源//ml是middlelayer类对象,不是指针//但是可以像原生的data* 指针一样使用箭头运算符和解引用运算符//访问data的成员//并且,不需要像原生的指针一样去手动调用回收堆上的data空阿金middlelayer ml(new Data());//为什么可以进行重载呢,因为middlelayer底层的原型就是data类型cout << ml->get() << endl;//如下是middlelayer使用箭头运算符访问data类成员函数的本质形式//ml对象调用operator->函数,返回值是一个Data*//编译器自动加上了一个->原生的箭头运算符cout << ml.operator->()->get() << endl;cout << (*ml).get() << endl;cout << ml.operator*().get() << endl;//本质形式
}
int main()
{test1();return 0;
}
三层结构下的使用(难点)
  • 拓展思考:那么如果结构再加一层,引入一个ThirdLayer类

创建ThirdLayer对象时注意避免这样的错误

注意:应该让ThirdLayer底层的指针管理一个堆上的MiddleLayer对象

希望实现如下使用方式,思考一下应该如何对ThirdLayer进行对应的运算符重载

ThirdLayer tl(new MiddleLayer(new Data));
cout << tl->getData() << endl;
cout << (*(*tl)).getData() << endl;

在ThirdLayer类中定义这两个成员函数

—— 在使用时就可以这样用

箭头运算符的使用:

解引用运算符的使用:

  • 拓展思考:如果解引用的使用也希望和箭头运算符一样,一步到位,又该如何实现

ThirdLayer tl(new MiddleLayer(new Data));
cout << (*tl).getData() << endl;

如下三种return的形式都可以实现。

内存分析

三层的结构比较复杂,我们可以通过内存图的方式进行分析。

ThirdLayer对象的创建

ThirdLayer tl(new MiddleLayer(new Data));

实际上的内存结构如图

创建和销毁的过程:

创建tl对象时,调用ThirdLayer的构造函数,在ThirdLayer构造函数的参数初始化过程中调用MiddleLayer的构造函数,在ThirdLayer构造函数的参数初始化过程调用Data的构造。

Data构造完才能完成MiddleLayer的指针数据成员初始化,MiddleLayer创建完毕,才能完成ThirdLayer的指针数据成员初始化。

tl销毁时,马上调用ThirdLayer的析构,执行delete _pml时,第一步调用MiddleLayer的析构,在这个过程中,会delete _pdata,会调用Data的析构函数。

由于构造函数打印信息语句只能在函数体中,所以呈现出如下结果

下面为测试代码,可以自行测试

#include<iostream>
using namespace std;
class Data {
public ://构造函数Data(){cout << "Data()" << endl;}Data(int x):_data(x){cout << "Data(int)" << endl;}int get()const { return _data; }~Data() { cout << "~Data()" << endl; }
private:int _data = 10;
};class middlelayer {
public:middlelayer(Data* p):_pdata(p){cout << "middlelayer(Data*)" << endl;}~middlelayer() {cout << "~middlelayer()" << endl;if (_pdata) {delete _pdata;_pdata = nullptr;}}Data * operator->(){return _pdata;}//要返回一个data对象,才可以用点来Data& operator* (){return *_pdata;}friend class thirdlayer;
private:Data* _pdata;
};class thirdlayer {
public:thirdlayer(middlelayer* ml):_pml(ml){cout << "thirdlayer(middlelayer *)" << endl;}~thirdlayer() {cout << "~thirdlayer()" << endl;if (_pml){delete _pml;_pml = nullptr;}}//返回middlelayer对象,因为middlelayer对象对->进行了重载middlelayer& operator ->(){return *_pml;}//1.一步一步解引用/*middlelayer& operator*(){return *_pml;}*///2.一步到位解引用Data& operator*(){//从thirdlayer出发只能得到middlelayer对象//由于middlelayer对象已经对解引用运算符进行了初始化进行过重载//所以可以直接通过对middlelayer对象解引用得到data对象//return *(*_pml);//return (*_pml).operator*();//从内存的角度//_pml存储的是middlelayer,通过middlelayer访问data的地址,但是要将thirdlayer声明为middlelayer的友元类才能访问私有成员return *((*_pml)._pdata);}
private:middlelayer* _pml;
};void test()
{//下面两行代码会出错,因为栈帧结束,ml会调用析构函数,所以ml管理的对象至少应该在堆上的空间/*Data d1;middlelayer ml( &d1);*///不建议这样使用,//因为如果手动的通过p1指针来回收堆上的Data对象空间//会出现double free的问题Data* p1 = new Data();cout << p1->get() << endl;cout << (*p1).get() << endl;middlelayer ml(p1);//delete p1;
}
void test1()
{Data* p1 = new Data();cout << p1->get() << endl;cout << (*p1).get() << endl;delete p1;//智能指针的雏形//利用局部对象的生命周期管理堆上的资源//ml是middlelayer类对象,不是指针//但是可以像原生的data* 指针一样使用箭头运算符和解引用运算符//访问data的成员//并且,不需要像原生的指针一样去手动调用回收堆上的data空阿金middlelayer ml(new Data());//为什么可以进行重载呢,因为middlelayer底层的原型就是data类型cout << ml->get() << endl;//如下是middlelayer使用箭头运算符访问data类成员函数的本质形式//ml对象调用operator->函数,返回值是一个Data*//编译器自动加上了一个->(原生的箭头运算符)cout << ml.operator->()->get() << endl;cout << (*ml).get() << endl;cout << ml.operator*().get() << endl;//本质形式//是不可以的,和上面原因是一样的,因为tl是指向在栈上的空间,但是函数栈帧销毁的时候会调用delete,会出现错误//thirdlayer tl(&ml);}void test2()
{//调用tl的构造函数在调用middlelayer的构造函数在调用Data的构造函数,当data构造完成,后middlelayer构造完成,tl才能构造完成//就和递归很像thirdlayer tl(new middlelayer(new Data()));//tl调用operator->函数,返回值是其管理的middlelayer对象//由于middlelayer类中已经有对->运算符进行了重载//所以middlelayer对象可以直接使用->运算符访问data类中的成员//本质上就是调用了middlelayer的operator->函数cout << (tl.operator->()).operator->()->get() << endl;cout << (tl.operator->())->get() << endl;cout << tl->get() << endl;//1.一步一步解引用//因为middlelayer类已经对*运算符进行了重载//所以对third对象解引用只需要得到一个middlelayer对象/*cout << ((tl.operator*()).operator*()).get() << endl;cout << (*(*tl)).get() << endl;cout << (*(tl.operator*())).get() << endl;*///2.一步解引用cout << (*tl).get() << endl;cout << (tl.operator*()).get() << endl;
}
int main()
{test2();return 0;
}

相关文章:

  • 【双指针】专题:LeetCode 202题解——快乐数
  • v3 自定义导航头部
  • OpenCV学习之获取图像所有点的坐标位置(二)
  • 从图像“看出动作”
  • Redis集群部署三主三从(docker-compose方式)
  • 2025.04.15【Connection】| 生信数据可视化:连接图绘制指南
  • 跨域(CORS)的几种方式
  • 双重路由引入的环路,选路次优的产生以及解决方法
  • 2025最新版flink2.0.0安装教程(保姆级)
  • 层次式架构核心:中间层的功能、优势与技术选型全解析
  • Oracle中用户密码过期修改为不限制
  • Linux系统-scp命令--两台服务器之间传输文件
  • 利用纯JS开发浏览器小窗口移动广告小功能
  • 通过微信APPID获取小程序名称
  • Spring 框架知识整理
  • K8S_ResourceQuota与LimitRange的作用
  • Materials Studio学习笔记(一)——Materials Studio软件介绍
  • Flutter学习 滚动组件(1):ListView基本使用
  • 【差分隐私相关概念】瑞丽差分隐私(RDP)命题4
  • 宝塔面板中解锁Laravel日志查看的奥秘
  • 白酒瓶“神似”北京第一高楼被判侵权,法院一审判赔45万并停售
  • “爱泼斯坦案”关键证人弗吉尼亚·朱弗雷自杀身亡
  • 体坛联播|皇马上演罢赛闹剧,杨瀚森宣布参加NBA选秀
  • 五矿地产:今年要确保债务“不爆雷”、交付“不烂尾”
  • 冯象|那“交出”后的崩溃,如撒旦坠落诸天
  • 大卫·第艾维瑞谈历史学与社会理论③丨尼古拉斯·卢曼与历史研究