三、继承#

1. 什么是继承#

继承是类与类之间的关系,是一个很简单很直观的概念,与现实世界中的继承类似,例如儿子继承父亲的财产。

继承(Inheritance)可以理解为一个类从另一个类获取成员变量和成员函数的过程。例如B类 继承于A类,那么 B 就拥有 A 的成员变量和成员函数。被继承的类称为父类或基类,继承的类称为子类或派生类。 子类除了拥有父类的功能之外,还可以定义自己的新成员,以达到扩展的目的。

1. is A 和 has A#

大千世界,不是什么东西都能产生继承关系。只有存在某种联系,才能表示有继承关系。如:哺乳动物是动物,狗是哺乳动物,因此,狗是动物,等等。所以在学习继承后面的内容之前,先说说两个术语 is Ahas A

  • is A

是一种继承关系,指的是类的父子继承关系。表达的是一种方式:这个东西是那个东西的一种。例如:长方体与正方体之间--正方体是长方体的一种。正方体继承了长方体的属性,长方体是父类,正方体是子类。

  • has A

has-a 是一种组合关系,是关联关系的一种(一个类中有另一个类型的实例),是整体和部分之间的关系(比如汽车和轮胎之间),并且代表的整体对象负责构建和销毁部分对象,代表部分的对象不能共享。

2. 继承入门#

通常在继承体系下,会把共性的成员,放到父类来定义,子类只需要定义自己特有的东西即可。或者父类提供的行为如不能满足,那么子类可以选择自定重新定义。

 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
#include <iostream>
#include <string>

using namespace std;

//父类
class Person{
    public:
        string name;
        int age ;
};


//子类
class Student:public Person{

};

int main(){

    //子类虽然没有声明name 和 age ,但是继承了person类,等同于自己定义的效果一样
    Student s;
    s.name = "张三";
    s.age = 18;

    cout << s.name << " = " << s.age << endl;


    return 0 ;
}

3. 访问权限回顾#

当一个类派生自基类,该基类可以被继承为 public、protectedprivate 几种类型。继承类型是在继承父类时指定的。 如: class Student : public Person。 我们几乎不使用 protectedprivate 继承,通常使用 public继承。

  • public

表示公有成员,该成员不仅可以在类内可以被访问,在类外也是可以被访问的,是类对外提供的可访问接口;

  • private

表示私有成员,该成员仅在类内可以被访问,在类的外面无法访问;

  • protected

表示保护成员,保护成员在类的外面同样是隐藏状态,无法访问。但是可以在子类中访问。

1. 公有继承(public)#

基类成员在派生类中的访问权限保持不变,也就是说,基类中的成员访问权限,在派生类中仍然保持原来的访问权限;

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <string>
using namespace std;

class person{
    public:
        string name;

    private:
        int age;
};

class student:public person{
    //name 和 age保持原有访问权限。
};

int main(){

    student s;
    s.name = "张三" ;
    s.age = 18 ; //编译错误! 无法访问 age  

    return 0 ;
}

2. 私有继承(private)#

基类所有成员在派生类中的访问权限都会变为私有(private)权限;

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <string>
using namespace std;

class person{
    public:
        string name;

    private:
        int age;
};

class student:private person{
    //name 和 age保持全部变成private权限
};

int main(){

    student s;
    s.name = "张三" ;//编译错误! 无法访问 name  
    s.age = 18 ; //编译错误! 无法访问 age  

    return 0 ;
}

3. 保护继承(protected)#

基类的共有成员和保护成员在派生类中的访问权限都会变为保护(protected)权限,在子类中具有访问权限,但是在类的外面则无法访问。

 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
#include <string>
using namespace std;

class person{
public:
    string name;

private:
    int age;
};

class student: protected person{
    //name 和 age保持原有访问权限。

public:
    void read(){
        s.name = "李四";
        s.age = 19 ;
        cout << s.age << " 的 " << s.name << " 在看书"
    }
};

int main(){

    student s;
    s.read();

    //类的外面无法访问,编译错误。
    s.name = "张三" ;
    s.age = 18 ; 

    return 0 ;
}

4. 构造和析构#

1. 继承状态#

构造函数是对象在创建是调用,析构函数是对象在销毁时调用。但是在继承关系下,无论在对象的创建还是销毁,都会执行父类和子类的构造和析构函数。 它们一般会有以下规则:

a. 子类对象在创建时会首先调用父类的构造函数;

b. 父类构造函数执行完毕后,执行子类的构造函数;

c. 当父类的构造函数中有参数时,必须在子类的初始化列表中显示调用;

d. 析构函数执行的顺序是先调用子类的析构函数,再调用父类的析构函数

 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
#include <iostream>

using namespace std;

class person{
public :
    person(){
        cout << "调用了父类构造函数" << endl;
    }
    ~person(){
        cout << "调用了父类析构函数" << endl;
    }
}

class student: public person{
public :
    student(){
        cout << "调用了子类类构造函数" << endl;
    }
    ~student(){
        cout << "调用了子类析构函数" << endl;
    }
};


int main() {
    Student s1
    return 0;
}

2. 继承和组合#

如果在继承状态下,子类中的成员又含有其他类的对象属性,那么他们之间的构造很析构调用顺序,遵循以下原则:

a. 先调用父类的构造函数,再调用组合对象的构造函数,最后调用自己的构造函数;

b. 先调用自己的析构函数,再调用组合对象的析构函数,最后调用父类的析构函数。

 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
#include <iostream>

using namespace std;

//父类
class Person{

public :

    Person(){
        cout << "调用了父类构造函数" << endl;
    }

