8.运算符重载

8.重载运算符

简单的说,给原有的运算符赋予新功能,就是运算符重载

重载运算符的本质是函数重载,它是C++多态的一种体现,为用户提供了一个直观的接口,调用运算符操作自定义数据类型其实就是调用运算符函数。运算符重载增强了C++的可扩充性,使C++代码更加直观,易懂。

8.1重载运算符的定义

在C++中,我们可以重载或重定义大部分C++内置的操作符。这样,就可以自定义类型的运算符。

重载的运算符是带有特殊名称的函数,函数名是由关键字operator和其后要重载的运算符符号构成。与其他函数构成相同,重载运算符有一个返回类型和一个参数列表。

int operator+(const A &a);

实例:

#include <iostream>
using namespace std;
class A
{
public:
	int operator+(const A& a)	//重载操作符的定义
	{
		return m + a.m;
	}
	void set(int m_)
	{
		m = m_;
	}
private:
	int m;
};
int main()
{
	A a1,a2;
	a1.set(1);
	a2.set(2);
	cout << a1 + a2 << endl;	//重载操作符的使用
	return 0;
}
//输出:
//2

8.2可重载运算符/不可重载运算符

下面是可重载的运算符列表:

双目算术运算符+ (加),-(减),*(乘),/(除),% (取模)
关系运算符==(等于),!= (不等于),< (小于),> (大于>,<=(小于等于),>=(大于等于)
逻辑运算符||(逻辑或),&&(逻辑与),!(逻辑非)
单目运算符+ (正),-(负),*(指针),&(取地址)
自增自减运算符++(自增),–(自减)
位运算符|(按位或),& (按位与),~(按位取反),^(按位异或),,<< (左移),>>(右移)
赋值运算符=, +=, -=, *=, /= , % = , &=,
空间申请与释放new, delete, new[ ] , delete[]
其他运算符()(函数调用),->(成员访问),,(逗号),

下面是不可重载的运算符列表:

  • .:成员访问运算符
  • . * , ->*:成员指针访问运算符
  • :::域运算符
  • sizeof:长度运算符
  • ?::条件运算符
  • #: 预处理符号

8.3运算符重载机制

重载的运算符是具有特殊名字的函数:它们的名字由关键字operator和其后要重载的运算符共同组成。

语法结构:

返回类型 operator 运算符(参数列表)
{
	函数体;
}

运算符重载规则:

  1. 只能重载C++已有的运算符,不能创建新的运算符。
  2. 重载之后的运算符不能改变其优先性结合性,也不能改变其操作数的个数语法结构
  3. 避免没有目的的使用重载运算符。

8.4运算符重载规则(类成员函数、类友元函数)

重载为类的成员函数

重载为类的成员函数时,当函数无参时,编译器其实会自动添加一个this指针。

双目运算符
左操作数.运算符重载函数(右操作数);

定义二元运算符重载函数时,函数中只能定义一个形参。成员赋值操作符也只接受一个(显式的)形参。使用操作符时,一个指向左操作数的指针自动绑定到this,而右操作数限定为函数的唯一形参。
单目运算符
操作数.运算符重载函数();

重载为类的友元函数

语法格式:

friend 返回类型 operator 运算符(参数列表)
{
	函数体;
}

返回类型为的类的对象的重载运算符

#include <iostream>
using namespace std;
class Box
{
public:
	void set(int bb)
	{
		b = bb;
	}
	int get()
	{
		return b;
	}
	Box operator+(const Box& box)	//重载运算符的定义
	{
		Box box_;
		box_.b = this->b + box.b;
		return box_;	//返回类型为Box对象
	}
private:
	int b;
};
int main()
{
	Box b1, b2, b3;
	b1.set(1);
	b2.set(2);
	b3 = b1 + b2;	
	cout << b3.get() << endl;
	return 0;
}

一元运算符重载

一元运算符只对一个操作数进行操作,下面是一元运算符的实例:

  • 递增运算符(++)和递减运算符(–)
  • 一元运算符,即负号(-)
  • 逻辑非算符(!)

实例:

#include <iostream>
using namespace std;
class Data
{
public:
	Data(int a):a(a)
	{}
	void set(int aa)
	{
		a = aa;
	}
	int get()
	{
		return a;
	}

	Data operator!()
	{
		a = !a;
		return *this;
	}
private:
	int a;
};
int main()
{
	Data data(1);

	!data;
	cout << "after !:" << data.get() << endl;
	return 0;
}
-运算符的重载

注意:必须返回类的对象的引用,否则会出现错误
实例:

#include <iostream>
using namespace std;
class Data
{
public:
	Data(int a) :a(a)
	{}
	void set(int aa)
	{
		a = aa;
	}
	int get()
	{
		return a;
	}
	//Data &operator-()	//此处必须写上引用,否则会出现错误
	//{
	//	a = -a;
	//	return *this;
	//}
	friend Data &operator-(Data &d)	//此处同理,写引用
	{
		d.a = -d.a;
		return d;
	}

private:
	int a;
};

int main()
{
	Data data(1);
	cout << data.get() <<endl;
	-(-data);	//若不写引用,此处会出现错误
	cout << data.get() <<endl;
	return 0;
}
++运算符的重载

首先注意几点:

  • ++运算符有前缀和后缀,这两种用法不同,自然写法写不同
  • ++前缀是得到自增后的值,同时自增,++后缀是得到自增前的值,再自增

实例:

#include <iostream>
using namespace std;
class Data
{
public:
	Data(int a) :a(a)
	{}
	void set(int aa)
	{
		a = aa;
	}
	int get()
	{
		return a;
	}
	Data& operator++()	//前缀++,此处加引用,因为得到的是自增后的值
	{
		a++;
		return *this;
		//return Data(a);	//方法2
	}
	Data operator++(int)	//后缀++,此处不加引用,因为得到的是自增前的值
	{						//int标识作用,不传入任何值,即便是传入任何值也没有意义,因为我们不会去使用它
		Data da(*this);	//定义一个新的对象,将自增前的值保存
		this->a++;
		return da;
	}

private:
	int a;
};

int main()
{
	Data data(1);
	cout << data.get() << endl;
	cout << (data++).get() << endl;	//data.operator++(0);	系统默认传入0
	cout << data.get() << endl;
	return 0;
}

二元运算符

	Coordinate operator+(Coordinate& c)	//类的成员函数实现重载运算符+
	{
		Coordinate coor(0, 0);
		coor.m_x = this->m_x + c.m_x;
		coor.m_y = this->m_y + c.m_y;
		return coor;
	}
	friend Coordinate operator+(Coordinate& a, Coordinate& b)	//友元函数实现重载运算符+
	{
		Coordinate coor(0, 0);
		coor.m_x = a.m_x + b.m_x;
		coor.m_y = a.m_y + b.m_y;
		return coor;
	}

实例:

#include <iostream>
using namespace std;
class Data
{
public:
	Data()
	{}
	Data(int a) :a(a)
	{}
	int get()
	{
		return a;
	}
	friend Data operator+(const Data& d1,const Data& d2)	//重载符号+  用于对象相加
	{
		Data da;
		da.a = d1.a + d2.a;
		return da;
	}
	friend Data operator+(const Data& d1,const int& a_)	//重载符号+  对象与数相加
	{
		Data da;
		da.a = d1.a + a_;
		return da;
	}
	friend Data operator+(const int& b, Data da);
private:
	int a;
};

Data operator+(const int& b, Data da)		//重载符号+  数与对象相加
{
	Data d;
	d = da + b;
	return d;	//友元函数调用第二个重载+的成员函数,相当于 da.operator+(b)
}

int main()
{
	Data d1(1), d2(2), d3;
	int m = 6;
	d3 = d1 + d2;
	cout << "obj + obj:" << d3.get() << endl;
	d3 = d1 + m;
	cout << "obj + num:" << d3.get() << endl;
	d3 = m + d1;
	cout << "num + obj:" << d3.get() << endl;
	return 0;
}

关系运算符重载

实例:

#include <iostream>
using namespace std;
class Data
{
public:
	Data()
	{}
	Data(int a):a(a)
	{}
	int get()
	{
		return a;
	}
	bool operator<(const Data &da)	//重载运算符 <
	{
		if (a < da.a)
			return true;
		else
			return false;
	}
private:
	int a;
};

int main()
{
	Data d1(1), d2(2);
	if (d1 < d2)
		cout << "d1 < d2" << endl;
	return 0;
}

输入/输出 运算符重载

实例:

#include <iostream>
using namespace std;
class Data
{
public:
	Data()
	{
		a = 0;
	}
	Data(int a):a(a)
	{}
	int get()
	{
		return a;
	}
	friend ostream& operator <<(ostream& output, const Data& da)	//重载运算符 <<
	{																//左操作数为输出流,右操作数为类的对象
		output << da.a;
		return output;
	}
	friend istream& operator >>(istream& input, Data& da)	//重载运算符 >>
	{														//左操作数为输入流,右操作数为类的对象
		input >> da.a;
		return input;
	}
private:
	int a;
};

int main()
{
	Data d;
	cin >> d;	//operator>>(cin ,d)
	cout << d;	//operator<<(cout ,d)
	return 0;
}
输入输出运算符重载的另类写法

一般的,IO操作符必须为非成员函数,否则,左操作数只能是该类型的对象

Data data;
data << cout;

实例:

//输入/输出 运算符重载只带有一个形参值
#include <iostream>
using namespace std;
class Data
{
public:
	Data()
	{
		a = 0;
	}
	Data(int a):a(a)
	{}
	int get()
	{
		return a;
	}
	ostream& operator <<(ostream& output)	//重载运算符 <<
	{										//形参为输出流,因为友元函数不带有this指针,所以不能定义为友元函数
		output << a;
		return output;
	}
	istream& operator >>(istream& input)	//重载运算符 >>
	{										//形参为输入流,因为友元函数不带有this指针,所以不能定义为友元函数
		input >> a;
		return input;
	}
private:
	int a;
};

int main()
{
	Data d;
	d >> cin;	//调用 >> 函数,形参为cin输入流
	d << cout;	调用 << 函数,形参为cout输出流
	return 0;
}

赋值运算符重载

赋值运算符可以重载。无论形参为何种类型,赋值操作符必须定义为成员函数,这一点与复合赋值操作符有所不同。

赋值必须返回*this的引用。

一般而言,赋值操作符和复合赋值操作符应返回左操作数的引用。

实例:

#include <iostream>
using namespace std;
class Data
{
public:
	Data()
	{
		a = 0;
	}
	Data(int a):a(a)
	{}
	int get()
	{
		return a;
	}
	void operator +=(const int& m)	//写法1
	{
		this->a += m;
	}
	/*Data operator +=(const int &m)	//写法2
	{
		a += m;
		return *this;
	}*/
	Data operator -=(const int& m)
	{
		a -= m;
		return *this;
	}
	Data operator *=(const int& m)
	{
		a *= m;
		return *this;
	}
	Data operator /=(const int& m)
	{
		a /= m;
		return *this;
	}
private:
	int a;
};

int main()
{
	Data d;
	int m = 1;
	d += m;
	cout << d.get() << endl;
	return 0;
}
自定义类型的隐式转换

当用户使用自定义类型变量向内置类型变量赋值时,可以使用自定义类型的隐式转换。
实例:

#include <iostream>
using namespace std;
class Int
{
private:
	int n;
public:
	Int(int i):n(i){}
	operator int()	//隐式转换声明,应该注意此处它与运算符重载的不同之处
	{
		return n;
	}
};
int main()
{
	Int a(5);
	int c = a;	//隐式调用转换函数
	cout << c << endl;
	cout << a << endl;	//由于为重载Int的<<操作符,将隐式调用转换函数
	return 0;
}

注意谨慎的使用隐式转换函数,因为当你不需要使用转换函数时,这些函数却可能会被调用运行,这些不正确的程序会做出咦嘻嘻额意想不到的事情,而你有很难判断出原因。

函数调用运算符()重载

函数调用运算符()可以被重载用于类的对象。当重载()时,这就创建了一种新的太哦用函数的方式,相反的,这是穿件一个可以传递任意数目参数的运算符函数。
实例:

#include <iostream>
using namespace std;
class Data
{
public:
	Data()
	{
		a = 0;
	}
	Data(int a) :a(a)
	{}
	int get()
	{
		return a;
	}
	Data operator()(int b, int c)	//重载函数调用运算符
	{
		a = b + c;
		return *this;
	}
private:
	int a;
};

int main()
{
	Data d1(1), d2;
	d2 = d1(10, 10);
	cout << d2.get() << endl;
	return 0;
}

索引运算符[]重载

实例:

#include <iostream>
using namespace std;
class Coordinate
{
public:
	Coordinate(int x,int y):m_x(x),m_y(y)
	{}
	int operator[](int index)
	{
		if (index == 0)
			return m_x;
		if (index == 1)
			return m_y;
	}
private:
	int m_x;
	int m_y;
};
int main()
{
	Coordinate coor(3,4);
	cout << coor[0] << endl;	//coor.operator[](0)
	cout << coor[1] << endl;	//coor.operator[](1)
	return 0;
}

箭头操作符(->)的重载

箭头操作符(->)的通常用法是,使用一个类对象的指针来调用该指针指向对象的成员。左操作数作为指针,右操作数为该对象的成员。定义重载箭头操作符后就比较特殊了,可以用类对象的指针来调用,也可以类的对象来调用。

重载箭头操作符,必须重载为类的成员函数。

箭头操作符可能看上去像一个二元操作符:接受一个对象名和一个成员名,对对象解引用获取成员。其实箭头操作符是一个一元操作符,没有显式形参,(唯一的隐式形参是this)。->的右操作数不是表达式,而是对象类成员的一个标识符,由编译器处理获取成员工作(编译器对重载箭头操作符所做的工作,比其他重载操作符要多,这也正是复杂的地方)


《C++ primer》操作符重载内容:

重载箭头操作符

重载运算符没有第二个形参,因为->的右操作数不是表达式,相反,是对应的类成员的一个标识符。没有明显可行的途径将一个标识符传递给形参,由编译器处理获取成员的工作。

当这样编写时:

point->action();

由于优先规则,所以等价于:

(point -> action) ();

换句话说,编译器想调用的是point->action()求值的结果。编译器这样对代码进行求值:

  1. 如果point是一个指针,指向具有名为action的成员的类对象,则编译器将代码编译为调用该对象的成员。

  2. 否则,如果point是定义了->重载操作符的类的一个对象,则point->action与point->()->action相同,即执行point的operator->(),然后使该结果重复三遍。

  3. 否则,代码出错

对重载箭头的返回值的约束

重载的箭头操作符必须返回指向类类型的指针,或者返回定义了自己的箭头操作符的类类型对象。

如果返回类型是指针,则内置箭头操作符可用于该指针,编译器对该指针解引用并从结果对象获取指定成员。如果被指向的类型没有定义那个成员,则编译器产生一个错误。

如果返回类型是类类型的其他对象(或是这种对象的引用),则将递归应用该操作符。编译器检查返回对象所属类型是否具有成员箭头,如果有,就应用那个操作符;否则,编译器产生一个错误。这个过程继续下去,直到返回一个指向带有指定成员的的对象的指针,或者返回某些其他值,在后一种情况下,代码出错。


实例:

#include <iostream>
using namespace std;
class A
{
public:
	void action()
	{
		cout << "action in class A" << endl;
	}
};

class B
{
public:
	A a;
	void actoin()
	{
		cout << "action in class B" << endl;
	}
	A* operator->()
	{
		return &a;
	}
};
class C
{
public:
	B b;
	void action()
	{
		cout << "action in class C" << endl;
	}
	B operator->()
	{
		return b;
	}
};
int main()
{
	C* pc = new C;
	pc->action();
	C c;
	c->action();
	return 0;
}

输出结果:
Action in class C
Action in class A

其中的代码

C* pc = new C;
 pc->action();

输出的结果是

Action in class C!

这个结果比较好理解,pc是类对象指针,此时的箭头操作符使用的是内置含义,对pc解引用然后调用对象的成员函数action。

而下面的代码

C c;
 c->action();

输出的结果是

Action in class A!

其实c->action();的含义与c.operator->().operator->()->action();相同。

c是对象,c后面的箭头操作符使用的是重载箭头操作符,即调用类C的operator->()成员函数。此时返回的是类B的对象,所以调用类B的operator->()成员函数,B的operator->()返回的是指针,所以现在可以使用内置箭头操作符了。对B的operator->()返回的指针进行解引用,然后调用解引用后的对象的成员函数action,此时调用的就是类A的action()。这里存在一个递归调用operator->()的过程,最后再使用一次内置含义的箭头操作符。

总结

  • 输入输出运算符的重载必须是友元函数
  • 索引运算符的重载必须是类的成员函数
  • 单目运算符最好重载为类的成员函数(单目运算符改变操作对象),双目运算符最好重载为友元函数(双目运算符不改变左右操作数)
  • 有4个运算符必须重载为类的成员函数:赋值(=)、下标([])、调用(())、成员访问箭头(->)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值