C++运算符的重载

Posted 悲伤土豆拌饭

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++运算符的重载相关的知识,希望对你有一定的参考价值。

运算符的重载

写一个Add函数

我们先讨论下面代码,并复习前面的内容

class Complex

private:
	double Real, Image;
public:
	Complex() :Real(0), Image(0) 
	Complex(double r, double i) :Real(r), Image(i) 
	~Complex() 
	//Complex Add(const Complex* const this,const Complex &c)
	Complex Add(const Complex& x)const
	
		Complex y;
		y.Real = Real + x.Real;
		y.Image = Image + x.Image;
		return y;
		//return Complex(this->Real + x.Real, this->Image + x.Image);
	
	void Print()
	
		cout << Real << "+" << Image << "i" << endl;
	

;
int main()

	Complex c1(12, 23);
	Complex c2(4.5, 5.6);
	Complex c3;
	c3 = c1.Add(c2);
	c3.Print();

	return 0;


直接return可以使用无名函数直接代替将亡值对象,相比可以省一次对象的构建

我们再分析如果我们使用引用返回Add函数

const Complex& Add(const Complex& x)const
	
		Complex y;
		y.Real = Real + x.Real;
		y.Image = Image + x.Image;
		return y;
		//return Complex(this->Real + x.Real, this->Image + x.Image);
	


若我们以引用返回,将亡值对象会创建在Add函数的栈帧中,然后返回将亡值地址,函数return结束该空间会被释放、

若没有引用,构建无名对象也就是将亡值对象会构建在主函数的空间中,这里使用将亡值对象值给到c3是没有问题的

我们查看对象的构造与析构

class Complex

private:
	double Real, Image;
public:
	Complex() :Real(0), Image(0) 
	Complex(double r, double i) :Real(r), Image(i) 
	 
		cout << "Create:" << this << endl; 
	
	Complex(const Complex& x):Real(x.Real),Image(x.Image)
	
		cout << "Copy Create:" << this << endl;
	
	~Complex()
	
		cout << "~Complex:" << this << endl;
	
	//Complex Add(const Complex* const this,const Complex &c)
	Complex Add(const Complex& x)const
	
		return Complex(this->Real + x.Real, this->Image + x.Image);
	
	void Print()
	
		cout << Real << "+" << Image << "i" << endl;
	

;

int main()

	Complex c1(12, 23);
	Complex c2(4.5, 5.6);
	Complex c3;
	c3 = c1.Add(c2);
	c3.Print();

	return 0;

首先我们使用引用返回需要加上const修饰,这是因为我们返回将亡值在临时空间具有常性,所以普通引用是不能进行返回的,需要使用常引用返回

const Complex& Add(const Complex& x)const

	return Complex(this->Real + x.Real, this->Image + x.Image);

我们发现对临时对象的构建后马上就进行析构,那么是怎么将数据拿出给到c3的?这个在最后我们进行分析

为什么不用加号作为函数名

    Complex +(const Complex& x) const
	
		return Complex(this->Real + x.Real, this->Image + x.Image);
	
		
int main()

	Complex c1(12, 23);
	Complex c2(4.5, 5.6);
	Complex c3;
	c3 = c1 + c2;

这里是不可以的,加号是一个操作符,不能使用操作放作为有效的函数名称;但是在C++中为了使这些操作符号能够当作函数名,那么我们需要在前面加上一个关键字operator

//Complex operator+(const Complex* const this,const Complex &c)
Complex operator+(const Complex &c) const

	return Complex(this->Real + x.Real, this->Image + x.Image);

也就是告诉编译器,加号是一个有效的函数名,这就叫做运算符的重载;随后我们之间使用 c3 = c1 + c2 就是可以的

int main()

	Complex c1(12, 23);
	Complex c2(4.5, 5.6);
	Complex c3;
	c3 = c1 + c2;
	//编译器编译会是下面的情况
	//c3 = c1.operator+(c2);
	//c3 = operator+(&c1,c2); 加上this指针

