【C++面向对象程序设计】CH4 运算符重载

目录

一、什么是运算符重载

1.定义

2.实质

3.【例4.1】通过成员函数实现复数的加法

二、运算符重载的方法

1.格式

2.规则和限制

(1)规则和限制

(2)两种形式

(3)运算符函数

3.【例4.2】重载运算符,用于两个复数相加

(1)分析

(2)代码&结果

(3)说明

三、重载运算符的规则

四、运算符重载函数作为类成员函数与友元函数

1.【例4.3】将加法运算符重载为适用于复数加法,重载函数作为类的友元函数

​编辑2.【注】

3.书上的错误

五、重载双目运算符

1.含义

2.【例4.4】定义一个字符串类String,用来处理不定长的字符串,重载相等、大于、小于关系运算符,用于两个字符串的等于、大于、小于的比较运算

(1)【分析】

(2)代码&结果

(3)说明

3.扩展到对三个运算符重载:在String类体中声明三个重载函数是友元函数,并编写相应的函数

六、重载单目运算符

1.含义

2.【例4.5】有一个Time类,数据成员有时、分、秒。要求模拟秒表,每次走一秒,满60秒进位,秒又从零开始计数。满60分进位,分又从零开始计数。输出时、分、秒的值

3.【例4.6】在【例4.5】的基础上增加后++运算符重载函数

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

1.含义

2.重载流插入运算符“<<”

(1)【例4-7】在【例4-2】的基础上用<<重载函数输出复数

(2)说明

3.重载流提取运算符“>>”

(1)【例4.8】在【例4.7】的基础上增加流提取运算符>>重载函数,用cin>>输入复数,用cout输出复数

(2)说明 


一、什么是运算符重载

1.定义

        C++为程序员提供了灵活的手段,让程序员自己定义类,自己设计相应的运算符(必须在已有的运算符基础上设计),使之应用于自己定义的类。与函数重载相似,对已有的运算符赋予新的含义,用一个运算符表示不同功能的运算,这就是运算符重载

        实际上,我们在此之前已经使用了运算符重载。如<<是C++的移位运算符,它又与流对象cout配合作为流插入运算符,这是C++对<<进行了重载处理。

2.实质

        运算符重载是对已有的运算符赋予多重含义。

  • 必要性:C++中预定义的运算符其运算对象只能是基本数据类型,而不适用于用户自定义类型(如类)
  • 将指定的运算表达式转化为对运算符函数的调用,运算对象转化为运算符函数的实参
  • 编译系统对重载运算符的选择,遵循函数重载的选择原则

3.【例4.1】通过成员函数实现复数的加法

#include 
using namespace std;

class Complex 
{
	private:
		double real;
		double imag;
	public:
		Complex() 
		{
			real = 0;
			imag = 0;
		}
		Complex(double r, double i) 
		{
			real = r;
			imag = i;
		}
		Complex complex_add(Complex &c2);
		void display();
};

Complex Complex::complex_add(Complex &c2) 
{
	Complex c;
	c.real = real + c2.real;
	c.imag = imag + c2.imag;
	return c;
}

void Complex::display() 
{
	cout << "(" << real << "," << imag << "i)" << endl;
}

int main() 
{
	Complex c1(3, 4), c2(5, -10), c3;
	c3 = c1.complex_add(c2);
	cout << "c1=";
	c1.display();
	cout << "c2=";
	c2.display();
	cout << "c1+c2";
	c3.display();
	return 0;
}

【C++面向对象程序设计】CH4 运算符重载_第1张图片

  • 在Complex类中定义了complex_add函数做加法,函数的参数是引用对象,作为一个加数。在函数里定义了临时变量c,两个赋值语句相当于:
  • c.real=this->real+c2.real;
  • c.imag=this->imag+c2.imag;
  • 在mian函数中通过对象c1调用加法函数,上面的语句相当于:
  • c.real=c1.real+c2.real;
  • c.imag=c1.imag+c2.imag;

二、运算符重载的方法

1.格式

        运算符重载的方法是定义一个重载运算符函数,在需要时系统自动调用该函数,完成相应的运算。运算符重载实质上是函数的重载。运算符重载函数的格式是:

数据类型 operator 运算符(形参表)

{重载处理}

数据类型:是重载函数值的数据类型

operator是保留字

2.规则和限制

(1)规则和限制

  •  C++中可以重载除下列运算符外的所有运算符:. .* :: ? :
  • 只能重载C++语言中已有的运算符,不可臆造新的
  • 不可改变原运算符的优先级和结合性
  • 不能改变操作数个数
  • 经重载的运算符,其操作数中至少应该有一个是自定义类型