    ~Person(){
        cout << "调用了父类析构函数" << endl;
    }

};

//其他类
class  A{
public :
    A(){
       cout << "调用A的构造函数" << endl;
    }

    ~A(){
        cout << "调用A的析构函数" << endl;
    }
};

//子类
class Student: public Person{

public :
    Student(){
        cout << "调用了子类类构造函数" << endl;
    }

    ~Student(){
        cout << "调用了子类析构函数" << endl;
    }
public:
    A a;
};


int main() {
    Student s1(18 , "zhangsan");
    return 0;
}

3. 调用父类有参构造#

继承关系下,子类的默认构造函数会隐式调用父类的默认构造函数,假设父类没有默认的无参构造函数,那么子类需要使用参数初始化列表方式手动调用父类有参构造函数。

一般来说在创建子类对象前,就必须完成父类对象的创建工作,也就是在执行子类构造函数之前,必须先执行父类的构造函数。c++ 使用初始化列表来完成这个工作

  • 父类
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#include <iostream>

using namespace std;

class Person{

private :
    int age ;
    string name ;

public :
    Person(int age , string name){
        cout << "调用了父类构造函数" << endl;
        this->age = age ;
        this->name = name;
    }
};
  • 子类

子类只能使用初始化列表的方式来访问父类构造

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class Student: public Person{

public :
    Student(int age , string name):Person(age ,name){
        cout << "调用了子类类构造函数" << endl;
    }
};

int main(){
    Student s1(18 , "zs");
    return 0 ;
}

4. 再说初始化列表#

初始化列表在三种情况下必须使用:

  • 情况一、需要初始化的数据成员是对象,并且对应的类没有无参构造函数
  • 情况二、需要初始化const修饰的类成员或初始化引用成员数据;
  • 情况三、继承关系下,父类没有无参构造函数情况

初始化列表的赋值顺序是按照类中定义成员的顺序来决定

1. 常量和引用的情况#

类中成员为 引用 或者 const修饰 的成员

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <string>

using namespace std;

class stu{

public:
    const string name; //常量不允许修改值,所以不允许在构造里面使用  = 赋值
    int &age; //

    stu(string name , int age):name(name),age(age){
        cout << "执行构造函数" <<endl;
    }
};

int main(){

    stu s1("张三" , 88);
    cout << s1.name << " = " << s1.age << endl;

    return 0 ;
}

2. 初始化对象成员#

类中含有其他类的对象成员,如果要初始化,只能使用初始化类列表方式。

 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
#include <iostream>

using namespace std;

class A{

public:
    int number;
    A(int number):number(number){
        cout << "执行了A的构造函数" <<endl;
    }
};

class stu{
public:
    A a;

    stu():a(9){

        cout << "执行了stu的构造函数" <<endl;
    }
};

int main(){
    stu s;
    return 0;
}

5. 重写父类同名函数#

在继承中,有时候父类的函数功能并不够强大,子类在继承之后,可以对其进行增强扩展。 如果还想调用你父类的函数,可以使用 父类::函数名() 访问

 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
#include <iostream>

using namespace std;

class WashMachine{
public:
    void wash(){
        cout << "洗衣机在洗衣服" << endl;
    }
};


class SmartWashMachine : public WashMachine{
public:
    void wash(){
        cout << "智能洗衣机在洗衣服" << endl;
        cout << "开始添加洗衣液~~" << endl;
        //调用父类的函数
        WashMachine::wash();
    }
};

int main(){

    SmartWashMachine s;
    s.wash();

    return 0 ;
}

6. 多重继承#

C++ 允许存在多继承,也就是一个子类可以同时拥有多个父类。只需要在继承时,使用逗号进行分割即可。

 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
using namespace std;

class Father{
public:
    void makeMoeny(){
        cout << "赚钱" << endl;
    }
};
class Mother{
public:
    void makeHomeWork(){
        cout << "做家务活" << endl;
    }
};

class Son:public Father , public Mother{

};

int main(){

    Son s ;
    s.makeMoeny();
    s.makeHomeWork();

    return 0 ;
}

1. 多重继承的构造函数#

多继承形式下的构造函数和单继承形式基本相同,只是要在子类的构造函数中调用多个父类的构造函数 。 他们调用的顺序由定义子类时,继承的顺序决定。

 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
#include <iostream>

using namespace std;

class Father{

    string name;
public:
    Father(string name):name(name){
        cout << "执行父亲构造函数" <<endl;
    }
};



class Mother{
    int age;

public:
    Mother(int age):age(age){
        cout << "执行母亲构造函数" <<endl;
    }

};
class Son:public Father , public Mother{

public:
    Son(string name ,int age):Father(name),Mother(age){
        cout << "执行孩子构造函数" <<endl;
    }
};

int main(){

    Son s("无名氏" ,38);

    return 0 ;
}

7. 类的前置声明#

一般来说,类和 变量是一样的,必须先声明然后再使用,如果在某个类里面定义类另一个类的对象变量,那么必须在前面做前置声明,才能编译通过。

 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
class father; //所有前置声明的类,在某个类中定义的时候,只能定义成引用或者指针。

class son{
public:
    //father f0; //因为这行代码,单独拿出来说,会执行B类的无参构造,
    //但是编译器到此处的时候,还不知道B这个类的构造长什么样。
    father &f1;
    father *f2;

    son(father &f1 , father *f2):f1(f1),f2(f2){

    }
};


class father{

};


int main(){

    //  father b; //---> 执行B的构造函数。
    father f1;
    father f2;

    son s(f1 ,&f2);

    return 0 ;
}