RaymondHuang
RaymondHuang
发布于 2022-12-17 / 88 阅读
0
0

类的继承(Inheritance)

类的继承:

通过继承来减少代码量与提高复用性,一个基本例子:

class Father {
public:
    int age;
    string name;
    
    Father(): age(30),name("Father") {}
}

class Son: public Father {
public:
    int examScore;
    
    Son(): examScore(100) { // 不可以在这里初始化age和name
        // 值得注意的是,继承来的成员属性只能在方法内更改,不可以使用初始化列表更改。
        age = 10;
        name = "Son";
    }
}

访问权限:

可以用来控制成员变量与函数能否被访问,有private、protected和public三种。其访问权限如下:

继承类型:

我们一般就用public来继承,但不排除特殊情况,也是有private、protected和public三种,其继承含义如下:

① 公有继承(public):父类的public和protected成员都成为了子类的public和protected成员,但父类的private成员不能直接访问,可以通过public或protected的方法来访问或修改。

② 保护继承(protected):父类的public和protected成员变成了子类的protected成员。

③ 私有继承(private):父类的public和protected成员成为了子类的private成员。

class Father {
public:
    int a;
protected:
    int b;
private:
    int c;
};

class Son1 : public Father { // public继承
public:
    void show() {
        cout << a << endl; // 可以输出
        cout << b << endl; // 可以输出
        cout << c << endl; // 报错,因为父类的c是private的
    }
};

class Son2 : protected Father { // protected继承
public:
    void show() {
        cout << a << endl; // 可以输出
        cout << b << endl; // 可以输出
        cout << c << endl; // 报错,因为父类的c是private的
    }
};

class Son3 : private Father { // private继承
public:
    void show() {
        cout << a << endl; // 可以输出
        cout << b << endl; // 可以输出
        cout << c << endl; // 报错,因为父类的c是private的
    }
};

class GrandSon : public Son3 { //public继承,但父类是Son3
public:
    void show() {
        // 三个输出都报错,因为Son3里继承的abc都变成了private的了
        cout << a << endl;
        cout << b << endl;
        cout << c << endl;
    }
};

int main() {
    // Father f;
    // cout << f.a << endl; // 可以输出
    // cout << f.b << endl; // 报错,protected的
    // cout << f.c << endl; // 报错,private的

    // Son1 s1;
    // cout << s1.a << endl; // 可以输出
    // cout << s1.b << endl; // 报错,protected的
    // cout << s1.c << endl; // 报错,private的
    // s1.show();

    // Son2 s2;
    // cout << s2.a << endl; // 报错,protected的
    // cout << s2.b << endl; // 报错,protected的
    // cout << s2.c << endl; // 报错,没有继承这个成员
    // s2.show(); 

    //Son3 s3;
    //cout << s3.a << endl; // 报错,private的
    //cout << s3.b << endl; // 报错,private的
    //cout << s3.c << endl; // 报错,没有继承这个成员
    // s3.show();

    return 0;
}

子类的初始化:

当一个子类被初始化的时候,会先初始化它的父类,然后再初始化子类

当一个子类被销毁的时候,会先销毁子类,再销毁它的父类

class Father {
public:
    Father() {
        cout << "Father()" << endl;
    }

    ~Father() {
        cout << "~Father()" << endl;
    }
};

class Son : public Father {
public:
    Son() {
        cout << "Son()" << endl;
    }

    ~Son() {
        cout << "~Son()" << endl;
    }
};

int main {
    Son s;
    return 0;
}

上述程序的输出为:

Father()
Son() 
~Son()
~Father()

有一种类似的情况,就是当一个类的成员变量是另一个类的实例对象时,也是先初始化这个成员变量所在的类,再初始化这个类本身

class Father {
public:
    Father() {
        cout << "Father()" << endl;
    }

    ~Father() {
        cout << "~Father()" << endl;
    }
};

class Son{ // 注意看,这个类没有继承
public:
    Father s; // 但是他有一个Father的对象作为成员

