C++构造函数语义学(一)(基于C++对象模型)
如果一个类没有自己的构造函数,编译器会在需要的时候为其合成一个出来,俗称:合成默认构造函数。但是请注意是在需要的时候,并不是所有情况。
请看下面代码:
1 #include<iostream>
2 using namespace std;
3 class Foo {
4 public:
5 int val;
6 Foo *pnext;
7 };
8
9 void foo_bar()
10 {
11 Foo bar;
12 if (bar.val || bar.pnext)
13 {
14 cout << "数据被编译器初始化了" << endl;
15 }
16 }
17 int main()
18 {
19 foo_bar();
20 while (1);
21 return 0;
22 }
输出会报错:
分析:对于上述情况,编译器并不会认为是需要的,因为初始化数据成员应该是程序员的职责,而并非是编译器的职责。所以必须自己写一个构造函数出来为其数据成员进行初始化。
假设编译器认为需要的时候到了,这时候为其合成了一个默认构造函数出来,也不会对该类的数据成员进行初始化,因为这并不是编译器的职责。比如下面代码:
1 #include<iostream>
2 using namespace std;
3 class Foo {
4 public:
5 Foo(){
6 cout << "默认构造函数被调用" << endl;
7 };
8 };
9
10 class Bar {
11 public:
12 Foo foo;
13 char *str;
14 };
15 void foo_bar()
16 {
17 Bar bar;
18 if (bar.str)
19 {
20 cout << "类Bar的数据成员被编译器初始化了" << endl;
21 }
22 }
23 int main()
24 {
25 foo_bar();
26 while (1);
27 return 0;
28 }
输出:

和第一种情况比,这时候的编译器没有报错,但是编译器还是没有为数据成员进行初始化。
上面的代码引出第一种编译器会自动合成默认构造函数的情况:如果一个类中包含另一个类,对于后者,有自己定义的默认构造函数。而前者没有,那么此时编译器会为其合成一个出来,但是合成的目的仅仅只是为了调用后者的默认构造函数,对于自己类的数据成员,必须由程序员自己初始化。
所以合理的应该是这样的:
1 #include<iostream>
2 using namespace std;
3 class Foo {
4 public:
5 Foo() {
6 cout << "默认构造函数被调用" << endl;
7 };
8
9 };
10
11 class Bar {
12 public:
13 Foo foo;
14 char *str;
15 Bar()
16 {
17 //编译器自动安插代码段foo.Foo::Foo();
18 str = new char;//程序员为自己的数据成员赋值。
19 }
20
21 };
22 void foo_bar()
23 {
24 Bar bar;
25 if (bar.str)
26 {
27 cout << "类Bar的数据成员被编译器初始化了" << endl;
28 }
29 }
30 int main()
31 {
32 foo_bar();
33 while (1);
34 return 0;
35 }
输出:

分析:
对于类Bar,现在有了自己的构造函数,虽然没有显示的定义类Foo的,编译器还是为其合成了一个出来,因为编译器认为这是需要的时候,这种需要仅仅是需要调用类Foo的默认构造函数,但是对于自己的数据成员,它依然置之不顾。
如果一个类包含有多个类成员,这些类成员都有自己的构造函数(假设默认的不带参数和带参数的构造函数都存在),但是包含了多个类成员的类并没有定义自己的构造函数,编译器此时就会为其合成一个出来,为的就是调用其类成员,且调用顺序按照类成员定义的顺序来依次调用,比如以下情况:
1 #include<iostream>
2 using namespace std;
3 class Dopey {
4 public:
5 Dopey() {
6 cout << "默认构造函数Dopey()被调用" << endl;
7 }
8 };
9 class Sneezy {
10 public:
11 Sneezy(int) {
12 cout << "构造函数Sneezy(int)被调用" << endl;
13 }
14 Sneezy(){
15 cout << "默认构造函数Sneezy()被调用" << endl;
16 }
17 };
18 class Bashful {
19 public:
20 Bashful() {
21 cout << "默认构造函数Bashful()被调用" << endl;
22 }
23 };
24 class Snow_White
25 {
26 public:
27 Dopey dopey;
28 Sneezy sneezy;
29 Bashful bashful;
30 private:
31 int mumble;
32 };
33 int main()
34 {
35
36 Snow_White s;
37 while (1);
38 return 0;
39 }
输出:

如果该类为自己的数据成员定义了自己的构造函数来初始化它们,如果不明确调用父类的构造函数,编译器依旧会在该构造函数中安插代码,为了调用父类的默认构造函数(编译器调用的都是默认的版本,也就是父类中定义的都是不带参的构造函数或者带了默认参数的构造函数,其他情况则需要程序员自己显示调用),如下情况:
1 #include<iostream>
2 using namespace std;
3 class Dopey {
4 public:
5 Dopey() {
6 cout << "默认构造函数Dopey()被调用" << endl;
7 }
8 };
9 class Sneezy {
10 public:
11 Sneezy(int) {
12 cout << "构造函数Sneezy(int)被调用" << endl;
13 }
14 Sneezy(){
15 cout << "默认构造函数Sneezy()被调用" << endl;
16 }
17 };
18 class Bashful {
19 public:
20 Bashful() {
21 cout << "默认构造函数Bashful()被调用" << endl;
22 }
23 };
24 class Snow_White
25 {
26 public:
27 Dopey dopey;
28 Sneezy sneezy;
29 Bashful bashful;
30 Snow_White() :sneezy(1024)
31 {
32 /*
33 编译器安插:
34 dopey.Dopey::Dopey();
35 sneey.Sneey::Sneey(1024);
36 bashful.Bashful::bashful();
37 */
38 mumble = 2048;
39 }
40 private:
41 int mumble;
42 };
43 int main()
44 {
45 Snow_White s;
46 while (1);
47 return 0;
48 }
输出:

上述是类中含有类成员的情况,继承的情况和它类似,如果一个类继承了另外一个类,但是前者没有自己的构造函数,而后者有自己的默认构造函数,那么对于前者,编译器会在需要的时候合成一个出来,目的仅仅是为了调用后者的默认构造函数,前者的数据成员仍然需要程序源自己初始化,对于多继承同样如此,下面举出多继承的例子:
1 #include<iostream>
2 using namespace std;
3 class Dopey {
4 public:
5 Dopey() {
6 cout << "默认构造函数Dopey()被调用" << endl;
7 }
8 };
9 class Sneezy {
10 public:
11 Sneezy(int) {
12 cout << "构造函数Sneezy(int)被调用" << endl;
13 }
14 Sneezy(){
15 cout << "默认构造函数Sneezy()被调用" << endl;
16 }
17 };
18 class Bashful {
19 public:
20 Bashful(int x = 2) {
21 cout << "默认构造函数Bashful()被调用" << endl;
22 }
23 };
24 class Snow_White:public Dopey,public Sneezy,public Bashful
25 {
26 public:
27 Snow_White() :Sneezy(1024)
28 {
29 /*
30 编译器安插:
31 Dopey dopey;
32 dopey.Dopey::Dopey();
33 Sneezy sneezy;
34 sneezy.Sneey::Sneey(1024);
35 Bashful bashful;
36 bashful.Bashful::bashful();
37 */
38 mumble = 2048;
39 }
40 private:
41 int mumble;
42 };
43 int main()
44 {
45 Snow_White s;
46 while (1);
47 return 0;
48 }
输出:

