如果一个类没有自己的构造函数,编译器会在需要的时候为其合成一个出来,俗称:合成默认构造函数。但是请注意是在需要的时候,并不是所有情况。

请看下面代码:

 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++对象模型)的更多相关文章

  1. C++构造函数语义学(二)(基于C++对象模型)

    带有虚函数的情况. 下面情况编译器也会在需要的时候为其合成. 1.如果一个类自己声明为虚函数. 1 #include<iostream> 2 using namespace std; 3 ...

  2. C++构造函数语义学(三)(基于C++对象模型)

    带有虚基类的情况. 1 #include<iostream> 2 using namespace std; 3 class X 4 { 5 public: 6 int i; 7 }; 8 ...

  3. 构造函数语义学——Copy Constructor 篇

    构造函数语义学--Copy Constructor 篇 本文主要介绍<深度探索 C++对象模型>之<构造函数语义学>中的 Copy Constructor 构造函数的调用时机 ...

  4. 构造函数语义学之Copy Constructor构建操作(2)

    二.详述条件 3 和 4 那么好,我又要问大家了,条件1 和 2比较容易理解.因为member object或 base class 含有copy constructor.那么member objec ...

  5. 构造函数语义学——Default Constructor篇

    构造函数语义学--Default Constructor 篇 这一章原书主要分析了:编译器关于对象构造过程的干涉,即在对象构造这个过程中,编译器到底在背后做了什么 这一章的重点在于 default c ...

  6. 《深度探索c++对象模型》chapter2 构造函数语义学

    关于c++,最常听到的一个抱怨是,编译器背着程序员做了太多事情,conversion运算符是最常被引用的一个例子:jerry schwarz,iostream函数库的建筑师,就曾经说过一个故事,他说他 ...

  7. 【C++】深度探索C++对象模型读书笔记--构造函数语义学(The Semantics of constructors)(四)

    成员们的初始化队伍(member Initia 有四种情况必须使用member initialization list: 1. 当初始化一个reference member时: 2. 当初始化一个co ...

  8. 构造函数语义学之Copy Constructor构建操作(1)

    一.Copy Constructor的构建操作 就像 default constructor 一样,如果class没有申明一个 copy constructor,就会隐含的声明或隐含的定义一个.生成的 ...

  9. 构造函数语义学之Default Constructor构建操作

    一.Default Constructor的构建操作 首先大家要走出两个误区: 1).任何class如果没有定义default constructor,就会被合成一个来. 2).便以其合成出来的def ...

随机推荐

  1. SpringBoot中Post请求提交富文本数据量过大参数无法获取的问题

    yml增加配置 # 开发环境配置 server: tomcat: max-http-form-post-size: -1

  2. JAVA中json对象转JAVA对象,JSON数组(JSONArray)转集合(List)

    json格式 {userId:'1',message:'2',create_time:'2020-03-28 20:58:11',create_date:'2020-03-28'}JAVA对象 Cha ...

  3. lldb调试C++总结(3)

    note 本文将弥补之前的遗漏部分. continue 前面提到,当设置断点后,使用step和next和finish,程序会停下来,需要程序继续运行,键入continue, 程序可自动继续向下执行. ...

  4. 【LeetCode】513. Find Bottom Left Tree Value 解题报告(Python & C++ & Java)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 BFS DFS Date 题目地址:https:// ...

  5. 【LeetCode】112. 路径总和 Path Sum 解题报告(Java & Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 DFS 回溯 BFS 栈 日期 题目地址:https ...

  6. 【剑指Offer】二叉搜索树的后序遍历序列 解题报告(Python)

    [剑指Offer]二叉搜索树的后序遍历序列 解题报告(Python) 标签(空格分隔): 剑指Offer 题目地址:https://www.nowcoder.com/ta/coding-intervi ...

  7. 【LeetCode】90. Subsets II 解题报告(Python & C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 递归 回溯法 日期 题目地址:https://leet ...

  8. 【C\C++笔记】数组指针越界

    指针越界,t的数组指针越界,修改了c的内容. 使用指针时,必须规定指针移动的范围 #include <iostream> using namespace std; int main(){ ...

  9. Vue.js高效前端开发 • 【Vue组件】

    全部章节 >>>> 文章目录 一.Vue组件介绍 1.组件概述 2.组件使用步骤 3.实践练习 一.Vue组件使用 1.组件注册 2.组件注册语法糖 3.使用script或te ...

  10. jquery控制元素的隐藏和显示的几种方法

    使用jquery控制div的显示与隐藏,一句话就能搞定,例如: 方法一 显示: $("#id").show()表示为display:block, 隐藏: $("#id&q ...