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 ...
随机推荐
- JAVA结合WebSocket实现简单客服聊天功能
说明:该示例只简单的实现了客服聊天功能. 1.聊天记录没有保存到数据库中,一旦服务重启,消息记录将会没有,如果需要保存到数据库中,可以扩展 2.页面样式用的网上模板,样式可以自己进行修改 3.只能由用 ...
- 【Tools】VS搭建Qt开发环境
00. 目录 @ 目录 00. 目录 01. 概述 02. Visual Studio 2019安装 03. Qt6安装 04. qt-vsaddin插件下载 05. qt-vsaddin插件安装 0 ...
- c++之升序和降序排序
1.头文件 #include <functional> 2. 降序 // 期末成绩 int score[] = {99, 77, 30, 80}; // 1. 降序排列 std::sort ...
- 【LeetCode】124. Binary Tree Maximum Path Sum 解题报告 (C++)
作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 递归 日期 题目地址:https://leetcode ...
- 【LeetCode】480. 滑动窗口中位数 Sliding Window Median(C++)
作者: 负雪明烛 id: fuxuemingzhu 公众号: 每日算法题 本文关键词:LeetCode,力扣,算法,算法题,滑动窗口,中位数,multiset,刷题群 目录 题目描述 题目大意 解题方 ...
- [LeetCode]621. Task Scheduler 任务安排 题解
题目描述 给定一个char数组,代表CPU需要做的任务,包含A-Z,不用考虑顺序,每个任务能在1个单位完成.但是有规定一个非负整数n代表两个相同任务之间需要至少n个时间单位.球最少数量的时间单位完成所 ...
- 【LeetCode】844. Backspace String Compare 解题报告(Python)
作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 字符串切片 栈 日期 题目地址:https://le ...
- 【操作系统】 DOS命令windows批处理batch编程——第一章
参考网址: http://docs.30c.org/dosbat/index.html 很多情况下,我们只需要记住一条命令 help ,就能掌握整个DOS命令.比如直接输入 help 可以得到命令的帮 ...
- CS5266代替AG9311|Type C转HDMI带PD3.0转换芯片|AG9311替代方案
ALGOLTEK AG9311是一款带PD3.0 Type C转HDMI的转换芯片,它主要用于usb Type-c拓展坞以及多功能usb Type-c转换器等产品设计当中,台湾瑞奇达新推出的CS526 ...
- 使用 JavaScript 用循环嵌套输出乘法表。外循环控制行数,内循环控制当前行要输出的乘法表达式,在页面上输出九九乘法表
查看本章节 查看作业目录 需求说明: 在页面上输出九九乘法表,实现效果如图所示 实现思路: 创建HTML页面 在页面中嵌入 <script type="text/javascript& ...