Upload
annice
View
43
Download
6
Embed Size (px)
DESCRIPTION
第十五章 继承. 15.1 继承基础 15.1.1 派生类的定义 15.1.2 派生类的构造函数和析构函数 15.2 继承细节 15.3 多态性 15.3.1 晚期绑定(动态联编、动态绑定) 15.3.2 C++中的虚函数. 15.1 继承基础. 15.1.1 派生类的定义 15.1.2 派生类的构造函数和析构函数. 15.1.1 派生类的定义. 1. 派生类的概念 生物的层次关系: 生物 →植物 →微生物 → 动物→ 人 →野生动物 →家畜→鸭子 - PowerPoint PPT Presentation
Citation preview
高级语言程序设计 ( 第 15 章继承 )
第十五章 继承15.1 继承基础
15.1.1 派生类的定义15.1.2 派生类的构造函数和析构函数
15.2 继承细节15.3 多态性 15.3.1 晚期绑定(动态联编、动态绑定) 15.3.2 C++中的虚函数
高级语言程序设计 ( 第 15 章继承 )
15.1 继承基础
15.1.1 派生类的定义15.1.2 派生类的构造函数和析构函数
高级语言程序设计 ( 第 15 章继承 )
15.1.1 派生类的定义1. 派生类的概念生物的层次关系:
→生物 植物 → 微生物→ → 动物 人
→野生动物 → →家畜 鸭子
继承是我们得以用简单的方法理解、描述客观存在应该关键。 C++面向对象的程序设计中,用不同的
“ ” 类定义描述客观现实, 继承 是类间的一种常用关系,它使得我们用一个已经存在的类(基类)来定义一个新类(派生类)。
高级语言程序设计 ( 第 15 章继承 )
如 class A class B基类 派生类学生类 研究生类
又例如,公司雇员档案的管理:employee( 雇员): 姓名、年龄、工资;manager( 经理): 姓名、年龄、工资、行政级别;engineer( 工程师): 姓名、年龄、工资、专业、学位;director(高级主管):姓名、年龄、工资、专业、学位、职务。
C++提供了类定义的派生和继承功能,能很好地解决上述问题
(使代码可重用,避免重复!)。
高级语言程序设计 ( 第 15 章继承 )
若类A是类B的基类(父类),则类B是类A的派生类(子类)。 也可以说,类 B(子类)继承了类 A (父类);或说,类 A (父类)派生出类 B(子类)。
姓名、年龄、工资employee
姓名、年龄、工资、行政级别manager
姓名、年龄、工资、专业、学位engineer
姓名、年龄、工资、专业、学位、职务director
高级语言程序设计 ( 第 15 章继承 )
2. 派生类的定义格式
class <派生类类型名> :<基类表> {
private: <各私有成员说明>;
public: <各公有成员说明>;
protected: <各保护成员说明>;
<以关键字 friend开头的友元说明>;};
高级语言程序设计 ( 第 15 章继承 )
<基类表>的一般格式为:
<派生方式 > <基类名 1>, ... , <派生方式 > <基类名 n>
<派生方式>又可为:private、 public或 protected 。
高级语言程序设计 ( 第 15 章继承 )
定义一个类:class employee
{
//employee类 (类型 ),将作为其它几个类的基类
private: short age;
float salary;
protected:
char * name;
};
高级语言程序设计 ( 第 15 章继承 )
定义一个派生类 manager :class manager:public employee //派生类{ private: int level;public:
manager(short ag, float sa, char* na, int lev) :employee (ag,sa,na) // 调用基类构造函数{ //对基类初始化负责
level=lev; }
};
高级语言程序设计 ( 第 15 章继承 )
派生方式 ( 基类 在基类中的 在派生类中的被继承方式 ) 存取权限 的存取权限=================================
=================public public publicpublic potected protectedpublic private (inaccessible)potected public protectedpotected potected protectedpotected private (inaccessible)private public privateprivate potected privateprivate private (inaccessible)
==================================================
3. 派生方式与权限继承
高级语言程序设计 ( 第 15 章继承 )
注意: public派生方式:使基类的公有成员和保护成员在派生类中仍然是公有成员和保护成员,而基类的私有成员不可在派生类中被存取。
protected派生方式:使基类的公有成员和保护成员在派生类中都变为保护成员,而基类的私有成员不可在派生类中被存取。
private派生方式:使基类的公有成员和保护成员在派生类中都变为私有成员,而基类的私有成员不可在派生类中被存取。
高级语言程序设计 ( 第 15 章继承 )
派生类中可出现四种成员:1) 不可访问的成员 -- 基类的 private私有成员被继承过来后,这些成员在派生类中是不可访问的。
2) 私有成员 -- 包括在派生类中新增加的 private私有成员以及从基类私有继承过来的某些成员。这些成员在派生类中是可以访问的。
3) 保护成员 -- 包括在派生类中新增加的 potected保护成员以及从基类继承过来的某些成员。这些成员在派生类中是可以访问的。
4) 公有成员 -- 包括在派生类中新增加的 public公有成员以及从基类公有继承过来的基类的 public成员。这些成员不仅在派生类中可以访问,而且在建立派生类对象的模块中,也可以通过对象来访问它们 。
高级语言程序设计 ( 第 15 章继承 )
例 继承与派生关系中成员的访问权限
分析下述程序中的继承与派生关系class B
{
int priDat;
protected:
int proDat;
public:
int pubDat;
};
高级语言程序设计 ( 第 15 章继承 )
class D11 : public B {public:
void useBData() {pubDat=11; //OK!proDat=12; //OK!priDat=13; //ERROR!
} };
高级语言程序设计 ( 第 15 章继承 )
class D21 : public D11 {
public:
void useBData() {
pubDat=111; //OK!
proDat=121; //OK!
priDat=131; //ERROR!
}
};
高级语言程序设计 ( 第 15 章继承 )
class D12 : protected B {
public:
void useBData() {
pubDat=21; //OK!
proDat=22; //OK!
priDat=23; //ERROR!
}
};
高级语言程序设计 ( 第 15 章继承 )
class D22 : public proD12 {public:
void useBData() {pubDat=211; //OK!proDat=221; //OK!priDat=231; //ERROR!
} };
高级语言程序设计 ( 第 15 章继承 )
class D13 : private B {public:
void useBData() {pubDat=31; //OK!proDat=32; //OK! priDat=33; //ERROR!
} };
高级语言程序设计 ( 第 15 章继承 )
class D23 : public priD13 { public:
void useBData() {pubDat=311; //ERROR!proDat=321; //ERROR!priDat=331; //ERROR!
} };
高级语言程序设计 ( 第 15 章继承 )
15.1.2 派生类的构造函数和析构函数
派生类的构造函数的一般格式如下: <派生类名>(<参数总表>):<初始化列表>
{<构造函数体>
} 而 <初始化列表> 按如下格式构成: < 基类名 1>(< 基类参数表 1>) , ... , < 基类名
n>(<基类参数表 n>), <对象成员名 1>(<对象成员参数表 1>) , ... , <对象成员名m>(<对象成员参数表m>)
(注:若无对象成员时,则不出现此后半部分;基类名与对象成员名的次序无关紧要,各自出现的顺序可以任意)
高级语言程序设计 ( 第 15 章继承 )
派生类构造函数执行的一般次序如下: (1) 调用各基类的构造函数,调用顺序为继承时的声明顺序。
(2) 若派生类含有对象成员的话,调用各对象成员的构造函数,调用顺序按照声明顺序。
(3) 执行派生类构造函数的函数体。
析构派生类对象时,其执行次序恰与构造时的次序相反。
高级语言程序设计 ( 第 15 章继承 )
例 继承与派生关系中对构造函数及析构函数的调用#include<iostream>using namespace std;class CB{
int b;public:
CB(int n){ b=n; cout<<"CB::b="<<b<<endl; }; ~CB(){cout<<"CBobj is destrcting"<<endl;};
}; class CC {
int c;public:
CC(int n1,int n2){ c=n1; cout<<"CC::c="<<c<<endl; }; ~CC(){cout<<"CCobj is destructing"<<endl;};
};
高级语言程序设计 ( 第 15 章继承 )
class CD:public CB,public CC{ int d;
public: CD(int n1,int n2,int n3,int n4)
:CC(n3,n4),CB(n2){ d=n1; cout<<"CD::d="<<d<<endl; }; ~CD(){cout<<"CDobj is destructing"<<endl;};
};
int main(){ CD CDobj(2,4,6,8);}
高级语言程序设计 ( 第 15 章继承 )
运行结果为:CB::b=4CC::c=6CD::d=2CDobj is destructingCCobj is destructingCBobj is destrcting 思考:将派生类 CD改写为如下形式后,请给出输出结果。
高级语言程序设计 ( 第 15 章继承 )
class CD:public CB,public CC { int d; CC obcc;//对象成员 CB obcb;
public: CD(int n1,int n2,int n3,int n4) :CC(n3,n4), CB(n2), obcb(100+n2), obcc(100+n3,100+n4) { d=n1; cout<<"CD::d="<<d<<endl; }; ~CD(){cout<<"CDobj is destructing"<<endl;};
};
高级语言程序设计 ( 第 15 章继承 )
输出:CB::b=4CC::c=6CC::c=106CB::b=104CD::d=2CDobj is destructingCBobj is destrcting.CCobj is destructingCCobj is destructingCBobj is destrcting
高级语言程序设计 ( 第 15 章继承 )
4. 使用举例 ——公司员工工资的管理抽象的员工
姓名社会安全号薪水(空)
计时工姓名社会安全号工作时数小时工资
合同员工姓名社会安全号月薪 / 周薪 / 年薪
高级语言程序设计 ( 第 15 章继承 )
基类class employee {作为其它类的基类,为不同类别的员工派生出不同的子类 string name;
string ssn; double net_pay;
public: employee();
employee (string the_name,string the_ssn); string get_name()const;string get_ssn()const;double get_pay()const;void set_name(string new_name);void set_ssn(string new_ssn);void set_pay(double new_pay);void print_check()const;
};
高级语言程序设计 ( 第 15 章继承 )
派生类(计时工)class hourlyemployee:public employee {
double wage_rate;double hours;
public:hourlyemployee();hourlyemployee(string the_name,string the_ssn,double the_wage_rate,double the_hours) ;double get_rate()const;double get_hours()const;void set_rate(double new_wage_rate);void set_hours(double hours_worked);
void print_check();//注意与基类的成员函数声明完全相同};
高级语言程序设计 ( 第 15 章继承 )
//派生类的构造函数要完成所有成员的初始化工作:
hourlyemployee::hourlyemployee(string the_name,string the_ssn ,double the_wage_rate,double the_hours)
:employee(the_name,the_ssn),wage_rate(the_wage_rate),hours(the_hours)
{//函数体为空。}
高级语言程序设计 ( 第 15 章继承 )
在派生类中重定义基类中的成员函数//基类的成员函数声明完全相同void hourlyemployee::print_check()//不能设为常量成员函数{
set_pay(hours*wage_rate);//调用基类 employee的成员函数cout<<"\n\n";cout<<"\t"<<get_name()<<"工资细单 \n";cout<<"--------------------------------------------\n";cout<<"\t工号 (社会安全号 ): "<<get_ssn()<<endl;cout<<"\t工作总小时数: "<<get_hours()<<"小时 "<<endl;cout<<"\t小时工资: "<<get_hours()<<"元 /小时 "<<endl;cout<<"\t实发工资: "<<get_pay()<<"元 \n";cout<<"--------------------------------------------\n";
}
高级语言程序设计 ( 第 15 章继承 )
思考:基类中的成员函数 void print_chedk()能不
能不要,它有什么作用?作业:P575 15.1
高级语言程序设计 ( 第 15 章继承 )
15.2 继承细节
15.2.1 不继承的函数15.2.2 与基类对象和派生类对象相关的赋值兼容性问题15.2.3 派生关系中的二义性处理15.2.4 虚基类
高级语言程序设计 ( 第 15 章继承 )
15.2.1 不继承的函数1.派生类中的构造函数派生类中的构造函数是不继承的。
如,class salaredemployee:public employee { //派生类double salary;
public:salaredemployee();salaredemployee(string the_name,string ssn,double the_weekly_salary) ;double get_weekly_salary()const;void set_weekly_salary(double new_salary);
void print_check();};
高级语言程序设计 ( 第 15 章继承 )
salaredemployee::salaredemployee(string the_name,string the_ssn ,double the_weekly_salary)
:employee(the_name,the_ssn),//必须(显示)的调用基类的构造函数,//来对继承的成员行初始化工作。salary(the_weekly_salary)//对新派生的成员完成初始化的工作{//函数体为空。}
高级语言程序设计 ( 第 15 章继承 )
2. 复制构造函数派生类中的复制构造函数当然也是不继承的。
“参见例程 15_2person ”类与继承细节 。
人员类 姓名 编号 姓别 年龄
派生学生类
姓名 编号 姓别 年龄 数学 语文 平均分
高级语言程序设计 ( 第 15 章继承 )
2.复制构造函数的声明
//基类中的拷贝复制函数class person{protected:
char* no; //人员编号char* name; //姓名char* sex; //姓别int age; //年龄
public:person();person(char*no1,char* na,char* se ,int ag);person(const person &obj);//拷贝复制函数的声明……};
高级语言程序设计 ( 第 15 章继承 )
拷贝复制函数实现person::person(const person &obj){
delete[]no;no=new char[strlen(obj.no)+1];strcpy(no,obj.no);delete[]name;name=new char[strlen(obj.name)+1];strcpy(name,obj.name);delete[]sex;sex=new char[strlen(obj.sex)+1];strcpy(sex,obj.sex);age=obj.age;pernum++;
}
高级语言程序设计 ( 第 15 章继承 )
基于人员类的 ——派生类 学生类class student:public person{public:
double math,chinese,ave;//数学,语文,平均成绩public:
student( );student(char*no1,char* na,char* se ,int ag,double ma,double chi,double av);student(const student &obj);//派生类的拷贝复制函数void operator=(const student& right_obj);//赋值运算符的重载friend ostream &operator<<(ostream& os,const student& per);//重载插入运算符friend istream &operator>>(istream& is,student& stu);//重载提取运算符void printtab();//输出表头
};
高级语言程序设计 ( 第 15 章继承 )
派生类中拷贝复制函数的实现student::student(const student &obj) :person(obj) //显示调用基类是的复制构造函数{
math=obj.math;chinese=obj.chinese;ave=obj.ave;
}
高级语言程序设计 ( 第 15 章继承 )
3.派生类中的赋值操作符class person{protected:
char* no; //人员编号char* name; //姓名char* sex; //姓别int age; //年龄
public:person();person(char*no1,char* na,char* se ,int ag);person(const person &obj);//拷贝复制函数void operator=(const person& right_obj);//赋值运算符的重载};
高级语言程序设计 ( 第 15 章继承 )
基类中的赋值运算符的重载void person::operator =(const person& right_obj) {
delete[]no;no=new char[strlen(right_obj.no)+1];strcpy(no,right_obj.no);delete[]name;name=new char[strlen(right_obj.name)+1];strcpy(name,right_obj.name);delete[]sex;sex=new char[strlen(right_obj.sex)+1];strcpy(sex,right_obj.sex);age=right_obj.age;pernum++;
}
高级语言程序设计 ( 第 15 章继承 )
若要使运算兼容:(1) obj=obj;(2) obj1=obj2=obj3;要使用如下加强版的赋值运算符重载形式:person& person::operator =(const person& right_obj) {
if(strcmp(no,right_obj.no)<0)// 若不是一个对象自身的复制 {delete[]no; //开域前要进行删除,否者会将不用的数据占用自由内存no=new char[strlen(right_obj.no)+1];
strcpy(no,right_obj.no);……
}return *this;//返回调用对象变量本身
}
高级语言程序设计 ( 第 15 章继承 )
派生类中的赋值运算符的重载class student:public person{public:
double math,chinese,ave;//数学,语文,平均成绩
public:student( );student(char*no1,char* na,char* se ,int ag,double ma,double chi,double av);student(const student &obj);//拷贝复制函数void operator=(const student& right_obj);//赋值运算符的重载};
高级语言程序设计 ( 第 15 章继承 )
派生类中赋值运算符的重载的实现void student::operator=(const student&
right_obj)//赋值运算符的重载{
person::operator =(right_obj);math=right_obj.math;chinese=right_obj.chinese;ave=right_obj.ave;
}
高级语言程序设计 ( 第 15 章继承 )
派生类中赋值运算符重载实现(增强版)student & student::operator=(const
student& right_obj)//赋值运算符的重载{
person::operator =(right_obj);math=right_obj.math;chinese=right_obj.chinese;ave=right_obj.ave; return *this;//返回调用对象变量本身
} 注: return this; 返回调用对象变量地址,即指针
高级语言程序设计 ( 第 15 章继承 )
4. 派生类中的友元函数
派生类中的友元函数是不继承的,准备作为基类的类在其操作中,尽量不用友员函数而用成员函数来实现。但插入与提取运算符重载除外。
高级语言程序设计 ( 第 15 章继承 )
istream & operator>>(istream & is,student& stu){ if(is==cin) {//键盘版
char str[80];//这样能保证不会因用户输入引起运行错误cout<<endl<<"学号 (回车取默认值 000000): "<<endl;is.getline(str,80);if(str[0]=='\0')
strcpy(str,"000000");//回车取默认值stu.no=new char[strlen(str)+1];strcpy(stu.no,str);cout<<"姓名 (回车取默认值 noname): "<<endl;…… else //文件输入版
{
is>>stu.no>>stu.name>>stu.sex>>stu.age>>stu.chinese>>stu.math>>stu.ave;
}return is; }
高级语言程序设计 ( 第 15 章继承 )
4. 派生类中的析构函数析构函数是不继承的。//基类中的析构函数person::~person(){
delete name,no,sex;}//派生类中的析构函数student::~student(){
person::~person();}
高级语言程序设计 ( 第 15 章继承 )
15.2.2 与基类对象和派生类对象相关的赋值兼容性问题
1. 基类对象 = 派生类对象 ; //OK! 切片问题 派生类对象 = 基类对象 ; //ERROR! 2. 指向基类型的指针 = 派生类对象的地址 ; //OK! 指向派生类类型的指针 = 基类对象的地址 ; //ERROR!
注:访问非基类成员部分时,要经过指针类型的强制转换 。
高级语言程序设计 ( 第 15 章继承 )
例 基类对象 与派生类对象的赋值兼容性问题
#include<iostream.h>class base{ //定义基类
int a; public:
base( ) {...}base(int sa) {a=sa;}int geta() {return a;}
}; class derived:public base { //派生类
int b; public:
derived() {...}derived(int sa, int sb):base(sa) {b=sb;}int getb() {return b;}
};
高级语言程序设计 ( 第 15 章继承 )
int main() {base bs1(11);derived der(2,4);bs1=der; //OK! //der=bs1; //ERR!derived der2(6,8);base *pb = &der2; //OK!cout<<pb->geta()<<endl; //OK!//cout<<pb->getb()<<endl;//ERR! --访问非基类成员部分cout<<((derived *)pb)->getb()<<endl;
//OK! -- 经过类型转换//derived *pd = &bs1; //ERR!
system("pause");return 0;}
高级语言程序设计 ( 第 15 章继承 )
15.2.3 派生关系中的二义性处理
1. 单继承时父类与子类间重名成员的处理 单继承时父类与子类间成员重名时,按如下规定进行处理:对子类而言,不加类名限定时默认为是处理子类成员,而要访问父类重名成员时,则要通过类名限定。
#include <iostream>using namespace std;class CB { public:
int a;CB(int x){a=x;}void showa(){cout<<"Class CB -- a="<<a<<endl;}
};
高级语言程序设计 ( 第 15 章继承 )
class CD:public CB {public:
int a; //与基类 a同名CD(int x, int y):CB(x){a=y;} void showa(){ //与基类 showa同名
cout<<"Class CD -- a="<<a<<endl;}void print2a() {
cout<<"a="<<a<<endl; //子类 acout<<"CB::a="<<CB::a<<endl; //父
类 a}
};
高级语言程序设计 ( 第 15 章继承 )
int main() {CB CBobj(12);CBobj.showa();CD CDobj(48, 999);CDobj.showa(); //子类的 showaCDobj.CB::showa(); //父类的 showa cout<<"CDobj.a="<<CDobj.a<<endl;cout<<"CDobj.CB::a="<<CDobj.CB::a<<endl;
}
程序执行后的显示结果如下:Class CB -- a=12Class CD -- a=999Class CB -- a=48CDobj.a=999CDobj.CB::a=48
高级语言程序设计 ( 第 15 章继承 )
2. 多继承情况下二基类间重名成员的处理 多继承情况下二基类间成员重名时,按如下方式进行处理:对子类而言,不加类名限定时默认为是处理子类成员,而要访问父类重名成员时,则要通过类名限定。
#include <iostream.h>class CB1 {public:
int a; CB1(int x){a=x;}void showa(){ cout<<"Class CB1 ==> a="<<a<<endl;}
};
高级语言程序设计 ( 第 15 章继承 )
class CB2 { public:
int a; CB2(int x){a=x;}void showa(){cout<<"Class CB2 ==> a="<<a<<endl;}
}; class CD:public CB1, public CB2 { public:
int a; //与二基类数据成员 a同名CD(int x, int y, int z):CB1(x),CB2(y){a=z;}
高级语言程序设计 ( 第 15 章继承 )
void showa(){ //与二基类成员函数 showa同名
cout<<"Class CD ==> a="<<a<<endl;}void print3a() {
//显示出派生类的 a及其二父类的重名成员 acout<<"a="<<a<<endl; cout<<"CB1::a="<<CB1::a<<endl;cout<<"CB2::a="<<CB2::a<<endl;
}};
高级语言程序设计 ( 第 15 章继承 )
int main() {CB1 CB1obj(11);CB1obj.showa();CD CDobj(101, 202,909);CDobj.showa(); //子类 showaCDobj.CB1::showa(); //父类 showacout<<"CDobj.a="<<CDobj.a<<endl;cout<<"CDobj.CB2::a="<<CDobj.CB2::a<<endl;
sytem("pause");return 0;}程序执行后的显示结果如下:Class CB1 ==> a=11Class CD ==> a=909Class CB1 ==> a=101CDobj.a=909CDobj.CB2::a=202
高级语言程序设计 ( 第 15 章继承 )
3. 多级混合继承 (非虚拟继承 ) 包含两个基类实例情况的处理
多级混合继承情况下,若类D “ ”从两条不同 路径 同时对类 A进行了一般性继承(非虚拟继承)的话,则类D的对象中会同时包含着两个类 A的实例。此时,对类D而言,要通过类名限定来指定访问两个类 A实例中的哪一个。
本例的类间继承关系示例如下 :class Aclass B : public Aclass C : public Aclass D : public B, public C
存储结构示意 :( ( (A) B ) ( (A) C ) D )
A
B C
D
高级语言程序设计 ( 第 15 章继承 )
上述多级混合继承关系应用例举 : 例 1. 类 A-- 人员类; 类 B-- 学生类; 类 C-- 助教类; 类
D--学生助教类。 例 2. 类 A-- 人员类; 类 B-- 学生类; 类 C-- 工人类; 类
D--工人学生类。 例 3. 类 A-- 家具类; 类 B-- 沙发类; 类 C-- 床类; 类
D--沙发床类。 #include <iostream.h>class A {public:
int a;A(int x){a=x;} void showall(){cout<<"a="<<a<<endl;}
};
高级语言程序设计 ( 第 15 章继承 )
class B:public A {public:
int b;B(int x):A(x-1){b=x;}
}; class C:public A {public:
int c; C(int x):A(x-1){c=x;}
};
高级语言程序设计 ( 第 15 章继承 )
class D:public B,public C {public:
int d;D(int x, int y, int z):B(x+1),C(y+2){d=z;}void showall() {
cout<<"C::a="<<C::a<<endl; //在类D定义范围内,要通过类名限定来指定 //访问两个类 A实例中的哪一个cout<<"B::a="<<B::a<<endl;cout<<"b,c,d="<<b<<", "<<c<<",
"<<d<<endl; //b、 c 、 d 不重名,具有唯一性
}};
高级语言程序设计 ( 第 15 章继承 )
int main() {D Dobj(101, 202, 909); Dobj.showall();cout<<"-------------------"<<endl;cout<<"Dobj.C::a="<<Dobj.C::a<<endl;
//访问类D的从 C继承而来的 acout<<"Dobj.B::a="<<Dobj.B::a<<endl;//cout<<Dobj.a;//error
system("pause");return 0;} 程序执行后的显示结果如下:C::a=203B::a=101b,c,d=102, 204, 909-------------------Dobj.C::a=203Dobj.B::a=101
高级语言程序设计 ( 第 15 章继承 )
* 15.2.4 虚基类 (选读 )
多级混合继承情况下,若类D “ ”从两条不同 路径 同时对类 A进行了虚拟继承的话,则类D的对象中只包含着类 A的一个实例,这种继承也称为共享继承。被虚拟继承的基类 A被称为虚基类(注意,虚基类的说明是在定义派生类时靠增加关键字 virtual来指出的)。
说明格式:
class <派生类名> : virtual <派生方式> <基类名>
{ <派生类体> };
高级语言程序设计 ( 第 15 章继承 )
即是说,采用虚拟继承后的类间继承关系如下所示:
class Aclass B : virtual public Aclass C : virtual public Aclass D : public B, public C 存储结构示意:( ( (A) B C ) D ) “ ”让系统进行 干预 ,在派生类中只生成公共基类 A
的一个拷贝,从而可用于解决二义性问题。
高级语言程序设计 ( 第 15 章继承 )
应用示例:#include <iostream.h>class A {public:
int a;void showa(){cout<<"a="<<a<<endl;}
};class B: virtual public A //对类 A进行了虚拟继承{public:
int b;};
高级语言程序设计 ( 第 15 章继承 )
class C: virtual public A //对类 A进行了虚拟继承{public:
int c;}; class D : public B, public C //派生类D的二基类 B 、 C具有共同的基类 A ,但采用了虚拟继承
//从而使类D的对象中只包含着类 A的 1个实例{public:
int d;};
高级语言程序设计 ( 第 15 章继承 )
int main() {D Dobj; //说明D类对象Dobj.a=11; //若非虚拟继承时会出错 ! // -- “因为 D::a”具有二义性Dobj.b=22;Dobj.showa(); //若非虚拟继承时会出错 ! // -- “因为 D::showa”具有二义性cout<<"Dobj.b="<<Dobj.b<<endl;
} 程序执行后的显示结果如下:a=11Dobj.b=22
高级语言程序设计 ( 第 15 章继承 )
上机作业电子作业:
P575 编程项目 15_3
高级语言程序设计 ( 第 15 章继承 )
15.3 多态性
类有三大特性:封装性、继承性和多态性。多态性的是面象对象编程技术的核心组件之一。
多态性是指在类的派生过程中,通过动态联编(又晚期绑定)的技术,为一个函数名关联多种含义的能力。
高级语言程序设计 ( 第 15 章继承 )
15.3.1 虚函数的概念
1. 函数重载 (overloading)与静态联编(static binding)
函数重载( overloading)指的是,允许多个不同函数使用同一个函数名,但要求这些同名函数具有不同的参数表。
. 参数表中的参数个数不同 ; . 参数表中对应的参数类型不同 ; . 参数表中不同类型参数的次序不同。
高级语言程序设计 ( 第 15 章继承 )
(1)函数重载 (overloading)
例 有如下函数重载:int max(int a,int b);double max(double a,double b,double c);int main(){
cout<<max(2,3);cout<<max(12.3,10.0,20.0);
system("pause");return 0;} 系统对函数重载这种多态性的分辨与处理,是在编
译阶段完成的 -- 静态联编 (static binding) 。
高级语言程序设计 ( 第 15 章继承 )
编译多文件的 SP结构产生 *.exe文件的过程
可执行文件( *.exe )
预处理 (#) *.cpp+*.h
翻译单元(临时内
存)
*.obj
预处理 (#) *.cpp+*.h
翻译单元(临时内
存)
*.obj
……
编译
构建 标准类库
高级语言程序设计 ( 第 15 章继承 )
(2)静态联编 (static binding)
在编译时就已确定函数调用语句对应的函数体代码(位置) ,例:
…void swap (int x, int y); //赋值参数void swap (char* , char* );//字符指针参数…void swap (int x, int y){…} //函数体代码void swap (char* c1, char*c2 ){…}//函数体代码…注:普通函数、类的成员函数与内联函数都采用静态联编的方式 .
高级语言程序设计 ( 第 15 章继承 )
2. 函数超载、虚函数及动态联编
(1) 函数超载 (overriding)
允许多个不同函数使用完全相同的函数名、函数参数表函数返回类型;
仅在基类与其派生类的范围内实现;
高级语言程序设计 ( 第 15 章继承 )
class employee {string name;string ssn;
double net_pay;public:
employee(); employee (string the_name,string the_ssn); void print_check();
}; class hourlyemployee:public employee { //派生类
double wage_rate;double hours;
public:hourlyemployee();
void print_check();//函数超载()};
高级语言程序设计 ( 第 15 章继承 )
int main() {
employee emp (“zhang”,“2008001”),*pemp; hourlyemployee man (“张
”华 ,“001”,10,20),*phemp; pemp=phemp;pemp.print();//(静态联编) out: “ ”张华 ,“001”
system("pause"); return 0;}
高级语言程序设计 ( 第 15 章继承 )
(2) 虚函数 (virtual function)在定义某一基类 (或其派生类 )时,若将其中的某一函数
成员的属性说明为 virtual,则称该函数为虚函数 ,其一般说明格式为:
Virtual<返回类型><函数名>(<参数表>) {…}
① 允许多个不同函数使用完全相同的函数名、函数参数表、函数返回类型;
② 仅在基类与其派生类的范围内实现;③ 前面加上关键字: Virtual
虚函数的使用与函数超载密切相关。若基类中某函数被说明为虚函数,则意味着其派生类中也要用到与该函数同名、同参数表、同返回类型、但函数 (实现 )体不同的这同一个所谓的超载函
数。
高级语言程序设计 ( 第 15 章继承 )
(3) 动态联编 (dynamic binding)class employee {
string name;string ssn;
double net_pay;public:
employee(); employee (string the_name,string the_ssn); virtual void print_check();//动态联编(晚期绑
定) }; class hourlyemployee:public employee {//派生类
double wage_rate;double hours;
public:hourlyemployee(); virtual void print_check();//晚期绑定
};
高级语言程序设计 ( 第 15 章继承 )
动态联编(晚期绑定):int main() {
employee emp (“zhang”,“2008001”),*pemp; hourlyemployee man (“张
”华 ,“001”,10,20),*phemp; pemp=phemp;pemp.print();//晚期绑定
system("pause"); return 0;} 输出结果:张华 , 001,200
高级语言程序设计 ( 第 15 章继承 )
15.3.2 虚函数应用举例一例 15_3 销售类与虚函数class Sale
{protected:
double price;public:
Sale();Sale(double the_price);virtual double bill()const; //虚函数double savings(const Sale& other)const;
//如果购买的不是调用者对象,而是对象 other,则返回节省的金额。};bool operator<(const Sale& first,const Sale& second );//比较两个销售,看哪个较大
高级语言程序设计 ( 第 15 章继承 )
virtual virtual double Sale:: bill()const{// ——“注意:虚函数的实现时并不带关键字 virtual”
return price;}
double Sale::savings(const Sale& other)const//如果购买的不是调用者对象,而是对象 other,则返回节省的金额。
{return(bill()-other.bill());
}bool operator<(const Sale& first,const Sale& second )//比较两个销售,看哪个较大{
return(first.bill()<second.bill ());//动态编译}
高级语言程序设计 ( 第 15 章继承 )
class DiscountSale:public Sale{protected:
double discount;public:
DiscountSale();DiscountSale(double the_price,double
discount);virtual double bill()const;//派生类中的虚函数
}; double DiscountSale:: bill()const//实现时不能带关键
“词 virtual”{
return (1-discount/100)*price;}
高级语言程序设计 ( 第 15 章继承 )
int main(){Sale simple(10.0);//一件单价为¥ 10.0的产品DiscountSale discount(11.00,10);//一件单价为¥ 11.0,折扣为 10%的产品if(discount<simple){
cout<<"打折的产品比较便宜,打扣之后可节省 ";cout<<simple.savings(discount)<<"元。 \
n";}else cout<<"打折的产品并不便宜。 \n";
system("pause");return 0;}
高级语言程序设计 ( 第 15 章继承 )
程序的两种运行结果:
采用虚函数方式之后的运行结果:打折的产品比较便宜,打扣之后可节省 0.10 元。
不采用虚函数的运行结果:打折的产品并不便宜。
高级语言程序设计 ( 第 15 章继承 )
例 15_4 虚函数来解决切片问题
class pet //定义一个宠物类{public:
string name;//void print(); virtual void print();
};class dog:public pet //定义一个宠物 breed狗类{public:
string breed;//breed 是一个狗的品种//void print();virtual void print();
};
高级语言程序设计 ( 第 15 章继承 )
//类的实现部分void pet::print(){
cout<<"宠物的名字是: "<<endl;}
void dog::print(){
cout<<"宠物的名字是: "<<name<<endl;cout<<"宠物的品种是 "<<breed<<endl;
}
高级语言程序设计 ( 第 15 章继承 )
切片问题的解决方法dog *pdog;//指向派生类的一个指针pdog=new dog;pdog->name =vdog.name ;pdog->breed =vdog.breed ;pet *ppet;//指向基类的一个指针ppet=pdog;//一个基类的指针指向派生类型ppet->print();
// 切片问题在静态编译时已经产生,动态编译即虚函数方式:程序执行进确定调用哪个输出函数
pdog->print();}
高级语言程序设计 ( 第 15 章继承 )
高级语言程序设计 ( 第 15 章继承 )
仅有虚函数也不能解决切片问题:int main(){
system("color fc");pet vpet;dog vdog;vdog.name="Tiny(珍妮 )";vdog.breed="Great Dane";vpet=vdog;
//切片问题在调用虚函数前已经产生, vpet的值已确定cout<<"基类型及派生类对象的输出结果: "<<endl;vpet.print();//动态联编中的切片问题,cout<<endl;
vdog.print();system("pause");return 0;}
高级语言程序设计 ( 第 15 章继承 )
总结在面向对象编程中,利用虚函数的动态联编体
现类的多态性,如解决切片问题,须记住以下两条编程原则:( 1 )将类的某些成员定义成虚函数实现动态联编;( 2 )利用指向基类和派生类指针的赋值兼容性,实现类的多态性。
高级语言程序设计 ( 第 15 章继承 )
15.3.3虚函数与类的多态性应用举例二 在销售类与虚函数中我们讲到:定义时利用基类 Sale的引用:bool operator<(const Sale& first,const Sale& second
){return(first.bill()<second.bill ());//动态编译
}调用时为其赋一个派生类对象: Sale& first= discountif(discount<simple)
{cout<<"打折的产品比较便宜,打扣之后可节省 ";cout<<simple.savings(discount)<<"元。 \
n";}
高级语言程序设计 ( 第 15 章继承 )
利用虚函数的动态联编体现类的多态性,一般采用两种方法,先将类的某些成员定义成虚函数实现动态
联编; 再:( 1)利用派生类对象初始化基类对象的引用,实现类的多态性,如:
Sale& first= discount( 2)利用指向基类和派生类指针的赋值兼容性,实现类的多态性。
Base* point=&derevied_object
高级语言程序设计 ( 第 15 章继承 )
下面我们要讲到定义时:void paint(Figure *pg ) //“ ”画 出图形的各图元{
pg->draw(); }调用时: paint(&cirle_object)
高级语言程序设计 ( 第 15 章继承 )
编程项目 15_8 :利用图元类画图 “见例程 15_8 虚函数与图形系统”
本程序自定义 pixel、 graphelem、 line、rectangle、 triangle、 circle、 square、 figure等 8个类(类型)并对它们进行使用。
实现要点: 1. 设立并处理以下图元类 : 直线 (line类 ),矩形 (rectangle类 ),三角形
(triangle类 ),圆 (circle类 ),正方形 (square类 )。
2. 将每一种图元设计成一个类,在每一个类的定义中,除含有其构造函数外,还包含一个可将本类的图元画出来的公有函数 draw 。
高级语言程序设计 ( 第 15 章继承 )
3.设立一个基类 Figure,由于不准备在基类虚函数 draw中“做任何事情,,所以在其原型后加上 =0”字样而构成纯虚函
数。从而使 Figure 成为抽象基类。 5. 程序中至少要设立具有以下关系的六个类: 抽象基类 Figure ; 由直接 Figure派生出的四个类:line, rectangle, triangle, circle ;
由 rectangle派生出一个类: square 。 6. “ ” “ ”由于 画 以上图元时,都要用到 点 的概念与位置,所以设立的第七个类为: pixel 。7. 为了通过虚函数进行动态联编处理,需说明并使用指向基类Figure的指针 ,而后通过这些指针的动态取值 (使它们指向不同的派生类 ) “,进而利用函数调用 pg->draw( )” “ ”来 画 出组成一个图形的不同图元来。虽然 pixel这个类与其它六个类没有继承和派生的关系,但却有成员关系。类 pixel的对象要作为派生类的成员和构造函数成员的参数。
高级语言程序设计 ( 第 15 章继承 )
7. 为了通过虚函数进行动态联编处理,需说明并使用指向基类 Figure的指针 ,而后通过这些指针的动态取值 (使它们指向不同的派生类 ),进而利用函数
“调用 pg->draw( )” “ ”来 画 出组成一个图形的不同图元来。虽然 pixel这个类与其它六个类没有继承和派生的关系,但却有成员关系。类 pixel的对象要作为派生类的成员和构造函数成员的参数。
高级语言程序设计 ( 第 15 章继承 )
本程序要用到的类及关系:
基类 Figure :virtual draw()
line :draw()
rectangle :draw()
sauqre :draw()
circle :draw()
triangle :draw()
点类: pixel
高级语言程序设计 ( 第 15 章继承 )
程序各功能模块及关系:void
paint(Figure *pg )
{
pg->draw(); }
主函数 :main()
与各类的接口paint(figure* pg)
菜单界面及选择menuselect()
Circle:draw()
Line:draw()
trangledraw()
Rectangle:draw()
Square:draw()
菜单处理函数menuhandle()
高级语言程序设计 ( 第 15 章继承 )
//基类 Figure(实际为抽象基类)
class Figure{ public:
virtual void draw( )=0; //纯虚函数 draw void erase();//清屏
};
高级语言程序设计 ( 第 15 章继承 )
//派生类 lineclass line:public Figure{
pixel start, end; //成员为 pixel类对象 public:
line(pixel sta, pixel en) :start(sta),end(en){ }; virtual void draw( );
//虚函数 draw:在两点 start(x,y)—end(x,y)之间画一
//条直线};
高级语言程序设计 ( 第 15 章继承 )
//矩形派生类 rectangleclass rectangle:public Figure{
pixel ulcorner,lrcorner; public:
rectangle(pixel ul,pixel el):ulcorner(ul),lrcorner(el)
{};virtual void draw( );
//虚函数 draw:从左上角 ulcorner ( x,y)至右下角
//lrcorner(x,y) 画一个矩形};
高级语言程序设计 ( 第 15 章继承 )
//圆形派生类 circleclass circle:public Figure{
pixel center; int radius; public:
circle(pixel cen,int rad) :center(cen){radius=rad;
};virtual void draw( );
//虚函数 draw// 以点 center( x,y)为圆心,以 radius 为半径画一个圆};
高级语言程序设计 ( 第 15 章继承 )
//派生类 triangleclass triangle:public Figure{
pixel pointa,pointb,pointc; public:
triangle(pixel pa,pixel pb,pixel pc) :pointa(pa),pointb(pb),pointc(pc)
{};virtual void draw( ); //虚函数 draw};
高级语言程序设计 ( 第 15 章继承 )
//派生类 squareclass square:public rectangle { public:
square(pixel ul,int lh) :rectangle(ul,pixel(ul.getx()+6*lh,ul.gety()+lh))
//6*lh:调整纵横比{ };virtual void draw( ); //虚函数 draw
};void square::draw( ){
rectangle::draw ();cout<<"draw()被调用:画正方形。 "<<endl;}
高级语言程序设计 ( 第 15 章继承 )
//配合菜单,与各派生类的虚函数的接口函数void paint(Figure *pg ) //“ ” 画 出图形的各图元{
pg->draw(); }
高级语言程序设计 ( 第 15 章继承 )
//菜单选择函数,选择变量用字符型char menuselect( void){ //界面设计char str[80];do{
cout<<" 1.画一条直线 ."<<endl;cout<<" 2.画一个矩形 ."<<endl;cout<<" 3.画一个三角形 ."<<endl;cout<<" 4.画一个正方形 ."<<endl;cout<<" 5.画一个圆 ."<<endl;cout<<" 6.退出程序。 "<<endl;cout<<" 请输入序号( 1--6): ";
//设定选择变量cin.getline(str,80);cout<<endl;
}while(str[0]>'6'||str[0]<'1');return str[0];
}
高级语言程序设计 ( 第 15 章继承 )
void menuhandle(void){
//调用 paint “ ”函数, 画 出图元line ln(pixel(10,20),pixel(70,20));rectangle rec(pixel(5,20),pixel(70,24)); //长方形
square sq(pixel(20,15),5); triangle
tri(pixel(12,40),pixel(18,20),pixel(18,60));//三角
circle cir(pixel(40,15),10); //圆
高级语言程序设计 ( 第 15 章继承 )
while(true){//根据菜单选择函数的返回值调用相应的处理函数switch(menuselect()){case '1':
paint(&ln); break;case '2':
paint(&rec); break;case '3':
paint(&tri); break;case '4':
paint(&sq); break;case '5':
paint(&cir); break;case '6':
cout<<"再见! \n"; exit(0);}//switch
cout<<"请继续! "<<endl;}//while}
高级语言程序设计 ( 第 15 章继承 )
int main(){
system("color f4");//调用菜单处理函数menuhandle();
system("pause"); return 0;}
高级语言程序设计 ( 第 15 章继承 )
主函数 :main()
与各类的接口paint(figure* pg)
菜单界面及选择menuselect()
Circle:draw()
Line:draw()
trangledraw()
Rectangle:draw()
Square:draw()
菜单处理函数menuhandle()
高级语言程序设计 ( 第 15 章继承 )
( 1)利用派生类对象初始化基类对象的引用,实现类的多态性,如:
Sale& first= discount( 2)利用指向基类和派生类指针的赋值兼容性,实现类的多态性。
Base* point=&derevied_object
高级语言程序设计 ( 第 15 章继承 )
上机作业电子作业:
P575 编程项目 15_4, 15_9
高级语言程序设计 ( 第 15 章继承 )
实验一 继承和派生
实验类型:验证性实验目的:1. 学习定义和使用类的继承关系、定义派生类;2. 熟悉不同继承方式下对基类成员的访问控制;实验内容:
参照编程项目 15_3 ,定义基类及其派生类,观察分析运行结果。
上交实验报告。
高级语言程序设计 ( 第 15 章继承 )
实验二 多态性实验类型:验证性实验目的:
1. 理解函数超载与动态联编的概念;2.会使用虚函数实现类的多态性;
实验内容:1. 参照编程项目 15_9,编写程序,声明一个
基类,再派生出两个以上的派生类,采用虚函数的方法实现动态联编;
2. 运行程序,分析程序的运行结果,写出实验报告书。
高级语言程序设计 ( 第 15 章继承 )
补: 纯虚函数与抽象基类示例——定积分计算
如果不准备在基类的虚函数中做任何事情,则可使用如下的格式将该虚函数说明成纯虚函数: virtual <函数原型>=0;
高级语言程序设计 ( 第 15 章继承 )
纯虚函数不能被直接调用,它只为其派生类的各虚函数规定了一个一致的“原型规格”(该虚函数的实现将在它的派生类中给出)。
含有纯虚函数的基类称为抽象基类。注意,不可使用抽象基类来说明并创建它自己的对象,只有在创建其派生类对象时,才有抽象基类自身的实例伴随而生。
实际上,抽象基类是其各派生类之共同点的一个抽象综合,通过它,再“加上”各派生类的特有成员以及对基类中那一纯虚函数的具体实现,方可构成一个具体的实用类型。
另外:如果一个抽象基类的派生类中没有定义基类中的那一纯虚函
数、而只是继承了基类之纯虚函数的话,则这个派生类还是一个抽象基类(其中仍包含着继承而来的那一个纯虚函数)。
高级语言程序设计 ( 第 15 章继承 )
【补充例题】用纯虚函数来实现辛普生法求函数的定积分的通用算法。辛普生法求定积分是将积分区间划分 n 等份,定积分的值就是各个小曲边梯形的面积和,相邻 2 个曲边梯形的 3 个边与函数曲线有 3 个交点,考虑过这 3 个点可作出一条抛物线,用这条抛物线来近似代替这一段函数曲线求面积,这就是辛普生法,也称为抛物线法。用这种方法推导出的求定积分的近似公式如下:
其中, yi=f(xi)=f(x0 +iΔx)=f(x0+i(b-a)/n),a 为积分下限, b 为积分上限, i=0,1,2,...,n
设计一个抽象类用于求定积分,被积函数设为纯虚函数,再设计几个派生类用于求具体函数的积分。
))(2(43
1)( 2421310
b
nnn
a
yyyyyyyyxdxxf
高级语言程序设计 ( 第 15 章继承 )
class Simpson //Intevalue积分值, a积分下限, b积分上限{ double Intevalue,a,b,n;// n 为积分区间等分份数 public: virtual double fun(double x)=0; //被积函数声明为纯虚函数 Simpson(double ra=0,double rb=0,int rn=100) {a=ra;b=rb;Intevalue=0;n=rn;} void Integrate( ){ double dx; int i; dx=(b-a)/n; Intevalue=fun(a)+fun(b); for(i=1;i<n;i+=2) Intevalue+=4*fun(a+dx*i); for(i=2;i<n;i+=2) Intevalue+=2*fun(a+dx*i); Intevalue*=dx/3; } void Print(){cout<<"积分值 ="<<Intevalue<<endl;}}
【补充例题】辛普生法求定积分类:
高级语言程序设计 ( 第 15 章继承 )
class A:public Simpson{public: A(double ra,double rb,int rn):Simpson(ra,rb,rn){ }; double fun(double x){return sin(x) ;}};class B:public Simpson{//B 也可以说明为由 A 派生,更利于说明动态多态性public: B(double ra,double rb,int rn):Simpson(ra,rb,rn){ }; double fun(double x){return exp(x) ;}};
【补充例题】辛普生法求定积分在派生类中加被积函数:
高级语言程序设计 ( 第 15 章继承 )
【补充例题】辛普生法求定积分#include<iostream>#include<cmath>using namespace std;int main(){ A a1(0,3.1416/2,200); Simpson *s=&a1; s->Integrate( ); //动态联编 B b1(0,1,200); b1.Integrate( ); //静态联编 s->Print( ); b1.Print( );system("pause");}运行该程序,结果为 1、 1.71828再使用不定积分方法计算,比较结果。