运算符的重载

一个对象,编译器会给它有6个缺省函数

我们再来看下面这个问题

//我们写一个赋值运算符重载
void operator=(const Object& obj)

	this->value = obj.value;

//返回类型为void,这样不可以就不可以连等
//obja = objb = objc;
//obja = objb.operator=(objc);
//obja = operator=(&objb,objc); 返回的无类型,不能给obja赋值

且赋值函数不可以定义为const修饰

Object& operator=(const Object& obj)

	this->value = obj.value;
	return *this;

obja = objb = objc;

//改写
obja = objb.operator=(objc);
obja = operator=(&objb,objc);

obja.operator=(operator=(&objb,objc));
operator=(&obja,operator=(&objb,objc));

通过返回对象,就可以实现连等;并且我们可以通过引用返回,因为此对象的生存期并不受函数的影响,不会产生一个临时对象作为一个过度

防止自赋值

若是我们将obja给obja赋值,也就是自赋值

obja = obja
operator=(&obja,obja);

我们就需要进行一步判断,来防止这种情况

Object& operator=(const Object& obj)

	if(this != &obj)//防止自赋值
	
		this->value = obj.value;
	
	return *this;

探究++运算符的重载

class Int

	int value;
public:
	Int(int x = 0) :value(x)
	
		cout << "Create Int:" << this << endl;
	
	Int(const Int& it) :value(it.value)
	
		cout << "Copy Create Int" << this << endl;
	
	Int& operator=(const Int& it)
	
		if (this != &it)
		
			this->value = it.value;
		
		cout << this << " = " << &it << endl;
		return *this;
	
	~Int()
	
		cout << "Destroy Int:" << this << endl;
	
	
	//前置++
	Int& operator++()
	
		this->value++;
		return *this;
	
	//后置++
	Int operator++(int)
	
		Int old = *this;
		++* this;
		return old;
	
	//对象+对象
	Int operator+(const Int tmp)const
	
		return Int(this->value + tmp.value);
	
	//对象+整型
	Int operator+(const int tmp)const
	
		return Int(this->value + tmp);
	
;
Int operator+(const int val, const Int& it)

	return it + val;//调用成员函数 对象+整型

int main()

	Int a(10), b(0), c(0);
	
	b = ++a;
	//b = a.operator++();
	//b = operator++(&a);
	c = a++;
	//c = a.operator++(0);
	//c = operator++(&a,0);

	c = a + b;//对象与对象相加
	c = a + 10;//对象加整型
	c = 10 + a;//整型加对象

	return 0;

我们无法将c = 10 + a;写入成员对象的方法中,编译器会提示参数过多,但是可以做成全局函数

我们接着设计了下面的这些运算符重载,这些代码可以通过一个函数统统解决,我们后面时候再说

	bool operator==(const Int& it)const
	
		return this->value == it.value;
	
	bool operator!=(const Int& it)const
	
		return !(*this == it);
	
	bool operator<(const Int& it)const
	
		return this->value < it.value;
	
	bool operator>=(const Int& it)const
	
		return !(*this < it);
	
	bool operator>(const Int& it)const
	
		return this->value > it.value;
	
	bool operator<=(const Int& it)const
	
		return !(*this > it);
	

我们写一个main函数来探究运算符重载后的优先级

int main()

	int i = 0;
	i = i++ + 1;
	cout << i <<endl;
	
	Int j = 0;
	j = j++ + 1;
	j.Print();

  • 对于内置类型得出2,而自定义类型得出的1;这就是我们一旦重载运算符,它将不再是运算符而是一个函数,会以一个函数的方式进行运算
  • 当我们重载了前置++和后置++
	Int i = 0;
	Int n = 10;
	for (; i < n; ++i)
	
		i.Print();
	

在使用前置++的时候,看到下面的结果

仅仅创造两个对象,析构两个对象,如果是后置++

	Int i = 0;
	Int n = 5;
	for (; i < n; i++)
	
		i.Print();
	


