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

C++继承的理解与四种默认构造函数探索

程序员文章站 2022-11-11 12:34:08
继承定义 继承的目的: 是为了面向对象的复用,所谓复用就是父类是子类的一部分。 继承的定义:c++中一个子类(派生类)可以由一个父类(基类)派生而来,这是单继承;一个子类由多个基类派生而...

继承定义

继承的目的:  是为了面向对象的复用,所谓复用就是父类是子类的一部分。

继承的定义:c++中一个子类(派生类)可以由一个父类(基类)派生而来,这是单继承;一个子类由多个基类派生而来,这叫多继承;如果两个子类同时由一个基类派生而来,并且他们有一个子类,这是菱形继承(钻石继承)。
如图所示:
C++继承的理解与四种默认构造函数探索

不同的继承格式

单继承格式:
class 派生类名:继承方式 基类名
{
<派生类新定义成员>
};

其中,class是关键词,<派生类名>是新定义的一个类的名字,它是从<基类名>中派生的,并且按指定的<继承方式>派生的。<继承方式>常使用如下三种关键字给予表示:

public 表示公有继承,在共有继承下,积累的成员属性不会变化,其中私有成员不可见;
private 表示私有继承,在私有继承情况之下,积累的public成员和protected成员变为私有,基类私有成员不可见;
protected 表示保护继承,基类public成员变为保护,私有成员不可见;

多继承的定义格式如下:

class <派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>,…
{
<派生类新定义成员>
};

菱形继承定义格式如下:
class <派生类名1>:<继承方式><基类名1>
{
<派生类新定义成员>
};
class <派生类名2>:<继承方式><基类名1>
{
<派生类新定义成员>
};
class <派生类名3>:<继承方式1><派生类名1><继承方式1><派生类名1>
{
<派生类新定义成员>
};
注意:对class将采用私有继承,对struct将采用公有继承(主要是为了兼容c中的struct)。
也就是说
class base1{};
struct base2{};
class derive:base1,base2{};
那么,derive类将私有继承base1,公有继承base2。相当于:
class derive:private base1,public base2{};

对于默认构造函数的正确理解

(1)如何理解正确理解默认构造函数
(2)理解”被需要得时候“是什么时候
对于我这样一个刚接触c++不久的人来说,有时候觉得c++好简单,但是又觉得好复杂,因为对于里面的东西理解总是有误,
例如:
    刚开始接触的时候认为如果不显示定义构造函数,那么编译器一定会帮我们合成一个构造函数,并且帮我们初始化成员变量。真的是这样么???
    直到到了认识了继承这一块知识之后,才发现事实和我认为的偏差好大,一脸的懵逼,在继承中发现编译器在认为 “被需要”的时候会自动合成构造函数,问题又来了,什么时候是被需要的???好复杂,看不懂了。

什么是默认构造函数??

默认构造函数满足下列条件之一:
(1)不带形参的构造函数;
(2)为所有的形参提供默认实参的构造函数。

何时调用默认构造函数??

class base
{
public:
    base(int a = 10, int b = 20)  //默认构造函数
    {
        _a = a;
        _b = b;
    }
private:
    int _a;
    int _b;
};
int main()
{
    base b1;  //这里就会调用默认base类的构造函数
    return 0;
}

”被需要“是什么时候??

class base
{
public:
    int _a;
    int _b;
};
int main()
{
    base b1;
    return 0;
}

这段代码被需要了么?会不会调用默认构造函数?这里我通过打断点调试的方式发现根本不在这里停下,为什么?很明显他不被需要,因为编译器认为它什么事都不做,所以就没有生成相应的汇编代码,进行了优化。毕竟你啥事都不做,不能白白给你构造啊!浪费资源。我在vs2008运行此程序发现如下
C++继承的理解与四种默认构造函数探索
看起来一个警告没啥事,但是仔细看会发现b1没有初始化,那么我们还能使用类里面的成员么?肯定不能了。
默认构造函数被需要是相对于一个程序,但是我们如果要使用类中的成员变量,那么变脸和必须被初始化,这种需要并不能让编译器合成默认构造函数,只有编译器被需要才会合成默认构造函数。
总结:
当类只含有内置类型或复合类型的成员时,编译器是不会为类合成默认构造函数的,这种类并不符合”被需要“的条件,甚至当类满足“被需要”条件,编译器合成了默认构造函数时,类中内置类型与复合类型数据成员依然不会在默认构造函数中进行初始化。

何时编译器认为被需要合成默认构造函数,主要包括了四种情况,如下:

1.包含类的对象数据成员,编译器”被需要“合成默认构造函数
如果一个b类不含有构造函数,但是它包含了另外一个a类的对象,且该类对象有默认构造函数,那么在b类调用的情况下编译器会默认合成一个构造函数,例如:
class a
{
public:
    a(int num = 10, int s = 100)//a类默认构造函数
    {
        _num = num;
        _s = s;
    }
int _num;
int _s;
};
class b
{
public:
    a aa;   //调用了a类的对象
    int _a;
    int _b;
};
int main()
{
    b b1;  //合成b类的默认构造函数
    return 0;
}
通过转到反汇编我们可以看到调用了b的构造函数,在b的构造函数中调用了a的构造函数
C++继承的理解与四种默认构造函数探索
2. 基类含有默认构造函数的派生类
当一个派生类的基类含有默认构造函数时,这时候编译器认为是被需要的,因此会合生一个默认构造函数。例如:
class a
{
public:
    a(int num = 10, int s = 100)
    {
        _num = num;
        _s = s;
    }
int _num;
int _s;
};
class b:public a  //b是a的派生类
{
public:
    int _a;
    int _b;
};
int main()
{
    b b1;  //为b合成默认构造函数
    return 0;
}
3. 带有虚函数的类
这里可以分为两种情况
(1)类中自己含有虚函数
(2)类继承的基类含有虚函数
这两种情况都会为这个类合成默认构造函数,因为被编译器所需要,具体原因是因为含有虚函数的类都有一个虚函数指针,指向一张虚函数表,而且编译器将指针的初始化放在了构造函数中,因此对于这样一含有虚函数的类,当我们不自己定义构造函数时,编译器会给我们自动合成一个默认构造函数
4 .带有虚基类的类
首先我们要明白这里的虚基类是相对的一个类,并不是针对所有的类它都是一个虚基类。例如下图:在菱形继承中,存在二义性问题,因此我们可以通过虚拟继承的方式解决二义性的问题,在虚拟继承中,这里b就是c1和c2的虚基类,而c1和c2就是带有虚基类的类
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;
};

void funtest(c1* ptr)  //重点观察我们的这个函数
{
    ptr->_b = 10;
}
int main()
{
    d* d = new d();
    c1* c = new c1();
    funtest(d);
    funtest(c);
    return 0;
}
C++继承的理解与四种默认构造函数探索
我们可以看到funtest函数形参是c1类型的指针,但是实参是不同的,但是我们运行之后发行,可以编译通过,说明这里和形参的类型无关,因为这里是一个带有虚基类的类,因此编译器认为是被需要的,因此会自动合成一个默认构造函数,并且有一个虚拟指针指向一张虚函数表,指向b类的构造函数,最后两个实参都指向了同一个地址,就可以找到_b 在编译器是否会自己合成默认构造函数这个问题上,并不是我所想的那么简单,如果发现错误,希望被指正!