欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

c++中继承知识点详解

程序员文章站 2024-03-15 18:06:42
...

继承基本知识

定义:

 继承是面向对复用的重要手段。通过继承定义一个类,继承是类型之间的关系建模,共享公有的东西,实现各自本质不同的东西。

继承关系:

 三种继承关系下基类成员的在派生类的访问关系变化(图)
c++中继承知识点详解
 举个栗子(公有继承)

class Person
{
public :
    Person(const string& name)
    : _name(name )
    {}
    void Display ()
    {
        cout<<_name <<endl;
    }
protected :
    string _name ; // 姓名
    string _sex ;
};

class Student : public Person //公有继承
{
protected :
    int _num ; // 学号
};

继承图例解释:

c++中继承知识点详解
私有继承和保护继承很少用到,我们重点要掌握公有继承

继承与转换–赋值兼容规则–public继承

  1. 子类对象可以赋值给父类对象(切割/切片)
  2. 父类对象不能赋值给子类对象
  3. 父类的指针/引用可以指向子类对象
  4. 子类的指针/引用不能指向父类对象(可以通过强制类型转换完成)
class Person
{
public:
    void Display()
    {
        cout << "AA" << endl;
    }
protected:
    string _name; // 姓名
};
class Student : public Person
{
public:
    int _num; // 学号
};

int main()
{
    Person a;
    Student b;
    a = b; //子类对象赋值给基类对象(切片)这个特性是编译器支持的
    b = a; //父类对象不能赋值给子类对象

    Person *p1 = &b; //特性3
    //Person &a1 = b; //特性3
    Student *p2 = (Student*)&a; //特性4
    Student& b1 = (Student&)a; //特性4
    getchar();
    return 0;
}

继承体系中的作用域

  1. 在继承体系中基类和派生类都有独立的作用域。
  2. 子类和父类中有同名成员(成员函数,成员变量)子类成员将屏蔽父类对成员的直接访问。(在子类成员函数中,可以使用 基类::基类成员 访问)–隐藏(重定义)
  3. ==注意在实际中在继承体系里面最好不要定义同名的成员==。
class Person
{
public:
    Person(const char *name = "",int num = 0)
        :_name(name)
        ,_num(num)
    {}

protected:
    string _name; // 姓名
    int _num;
};
class Student : public Person
{
public:
    Student(const char* name = "", const int num1 = 0, int num2 = 0)
        :Person(name,num1)
        ,_num(num2)
    {}
    void Display()
    {
        cout << _num << endl;
        cout <<Person:: _num << endl;//必须显示指出基类作用域才能打印基类成员
    }
protected:
    int _num; // 学号
};

int main()
{
    Person a("boday",15);
    Student b("crash",1502,17);
    b.Display();

    return 0;
}

运行结果:
c++中继承知识点详解
可以很明显看出此时打印的是子类的成员,而隐藏掉了父类的成员,(这就是隐藏

派生类的默认成员函数

 在继承关系里面,在派生类中如果没有显示定义这六个成员函数,编译系统则会默认合成这六个默认的成员函数。
c++中继承知识点详解
来个栗子说说默认成员函数的前四个(后两个不常用

class Person
{
public:
    Person(const char *name = "",int num = 0) //父类构造函数
        :_name(name)
        ,_num(num)
    {}
    ~Person()//父类析构函数
    {
        cout << "~Person()" << endl;
    }
    Person(const Person& p)//父类拷贝构造函数
        :_name(p._name)
        ,_num(p._num)
    {}
    Person& operator=(const Person& p)//父类赋值运算符重载
    {
        if (this != &p)
        {
            _name = p._name;
            _num = p._num;
        }
        return *this;
    }

protected:
    string _name; // 姓名
    int _num;
};
class Student : public Person
{
public:
    Student(const char* name = "", const int num1 = 0, int num2 = 0)//子类构造函数
        :Person(name,num1)   
        ,_num(num2)
    {}
    ~Student()//子类析构函数
    {
        cout << "~Student()" << endl;
    }
    Student(const Student& s)//子类拷贝构造函数
        :Person(s)
        ,_num(s._num)
    {}
    Student& operator=(const Student& s)//子类赋值运算符重载
    {
        Person::operator=(s); //显示调用父类赋值运算符重载
        _num = s._num;
    }
protected:
    int _num; // 学号
};

先调用父类构造函数,在调用基类构造函数;析构函数调用顺序与构造函数相反(先构造后析构,这个和栈的规则有关(先入后出))

继承方式(单继承,多继承,菱形继承)

c++中继承知识点详解

1.单继承

定义:一个子类只有一个直接父类时称这个继承关系为单继承

代码示例:

class A
{
protected:
    int _a;
};

class B : public A //B类 继承 A类
{
protected:
    int _b;
};

2.多继承

定义:一个子类有两个或以上直接父类时称这个继承关系为多继承

代码示例:

class A
{
protected:
    int _a;
};

class B 
{
protected:
    int _b;
};

class C : public A,B
{
protected:
    int _c;
};

3.菱形继承

代码示例:

class Person
{
public:
    string _name; // 姓名
};
class Student : public Person
{
protected:
    int _num; //学号
};
class Teacher : public Person
{
protected:
    int _id; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected:
    string _majorCourse; // 主修课程
};
void Test()
{
    // 显示指定访问哪个父类的成员(二义性问题)
    Assistant a;
    a.Student::_name = "xxx";
    a.Teacher::_name = "yyy";//数据冗余问题
}

很明显菱形继承存在问题,存在二义性和数据冗余的问题。为了解决这个问题就引入了虚继承。

虚继承:解决菱形继承的二义性和数据冗余的问题

在声明派生类时,指定其继承方式时声明为虚继承的方式。如

class A
{
public:
    int _a;
};

class B : virtual public A //声明为虚基类
{
protected:
    int _b;
};

class C : virtual public A //声明为虚基类
{
protected:
    int _c;
};

class D : public B,public C
{
protected:
    int _d;
};

看看测试效果:

void Test()
{
    D d;
    d._a = 10;
}

c++中继承知识点详解
是不是很疑惑到底是如何解决的?那就要深入到底层探索下
c++中继承知识点详解
这里在虚继承时用一个虚基表存放偏移量,这样B和C类同时使用一个虚基表存放A相对于B和C的偏移量,当发生虚继承时A会存放在一个公共区域,这就很好的解决了二义性问题,同时也节省了空间。
当基类通过多条派生路径被一个派生类继承时,该派生类只继承该基类一次,也就是说,基类成员只保留一次
为了保证虚基类在派生类中只继承一次,应当在该基类的所有直接派生类中声明为虚基类,否则仍然会出现对基类的多次继承

虚继承很好的解决了菱形继承带来的问题。

这里建议大家写下代码调试一下,同时查看内存变化。

相关标签: 继承