在此处效率就会非常低,我们的后置++;在++过程创建一个对象Int old = *this;在返回过程又创建一个对象return old;,自家结束这些对象会被析构,再进行自加又会进行对象的创建,所以我们可以明确看到前置++和后置++有很大的不同

上面问题解决

我们通过这段代码来看,与上面问题相同

	Object& operator=(const Object& obj)
	
		if (this != &obj)
		
			this->value = obj.value;
		
		return *this;
	

;
Object& fun(const Object& obj)

	int val = obj.Value() + 10;
	Object obja(val);
	return obja;


int main()

	Object objx(0);
	Object objy(0);
	objy = fun(objx);
	cout << objy.Value() << endl;
	return 0;

我们在这里希望通过引用返回,这里return的临时对象会构建在fun函数的栈帧中,并且在函数结束栈帧释放,随后调用赋值运算符重载,但是数值依旧是正确的

我们跟踪这个被析构对象的地址,首先我们定位在fun函数的return obja;,随后进入析构函数将我们的obja进行析构

接着运行到回到主函数进行赋值,接着进入赋值运算符重载,可以看到,这里的obj地址与已被析构的obja地址相同


可以看到这个值依旧存在,依旧可以打印给出,这是因为vs2019的特殊性质造成的;我们每次运行程序会发现每次的对象地址都在变化,逻辑地址会随机改变,被析构对象的栈帧不会被接下来的赋值运算符重载扰乱地址空间,所以即使我们引用返回的对象已经死亡依旧可以将数值正确返回

但是在vc中,我们得到的值会是随机值,这是因为vc中每次运行程序地址都不会改变,当我们从fun函数退出进入赋值语句中,就会将原本存储数据的地址扰乱,继而变成了随机值

尽管我们引用返回能够将数据正确打印,但是该对象已经死亡,这是不安全的,所以我们一定不要以引用返回对象


VS2019具有一个特点:当我们调用函数,若函数中没有定义局部变量或局部对象时,该函数基本不对栈帧进行清扫

总结

  1. 运算符重载的函数的函数名必须为关键字operator加上一个合法的运算符,在调用该函数时,将右操作数作为函数的实参
  2. 当用类的成员函数实现运算符的重载时,运算符重载函数的参数(当为双目运算符时)为一个或(当为单目运算符时)没有,运算符的左操作数一定是对象,因为重载的运算符是该对象的成员函数,而右操作数是该函数的参数
  3. 单目运算符“++”和“–”存在前置与后置问题
    前置“++”格式为:
    返回类型 类名::operator++()
    后置“++”格式为:
    返回类型 类名::operator++(int)
    后置“++”中的参数int仅用作区分,并无实际意义,可以给一个变量名,也可以不给变量名
  4. C++中只有极少数的运算符不允许重载

    重载运算符有以下限制:
  • 不可捏造新的运算符
  • 不能改变运算符操作数的个数
  • 运算符重载不宜使用过多
  • 重载运算符含义必须清晰,不能有二义性
  • 一旦将运算符重载过后,它将不再是运算符而是一个函数,它的调用规则、结合性、优先级法则是按照函数的法则进行

以上是关于C++运算符的重载的主要内容,如果未能解决你的问题,请参考以下文章

[ C++ ] C++类与对象(中) 类中6个默认成员函数 -- 运算符重载

C++类和对象(构造函数析构函数拷贝构造函数赋值运算符重载Const成员)详细解读

C++类和对象(构造函数析构函数拷贝构造函数赋值运算符重载Const成员)详细解读

C++类和对象(构造函数析构函数拷贝构造函数赋值运算符重载Const成员)详细解读

C++类与对象(详解构造函数,析构函数,拷贝构造函数,赋值重载函数)

C++初阶:类和对象(中篇)构造函数 | 析构函数 | 拷贝构造函数 | 赋值运算符重载