继承,继承,继承

目录

继承

查看对象模型

继承中构造和析构的调用顺序

继承中同名成员的处理方法

继承中的静态成员特性

多继承

菱形继承

虚继承

虚继承的解决二义性的原理

虚继承中通过指针来访问变量


继承

继承的作用:代码复用以及扩展类的功能

私有权限:类内可访问,类外实例化、子类都不可访问

保护权限:类内可访问,子类可访问,类外实例化不可访问

公有权限:类内可访问,子类可访问,类外实例化可访问

查看对象模型

开始—VS—Developer Command Prompt for VS 2019—cd 到要查看的.cpp文件的目录—输入:cl/d1 reportSingleClassLayout类名 文件名全称

继承中构造和析构的调用顺序

有如下代码:

class A
{
public:
    A()
    {
        cout << "A的构造函数" << endl;
    }
    ~A()
    {
        cout << "A的析构函数" << endl;
    }
public:
    Son s;
};

class B
{
public:
    B()
    {
        cout << "B的构造函数" << endl;
    }
    ~B()
    {
        cout << "B的析构函数" << endl;
    }
};

class C
{
public:
    C()
    {
        cout << "C的构造函数" << endl;
    }
    ~C()
    {
        cout << "C的析构函数" << endl;
    }
};

class D:public A
{
public:
    D()
    {
        cout << "D的构造函数" << endl;
    }
    ~D()
    {
        cout << "D的析构函数" << endl;
    }
public:
    B b;
    C c;
};

void test02()
{
    D d;
}

其顺序是:父类的构造函数—成员对象的构造函数—自身的构造函数—(函数结束)—自身的析构函数—成员对象的析构函数—父类的析构函数

构造是从祖类开始,再父类,然后对象,然后自身,析构则是反过来,类似于栈,先把父类的构造和成员对象的构造压入栈中,再依次弹出。

继承中同名成员的处理方法

1.当子类和父类有同名成员时,子类的同名成员会隐藏(不是覆盖)父类的同名成员

class Father
{
public:
    Father()
    {
        a = 10;
    }
public:
    int a;
};

class son :public Father
{
public:
    son()
    {
        a = 20;
    }
public:
    int a;
};
void test()
{
    son s;
    cout << s.a << endl;
    cout << sizeof(son) << endl;//大小是8
    //通过父类名作用域来访问
    cout<<s.Father::a << endl;
}

2.当子类和父类有同名函数时,父类的所有函数重载也都会被隐藏

且同样可以通过作用域来访问

s.Father::func(10,20);

继承中的静态成员特性

//1.静态成员可以被继承
class Father
{
public:
    static int mA;
    static void func()
    {
        cout << "Father func()" << endl;
    }
    static void func(int a)
    {
        cout << "Father func(int a)" << endl;
    }
    static void func(int a, int b)
    {
        cout << "Father func(int a,int b)" << endl;
    }
};
int Father::mA = 10;

class Son :public Father
{
public:
    static int mA;
    //static void func()
    //{
    //    cout << "Son func()" << endl;
    //}
    //static int func()
    //{
    //    cout << "int func()" << endl;
    //    return 0;
    //}
};
int Son::mA = 20;

void test()
{
    //2.继承中的静态成员变量一样会被同名的子类成员变量隐藏
    Son s;
    cout << s.mA << endl;
    //cout << s.mB << endl;
    //3.当子类和父类有同名静态函数时,父类的所有函数重载都会被隐藏
    s.Father::func(10);
    //4.(与普通成员函数不一样)继承中,如果在子类中改变基类继承过来的静态函数中的某个特性,无论是返回值,还是参数,都会隐藏基类重载的函数
    s.func(10);
    //5.(与普通成员函数不一样)静态成员函数不能是虚函数
    //6.父类继承过来的静态成员变量就是父类的静态成员变量,是同一个
}

1.静态成员可以被继承。

2.继承中的静态成员变量一样会被同名的子类静态成员变量隐藏,若是不同名的静态成员变量那就是父类的静态成员变量,是同一个。

3.当子类和父类有同名静态函数时,父类的所有函数重载都会被隐藏。

4.(与普通成员函数不一样)继承中,如果在子类中改变基类继承过来的静态函数中的某个特性,无论是返回值,还是参数,都会隐藏基类重载的函数。

5.(与普通成员函数不一样)静态成员函数不能是虚函数。

多继承

一个类有两个以上的父类,当父类中有同名成员时子类会产生二义性,不知道调用哪个成员,不建议使用,但是菱形继承中的虚继承的方法可以解决这个问题。

菱形继承

 菱形继承不也会有二义性吗?怎么解决呢?

虚继承来解决

虚继承

虚继承需要使用virtual关键字,被virtual关键字修饰的继承叫做虚继承,被虚继承的基类叫做虚基类。

class Sheep : virtual public Animal
{
......
};

虚继承的解决二义性的原理

虚继承的派生类,编译器会给它添加一个指针,指针指向类似于表的组织,该表记录了该指针距离变量的偏移量(这个偏移量的意思是,类地址的首地址距离变量的偏移量),而如果你想要查看这个表,可以通过查看对象模型来看,比如代码:

class Animal
{
public:
    Animal()
    {
        mA = 100;
    }
public:
    int mA;
};
class Sheep :virtual public Animal
{

};
class Camel :virtual public Animal
{

};

class SheepCamel :public Sheep, public Camel
{

};

则Sheep的对象模型:

以及SheepCamel的对象模型

 

由图可得,虚继承可以解决二义性的问题,被virtual修饰的类,编译器生成了一个vbptr指针,这个指针指向了类与mA的偏移距离,注意这个mA是放在一个公共区域里的,这样,当SheepCamel子类同时继承了Sheep和Camel两个父类时,并没有同时继承两个父类的mA变量,而是继承了两个父类的vbptr指针,唯一继承的那个mA变量是来自祖类的,所以,这样就不会产生二义性的问题了,你访问的mA就是祖类的mA。

虚继承中通过指针来访问变量

参照上图,可以通过指针来获取变量

    Sheep s;
    s.mA;

    //1.&s;//类的首地址
    //2.(int*)&s;//强转为int*类型,int*类型就是数组类型,每个所占字节为4
    //3.*(int*)&s;
    获取指针中的地址,此时如果你+1,因为是int*类型,是以4为单位的数组,
    即把内存以字节4均分,然后以下标0,1,2,3...划分,所以你+1实质上是走了4个字节,
    通过你观察对象模型,也能取到mA的值,即*((int*)(*(int*)(&s)) + 1) 
    //4.(int *)*(int*)&s;//指向表的首地址
    //5.(int *)*(int*)&s+1;//指向了表存储偏移量的地址
    //6.*((int *)*(int*)&s+1);//这就是偏移量
    cout << *((int*)*(int*)&s + 1) << endl;

    //1.&s;
    //2.(char*)&s;
    char *p;*p为字符串,所占字节单位为1,p为指针,所占字节单位为4
    这里是将&s转化为char*类型,就是字符串类型,所以所占字节单位为1
    //3.(char*)&s+*((int*)*(int*)&s + 1)//所占字节为1,去的是首地址即0,加上偏移量就是4,这个4就是mA的地址。
    //4.把类型转换为Animal指针类型
    cout << ((Animal*)((char*)&s + *((int*)*(int*)&s + 1)))->mA << endl;

输出分别为4和100,4是偏移量,100是mA变量的值

注意(32位,vs):

        1.Int*,char*,double*.....指针类型在内存中所占大小一律为4字节

        2.Int占4字节,char占1字节,double占8字节,float占4字节

        3.int a[n]所占字节为4*n

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值