




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第六章多态性
学习目标
(1)掌握重载运算符的定义方法。(2)了解运算符重载为成员函数与友元函数的区别。(3)掌握不同类型数据间的转换方法。(4)掌握多态性的概念。(5)掌握虚函数的定义和使用方法。(6)掌握纯虚函数和抽象类的定义。(7)了解面向对象程序设计的基本思想
6.1多态性多态性是面向对象程序设计的一个重要特征。多态的是指一个事物有多种状态。多态性可以简单地概括为“一个接口,多种方法”通常我们希望所设计的类具有共同的风格。例如,在不同的类中具有相似功能的函数具有相同的名字、具有相同的参数类型、参数的顺序也相同。这种统一性帮助我们记忆,且有助于新类的设计。在新类的设计中只需要添加相同的数据成员并改写相应的成员函数。不同类的对象调用自己的函数成员,这就是多态性。6.1.1多态的类型
在面向对象程序设计语言C++中,多态性的实现方式有4种:重载多态、强制多态、类型参数化多态和包含多态。重载多态和强制多态称为特殊多态性,用来描述语义上无关联的类型间的关系。前面介绍过的函数重载和本章将要介绍的运算符重载都属于重载多态;强制多态就是将一个变量的类型加以强制转换来满足某种操作要求,本章介绍的强制类型转换就属于强制多态。类型参数化多态和包含多态称为一般多态性,用来系统地描述语义上相关的一组类型。包含多态是类族中定义于不同类中的同名成员函数的多态行为,主要是继承过程中通过虚函数来实现,本章介绍的虚函数属于包含多态;类型参数化多态是指当1个函数(或类)对若干个类型参数操作时,这些类型具有某些公共的语义特性,C++中的类模板是实现类型参数化多态的工具,关于类模板的相关内容将在本书中的第7章进行详细介绍。6.1.2静态关联与动态关联
从系统实现的角度看,多态性分为两种:静态多态和动态多态。关联(binding)是指捆绑或连接的意思。C++中的多态性与关联这一概念密切相关,一个源程序需要经过编译、连接才能形成可执行的文件,在这个过程中要把调用函数和对应函数连接在一起,这个过程就是关联,又称为绑定。关联就是确定调用的具体对象的过程,一般来说,关联指把一个标识符和一个存储地址联系起来。关联分为静态关联与动态关联。如果在编译程序时就能确定具体的调用对象,称为静态关联;如果在编译程序时还不能确定具体的调用对象,只有在程序运行过程中才能确定具体调用对象,称为动态关联由于静态关联是在程序运行前进行关联的,所以又称为早期关联,而动态关联是在程序运行中进行关联的,也叫作滞后关联。在C++中,采用静态关联与动态关联都能实现多态性,采用静态关联实现的多态称为静态多态,而采用动态关联实现的多态称为动态多态。静态多态在程序编译时系统就能决定调用的哪个函数,因此又称为编译时的多态性。静态多态性是通过函数的重载实现的,以前学过的函数重载和本章将要学习的运算符重载实现的多态性属于静态多态性。动态多态是在程序运行过程中才动态地确定操作的对象,在运行阶段确定关联关系,又称运行时的多态性。本章学习的虚函数实现的多态属于动态多态。
6.2
运算符重载运算符重载是指对已有的运算符赋予它新的含义,是通过运算重载函数来实现的,本质上也是属于函数重载,是C++实现静态多态的一重要手段。重载是面向对象的基本特点之一。面向对象程序设计的重载:函数重载和运算符重载。函数重载是指在相同作用域内,若干个参数特征不同的函数使用相同的函数名,也称为函数名重载;运算符重载是另一种调用函数的方法,是指同样的运算符可以施加于不同类型的操作数上面,也就是对已有的运算符赋予多重含义,使同一个运算符作用于不同类型的数据时产生不同的行为,是一种静态联编的多态。6.2.1什么是运算符重载12问题举例——复数的运算classcomplex //复数类声明{public: complex(doubler=0.0,doublei=0.0)//构造函数
{real=r;imag=i;} voiddisplay(); //显示复数的值private: doublereal; doubleimag;}; 运算符重载13问题举例——复数的运算用“+”、“-”能够实现复数的加减运算吗?实现复数加减运算的方法
——重载“+”、“-”运算符运算符重载14运算符重载的实质运算符重载是对已有的运算符赋予多重含义必要性C++中预定义的运算符其运算对象只能是基本数据类型,而不适用于用户自定义类型(如类)实现机制将指定的运算表达式转化为对运算符函数的调用,运算对象转化为运算符函数的实参。编译系统对重载运算符的选择,遵循函数重载的选择原则。运算符重载6.2.2运算符重载的方法运算符重载实质上是函数的重载。运算符重载函数的一般格式如下:函数类型operator运算符名称
(形参表列){对运算符的重载处理}例如,若要对用户定义的类型Complex重载实现加法操作,重载函数原型如下:
Complexoperator+(Complex&a,Complex&b);6.2.2运算符重载的方法在定义了重载运算符的函数后,可以说,函数operator+重载了运算符+。在执行复数相加的表达式c1+c2时,系统就会调用operator+函数,把c1和c2作为实参,与形参a,b进行虚行结合,执行函数operator+(a,b)。【例6-1】将运算符“+”重载为适用于复数加法。这里重载函数作为类的友元函数。#include<iostream>usingnamespacestd;classComplex{public:Complex(){real=0;imag=0;}Complex(doubler,doublei){real=r;imag=i;}friendComplexoperator+(Complex&a,Complex&b);voiddisplay();private:doublereal;doubleimag;};Complexoperator+(Complex&a,Complex&b){returnComplex(a.real+b.real,a.imag+b.imag);}
voidComplex::display(){cout<<"("<<real<<","<<imag<<"i)"<<endl;}intmain(){Complexc1(3,4),c2(5,-10),c3;c3=c1+c2;cout<<"c1=";c1.display();cout<<"c2=";c2.display();cout<<"c1+c2=";c3.display();}6.2.3重载运算符的规则C++语言对运算符重载进行了以下规定限制:只能重载C++语言中原先已定义的运算符,不能自己“创造”新的运算符进行重载。并不是所有的运算符都可以进行重载。不能进行重载的运算符如下:
·.*∷?:Sizeof(长度运算符)不能改变运算符原有的优先级和结合性。C++语言已预先规定了每个运算符的优先级和结合性,以决定运算次序。不能改变其运算次序,如果确实需要改变,只能采用加括号“()”的办法。经重载的运算符,其操作数中至少应该有一个是自定义类型。
intoperator+(int,int);//error:不能对内置类型重载+Vectoroperator+(constVector&,constVector&);//ok不能改变运算符对预定义类型数据的操作方式,但是可以根据实际需要,对原有运算符进行适当地改造与扩充。运算符重载有两种方式:重载为类的成员函数和重载为类的友元函数。
6.2.4运算符重载为成员函数和友元函数运算符的重载实质上是函数重载,在C++中,运算符重载有两种形式:重载为类的非静态成员函数和重载为非成员函数。如果将运算符重载为类的成员函数,它可以自由地访问本类的数据成员,使用时,总是通过该类的某个对象来访问重载运算符。运算符也可以重载为非成员函数,这时,运算所需要的操作数都需要通过函数的形参表来传递,在形参表中形参从左到右的顺序就是运算符操作数的顺序。如果需要访问运算符参数对象的私有成员,可以将该函数声明为类的友元函数。当运算符重载为成员函数和友元函数时,参数的个数有所不同,具体如下。(1)运算符重载为类成员函数时:参数个数=原操作数个数-1(后置++、--除外)。(2)运算符重载为友元函数时:参数个数=原操作数个数,且至少应该有一个自定义类型的形参。对于双目运算符B,如果将运算符B重载为类成员函数,使之能够实现表达式oprd1Boprd2,其中oprd1为A类对象,则B应被重载为A类的成员函数,形参类型应该是oprd2所属的类型。经重载后,表达式oprd1Boprd2相当于oprd1.operatorB(oprd2);否则,如果将运算符B重载为友元函数,表达式oprd1Boprd2等同于operatorB(oprd1,oprd2),函数的形参代表依自左至右次序排列的各操作数。对于单目运算符,有前置和后置之分。前置单目运算符U,如果将运算符U重载为类成员函数,使之能够实现表达式:Uoprd,其中oprd为A类对象,则U应被重载为A类的成员函数,无形参。经重载后,表达式Uoprd相当于oprd.operatorU();如果将运算符重载为友元函数时,前置单目运算符U重载后,表达式Uoprd等同于operatorB(oprd)。后置单目运算符,如++和--,如果要重载++或--为类成员函数,使之能够实现表达式oprd++或oprd--,其中oprd为A类对象,则++或--应被重载为A类的成员函数,且具有一个int类型形参,用来与前置单目运算符的++oprd和--oprd加以区别。经重载后,表达式oprd++相当于oprd.operator++(0);如果运算符重载为友元函数,则后置单目运算符++和--重载后,表达式oprdB等同于operatorB(oprd,0)。
【例6-2】将运算符“+”重载为适用于复数加法。重载函数作为类的成员函数。
classComplex{public:Complex(){real=0;imag=0;}Complex(doubler,doublei){real=r;imag=i;}//friendComplexoperator+(Complex&a,Complex&b);Complexoperator+(Complex&b);voiddisplay();private:doublereal;doubleimag;};//Complexoperator+(Complex&a,Complex&b)//{returnComplex(a.real+b.real,a.imag+b.imag);}ComplexComplex::operator+(Complex&b){returnComplex(real+b.real,imag+b.imag);}
voidComplex::display(){cout<<"("<<real<<","<<imag<<"i)"<<endl;}intmain(){Complexc1(3,4),c2(5,-10),c3;c3=c1+c2;//执行c1.operator+(c2);
cout<<"c1=";c1.display();cout<<"c2=";c2.display();cout<<"c1+c2=";c3.display();}C++规定,当重载以下的运算符时,必须重载为某个类的成员函数:
=、[]、(),->当重载以下的运算符时,必须是普通函数或友元函数,不能为成员函数:
>>、<<一般将双目运算符重载为友元函数,单目运算符重载为成员函数。由于友元的使用会破坏类的封装性,因此从原则上说,要尽量将运算符函数作为成员函数。6.2.5重载双目运算符
双目运算符(或称二元运算符)是C++中最常用的运算符。双目运算符有两个操作数,通常在运算符的左右两侧,如3+5,a=b,i<10等。在重载双目运算符时,在函数中应该有两个参数。下面举一个例子说明重载双目运算符的应用。【例6-3】定义一个字符串类String,用来存放不定长的字符串,重载运算符”==”,“<”和“>”,用于两个字符串的等于、小于和大于的比较运算。#include<iostream>usingnamespacestd;classString{public:String(){p=NULL;}String(char*str);friendbooloperator>(String&string1,String&string2);voiddisplay();private:char*p;};String::String(char*str){p=str;}voidString::display(){cout<<p;}booloperator>(String&string1,String&string2){if(strcmp(string1.p,string2.p)>0)returntrue;elsereturnfalse;}intmain(){Stringstring1("Hello"),string2("Book");string1.display();cout<<endl;string2.display();cout<<(string1>string2)<<endl;return0;}6.2.6重载单目运算符单目运算符只有一个操作数,如!a,-b,&c,*p,还有最常用的++i和--i等。
重载单目运算符的方法与重载双目运算符的方法是类似的。但由于单目运算符只有一个操作数,因此运算符重载函数只有一个参数,如果运算符重载函数作为成员函数,则还可省略此参数。下面以自增运算符”++”为例,介绍单目运算符的重载。【例6-4】有一个time类,包含数据成员minute(分)和sec(秒),模似秒表,每次走一秒,满60秒进一分钟,此时秒又从0开始算。要求输出分和秒的值。classTime{public:Time(){minute=0;sec=0;}Time(intm,ints):minute(m),sec(s){}
Timeoperator++(int);Voiddisplay(){cout<<minute<<":"<<sec<<endl;}private:intminute;intsec;};Timetime::operator++(int){Timetemp(*this);sec++;if(sec>=60){sec-=60;++minute;}returntemp;}intmain(){Timetime1(34,59),time2;cout<<''Time1:'';time1.display();++time1;cout<<''++time1'';time1.display();time2=time1++;cout<<''time1++:'';time1.display();cout<<''time2:'';time2.display();}【例6-5】在例6-4的基础上增加对后置运算符的重载。修改后的程序如下。
classTime{public:Time(){minute=0;sec=0;}Time(intm,ints):minute(m),sec(s){}Timeoperator++();voiddisplay(){cout<<minute<<":"<<sec<<endl;}private:intminute;intsec;};TimeTime::operator++(){if(++sec>=60){sec-=60;++minute;}return*this;}intmain(){Timetime1(34,0);for(inti=0;i<61;i++){++time1;time1.display();}return0;}6.2.7重载流插入运算符和流提取运算符用户自己定义的类型的数据,是不能直接用“<<”和“>>”来输出和输入的。如果想用它们输出和输入自己定义的类型的数据,必须在自己定义的类中对这两个运算符进行重载。对“<<”和“>>”重载的函数形式如下:istream&operator>>(istream&,自定义类&);ostream&operator<<(ostream&,自定义类&);所有C++编译系统都在类库中提供输入流类istream和输出流类ostream。cin和cout分别是istream类和ostream类的对象。在类库提供的头文件中已经对这两个运算符进行了重载,使之作为流插入运算符和流提取运算符,能用来输出和输入C++标准类型的数据。因此,在本书前面几章中,凡是用“cout<<”和“cin>>”对标准类型数据进行输入和输出的,都要用#include<iostream>把头文件包含到本程序文件中。6.2.7重载流插入运算符和流提取运算符用户自己定义的类型的数据,是不能直接用“<<”和“>>”来输出和输入的。如果想用它们输出和输入自己定义的类型的数据,必须在自己定义的类中对这两个运算符进行重载。对“<<”和“>>”重载的函数形式如下:istream&operator>>(istream&,自定义类&);ostream&operator<<(ostream&,自定义类&);即重载运算符“>>”的函数的第一个参数和函数的类型都必须是istream&类型,第二个参数是要进行输入操作的类。重载“<<”的函数的第一个参数和函数的类型都必须是ostream&类型,第二个参数是要进行输出操作的类。由于重载函数的第一个参数是输入输出流类iostream的对象而不是自定义类的对象,只能将重载“>>”和“<<”的函数作为类的友元函数,而不能将它们定义为成员函数。【例6-6】用友元函数重载流插入运算符“<<”和流提取运算符“>>”。
#include<iostream>usingnamespacestd;classComplex{public:friendostream&operator<<(ostream&,Complex&);friendistream&operator>>(istream&,Complex&);private:doublereal;doubleimag;};ostream&operator<<(ostream&output,Complex&c){output<<"("<<c.real<<"+"<<c.imag<<"i)";returnoutput;}istream&operator>>(istream&input,Complex&c){cout<<"inputrealpartandimaginarypartofcomplexnumber:";input>>c.real>>c.imag;returninput;}intmain(){Complexc1,c2;cin>>c1>>c2;cout<<"c1="<<c1<<endl;cout<<"c2="<<c2<<endl;cin.clear();//以下几行是为了程序运行结束时不自动关闭窗口cout<<"Pleaseenteracharactertoexit\n";charch;cin>>ch;return0;}6.3不同类型数据间的转换1.标准类型数据间的转换2.用转换构造函数实现类型转换Complex(doubler){real=r;imag=0;}3.类型转换函数operatordouble(){returnreal;}6.4虚函数通过在基类中将同名的成员函数设为虚函数,当用基类的指引或引用指向派生类时,实际运行时调用的是实际指向或引用的对象的相应函数而不是基类的同名函数。虚函数是一个成员函数,在基类的类定义中定义虚函数的一般形式:
class基类名{
.......
virtual返回值类型将要在派生类中重载的函数名(参数列表);
};例如:classStudent{
virtualvoiddisplay();//定义虚函数
}当基类中的某个成员函数被声明为虚函数后,它就可以在基类的派生类中对虚函数重新定义。在派生类中重新定义的函数应与虚函数具有相同的形参个数和形参类型。以实现统一的接口,不同的定义过程。如果在派生类中没有对虚函数重新定义,则它继承其基类的虚函数。当程序发现虚函数名前的关键字virtual后,会自动将其作为动态联编处理,即在程序运行时动态地选择合适的成员函数。6.4.1虚函数的作用虚函数的作用是允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问派生类中的同名函数。【例6-7】基类与派生类中有同名函数classStudent{
voiddisplay();};classGraduate:publicStudent{
voiddisplay();};6.4.2虚函数的作用classStudent{
voiddisplay();};classGraduate:publicStudent{
voiddisplay();};intmain(){Student*pt=&stud1;pt->display();pt=&grad1;pt->display();}6.4.2虚函数的作用classStudent{virtualvoiddisplay();};classGraduate:publicStudent{virtualvoiddisplay();};intmain(){Student*pt=&stud1;pt->display();pt=&grad1;pt->display();}6.4.2虚函数的作用观察改动后程序的运行结果可以发现,用同一种调用形式pt->display()不但输出了学生stud1的全部数据,而且还输出了研究生grad1的全部数据。pt一个基类指针,可以调用同一类族中不同类的虚函数,这就是多态性,对同一消息,不同对象有不同的响应方式。虚函数的奇妙作用在于实现动态多态。说明:(1)非类的成员函数不能定义为虚函数。(2)类的静态成员函数和构造函数不能定义为虚函数,但可以将析构函数定义为虚函数。(3)虚函数一般不声明为内联函数,因为对虚函数的调用采用的是动态绑定,而对内联函数采用的是静态绑定,即使虚函数在类体内定义,C++编译器也将它视为非内联函数。(4)只需要在声明函数的类体中使用关键字“virtual”将函数声明为虚函数,而定义函数时不需要使用关键字“virtual”。(5)如果声明了某个成员函数为虚函数,则在该类中不能出现和这个成员函数同名并且返回值、参数个数、参数类型都相同的非虚函数。在以该类为基类的派生类中,也不能出现这种非虚的同名同返回值同参数个数同参数类型函数。(6)C++规定,当一个成员函数被声明为虚函数后,其派生类中的同名函数都自动成为虚函数,因此在派生类重新声明该虚函数时要加上virtual,也可以不加,但习惯上一般在每一层声明该函数时都加virtual,使程序更加清晰。(7)在派生类中重新定义基类虚函数,要求函数名、函数类型、函数参数个数和类型全部与基类的虚函数相同,并根据派生类的需要重新定义函数体。以前介绍的函数重载处理的是同一层次上的同名函数问题,是横向重载,虚函数可以理解为纵向重载,一般称为函数的覆盖(overriding)。与重载不同的是,同一类族的虚函数的函数名、参数的类型、个数都是相同的,而函数重载时函数名相同,参数的个数或类型不同。(8)如果在派生类中没有对基类的虚函数覆盖定义,则派生类简单地继承其直接基类的虚函数。6.4.2虚函数的作用虚函数的作用是实现动态联编,也就是在程序的运行阶段动态地选择合适的成员函数。实现动态关联需要3个条件:(1)必须把需要动态关联的行为定义为类的公共属性的虚函数;(2)类之间存在子类型关系,一般表现为一个类从另一个类公有派生而来;(3)必须先使用基类指针指向子类型的对象,然后直接或者间接使用基类指针调用虚函数。因此,实现动态关联,只能通过指向基类的指针或基类对象的引用来调用虚函数,其格式如下:(1)指向基类的指针变量名->虚函数名(实参表)(2)基类对象的引用名.虚函数名(实参表)【例6-7】
Student是基类,Graduate是派生类,它们都有同名函数display(),但不将其设置为虚函数。#include<iostream>#include<string>usingnamespacestd;classStudent{public:Student(int,string,float);voiddisplay();protected:intnum;stringname;floatscore;};Student::Student(intn,stringnam,floats){num=n;name=nam;score=s;}
voidStudent::display(){cout<<"num:"<<num<<"\name:"<<name<<"\nscore:"<<score<<"\n\n";}classGraduate:publicStudent{public:Graduate(int,string,float,float);voiddisplay();private:floatpay;};voidGraduate::display(){cout<<"num:"<<num<<"\nname:"<<name<<"\nscore:"<<score<<"\npay="<<pay<<endl;}
Graduate::Graduate(intn,stringnam,floats,floatp):Student(n,nam,s),pay(p){}
intmain(){Studentstud1(1001,"Li",87.5);Graduategrad1(2001,"Wang",98.5,563.5);Student*pt=&stud1;pt->display();pt=&grad1;pt->display();getchar();return0;}【例6-8】将基类Student与派生类Graduate中的同名函数display()设为虚函数。
classStudent{public:Student(int,string,float);
virtualvoiddisplay();protected:intnum;stringname;floatscore;};Student::Student(intn,stringnam,floats){num=n;name=nam;score=s;}
voidStudent::display(){cout<<"num:"<<num<<"\name:"<<name<<"\nscore:"<<score<<"\n\n";}classGraduate:publicStudent{public:Graduate(int,string,float,float);
virtualvoiddisplay();private:floatpay;};voidGraduate::display(){cout<<"num:"<<num<<"\nname:"<<name<<"\nscore:"<<score<<"\npay="<<pay<<endl;}
Graduate::Graduate(intn,stringnam,floats,floatp):Student(n,nam,s),pay(p){}
intmain(){Studentstud1(1001,"Li",87.5);Graduategrad1(2001,"Wang",98.5,563.5);Student*pt=&stud1;pt->display();pt=&grad1;pt->display();getchar();return0;}在使用虚函数时,有两点要注意:(1)只能用virtual声明类的成员函数,使它成为虚函数,而不能将类外的普通函数声明为虚函数。因为虚函数的作用是允许在派生类中对基类的虚函数重新定义。显然,它只能用于类的继承层次结构中。(2)一个成员函数被声明为虚函数后,在同一类族中的类就不能再定义一个非virtual的但与该虚函数具有相同的参数和函数返回值类型的同名函数。由虚函数实现的动态多态就是同一类族中不同类的对象,对同一函数调用作出不同的响应。那么,在什么情况下把一个成员函数声明为虚函数呢?主要考虑以下几点。(1)首先看成员函数所在的类是否会作为基类,然后看成员函数在类的继承后是否要更改功能,如果希望更改其功能,一般应该将它声明为虚函数。(2)如果成员函数在类被继承后功能不需修改,或派生类用不到该函数,则不要声明为虚函数。不要把基类中的所有的成员函数都声明为虚函数。(3)应考虑对成员函数的调用是通过对象名还是通过基类的指针或引用去访问,如果是通过基类的指针或引用去访问,则应当声明为虚函数。仅仅是通过对象名去访问派生类时,没有必要声明为虚函数。(4)有时,在定义虚函数时,并不定义其函数体,即函数体是空的。它的作用只是定义了一个虚函数名,具体功能留给派生类去添加。这时需要将虚函数设为纯虚函数,包含纯虚函数的类称为抽象类,在6.4节对此进行详细讨论。【例6-9】分析下面的例子的运行结果,理解虚函数与普通函数的不同。
classB//基类B0声明{public:virtualvoidf()const{cout<<"B::f";}voidg()const{cout<<”B::g”;}};classD:publicB{public:voidf()const{cout<<"D::f";}//重写B::fvoidg(){cout<<”D::g”;}};classDD:publicD{public:voidf(){cout<<"DD::f";}//此处没有const,不是D::f的重写函数voidg()const{cout<<”DD::g”;}};voidcall(constB&b){b.f();b.g();}voidmain()//主函数{Bb;Dd;DDdd;call(b);call(d);call(dd);b.f();b.g();d.f();d.g();dd.f();dd.g();}6.4.4虚析构函数
以前曾经介绍过,析构函数的作用是在对象撤销之前做必要的“清理现场”的工作。当派生类的对象从内存中撤销时一般先运行派生类的析构函数,然后再调用基类的析构函数。如果用new运算符建立了派生类的临时对象,对指向基类的指针指向这个临时对象,当用delete运算符撤销对象时,系统执行的是基类的析构函数,而不是派生类的析构函数,不能彻底完成“清理现场”的工作。解决的办法是将基类及派生类的析构函数设为虚函数,这时无论基类指针指的是同一类族中的哪一个类对象,系统会采用动态关联,调用相应的析构函数,对该对象进行清理工作。
【例6-10】基类的指针指向派生类的对象,且基类有非虚析构函数时的执行情况:
#include<iostream>usingnamespacestd;classPoint{public:Point(){}~Point(){cout<<"executingPointdestructor"<<endl;}};classCircle:publicPoint{public:Circle(){}~Circle(){cout<<"executingCircledestructor"<<endl;}private:intradius;};
intmain(){Point*p=newCircle;deletep;getchar();return0;}【例6-11】基类的指针指向派生类的对象,基类中有虚析构函数时的执行情况:
#include<iostream>usingnamespacestd;classPoint{public:Point(){}
virtual~Point(){cout<<"executingPointdestructor"<<endl;}};classCircle:publicPoint{public:Circle(){}~Circle(){cout<<"executingCircledestructor"<<endl;}private:intradius;};intmain(){Point*p=newCircle;deletep;getchar();return0;}【例6-12】析构函数的隐式调用和显式调用。
Point*fc(){Circlec1;Point*p=newCircle;returnp;}voidmain(){Point*q=fc();deleteq;}6.5纯虚函数与抽象类
当基类只是起到接口作用,不需要生成该类的对象,也不需要对该类的某些成员函数进行实现,这样的基类就是抽象类。因此,抽象类是一种特殊的基类,它为类族提供一个统一的操作界面。在C++中,包含纯虚函数的类称为抽象类。它只能被其他类继承,而不能创建对象实例,所以通常也把它称为抽象基类。
6.5.1纯虚函数某些情况下,基类无法确定或者无法完全确定一个函数的具体操作方式和内容,只能靠派生类来提供各种实现版本,基类这种只能靠派生类提供重定义版本的虚函数叫纯虚函数。即纯虚函数是只给出函数声明而没给出函数实现的虚函数。因此,纯虚函数为各派生类提供一个公共界面。纯虚函数的一般定义形式为:class类名
{
virtual返回值类型函数名(参数表)=0;
......
};classShape{public:virtualdoublearea()=0;
};
classCircle:publicShape{public:Circle(doublex):radius(x){}doubleget_radius(){returnradius;}virtualdoublearea(){returnradius*radius*3.14;}private:doubleradius;};6.5.2抽象类
如果一个类中至少有一个纯虚函数,这个类就是为抽象类,通常也称为抽象基类。它的主要作用是为一个类族提供统一的公共接口,使它们更有效地发挥多态性的特性。使用抽象类时需注意以下几点。(1)抽象类只能作为用作其他类的基类,不能建立抽象类的对象。抽象类处于继承层次结构的较上层,一个抽象类自身无法实例化,而只能通过继承机制,生成抽象类的非抽象派生类,然后再实例化。(2)抽象类不能用作参数类型、函数返回值或显式转换的类型。(3)抽象类不能定义对象,但是可以声明一个抽象类的指针和引用。通过指针或引用可以指向并访问派生类对象,以访问派生类的成员。(4)抽象类派生出新的类之后,如果派生类给出所有纯虚函数的函数实现,这个派生类就可以声明自己的对象,因而不再是抽象类;反之,如果派生类没有给出全部纯虚函数的实现,这时的派生类仍然是一个抽象类。【例6-13】虚函数和抽象基类的应用。#include<iostream>usingnamespacestd;//声明抽象基类ShapeclassShape{public:virtualfloatarea()const{return0.0;}//虚函数
virtualfloatvolume()const{return0.0}//虚函数
virtualvoidshapeName()const=0;//纯虚函数};classPoint:publicShape{public:Point(float=0,float=0);voidsetPoint(float,float);floatgetX()const{returnx;}floatgetY()const{returny;}virtualvoidshapeName()const{cout<<"Point:";}
friendostream&operator<<(ostream&,constPoint&);protected:floatx,y;};Point::Point(floata,floatb){x=a;y=b;}voidPoint::setPoint(floata,floatb){x=a;y=b;}ostream&operator<<(ostream&output,constPoint&p){output<<"["<<p.x<<","<<p.y<<"]";returnoutput;}classCircle:publicPoint{public:Circle(floatx=0,floaty=0,floatr=0);voidsetRadius(float);floatgetRadius()const;virtualfloatarea()const;virtualvoidshapeName()const{cout<<"Circle";}//对虚函数进行再定义
friendostream&operator<<(ostream&,constCircle&);protected:floatradius;};Circle::Circle(floata,floatb,floatr):Point(a,b),radius(r){}voidCircle::setRadius(floatr){radius=r;}floatCircle::getRadius()const{returnradius;}floatCircle::area()const{return3.1415926*radius*radius;}ostream&operator<<(ostream&output,constCircle&c){output<<"["<<c.x<<","<<c.y<<"],r="<<c.radius;returnoutput;}voidmain(){Pointpoint(3.2,4.5);Circlecircle(2.4,1.2,5.6);point.shapeName();//静态关联cout<<point<<endl;
circle.shapeName();//静态关联cout<<circle<<endl;Shape*pt;//定义基类指针pt=&point;//指针指向Point类的对象pt->shapeNa
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 【正版授权】 IEC TS 62453-43:2024 EN Field device tool (FDT) interface specification – Part 43: Object model integration profile – CLI and HTML
- 【正版授权】 IEC 62047-45:2025 EN Semiconductor devices - Micro-electromechanical devices - Part 45: Silicon based MEMS fabrication technology - Measurement method of impact resistance o
- 【正版授权】 IEC 60947-4-2:2020+AMD1:2024 CSV EN Low-voltage switchgear and controlgear - Part 4-2: Contactors and motor-starters - Semiconductor motor controllers,starters and soft-sta
- 【正版授权】 IEC 60601-1:1988/AMD1:1991 FR-D Amendment 1 - Medical electrical equipment - Part 1: General requirements for safety
- 小班水班本课程
- 酱料生产知识培训课件
- 大数据在石油行业的应用
- 2025年幼儿园小班国庆节活动方案
- 2025年社区迎新春活动方案
- 上肢骨折护理诊断及措施
- 化学入门-给小学生讲化学
- 绵竹事业单位招聘2022年考试《职业能力倾向测验》真题及答案解析【完整word版】
- GB/T 9166-2009四柱液压机精度
- GB/T 39711-2020海洋工程用硫铝酸盐水泥修补胶结料
- GB/T 34685-2017丙烯腈-丁二烯橡胶(NBR)评价方法
- GB/T 34120-2017电化学储能系统储能变流器技术规范
- GB/T 31216-2014全价宠物食品犬粮
- GB/T 17376-2008动植物油脂脂肪酸甲酯制备
- GB/T 14846-2008铝及铝合金挤压型材尺寸偏差
- GB/T 11026.1-2016电气绝缘材料耐热性第1部分:老化程序和试验结果的评定
- 院内ACS救治流程图
评论
0/150
提交评论