C++继承

1. 继承的概念

        继承是类设计层次的复用,使得程序员可以在原有类的基础上进行扩展。这样产生的新类称为派生类,原有的类称为基类。继承可以使得代码的复用程度更高,呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。

2. 基类成员在派生类中的权限变化 

  •  基类的private成员在派生类中不可见。
  • class声明的类默认的继承方式是private,struct声明的类的继承方式是public。
继承方式基类 public 成员基类 protected 成员基类 private 成员
public在派生类中仍为 public仍为 protected不可访问
protected降级为 protected仍为 protected不可访问
private降级为 private降级为 private不可访问

【注意】1.友元关系不能够被继承。即基类的友元函数不能访问派生类的非public成员。

               2.静态成员可以被继承,被派生类继承的静态成员和基类的静态成员公用相同的内                 存空间。

3. 基类和派生类之间的相互赋值

  • 派生类的对象可以直接赋值给基类的对象/指针/引用(向上转型安全)。

  •  基类的对象不能直接赋值给派生类的对象/指针/引用。(向下转型危险)

  •  如果基类是派生类的引用,或者基类类型的指针是指向派生类的,那么可以通过强制类型转换的方式将基类类型的引用,或者基类类型的指针,赋值给派生类的引用(或指针)。

 4. 同名成员的重定义行为

        当派生类中存在和基类中相同名称的成员变量或者成员函数(不要求相同的参数),派生类中的成员变量或者成员函数就与基类中的构成了重定义。依据就近原则,派生类会优先使用自己的这些重定义的成员。

#include <iostream>
#include <string>
using std::cout;
using std::endl;

void Test3()
{
    class A
    {
    public:
        A (int n = 0)
        :_n(n)
        { }
        void function() {cout << "A:" << _n << endl; };
    protected:
        int _n;
    };

    class B : public A
    {
    public:
        B (int n = 0)
        :A(n)
        ,_n(n)
        { }
        void function() 
        {
            ++_n;
            cout << "B:" <<  _n << endl;   //优先使用派生类里重定义的成员变量
            cout << "A:" << A::_n << endl; //需要使用范围限定符才可以显式使用基类的成员变量
        }
    protected:
        int _n;
    };

    B b(10);
    b.function();    //优先调用派生类里冲定义的成员函数
    b.A::function(); //需要使用范围限定符才可以显式调用基类的成员函数
}

int main()
{
    Test3();
    
    return 0;
}

5. 派生类的默认函数

  • 派生类会在构造函数里调用基类的构造函数,保证基类的对象比派生类对象更早实例化。
  • 派生类会在拷贝构造函数里面调用基类的拷贝构造,完成属于基类那一部分成员的拷贝。
  • 派生类会在赋值运算符重载里调用基类的赋值运算符重载。
  • 派生类会在析构函数里面调用基类的析构函数,但是需要等派生类对象先完成析构。
#include <iostream>
#include <string>
// #include <windows.h>
using std::string;
using std::cout;
using std::endl;

class Person
{
public:
    Person(string name = "", unsigned int age = 0, string sex = "")
    :_name(name)
    ,_age(age)
    ,_sex(sex)
    { }

    Person (const Person& p)
    :_name(p._name)
    ,_age(p._age)
    ,_sex(p._sex)
    { }

    Person& operator=(const Person& p)
    {
        if (this != &p)
        {
            _name = p._name;
            _age = p._age;
            _sex = p._sex;
        }

        return *this;
    }

    ~Person()
    { }

    void InfoMessage()
    {
        cout << "name:" << _name << endl;
        cout << "sex:" << _sex << endl;
        cout << "age:" << _age << endl;
    }

protected:
    string _name;
    string _sex;
    unsigned int _age;
};

/**
 * @brief 学生类,继承了Person类
 * 
 */
class Student : public Person
{
public:
    Student(string name = "", string sex = "", unsigned int age = 0, string stuId = "")
    :Person(name, age, sex)   //显式调用基类的构造函数完成基类部分的构造
    ,_stuId(stuId)
    { }

    Student(const Student& s)
    :Person(s)  //const Student&  ---->   const Person&,调用基类的构造函数完成基类部分的构造              
    ,_stuId(s._stuId)
    { }

    Student& operator=(const Student &s)
    {
        if (this != &s)
        {
            Person::operator=(s);   //const Student&   ---->    const Person&, 调用基类的复制运算符
            _stuId = s._stuId;
        }

        return *this;
    }

    ~Student()
    { 
       //编译器会在派生类完成析构之后,默认调用基类的析构函数,确保先析构派生类,再析构基类
    }

    inline void InfoMessage()
    {
        cout << "stdId:" << _stuId << endl;
        Person::InfoMessage();
    }

private:
    string _stuId;   //学号
};

int main()
{
    Student s("Anna", "female", 22, "0123456");
    s.InfoMessage();
    return 0;
}

 6. 多继承

6.1 定义

  • 单继承:一个派生类只有一个基类。

  • 多继承:一个子类有多个基类。

  • 菱形继承:多继承的一种,一个子类有多个基类,其中一些基类又具有共同的基类。

6.2 菱形继承存在的问题

  • 数据冗余性问题

  •  二义性问题
#include <iostream>

class A
{
public:
    int _a = 0;
};

class B : public A
{
public:
    int _b = 0;
};

class C : public A
{
public:
    int _c = 0;
};

class D : public B, public C   //D类属于多继承的情况
{
public:
    int _d = 0;
};


int main()
{
    D d;
    // d._a = 1;     //err,不明确这个_a是继承自类B还是类C
    d.B::_a = 1;  //明确指定这个_a来自哪个基类才可以,但是存在数据冗余
    d.C::_a = 1;  //明确指定这个_a来自哪个基类才可以,但是存在数据冗余
    d._b = 2;
    d._c = 3;
    d._d = 4;
    return 0;
    
}

  • 内存布局

6.3 虚继承

        使用关键字virtual可以解决菱形继承中的数据冗余和二义性问题。

#include <iostream>
#include <cstdint>

class A
{
public:
    int _a = 0;
};

class B : virtual public A     //此时A是B的虚基类
{
public:
    int _b = 0;
};

class C : virtual public A     //此时A是C的虚基类
{
public:
    int _c = 0;
};

class D : public B, public C   //D类属于多继承的情况
{
public:
    int _d = 0;
};


int main()
{
    D d;
    std::cout << sizeof(d) << std::endl; //根据不同的编译器会有不同的结果
    d._a = 1;
    d._b = 2;
    d._c = 3;
    d._d = 4;
    return 0;

}

        使用虚继承之后,每个继承虚基类的类都有一个虚指针,虚指针指向虚基表,虚基表里记录了虚指针到虚基类共享成员的偏移量。

        使用虚继承后的菱形继承内存分布:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值