(2)两种形式

  • 重载为类成员函数
  • 重载为友元函数

(3)运算符函数

  • 声明类型

函数类型  operator  运算符(形参)

{

        ...

}

  • 重载为类成员函数时:参数个数=原操作数个数-1(后置++,--除外)
  • 重载为友元函数时:参数个数=原操作数个数,且至少应该有一个自定义类型的形参
  • 不能重载的运算符有五个:.(成员运算符)、.*(成员指针运算符)、::(域运算符)、sizeof(长度运算符)、?:(条件运算符) 
  • 重载函数名是由operator和运算符联合组成
  • 复数加法运算符重载函数原型可以是:

Complex  operator+(Complex &c2);

3.【例4.2】重载运算符,用于两个复数相加

(1)分析

定义一个复数类,用成员函数实现加号的重载函数。两个复数相加结果仍是复数,所以函数的返回值的类型也是复数。用成员函数实现运算符重载函数时,掉用格式是“对象名.成员名”,此时对象就是一个参与运算的操作数,假发还需要另一个操作数,这个操作数用函数的参数传递,参数的类型就是复数类,而运算结果用函数值返回。

(2)代码&结果

#include 
using namespace std;

class Complex 
{
	public:
		Complex() 
		{
			real = 0;
			imag = 0;
		}
		Complex(double r, double i) 
		{
			real = r;
			imag = i;
		}
		Complex operator +(Complex &c2);
		void display();
	private:
		double real;
		double imag;
};

Complex Complex::operator +(Complex &c2) 
{
	Complex c;
	c.real = real + c2.real;
	c.imag = imag + c2.imag;
	return c;
}

void Complex::display() 
{
	cout << "(" << real << "," << imag << "i)" << endl;
}

int main() 
{
	Complex c1(3, 4), c2(5, -10), c3;
	c3 = c1 + c2;    //直接用+即可
	cout << "c1=";
	c1.display();
	cout << "c2=";
	c2.display();
	cout << "c1+c2=";
	c3.display();
	return 0;
}

【C++面向对象程序设计】CH4 运算符重载_第2张图片

(3)说明

  • 用运算符重载函数取代【例4.1】中的加法成员函数,从外观上看函数体和函数返回值都是相通的
  • 在主函数中表达式c3=c1+c2取代了【例4.1】中的c3=c1.complex_add(c2),编译系统将表达式c3=c1+c2解释为c1.operator +(c2),对象c1调用的重载函数operator +,以c2为实参计算两个复数之和
  • 在【例4.2】中能否用一个常量和一个复数相加?如c3=3+c2;(错误)。应该定义对象Complex  c1(3.0,0);c3=c1+c2;
  • 运算符重载后,其原来的功能仍然保留,编译系统根据运算表达式的上下文决定是否调用运算符重载函数。运算符重载和类结合起来,可以在C++中定义使用方便的新数据类型

三、重载运算符的规则

  • C++只允许已有的部分运算符实施重载
  • 不能重载的运算符有五个
  • 重载不改变操作数的个数
  • 重载不改变运算符的优先级
  • 运算符重载函数不能带默认值参数
  • 运算符重载函数必须与自定义类型的对象联合使用,其参数至少有一个类对象或类对象引用
  • C++默认提供=和&运算符重载
  • 运算符重载函数可以是类成员函数,也可以是类的友元函数,还可以是普通函数
  • C++规定赋值运算符、下标运算符、函数调用运算符必须定义为类的成员函数;而输出流插入、输入流提取、类型转换运算符不能定义为类的成员函数

四、运算符重载函数作为类成员函数与友元函数

        在【例4.2】程序中对运算符+进行了重载,该例将运算符重载函数定义为复数类的成员函数。从该程序中看到运算符重载为成员函数时,带有一个类类型的形参,而另一个加数就是对象自己。

1.【例4.3】将加法运算符重载为适用于复数加法,重载函数作为类的友元函数

#include 
using namespace std;

class complex 
{
	public:
		complex() 
		{
			real = 0;
			imag = 0;
		}
		complex(double r) 
		{
			real = r;
			imag = 0;
		}
		complex(double r, double i) 
		{
			real = r;
			imag = i;
		}
		friend complex operator +(complex &c1, complex &c2);
		void display();
	private:
		double real;
		double imag;
};