C++构造函数语义学(一)(基于C++对象模型)的更多相关文章
- C++构造函数语义学(二)(基于C++对象模型)
带有虚函数的情况. 下面情况编译器也会在需要的时候为其合成. 1.如果一个类自己声明为虚函数. 1 #include<iostream> 2 using namespace std; 3 ...
- C++构造函数语义学(三)(基于C++对象模型)
带有虚基类的情况. 1 #include<iostream> 2 using namespace std; 3 class X 4 { 5 public: 6 int i; 7 }; 8 ...
- 构造函数语义学——Copy Constructor 篇
构造函数语义学--Copy Constructor 篇 本文主要介绍<深度探索 C++对象模型>之<构造函数语义学>中的 Copy Constructor 构造函数的调用时机 ...
- 构造函数语义学之Copy Constructor构建操作(2)
二.详述条件 3 和 4 那么好,我又要问大家了,条件1 和 2比较容易理解.因为member object或 base class 含有copy constructor.那么member objec ...
- 构造函数语义学——Default Constructor篇
构造函数语义学--Default Constructor 篇 这一章原书主要分析了:编译器关于对象构造过程的干涉,即在对象构造这个过程中,编译器到底在背后做了什么 这一章的重点在于 default c ...
- 《深度探索c++对象模型》chapter2 构造函数语义学
关于c++,最常听到的一个抱怨是,编译器背着程序员做了太多事情,conversion运算符是最常被引用的一个例子:jerry schwarz,iostream函数库的建筑师,就曾经说过一个故事,他说他 ...
- 【C++】深度探索C++对象模型读书笔记--构造函数语义学(The Semantics of constructors)(四)
成员们的初始化队伍(member Initia 有四种情况必须使用member initialization list: 1. 当初始化一个reference member时: 2. 当初始化一个co ...
- 构造函数语义学之Copy Constructor构建操作(1)
一.Copy Constructor的构建操作 就像 default constructor 一样,如果class没有申明一个 copy constructor,就会隐含的声明或隐含的定义一个.生成的 ...
- 构造函数语义学之Default Constructor构建操作
一.Default Constructor的构建操作 首先大家要走出两个误区: 1).任何class如果没有定义default constructor,就会被合成一个来. 2).便以其合成出来的def ...
随机推荐
- Linux 主从数据库
主从数据库 主数据库的内容同步传输到附属数据库 客户访问附属数据库 这样做保证了数据库的稳定性 需要两台虚拟机 两边个虚拟机都要操作 配置hosts文件 进入/etc/hosts追加输入 192.16 ...
- Flink使用IDEA进行jar打包
pom文件增加 <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>mav ...
- 钉钉提供的内网穿透之HTTP穿透
此方法无需自行提供服务器和域名 官方地址:https://developers.dingtalk.com/document/resourcedownload/http-intranet-penetra ...
- 【LeetCode】Longest Word in Dictionary through Deleting 解题报告
[LeetCode]Longest Word in Dictionary through Deleting 解题报告 标签(空格分隔): LeetCode 题目地址:https://leetcode. ...
- 【LeetCode】883. Projection Area of 3D Shapes 解题报告(Python)
作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 数学计算 日期 题目地址:https://leetc ...
- 1479 小Y的数论题
小Y喜欢研究数论,并且喜欢提一些奇怪的问题.这天他找了三个两两互质的数a, b, c,以及另一个数m, 现在他希望找到三个(0, m)范围内的整数x, y, z,使得 (xa+yb) Mod m=(z ...
- 线程 IO流 网络编程 基础总结
线程 进程---->进行中的程序 线程---->由进程创建 一个进程可以创建多个线程 并发:同一个时刻 多个任务交替执行 造成一种貌似同时进行的错觉 简单来说 单个cpu的多任务就是并发 ...
- 编写Java程序,使用抽象类和抽象方法构建不同动物的扑食行为,抽象类设计
返回本章节 返回作业目录 需求说明: 不同的动物都有进食的行为,但是在进食前需要捕获到食物后才可以进食. 要求定义一个抽象的动物类,该类中有一个抽象的捕食方法,和一个非抽象的进食方法.在进食方法中调用 ...
- .net core的配置介绍(一):IConfiguration
说到配置,绝大部分系统都会有配置,不需要配置的系统是非常少的,想想以前做.net 开发时,我们常常将配置放到web.config中,然后使用ConfigurationManager去读取. 初次接触到 ...
- nodejs创建一个简单的web服务
这是一个突如其来的想法,毕竟做web服务的框架那么多,为什么要选择nodejs,因为玩前端时,偶尔想调用接口获取数据,而不想关注业务逻辑,只是想获取数据,使用java或者.net每次修改更新后还要打包 ...