【C++ 】运算符重载

目录

一、运算符重载的基本概念

二、运算符重载的形式

运算符重载-如何决定作为成员函数还是非成员函数

三、赋值运算符的重载

 四、流插入运算符和流提取运算符的重载

<<运算符的重载

 >>运算符的重载

 五、类型转换运算符,自增自减运算符的重载

重载类型转换运算符

重载自增,自减运算符

附:运算符重载的注意事项

参考:程序设计与算法(三)C++面向对象程序设计


一、运算符重载的基本概念

我们知道C++预定义的运算符,只能用于基本数据类型的运算

如:整型,实型,字符型,逻辑型.......

运算符如:+、-、*、/、%、&、~、!、|、=、<<、>>、!=、.....

运算符重载,就是对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。

运算符重载的目的是:扩展C++中提供的运算符的适用范围,使之能作用于对象。

二、运算符重载的形式

1、运算符重载的实质是函数重载

2、可以重载为普通函数,也可以重载为成员函数

3、把含运算符重载的表达式转换成为对运算符函数的调用

4、把运算符的操作数转换成运算符函数的参数

5、运算符被多次重载时,根据实参的类型决定调用哪个运算符函数

运算符重载的形式

返回值类型 operator 运算符 (形参表)

{

        ..........

}

下面举一个重载的例子

定义一个复数对象重载运算符+ -

#include 
using namespace std;

class Complex {
public:
	double real, imag;
	Complex():real(0),imag(0) {}
	Complex(double r, double i):real(r),imag(i){}
	Complex operator-(const Complex& c);
};
//重载运算符+ (重载为普通函数)
Complex operator+ (const Complex& a, const Complex& b)
{
	return Complex(a.real + b.real, a.imag + b.imag); //返回一个临时对象
}
//重载运算符- (重载为成员函数)
Complex Complex::operator-(const Complex& c)
{
	return Complex(real - c.real, imag - c.imag);	//返回一个临时对象
}

int main()
{
	Complex a(9, 3), b(6, 2), c;

	c = a + b;
	//c = operator+(a, b);

	cout << c.real << " " << c.imag << endl;//输出15 5

	cout << (a - b).real << " " << (a - b).imag << endl;//输出3 1

	//cout << a.operator-(b).real << " " << a.operator-(b).imag;

	return 0;
}

 重载为成员函数时,参数个数为运算符目数减一

 重载为普通函数时,参数个数为运算符目数

运算符重载-如何决定作为成员函数还是非成员函数

对于运算符重载可通过成员函数和非成员函数实现,这二者的区别如下:

(1)成员函数运算符重载时,运算符的左值为调用对象,右值为参数对象,举个例子,a,b均是类A的对象,重载“+”实现a+b,可以将其认为是:a对象调用“+”函数,函数的参数为b对象;而在事实上,a对象和b对象都是这个“+”函数的参数,只不过a对象被隐式调用,由this指针所绑定。因此成员函数运算符重载的显式参数比实际运算参数少一个;而在非成员函数运算符重载中,必须将操作数全部显式添加在参数列表中,运算符左值为第一个参数,运算符右值为第二个参数。

(2)成员函数运算符重载时,运算符左值类型必须为所在类类型;而非成员函数运算符重载则不必。

这个时候我们就会有这样的疑问,如何决定运算符重载为成员函数还是非成员函数呢?

 一般来说,对于双目运算符,应当将其重载为非成员函数(友元函数),而对于单目运算符,则应将其重载为成员函数。但这也不是绝对的,双目运算符中,“=”、“[]”、“->”和“()”是必须重载为成员函数的。而"<<"运算符由于其第一个运算符必须是ostream的,所以只能重载为友元函数

三、赋值运算符的重载

有时候希望赋值运算符两边的类型可以不匹配,比如把一个int类型的变量赋值给一个Complex对象,或把一个char * 类型的字符串赋值给一个字符串对象,此时就要重载赋值运算符“=”

赋值运算符“=”只能重载为成员函数

#include 
#include 
using namespace std;

class String {
private:
	char* str;
public:
	String() :str(new char[1]) { str[0] = 0; }
	const char* c_str() { return str; };
	String& operator = (const char* s);
	~String() { delete[] str; }
};

String& String::operator=(const char* s)//重载“=”使得obj = "hello"
{
	delete[] str;
	str = new char[strlen(s) + 1];
	strcpy(str, s);
	return *this;
}

int main()
{
	String s;
	s = "Good Luck,";//s.operator=("Good Luck,");
	cout << s.c_str() << endl;	//输出Good Luck,

	s = "Shenzhou 8!";//s.operator=("Shenzhou 8!");
	cout << s.c_str() << endl;  //输出 Shenzhou 8!
}

 四、流插入运算符和流提取运算符的重载

