【c++学习笔记】继承

什么是继承?

  • 继承是面向对象程序设计使代码可以复用的重要手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能。

继承的格式:

这里写图片描述

实例:

#include <iostream>
using namespace std;


class Base
{
public:
    void TestFunc()
    {
        cout << "Base::TestFunc()" << endl;
    }

private:
    int _b;
};

class Derived :public Base
{
public:
    void TestFunc2()
    {
        cout << "Derived:TestFunc()" << endl;
    }
private:
    int _d;
};

继承权限和访问限定符

继承方式 基类的public成员 基类的protected成员 基类的private成员 继承引起的访问控制关系变化概括
public继承 仍为public成员 仍为protected成员 不可见 基类的非私有成员在派生类的访问属性都不变
protected继承 变为protected成员 仍为protected成员 不可见 基类的非私有成员都成为派生类的保护成员
private继承 变为private成员 变为private成员 不可见 基类的非私有成员都成为派生类的私有成员

protected成员和private成员

class Base
{
protected:
    int _b1;
private:
    int _b2;
};

class Derived : public Base
{
public:
    void SetValue()
    {
        _b1 = 10;
        _b2 = 20;
    }
};

这里写图片描述

  • 区别:基类中的protected成员可以在派生类中访问,但是基类中的private成员不能在派生类中访问。
class Base
{
protected:
    int _b1;
private:
    int _b2;
};

class Derived : public Base
{
public:
    int _d;
};

int main()
{
    Derived d;
    d._b1 = 10;
    d._b2 = 20;
}

这里写图片描述

  • 相同点:protected对象和private对象都不能被其派生类对象访问。

注:

  • 使用关键字class时默认继承方式为private,使用struct时默认的继承方式是public。

单继承对象模型

class Base
{
public:
    int _b;
};

class Derived : public Base
{
public:
    int _d;
};

int main()
{
    Derived d;
    d._b = 10;
    d._d = 20;
}

这里写图片描述

下断点,调出内存窗口,&d,然后单步执行,我们可以发现,先给地址较小的赋值,所以,单继承对象模型如下:
这里写图片描述

派生类的默认成员函数

  • 继承体系下,派生类中如果灭有显示定义这六个成员函数,编译器则会合成这六个默认的成员函数(不是每次都合成,看有没有必要)。
  • 说明:
    基类没有缺省构造函数,派生类必须要在初始化列表中显式给出基类名和参数列表
    基类没有定义构造函数,则派生类也可以不用定义,全部使用缺省构造函数

注:
静态成员也会被继承,友元函数不会被继承(因为友元函数不属于类的成员函数)

  • 继承体系下派生类和基类构造函数和析构函数的调用次序?
class Base
{
public:
    Base()
    {
        cout << "Base::Base()" << endl;
    }

    ~Base()
    {
        cout << "Base::~Base()" << endl;
    }
};

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

    ~Derived()
    {
        cout << "Derived::~Derived()" << endl;
    }
};


void TestFunc()
{
    Derived d;

}

int main()
{
    TestFunc();

    return 0;
}

这里写图片描述

  • 从上面的打印结果我们不难看出,继承体系下,派生类对象在构造时先去调用自己的构造函数,但在初始化列表中先去调用基类的构造函数,然后再构造自己;在销毁时,调用派生类自己的析构函数,在对自己的资源释放完后,出析构函数之前,再去调用基类的析构函数。

继承体系中的作用域

  • 在继承体系中基类和派生类是两个不同作用域
  • 子类和父类中有同名成员,这种情况就是同名隐藏,子类成员将屏蔽父类对成员的直接访问(在子类成员函数中,可以使用 基类::基类成员 访问)
  • 注意在实际中在继承体系里面最好不要定义同名的成员
class Base
{
public:
    void Test()
    {
        cout << "Base::Test()" << endl;
    }
    int _b;
    int _a;
};