    Son() {
        cout << "Son()" << endl;
    }

    ~Son() {
        cout << "~Son()" << endl;
    }
};

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

上述程序输出为:

Father()
Son()
~Son()
~Father()

子类与父类的冲突:

如果子类和父类定义了一个同名的成员,那么会优先调用子类的:

class Father {
public:
    char dream;

    Father() : dream('b') {}
};

class Son : public Father {
public:
    char dream;

    Son() : dream('c') {}
};

int main() {
    Son s;
    cout << s.dream << endl; //输出的是c而不是b。
    cout << s.Father::dream << endl; //这样就可以访问父类的dream了,输出的是b
}

同理,如果子类和父类有一个同名的函数,会优先执行子类的。但值得注意的是,如果父类里面存在函数的重载,那么如果子类重写了父类的某一个函数,那么就没有办法继承来自父类的原来的重载的函数,如果子类没有重写任何一个父类的包含重载的函数,那么子类将能继承父类所有的重载函数。(别看了,太tm绕了。。。

多继承:

一个子类可以有多个父类(但人不行啊),多继承不太常用,仅举个例子:

class Father {
public:
    int height;
    int age;
};

class Mother {
public:
    int beauty;
    int age;
};

class Son : public Father, public Mother {
};

void main() {
    Son s;
    cout << s.age << endl; // 报错,因为同时继承了两个age,下面的菱形继承会解决这种情况
    cout << s.Father::age << endl; // 正常输出
    cout << s.Mother::age << endl; // 正常输出
    cout << s.height << endl;// 正常输出
    cout << s.beauty << endl;// 正常输出
}

菱形(钻石)继承:

菱形继承也是子类和父类的一种冲突,比如如下情况:

class Animal {
public:
    int age;
};

class Yang: public Animal {};
class Tuo: public Animal {};

class YangTuo: public Yang, public Tuo {};

可以看到,羊驼继承了羊和驼,但是,羊和驼都继承了动物,也就是说,羊驼有两个age了,这是不应该的,产生了冲突。因此,我们需要使用虚拟继承来解决这个问题:

class Animal {
public:
    int age;
};

class Yang: virtual public Animal {}; // 虚拟继承
class Tuo: virtual public Animal {}; // 虚拟继承

class YangTuo: public Yang, public Tuo {};

int main() {
    YangTuo s;

    s.age = 18;
    s.Yang::age = 28;
    s.Tuo::age = 38;

    cout << s.age << endl; // 输出38
    cout << s.Yang::age << endl; // 输出38
    cout << s.Tuo::age << endl; // 输出38

    return 0;
}

通过虚拟继承,可以解决上述问题,但奇怪的是,上面的程序的三个输出都是38,也就是,这三个age指向的是同一个变量!C++为了解决菱形继承的问题,特别引入了这么一种特殊的机制,如果存在菱形继承的错误,那么就会使用一个类似指针的东西,去保存这个冲突的变量,然后让冲突的子类和父类都共享这个指针。

子类继承父类后占用空间:

按照直觉去想,因为子类没办法继承父类的private成员,那么应该不会因为父类的private成员而消耗内存,实际上不是这样的:

class Father {
public:
    int id;
protected:
    int age;
private:
    int bank_card;
};

class Son : public Father {
public:
    int n_toy;
};

int main() {
    Father f;
    Son s;
    cout << sizeof(f) << endl; // 输出12,三个int,每个int占4字节
    cout << sizeof(s) << endl; // 输出16,说明父类的private成员也被继承下来了!
    return 0;
}

原因其实很简单,因为其实子类是可以通过调用父类的public或protected方法来访问父类的private变量的,所以也要把父类的private继承下来,但只是不能直接访问罢了。

当然,其实没那么简单,上述例子只讨论了全部成员都是同一个类型的情况,如果类型不同,那么就涉及到内存对齐的问题了,但这个问题非常复杂,这里不做讨论。


评论