C++程序设计 第4章
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
第4 章重载
4.1 函数的重载
4.2 运算符重载
返回主目录
4.1 函数的重载
4.1.1 函数重载的定义
在第1章介绍C语言与C++之间的关系时,提到C++语言中允许函数重载,即可以有多个同名但参数形式不完全相同的函数。
它允许程序员定义作用相同但参数形式不同的函数,在调用时无须记忆多个函数名,而且可以增加程序的可读性。
例如,求两个数中最大值的函数max,不管其参数的整数类型、实数类型、字符串,都可以使用同名函数来实现,调用时只需使用max就可以了,编译器将根据实参的类型判断应该调用哪一个函数。
函数重载无须特别声明,只要所定义的函数与已经定义的同名函数形参形式不完全相同,C++编译器就认为是函数的重载。
例如下面的两个函数:
void GetTime(long *ticks);
void GetTime(int*hours,int*minutes,int*seconds);
作用相同,都是返回当前时间,只是其返回的时间表示形式不同,使用相同的函数名,但其参数形式不同。
在使用函数重载时要注意,如果某个函数参数有缺省值,必须保证其参数缺省后调用形式不与其它函数混淆。
例如下面的重载是错误的:
int f(int a, float b);
void f(int a, float b,int c=0);
因为第二个函数缺省参数c后,其形式与第一个函数参数形式相同。
下面的函数调用语句:
f(10, 2.0);
具有二义性,既可以调用第一个函数,也可以调用第二个函数,编译器不能根据参数的形式确定到底调用哪一个。
类的函数成员同样也可以重载。
例如前面介绍的构造函数,一个类可以具有多个不同参数形式的构造函数。
类的函数成员的重载与全局函数的重载方法相同。
4.1.2 一个简单的例子
下面实现前面提到的max函数的重载版本:int max(int a,int b)
{
if(a>b)
return a;
else
return b;
}
float max(float a, float b)
{
if(a>b)
return a;
else
return b;
}
char * max(char *a, char *b) {
if(strcmp(a,b)>0)
return a;
else
return b;
}
这里定义了三个名为max的函数,它们的函数原型不同,C++编译器在遇到程序中对max函数的调用时将根据参数形式进行匹配,如果找不到对应的参数形式的函数定义,将认为该函数没有函数原型,编译器会给出错误信息。
4.1.3 在C++中编译C程序
在使用C++语言进行程序开发时,由编译器将源程序文件编译为目标文件,并进行连接。
为了实现函数的重载,C++语言编译器在编译时对函数的处理与C语言编译器采取了不同的方法。
C语言编译器在处理函数时只需保留函数名的信息,即可在连接时将不同的函数区别开来;而C++编译器在编译时生成一个包含函数名和函数参数信息的符号来取代原来的函数名,以保证在连接时能够找到正确的函数。
但是,有时程序员可能希望编译器对某些函数使用C编译的习惯来进行处理。
例如,需要在C++程序中调用C语言编译的函数库中的函数,此时可以采取如下的方法来编写包含该函数库中的函数原型声明的头文件。
extern "C" {
/*在此处写上函数的原型声明*/
编译器将以C语言的编译方式处理该函数的名字。
如果要保证该头文件能够同时被C编译器和C++编译器正确编译,可采用下面的方法:
#ifdef__cplusplus
extern "C"{
#endif
/*在此处写上函数的原型声明*/
#ifdef__cplusplus
}
#endif
4.2 运算符重载
4.2.1 运算符重载的基本方法
C++语言中提供了运算符重载的机制,程序员可以对自定义的数据类型使用C++语言本身提供的标准运算符进行运算,运算的方式由重载的特殊函数来完成。
这些函数有特殊的函数名,可使用运算符方式进行调用,当然也可以使用传统的函数调用的方式进行调用。
使用运算符重载可以提高程序的可读性。
例如,前面讲的整形数据元素集合类的例子中定义了一个函数Union来实现集合的并集运算,如果使用+运算符来实现集合的并集运算,如用A+B表示两个集合A、B的并集运算,则程序会具有更好的可读性。
C++语言中常见的单目和双目运算符大部分都可以重载。
下面以一个简单类的运算的例子来介绍运算符重载的基本方法。
先来看看如何在类的定义中声明一个重载的运算符:
class Point{
int x, y;
public:
Point(int x,int y){Point::x=x;Point::y=y;}
Point operator +(Point p)
{return Point(x+p.x, y+p.y);}
Point operator –(Point p)
{return Point(x-p.x, y–p.y);}
};
在Point类中重载了运算符+、-,实际上定义了两个函数成员,它们的名字是operator +与operator –。
定义了这两个函数成员之后,编译器在遇到下面的程序段时,将调用这两个函数成员。
Point p1(10, 0), p2(0, 10);
Point p3=p1+p2;
Point p4=p1–p2;
对这里的表达式p1+p2、p1–p2,编译器将其解释为:p1.operator +(p2);
p1.operator –(p2);
从这个例子中可以看出,在重载某个运算符时,实际上就是定义了一个函数,函数名为operator 运算符。
对于重载的运算符可以使用运算符方式调用,也可以使用函数调用方式调用。
通常对于双目运算符,重载时一般声明为类的友元来提高程序的可读性。
例如对上面的类Point,可以采用下面的方法来实现:
class Point{
int x, y;
public:
Point(int x,int y){Point::x=x;Point::y=y;} friend Point operator +(Point p,Point q) {return Point(p.x+q.x, p.y+q.y);} fridend Point operator –(Point p,Point q) {return Point(p.x–q.x,p.y–q.y);}
};
从函数原型中可以直接看出,+、–运算符为双目运算符,而重载为函数成员时,函数原型中似乎只有一个参数,影响了程序的可理解性。
当然有时某些运算符必须重载为友元而不是成员,主要是因为表达式中运算符的顺序问题,因为重载为函数成员时类对象本身作为第一个运算参数,例如流输出运算符<<在重载时必须声明为友元,这一点在后面的章节中将详细介绍。
可以重载的双目运算符见表4.1。
单目运算符在重载时应该声明为函数成员而不是友元,常用的单目运算符有逻辑取反运算符!、取地址运算符&、自增运算符++、自减运算符––等均可以重载。
下面的例子中实现了一个计数器类,其中重载了单目运算符++和––。
表4.1 可重载的运算符
运算符名称运算符名称,逗号运算符<小于
!=不等<<左移
%取模<<=左移/赋值
%=取模/赋值<=小于等于
&=赋值
&&逻辑与==等于
&=按位与/赋值>大于
*乘>=大于等于
*=乘/赋值>>右移
+加>>=右移/赋值
+=加/赋值
^异或
-减^=异或/赋值
-=减/赋值
|按位或
->成员选取|=按位或/赋值/除||逻辑或
/=除/赋值
#include <iostream.h> class counter{
unsigned int value; public:
counter( ){value=0;}
void operator++( );
void operator––( );
void Print( ){cout<<value;} };
void counter::operator++( )
{
value++;
}
void counter::operator––( )
{
value––;
}
void main( )
{
counter counter1;
counter1++;
++counter1;
counter1.Print( ); }
自增、自减运算符在C语言中作前缀和后缀是不同的,但是在重载时,并不区分其定义为前缀或后缀运算符。
上面例子中的counter1++在Borland C++3.1环境中编译时,将给出如下的警告信息:
Overloaded prefix 'operator ++' used as a postfix operator
Borland C++3.1编译器将该句编译为++counter1,即前缀和后缀是一样看待的。
另外要注意的一点是,重载运算符并不保持等价规则。
例如,为一个用户定义类型重载了运算符==,并不表示同时重载了!=运算符,程序员必须显式地重载!=运算符方可使用该运算符。
*4.2.2 类型转换运算符
在C与C++语言中,数据的强制类型转换是通过类型转换运算符来实现的。
C++语言预定义了整型、字符型以及指针等数据类型之间相互转换的方法,对于用户自定义的类型可以通过重载类型转换运算符来实现数据类型的转换。
下面的例子为上一节中的counter类重载了类型转换运算符int,利用重载的运算符可以将counter类的对象转换为整型数据。
class counter{
public:
operator int( ){return value;}
//这里省略了类的其它部分的声明
...
};
在上面的程序片断中,没有显式地声明operator int的返回值类型,因为类型转换运算符返回的数据类型肯定为该数据类型,所以无须声明。
重载了类型转换运算符后,既可以通过显式数据类型转换,也可以通过隐含数据类型转换。
如上面例子主函数中的counter1,下面的表达式语句都是正确的:
1/counter1;
1/(int)counter1;
对于第一个表达式,编译器试图将counter1转换为可以与常数1进行除运算的数据类型,而对counter类程序中只定义了将其转换为整型的方法,因此编译器将使用重载的int运算符。
如果counter类的定义像下面这样:
class counter{
public:
operator int( ){return value;}
operator float( ){return value;}
//这里省略了类的其它部分的声明
...
};
则表达式1/counter1将不能正常编译,因为此时该表达式具有二义性,它可以解释为下面的两种情况:
1/(int)counter1;
1/(float)counter1;
此时只能使用显式的数据转换方法,即指定转换为int或float。
上面的例子中通过重载类型转换运算符将自定义的数据类型转换为C++内部数据类型,如果想将内部数据类型转换为自定义的数据类型,例如将一个整型数据转换为counter类的对象,可以通过为类增加一个构造函数来实现。
看下面的例子:
class counter{
unsigned int value;
public:
counter( ){value=0;}
counter(int x){value=x;}
//下面省略类的其它部分
...
};
这里增加了一个构造函数counter(int),则对下面的程序片断:
counter c1;
c1=5;
编译器首先将整型常数5转换为counter类对象,然后复制到c1。
在使用这种类型转换时同样需要注意二义性。
请看下面一个较完整的程序片断:
#include <iostream.h>
class counter{
unsigned int value;
public:
counter( ){value=0;}
counter(int x){value=x;}
void operator++( ){ value++;}
void operator––( ){value––;}
friend counter operator +(counter c1,counter c2); operator int( ){return value;}
void Print( ){cout<<value;}
};
counter operator +(counter c1,counter c2)
{
counter c;
c.value= c1.value+c2.value;
return c;
}
void main( )
{
counter c1,c2;
c1=5+c2;//语句1,有二义性,不能正确编译
c1=5+(int)c2;//语句2,正确,可正确编译
c1.Print();
}
其中语句1具有二义性,不能正确编译,编译时Borland
C++ 3.1给出如下警告信息:
Ambiguity between 'operator +(counter,counter)'and 'counter::operator int( )'
它可以解释成下面的两种形式:
(1) 将5转换为counter类对象,然后调用重载的运算符+与
c2运算。
(2) 将c2转换为整型数据后,与5运算,最后再转换为counter类对象赋值给c1。
如果未重载+运算符,则语句1可以正确编译,将使用上面的第二种转换方法对数据类型进行隐含转换。
语句2显式地使用int运算符将c2转换为整型数据,与5进行加法运算,然后使用隐含的数据转换, 将整型数据转换为counter类对象。
4.2.3 下标和函数调用运算符
下标运算符和函数调用运算符( )在C语言中是两个特殊用途的运算符,在C++语言中它们也可以按照其它运算符完全相同的方式重载,这两个运算符在重载时只能重载为类的函数成员而不能是友元。
下标运算符重载时总是双目的,函数调用运算符可以是单目或双目的。
下面是一个简单的例子:
#include <iostream.h>
#include <string.h>
class Vect{
int xVal,yVal;
public:
Vect(int x,int y){xVal=x;yVal=y;} int operator[ ](char *);
int operator( )(char *);
};
int Vect::operator[](char *s)
{
if(strcmp(s, "x")==0)
return xVal;
if(strcmp(s, "y")==0)
return yVal;
return 0;
}
int Vect::operator( )(char *s)
{
if(strcmp(s, "x")==0)
return xVal;
if(strcmp(s, "y")==0)
return yVal;
return 0;
}
void main( )
{
Vect v1(10, 20);
cout<<v1["x"]<<'\n';
cout<<v1("y")<<'\n';
}
上面的程序定义了一个存储二维向量的类Vect,重载了下标运算符和函数调用运算符,在主函数中使用v1["x"]和v1["y"]来返回v1的x维向量和y维向量。
这种表达式更接近数学中的表达方式,使得程序的可理解性更好。