1、面向对象程序设计语言C+,电子科技大学计算机学院,1,第五章 运算符重载,当在使用一种程序设计语言编写程序时,我们不仅要设计新的数据类型,同时还要为新类型设计运算。一般地,用户定义类型的运算都是用函数的方式实现的。而在一般情况下,一种类型的运算都是用运算符表达的,这很直观,语义也简单。但是如果直接将运算符作用在用户定义类型之上,那么编译器将不能识别运算符的语义。因此,在这种情况下,我们需要一种特别的机制来重新定义作用在用户定义类型上的普通运算符的含义。这就是运算符重载的简单概念。,2,第五章 运算符重载,其实在C编译器里早就存在简单的运算符重载的概念了。考虑整型和浮点型两种加法运算:int a
2、 = 1, b; b = a + 2;float c = 1.0, d; d = c + 2.0;在上面的两条加法语句中,都用到了运算符“+”。这符合我们数学常识。但对于C编译器来说,这两个加法却被翻译成不同的指令。这就是说,“+”运算符至少具有两种不同的解释(实现代码)。此时我们说, “+”运算符被重载了。遗憾的是,C仅支持很少量的运算符重载。,3,第五章运算符重载,C+扩充了C运算符重载的功能,允许已存在的预定义运算符在不同的上下文中做出不同的解释。当两个字符串类进行连接的时候,我们可能用到这样的方式: class String public:String string_cat(Strin
3、g); ; String str1, str2, str3; str3 = str1.string_cat(str2);,4,第五章运算符重载,显然,这不如str3 = str1 + str2 简单明了。但问题是,C+编译器只知道+运算符是作用在整型和浮点型数据之上的,它不知道在上述表达式中+运算符的确切含义。因此,在设计类String时,我们必须增加“+”运算符的含义。这样一来,语义变得更加清晰和易懂。,5,第五章 引论,在原来预定义的运算符含义的基础上,再增加对于某个用户定义类型的对象进行操作的新的含义。这就是运算符重载。运算符重载后,其优先级和结合性不变。,6,第五章 引论,考虑复数的加
4、法运算: class Complex double re, im; public:Complex(double r, double im) re=r; im=i;Complex() re = 0.0; im = 0.0; Complex add_Complex(Complex c)Complex temp; temp.re = re + c.re; temp.im = im + c.im;return temp; ;,7,/整个函数体也可简化为return Complex(re+c.re, im+c.im);,第五章 引论,void main( ) Complex obj1(1,2),obj2
5、(3,4); Complex obj3=obj1.add_Complex(obj2); 能不能将2个复数相加表示为:obj1+obj2 呢?,9,第五章 引论,5.1 重载运算符C+提供了一种将标准定义的运算符用在用户自定义类型上的方法,称为运算符重载。C+约定,如果一个成员函数的函数名字是特殊的,即由关键字operator加上一个运算符构成,如operator+。obj1.operator+(obj2) 称为函数operator+()的显示调用形式;obj1+obj2称为函数operator+()的隐式调用形式;函数operator+()称为运算符重载函数。,10,第五章 引论,考虑复数的加
6、法运算: class Complex double re, im; public:Complex(double r, double im) re=r; im=i;Complex() re = 0.0; im = 0.0; Complex operator+ (Complex c)Complex temp; temp.re = re + c.re; temp.im = im + c.im;return temp; ;,11,第五章 引论,5.1 重载运算符 void main( ) Complex obj1(1,2),obj2(3,4); Complex obj3=obj1.+obj2; obj
7、1+obj2 等价于:obj1.operator+(obj2),12,第五章 引论,5.1.1运算符重载的语法形式在C+中,运算符通过一个运算符重载函数进行重载。运算符重载可以采用成员函数和友元函数两种重载方式,语法形式如下: (1)运算符重载函数为成员函数的语法形式为:type Class_Name:operator(参数表) /相对于Class_Name类而定义的操作 其中,type是返回类型,“”是要重载的运算符符号,Class_Name是重载该运算符的类的类名,函数名operator。,13,第五章 引论,5.1.1运算符重载的语法形式 (2)运算符重载函数为友元函数的语法形式为:ty
8、pe operator(参数表)/相对于该类而定义的操作,14,第五章 引论,5.1.2 重载运算符规则 (1) 只有少数的C+语言的运算符不能重载: : # ?: . .*,15,第五章 引论,5.1.2 重载运算符规则 (2)只能对已有的C+运算符进行重载。 (3)重载运算符时,不能改变它们的优先级,不能改变它们的结合性,也不能改变这些运算符所需操作数的数目。 (4)重载运算符的函数不能有默认的参数,否则就改变了运算符所需要的操作数的数目。,16,第五章 引论,5.1.2 重载运算符规则(5)重载的运算符必须和用户自定义类型的对象一起使用,其参数至少应用一个是类对象(或类对象的引用)。 (
9、6)用于类对象的运算符一般必须重载,有两个运算符:赋值运算符=和地址运算符&可以不必重载,17,第五章 引论,5.1.2 重载运算符规则 (7)重载运算符时,可以让运算符执行任意的操作,比如将运算符重载为运算但这样违背了运算符重载的初衷,降低程序可读性,使人无法理解程序功能。因此,应当使重载运算符的功能类似于该运算符作用于标准类型数据时所实现的功能。如果不能建立运算符的这种习惯用法,应该采用函数调用方法,以免造成阅读困难(除非程序员故意如此)。,18,第五章 引论,5.1 重载运算符 5.1.3 一元和二元运算符 1.一元运算符一元运算符,不论是前缀还是后缀,都需要一个操作数。暂时不区分前缀和
10、后缀的重载,(5.1.5节:前缀和后缀重载的区别)。,19,第五章 引论,5.1.3 一元和二元运算符 对任意一元运算符,有: (1)成员函数重载运算符 type Class_Name:operator( ) 设obj为Class_Name的类对象,则: 显式调用方式: obj.operator() 隐式调用方式: obj 或者 obj,20,成员函数,Complex Complex:operator!() Complex temp;temp.re=-re;temp.im=-im;return temp; ,第五章 引论,5.1.3一元和二元运算符 (2)友元函数重载运算符type opera
11、tor(Class_Name Cobj) 显式调用方式:operator(obj) 隐式调用方式: obj 或者 obj (例5-5/6),22,友元函数,Complex operator!(const Complex ,第五章 引论,5.1.3一元和二元运算符 2.二元运算符 (1)成员函数重载运算符type Class_Name:operator(Class_Name Cobj) 显式调用方式: obj1.operator(obj2) 隐式调用方式: obj1 obj2,24,第五章 引论,5.1.3一元和二元运算符 (2)友元函数重载运算符type operator (Class_Nam
12、e Cobj1,Class_Name Cobj2) 显式调用方式: operator (obj1,obj2) 隐式调用方式: obj1 obj2 (例5-7/8),25,成员函数,Complex Complex :operator+(const Complex ,友元函数,Complex operator+(const Complex ,第五章 引论,5.1.3一元和二元运算符 3运算符重载为成员函数和友元函数的选择建议,28,第五章 引论,5.1 重载运算符 5.1.4 重载+和-的前缀和后缀方式 以+为例: 对于前缀方式+obj: 成员函数重载Class_Name Class_Nam:op
13、erator+( ); 友元函数重载Class_Nam operator+(Class_Nam ,29,成员函数,Complex ,第五章 引论,5.1 重载运算符 5.1.4 重载+和-的前缀和后缀方式 对于后缀方式obj+: 成员函数重载Class_Nam Class_Nam:operator+(int); 友元函数重载Class_Nam operator+(Class_Nam 这时,第二个参数(int)只是一个占位符号,用来区分该重载函数是前缀方式还是后缀方式,在函数内部不需要也不能使用。通常用0表示。(例5-11/12),31,有元函数,Complex ,第五章 引论,5.1 重载运算
14、符 5.1.5 重载赋值运算符 赋值运算符“=”可以被重载,用户可以定义自己需要的重载“=”的运算符重载函数。重载了的运算符函数operator=不能被继承,而且它必须被重载为成员函数,一般重载格式为:X X:operator=(const X & from) /复制X的成员 拷贝构造函数和赋值运算符都是把一个对象的数据成员拷贝到另一对象。它们的区别是,拷贝构造函数要创建一个新对象,而赋值运算符则是改变一个已存在的对象的值。(例5-13),33,第五章 引论,5.1.6重载运算符( )和 运算符“()”和运算符“”不能用友元函数重载,只能采用成员函数重载。 1重载函数调用运算符( ) 对应的运
15、算符重载函数为operator()() 设obj为类Class_Nam的一个对象,则表达式 obj(arg1,arg2) 可被解释为 obj.operator()(arg1,arg2) (例5-14),34,第五章 引论,5.1.6重载运算符( )和 2重载下标运算符 相应的运算符重载函数为operator () 设xobj为类X的对象,则表达式 xobjarg 解释为 xobj.operator (arg) (例5-15) 需要注意,这里的参数arg必须是一个整型表达式。,35,考虑一个整数数组数组的大小在定义时初始化;可以将对象(名)直接作为数组(名);使用而且其大小在运行时可以改变。,c
16、lass Arrayint * p;int size;public:Array(int num) size=(num6)?num:6;p=new intsize; Array( ) delete p; ,int ,void expend(int off) int * pi;pi=new int size+off;for (int n=0;nsize;n+)pin=pn;delete p;p=pi;size+=off; ,void contract(int offset) size=size-offset; ; /类结束,void main( ) int num=0;Array a_Array(
17、10);for (;num 10; num +)a_Arraynum= num; a_Array10=10;for (n=0; n=10; n+)couta_Arrayn; ,第五章 引论,5.1.7重载输入和输出运算符 在标准文件iostream.h中,有2个标准的类类型:istream和ostream(流库的介绍参考第八章)。对于预定义类型,用户可以方便地使用运算符“”和“”进行输入和输出。,42,输出运算符“ void main( ) int num=10;ostream 则输出10,输入运算符“”的第一个操作数是cin,它实际上是标准类类型istream的对象的引用(它的定义在文件io
18、stream中)。 若在程序中,用户自己定义 一个istream的对象的引用,也可以直接使用运算符“”。,第五章 引论,5.1.7重载输入和输出运算符 对于类类型,用户可以重载运算符“”和“也有类似的情况,但要注意第二个参数必须是对象的引用 。(例5-16),45,class Complex double re,im; public: friend ostream ,void main( ) Complex obj1,obj2;coutobj1obj2; coutobj1“ ”obj2;,第五章 引论,5.2 new和delete 5.2.1 为一个对象动态分配存储区 可以为任何类型(除voi
19、d类型外)的数据动态分配存储空间; 当为对象动态分配存储区时,实际上是通过调用构造函数实现的; 当释放动态存储区时,是通过调用析构函数来实现的。动态存储区的分配和释放一定要成对出现。(例5-17),48,第五章 引论,5.2 new和delete 5.2.2 为一个数组动态分配存储区 void main() Complex *cp;int size;cinsize;cp=new Complexsize; ,49,第五章 引论,5.2.3 指针悬挂问题 指针悬挂就是指:使用new申请的存储空间无法访问,也无法释放。 造成指针悬挂原因是对指向new申请的存储空间的指针变量进行赋值修改。char *
20、 p,* q;p=new char10; q=new char10;strcpy(p, “abcd“);q=p;delete p; delete q; /错误,同样的空间被释放两次,50,对象的拷贝有两种方式:初始化和赋值。将一个对象的数据成员对应地赋值给另一个对象的数据成员; 如果一个类包含有指针类型的数据成员,那么,该类的对象的拷贝(初始化和赋值)就可能会有问题。对象的指针成员直接赋值,就可能导致指针悬挂。 需要重新定义拷贝构造函数和超载“=”的函数,class String char * pstr;int sz;public:String (int s) pstr=new char sz
21、=s; String ( ) delete pstr; ;,void main( ) String str1(10);String str2=str1;String str3(10);str3=str1; 执行该程序,会出现问题。Why?,缺省的拷贝构造函数为:String: String(const String ,缺省的超载”=”函数为:String String :operator=(const String ,集合的实现,C+中没有集合类型,可以定义一个集合类来实现。与集合相关的操作有:加入一个元素到集合中判断一个元素是否在集合中可以取两个集合的交集和并集删除集合中的一个元素可以扩充集
22、合可以输入、输出集合的所有元素集合可以互相复制可以清空一个集合,等,class Set int * p; int size; int have; public:Set(int a);Set( );,void expend(int offset); void add(int ); int In(int ); void delete_item(int); Set operator +(Set ); Set operator *(Set t);,friend void operator(ostream ,Set Set:operator*(Set t)Set temp(size);for (int
23、a=0;a-1)temp.add(pa);return temp;,Set Set:operator+( Set t)Set temp(size+t.size);int a;for (a=0;ahave;a+)temp.add(pa);for (a=0;at.have;a+)if (In(t.pa)=-1)temp.add(t.pa);return temp; ,Set:Set (const Set ,Set Set:operator=(const Set ,第五章 引论,5.3 类型转换运算符的重载类型转换是将一种类型的值转换为另一种类型的值。对于类类型,是否也存在一种类型转换机制,使得类对
24、象之间能进行类型转换?一般,类对象的类型转换可由构造函数和转换函数实现。这种转换常称为用户定义的类型转换或类类型转换。在C+中,类被视为用户定义的类型,可以像系统预定义类型一样进行类型转换。,64,第五章 引论,5.3 类型转换运算符的重载 C+语言允许的类型转换有4种:标准类型-标准类型标准类型-类类型类类型-标准类型类类型-类类型,65,第五章 引论,5.3 类型转换运算符的重载 5.3.1 标准类型转换为类类型 可以通过自定义的重载赋值号“=”的函数和构造函数实现标准类型-类类型;它们都需要有标准类型的参数。 具有标准类型参数的构造函数说明了一种从参数类型到该类类型的转换。(例5-24/
25、25),66,class INTEGER int num;public:INTEGER (int i);INTEGER (const char * str); ;INTEGER:INTEGER(int i) num=i; INTEGER:INTEGER(const char *str) num=strlen(str); void main() INTEGER obj1= 1; INTEGER obj2=“Cheng“;obj1=20; /obj1.operator=(INTEGER(20);obj2=“Du“ ; /* obj2.mem_fun(INTEGER(3);*/ ,第五章 引论,5.
26、3 类型转换运算符的重载 5.3.2 类类型转换函数 带一个参数的构造函数可以进行类型转换,但是它的转换功能很受限制。 引入一种特殊的成员函数:类型转换函数,它在类对象之间提供一种类似显式类型转换的机制。,68,第五章 引论,5.3 类型转换运算符的重载 5.3.2 类类型转换函数 C+允许程序员为类定义一个类型转换函数,它的语法是Class_Name:operator type( ) return (type类型的实例); 类型转换函数没有参数,没有返回类型,但这个函数体内必须有一条返回语句,返回一个type类型的实例。,69,第五章 引论,5.3 类型转换运算符的重载 5.3.2 类类型转
27、换函数 class INTEGER int num; public:INTEGER(int anint=0) num=anint; operator int( ) return num; ;,70,void main( ) INTEGER obj(12);int anint=int(obj);anint=(int)obj;anint=obj; / obj.operator int( ),实现一个Point类(数据成员为一个点在两维直角坐标系内的坐标);实现一个Vector类(数据成员为一个点在两维极坐标系内的坐标);要求两个类的对象能互相赋值。,#include #include const
28、double PI=3.14; class Vector;,class Point friend Vector;int x; int y;public:Point(int initx=0,int inity=0)x=initx;y=inity;,operator Vector( );friend ostream ,class Vectorfriend Point ;double p; double seta;public: Vector(double initp=0,double initseta=0)p=initp;seta=initseta;,operator Point( );friend ostream ,Vector:operator Point( ) Point Pobj;Pobj.x=p*cos(seta);Pobj.y=p*sin(seta);return Pobj; ,Point:operator Vector( )Vector Vobj;Vobj.p=sqrt(x*x+y*y);if (x=0) if (y0) Vobj.seta=PI/2; elseif (y0 ) Vobj.seta=3*PI/2;else Vobj.seta=0; else Vobj.seta=atan(y/x); return Vobj; ,