Transcript Document

第7章 运算符重载
7.1运算符重载概述
7.1.1 运算符重载的好处
运算符重载允许C/C++的运算符在用户定义类型
(通常指类)上拥有一个用户定义的意义。
当你定义一个类之后,系统是不会对你自己定义
的类进行算术运算等运算的,只有当你对相应的运
算府重载之后,你才可以对两个或者多个对象进行
运算。
运算符操作函数是重载函数,比如对操作符+ 系
统中可能有如下原型():
int operator+(int a,int b);
short operator+(short a,short b);
double operator+(double a,double b);
long operator+(long a,long b)
...
定义一个简化的复数类complex:
class complex {
public:
double real,imag;
complex(double r=0,double i=0)
{ real=r; imag=i;}
};
若要把类complex的两个对象com1和com2加在一起,下面的
语句是不能实现的:
main()
{
complex com1(1.1,2.2),com2(3.3,4.4),total;
total=com1+com2;
//错误
//…
return 0;
}
若要将上述类complex的两个对象相加,只要编写一个运算符
函数operator+(),如下所示:
complex operator+(complex om1,complex om2)
{
complex temp;
temp.real=om1.real+om2.real;
temp.imag=om1.imag+om2.imag;
return temp;
}
我们就能方便地使用语句:
total=com1+com2;
将类complex的两个对象com1和com2相加。
7.1.2运算符重载的规则
(1)重载功能应当与原有功能相类似;
(2)程序员不能臆造新的运算符来扩充C++
语言;
(3)“.”、“*”、“::”、sizeof和“?:”
这五个运算符不能重载;
(4)不能改变运算符的操作个数;
(5)不能改变运算符原有的优先级;
(6)不能改变原有的结合性;
(7)不能改变运算符对预定义类型数据的操作
方式。
7.2 运算符重载函数的两种形式
运算符重载一般采用如下二种形式:一是定
义为它将要操作的类的成员函数,二是定义
为类的友元函数。
7.2.1 友元运算符函数
在C++中,可以把运算符重载函数定义成某
个类的友元函数,称为友元运算符函数。
1. 友元运算符函数定义的语法形式
友元运算符函数的原型在类的内部声明格式如下:
class X {
//…
friend 返回类型 operator运算符(形参表);
//…
}
在类外定义友元运算符函数的格式如下:
返回类型 operator运算符(形参表)
{
函数体
}
2. 双目运算符重载
当用友元函数重载双目运算符时,两个
操作数都要传递给运算符函数。
例7.1 用友元运算符函数进行复数运算。
一般而言,如果在类X中采用友元函数
重载双目运算符@,而aa和bb是类X的两个
对象,则以下两种函数调用方法是等价的:
aa @ bb;
// 隐式调用
operator @(aa,bb); // 显式调用
3. 单目运算符重载
用友元函数重载单目运算符时,需要一个
显式的操作数。
例7.2 用友元函数重载单目运算符“-”。
例 7.3 用 友 元 函 数 重 载 单 目 运 算 符
“++”(传递对象参数)
例7.4 用友元函数重载单目运算符“++”
(传递引用参数)
使用友元函数重载单目运算符“++”
或“--”时,采用引用参数传递操作数。
一般而言,如果在类X中采用友元函数
重载单目运算符@,而aa是类X的对象,则以
下两种函数调用方法是等价的:
@aa;
// 隐式调用
operator@(aa);
// 显式调用
说明:
(1)运算符重载函数operator@()可以返回任
何类型;
(2)可以不保持原有含义;
(3)不能定义新的运算符;
(4)编译器根据参数类型和个数决定调用哪
个重载函数;
(5)不能用友元重载的运算符是:=,(),[],->;
(6)单目运算符“-”可不改变操作数自身的
值,所以在重载单目运算符“-”的友元函数的
原型可写成:
frinend AB operator-(AB obj);
7.2.2 成员运算符函数
在C++中,可以把运算符函数定义成某
个类的成员函数,称为成员运算符函数。
1. 成员运算符函数定义的语法形式
成员运算符函数的原型在类的内部声明格式如下:
class X {
//…
返回类型 operator运算符(形参表);
//…
}
在类外定义成员运算符函数的格式如下:
返回类型 X::operator运算符(形参表)
{
函数体
}
2. 双目运算符重载
对双目运算符而言,成员运算符函数的
形参表中仅有一个参数,它作为运算符的
右操作数,此时当前对象作为运算符的左
操作数,它是通过this指针隐含地传递给
函数的。
例7.5 采用成员运算符函数来完成例
7.1中同样的工作。
一般而言,如果在类X中采用成员函数重
载 双 目 运 算 符 @, 成 员 运 算 符 函 数
operator@ 所需的一个操作数由对象aa通
过this指针隐含地传递,它的另一个操作数
bb在参数表中显示,aa和bb是类X的两个对
象,则以下两种函数调用方法是等价的:
aa @ bb;
// 隐式调用
aa.operator @(bb); // 显式调用
3. 单目运算符重载
对单目运算符而言,成员运算符函数的参
数表中没有参数,此时当前对象作为运算符
的一个操作数。
例7.6 重载单目运算符“++”。
一般而言,采用成员函数重载单目运算符时,
以下两种方法是等价的:
@aa;
// 隐式调用
aa.operator@(); // 显式调用
成员运算符函数operator @所需的一个操
作数由对象aa通过this指针隐含地传递。因
此,在它的参数表中没有参数。
7.2.3 成员运算符函数与友元运算符函数的比较
(1) 对双目运算符而言,成员运算符函数带有一个参数,而友
元运算符函数带有两个参数;对单目运算符而言,成员运算符
函数不带参数,而友元运算符函数带一个参数。
(2) 双目运算符一般可以被重载为友元运算符函数或成员运
算符函数,但有一种情况,必须使用友元函数。例7-7。
(3) 成员运算符函数和友元运算符函数可以用习惯方式调用,
也可以用它们专用的方式调用,表7-1列出了一般情况下运算
符函数的调用形式。
(4) C++的大部分运算符既可说明为成员运算符函数,又可说
明为友元运算符函数。究竟选择哪一种运算符好一些,没有定
论,这主要取决于实际情况和程序员的习惯。
7.2.3 成员运算符函数与友元运算符函数的比
较
表7-1 运算符函数调用形式
习惯形式
a+b
友元运算符函数调用形式
成员函数运算符调用形式
operator+(a,b)
a.operator+(b)
-a
operator-(a)
a.operator-()
a++
operator++(a,0)
a.operator++(0)
7.3 几个常用运算符的重载
7.3.1 单目运算符“++”和“--”的重载
在C++中,可以通过在运算符函数参数表中是否插入关
键字int来区分前缀和后缀这两种方式。
◆对于前缀方式++ob,可以用运算符函数重载为
ob.operator ++();
// 成员函数重载
或 operator ++ (X& ob);
// 友元函数重载,
其中ob为类X对象的引用
◆ 对于后缀方式ob++,可以用运算符函数重载为
ob.ooperator ++(int);
// 成员函数重载
或 operator++(X& ob,int); // 友元函数重载
在调用后缀方式的函数时,参数int一般被传递给值0。
7.3.2 赋值运算符“=”的重载
对任一类X,如果没有用户自定义的赋值运算符函数,那么系
统自动地为其生成一个缺省的赋值运算符函数,定义为类X中的
成员到成员的赋值,例如:
X &X::operator=(const X& source)
{
//…成员间赋值
}
若obj1和obj2是类X的两个对象,obj2已被创建,则编译程序
遇到如下语句:
obj1=obj2;
就调用缺省的赋值运算符函数,将对象obj2的数据成员的值
逐个赋给对象obj1的对应数据成员中。
1.指针悬挂问题
在某些特殊情况下,如类中有指针类型时,
使用缺省的赋值运算符函数会产生错误。
例7.10
例7.10 使用缺省的赋值运算符函数产生错误的例子。
#include <iostream.h>
#include<string.h>
class string {
public:
string(char *s)
{
ptr=new char[strlen(s)+1];
strcpy(ptr,s);
}
~string()
{ delete ptr; }
void print()
{ cout<<ptr<<endl; }
private:
char *ptr;
};
void main()
{
string p1("book");
string p2("pen");
p1=p2;
cout<<"p2:";
p2.print();
cout<<"p1:";
p1.print();
}
2. 重载赋值运算符解决指针悬挂问题
为了解决上述使用缺省的赋值运算符所
遇到的指针悬挂问题,必须重载赋值运算符,
使得对目标对象数据成员指针的赋值,是把
原对象指针ptr所指向的内容传递给它,而不
是简单地传递指针值。
例7.11 重载赋值运算符解决指针悬挂问题。
#include <iostream.h>
#include<string.h>
class string {
public:
string(char *s)
{ ptr=new char[strlen(s)+1]; strcpy(ptr,s);}
~string(){ delete ptr; }
void print(){ cout<<ptr<<endl; }
string& operator=(const string&);//声明赋值运算符重载函数
private:
char *ptr;
};
string& string::operator=(const string& s) // 定义赋值运算符
重载函数
{ if (this==&s) return *this;
// 防止s=s的赋值
delete ptr;
// 释放掉原区域
ptr=new char[strlen(s.ptr)+1]; // 分配新区域
strcpy(ptr,s.ptr);
// 字符串拷贝
return *this;
}
void main()
{ string p1("book");
string p2("pen");
p1=p2;
cout<<"p2:"; p2.print();
cout<<"p1:"; p1.print();
}
7.3.3 类型转换运算符重载
转换运算符的声明形式为:
operator 类型名();
它没有返回类型,因为类型名就代表了
它的返回类型,故返回类型显得多余。
转换运算符将对象转换成类型名规定的
类型。转换时的形式就像强制转换一样。 例
7-12
7.3.4 下标运算符重载
C++语言中数组下标越界系统无法判断,
可重载[ ],从而实现这类判断。
重载下标运算符必需是成员函数。
例7.13
7.3.5 函数调用运算符“()”重载
在C++中,在重载函数调用运算符
“()”时认为它是一个双目运算符,例如
X(Y)可以看成:
( )--双目运算符 X--左操作数 Y--右操作数
X(5)可被解释为:X.operator()(5);
对函数调用运算符重载只能使用成员函
数,其形式如下:
返回类型 类名::operator()(参数表)
{
//函数体
}
例7-14函数调用运算符重载
#include <iostream.h>
class fun
{
public:
double operator()(double x,double y,double z) const;
};
double fun::operator()(double x,double y,double z) const
{
return (x*x+y*y)*z;
}
void main()
{
fun f;
cout<<f(3.0,2.0,5.0)<<endl;
}