complex operator +(complex &c1, complex &c2) 
{
	return complex(c1.real + c2.real, c1.imag + c2.imag);	//显式调用构造函数
}

void complex::display() 
{
	cout << "(" << real << "," << imag << "i)" << endl;
}

int main() 
{
	complex c1(3, 4), c2(5, -10), c3;
	c3 = c1 + c2;
	cout << "c1=";
	c1.display();
	cout << "c2=";
	c2.display();
	cout << "c1+c2=";
	c3.display();
	return 0;
}

【C++面向对象程序设计】CH4 运算符重载_第3张图片2.【注】

  • 加法运算符重载为友元函数,C++编译时将表达式c1+c2解释为operator +(c1+c2),即相当于执行以下函数

complex operator +(complex &c1,complex &c2) 

{

        return complex(c1.real+c2.real,c1.imag+c2.imag);

}

  •  因为普通函数是不能直接访问对象的私有成员,如果普通函数必须访问对象的私有成员,可调用类的公有成员函数访问对象的底油成员,这会降低效率

3.书上的错误

  • 如果想将一个复数和一个整数相加,运算符重载函数作为成员函数定义如下。注意在运算符+的左侧必须是complex类对象,程序中可以写成c3=c2+n;不能写成c3=n+c2;
complex complex::operator +(int &i)
{
	return complex(real+i,imag);
}
 
  •  如果要求在使用重载运算符时,运算符左侧操作数不是对象,就不能使用前面定义的运算符重载函数,可以将运算符重载函数定义为友元函数。友元函数不要求第一个参数类型必须是类类型,但是要求实参要与形参一一对应:c3=n+c2(正确)c3=c2+n(错误)
friend complex operator +(int &i,complex &c)
{
	return complex(c.real+i,c.imag);
}
  • 为了实现加法的交换律,必须定义两个运算符重载函数,记住成员函数要求运算符左侧的操作数必须是自定义类型的对象,而友元函数没有这个限制,可以用下面两个组合中任意一个:
  • 成员函数(左操作数是对象,右操作数是非对象)、 友元函数(左操作数是非对象,右操作数是对象)
  • 友元函数(左操作数是对象,右操作数是非对象)、友元函数(左操作数是非对象,右操作数是对象)
  • 使用友元函数会破坏类的封装,要尽量将运算符重载函数定义为成员函数。但考虑到各方面的因素,一般将单目运算符重载为成员函数,将双目运算符重载为友元函数
  • VC++6.0的不带后缀h的头文件不支持把成员函数重载为友元函数,但VC6.0++带后缀h的头文件支持这项功能,所以要将程序中的语句:
#include 
using namespace std;
  • 改成
#include
  •  即可正常运行

五、重载双目运算符

1.含义

  • 双目的意思是运算符左边和右边的操作数均参加运算
  • 如果要重载B为类成员函数,使之能够实现表达式oprd1 B oprd2,其中oprd1为A类对象,则B应被重载为A类的成员函数,形参类型应该是oprd2所属的类型
  • 经重载后,表达式oprd1 B oprd2相当于oprd1.operator B(oprd2)

2.【例4.4】定义一个字符串类String,用来处理不定长的字符串,重载相等、大于、小于关系运算符,用于两个字符串的等于、大于、小于的比较运算

(1)【分析】

  • 操作数:两个操作数都是字符串类的对象
  • 规则:两个字符串进行比较
  • 将<+>运算重载为字符串类的成员函数

(2)代码&结果

#include 
#include 
using namespace std;

class String 
{
	public:
		String() 
		{
			p = NULL;
		}
		String(char *str);
		friend bool operator > (String &string1, String &string2);
		void display();
	private:
		char *p;
};

String::String(char *str) 
{
	p = str;
}

void String::display() 
{
	cout << p;
}

bool operator >(String &string1, String &string2) 
{
	if (strcmp(string1.p, string2.p) > 0)
		return true;
	else
		return false;
}

int main() 
{
	String string1("Hello"), string2("Book");
	cout << (string1 > string2) << endl;
	return 0;
}

(3)说明

  • 运算符重载函数定义为友元函数,函数值是布尔类型,在函数中调用了strcmp库函数,string1.p指向“Hello”,string2.p指向“Book”,程序运行结果是1

3.扩展到对三个运算符重载:在String类体中声明三个重载函数是友元函数,并编写相应的函数

#include 
#include 
using namespace std;

