C++中的运算符重载详解
作者:看书就头疼 发布时间:2022-03-22 07:20:13
1、引例
class Complex
{
private:
double Real,Image;
public:
Complex():Real(0),Image(0) {}
Complex(double r, double i) : Real(r),Image(i) {}
~Complex() {}
};
int main()
{
Complex c1(1.2,2.3);
Complex c2(45,56);
Complex c3;
c3 = c1.Add(c2);
}
类非常简单,下面我们要讨论如何写一个Add函数,使得两个对象的属性相加,返回一个新的对象。
第一种:
Complex::Complex Add(const Complex &c)
{
Complex co;
co.Real = this->Real + c.Real;
co.Image = this->Image + c.Image;
return co;
}
问题1:如何写出最节省空间的Add函数?
第二种:
Complex::Complex Add(const Complex &c) const
{
return Complex(c.Real + this->Real, c.Image + this.Image);
}
由于不需要改变调用对象的属性值,我们给this指针添加const 修饰。
分析过程如下:
问题2:为什么第一种方式不节省空间呢?
首先第一种的代码比较繁琐,并且在函数栈帧中又创建了一个对象(第3行)。并且函数类型是值传递类型(第6行),返回的是一个将亡值对象。那么整个Add函数空间会产生两个对象,造成空间的浪费。
第二中代码创建的是无名对象,减少了一个co对象的创建,并且将无名对象直接作为将亡值对象传递给main函数中的c3。
问题3:我们能否将Add函数改为引用类型,这样来减少将亡值对象的创建
Complex::Complex &Add(const Complex &c) const
{
return Complex(c.Real + this->Real, c.Image + this.Image);
}
VS2019发现报错,不能返回引用对象:
我们进行分析:
问题4:我们能否将这个Add函数名改为 + 运算符?
//Complex::Complex Add(const Complex &c) const
Complex::Complex +(const Complex &c) const
{
Complex co;
co.Real = this->Real + c.Real;
co.Image = this->Image + c.Image;
return co;
}
int main()
{
...
//c3 = c1.Add(c2);
c3 = c1.+(c2); //将原先Add的地方改变为加号。
...
}
这样使用,编译器又会报错,操作符不可作为一个有效的函数名来被使用。
问题5:如何使 +预算符 作为函数名使用?
这就引出了今天的关键,函数运算符重载。
在C++中,为了使操作符作为一个有效的函数名,我们在操作符前面添加一个operator。
Complex operator+(const Complex &c) const
{
return Complex(c.Real + this->Real,c.Image + this->Image);
}
int main()
{
Complex c1(1.2,2.3);
Complex c2(10,10);
Complex c3;
c3 = c1 + c2;
//上面一行实际上是
//c3 = c1.operator+ (c2);
//c3 = operator+(&c1,c2); //编译器还会经过一次编译
}
前面几篇博客已经分析了第15行的由来,是将c1的地址传递给this指针,将c2作为形参c的别名传递给函数。
2、类中自动建立的函数
在C++中,如果我们定义一个类,它会给我们自动创建六个缺省函数:
构造函数析构函数拷贝构造函数赋值函数普通对象的&(取地址符)的重载常对象的&(取地址符)重载
代码示例如下:
class Object
{
public:
Object() {}//构造函数
~Object() {}//析构函数
Object(const Object &obj) {}//拷贝构造函数
Object &operator=() {const Object &obj} //赋值函数
{
return *this;
}
Object *operator&()//普通对象的&(取地址符)的重载
{
return this;
}
const Object *operator&() const//常对象的&(取地址符)重载
{
return this;
}
};
然后,在C11标准下,又增添了两个缺省函数,这里不做深究:
移动构造函数移动赋值函数
3、重载赋值运算符解析
回到最初的例子:
class Object
{
int value;
public:
Object () {
cout << "create:" << this << endl;
} //普通构造函数
Object (int x = 0):value(x) {cout << "create:" << this << endl;} //缺省构造函数
~Object() //析构函数
{
cout << "~Objcet() " << this << endl;
}
Object(Object &obj):value(obj.value)
{
cout << "Copy create:" << this << endl;
}
int & Value()
{
return value;
}
const int &Value() const
{
return value;
}
Object &operator=(const Object& obj) //此处加引用
{
this->value = obj.value;
return *this; //this指针指向objb的地址。赋值函数结束后,objb不会被消亡,所以可以以引用返回
}
void operator=(const Object& obj) //赋值语句不可给this指针加const
{
this->value = obj.value;
}
};
int main()
{
Object objx(0);
Object objy(0);
objy = fun(objx);
cout << objy.Value() << endl;
return 0;
}
我们在34行添加一个等号运算符重载函数: void operator=(const Object& obj)
此处不可添加const修饰this指针,因为需要使用this指针作为左值被修改。
问题6:void operator=(const Object& obj) 只能用于 obja = objb,为什么不可以这样使用 obja = objb = objc;
我们逐一分析:
obja = objb = objc;
//当调用等号运算符函数的时候。
obja = objb.operator = (objc);
obja = operator = (&objb,objc);
//如果此处是调用的是 void operator=(const Object& obj) ;
//等号从右向左指向,我们不能把一个void 类型赋给一个obja对象类型。
我们将赋值运算符进行再次重载,丢弃 void 版本:
Object &operator=(const Object& obj) //此处加引用
{
this->value = obj.value;
return *this; //this指针指向objb的地址。赋值函数结束后,objb不会被消亡,所以可以以引用返回
}
这样就可以使用了。
我们接着上次的深入分析:
obja.operator=(operator=(&objb,objc));
operator=(&obja,operator=(&objb,objc));
问题7:如果遇到obja = obja这种情况,如何赋值呢?
回答:对this指针和形参引用进行判断。
Object &operator=(const Object &obj)
{
if(this != &obj)
{
this->value = obj.value
}
}
问题8:为什么函数是在栈区构建的,以引用返回打印的不是一个随机值?
运行程序,VS2012中,打印的是一个随机值。
VS2019打印的是一个正常值。
c));
> 问题7:如果遇到obja = obja这种情况,如何赋值呢?
>
> 回答:对this指针和形参引用进行判断。
```cpp
Object &operator=(const Object &obj)
{
if(this != &obj)
{
this->value = obj.value
}
}
问题8:为什么函数是在栈区构建的,以引用返回打印的不是一个随机值?
运行程序,VS2012中,打印的是一个随机值。
VS2019打印的是一个正常值。
在WIN10系统中,VS2019与操作系统完全结合,安全性更高。当程序多次运行的时候,它的逻辑地址都不一样,这样做的好处是:当病毒入侵时,由于程序的逻辑地址是变化的,病毒不好寻找入侵的入口。
来源:https://blog.csdn.net/weixin_46401837/article/details/122555466
猜你喜欢
- 在Unity3d中开发虚拟摇杆方式有比较多,可以使用EasyTouch、FairyGUI等插件来开发。本文给大家介绍使用Unity3d的原生
- 本文实例讲述了Java中的异常和处理机制。分享给大家供大家参考,具体如下:简介程序运行时,发生的不被期望的事件,它阻止了程序按照程序员的预期
- 前言对于 InterruptedException,一种常见的处理方式是 “生吞(swallow)” 它 —— 捕捉它,然后什么也不做(或者
- 一、maven引入依赖,数据库驱动根据项目需求自行引入<!-- https://mvnrepository.com/artifact/
- 当把窗体的FormBorderStyle属性设为None后会导致边框没了,结果窗体无法用鼠标拖动、最大、最下化和关闭……下面解决窗体可拖动问
- DSL的作用是解决领域专家与软件开发人员之间的沟通问题。听起来很唬人,其实不是什么高深的东西,我们可以使用Fluent API 创建自己的D
- 前一篇文章《C#影院售票系统毕业设计(2)》中总结了动态绘制控件、票类型的切换以及数据在窗体中的展现。今天继续总结!本文总结项目中最核心的部
- 本文实例讲述了C#读取csv格式文件的方法。分享给大家供大家参考。具体实现方法如下:一、CSV文件规则 1 开头是不留空,以行为单
- EntityWrapper的常用方法#WHERE (issue_type = ?) AND (status = ? OR status =
- 过滤器实现过滤器需要实现 javax.servlet.Filter 接口。重写三个方法。其中 init() 方法在服务启动时执行,destr
- 前言本文将模块化地介绍如何实现一个动态开辟空间的通讯录,其有以下九个功能:打印主菜单添加联系人删除联系人打印通讯录查找联系人修改联系人置顶联
- 在C#中,数组由于是固定长度的,所以常常不能满足我们开发的需求。由于这种限制不方便,所以出现了ArrayList。ArrayList、Lis
- mybatis-plus Condition拼接Sql语句各方法1.setSqlSelect—用于添加查询的列信息public Wrappe
- 在上一篇文章中,我为大家介绍了《5种创建文件并写入文件数据的方法》,本节我们为大家来介绍6种从文件中读取数据的方法.另外为了方便大家理解,我
- 这个很基础的知识,但我至今才意识到它。想想也很失败。直接上代码:很简单public class Base
- 本文实例为大家分享了Java Socket编程实现多人交互聊天室的具体代码,供大家参考,具体内容如下本项目由三个.java文件(
- 1、spring aop实现首先application-test.yml增加如下数据源的配置spring: datasource
- 最近有学生做毕业设计,想使用悬浮窗这种效果,其实很简单,我们可以通过系统服务WindowManager来实现此功能,本章我们来试验一下在当前
- 本文实例分析了c#中Empty()和DefalutIfEmpty()用法。分享给大家供大家参考。具体分析如下:在项目中,当我们想获取IEnu
- 本文实例讲述了C#实现绑定Combobox的方法。分享给大家供大家参考。具体实现方法如下:public class StaticVariab