继承

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

继承是一种复用手段,在继承关系里,基类继承派生类的成员,以此来达到复用的效果。

先看一段简单的继承:
简单认识下继承实现复用,
这里写图片描述

成员访问限定符&继承关系
这里写图片描述

三种继承关系下基类成员的在派⽣类的访问关系变化
这里写图片描述
简单的总结下:
三种继承关系:public,protected,private其实是权限放缩的过程,public继承不会改变父类成员(成员变量&成员函数)的性质,proteced继,父类的public成员访问权限成为了protected(权限放缩),private继承,成员权限放缩的最小。均成为私有。
备注:protected与private成员对类外均为不可见,两者间的区别主要体现在继承过程中,private成员子类继承后子类仍然不可见,protected成员可见。

总结:
1.基类的私有成员在派⽣类中是不能被访问的,如果⼀些基类成员不想被基类对象直接访问,但需要在派⽣类中能访问,就定义为保护成员。可以看出保护成员限定符是因继承才出现的
2.public继承是⼀个接口继承,保持is-a原则,每个⽗类可⽤的成员对⼦类也可⽤,因为每个⼦类对象也都是⼀个⽗类对象。
3.protetced/private继承是⼀个实现继承,基类的部分成员并未完全成为⼦类接口的⼀部分,是 has-a 的关系原则,所以⾮特殊情况下不会使⽤这两种继承关系,在绝⼤多数的场景下使⽤的都是公有继承。
4.不管是哪种继承⽅式,在派⽣类内部都可以访问基类的公有成员和保护成员,但是基类的私有成员存在但是在⼦类中不可见(不能访问)。
5.使⽤关键字class时默认的继承⽅式是private,使⽤struct时默认的继承⽅式是public,不过最好显⽰的写出继承⽅式。
6.在实际运⽤中⼀般使⽤都是public继承,极少场景下才会使⽤protetced/privat继承.。

备注:struct与class的区别
a.struct的默认成员是共有的,class是siyoude;
b.struct的默认继承关系是共有的,class是私有的。

继承与转换–赋值兼容规则–public继承
1. ⼦类对象可以赋值给⽗类对象(切割/切⽚);
2. ⽗类对象不能赋值给⼦类对象;
3. ⽗类的指针/引⽤可以指向⼦类对象;
4. ⼦类的指针/引⽤不能指向⽗类对象(可以通过强制类型转换完成)。但这样做会留下隐患,父类中没有子类的成员,子类通过强转赋给父类,父类访问子类定义的成员时,因为自身没有导致访问越界,造成程序崩溃。

#include <iostream>
#include <string>
using namespace std;

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

class Student : public Person
{
public:
    int _num; // 学号
};

void Test()
{
    Person p;
    Student s;

    // 1.⼦类对象可以赋值给⽗类对象(切割 /切⽚)
    p = s;

    // 2.⽗类对象不能赋值给⼦类对象
    //s = p;  编译器不会允许这样的操作发生

    // 3.⽗类的指针/引⽤可以指向⼦类对象
    Person* p1 = &s;
    Person& r1 = s;

    // 4.⼦类的指针/引⽤不能指向⽗类对象(可以通过强制类型转换完成)
    Student* p2 = (Student*)& p;
    Student& r2 = (Student&)p;

    //p2->_num = 10;  赋值过来后,但访问子类定义的成员会造成崩溃
}

int main()
{
    Test();

    system("pause");
    return 0;
}


这里写图片描述
从图片中也可以看出,如果子类强转赋给父类(引用和指针,对象赋值即便强转也无法赋过去),父类在指向_num时,因为后续空间不是自己的,会非法访问,造成崩溃。

继承体系中的作用域:
1. 在继承体系中基类和派⽣类都有独⽴的作⽤域;
2. ⼦类和⽗类中有同名成员,⼦类成员将屏蔽⽗类对成员的直接访问。(在⼦类成员函数中,可以使⽤基类::基类成员 访问)–隐藏 –重定义;
3. 但在继承体系中最好不要定义同名的函数。

#include <iostream>
#include <string>
using namespace std;

class A
{
public:
    void Display()
    {
        cout << "A::Display()" << endl;
    }
protected:
    int _a;
};

class B :public A
{
public:
    void Display()
    {
        cout << "B::Dispiay()" << endl;
    }
protected:
    int _b;
};

int main()
{
    B b;
    b.Display();
    b.A::Display();

    system("pause");
    return 0;
}

输出结果是:
这里写图片描述

