C++继承时常见问题
目录
前言
子类默认调用父类无参构造
观点一
观点二
子类调用父类有参构造
子类与父类的构造顺序
总结
结语
前言
关于C++继承的基础,请看:《C++继承浅谈》或自行百度
子类默认调用父类无参构造
观点一
如果父类不写构造函数,那么子类构造对象的时候不传参/传参调用的都是子类自己的无参/有参构造。
验证代码如下:
#include <stdio.h>
#include <Windows.h>
class Person
{
public:
};
class Teacher:public Person
{
public:
Teacher(){ printf("这是子类无参构造。\n"); }
Teacher(int a){ printf("这是子类有参构造。\n"); }
};
int main()
{
Teacher t1; // 可以此处下断点观察反汇编
Teacher t2(2);
system("pause");
return 0;
}
运行查看结果:
断点、编译、调试、ALT+8转到反汇编:
F10步过,不会进入函数内部,F11跟进这两个函数的内部:
可以看到,在父类没有写构造方法的情况下,子类的构造无论是函数内部还是函数外部和父类都没有任何关系。
观点二
如果父类写了构造函数,不管父类写的是有参还是无参,子类创建对象的时候无论有参还是无参都会默认调用父类的无参构造函数
测试代码如下:
#include <stdio.h>
#include <Windows.h>
class Person
{
public:
Person(){ printf("这是子类无参构造。\n"); }
Person(int a){ printf("这是子类有参构造。\n"); }
};
class Teacher:public Person
{
public:
Teacher(){ printf("这是子类无参构造。\n"); }
Teacher(int a){ printf("这是子类有参构造。\n"); }
};
int main()
{
Teacher t1;
Teacher t2(2);
system("pause ");
return 0;
}
运行:
反汇编:
跟进函数内部:
下面那个也一样。
调用的都是父类的无参。
这导致父类如果写了有参构造而不写无参构造,那么子类创建对象的时候默认调用父类的无参构造就会失败
如下:
运行:
那么子类如何调用父类有参构造呢?
子类调用父类有参构造
调用方法如下:
执行:
那么子类的无参是否可以调用父类的有参呢?
不可以
因为父类的有参构造需要传入参数,然而子类的无参没有参数:
那么我们为什么不能直接在函数内部调用呢?
也就是说不用构造(int a):Person(a){}这种调用方式,而是如下:
我们先运行一下:
我们发现,子类构造了一个有参的对象,竟然调用了父类的有参和无参构造函数
先注释掉Teacher t1;防止被干扰。
断点跟进一下函数内部查看:
F11跟进函数内部:
一共三次调用。
先F11跟进第一个call调用:
发现这是父类的无参构造。
F10一直到返回,然后F11进入第二个call:
可以看到这是我们手动调用的父类有参构造。
F10返回。
从反汇编层面,我们可以看出,当我们自己调用父类有参构造而不是直接指定的时候,子类还是会调用一下默认无参构造,然后输出自己调用的有参构造,最后输出自己的构造内容。总共三次输出,调用默认无参构造是第一个输出;自己调用的有参构造和输出自己构造内容的顺序与自己函数内部的调用顺序有关。
虽然自己调用有参构造,会构造三次,但是最终还是实现了目的吗?
不是的。
查看如下汇编:
通过以上反汇编可以看出:
当我们手动调用有参构造的时候,实际上就是在Teacher(int a)这个有参构造的缓冲区中进行的构造,也就是说我们Person(3)实际上就是在Teacher(int a)中构造了个临时变量。我们也知道临时变量的生命周期只存在到块语句也就是Teacher(int a)调用完毕就会结束。所以这样调用的有参构造没有任何意义。
如果使用指定有参构造,那么汇编如下:
先看代码:
查看汇编:
这样才是有意义的调用有参构造。
子类与父类的构造顺序
测试时父类和子类都有有参和无参构造,不受到上面任何观点的影响。
测试代码如下:
#include <stdio.h>
#include <Windows.h>
class Person
{
public:
Person(){ printf("这是父类无参构造。\n"); }
Person(int a){ printf("这是父类有参构造。\n"); }
};
class Teacher:public Person
{
public:
Teacher()
{
printf("这是子类无参构造。\n");
}
Teacher(int a):Person(a)
{
printf("这是子类有参构造。\n");
}
};
int main()
{
// Teacher t1;
Teacher t2(2);
system("pause ");
return 0;
}
直接运行查看顺序:
可以看到是先输出父类,再输出子类。
但是这样可以说明子类在构造对象时,先调用父类构造再调用子类构造吗?
不能。
有一种办法,就是查看反汇编,从底层来查看构造函数的执行顺序。
别忘了断点、汇编如下:
我们发现先调用的是子类的有参构造。
继续F11跟进内部:
总结:
通过底层发现,构造函数并不是和输出的内容一样先调用父类。
而是先调用了子类,然后再调用父类的构造函数;只不过是父类的有参构造在子类的函数内部的位置在调用printf之前,所以就有了先输出父类的内容,然后再输出了子类的内容。
如下:
所以,构造函数的调用顺序:先子后父;
构造函数输出内容的顺序 : 先父后子;
总结
C++继承常见问题:
1、如果父类不写构造函数,那么子类构造对象的时候不传参/传参调用的都是子类自己的无参/有参构造;
2、如果父类写了构造函数,不管父类写的是有参还是无参,子类创建对象的时候无论有参还是无参都会默认调用父类的无参构造函数;
3、父类如果写了有参构造而不写无参构造,那么子类创建对象的时候默认调用父类的无参构造就会失败;
4、子类构造函数可以指定调用父类的有参构造,但不是在子类的函数中写Person(a,b);
如果是这样调用父类有参构造的话,系统还是默认会调用一遍父类的无参构造
并且这样调用父类有参构造给父类参数父类的话,只是给子类Teacher中创造的临时变量赋值,一旦Teacher构造调用完毕,临时变量那块内存也回收了,所以相当于没有赋值
5、当子类创建对象时,父类与子类的构造顺序是先调用子类再调用父类;但是构造内容执行的顺序是先执行父类再执行子类;
结语
如果有错误的地方,望指出;如果有听不懂的地方,可以私信或者评论,稍后会做修改。