<<运算符的重载

C++在输出内容时候 常常会是这样的方式

cout << 66 << "this";

那么这条语句为什么能成立呢?

// 实际上,cout 是在iostream头文件中定义的ostrem类的对象
//"<<"能用在cout 上是因为 ,在ostream类里面 "<<"进行了重载
cout << 66; //即 cout.operator<<(66);
cout << "this";//即 cout.operator("this");

假定我们要想把某个对象里的内容进行打印输出,那么我们可以重载 ostream 类的流插入 << 运算符

下面以 CStudent 类作为例子:

#include 
using namespace std;

class Cstudent {
private:
	int age;		//年龄
	int sno;		//学号
	string sname;	//姓名
public:
	//构造函数
	Cstudent(int age_ = 0, int sno_ = 0, string sname_ = ""):age(age_),sno(sno_),sname(sname_){}

	//将该函数声明为友元函数 目的是使得函数可以访问CStudent类的私有成员变量
	friend ostream& operator<<(ostream& o, const Cstudent& s);
};

ostream& operator<<(ostream& o, const Cstudent& s) {
	o << s.age << " " << s.sno << " " << s.sname;
	return o;
}

int main()
{
	Cstudent stu1(18, 201304088, "张三");
	Cstudent stu2(21, 201304066, "李四");

	cout << stu1 << endl;
	cout << stu2 << endl;

	return 0;
}

 输出结果:

18 201304088 张三
21 201304066 李四

 需要注意的是:

ostream& operator<<(ostream& o, const Cstudent& s)

 函数是全局的,所以函数的第一个参数必须要传入 ostream 的对象,并且 CStudent 类需要将此   函数声明成友元函数,使得函数可以访问 CStudent 类的私有成员变量

 >>运算符的重载

还是以Cstudent类作为例子,假设想通过键盘的输入的内容,来初始化对象,则我们可以重载 istream 类的流提取 >> 运算符

#include 
using namespace std;

class Cstudent {
private:
	int age;		//年龄
	int sno;		//学号
	string sname;	//姓名
public:
	//构造函数
	Cstudent(int age_ = 0, int sno_ = 0, string sname_ = "") :age(age_), sno(sno_), sname(sname_) {}

	//将该函数声明为友元函数 目的是使得函数可以访问CStudent类的私有成员变量
	friend ostream& operator<<(ostream& o, const Cstudent& s);
	friend istream& operator>>(istream& is, Cstudent& s);
};

//重载ostream对象的流插入<<运算符函数 使得打印输出Cstudent对象的信息
ostream& operator<<(ostream& o, const Cstudent& s) {
	o << s.age << "," << s.sno << "," << s.sname;
	return o;
}

//重载istream对象的流提取>>运算符函数 使得初始化Cstudent对象的内容
istream& operator>>(istream& is, Cstudent& s) {
	string inputStr;
	is >> inputStr;

	int pos = inputStr.find(",", 0);			//查找首次出现" "的位置
	string tempStr = inputStr.substr(0, pos);	//截取从0-pos位置的字符串
	s.age = atoi(tempStr.c_str());				//char* 类型转int类型

	int pos2 = inputStr.find(",", pos + 1);				//从pos+ 1开始 查找第二次出现" "的位置
	tempStr = inputStr.substr(pos + 1, pos2 - pos - 1);	//截取出sno
	s.sno = atoi(tempStr.c_str());						//char* 类型转int类型

	tempStr = inputStr.substr(pos2 + 1, inputStr.length() - pos2 - 1);//截取出sname
	s.sname = tempStr;
	return is;
}

int main()
{
	Cstudent stu;

	cin >> stu;//输入18,201304088,张三

	cout << stu;//输出18,201304088,张三

	return 0;
}

 下面来写一个Complex类 对流插入运算符和流提取运算符重载

这时我们便可以熟练的写出 二者的运算符重载

friend ostream& operator<<(ostream& o, const Complex& c);
friend istream& operator>>(istream& is, Complex& c);

 完整程序

#include 
using namespace std;

class Complex {
private:
	double real, imag;
public:
	Complex() :real(0), imag(0) {}//无参构造函数
	Complex(double r, double i) :real(r), imag(i) {}// 用参数初始化表对其数据成员初始化
	friend ostream& operator<<(ostream& o, const Complex& c);
	friend istream& operator>>(istream& is, Complex& c);
};

ostream& operator<<(ostream& o, const Complex& c)
{
	o << c.real << "+" << c.imag << "i";
	return o;
}

