1.22k likes | 1.36k Views
第十五章 继承. 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. 派生类的概念 生物的层次关系: 生物 →植物 →微生物 → 动物→ 人 →野生动物 →家畜→鸭子
E N D
第十五章 继承 • 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. 派生类的概念 • 生物的层次关系: • 生物 →植物 • →微生物 • → 动物→ 人 • →野生动物 • →家畜→鸭子 • 继承是我们得以用简单的方法理解、描述客观存在应该关键。C++面向对象的程序设计中,用不同的类定义描述客观现实, “继承” 是类间的一种常用关系,它使得我们用一个已经存在的类(基类)来定义一个新类(派生类)。
如class A class B • 基类 派生类 • 学生类 研究生类 • 又例如,公司雇员档案的管理: • employee(雇员): 姓名、年龄、工资; • manager(经理): 姓名、年龄、工资、行政级别; • engineer(工程师): 姓名、年龄、工资、专业、学位; • director(高级主管):姓名、年龄、工资、专业、学位、职务。 • C++提供了类定义的派生和继承功能,能很好地解决上述问题(使代码可重用,避免重复!)。
employee manager engineer director 姓名、年龄、工资 姓名、年龄、工资、行政级别 姓名、年龄、工资、专业、学位 姓名、年龄、工资、专业、学位、职务 • 若类A是类B的基类(父类),则类B是类A的派生类(子类)。 • 也可以说,类B(子类)继承了类A(父类);或说,类A(父类)派生出类B(子类)。
2. 派生类的定义格式 • class <派生类类型名> :<基类表> • { • private: • <各私有成员说明>; • public: • <各公有成员说明>; • protected: • <各保护成员说明>; • <以关键字friend开头的友元说明>; • };
<基类表>的一般格式为: • <派生方式> <基类名1>,... ,<派生方式> <基类名n> • <派生方式>又可为: • private、public或protected。
定义一个类: • class employee • { • //employee类(类型),将作为其它几个类的基类 • private: • short age; • float salary; • protected: • char * name; • };
定义一个派生类manager : • class manager:public employee //派生类 • { • private: • int level; • public: • manager(short ag, float sa, char* na, int lev) • :employee (ag,sa,na)//调用基类构造函数 • { //对基类初始化负责 • level=lev; • } • };
3.派生方式与权限继承 • 派生方式(基类 在基类中的 在派生类中 • 的被继承方式) 存取权限 的存取权限 • ================================================== • public public public • public potected protected • public private (inaccessible) • potected public protected • potected potected protected • potected private (inaccessible) • private public private • private potected private • private private (inaccessible) • ==================================================
注意: • public派生方式:使基类的公有成员和保护成员在派生类中仍然是公有成员和保护成员,而基类的私有成员不可在派生类中被存取。 • protected派生方式:使基类的公有成员和保护成员在派生类中都变为保护成员,而基类的私有成员不可在派生类中被存取。 • private派生方式:使基类的公有成员和保护成员在派生类中都变为私有成员,而基类的私有成员不可在派生类中被存取。
派生类中可出现四种成员: • 1) 不可访问的成员 -- 基类的private私有成员被继承过来后,这些成员在派生类中是不可访问的。 • 2) 私有成员 -- 包括在派生类中新增加的private私有成员以及从基类私有继承过来的某些成员。这些成员在派生类中是可以访问的。 • 3) 保护成员 -- 包括在派生类中新增加的potected保护成员以及从基类继承过来的某些成员。这些成员在派生类中是可以访问的。 • 4) 公有成员 -- 包括在派生类中新增加的public公有成员以及从基类公有继承过来的基类的public成员。这些成员不仅在派生类中可以访问,而且在建立派生类对象的模块中,也可以通过对象来访问它们。
例 继承与派生关系中成员的访问权限 • 分析下述程序中的继承与派生关系 • class B • { • int priDat; • protected: • int proDat; • public: • int pubDat; • };
class D11 : public B { • public: • void useBData() { • pubDat=11; //OK! • proDat=12; //OK! • priDat=13; //ERROR! • } • };
class D21 : public D11 { • public: • void useBData() { • pubDat=111; //OK! • proDat=121; //OK! • priDat=131; //ERROR! • } • };
class D12 : protected B { • public: • void useBData() { • pubDat=21; //OK! • proDat=22; //OK! • priDat=23; //ERROR! • } • };
class D22 : public proD12 { • public: • void useBData() { • pubDat=211; //OK! • proDat=221; //OK! • priDat=231; //ERROR! • } • };
class D13 : private B { • public: • void useBData() { • pubDat=31; //OK! • proDat=32; //OK! • priDat=33; //ERROR! • } • };
class D23 : public priD13 { • public: • void useBData() { • pubDat=311; //ERROR! • proDat=321; //ERROR! • priDat=331; //ERROR! • } • };
15.1.2 派生类的构造函数和析构函数 派生类的构造函数的一般格式如下: <派生类名>(<参数总表>):<初始化列表> { <构造函数体> } 而 <初始化列表> 按如下格式构成: <基类名1>(<基类参数表1>), ... ,<基类名n>(<基类参数表n>),<对象成员名1>(<对象成员参数表1>), ... ,<对象成员名m>(<对象成员参数表m>) (注:若无对象成员时,则不出现此后半部分;基类名与对象成员名的次序无关紧要,各自出现的顺序可以任意)
派生类构造函数执行的一般次序如下: (1) 调用各基类的构造函数,调用顺序为继承时的声明顺序。 (2) 若派生类含有对象成员的话,调用各对象成员的构造函数,调用顺序按照声明顺序。 (3) 执行派生类构造函数的函数体。 析构派生类对象时,其执行次序恰与构造时的次序相反。
例 继承与派生关系中对构造函数及析构函数的调用 • #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;}; • };
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); • }
运行结果为: • CB::b=4 • CC::c=6 • CD::d=2 • CDobj is destructing • CCobj is destructing • CBobj is destrcting • 思考:将派生类CD改写为如下形式后,请给出输出结果。
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;}; • };
输出: • CB::b=4 • CC::c=6 • CC::c=106 • CB::b=104 • CD::d=2 • CDobj is destructing • CBobj is destrcting. • CCobj is destructing • CCobj is destructing • CBobj is destrcting
合同员工 抽象的员工 计时工 姓名 社会安全号 薪水(空) 姓名 社会安全号 工作时数 小时工资 姓名 社会安全号 月薪/周薪/年薪 4.使用举例 ——公司员工工资的管理
基类 • 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; • };
派生类(计时工) • 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();//注意与基类的成员函数声明完全相同 • };
//派生类的构造函数要完成所有成员的初始化工作://派生类的构造函数要完成所有成员的初始化工作: • 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) • { • //函数体为空。 • }
在派生类中重定义基类中的成员函数 • //基类的成员函数声明完全相同 • 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"; • }
思考: • 基类中的成员函数void print_chedk()能不能不要,它有什么作用? • 作业: • P575 15.1
15.2 继承细节 • 15.2.1 不继承的函数 • 15.2.2 与基类对象和派生类对象相关的赋值兼容性问题 • 15.2.3 派生关系中的二义性处理 • 15.2.4 虚基类
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(); • };
salaredemployee::salaredemployee(string the_name,string the_ssn ,double the_weekly_salary) • :employee(the_name,the_ssn), • //必须(显示)的调用基类的构造函数, • //来对继承的成员行初始化工作。 • salary(the_weekly_salary) • //对新派生的成员完成初始化的工作 • { • //函数体为空。 • }
人员类 派生学生类 姓名 编号 姓别 年龄 姓名 编号 姓别 年龄 数学 语文 平均分 2.复制构造函数 • 派生类中的复制构造函数当然也是不继承的。 • 参见例程“15_2person类与继承细节”。
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);//拷贝复制函数的声明 • …… • };
拷贝复制函数实现 • 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++; • }
基于人员类的派生类——学生类 • 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();//输出表头 • };
派生类中拷贝复制函数的实现 • student::student(const student &obj) • :person(obj)//显示调用基类是的复制构造函数 • { • math=obj.math; • chinese=obj.chinese; • ave=obj.ave; • }
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);//赋值运算符的重载 • };
基类中的赋值运算符的重载 • 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++; • }
若要使运算兼容: • (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;//返回调用对象变量本身 • }
派生类中的赋值运算符的重载 • 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);//赋值运算符的重载 • };
派生类中赋值运算符的重载的实现 • void student::operator=(const student& right_obj)//赋值运算符的重载 • { • person::operator =(right_obj); • math=right_obj.math; • chinese=right_obj.chinese; • ave=right_obj.ave; • }
派生类中赋值运算符重载实现(增强版) • 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; 返回调用对象变量地址,即指针
4.派生类中的友元函数 • 派生类中的友元函数是不继承的,准备作为基类的类在其操作中,尽量不用友员函数而用成员函数来实现。但插入与提取运算符重载除外。
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; }
4.派生类中的析构函数 • 析构函数是不继承的。 • //基类中的析构函数 • person::~person() • { • delete name,no,sex; • } • //派生类中的析构函数 • student::~student() • { • person::~person(); • }
15.2.2 与基类对象和派生类对象相关的赋值兼容性问题 • 1. 基类对象 = 派生类对象; //OK! 切片问题 • 派生类对象 = 基类对象; //ERROR! • 2. 指向基类型的指针 = 派生类对象的地址; //OK! • 指向派生类类型的指针 = 基类对象的地址; //ERROR! • 注:访问非基类成员部分时,要经过指针类型的强制转换 。