思考下重载与重定义的区别?
重载:必须在同一作用域内,函数名相同,参数类型/个数不同构成重载。
重定义:在继承中,子类与父类有同名成员,子类成员将隐藏父类的成员的访问。

扩展:
看下面的代码

#include <iostream>
using namespace std;

int x = 10;

int main()
{
    int x = 20;
    //就近原则
    cout << x << endl;
    //'::'默认为全局域
    cout << ::x << endl;

    system("pause");
    return 0;
}

这里写图片描述

派⽣类的默认成员函数:
在继承关系⾥⾯,在派⽣类中如果没有显⽰定义这六个成员函数,编译系统则会默
合成六个默认的成员函数。(主要为前四个)

#include <iostream>
#include <string>
using namespace std;

class person
{
public:
    person(const char* name)
        :_name(name)
    {
        cout << "person(const char* name)" << endl;
    }

    person(const person& p)
        :_name(p._name)
    {
        cout << "person(const person& p)" << endl;
    }

    person& operator=(const person& p)
    {
        cout << "person& operator=(const person& p)" << endl;

        if (this != &p)
        {
            _name = p._name;
        }

        return *this;
    }

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

protected:
    string _name;
};

class student:public person
{
public:
    student(const char*name,int num)
        :person(name)  //匿名构造
        ,_num(num)
    {
        cout << "student(const char*name,int num)" << endl;
    }

    student(const student& s)
        :person(s)
        ,_num(s._num)
    {
        cout << "student(const student& s)" << endl;
    }

    student& operator= (const student& s)
    {
        cout << "student& operator= (const student& s)" << endl;

        if (this != &s)
        {
            person::operator=(s);
            _num = s._num;
        }
        return *this;
    }

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

private:
    int _num;
};

void test()
{
    student stu1("hehe", 18);
    student stu2(stu1);

    student stu3("lala", 20);
    stu3 = stu2;
}
int main()
{
    test();

    system("pause");
    return 0;
}

这里写图片描述
分析运行结果可以看出,子类在实现自身的构造函数时,需要使用匿名构造,直接构造父类的成员变量是不被允许的,在赋值运算符的重载时,同样赋值父类成员时需要调用父类的赋值运算符重载(此时父类的赋值是重定义的,需要person::operator=(s)这样去调用),在析构时遵循先构造的后析构,所以先析构子类,父类的析构会自动调用

不难想到:我们如果在创建一个类时,让其构造函数私有,那么这个类将无法被继承。

单继承&多继承
1.单继承–⼀个⼦类只有⼀个直接⽗类时称这个继承关系为单继承
2. 多继承–⼀个⼦类有两个或以上直接⽗类时称这个继承关系为多继承
3.
这里写图片描述

c++支持多继承会造成菱形继承的产生

下面是一个简单的菱形继承

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 ; // 主修课程
};

上述菱形继承的图解:
这里写图片描述
菱形继承对象的模型
这里写图片描述

菱形继承的问题经过上述两图的简单分析会明显的暴露出来:
1.代码的二义性(Assistant的对象访问 _name 时访问 Student类的还是Teacher类的)
2.数据冗余的问题,显然 Assitant的name存在了两个。

代码的二义性c++不难想出,可以使用 类名+’::’+成员名 这样的方式去解决,
数据的冗余c++使用了虚继承的方式去进行解决。下面就探讨下虚继承:

虚继承就是在继承关系前加 virtual

class Person
{}
class Student :virtual public Person
{}
class Teacher :virtual public Person
{}
class Assistant : public Student, public Teacher

虚继承是为了解决菱形继承数据冗余的问题产生的,所以很显然Assistant类里面关于Person类的成员只有一份,指向共有的地方。那么c++是如何去实现虚继承的呢?
为方便演示改用下面的代码:

#include <iostream>
#include <string>
using namespace std;

class A
{
public:
    int _a;
};

class B:virtual public A
{
public:
    int _b;
};

class C:virtual public A
{
public:
    int _c;
};

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

int main()
{
    D d;
    d.A::_a = 1;
    d.B::_a = 2;
    d._b = 3;
    d._c = 4;
    d._d = 5;

    return 0;
}

首先先看下对象d的内存监视:
这里写图片描述
上图截取了普通继承与虚继承的内存管理,可以看出,虚继承在存储公共区域时,存储了可以指向虚基表的一个指针,虚基表内存储了到公共区域的偏移数。

在实现虚继承时引入虚基表用来存储到公共区域的偏移数,解决数据冗余的问题,当公共区域所占字节数越大,虚继承的优势便越明显。

以上呢就是我对继承的简单认识,希望大家多多交流。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值