istream& operator>>(istream& is, Complex& c)
{
	string s;
	is >> s;
	int pos = s.find("+", 0);
	string temp = s.substr(0, pos);
	c.real = atof(temp.c_str());

	temp = s.substr(pos + 1, s.length() - pos - 1);
	c.imag = atof(temp.c_str());

	return is;
}
int main()
{
	Complex c;

	cin >> c;

	cout << c;

	return 0;
}

 五、类型转换运算符,自增自减运算符的重载

重载类型转换运算符

可以把对象转换成为想要转换成某一种其他的类型东西

我们依旧以Complex类为例  把Complex定义的一个对象 转换为double型 

完整程序

#include 
using namespace std;

class Complex {
private:
	double real, imag;
public:
	Complex() :real(0), imag(0) {}//无参构造函数
	Complex(double r, double i) :real(r), imag(i) {}// 用参数初始化表对其数据成员初始化
	operator double() { return real; }
	//重载强制类型转换运算符double
};

int main()
{
	Complex c(1.2, 3.4);

	cout << double(c) << endl;//输出1.2

	double n = 2 + c;// double n = 2 + c.operator double ()

	cout << n;//输出3.2

	return 0;
}

 类型转换运算符重载的时候返回值类型是不写的 因为返回的就是要重载的类型本身

operator double() { return real; }//重载强制类型转换运算符double

重载自增,自减运算符

自增、自减运算符有前置/后置之分 为了区分所重载的运算符是前置运算符还是后置运算符,C++规定:

前置运算符作为一元运算符重载

重载为成员函数:
T& operator++();
T& operator--();
重载为全局函数:
T& operator++(T2);
T& operator--(T2);

 后置运算符作为二元运算符重载,多写一个没有用的参数

重载为成员函数:
T& operator++(int);
T& operator--(int);
重载为全局函数:
T& operator++(T2, int );
T& operator--(T2, int );

我们以CDemo类为例

完整程序如下

#include 
using namespace std;

class CDemo {
private :
	int x;
public:
	CDemo(int x_ = 0) { x = x_; }
	CDemo& operator++();		//前置形式
	CDemo operator++( int );	//后置形式
	operator int() { return x; }
	friend CDemo& operator--(CDemo&);
	friend CDemo operator--(CDemo& , int );
};

CDemo& CDemo::operator++() {
	//前置 ++ 返回值是一个引用
	++x;
	return *this;
	//++s 即为s.operator++()
}

CDemo CDemo::operator++( int k ) {
	//后置 ++ 返回值是一个对象
	CDemo temp(*this);
	x++;
	return temp;
	//s++ 即为s.operator++(0)
}

CDemo& operator--(CDemo& d)
{
	//前置 ++ 返回值是一个引用
	d.x--;
	return d;
	//--s即为:operator--(s)
}

CDemo operator--(CDemo& d, int )
{
	//后置 ++ 返回值是一个对象
	CDemo temp(d);
	d.x--;
	return temp;
	//s--即为:operator--(s, 0);
}

//前置的++ -- 没有生成对象 返回值是引用
//后置的++ -- 会生成对象 引发构造函数的调用 返回一个对象又会引发构造函数的调用
//所以用前置++ 会比后置++ 效率更高

int main()
{
	CDemo d(5);
	cout << (d++ ) << ",";			//d.operator++(0);  输出5
	cout << d << ",";				//输出6
	cout << (++d) << ",";			//d.operator++();   输出7
	cout << d << endl;				//输出7
	cout << (d--) << ",";			//d.operator-(d, 0); 输出7
	cout << d << ",";				//输出6
	cout << (--d) << ",";			//d.operator-(d)输出5
	cout << d << endl;				//输出5
	return 0;
}

 注意:

前置的++ -- 没有生成对象 返回值是引用
后置的++ -- 会生成对象 引发构造函数的调用 返回一个对象又会引发构造函数的调用
所以用前置 会比后置 效率更高

附:运算符重载的注意事项

1、重载后运算符的含义应该符合原有用法习惯

例如重载 + 运算符,完成的功能就应该类似于做加法,在重载的 + 运算符中做减法是不合适的。

2、运算符重载不改变运算符的优先级。重载运算符时,应该尽量保留运算符原本的特性。

3、以下运算符不能被重载:. 、 .* 、 :: 、? : 、 sizeof 。

重载运算符 () 、 [] 、 -> 、或者赋值运算符 = 时,只能将它们重载为成员函数,不能重载为全局函数。

4、必要时需要重载赋值运算符=,以避免两个对象内部的指针指向同一片存储空间。

参考:程序设计与算法(三)C++面向对象程序设计

你可能感兴趣的:(C++面向对象,c++)