class String 
{
	public:
		String() 
		{
			p = NULL;
		}
		String(char *str);
		friend bool operator > (String &string1, String &string2);
		friend bool operator < (String &string1, String &string2);
		friend bool operator == (String &string1, String &string2);
		void display();
	private:
		char *p;
};

String::String(char *str) 
{
	p = str;
}

void String::display() 
{
	cout << p;
}

bool operator >(String &string1, String &string2) 
{
	if (strcmp(string1.p, string2.p) > 0)
		return true;
	else
		return false;
}

bool operator <(String &string1, String &string2) 
{
	if (strcmp(string1.p, string2.p) < 0)
		return true;
	else
		return false;
}

bool operator ==(String &string1, String &string2) 
{
	if (strcmp(string1.p, string2.p) == 0)
		return true;
	else
		return false;
}

int main() 
{
	String string1("Hello"), string2("Book");
	cout << (string1 > string2) << endl;
	cout << (string1 < string2) << endl;
	cout << (string1 == string2) << endl;
	return 0;
}

【C++面向对象程序设计】CH4 运算符重载_第4张图片

六、重载单目运算符

1.含义

  • 单目运算符只要一个操作数,由于只有一个操作数,重载函数最多只有一个参数,如果将运算符重载函数定义为成员函数还可以不用参数
  • 下面以自增运算符++为例,学习单目运算符的重载函数的编写方法

2.【例4.5】有一个Time类,数据成员有时、分、秒。要求模拟秒表,每次走一秒,满60秒进位,秒又从零开始计数。满60分进位,分又从零开始计数。输出时、分、秒的值

#include 
using namespace std;

class Time 
{
	public:
		Time() 
		{
			hour = 0;
			minute = 0;
			sec = 0;
		}
		Time(int h, int m, int s): hour(h), minute(m), sec(s) {}
		Time operator++();
		void display() 
		{
			cout << hour << ":" << minute << ":" << sec << endl;
		}
	private:
		int hour;
		int minute;
		int sec;
};

Time Time::operator++() 
{
	sec++;
	if (sec >= 60) 
	{
		sec = sec - 60;
		minute++;
		if (minute >= 60) 
		{
			minute = minute - 60;
			hour++;
			hour = hour % 24;
		}
	}
	return *this;
}

int main() 
{
	Time time1(23, 59, 0);
	for (int i = 0; i < 61; i++) 
	{
		++time1;
	}
	time1.display();
	return 0;
}

【注】C++除了有前++外,还有后++。同样的运算符由于操作数的位置不同,含义也不同。怎样区分前++和后++呢?C++给了一个方法,在自增或自减运算符重载函数中,增加一个int形参,程序员可以选择带int形参的函数做前++,也可以选择不带int形参的函数做前++

3.【例4.6】在【例4.5】的基础上增加后++运算符重载函数

#include 
using namespace std;

class Time 
{
	public:
		Time() 
		{
			hour = 0;
			minute = 0;
			sec = 0;
		}
		Time(int h, int m, int s): hour(h), minute(m), sec(s) {}
		Time operator++();	//前++
		Time operator++(int);	//后++
		void display() 
		{
			cout << hour << ":" << minute << ":" << sec << endl;
		}
	private:
		int hour;
		int minute;
		int sec;
};

Time Time::operator++() 
{
	sec++;
	if (sec >= 60) 
	{
		sec = sec - 60;
		minute++;
		if (minute >= 60) 
		{
			minute = minute - 60;
			hour++;
			hour = hour % 24;
		}
	}
	return *this;
}

Time Time::operator++(int) 
{
	Time temp(*this);	//保存修改前的对象做返回值
	++(*this);
	return temp;
}

int main() 
{
	Time time1(21, 34, 59), time2;
	cout << "time1:";
	time1.display();
	++time1;
	cout << "++time1:";
	time1.display();
	time2 = time1++;
	cout << "time1++:";
	time1.display();
	cout << "time2:";
	time2.display();
	return 0;
}

【C++面向对象程序设计】CH4 运算符重载_第5张图片

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

1.含义

  • cin和cout分别是istream类和ostream类的对象。C++已经对>>和<<移位运算符进行了重载,使它们分别成为流提取运算符和流插入运算符。用来输入或输出C++的标准类型数据,所以要用#includeusing namespace std;把头文件包含到程序中
  • 用户自定义类型的数据不能直接用<<和>>输出和输入,如想用他们进行输入或输出,程序员必须对他们重载
  • 重载函数原型的格式如下。从格式上看,>>重载函数和<<重载函数只能定义为友元函数,不能定义为成员函数,因为函数有两个形参,并且第一个形参不是自定义类型
