C++重要知识清单:完全搞懂运算符重载

1.什么是运算符重载

在实际开发中,我们经常会自己定义数据类型,也经常需要对这些自定义数据类型进行+-*/等常用的计算。这时C++自带的这些操作符却并没有处理我们自定义数据类型的能力,所以就需要我们对这些操作符的功能进行扩展。而实现这一功能的方式就叫做操作符重载。

其实重载这个概念大家一定并不陌生,所谓重载,就是重新赋予新的含义。函数重载就是对一个已有的函数赋予新的含义,使之实现新功能。运算符重载同理,运算符重载使得用户自定义的数据以一种更简介的方式工作。

2.运算符重载的两种方法

运算符重载可以使用全局函数和成员函数或友元函数两种方式实现。那么为什会有两种实现方式呢?问题的本质到底是什么?

把成员函数 转成 全局函数 会多一个参数;全局函数 转换成 成员函数会少一个参数(面向对象思想与面向过程思想)

方法一:用成员函数重载运算符

  • 运算符函数可以以类成员函数或友元函数的形式进行重载(注意:成员函数具有this指针,友元函数没有this指针
  • 运算符使用方式与重载方式无关。但传递参数的方式不同,所以应用场合也不同。

方法二:用全局函数或友元函数重载运算符

  • 要记得全局函数无法访问类的私有成员!!

3.具体实现

3.1二元操作符重载(+ - * /):

运算符op使用情景:

       左对象 op 右对象;

重载为成员函数:

       左对象.operator op(右对象){}

       左操作数由左对象通过this指针隐式的传递了,右操作数对象由参数显式传递。

重载为友元函数:

       operator op(左操作数,右操作数){}

       左右操作数都由参数传递。

例子:

使用友元全局函数实现“加号”重载,使用成员函数实现“减号”重载

class Complex 
{
private:
	int a;
	int b;
public:
	friend Complex operator+(Complex & c1, Complex &c2);
	Complex(int a = 0, int b = 0) {
		this->a = a;
		this->b = b;
	}
	//成员函数重载-操作符
	Complex operator-(const Complex& right) {
		Complex result;
		result.a = this->a - right.a;
		result.b = this->b - right.b;
		return result;
	}

};

//全局函数重载+操作符
Complex operator+(Complex & c1,Complex &c2) {
	Complex result;
	result.a = c1.a + c2.a;
	result.b = c1.b + c2.b;
	return result;
}

void main() {
	int a = 0, b = 0;
	int c;
	//基础数据类型
	c = a + b;
	//a+bi 符数运算规则
	Complex c1(1, 2), c2(3, 4);
	Complex c3;
	c3 = c1 + c2;
	c3 = c1 - c2;
}

3.2一元操作符重载(++  --):

运算符op使用情景:

       对象 op ; 或    op 对象;

重载为成员函数:

       对象.operator op(){}

       操作数由对象通过this指针隐式的传递。

重载为友元函数:

       operator op(对象){}

       操作数都由参数传递。

前置操作符重载(++i --i)

class Complex 
{
private:
	int a;
	int b;
public:
	friend Complex & operator++(Complex& c);
	Complex(int a = 0, int b = 0) {
		this->a = a;
		this->b = b;
	}
	//成员函数重载前置--操作符  --a
	Complex& operator--() {
		this->a--;
		this->b--;
		return *this;
	}
};

//全局函数重载前置++操作符   ++a
Complex & operator++(Complex& c) {
	c.a++;
	c.b++;
	return c;
}

后置操作符重载(i++ i--)

上面的前置操作符重载很好理解了,那我们现在写下一后置操作符重载。这时我们会发现一个严重的问题:后置操作符重载的函数怎么定义呀?函数名是相同的,参数也相同,这不符合重载的条件呀!!为了解决这个问题,C++提供了占位符。

Complex operator++(Complex & c, int){}

这样就满足重载的条件了,从而可以很好的区分前置操作符重载和后置操作符重载。


class Complex 
{
private:
	int a;
	int b;
public:
	friend	Complex operator++(Complex& c, int);
	Complex(int a = 0, int b = 0) {
		this->a = a;
		this->b = b;
	}
	//成员函数重载后置--操作符  a--
	Complex operator--(int) {
		Complex temp=*this;
        this->a--;
		this->b--;
		return temp;
	}
};

//全局函数重载后置++操作符   a++
Complex operator++(Complex& c,int) {
	Complex temp = c;
	c.a++;
	c.b++;
	return temp;
}

3.3左移右移操作符重载(<<  >>):

语法:

ostream&  operator<<(ostream& out,自定义类型& obj);

istream& operator>>(ostream& out,自定义类型& obj);

全局函数方式重载:

class Complex 
{
private:
	int a;
	int b;
public:
	friend ostream & operator<<(ostream & out, Complex & obj);
	Complex(int a = 0, int b = 0) {
		this->a = a;
		this->b = b;
	}
};

ostream & operator<<(ostream & out, Complex & obj) {
	out << obj.a << " " << obj.b << endl;
	return out;
}

成员函数方式重载:

通过前面的一些案例我们知道,成员函数方式重载需要写在对应类的内部。对于目前这个案例,相当于我们需要重载 cout.operator<<()这个函数,那么就需要写在ostream这个类中。那么问题来了,ostream和istream这个类的源码我们上那找去??拿不到啊,没法在类里重载啊!!

结论:输入输出操作符的重载只能使用友元函数进行重载,无法用成员函数的方式进行重载。

3.4等号操作符重载(=):

想完全搞懂等号操作符的重载需要先搞懂深拷贝和浅拷贝问题。没有基础的朋友可以看我的这篇文章:

https://blog.csdn.net/weixin_39568744/article/details/104884211

C++编译器提供的等号操作符也属于浅拷贝,比如obj1=obj2,如果对象中有指针变量时,默认等号操作只会把指针的值直接赋值,而不会开辟新的内存空间。

所以我们要进行等号操作符的重载来解决这个问题。首先写出重载的函数原型:

使用成员函数重载的方式: void operator=(类型 &obj);

在具体函数实现的过程中,有一点至关重要,就是一定要记得把旧的指针指向的内存空间释放掉!!

例:

void Array::operator=(Array & other) {
	//释放旧指针所指空间
	if (this->m_space != NULL) {
		delete[] m_space;
		this->m_length = 0;
	}
	//开辟新空间
	this->length = other.length;
	this->m_space = new int[this->length];
	//赋值
	for (int i = 0; i < this->length; i++) {
		this->m_space[i] = other.m_space[i];
	}
	return;
}

经过上面的例子就可以实现 obj2=obj1 的深拷贝正确赋值。但如果我想实现 obj3=obj2=obj1  这种连等操作怎么呢??我们知道等号操作符是从右往左执行的,按照上面的重载方式 ,右面obj2=obj1会先执行重载函数,然后返回void,于是就变成了obj3=void,所以就出错了。解决办法很简单,既然是链式编程,那么我们就返回引用就可以了

修改后的代码如下:

Array& Array::operator=(Array & other) {
	//释放旧指针所指空间
	if (this->m_space != NULL) {
		delete[] m_space;
		this->m_length = 0;
	}
	//开辟新空间
	this->length = other.length;
	this->m_space = new int[this->length];
	//赋值
	for (int i = 0; i < this->length; i++) {
		this->m_space[i] = other.m_space[i];
	}
	return *this;
}

3.5中括号操作符重载([ ]):

假设我们有一个自己定义的数组Array,我们需要进行[]操作符重载以实现  Array[i]数组元素快速访问  和 Array[i]=9  快速修改等操作。要怎么做呢?首先根据我们的业务需求写出函数原型:

int& Array::operator[](int i);

我们以成员函数的方式进行重载,所以对象直接通过this指针隐式的传递了,参数只需要有一个int,因为我们业务中的索引是通过int类型;返回值设计为int &是因为我们的业务需求中需要有Array[i]=0;这种赋值功能,这个语句的本质就是操作符重载这个函数做左值,当函数返回值做左值时需要返回引用类型,所以我们返回值设计为了int&.

class Array
{
public:
	Array(int length);
	Array(const Array& obj);
	~Array();

public:
	int length();

private:
	int m_length;
	int *m_space;

public:
	//函数返回值当左值需要返回引用
	int& operator[](int i);
};

//重载[]
int& Array::operator[](int i){
	return m_space[i];
}

3.6逻辑判断操作符重载( == ,!= ):

和3.5一样得情景中,添加逻辑判断操作符得重载;如下:

bool Array::operator==( Array& const a) {
	if (this->length() != a.length()) {
		return false;
	}
	for (int i = 0; i < this->length(); i++) {
		if ((*this)[i] != a[i]) {
			return false;
		}
	}
	return true;
}

bool Array::operator!=(Array& const a) {
	return !(*this == a);
}

3.7小括号操作符重载(())

除了上面我们常见的运算符重载外,()也可以进行重载。小括号操作符重载可以让对象看起来像函数调用一样使用。

class F
{
public:
	int operator()(int a, int b) {
		return a * 3 + b * 4 + k;
	}
private:
	int k = 0;
};

int main()
{
	F f;
	cout << f(1, 2);

}

4.运算符重载的机制总结

总结1:运算符重载本质是函数调用

总结2:运算符重载可以对运算符做出新的解释,但不会改变原有的基本语义:

  • 不改变运算符的优先级
  • 不改变运算符的结合性
  • 不改变运算符所需要的操作数
  • 不能创建新的运算符

总结3:友元函数实现操作符重载总结

  • 友元函数重载运算符常用于左右操作数类型不同的情况
  • 第一个参数需要隐式转换的情况下,使用友元函数重载
  • 友元函数不能重载:“=”,“()”,“[]”,“->”

总结4:重载等号操作符总结

  • 要先把被复制对象旧的内存释放
  • 根据大小开辟新内存,并赋值
  • 返回一个引用类型(为了实现连等)

总结5:最好不要重载&&||操作符

  • &&和||是非常特殊的操作符,因为他们内置实现了短路原则
  • 操作符重载是靠函数重载来实现的,操作数作为函数参数传递;
  • C++的函数参数都会被求值,所以无法实现短路原则

总结6:不要忘记()操作符也可以重载

  • 用于函数调用
  • 记得区分是类的构造函数还是对象的()重载

 

 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值