《C程序设计第3章.pptx》由会员分享,可在线阅读,更多相关《C程序设计第3章.pptx(71页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、13.2.1 运算符重载3.2.1 运算符重载概述在以前的学习中,C+中预定义的运算符的操作对象只能是基本数据类型如int或float等。实际上,对于很多用户自定义的类型(如类),也需要有类似的运算操作。例如复数类Complex。class Complexpublic:Complex()real=image=0;Complex(double r,double i)第1页/共71页23.2.1 运算符重载概述 real=r,image=i;void Print();private:double real,image;void Complex:Print()if(image0)coutrealim
2、agei;else coutreal+imagei;第2页/共71页33.2.1 运算符重载概述 声明复数类的对象:complex c1(2.0,3.0),c2(4.0,-2.0),c3。如果我们需要对c1和c2进行加法运算,“c3=c1+c2”,编译时却会出错,这是因为编译器不知道该如何完成这个加法。这时我们就需要编写程序来实现“+”运算符来作用于complex类的对象,这就是运算符的重载。运算符重载是对已有的运算符赋予多重含义,使同一个运算符作用于不同类型的数据时,导致不同类型的行为。第3页/共71页43.2.1 运算符重载概述 C+中运算符的重载虽然给我们设计程序带来很多的方便,但对运算
3、符的重载时,以下的几种情况需要注意:(1)一般来说,不改变运算符原有含义,只让它能针对新类型数据的实际需要,对原有运算符进行适当的改造。例如,重载“+”运算符后,它的功能还是进行加法运算。(2)重载运算符时,不能改变运算符原有的优先级别,也不能改变运算符需要的操作数的数目。重载之后运算符的优先级和结合性都不会改变。第4页/共71页53.2.1 运算符重载概述(3)不能创建新的运算符,只能重载c+中已有的运算符。(4)有些运算符不能进行重载。如:“.”类成员运算符、“*”类指向运算符、“:”类作用域运算符、“?:”条件运算符及“sizeof”求字节数运算符。第5页/共71页63.2.2 运算符重
4、载的实现运算符重载的本质就是函数重载。在实现过程中,首先把指定的运算表达式转化为对运算符函数的调用,运算对象转化为运算符函数的实参,然后根据实参的类型来确定需要调用的函数,这个过程是在编译过程中完成的。运算符重载形式有两种:重载为类的成员函数和重载为类的友元函数。1.运算符重载为类的成员函数 语法形式如下:函数类型 operator 运算符(形参表)函数体;第6页/共71页73.2.2 运算符重载的实现 2.运算符重载为类的友元函数 运算符重载还可以为友元函数。当重载友元函数时,将没有隐含的参数this指针。语法形式如下:friend 函数类型 operator 运算符(形参表);运算符重载为
5、友元函数时,要在函数类型说明之前使用friend关键词来说明。第7页/共71页83.2.3 双目运算符重载 双目运算符就是运算符作用于两个操作数。下面通过一个例子对“+”运算符重载来学习一下双目运算符重载的应用。【例3.1】定义一个复数类,重载“+”运算符为复数类的成员函数,使这个运算符能直接完成两个复数的加法运算,以及一个复数与一个实数的加法运算。第8页/共71页9#include class Complexpublic:Complex()real=image=0;Complex(double r,double i)real=r,image=i;void Print();Complex op
6、erator+(Complex&);Complex operator+(float);private:double real,image;第9页/共71页void Complex:Print()if(image0)coutrealimageiendl;else coutreal+imageiendl;Complex Complex:operator+(Complex&c)Complex t;t.real=real+c.real;t.image=image+c.image;return t;第10页/共71页11Complex Complex:operator+(float s)Complex
7、t;t.real=real+s;t.image=image;return t;第11页/共71页12void main(void)Complex c1(25,50),c2(100,200),c3;coutc1=;c1.Print();coutc2=;c2.Print();c3=c1+c2;coutc3=c1+c2=;c3.Print();c1=c1+200;coutc1=;c1.Print();第12页/共71页13【例3.23.2】重载“+”+”运算符为复数类的友元函数,使这个运算符能直接完成两个复数的加法运算,以及一个复数与一个实数的加法运算。class Complexpublic:Com
8、plex(double r,double i);friend Complex operator+(const Complex&c1,const Complex&c2);friend Complex operator+(const Complex&c1,double t);private:double real,image;第13页/共71页14Complex operator+(const Complex&c1,const Complex&c2)Complex c(0,0);c.real=c1.real+c2.real;c.image=c1.image+c2.image;return c;Co
9、mplex operator+(const complex&c1,double t)Complex c(0,0);c.real=c1.real+t;c.image=c1.image;return c;第14页/共71页15void main()Complex c1(2.0,3.0),c2(4.0,-2.0),c3(0,0);c3=c1+c2;coutc1+c2=;Print(c3);c3=c1+5;cout mon_daydt.month-1)/少一个闰年判断 dt.day=dt.day-mon_daydt.month-1;dt.month+;if(dt.month=13)dt.month=1
10、;dt.year+;return dt;第17页/共71页183.2.4 赋值运算符重载在C+中有两种类型的赋值运算符:一类是“+=”和“-=”等先计算后赋值的运算符,另一类是“=”即直接赋值的运算符。下面分别进行讨论。1运算符“+=”和“-=”的重载【例3.4】实现复数类“+=”和“-=”的重载。#includeclass Complexpublic:第18页/共71页19 Complex(double r,double i)real=r;image=i;Complex operator-=(Complex&t);Complex operator+=(Complex&t);Print();p
11、rivate:double real,image;Complex Complex:operator-=(Complex&t)real-=t.real;image-=t.image;return*this;第19页/共71页20Complex:Print()if(image0)coutrealimageiendl;else coutreal+imageiendl;void main()Complex c1(5.0,3.0),c2(2.1,1.8),c3(5.3,4.2);c1-=c2;coutc1=;c1.Print();c3+=c2;coutc3=;c3.Print();第20页/共71页21
12、3.2.4 赋值运算符重载2运算符运算符“=”的重载的重载【例例3.5】实现实现“=”运算符重载的示例。运算符重载的示例。#include#includeclass CMessage public:CMessage()buffer=NULL;CMessage()delete buffer;第21页/共71页22 void Display()coutbufferendl;void Set(char*string)if(buffer!=NULL)delete buffer;buffer=new charstrlen(string)+1;strcpy(buffer,string);void oper
13、ator=(const CMessage&Message)if(buffer!=NULL)delete buffer;buffer=new charstrlen(Message.buffer)+1;strcpy(buffer,Message.buffer);第22页/共71页23private:char*buffer;void main()CMessage c1;c1.Set(initial c1 message);c1.Display();CMessage c2;c2.Set(initial c2 message);c2.Display();c1=c2;c1.Display();第23页/共
14、71页243.2.5 单目运算符重载 类的单目运算符可重载为一个没有参数的非静态成员函数或者带有一个参数的非成员函数,参数必须是用户自定义类型的对象或者是对该对象的引用。在C+中,单目运算符有+和-,它们是变量自动增1和自动减1的运算符。在类中可以对这两个单目运算符进行重载。第24页/共71页253.2.5 单目运算符重载 如同“+”运算符有前缀、后缀两种使用形式,“+”和“-”重载运算符也有前缀和后缀两种运算符重载形式,以“+”重载运算符为例,其语法格式如下:函数类型 operator+();/前缀运算 函数类型 operator+(int);/后缀运算 使用前缀运算符的语法格式如下:+对象
15、;使用后缀运算符的语法格式如下:对象+;第25页/共71页26【例例3.6】重载单目运算符重载单目运算符“+”。#includeclass Counterpublic:Counter()v=0;Counter operator+();/前置单目运算符前置单目运算符Counter operator+(int);/后置单目运算符后置单目运算符void Display()coutvendl;private:int v;第26页/共71页27Counter Counter:operator+()/前置单目运算 +v;return*this;Counter Counter:operator+(int)/
16、后置单目运算Counter t;t.v=v+1;return t;第27页/共71页28void main()Counter c1,c2;int i;for(i=0;i4;i+)/后置单目运算符后置单目运算符 c1+;coutc1=;c1.Display();for(i=0;i4;i+)/前置单目运算符前置单目运算符 +c2;coutc2=;c2.Display();第28页/共71页293.2.6 下标运算符重载 下标运算符“”通常用于在数组中标识数组元素的位置,下标运算符重载可以实现数组数据的赋值和取值。下标运算符重载函数只能作为类的成员函数,不能作为类的友元函数。下标运算符“”函数重载的
17、一般形式为:函数类型 operator(形参表);其中形参表为该重载函数的参数列表。重载下标运算符只能且必须带一个参数,该参数给出下标的值。第29页/共71页30【例3.7】定义一个字符数组类,其中对下标运算符“”进行重载。#include#include class MyCharArraypublic:MyCharArray(int m)len=m;str=new charlen;MyCharArray(char*s)str=new charstrlen(s)+1;strcpy(str,s);len=strlen(s);第30页/共71页31 MyCharArray()delete str;
18、char&operator(int n)static char ch=0;if(nlen-1)cout整数下标越界;return ch;else return*(str+n);第31页/共71页32 void Disp()coutstrendl;private:int len;char*str;void main()MyCharArray word(This is a C+program.);word.Disp();cout位置0:;coutword0endl;第32页/共71页33 cout位置15:;coutword15endl;cout位置25:;coutword25endl;word0
19、=t;word.Disp();int f=10;MyCharArray word2(f);for(int i=0;i10;i+)word2i=wordi;word2.Disp();第33页/共71页343.2.7 关系运算符重载 关系运算符也可以被重载,例如定义一个日期类date,重载运算符“=”和“”,用于两个日期的等于和小于的比较运算。【例3.8】日期类重载关系运算符“=”、“”等。#includeclass Datepublic:Date(int m,int d,int y)month=m;day=d;year=y;第34页/共71页35void Display()cout month/
20、day/year;friend int operator t2返回返回0,否则返回,否则返回1 if(t1.yeart2.year)return 1;else if(t1.montht2.month&t1.year=t2.year)return 1;else if(t1.day(Date&t1,Date&t2)return(t2t1);private:int month,day,year;第36页/共71页37void main()Date date1(11,25,90),date2(11,22,90);if(date1date2)date1.Display();cout is less th
21、an;date2.Display();coutendl;else if(date1=date2)date1.Display();第37页/共71页38 cout is equal to;date2.Display();coutdate2)date1.Display();cout is more than;date2.Display();coutendl;第38页/共71页393.2.8 类型转换运算符重载类型转换运算符重载函数的格式如下:operator 类型名()函数体 与前面重载运算符函数不同,类型转换运算符重载函数没有返回类型,因为类型名就代表了返回类型,也没有任何参数。在调用过程中要带
22、一个对象实参。实际上,类型转换运算符将对象转换成类型名规定的类型。转换时的形式就像强制转换。如果没有转换运算符定义,直接用强制转换是不行的,因为强制转换只能对标准数据类型进行操作,对类类型的操作是没有定义的。第39页/共71页40【例3.9】实现人民币Money与double的转换。#includeclass Money /人民币类public:Money(double value=0.0)yuan=(int)value;fen=(value-yuan)*100+0.5;void Show()coutyuan 元fen 分 endl;第40页/共71页41 operator double()/
23、类型转换运算符重载函数return yuan+fen/100.0;private:int yuan,fen;void main()Money r1(1.01),r2(2.20);Money r3;r3=Money(double(r1)+double(r2);r3.Show();r3=r1+2.40;/隐式转换类型 r3.Show();3=2.0-r1;/隐式转换类型 r3.Show();第41页/共71页423.3联编和虚函数 3.3.1 静态联编和动态联编 面向对象的多态性从实现的角度来讲,可以分为静态多态性和动态多态性两种。在源程序编译的时候就能确定具有多态性的语句调用哪个函数,称为静态联
24、编。对于重载函数的调用就是在编译的时候确定具体调用哪个函数,所以是属于静态联编。第42页/共71页433.3.1 静态联编和动态联编 从对静态联编的上述分析中可以知道,编译程序在编译阶段并不能确切知道将要调用的函数,只有在程序执行时才能确定将要调用的函数,为此要确切知道该调用的函数,要求联编工作要在程序运行时进行,这种在程序运行时进行联编工作被称为动态联编,或称动态束定,又叫晚期联编。第43页/共71页443.3.2 虚函数 在C+中,动态联编是通过虚函数来实现的。虚函数的本质是将派生类类型的指针赋给基类类型的指针,虚函数被调用时会自动判断调用对象的类型,从而做出相应的响应。【例3.10】虚函
25、数引例#include class CPerson public:void PrintInfo()coutPersonn;第44页/共71页45class CWorker:public CPersonprivate:int kindofwork;public:void PrintInfo()/在派生类worker中重新定义 coutWorkern;class CTeacher:public CPersonprivate:int subject;第45页/共71页46public:void PrintInfo()coutPrintInfo();p=&t;p-PrintInfo();t1=&t;t
26、1-PrintInfo();第46页/共71页47从前面的知识,我们可以很容易分析出它的运行结果。因为指针的类型决定调用那一个成员函数,所以,一个person*调用person成员函数,即使它指向派生类的对象。同样,一个teacher*也调用teacher的成员函数。我们把这称为早期联编或静态联编,因为指针要调用那一个函数是在编译时就确定的。那么,当person*指向派生类对象时,我们能不能通过该指针来调用派生类的成员函数呢?在C+中,我们是可以做到的,这要用到C+的多态特性。3.3.3 虚函数定义第47页/共71页48 也就是说,要等到程序运行时,确定了指针所指向的对象的类型时,才能够确定。
27、在C+语言中,是通过将一个函数定义成虚函数来实现运行时的多态的。虚函数的定义很简单,只要在成员函数原型前加一个关键字virtual即可。virtual将一个成员函数说明为虚函数,对于编译器来讲,它的作用是告诉编译器,这个类含有虚函数,对于这个函数不使用静态联编,而是使用动态联编机制。编译器就会按照动态联编的机制进行一系列的工作。3.3.3 虚函数定义第48页/共71页49【例3.11】对于上面的例子,把基类的成员函数定义为虚函数,分析运行结果。#include class CPersonpublic:virtual void PrintInfo()/基类中的虚函数 coutPersonn;cl
28、ass CWorker:public CPersonprivate:int kindofwork;第49页/共71页50public:void PrintInfo()/在派生类worker中重新定义 coutWorkern;void PrintotherInfo()/在派生类中重新定义 coutother information of Workern;class CTeacher:public CPersonprivate:int subject;public:void PrintInfo()/在派生类中重新定义 coutTeachern;第50页/共71页51 void Printother
29、Info()/重新定义 coutPrintInfo();p=&t;p-PrintInfo();p=&d;p-PrintInfo();t.PrintInfo();第51页/共71页52虚函数与重载函数的比较(1)重载函数要求函数有相同的函数名,并有不同的参数序列;而虚函数则要求这三项(函数名、返回值类型和参数序列)完全相同;(2)重载函数可以是成员函数或友员函数,而虚函数只能是成员函数;(3)重载函数的调用是以所传递参数序列的差别作为调用不同函数的依据;虚函数是根据对象的不同去调用不同类的虚函数;(4)虚函数在运行时表现出多态功能,这是C+的精髓;而重载函数则在编译时表现出多态性。第52页/共7
30、1页533.3.4动态联编的工作机制 编译器在执行过程中遇到编译器在执行过程中遇到virtual关键字的关键字的时候,将自动安装动态联编需要的机制,首时候,将自动安装动态联编需要的机制,首先为这些包含先为这些包含virtual函数的类(注意不是对象)函数的类(注意不是对象)建立一张虚拟函数表建立一张虚拟函数表VTABLE。在这些虚拟。在这些虚拟函数表中,编译器将依次按照函数声明次序函数表中,编译器将依次按照函数声明次序放置类的特定虚函数的地址。放置类的特定虚函数的地址。同时在每个带有虚函数的类中放置一个同时在每个带有虚函数的类中放置一个称之为称之为vpointer的指针,简称的指针,简称vpt
31、r,这个指针,这个指针指向这个类的指向这个类的VTABLE。第53页/共71页543.3.4动态联编的工作机制编译器在每个类中放置一个vptr,一般置于对象的起始位置,继而在对象的构造函数中将vptr初始化为本类的VTABLE的地址。如图下页图。C+编译程序时候按下面的步骤进行工作:1.为各类建立虚拟函数表,如果没有虚函数则不建立。2.暂时不连接虚函数,而是将各个虚函数的地址放入虚拟函数表中。3.直接连接各静态函数。第54页/共71页55第55页/共71页563.3.4动态联编的工作机制 所有的基类的派生类的虚拟函数表的顺序与基类的顺序是一样的,对于基类中不存在方法再按照声明次序进行排放。这样
32、不管是CPerson还是CWorker或者CTeache类它们的虚拟函数表的第一项总是printInf函数的地址。对于CWorker类,printInf函数下面的才是printotherInfo函数的地址。因此不管对于CPerson还是CWorker,this-printInf总是编译成 call this-VTABLE0。第56页/共71页573.3.4动态联编的工作机制 程序到真正运行时候将会发现this的真正指向的对象,如果是CPerson,则调用CPerson-VTABLE0,如果是CWorker,则调用CWorker-VTABLE0,就这样,编译器借助虚拟函数表实现了动态联编的过程,
33、从而使多态的实现有了可能。第57页/共71页58关于虚函数有以下几点说明(1)当基类中把成员函数定义为虚函数后,要达到动态联编的效果,派生类和基类的对应成员函数不仅名字相同,而且返回类型、参数个数和类型也必须相同。否则不能实现运行时多态。(2)基类中虚函数前的virtual不能省略,派生类中的虚函数的virtual关键字可以生省略,缺省后仍为虚函数。(3)运行时多态必须通过基类对象的引用或基类对象的指针调用虚函数才能实现。(4)虚函数必须是类的成员函数,不能是友员函数,也不能是静态成员函数。(5)不能将构造函数定义为虚函数,但可将析构函数定义为虚函数。第58页/共71页593.3.5 虚析构函
34、数 在C+中,不能声明虚构造函数,因为在构造函数执行时,对象还没有完全构造好,不能按虚函数方式进行调用。但是可以声明虚析构函数,如果用基类指针指向一个new生成的派生类对象,通过delete作用于基类指针删除派生类对象时,有以下两种情况:第59页/共71页603.3.5 虚析构函数(1)如果基类析构函数不为虚析构函数,则只会调用基类的析构函数,而派生类的析构函数不会被调用,因此派生类对象中派生的那部分内存空间无法析构释放。(2)如果基类析构函数为虚析构函数,则释放基类指针的时候会调用基类和派生类中的所有析构函数,派生类对象中所有的内存空间都将被释放,包括继承基类的部分。所以C+中的析构函数通常
35、是虚析构函数。第60页/共71页61【例3.12】虚析构函数的用法和作用示例。#include class Base1 public:Base1()cout Base1()n;class Derived1:public Base1 public:Derived1()cout Derived1()n;class Base2 public:virtual Base2()cout Base2()n;第61页/共71页62class Derived2:public Base2 public:Derived2()cout Derived2()n;void main()Base1*bp=new Deriv
36、ed1;delete bp;Base2*b2p=new Derived2;delete b2p;运行结果:运行结果:Base1()Derived2()Base2()第62页/共71页633.4 纯虚函数和抽象类 3.4.1 纯虚函数 在许多情况下,在基类中不能给出有意义的虚函数定义。在C+中,对于那些在基类中不需要定义具体的行为的函数,可以定义为纯虚函数。声明纯虚函数的一般形式是 class 类名 virtual 类型 函数名(参数表)=0;/纯虚函数 .;第63页/共71页64 注意:(1)纯虚函数没有函数体。(2)最后面的“=0”并不表示函数返回值为0,它只起形式上的作用,告诉编译系统“这
37、是纯虚函数”。(3)这是一个声明语句,最后应有分号。(4)如果在一个类中声明了纯虚函数,而在其派生类中没有对该函数定义,则该虚函数在派生类中仍然为纯虚函数。3.4.1 纯虚函数第64页/共71页653.4.2 抽象类 如果一个类中至少有一个纯虚函数,那么这个类被称为抽象类(abstract class)。因此抽象类的定义是基于纯虚函数的。抽象类中不仅包括纯虚函数,也可包括虚函数。抽象类中的纯虚函数可能是在抽象类中定义的,也可能是从它的抽象基类中继承下来且重定义的。抽象类有一个重要特点,即抽象类必须用作派生其他类的基类,而不能用于直接创建对象实例。抽象类不能直接创建对象的原因是其中有一个或多个函
38、数没有定义,但仍可使用指向抽象类的指针支持运行时多态性。第65页/共71页663.4.2 抽象类抽象类定义的一般形式是:class 类名public:virtual 函数名(参数表)=0;其他函数的声明;.;第66页/共71页67【例例3.13】抽象基类示例。抽象基类示例。#include class Person /抽象基类抽象基类public:virtual void PrintInfo()/基类中的函数基类中的函数 coutPersonn;virtual void DisplaySalary(int m,double s)=0;class Worker:public Personpriv
39、ate:int kindofwork;第67页/共71页68public:void PrintInfo()/在派生类中重新定义 coutWorkern;void DisplaySalary(int m,double s)cout工人全年的工资是:m*sendl;class Teacher:public Personprivate:int subject;public:void PrintInfo()coutTeachern;第68页/共71页69 void DisplaySalary(int m,double s)cout教师全年的工资是:m*sendl;class Driver:public Personprivate:int subject;public:void DisplaySalary(int m,double s)cout司机全年的工资:m*sPrintInfo();p-DisplaySalary(12,1800.35);p=&t;p-PrintInfo();p-DisplaySalary(12,1300.45);p=&d;p-PrintInfo();p-DisplaySalary(12,1700.78);第70页/共71页71感谢您的欣赏!第71页/共71页