C++继承及其特点

继承

继承机制是面向对象程序设计,使代码可以复用最重要手段,它允许在保持原有类的基础上进行扩展,增加功能,这样产生的类,叫做派生类,被派生类继承的类叫做基类。继承呈现了面向对象程序设计的层次结构。体现了由简单到复杂的认知过程。

  • 继承方式
    派生类名字: 继承权限:父类的名字
    例如:已存在类class A
    那么派生类定义为
    class Derived: public A

  • 继承权限三种格式
    继承的权限跟类中成员的权限一样,有三种权限
    public. protected. pritave

默认派生类的继承方式为私有继承方式
如果基类为class,派生类默认继承方式私有
如果基类为struct,派生类默认继承方式为共有

  • 派生类定义方式
//Base //基类  父类
//Derived //派生类  子类
class Base
{
public:
    int _b;
};
class Derived : public Base//派生类: 继承方式 基类
{
public:
    int _d;
};

继承类型

基类的成员的三种类型,继承到派生类。保护的类型,继承的子类也是保护类型,在类外不可访问。类内可以。非本类不可访问。私有成员在派生类中不可访问。

public(公有方式)

子类Derived继承Base父类的数据。
共有的继承方式,可以访问父类的共有成员与保护成员。但是无法访问私有成员。
基类中public类的 在 子类中也是public
基类中protected类的成员 在 子类中也是 protected
基类中私有成员,仍然归父类私有,虽然子类继承,但是无法访问。但是可以调用父类中共有的方法来访问。

protected(保护方式)

保护的继承方式
基类中:共有的权限 如果是保护的继承方式, 到了子类中,已经从public类改变为protected类。
基类中保护的成员在子类中访问的权限不变。
基类中的私有成员, 子类继承下来了,但是就是用不了。
派生类里可以访问 保护类成员。 类外不行。私有类只能本类中访问。

private(私有方式)

私有的继承方式,基类中所有的成员变量/函数,在派生类中全部变为私有属性。

继承的特点

对象模型: 类中成员变量在内存中的布局形式(非静态成员变量)

class Base
{
public:
    int _a;
    int _b;
    int _c;
};
class Derived : public Base
{
public:
    int a;
    int b;
    int c;
};

以上的基类与派生类在对象存储形式为:
这里写图片描述

派生类与基类的关系

  • 共有的继承方式,可以将基类对象,当做是派生类对象。
    使用角度:所有用到基类的地方,都可以用派生类来代替。

  • 保护的继承方式,protected/private: 子类—基类 相当于has-a的关系
    相当于在子类中创建了一个基类对象,基类拥有自己的作用域以及对成员的保护权限。

基类/派生类赋值:切片/切割

子类可以给父类赋值。反过来则不行。(这种方式叫切片/切割)
子类本身就是父类的延伸,因此子类中拥有父类拥有的东西。
这里写图片描述
派生类给基类赋值时,可以将自己切开,将上半部分的name赋值给基类对象。这种方式叫切片/切割
切片/切割一个天然的行为,并没有伴随着隐式的类型转换,如下证实:

class Base
{
public:
    int _a;
    int _b;
    int _c;
};
class Derived : public Base
{
public:
    int a;
    int b;
    int c;
};
int main()
{
    Base B;
    Derived D;
    B = D;
    Base& b = D;
//基类引用对象引用派生类是没有问题的。这个引用变量只能访问到属于基类的数据
//此时没有发生类型转换,引用的是派生类切出来的基类对象,不是类型转换后的数据。
    int a = 1;
    double d = i;//这里发生隐式类型转换,但是不影响,因为这只是用常量给变量赋值

    //double& d1 = i;//因为发生隐式类型转换时,会产生一个临时变量
    //此时的double类型的引用变量,引用的是i类型转换后的临时变量的值
    //临时变量具有常属性,因此必须将引用类型用const修饰
    //与常量的属性保持一致,才可以通过编译。
    const double& d1 = i;//此时可以通过编译。
}
  • 基类 = 派生类:
    基类访问派生类中基类需要的数据,基类指针可以指向派生类对象。这时的指针可以访问基类的成员。基类的引用,可以引用派生类对象。这是也只是可以访问基类的数据。
  • 派生类 = 基类:
    基类对象不能强转为派生类对象,赋值给派生类。赋值时,派生类会访了基类中没有的数据, 会造成越界访问。发生错误。
    派生类指针或引用不可以指向或引用基类对象。可能会发生越界访问,所以不允许指向或者引用基类。如果给基类进行强转,将基类引用强转为派生类,强行访问派生类对象,访问到不允许访问的内容,直接出错。除非这个基类对象的指针之前就是指向派生类的指针。
