大一下第二学期期中知识复习梳理 之 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)析构顺序(与构造顺序相反
①执行派生类的析构函数
②依次执行成员对象的析构函数(按构造时定义先后)
③依次执行直接基类的析构函数(按构造时定义先后)
例子:

 解析:

#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;
}
【例8.1】派生类定义与使用
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;
}
【例8.2】多重继承派生出圆锥
(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;
}
【例8.3】虚基类的多重继承
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;
} 
【例8.6】虚函数计算本科生和研究生学分

(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为抽象类
};
基类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); } //重新定义纯虚函数
};
派生类Student/Teacher定义

使用:
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;
}    
【例8.9】虚函数实现辛普生法求函数定积分

三、小结

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. 与虚函数的异同
posted @ 2023-08-12 01:37  Au0702  阅读(60)  评论(0编辑  收藏  举报