istream & operator >>(istream&,自定义类&);
ostream & operator <<(ostream&,自定义类&);

2.重载流插入运算符“<<”

(1)【例4-7】在【例4-2】的基础上用<<重载函数输出复数

#include 
using namespace std;

class Complex 
{
	public:
		Complex() 
		{
			real = 0;
			imag = 0;
		}
		Complex(double r, double i) 
		{
			real = r;
			imag = i;
		}
		Complex operator +(Complex &c2);
		friend ostream &operator <<(ostream &, Complex &);
		void display();
	private:
		double real;
		double imag;
};

Complex Complex::operator +(Complex &c2) 
{
	Complex c;
	c.real = real + c2.real;
	c.imag = imag + c2.imag;
	return c;
}

ostream &operator <<(ostream &output, Complex &c) 
{
	output << "(" << c.real << "," << c.imag << "i)" << endl;
	return output;
}

void Complex::display() 
{
	cout << "(" << real << "," << imag << "i)" << endl;
}

int main() 
{
	Complex c1(3, 4), c2(5, -10), c3;
	c3 = c1 + c2;
	cout << c3;
	return 0;
}

(2)说明

分析C++怎样处理“cout<

  • 运算符的左边是ostream的对象cout,右边是程序员自定义类complex的对象c3,语句符合运算符重载友元函数operator<<的形参类型要求,系统调用友元函数,C++把这个语句解释为operator<<(cout,c3);通过形参引用传递,函数中的output就是cout,函数中的c就是c3,函数就变成

{

        cout<<"("<

        return cout;

}

  • return cout是将输出流现状返回。C++规定运算符<<重载函数第一个参数和函数的类型必须是ostream类型的引用,目的是为了返回cout的当前值,以便连续输出

3.重载流提取运算符“>>”

(1)【例4.8】在【例4.7】的基础上增加流提取运算符>>重载函数,用cin>>输入复数,用cout输出复数

#include 
using namespace std;

class Complex 
{
	public:
		Complex() 
		{
			real = 0;
			imag = 0;
		}
		Complex(double r, double i) 
		{
			real = r;
			imag = i;
		}
		Complex operator +(Complex &c2);
		friend ostream &operator <<(ostream &, Complex &);
		friend istream &operator >>(istream &, Complex &);
		void display();
	private:
		double real;
		double imag;
};

Complex Complex::operator +(Complex &c2) 
{
	Complex c;
	c.real = real + c2.real;
	c.imag = imag + c2.imag;
	return c;
}

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

istream &operator >>(istream &input, Complex &c) 
{
	cout << "请输入复数的实部和虚部:";
	input >> c.real >> c.imag;
	return input;
}

void Complex::display() 
{
	cout << "(" << real << "," << imag << "i)" << endl;
}

int main() 
{
	Complex c1, c2, c3;
	cin >> c1 >> c2;
	c3 = c1 + c2;
	cout << "c1=" << c1 << endl;
	cout << "c2=" << c2 << endl;
	cout << "c3=c1+c2=" << c3 << endl;
	return 0;
}

【C++面向对象程序设计】CH4 运算符重载_第6张图片

(2)说明 

  • 运算符>>重载函数中的形参input是istream类对象引用,在执行cin>>c1时,调用operator>>函数,将cin引用传递给input,input是cin的别名,同样c是c1的别名。因此,input>>c.real>>c.imag;相当于cin>>c1.real>>c1.imag;。函数返回cin的新值,使程序可以用重载函数连续从输入流提取数据给complex类对象
  • 程序逻辑上是正确的,但还有缺陷,如果输入的虚部是负数时,输出形式变成:c2=(4+-10i),在负数前面多个正号,可以对程序稍作修改
ostream &operator <<(ostream &output, Complex &c) 
{
	ouput<<"("<=0)
		ouput<<"+";
	output<< c.imag << "i)" << endl;
	return output;
}
  • 从本章例子中可以注意到,在运算符重载中使用引用参数的重要性,用引用形在再调用函数时,通过传递地址的方式让形参成为实参的别名,不用生成临时变量,减少了时间和空间的开销。此外,如重载函数的返回值是对象引用时,返回的是对象,他可以出现在赋值号的左侧而成为左值,可以被赋值或参与其他操作(如保留cout流的当前值以便能连续使用<<输出)

你可能感兴趣的:(C++面向对象程序设计,java,jvm,前端,1024程序员节)