同名隐藏

基类中继承下来的函数,与派生类中的同名函数不能形成重载,而且同名函数还会发生冲突。如果访问相同名称的函数,派生类中只能调用自己类中的函数或者成员。如果想在派生类中访问基类中的同名(成员函数||成员变量)。只能通过加基类类作用域的方式去访问相同名称的基类成员。

    d.Base::fun();

同名函数不能形成重载,因为函数重载的条件就是同名函数必须要在同一作用域,尽管基类与派生类函数名相同但参数信息不同, 他们没有在同一个作用域,因此不能形成重载。误当做重载函数调用时,肯定出错。

其实编译器在编译期间会为基类和派生类成员加上对应的作用域限定符,派生类的成员前会加上派生类的作用域限定符,基类的成员会加上基类的作用域限定符。

            Base::基类成员        Derived::派生类成员

派生类对象构造函数

派生类对象构造时,先调用派生类构造函数,在派生类构造函数的初始化列表中会调用基类的构造函数,在进入派生类对象的函数体之前,会先在初始化列表中完成派生类中成员的初始化,而这个初始化列表中就有基类的初始化,因此它会先初始化基类中成员(构造基类成员,调用基类的构造函数),之后再初始化派生类的成员。

默认的派生类构造函数只会调用基类的默认构造函数。所以基类的构造函数如果是定义的有参数的,这时必须在派生类的构造函数初始化列表中给出定义。否则构造派生类对象时,找不到默认的基类构造函数,代码不能编译。

派生类的析构函数

(先析构自己的,在最后一个括号处调用基类的析构函数)
如果要销毁派生类的对象,则需要掉用派生类析构函数来清理派生类对象中的资源,在派生类析构函数的最后,需要调用基类的析构函数,以完成基类部分资源的销毁。

final修饰的类

final修饰的类,不能被继承
将类的构造函数定义为私有的, 则变为不可继承的类。(C++11)中 被final修饰的类,不能被继承。

继承与友元函数

友元关系不能继承,基类友元不能访问子类私有和保护成员。因为友元函数不是类的成员,所以不能继承。基类的友元函数因此无法访问派生类的任何成员。
派生类的友元函数,不能访问基类的私有成员,如果是私有的继承方式,那么基类的所有成员都不能访问。就跟派生类的访问权限一致,派生类可以访问到的基类对象,友元函数也能访问到。

静态成员

静态成员变量可以被继承。并且派生类与基类公用这个静态成员。

多继承

  • 多继承方式:
class B1; class B2;   //已有的类
        class D:  public B1,public B2;
                B1;
                B2;
            D;        

先继承的基类在上,后继承的基类在下, 派生类在最下。

  • 菱形继承模型:
    这里写图片描述

虚拟继承

虚拟继承

        class D: virtual public B//关键字 virtual

为了解决菱形继承中的二义性问题,大佬们设计出了虚拟继承。虚拟继承的派生类中,不管派生类中有多少个公用的基类,都公用一个基类。因此在派生类中,前4个字节首先存了一个指针,这个指针指向一个类似数组的区域,里面装着要访问基类对象时,该指针要偏移的偏移量。细节如下:

  • 虚拟继承存储
    虚拟继承的派生类,存储方式会先存放派生类对象,将基类对象放在后面存放。
    这里写图片描述

  • 代码表现

#include <iostream>
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.B::a = 2;
    d.C::a = 1;
    d.b = 3;
    d.c = 4;
    d.d = 5;
    cout << sizeof(d) << endl;
}
  • 内存中体现

这里写图片描述

如果有不对的地方,希望大佬批评。

  • 11
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值