大一下第二学期期中知识复习梳理 之 c++继承与多态
一、继承性
1、基本概念
派生:以一个(或多个)已经存在的类为基础,定义新的类。
目的:代码复用
单继承:只有一个直接基类(人→学生)
多重继承:有多个直接基类(学生+老师→助教)
2、派生方式:公有、私有、保护
(1)派生过程
(2)派生类访问限定
(a)类访问限定:公有(public)/私有(private)/保护(protected)
默认私有
类成员函数(内部访问):可以访问所有
类对象(外部访问):只能访问公有
(b)派生类访问限定
派生类成员函数(内部访问):只要基类成员非私有。
派生类对象(外部访问):只能访问 基类成员公有+派生类公有。
3、定义,构造函数与析构函数
(1)定义
(2)派生类重写构造函数
(a)原因:
初始化基类成员(未覆盖的):调用基类构造函数完成
初始化基类成员(覆盖的):派生类构造函数完成
初始化派生类成员(内置数据类型):派生类构造函数完成
初始化派生类成员(类对象):调用成员对象构造函数完成
(b)派生类构造函数定义格式
派生类名::派生类名(参数总表)
:基类名1(参数名表1),
基类名2(参数名表2),
….,
成员对象1(成员对象参数名表1),
成员对象2(成员对象参数名表2),
…
{
……..
}
ps/基类名、成员对象无顺序影响(任意顺序),决定性的为类定义时顺序。
派生类构造函数定义也可写为:B::B(char a,int b,C c): A(a),m_c(c),m_b(b) {}
(c)构造函数的执行顺序
①依次执行各个直接基类的构造函数:按派生类定义的先后顺序(不是按派生类构造函数
中的先后顺序)【如果直接基类本身也是派生类,则再先调用其直接
基类的构造函数)】;
②依次执行成员对象的构造函数:按派生类定义的先后顺序(不是按派生类构造函数
中的先后顺序);
③执行派生类定义的构造函数。
(3)派生类重写析构函数
(a)定义格式
class B{
…
public:
~B(); //声明
}
//定义
B::~B() {…};
(b)析构顺序(与构造顺序相反)
①执行派生类的析构函数
②依次执行成员对象的析构函数(按构造时定义先后)
③依次执行直接基类的析构函数(按构造时定义先后)
例子:
【例8.1】派生类定义与使用
解析:
#include<iostream> #include<cstring> using namespace std; enum Tsex{mid,man,woman}; struct course{ string coursename; int grade; }; class Person{ string IdPerson; //身份证号,18位数字 string Name; //姓名 Tsex Sex; //性别 int Birthday; //生日,格式1986年8月18日写作19860818 string HomeAddress; //家庭地址 public: Person(string, string,Tsex,int, string); Person(); ~Person(); void SetName(string); string GetName(){return Name;} void SetSex(Tsex sex){Sex=sex;} Tsex GetSex(){return Sex;} void SetId(string id){IdPerson=id;} string GetId(){return IdPerson;} void SetBirth(int birthday){Birthday=birthday;} int GetBirth(){return Birthday;} void SetHomeAdd(string ); string GetHomeAdd(){return HomeAddress;} void PrintPersonInfo(); }; Person::Person(string id, string name,Tsex sex,int birthday, string homeadd){ IdPerson=id; Name=name; Sex=sex; Birthday=birthday; HomeAddress=homeadd; } Person::Person(){ IdPerson="#";Name="#";Sex=mid; Birthday=0;HomeAddress="#"; } Person::~Person(){} void Person::SetName(string name){ Name=name; //拷入新姓名 } void Person::SetHomeAdd(string homeadd){ HomeAddress=homeadd; } void Person::PrintPersonInfo(){ int i; cout<<"身份证号:"<<IdPerson<<'\n'<<"姓名:"<<Name<<'\n'<<"性别:"; if(Sex==man)cout<<"男"<<'\n'; else if(Sex==woman)cout<<"女"<<'\n'; else cout<<" "<<'\n'; cout<<"出生年月日:"; i=Birthday; cout<<i/10000<<"年"; i=i%10000; cout<<i/100<<"月"<<i%100<<"日"<<'\n'<<"家庭住址:"<<HomeAddress<<'\n'; } class Student:public Person{ //定义派生的学生类 string NoStudent; //学号 course cs[30]; //30门课程与成绩 public: Student(string id, string name,Tsex sex,int birthday, string homeadd, string nostud); //注意派生类构造函数声明方式 Student(); ~Student(); int SetCourse(string,int); int GetCourse(string); void PrintStudentInfo(); }; Student::Student(string id, string name,Tsex sex,int birthday, string homeadd, string nostud) :Person(id,name,sex,birthday,homeadd){ int i ; NoStudent=nostud; for(i=0;i<30;i++){ //课程与成绩清空,将来由键盘输入 cs[i].coursename="#"; cs[i].grade=0; } } Student::Student(){ //基类默认的无参数构造函数不必显式给出 int i; NoStudent="#"; for(i=0;i<30;i++){ //课程与成绩清空,将来由键盘输入 cs[i].coursename="#"; cs[i].grade=0; } } Student::~Student(){} int Student::SetCourse(string coursename,int grade){ //设置课程 int i; bool b=false; //标识新输入的课程,还是更新成绩 for(i=0;i<30;i++){ if(cs[i].coursename=="#"){ //判断表是否进入未使用部分 cs[i].coursename=coursename; cs[i].grade=grade; b=false; break; } else if(cs[i].coursename==coursename){ //是否已有该课程记录 cs[i].grade=grade; b=true; break; } } if(i==30) return 0; //成绩表满返回0 if(b) return 1; //修改成绩返回1 else return 2; //登记成绩返回2 } int Student::GetCourse(string coursename){ int i; for(i=0;i<30;i++) if(cs[i].coursename==coursename) return cs[i].grade; return -1; }//找到返回成绩,未找到返回-1 void Student::PrintStudentInfo(){ int i; cout<<"学号:"<<NoStudent<<'\n'; PrintPersonInfo(); for(i=0;i<30;i++)//打印各科成绩 if(cs[i].coursename!="#") cout<<cs[i].coursename<<'\t'<<cs[i].grade<<'\n'; else break; cout<<"--------完-------- "<<endl; } int main(void){ char temp[30]; int i,k; Person per1("320102820818161","沈俊",man,19820818,"南京四牌楼2号"); Person per2; per2.SetName("朱明"); per2.SetSex(woman); per2.SetBirth(19780528); per2.SetId("320102780528162"); per2.SetHomeAdd("南京市成贤街9号"); per1.PrintPersonInfo(); per2.PrintPersonInfo(); Student stu1("320102811226161","朱海鹏",man,19811226,"南京市黄浦路1号","06000123"); cout<<"请输入各科成绩:"<<'\n'; //完整的程序应输入学号,查找,再操作 while(1){ //输入各科成绩,输入"end"停止 cin>>temp; //输入格式:物理 80 if(!strcmp(temp,"end")) break; cin>>k; i=stu1.SetCourse(temp,k); if(i==0) cout<<"成绩列表已满!"<<'\n'; else if(i==1) cout<<"修改成绩"<<'\n'; else cout<<"登记成绩"<<'\n'; } stu1.PrintStudentInfo(); while(1){ cout<<"查询成绩"<<'\n'<<"请输入科目:"<<'\n'; cin>>temp; if(!strcmp(temp,"end")) break; k=stu1.GetCourse(temp); if(k==-1)cout<<"未查到"<<'\n'; else cout<<k<<'\n'; } return 0; }
4、多重继承:多个基类共同派生出新的派生类
#include<iostream> #include<cmath> using namespace std; class Circle{ protected: //Circle类对象不能访问,但公有派生类的成员函数可以访问 float x,y,r; //(x,y)为圆心,r为半径 public: Circle(float a=0,float b=0,float R=0){x=a;y=b;r=R;} void Setcoordinate(float a,float b){x=a;y=b;} void Getcoordinate(float &a,float &b){a=x;b=y;} void SetR(float R){r=R;} float GetR(){return r;} float GetAreaCircle(){return float(r*r*3.14159);} float GetCircumference(){return float(2*r*3.14159);} }; class Line{ protected: //Circle类对象不能访问,但公有派生类的成员函数可以访问 float High; public: Line(float a=0){High=a;} void SetHigh(float a){High=a;} float GetHigh(){return High;} }; //多重继承派生出圆锥 class Cone:public Circle,public Line{ public: Cone(float a,float b,float R,float d):Circle(a,b,R),Line(d){} float GetCV(){ //计算体积 return float(GetAreaCircle()*High/3);} float GetCA(){ //计算表面积 return float(GetAreaCircle()+r*3.14159*sqrt(r*r+High*High)); }//公有派生类中能直接访问直接基类的保护成员 }; int main(){ Cone c1(5,8,3,4); float a,b; //派生类Cone成员函数 cout<<"圆锥体积:"<<c1.GetCV()<<'\n'; cout<<"圆锥表面积:"<<c1.GetCA()<<'\n'; //基类Circle成员函数 cout<<"圆锥底面积:"<<c1.GetAreaCircle()<<'\n'; cout<<"圆锥底周长:"<<c1.GetCircumference()<<'\n'; cout<<"圆锥底半径:"<<c1.GetR()<<'\n'; c1.Getcoordinate(a,b); cout<<"圆锥底圆心坐标:("<<a<<','<<b<<")\n"; //基类Line成员函数 cout<<"圆锥高:"<<c1.GetHigh()<<'\n'; return 0; }
(a)成员变量的二义性问题
例子:成员变量No
5、虚基类
(a)问题&解决
(b)定义方式
//普通继承方式
class Student: public Person{…};
//虚拟继承方式
class Student: virtual public Person{…};
class Student: public virtual Person{…};
class Person {…};
class Student: virtual public Person{…};
class GStudent: public Student{…};
class Employee: virtual public Person{…};
class EGStudent: public GStudent, public
Employee{…};
(c)构造函数
i. 定义
EGStudent::EGStudent(int NoStu, int NoGStu, int NoEmpl, int No)
:GStudent(No, NoStu, NoGStu), //直接基类
Employee(No, NoEmpl), //直接基类
Person(No) //间接基类
{…}
GStudent, Employee:直接基类(多重继承)
Person:间接基类,作为虚基类,必须显式给出构造函数
ii. 调用顺序
EGStudent::EGStudent(int NoStu, int NoGStu, int NoEmpl, int No)
:GStudent(NoStu, NoGStu),
Employee(NoEmpl),
Person(No)
{…}
1. 虚基类(Person)
2. 直接基类(GStudent, Employee)
3. 自身(EGStudent)
(d)析构函数:与构造函数相反
#include<iostream> using namespace std; class Object{ public: Object(){cout<<"constructor Object\n";} ~Object(){cout<<"deconstructor Object\n";} }; class Bclass1{ public: Bclass1(){cout<<"constructor Bclass1\n";} ~Bclass1(){cout<<"deconstructor Bclass1\n";} }; class Bclass2{ public: Bclass2(){cout<<"constructor Bclass2\n";} ~Bclass2(){cout<<"deconstructor Bclass2\n";} }; class Bclass3{ public: Bclass3(){cout<<"constructor Bclass3\n";} ~Bclass3(){cout<<"deconstructor Bclass3\n";} }; class Dclass:public Bclass1,virtual Bclass3,virtual Bclass2{ //默认私有 Object object; public: Dclass():object(),Bclass2(),Bclass3(),Bclass1() {cout<<"派生类建立!\n";} ~Dclass(){cout<<"派生类析构!\n";} }; int main() { Dclass dd; cout<<"主程序运行!"<<endl; return 0; }
6、应用讨论: 公有派生与赋值兼容规则
(a)公有派生类的对象
属性/操作:派生类吸收基类所有成员(除构造/析构函数)//直接赋值、指针、引用。
访问限定:派生类对象访问(外部访问)基类成员,与基类对象一样)
//注意理解难点!(下)
(b)赋值兼容规则:公有派生类对象可以代替基类对象使用(只能向父亲兼容)
可以将派生类对象当作基类使用,反之不行。
(i)派生类的对象可以赋值给基类对象; 反之不行,即不能将基类对象赋值给派生类对象
(ii)派生类对象的地址可以赋值给基类的指针,通过该基类指针访问基类成员,但不能访问派生类新成员 ; 反之不行,即不能将基类对象地址赋值给派生类指针,通过该派生类指针访问。
(iii)派生类对象可用于初始化基类对象的引用 ; 反之不行,即不能用基类对象初始化派生类对象的引用。
(c)赋值兼容规则下的成员函数定义
(i)复制构造函数
(ii)赋值操作符重载函数
(d)派生类 vs. 类模板
虚基类:解决多重继承;
虚函数、重载函数:解决多态。
二、多态性
1、概念
接口相同(函数名相同,“开”)
功能不同(函数体不同,具体“开”的方式不同)
编译时的多态性(静态):通过重载函数实现
运行时的多态性(动态):通过虚函数(virtual function)实现
2、虚函数
(1)派生类中可定义基类的覆盖(override)函数,
虚函数:一类特殊的覆盖函数
与普通被覆盖的函数相比:相同点:都是函数名相同,参数相同,返回值相同 ; 不同点:虚函数可实现运行时的多态,普通的同名覆盖则不能。
(2)虚函数的例外:特殊情况下返回值可以不同
基类中虚函数返回值是基类指针时,派生类中对应虚函数返回值可以是派生类的指针
(3)虚函数的前提
① 基类→派生类 ; ② 类的成员函数
(4) 如果基类的某个成员函数是虚函数,则在其派生类,派生类的派生类……,该函数始终是虚函数;虚函数可实现运行时的多态,以牺牲速度为代价,换来通用性。
(5)定义
virtual 返回类型 函数名(参数表){…};
关键字virtual指明该成员函数为虚函数
只需要在基类中,虚函数定义/声明时加virtual
不用加virtual:派生类的声明和定义时;基类的虚函数在类外定义时。
(6)使用
#include<iostream> using namespace std; class Student{ string coursename; //课程名 int classhour; //学时 int credit; //学分 public: Student(){coursename="#";classhour=0;credit=0;} virtual void Calculate(){credit=classhour/16;} //虚函数 void SetCourse(string str,int hour){ coursename=str; classhour=hour; } int GetHour(){return classhour;} void SetCredit(int cred){credit=cred;} void Print(){cout<<coursename<<'\t'<<classhour<<"学时"<<'\t'<<credit<<"学分"<<endl;} }; class GradeStudent:public Student{ public: GradeStudent(){}; void Calculate(){SetCredit(GetHour()/20);} //虚函数的覆盖函数 }; void Calfun(Student &ps,string str,int hour){ ps.SetCourse(str,hour); ps.Calculate(); ps.Print(); } int main(){ Student s; GradeStudent g; cout<<"本科生:"; Calfun(s,"物理",80); cout<<"研究生:"; Calfun(g,"物理",80); //派生类对象作为基类引用的实参,只有calculate()为虚函数才能实现动态的多态性 return 0; }
(7)使用条件
(a)不能被定义为虚函数
静态成员函数(static):所有对象公有,不属于某个对象
内联函数(inline):每个对象独享函数代码
构造函数:调用构造函数时类对象尚未完成实例化
(b)通常被定义为虚函数
析构函数:实现撤销对象时的多态性 。
3、纯虚函数
(1)虚函数(virtual function)
基类指针指向基类对象时,虚函数调用基类的
基类指针指向派生类对象时,虚函数调用派生类的
(2)纯虚函数(pure virtual function)
基类指针只能指向派生类对象
(3)抽象类(abstract class)
含有纯虚函数的类是抽象类(只要有一个纯虚函数,就是抽象类)
抽象类不能定义类对象,只能作为派生类的基类
抽象类中的纯虚函数用于定义基类中无法定义的函数
如:Person::CalMark() 可以对学生/教师考核,但是对“人”考核无意义
(4)定义
(5)使用
基类:Person,CalMark无意义,定义为纯虚函数
class Person{ int MarkAchieve; string Name; public: Person(string name){ Name=name; MarkAchieve=0;} void SetMark(int mark){MarkAchieve=mark;}; void Print(){ cout<<Name<<MarkAchieve<<endl;} virtual void CalMark()=0; //纯虚函数,Person为抽象类 };
派生类:Student,重新定义CalMark
派生类:Teacher,重新定义CalMark
class Student:public Person{ int credit,grade; //学分和成绩 public: Student(string name,int cred,int grad):Person(name) {credit=cred; grade=grad; } void CalMark() {SetMark(credit*grade); } //重新定义纯虚函数 }; class Teacher:public Person{ int credit,classhour,studnum; //学分、授课学时、学生人数 public: Teacher(string name, int cred, int ch,int sn):Person(name) {credit=cred; classhour=ch; studnum=sn; } void CalMark() {SetMark(cred*classhour*studnum); } //重新定义纯虚函数 };
使用:
Person类指针指向Student类对象,调用对应CalMark;
Person类指针指向Teacher类对象,调用对应CalMark。
#include<iostream> #include<cmath> using namespace std; class Simpson{ double Intevalue,a,b; //Intevalue积分值,a积分下限,b积分上限 public: virtual double fun(double x)=0; //被积函数声明为纯虚函数 Simpson(double ra=0,double rb=0){ a=ra; b=rb; Intevalue=0; } void Print(){cout<<"积分值="<<Intevalue<<endl;} void Integrate(){ double dx; int i; dx=(b-a)/2000; Intevalue=fun(a)+fun(b); for(i=1;i<2000;i+=2) Intevalue+=4*fun(a+dx*i); for(i=2;i<2000;i+=2) Intevalue+=2*fun(a+dx*i); Intevalue*=dx/3; } }; class A:public Simpson{ public: A(double ra,double rb):Simpson(ra,rb){}; double fun(double x){return sin(x);} }; class B:public Simpson{ public: B(double ra,double rb):Simpson(ra,rb){}; double fun(double x){return exp(x);} }; int main(){ A a1(0.0,3.1415926535/2.0); Simpson *s=&a1; s->Integrate();//动态 B b1(0.0,1.0); b1.Integrate();//静态 s->Print(); b1.Print(); return 0; }
三、小结
1、继承性(8.1, 8.2, 8.5节)
(a)概念:基类vs.派生类;父类/超类vs.子类
(b)类继承层次结构:直接基类(单继承/多重继承)/间接基类
(c)派生过程:吸收→(改造)→扩展→重写
(d)派生类访问限定:
i. 公有/私有/保护
ii. 派生类成员函数访问基类成员(内部访问)
iii. 派生类对象访问基类成员(外部访问)
(e)构造函数/析构函数定义格式
(f)多重继承/虚基类
(g)继承与聚合
(h)赋值兼容规则
i. 内涵:派生类当基类用(兼容),反之不行
ii. 三种兼容场景:类对象赋值;指针访问;初始化引用
iii. 应用 :复制构造函数;赋值操作符重载函数
2、多态性(8.6节)
(a)概念:接口相同,功能不同(基类与多个派生类间)
(b)实现机制:虚函数
i. 特殊的覆盖函数
ii. 与普通覆盖函数的异同
iii. 多态性与赋值兼容规则的不同
(c)更进一步:纯虚函数
i. 抽象类
ii. 与虚函数的异同