1、程序设计实习 第十五讲 运算符重载,主讲教师:田永鸿 http:/ http:/ 2008年4月21日,2,回顾:类和对象,类的定义、成员属性、成员函数、类的作用域 对象的创建、存储、访问 构造函数、析构函数 定义、调用时机 特殊的构造函数:复制构造函数、转换构造函数、初始化列表 类的特殊成员:static成员、const成员、引用成员 const对象 成员对象和封闭类 友元 this指针,3,课堂问题,指出下列各题中的错误,并说明如何改正 void Time(int); class Time private: int hour = 12; int minute = 0, second = 0
2、; int Time(int nHour, int nMin, int nSec); 以下关于 this 指针的说法中不正确的是: A. const成员函数内部不可以使用this 指针 B. 成员函数内的this 指针,指向成员函数所作用的对象。 C. 在构造函数内部可以使用this指针 D. 在析构函数内部可以使用 this 指针,构造函数和析构函数不能有返回类型; 成员属性不能在类定义时初始化,const成员函数内部可以使用this 指针:是一个const指针,不能改变this的地址及所指向的值,4,课堂问题,指出下列题中的错误,并说明如何改正 class X X (int i, int
3、j): base(i), rem(base % j) int rem, base; 如下定义和申明中哪些是错误的,如何改正: /example.h class Example public: static double rate = 6.5; static const int nSize = 20; static Time Time(12, 0, 0); ; /example.c #include “example.h” double Example:rate; Time Example:Time;,Base应在rem之前定义; 或rem的初始化时不使用base,直接用i,静态成员变量或成员对
4、象不能在定义时初始化,应在.c程序中以全局变量的方式初始化; 静态const类型则可以,5,课堂问题,以下程序编译、连接都能通过,请写出运行时输出的结果。你认为没有输出的,就写“无输出“ #include using namespace std; class Sample int A; static int B; public: Sample(int a)A=a,B+=a; static void func(Sample s); ; void Sample:func(Sample s) cout“A=“s.A“,B=“Bendl; int Sample:B=0; void main() Sam
5、ple s1(2),s2(5); Sample:func(s1); Sample:func(s2); ,静态成员函数的使用方法。其中的数据成员B是静态数据成员,求B之值是在构造函数中进行的。所以输出为: A=2,B=7 A=5,B=7,6,内容提要,抽象数据类型与运算符重载 两种运算符重载的实现方式 常见的运算符重载 流运算符: 、 自增运算符+、自减运算符- 抽象数据类型的强制类型转换 示例程序 作业,7,抽象数据类型与运算符重载,C+预定义了一组运算符,用来表示对数据的运算 +、-、*、/、%、&、!、|、=、!=、 只能用于基本的数据类型:整型、实型、字符型、逻辑型、 cin和cout使
6、用运算符“”进行流操作时,要求操作数是基本数据类型,8,C+提供了数据抽象的手段,允许用户定义抽象数据类型:类 通过调用类的成员函数,对它的对象进行操作 但是,在有些时候,用类的成员函数来操作对象时,很不方便。例如 对一个群体,按照他们的体重指数进行排序:涉及不同对象中的“体重指数”成员属性 在数学上,两个复数可以直接进行+、-等运算。但在C+中,直接将+或-用于复数是不允许的,抽象数据类型与运算符重载,9,运算符重载,我们希望:对一些抽象数据类型,也能够直接使用C+提供的运算符 程序更简洁 代码更容易理解 例如 bool compareQuata = Bill Jimmy Bill和Jimm
7、y是CMan的两个对象 比较他们的体重指数 complex_a + complex_b complex_a和complex_b是两个复数对象 求两个复数的和,10,对已有的运算符(C+中预定义的运算符)赋予多重的含义,使同一运算符作用于不同类型的数据时导致不同类型的行为 目的是:扩展C+中提供的运算符的适用范围,以用于类所表示的抽象数据类型。同一个运算符,对不同类型的操作数,所发生的行为不同 (5, 10i) + ( 4, 8i) = (9, 18i) 5 + 4 = 9,运算符重载,11,class Complex public: Complex( double r = 0.0, doubl
8、e i= 0.0 ) real = r; imaginary = i; double real; / real part double imaginary; / imaginary part ;,示例:复数类型定义,12,若能对+进行重新定义如下: Complex operator+( const Complex ,示例:复数类型定义,13,对类Complex重载运算符号“+”,示例:复数的运算符重载,class Complex public: Complex( double = 0.0, double = 0.0 ); / constructor Complex operator+( con
9、st Complex ,问题:为什么使成员函数、参数及返回值为常数类型?,/ Overloaded addition operator Complex Complex:operator+( const Complex / enables cascading /函数的返回值定义为const,因为返回的是this指针,Complex x, y( 4.3, 8.2 ), z( 3.3, 1.1 );x = y + z; / y.operator+(z)/x.operator=(y.operator+(z)x = y - z;/ x.operator=(y.operator-(z),16,运算符重载,
10、实质是函数重载:在程序编译时 把指定的运算表达式转换成对运算符函数的调用 把运算的操作数转换成运算符函数的参数 根据实参的类型决定调用哪个运算符函数 C+中运算符重载的例子: “”和“” 是用于移位的运算符,通过C+的标准类库分别被重载为流输入和流输出运算符,17,运算符重载:注意,C+不允许定义新的运算符 通过重载现有的运算符,使它在用于类的对象时具有新类型的含义 重载后运算符的含义应该符合日常习惯 complex_a + complex_b word_a word_b date_b = date_a + n 有时使用函数调用更好 older(student_a, student_b)的语义
11、比student_a student_b更清晰:年龄大小、身材高矮、体型胖瘦、 重载不改变运算符的优先级、结合性、语法结构及参数个数 以下运算符不能被重载:“.”、“.*”、“:”、“?:”、sizeof 教材V2版p.340(V5版p.430)列出了可重载的运算符,18,运算符重载的形式,重载为类的成员函数 return_type operator operator_symbol(argument-list) function-body 重载为类的友员函数 friend return_type operator perator_symbol(argument-list) function-b
12、ody operator_symbol必须是C+中可以重载的运算符符号,例如“+”、“-”、 重载运算符“调用()、下标、成员访问-或者赋值运算符=”时,运算符重载函数必须声明为类的成员函数,19,运算符重载为成员函数,return_type operator operator_symbol(argument-list) function-body argument-list中参数的个数比原operator_symbol所需要的参数个数少一个(后置“+”、“-”除外) 例如 class Complex public: Complex( double = 0.0, double = 0.0 );
13、 / constructor Complex operator+( const Complex ,20,运算符重载为成员函数实现单目运算,单目运算: op operand 假如operand是类A的对象 op应该重载为A的成员函数,该函数没有参数 return_type operator op() return_type是op operand的类型 例如:! string_s,等价于string_s.operator!() class String public: String( const char * = “ ); / conversion/default constructor Stri
14、ng(); / destructor bool operator!() const return length = 0;/ is String empty? private: int length; / string length char *sPtr; / pointer to start of string ;,21,运算符重载为成员函数实现双目运算,双目运算:operand_1 op operand_2 假设operand_1是类A的对象 op应该重载为A的成员函数,该函数只有一个参数 return_type operator op(argument_type argument) ret
15、urn_type是operand_1 op operand_2的类型 argument_type是operand_2的类型 被重载双目运算符的两操作数类型可以相同 class String public: String( const char * = “ ); / conversion/default ctor String(); / destructor bool operator=( const String ,22,运算符重载为成员函数实现双目运算,被重载双目运算符的两操作数类型可以不同 例如:set_a + element_a ,等价于set_a.operator+(element_a
16、) class CSet public: CStet(); / constructor const CSet ,23,运算符重载为成员函数:小结,将运算符op重载为类A的成员函数时 op是单目运算:在op所在的表达式中, op右边的操作数是类A的对象 return_type operator op() op operand等价于operand.operator op() op是双目运算:在op所在的表达式中, op左边的操作数是类A的对象 return_type operator op(argument_type argument) operand_1 op operand_2等价于opera
17、nd_1.operator op(operand_2) operand_2可以是A的对象,也可以不是,24,运算符重载为友员函数,但是,在一些情况下,对类A进行双目运算符op的重载时,类A的对象只能作为op所在表达式的右操作数,左操作数不是A的对象 将流操作符“” 用于学生对象:coutstudent 将“+”用于日期型对象:给定一个日期date,计算n天后的日期 date+n:此时可以把“+”重载为成员函数 date.operator+(n); n + date :怎么办?不允许这样写显然不符合思维的习惯,也失去了运算符重载的意义,25,运算符重载为友员函数,friend return_ty
18、pe operator operator_symbol(argument-list) function-body argument-list中参数的个数与原operator_symbol所需要的参数个数相同 class String friend ostream ,26,运算符重载为友员函数,实现单目运算: op operand operand是类A的对象 op应该重载为A的友员函数,该函数有一个参数 friend return_type operator op(A arg) return_type是op operand的类型 例如:! string_s,等价于operator!(string
19、_s) class String friend bool operator!(const String ,27,运算符重载为友员函数,实现双目运算:operand_1 op operand_2 op被重载为A的友员函数,该函数有两个参数 friend return_type operator op(argT1 arg1, argT2 arg2 ) return_type是operand_1 op operand_2的类型 argT1是operand_1的类型 argT2是operand_2的类型 operand_1和operand_2中至少有一个是类型为A的对象 如果operand_1是类A的
20、对象,则argT1为A 在函数operator op(argT1 arg1, argT2 arg2 )的函数体中,可以访问类arg1的任何数据成员 如果operand_2是类A的对象,则argT2为A 在函数operator op(argT1 arg1, argT2 arg2 )的函数体中,可以访问类arg2的任何数据成员 将双目运算符op重载为类A的友员函数时,可以:以非类A的对象作为op所在表达式的左操作数,28,流输入输出运算符的重载,cout 5 “this”; 为什么能够成立? cout是什么? “” 为什么能用在 cout上?,29,cout 是在 iostream中定义的,ost
21、ream 类的对象“” 能用在cout 上是因为 在iostream.h里对 “” 进行了重载考虑,怎么重载才能使得 cout 5; 和 cout “this”都能成立?,流输入输出运算符的重载,30,void operator( const ostream ,流输入输出运算符的重载,31,怎么重载才能使得cout 5 “this” ; 能成立?,流输入输出运算符的重载,32,只需要将重载运算符的返回值设为引用 ostream ,流输入输出运算符的重载,33,引用,某个变量的引用,和这个变量是一回事,相当于该变量的一个别名。 void swap( int / n1,n2的值被交换,34,引用,
22、函数的返回值可以是引用,如:#include using namespace std; int n = 4; int 该程序输出结果是 40,35,const 引用类型参数,class Complex; Complex Add ( Complex c1, Complex c2); Complex Add ( const Complex const 代表参数的值在函数内部不能被修改 两种函数定义方式,后者比前者好在哪里呢? 后者的好处是避免了生成参数对象的过程,节省时间,空间开销。,36,对象指针和对象引用作函数的参数,对象作为函数的参数 值传递,但需进行对象副本的拷贝 对象指针作函数的参数 使
23、用对象指针作为函数参数要比使用对象作函数参数更普遍一些,有如下两点好处: (1) 实现传址调用。可在被调用函数中改变调用函数的参数对象的值,实现函数之间的信息传递。 (2) 使用对象指针实参仅将对象的地址值传给形参,而不进行副本的拷贝,这样可以提高运行效率,减少时空开销。 当形参是指向对象指针时,调用函数的对应实参应该是某个对象的地址值,一般使用&后加对象名。,#include using namespace std; class M public: M() x=y=0; M(int i, int j) x=i; y=j; void copy(M *m); void setxy(int i,
24、int j) x=i; y=j; void print() coutx; y=m-y; void fun(M m1, M *m2); int main() M p(5, 7), q; q.copy( ,输出结果为: 5,7 22,25,38,对象指针和对象引用作函数的参数,对象引用作函数参数 在实际中,使用对象引用作函数参数要比使用对象指针作函数更普遍 使用对象引用作函数参数具有用对象指针作函数参数的优点 用对象引用作函数参数将更简单,更直接。,#include using namespace std; class M public: M() x=y=0; M(int i, int j) x=
25、i; y=j; void copy(M ,40,假定下面程序输出为 5, 请问该补写些什么?#include using namespace std; class CStudent public:int nAge; ; int main()CStudent s ;s.nAge = 5;cout s;return 0; ,流输入输出运算符的重载,41,ostream ,流输入输出运算符的重载,问题: cout 5 “this”; 本质上的函数调用的形式是什么?(用重载后的写出),operator(operaotr ( cout,5) , “this”);,42,思考,int n = 5; cou
26、t n+ n; 输出结果是什么? 结果是 为什么?,43,思考,因为 cout n+ n; 可以理解成:operator( operator(cout,n+), n);而C/C+参数的计算顺序是从右到左,所以先入栈 不是仅对cout成立 即先将最右边的n入栈,在将n+入栈。之后先将n+出栈,输出n并执行自增运算,再出栈并输出n的栈中所存值,44,流输入输出运算符的重载,事实上在 iostream 里是将 重载成成员函数 class ostream ostream 的函数调用形式是什么呢?,45,流输入输出运算符的重载,是: cout.operator(n+).operator(n);实际上,这
27、条语句可以直接写在程序里,其效果和 cout n+ n; 完全一样为什么?,class PhoneNumber friend ostream ,示例:用友元函数重载输入输出流,istream ,输入流的函数: :ignore(n) /忽略n个输入字符 :setw(n) /限定读到每个字符数组的字符个数,产生调用operator(cin, phone);,返回值为引用,使得在Phonenumber上的对象输入操作可以串联: cin phone1 phone2;,48,注意: 函数 operator被声明为PhoneNumber的友员函数,因为按照日常习惯,PhoneNumber的对象只能作为的右
28、操作数 总结:流运算符重载 对于任何类A,如果希望对它重载流运算符“”,应该将函数 operator声明为A的友员函数 重载流运算符“”时,函数 operator的第一个操作数类型和返回值类型都必须是istream&,示例:用友元函数重载输入输出流,49,数组下标运算符重载,对数组对象,经常希望能够对其中的某个元素进行取值、赋值 C=array5 array6=8 在重载 “ ”时,使用 eleType ,int 如果在Array定义中用“int operator( int )”代替“int &operator( int )”,则 “integers15 = 1000”是错误的,#includ
29、e void assert( int expression ); assert的作用是现计算表达式expression ,如果其值为假(即为0),那么它先向stderr打印一条出错信息,然后通过调用 abort 来终止程序运行。,51,重载成员访问操作符:“*” 、“-”,class Screen; class ScreenPtr public: Screen const版本和非const版本区别:const成员返回const引用以防止用户改变基础对象,52,重载成员访问操作符,成员访问操作符与下标操作符一样,一般应该定义const版本和非const版本。 解引用操作符(*)不要求定义为成员函
30、数。 箭头操作符(-) 必须定义为类成员函数,必须返回指向类类型的指针,或者返回定义了自己的箭头操作符的类类型对象。 像一个二元操作符:接受一个对象和一个成员名,但事实上它不接受显式形参。 例如: Ptr *operator-() return ptr;。这里没有形参,由编译器处理获取成员的工作。 编写程序:ptr-action()时,实际上等价于:(ptr-action)()。而编译器按如下规则对ptr-action求值: ptr是一个指针,指向具有名为action的成员的类对象,则编译器将代码编译为调用该对象的action成员。 否则,如果action是定义了operator-操作符的类的
31、一个对象,则ptr-action与ptr.operator-()-action相同。即,执行ptr的operator-(),然后使用该结果重复这三步。 否则代码出错,53,重载+和-运算符,自增运算符+、自减运算符-有前置/后置之分,为了区分所重载的是前置运算符还是后置运算符,C+规定 前置运算符作为一元运算符重载 class_a &operator+()或 friend class_a &operator+(class_a &) class_a &operator-()或 friend class_a &operator-(class_a &) 后置运算符作为二元运算符重载 class_a
32、operator+( int )或 friend class_a operator+(class_a &, int) class_a operator-(int)或 friend class_a operator-(class_a &, int) obj+:obj.operator+(0)、或者operator+(obj, 0) +obj:obj.operator+()、或者operator+(obj),实参0为“哑值”,用于使编译器区分前置还是后置,问题:为什么前置返回值为对象引用,而后置返回值为对象?,class Date public: Date( int m = 1, int d =
33、1, int y = 1900 ); / constructor Date ,Date not a reference return/后置形式叫做“取回然后增加”。 void Date:dateIncrement() ,Date d4( 3, 18, 1969 ); +d4;/ call d4.operator+()d4+; /call d4.operator+(0),57,如何选择使用哪种方式重载?,对于单目运算符(后置“+”、“-”除外) ,通常只需要重载为成员函数 对于双目运算符,如果表达式的左右操作数都相同,也只要重载为成员函数 对于双目运算符op,在许多情况下都希望:对类A进行重载后
34、,表达式的左操作数可以是A的对象,也可以不是A的对象 例如对于字符串类String,重载“+”,合并两个字符串 s1+ s2 : s+“abc”: “abc”+s 同时将函数operator op重载为A的成员函数和友员函数 支持左操作数为类A的对象 String operator+(const String &) String operator+(const char *) 支持左操作数为非类A的对象 friend String operator+(const char *, const String &),58,this指针,this是C+中用来表示当前对象地址的一个隐含指针变量。 当对一
35、个对象调用成员函数时,编译程序先将对象的地址赋给this指针,然后调用成员函数,每次成员函数存取数据成员时,由隐含作用this指针。 可以使用*this来标识调用该成员函数的对象。 在类的成员函数中需要 当前对象的引用时,用“*this”表示。例如:在重载前置自增运算时,需要返回当前对象的引用 引用:对象的别名,直接指向目标,不分配空间,不需要销毁 赋值必须返回*this 当前对象的地址时,用“this”表示。例如:需要把当前对象的地址传递给某个函数调用,59,抽象数据类型的强制类型转换,在一些基本数据类型之间,C+允许进行强制类型转换 float_b = (float) int_a:把int
36、_a强制转换成float型数据 int_a = (int) char_c:把char_c强制转换成int型数据 对类A表示的抽象数据类型,如何将它的对象转换成其他类型的数据?使用非静态成员函数对A进行运算符重载,这种运算符称作“转换运算符” operator int() const “(int) A_obj”等价于“A_obj.operator int()”,返回值类型是int operator otherClass() const “(otherClass) A_obj”等价于“A_obj.operator otherClass()”,返回值类型是otherClass 对于转换运算符,不能够
37、为重载成员函数指定返回值类型,转换运算符本身就代表了返回值类型,60,转换运算符,转换操作符(conversion operator)是一种特殊的类成员函数,其形式为: operator type(); 其中,type表示内置类型名、类类型名或由类型别名所定义的名字。 对任何可作为函数返回类型的类型(void除外)都可以定义转换函数。 一般而言,不允许转换为数组或函数类型,转换为指针类型(数据和函数指针)以及引用类型是可以的。 转换函数并且形参表必须为空。 通常转换操作符应定义为const成员。,61,示例程序,CMan类 运算符重栽 man5:man的身高增长了5厘米 man+8:man的体重增长了8公斤 7+man:man的体重增长了7公斤 强制类型转换 quota = (float) man: 把man根据他的体重指数转换成一个浮点数 程序放到网上,课后阅读,62,作业,阅读教材 阅读V2版的第8章(V5版的第11章) 预习V2版的第9章(V5版的第12章):继承 邮件作业 大整数运算练习:请编写一个用于超长整数处理的类,使得下列程序能正确运行。假设每个超长整数最多有100位十进制整数。,63,关于程序设计校内对抗赛,时间:5月10日左右 参赛方式:3人组队参加 奖励方式:获得三等奖以上,每人期末成绩奖励2-5分 更详细信息 http:/