class Derived : public Base
{
public:
    void Test()
    {
        cout << "Derived::Derived()" << endl;
    }

    int _a;
};


void TestFunc()
{
    Derived d;
    d._a = 10;

    d.Test();
}

int main()
{
    TestFunc();

    return 0;
}

这里写图片描述
这里写图片描述
可以由内存窗口看出来赋值是给派生类中的_a赋值的(单继承对象模型),掉Test函数也是调的派生类中的,这里不会形成函数重载,因为不是一个作用域(基类和派生类是两个不同的作用域)

赋值兼容规则

在public继承权限下,子类和父类对象之间有:

  • 子类对象可以赋值给父类对象, 父类对象不能赋值给子类对象
    为什么是这样呢?
    这里写图片描述

  • 父类的指针/引用可以指向子类的对象,子类的指针/引用不可以指向父类的对象。
    因为指针要解引用,要是父类的指针指向子类的对象,解引用后只是会访问子类对象中属于父类的成员,这样并没有什么影响;但是,如果是子类的指针指向父类的对象,解引用后就可能会造成非法访问。

什么叫菱形继承

如下,这种继承方式就叫做菱形继承
这里写图片描述

菱形继承的对象模型

#include <iostream>
using namespace std;

class B
{
public:
    int _b;
};

class C1 :public B
{
public:
    int _c1;
};

class C2 :public B
{
public:
    int _c2;
};

class D : public C1, public C2
{
public:
    int _d;
};


int main()
{
    cout<<sizeof(D)<<endl;
    d1._b = 10;
    return 0;
}

这里写图片描述
这里写图片描述
这里写图片描述
但是,这里又会有一个问题,那就是在最底下的派生类D中,会有两份_b,在对象d1中访问_b就会报错,如果想要访问,就必须加访问限定符::
如何解决这种问题呢?就引出了虚拟继承。

什么是虚拟继承?

就是在继承权限的前面再加上virtual关键字

class B
{
public:
    int _b;
};

class D : virtual public B
{
public:
    int _d;
};

int main()
{
    cout << sizeof(D) << endl;

    return 0;
}

这里写图片描述

虚拟继承的对象模型

从单继承对象模型,上面的D大小应该为8字节,但是打印出的结果为12,那么多出的四个字节用来放什么呢?

class B
{
public:
    int _b;
};

class D : virtual public B
{
public:
    int _d;
};

int main()
{
    D d1;
    d1._b = 10;
    d1._d = 20;

    return 0;
}

这里写图片描述
我们可以看到编译器为d1合成了构造函数,我们之前说过,若非必要,编译器是不会合成构造函数的,那么,这里构造函数做了什么呢?
这里写图片描述
在对_b和 _d赋值之前,前面的4个字节还存放了类似地址的数据。那么这个指针指向的是什么呢?其实这个指针指向的是一张存放偏移量的表格,所以可以得出虚拟继承的对象模型如下:
这里写图片描述

菱形虚拟继承

什么是菱形虚拟继承?
如图,就是在上面的两条边的继承加上virtual关键字
这里写图片描述

菱形虚拟继承的对象模型

class B
{
public:
    int _b;
};

class C1: virtual public B
{
public:
    int _c1;
};

class C2: virtual public B
{
public:
    int _c2;
};

class D : public C1, public C2
{
public:
    int _d;
};

int main()
{
    D d1;
    cout << sizeof(D) << endl;
    d1._b = 10;
    d1._c1 = 20;
    d1._c2 = 30;
    d1._d = 40;

    return 0;
}

这里写图片描述
这里写图片描述
由打印结果可知,大小为24,由内存窗口,可以得出,基类的成员_b确实只存放了一份,这24个字节,除 _b,_c1,_c2,_d外还有8字节,这八字节都放的是地址,指向的也都是一张存放偏移量个表格,所以,菱形虚拟继承的对象模型如下:
这里写图片描述

猜你喜欢

转载自blog.csdn.net/virgofarm/article/details/80869473