# 十一. 继承

继承是指在已有类的或称为基类的基础上创建新类,这个类就是派生类

派生类自动包含了基类的成员,包括所有的数据和操作,而且它还可以增加自身的新成员

派生类是基类,基类有派生类

能实现代码复用,减轻负担,更有效地管理类,维护类,以及扩展新的类

# 1. 单继承

声明格式:class 派生类名:继承方式 基类名 //继承方式:public,protected,private都可,如果缺省不写,默认为private

{ 派生类成员声明 };

#include<iostream>
#include<string>
using namespace std;
class Person     //基类
{
    string name;
    int age;
    public:
    void set_Person(string s,int a)
    {
        name = s;
        age = a;
    }
    void show()
    {
        cout<<"name:"<<name<<endl;
        cout<<"age:"<<age<<endl;
    }
};
class Student:public Person   //派生类,自动继承基类的数据成员和成员函数 
{
    int grade;     //新增数据成员
    public:
    void setStudent(string n,int a,int g)  //新增成员函数
    {
        set_Person(n,a);   //调用基类中的成员函数
        grade = g;
    }
};

int main()
{
    Student s;//此对象s为基类Person的派生类Student的对象,可以调用两个类中的成员
    ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

# 2. 基类成员在派生类中的访问权限

能继承不等于能访问

在基类中的访问属性 继承方式 在派生类中的访问属性
private public 不可直接访问
private private 不可直接访问
private protected 不可直接访问
public public public
public private private
public protected protected
protected public protected
protected private private
protected protected protected

public > protected > private > 不可直接访问

# 3. 继承机制下的构造函数与析构函数

  • 在继承机制中,基类的构造函数与析构函数是不能被继承的
  • 派生类的构造函数负责对来自基类数据成员和新增加的数据成员进行初始化
  • 所以,在执行派生类的构造函数时,需要调用基类的构造函数,并提供基类构造函数所需的参数

基类中不能被继承的函数

  1. 构造函数、析构函数、拷贝构造函数、赋值运算符函数
  2. 友元函数
  3. 运算符函数
  4. 静态成员函数

派生类构造函数执行顺序

  1. 基类的构造函数
  2. 对象成员(即数据成员的类型是自己定义的类,此数据成员为该类的对象)的构造函数(如果有的话),有多个时按声明的顺序
  3. 派生类的构造函数

派生类析构函数执行顺序

与构造函数相反

#include <iostream>
using namespace std;
class A
{
    public:
    A()
    { 
        cout<<"Construct A\n"; 
    }
    virtual ~A() //虚析构函数
    { 
        cout<<"Destory A\n"; 
    }
};

class B1:virtual public A
{
    public:
    B1():A()
    {
        cout<<"Construct B1\n";
    }
    ~B1()override // 重写析构函数
    {
        cout<<"Destory B1\n";
    }
};

class B2:virtual public A
{
    public:
    B2():A()
    {
        cout<<"Construct B2\n";
    }
    ~B1()override
    {
        cout<<"Destory B2\n";
    }
};

class C:public B2,public B1  //继承B2,B1
{
    C():B1(),B2(),A()   //先调用B1的构造函数,再调用B2的构造函数   
                        //二次继承的类也要在构造函数初始化列表中写最开始的基类的构造函数
    {
        cout<<"Construct C\n";
    }
    ~C()override
    {
        cout<<"Destory C\n";
    }
};

int main()
{
    C c;
    return 0;
}

输出结果:
    Construct A
    Construct B1//构造顺序与派生类对象构造时的调用顺序有关,与继承顺序无关
    Construct B2
    Construct C
    Destory C
    Destory B2
    Destory B1
    Destory A
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
  • 定义默认构造函数

    如基类中定义了默认构造函数,且该默认构造函数能够完成派生类对象中基类成员的构造,派生类构造函数无需显式调用基类构造函数,直接调用派生类构造函数即可

:上上例中,如果在Person类和Student类中写默认构造函数(无参数的构造函数),并在函数中分别输出:“调用基类构造函数”,“调用派生类构造函数”,则在主函数中创建Student类对象s时,会依次输出:“调用基类构造函数”,“调用派生类构造函数”

  • 定义有参的构造函数

    若基类中定义了有参构造函数,则必须通过派生类的构造函数显式调用基类的构造函数,向带参数的构造函数传递参数,这需要用到成员初始化列表

因此,派生类的构造函数定义的一般格式

派生类名(参数列表):基类构造函数(参数列表1),子对象成员1(参数列表). . .

{ 派生类构造函数体 }

#include<iostream>
#include<string>
using namespace std;
class Person     //基类
{
    string name;
    int age;
    public:
    Person(string s,int a)//Person带参数的构造函数
    {
        name = s;
        age = a;
    }
    void show()
    {
        cout<<"name:"<<name<<endl;
        cout<<"age:"<<age<<endl;
    }
};
class Data
{
    int year,month,day;
    public:
    Data(int y,int m,int d)//Data的构造函数
    {
        year = y;
        month = m;
        day = d;
    }
    
}
class Student:public Person   //派生类
{
    int grade;     //新增数据成员
    //对象成员的声明
    Data entranceday;
    Data birthday;
    public:
    //定义Student带参数的构造函数
    Student(string n,int a,int g,int y1,int m1,int d1,int y2,int m2,int d2):Person(n,a),entranceday(y1,m1,d1),birthday(y2,m2,d2)  //对基类的成员初始化需要用到成员初始化列表,entranceday和birthday为子对象成员
    {
        grade = g;
    }
};

int main()
{
    Student s("Tom",15,9.2012,9,1,2005,10,3);
    ...
}
//构造函数调用顺序:Person带参数的构造函数,entranceday的构造函数,birthday的构造函数,Student带参数的构造函数。子对象成员的构造调用顺序与声明顺序有关,与初始化顺序无关
//析构顺序与其相反
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
  • 基类数据成员仍由基类完成其构造
  • 派生类负责调用基类的构造函数来完成上述过程
  • 每个派生类仅负责其直接基类的构造
  • 不同基类中有同名的成员,则在多重继承之后,派生类对该同名成员的访问存在冲突
  • 成员名限定法派生类对象.基类的名字::同名的成员

# 4. 多继承

多继承下派生类的声明格式class 派生类名:继承方式1 基类名1,继承方式2 基类名2 … //继承方式:public,protected,private都 可,如果缺省不写,默认为private

{ 派生类类体; };

:

#include<iostream>
#include<string>
using namespace std;
class Student         //第一个基类
{
    protected:        //保护+公有=保护,可以访问
    string name;
    private:
    string ID;
    char gender;
    public:
    STudent(string name,string ID,char gender)
    {
        this->name = name;
        this->ID = ID;
        this->gender = gender;
    }
    void showStudent()
    {
        cout<<"name:"<<name<<endl;
        cout<<"ID:"<<ID<<endl;
        cout<<"gender:"<<gender<<endl;
    }
};
class Employee           //第二个基类
{
    private:
    string name;
    protected:       //保护+公有=保护,可以访问
    string job;
    public:
    Employee(string name,string job)
    {
        this->name = name;
        this->job = job;
    }
    void showEmployee()
    {
        cout<<"name:"<<name<<endl;
        cout<<"job:"<<job<<endl;
    }
};
class StudentHasJob():public Student,public Employee         //继承了两个基类的派生类
{
    public:
    StudentHasJob(string name,string Id,char gender,string job):Student(name,Id,gender),Employee(name,job)       //依次对基类进行初始化
    {}
    void showStudentHasJob()
    {
        showStudent();
        cout<<jod;
    }
};

int main()
{
    StudentHasJob s("Jack","230701",'H',"工程师");
    s.showStudentHasJob();
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

# 5. 虚基类和赋值兼容规则

# (1)成员名限定法

在子类的函数中调用父类同名函数,需要在调用的父类函数前加父类名::,注意是“ : : ”

:(利用上例)

//只将StudentHasJob类中的showStudentHasJob()函数修改
void showStudentHasJob()
{
    //? cout<<name;   //此方法不行,因为Student类中和Employee类中均有name数据成员,会造成二义性,不知道访问哪个
    cout<<Student::name;    //运用成员名限定法,正确
    showStudent();
    cout<<jod;
}
1
2
3
4
5
6
7
8

# (2)虚基类

定义虚基类的格式:class 派生类名:virtual 继承方式 基类名称

{ … … };

  • 所有从虚基类直接或间接派生的类必须在该类构造函数的成员初始化列表列出对虚基类构造函数的调用但是只有实际构造对象的类的构造函数才会引发对虚基类构造函数的调用,而其他基类在成员初始化列表中对虚基类构造函数的调用都会被忽略,从而保证了派生类对象中虚基类成员只会被初始化一次

  • 若某类构造函数的成员初始化列表中同时列出对虚基类构造函数和非虚基类构造函数的调用,则会优先执行虚基类的构造函数

    例1:

#include<iostream>
#include<string>
using namespace std;
class Person           //基类
{
    private:
    string name;
    int age;
    public:
    Person(string name,int n)
    {
        this->name = name;
        this->age = n;
        cout<<"Person的构造函数"<<endl;   //在构造函数中添加输出语句,方便查看调用顺序
    }
    void showPerson()
    {
        cout<<"name:"<<name<<endl;
        cout<<"age:"<<age<<endl;
    }
};
class Student:public Person         //基类,继承Person
{
    private:
    char gender;
    public:
    Student(string name,int n,char gender):Person(name,age)
    {
        this->gender = gender;
        cout<<"Student的构造函数"<<endl;
    }
    void showStudent()
    {
        showPerson();
        cout<<"gender:"<<gender<<endl;
    }
};
class Employee:public Person           //基类,继承Person
{
    private:
    string job;
    public:
    Employee(string name,int n,string job):Person(name,n)
    {
        this->job = job;
        cout<<"Employee的构造函数"
    }
    void showEmployee()
    {
        showPerson();
        cout<<"job:"<<job<<endl;
    }
};
class StudentHasJob:public Student,public Employee         //继承了Student和Employee
{
    public:
    StudentHasJob(string name,int n,char gender,string job):Person(name,n),Student(name,n,gender),Employee(name,n,job)       //依次对基类进行初始化
    {
        cout<<"StudentHasJob的构造函数"<<endl;
    }
    void showStudentHasJob()
    {
        showStudent();
        cout<<jod;
    }
};

int main()
{
    StudentHasJob s("Jack",20,'H',"工程师");
    s.showStudentHasJob();
    return 0;
}
输出结果:
    Person的构造函数
    Student的构造函数
    Person的构造函数
    Employee的构造函数
    StudentHasJob的构造函数
    name:Jack
    age:20
    gender:H
    工程师
//在主函数中创建s对象时,会调用Student的构造和Employee的构造(因为StudentHasJob继承了他俩,在初始化时调用了他们的构造函数),又因为二者均继承了Person,所以又会调用Person的构造,所以Person先被调用,且调用了两次
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84

例2:(为了不使Person的构造函数调用两次,所以用virtual

#include<iostream>
#include<string>
using namespace std;
class Person           //基类
{
    private:
    string name;
    int age;
    public:
    Person(string name,int n)
    {
        this->name = name;
        this->age = n;
        cout<<"Person的构造函数"<<endl;   //在构造函数中添加输出语句,方便查看调用顺序
    }
    void showPerson()
    {
        cout<<"name:"<<name<<endl;
        cout<<"age:"<<age<<endl;
    }
};
class Student:virtual public Person         //定义虚基类,继承Person
{
    private:
    char gender;
    public:
    STudent(string name,int n,char gender):Person(name,age)
    {
        this->gender = gender;
        cout<<"Student的构造函数"<<endl;
    }
    void showStudent()
    {
        showPerson();
        cout<<"gender:"<<gender<<endl;
    }
};
class Employee:virtual public Person          //定义虚基类,继承Person
{
    private:
    string job;
    public:
    Employee(string name,int n,string job):Person(name,n)
    {
        this->job = job;
        cout<<"Employee的构造函数"
    }
    void showEmployee()
    {
        showPerson();
        cout<<"job:"<<job<<endl;
    }
};
class StudentHasJob:public Student,public Employee         //继承了Student和Employee
{
    public:
    StudentHasJob(string name,int n,char gender,string job):Person(name,n),Student(name,n,gender),Employee(name,n,job)Person(name,n)       //依次对基类进行初始化,还需添加Person的构造函数
    {
        cout<<"StudentHasJob的构造函数"<<endl;
    }
    void showStudentHasJob()
    {
        showStudent();
        cout<<jod;
    }
};

int main()
{
    StudentHasJob s("Jack",20,'H',"工程师");
    s.showStudentHasJob();
    return 0;
}
输出结果:
    Person的构造函数
    Student的构造函数
    Employee的构造函数
    StudentHasJob的构造函数
    name:Jack
    age:20
    gender:H
    工程师
//Person的构造函数只被调用了一次
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83

解决了派生类对象中基类对象存在多个副本的问题

# (3)赋值兼容规则

  • 基类对象可以赋值给基类对象,也可以把派生类对象赋值给基类对象
  • 基类指针可以指向基类对象,也可以指向派生类对象
  • 基类引用可以指向基类对象,也可以指向派生类对象

:

#include<iostream>
using namespace std;
class Shape         //基类
{
    int x,y;
    public:
    Shape(int xx = 0,int yy = 0)
    {
        x = xx;
        y = yy;
    }
    void show()
    {
        cout<<"圆形的中心点是:"<<"("<<x<<","<<y<<")"<<endl;
    }
    void Area()
    {
        cout<<"不知道什么图形,不知道面积"<<endl;
    }
};
class Circle:public Shape         //派生类
{
    int radius;
    public:
    Circle(int x,int y,int r):Shape(x,y)
    {
        radius = r;
    }
    void show()
    {
        cout<<"该图形是圆"<<endl;
        Shape::show();
    }
    void Area()
    {
        cout<<"圆的面积是:"<<3.14*radius*radius<<endl;
    }
};
int main()
{
    Shape s1,s2(2,2);//建立了两个基类的对象s1和s2
    s1 = s2;//同一类型的对象可以赋值
    s1.show();
    Circle c(30,30,8);//建立了一个对象圆,中心点在(30,30),半径是8
    s1 = c;//基类的对象等于派生类的对象
    s1.show() ;
    Shape *s3 = &s2;//基类的指针指向基类的对象
    s3->show();
    Shape *s4 = &c;//基类的指针指向派生类的对象
    s4->show();
    Shape &s5 = s2;//基类的引用指向基类的对象
    s5.show();
    Shape &s6 = c;//基类的引用等于派生类的对象
    s6.show();
    //但是
    //? s6.Area();   //输出“不知道什么图形,不知道面积”,而不是圆的面积
}
输出结果:
    圆形的中心点是:(2,2)
    圆形的中心点是:(30,30)
    圆形的中心点是:(2,2)
    圆形的中心点是:(30,30)
    圆形的中心点是:(2,2)
    圆形的中心点是:(30,30
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
上次更新时间:: 2/9/2025, 9:12:58 PM