默认构造函数(缺省构造函数)分析
即没有参数的构造函数
传统认识认为:如果我们自己没定义任何构造函数,那么编译器就会为我们隐式自动定义 一个默认的构造函数,
我们称这种构造函数为:“合成的默认构造函数”
结论:“合成的默认构造函数”,只有在 必要的时候,编译器才会为我们合成出来,而不是必然或者必须为我们合成出来。
必要的时候是什么时候?
示例代码
#include <iostream>
using namespace std;
class MATX
{
public:
MATX() //默认构造函数
{
cout << "goodHAHAHAHA" << endl;
}
};
class MBTX
{
public:
int m_i;
int m_j;
void funct()
{
cout << "IAmVeryGood" << endl;
}
};
int main()
{
MBTX myb;
return 1;
}
编译通过会生成.obj文件:
每个.cpp源文件会编译生成一个.obj(linux下 gcc -c生成.o文件),最终把很多的.obj(.o)文件链接到一起生成一个可执行。
Windows下查看.obj文件
(1)右键单击正在运行程序的文件,打开所在文件夹
(2)进入它的debug文件夹,就可以找到该文件刚才编译生成的.obj文件
(3)找到开始菜单中visual studio中的开发人员命令提示符并打开
(4)输入命令如下(我的test1文件在F:盘,所以首先进入F:盘),会在.obj的文件目录下生成test1.txt文件,存放的是.o文件的分析结果
(5)用visual studio2017查看test1.txt结果如图
我们使用快速查找验证编译器是否为MBTX生成了默认构造函数MBTX::MBTX()
编译器并没有合成,因为此时只有普通类型的变量,编译器并不会合成默认构造函数
编译器会在什么时候帮我们合成默认构造函数呢?
(1)该类MBTX没有任何构造函数,但包含一个类类型的成员ma,而该对象ma所属于的类MATX 有一个缺省的构造函数。
#include <iostream>
using namespace std;
class M0TX
{
public:
M0TX() //默认构造函数
{
cout << "合成了默认构造函数,调用了M0TX类" << endl;
}
};
class MATX
{
public:
MATX() //默认构造函数
{
cout << "合成了默认构造函数,调用了MATX类" << endl;
}
};
class MBTX
{
public:
int m_i;
int m_j;
M0TX m0; //类类型成员变量
MATX ma; //类类型成员变量
void funct()
{
cout << "IAmVeryGood" << endl;
}
};
int main()
{
MBTX myb;
return 1;
}
运行结果:
再使用dumpbin命令更新test1.txt,查找MBTX::MBTX()
此时编译器帮我们合成了默认构造函数,合成的目的是为了调用MATX里的默认构造函数。
换句话说:编译器合成了默认的MBTX构造函数,并且在其中 安插代码,调用M0TX,MATX中的默认构造函数;
并且注意:此时调用类类型M0TX,MATX的默认构造函数的顺序与在该类MBTX中声明的顺序相同
(2)父类带缺省构造函数,子类没有任何构造函数,那因为父类这个缺省的构造函数要被调用,所以编译器会为这个子类合成出一个默认构造函数。
合成的目的是为了调用这个父类的构造函数。换句话说,编译器合成了默认的构造函数,并在其中安插代码,调用其父类的缺省构造函数。
#include <iostream>
using namespace std;
class MBTXPARENT
{
public:
MBTXPARENT()
{
cout << "父类构造函数被调用了" << endl;
}
};
class MBTX :public MBTXPARENT
{
public:
int m_i;
int m_j;
};
int main()
{
MBTX myb;
return 1;
}
运行结果:父类函数被调用了
查看.obj文件:
(3)如果一个类含有虚函数,但没有任何构造函数时
a.编译器会给我们生成一个基于该类的虚函数表vftable
b.编译器给我们合成了一个构造函数,并且在其中安插代码: 把类的虚函数表地址赋给类对象的虚函数表指针 (赋值语句/代码);
#include <iostream>
using namespace std;
class MBTX
{
public:
int m_i;
int m_j;
virtual void MBTXfunc()
{
cout << "虚函数" << endl;
}
};
int main()
{
MBTX myb;
return 1;
}
查看.obj文件:
我们再来看一下我们有自己的构造函数的情况:
#include <iostream>
using namespace std;
class MBTXPARENT
{
public:
MBTXPARENT()
{
cout << "MBTXPARENT()" << endl;
}
};
class MBTX:public MBTXPARENT
{
public:
int m_i;
int m_j;
virtual void MBTXfunc()
{
cout << "虚函数" << endl;
}
MBTX()
{
m_i = 10;
}
};
int main()
{
MBTX myb;
return 1;
}
结论:当我们有自己的默认构造函数时,编译器会根据需要扩充我们自己写的构造函数代码,比如调用父类构造函数,给对象的虚函数表指针赋值。
(4)如果一个类带有虚基类,编译器也会为它合成一个默认构造函数
虚基类:通过两个直接基类继承同一个简介基类。所以一般是三层结构 ,有爷爷类Grand,有两个父类A,A2,有子类C,大致如下
示例代码:
class Grand //爷爷类
{
public:
};
//两个父类
class A : virtual public Grand
{
public:
};
class A2 : virtual public Grand
{
public:
};
class C :public A, public A2 //这里不需要virtual
{
public:
C()
{
int aa;
aa = 1;
}
};
int main()
{
C cc;
return 1;
}
我们再来看一下我们有自己的构造函数的情况:
编译器没有合成爷爷类Grand的构造函数
编译器合成了父类A和A2的构造函数
A
A2
编译器合成了子类的构造函数,并向其中添加了调用基类构造函数,虚基类表的代码