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{};

赋值兼容规则 ---public继承(is -- A关系)

1.子类可以直接赋值给父类;
2,父类的指针或引用可以指向子类。

因为is-A继承关系:“表示类与类之间的继承关系;子类直接继承了父类,父类中的内容是子类的一部分,可以通过切片的方式直接把父类的那一部分直接切给他,如图

注意虽然子类的指针引用可以通过强制类型转换的方式指向基类,但是如果那样转换以后的指针就不能访问子类的其他成员了。

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

(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运行此程序发现如下

看起来一个警告没啥事,但是仔细看会发现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的构造函数
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;
}

我们可以看到FunTest函数形参是C1类型的指针,但是实参是不同的,但是我们运行之后发行,可以编译通过,说明这里和形参的类型无关,因为这里是一个带有虚基类的类,因此编译器认为是被需要的,因此会自动合成一个默认构造函数,并且有一个虚拟指针指向一张虚函数表,指向B类的构造函数,最后两个实参都指向了同一个地址,就可以找到_b
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
C++ 默认构造函数是在没有显式定义构造函数的情况下自动生成的特殊成员函数。它通常用于在创建对象时进行初始化操作。默认构造函数无参数,不接受任何实参。当我们通过调用类的构造函数来创建对象时,如果没有提供实参,则编译器会自动调用默认构造函数默认构造函数的作用是确保对象的所有成员变量都被正确初始化。例如,如果一个类有一个int类型的成员变量,那么在默认构造函数中,可以将该成员变量初始化为0。如果没有默认构造函数,当我们创建对象时,该成员变量可能会未被初始化,导致程序运行时出现意外结果。 另一个重要的地方是,当我们定义了类的其他构造函数时(比如有参数的构造函数),默认构造函数依然会被生成。这是因为在某些情况下,我们可能只想使用默认构造函数来创建对象,而不希望传递实参。此时,默认构造函数就能满足需求。当我们重载构造函数时,可以使用默认参数来实现默认构造函数的功能。 需要注意的是,默认构造函数在一些特殊情况下可能不会被生成。例如,如果我们显式定义了有参数的构造函数,但没有提供默认构造函数,那么编译器将不会自动生成默认构造函数,这意味着我们不能再使用无参的方式来创建对象。 总之,理解C++默认构造函数的作用和用法对于编写高质量的代码至关重要。它可以帮助我们确保对象的正确初始化,并且在一些特殊情况下可以提供方便的使用方式。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值