继承

可以让子类拥有父类的所有可访问成员(变量/函数)。

C++没有像Java的基类。

语法

在类名后面写个:再接上父类类名即可。Student : Person

struct Person
{
    int id;

    void run(){  }
};

struct Student : Person
{
    int score;

    void study() {  }
};

struct Worker : Person
{
    int salary;
    void work() {  }
};

关系描述:Person:父类(superclass,超类),Student:子类(subclass,派生类)。

内存布局:像上面的代码,Wroker这个类型的对象占用8个字节,以为父类占4个字节,自己占4个字节。父类的成员变量在前面,子类的在后面。

成员访问权限

成员访问权限、继承方式有3种

public:公共的,任何地方都可以访问(struct默认)。

protected:子类内部、当前类内部可以访问。

private:私有的,只有当前类内部可以访问(class默认)。

访问权限不影响对象的内存布局

继承方式

可以通过在写继承关系的时候,指定访问权限,来决定子类是否可以访问这个类的父类成员。

class Person
{
private:
    int id;
public:
    void run(){  }
    int get_id()
    {
        return this->id;
    }
};

class Student : private Person // Student的子类无法访问Person类的成员
{
private:
    int score;
public:
    void study() 
    { 
        cout << this->get_id()  << endl;
    }
};

class GoodStudent : Student
{
    void test()
    {
        // cout << this->get_id() << endl; 不可以访问,因为父类继承他父类的时候给权限是private
    }
};

子类内部访问父类成员的权限,是成员本身的访问权限上一级父类的继承方式中权限最小的那个。

默认访问权限

struct的默认访问权限是public,继承方式默认是public。不写默认是:struct A : public B {};

class的默认访问权限是private,继承方式默认是private。不写默认是:class A : private B{};

这个和父类用struct定义还是class定义没有关系。

struct Person
{
    int id;
    void run(){  }
};

class Student : Person
{
private:
    int score;
public:
    void study() 
    { 
        this->run();
    }
};

class C : Student
{
    void test()
    {
        this->run(); // 无法访问。
    }
};

父类的构造函数

子类的构造函数默认会调用父类的无参构造函数。

如果子类的构造函数显式地调用了父类的有参构造函数,就不会再去默认调用父类的无参构造函数。

显式的调用父类的有参构造函数:

struct A
{
    int a;
    int b;

    A() :A(10, 20) 
    {

    }

    A(int a, int b)
    {
        this->a = a;
        this->b = b;
    }

};

struct B : A
{
    int a;
    int b;
    B() :A(1, 2)
    {
        
    }    
};

如果父类缺少无参构造函数,子类的构造函数必须显式调用父类的有参构造函数。

struct A
{
    int a;
    int b;
    
    A(int a, int b)
    {
        this->a = a;
        this->b = b;
    }

};

struct B : A
{
    int a;
    int b;
    B() :A(1, 2)
    {
        
    }    
};

如果父类没有写无参构造函数,就不会自动调用父类的构造函数。

构造/析构的顺序

struct A
{
    A()
    {
        printf("A()\n");
    }

    ~A()
    {
        printf("~A()\n");
    }
};

struct B : A
{
    B()
    {
        printf("B()\n");
    }

    ~B()
    {
        printf("~B()\n");
    }
};

int main()
{
    {
        B b;
    }
    return 0;
}
// A()
// B()
// ~B()
// ~A()

多继承

C++允许一个类可以有多个父类(不建议使用,会增加程序设计复杂度)。

struct Student
{
    int score;
    void study()
    {
        printf("Student::study() -- score = %d\n", this->score);
    }
};

struct Worker
{
    int salary;
    void work()
    {
        printf("Worker::work() -- salary = %d\n", this->salary);
    }
};

struct Undergraduate : Student, Worker
{
    int grade;
    void play()
    {
        printf("Undergraduate::play() -- grade = %d\n", this->grade);
    }
};

父类成员变量哪个在前面只跟继承的顺序有关(内存空间中)。

构造函数的调用

struct Student
{
    int score;

    Student(int score) : score(score) {}

    void study()
    {
        printf("Student::study() -- score = %d\n", this->score);
    }
};

struct Worker
{
    int salary;
    
    Worker(int salary) : salary(salary) {}

    void work()
    {
        printf("Worker::work() -- salary = %d\n", this->salary);
    }
};

struct Undergraduate : Student, Worker
{
    int grade;

    Undergraduate(int score, int salary, int grade) 
        : Student(score)
        , Worker(salary)
        , grade(grade) {}

    void play()
    {
        printf("Undergraduate::play() -- grade = %d\n", this->grade);
    }
};

虚函数

如果子类继承的多个父类都有虚函数,那么子类对象就会产生对应的多张虚表。

struct Student
{
    virtual void study()
    {
        printf("Student::study()\n");
    }
};

struct Worker
{
    virtual void work()
    {
        printf("Worker::work()\n");
    }
};

struct Undergraduate : Student, Worker
{
    void study()
    {
        printf("Undergraduate::study()\n");
    }

    void work()
    {
        printf("Undergraduate::work()\n");
    }

    void play()
    {
        printf("Undergraduate::play()\n");
    }
};

image-20210720160516552

同名函数

struct Student
{
    void eat()
    {
        printf("Student::eat()\n");
    }
};

struct Worker
{
    void eat()
    {
        printf("Worker::eat()\n");
    }
};

struct Undergraduate : Student, Worker
{
    void eat()
    {
        printf("Undergraduate::eat()\n");
    }
};

int main()
{
    Undergraduate u;
    u.eat();                  // Undergraduate::eat()
    u.Student::eat();         // Student::eat()
    u.Worker::eat();          // Worker::eat()
    u.Undergraduate::eat();   // Undergraduate::eat()
    return 0;
}

同名成员变量

struct Student
{
    int age;
};

struct Worker
{
    int age;
};

struct Undergraduate : Student, Worker
{
    int age;
};


int main()
{
    Undergraduate u;
    u.age = 10;
    u.Student::age = 20;
    u.Worker::age = 30;
    // Student::age = 20, Worker::age = 30, Undergraduate::age = 10
    printf("Student::age = %d, Worker::age = %d, Undergraduate::age = %d", 
           u.Student::age, u.Worker::age, u.Undergraduate::age);
    return 0;
}

QQ截图20210720162711

菱形继承

image-20210720163943942

QQ截图20210720164027

struct Person
{
    int age;
};

struct Student : Person
{
    int score;
};

struct Worker : Person
{
    int salary;
};

struct Undergraduate : Student, Worker
{
    int grade; // age被重复继承了
};

int main()
{
    Undergraduate g;
    printf("%d", sizeof(g)); // 20
    return 0;
}

无标题

问题

最底下子类从基类继承的成员变量冗余、重复。

最底下子类无法访问基类的成员,有1二义性。

解决:虚继承

虚继承可以解决菱形继承带来的问题。

QQ截图20210720164027

Person类被称为虚基类

struct Person
{
    int age = 1;
};

struct Student : virtual Person
{
    int score = 2;
};

struct Worker : virtual Person
{
    int salary = 3;
};

struct Undergraduate : Student, Worker
{
    int grade = 4;
};

未标题_全景图-1