C++进阶——继承

一、什么是继承

继承就是一种类与类之间的一种关系。举个例子,如果 B 继承了 A ,那么 A 就相当于成为了 B 的 内部类,因此 B 可以在 A 的基础上添加新的东西(比如新的成员变量或函数)。

二、为什么要用到继承

在设计类时,当我们遇到一种情况:我们要设计很多个类,但是这些类都有 80% 的地方都是相似的,但偏偏就是有些地方不同。这时候我们就可以先把它们共同的部分提取出来写成一个类,然后再用继承的方式用这个类构造其他的类,这样就省事很多了,而且代码也没那么冗余了。简而言之,继承就是用来提取多个类之间的公因数的。

三、基类与派生类

基类就相当于上面说的多个类之间的公因数,而派生类就相当于与公因数相乘的后面的多个单项式。用 a * (b + c + d) 这个式子来举例,基类就相当于 a,即公因数,而派生类就相当于 a * b,a * c,a * d;而 b,c,d 就相当于派生类在继承基类之后额外加的成员。
要注意的是:
(1)如果是基类静态成员的话,派生类只会继承它的使用权,而不会继承一份新的静态成员。意思就是说,所有的派生类与基类公用一个静态成员。
(2)基类的友元不能被继承,因此基类的友元不能访问派生类的东西。

// Person 是基类,Student 是派生类
class Person
{
public:
    void Print()
    {
        cout << "name:" << _name << endl;
        cout << "age:" << _age << endl;
    }
protected:
    string _name = "peter"; 
    int _age = 18; 
};

class Student : public Person // Student 继承了 Person
{
protected:
    int _stuid; 
};

四、继承方式 & 访问方式

C++进阶——继承_第1张图片

这张表一一说明了在 public,protected,private 继承方式下不同成员的访问权限。但对于这张表,我们可以这样记:除了基类的 private 成员之外,其他基类成员在继承后的访问权限是 min(继承方式, 基类中的访问权限),然后基类的 private 成员在被继承后不可访问。

五、派生类对象的切片

C++里面有规定,派生类是可以赋值给基类的对象、引用、指针的,但只能实现派生类与基类都有的成员进行赋值,这就是派生类对象的切片——即把与基类都有的部分切下来赋值,其他基类没有的就不赋值。但是,基类并不能赋值给派生类。

C++进阶——继承_第2张图片

除此之外,基类对象也可以赋值给派生类指针,但基类的对象的地址要进行强制类型转换。但我是不推荐的,因为派生类的大小肯定不小于基类的大小,万一派生类比基类大,那派生类指针访问基类对象就很容易产生越界问题。 

六、继承中的作用域

 1、隐藏

当基类和派生类有同名的成员时,派生类的成员会把由基类继承下来的同名成员“隐藏”掉——就是表面上时看不到由基类继承下来的同名成员的,但是如果用类域访问符指定类域访问,才能访问到基类的同名成员。

2、成员函数的隐藏 & 函数重载的区别

要注意的是,成员函数的重载只发生在相同的类域里,而只要成员函数同名就可以构成隐藏,而且成员函数的隐藏可以发生在不同的类域中。

七、派生类的默认成员函数

1、构造函数

派生类的成员有一部分是来自于基类复制的,因此派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员,因此如果基类没有默认构造函数,则必须在派生类构造函数的初始化列表中显示调用基类的构造函数。

class Person
{
public:
    Person(char name)
        :_name(name)
    {}
private:
    char _name;
};

class Student : public Person
{
public:
    Student(int id, char name)
        :_id(id)
        ,Person(name) // 调用基类的构造函数
    {}
private:
    int _id;
};

2、拷贝构造函数

和构造函数一样,只不过拷贝构造不是默认构造函数,所以和普通的构造函数不同的是派生类在拷贝构造函数的初始化列表里必须显示调用基类的拷贝构造函数。

class Person
{
public:
    Person(Person& p)
        :_name(p._name)
    {}
private:
    char _name;
};

class Student : public Person
{
public:
    Student(Student& s)
        :_id(s.id)
        ,Person(s) // 调用基类的构造函数,派生类切片
    {}
private:
    int _id;
};

3、析构函数

派生类的析构函数会在调用之后自动调用基类的析构函数清理派生类里基类的部分。

4、赋值重载函数

派生类在调用 operator=() 之前必须要先调用基类的 operator=() 完成基类部分的赋值。

class Person
{
public:
    Person& operator=(const Person& s)
    {
        _name = s._name;
        return *this;
    }
private:
    char _name;
};

class Student : public Person
{
public:
    Student& operator=(const Student& s)
    {
        Person::operator=(s); // 先调用基类的 operator=()
        _id = s._id;
        return *this;
    }
private:
    int _id;
};

八、菱形继承 & 虚继承

1、菱形继承 & BUG

C++进阶——继承_第3张图片

一这张图为例,就是 Assistant 这个类继承了 Student 和 Teacher 这两个类,然而 Student 和 Teacher 都继承了 Person 这个类,因此 Assistant 这个类就有两份 Person 类的成员,因此就会构成成员重名的报错问题。而这种继承了同一个类两次的行为就叫“菱形继承”。

2、相应的解决方案——虚继承

为了解决这个问题,C++引入了虚继承。就是通过虚继承这个方式防止上图 Assistant 类里有两份 Person 的成员。

虚继承使用语法:

class Person
{
public :
    string _name ; 
};

class Student : virtual public Person // virtual 加在菱形的腰间
{
protected :
    int _num ; 
};

class Teacher : virtual public Person // virtual 加在菱形的腰间
{
protected :
    int _id ; 
};

class Assistant : public Student, public Teacher
{
protected :
    string _majorCourse ; 
};

你可能感兴趣的:(c++,开发语言)