【C/C++】构造函数和析构函数
2015-08-29 19:38
274 查看
问题1:
MFC类库中,CObject类的重要性不言自明。在CObject的定义中,我们看到一个有趣的现象,即CObject的析构函数是虚拟的。为什么MFC的编写者认为虚拟的析构函数是必要的?
问题2:
析构函数可以为virtual型,构造函数则不能。那么为什么构造函数不能为虚呢?
问题3:
如果虚函数是非常有效的,我们是否可以把每个函数都声明为虚函数?
问题4:
析构函数可以是内联函数吗?
问题5:
请看下面一段程序:
问题:
(1)该程序输出的结果是什么?为什么会有这样的输出?
(2)B(int i):data(i),这种用法的专业术语叫什么?
(3)Play(5),形参类型是类,而5是常量,这样写合法吗?为什么?
=======================================================================================
问题1解析:
我们可以先构造一个类如下:
上面代码在运行时,由于在生成CChild对象c时,实际上在调用CChild类的构造函数之前必须首先调用其基类CBase的构造函数,所以当撤销c时,也会调用CChild类析构函数之后,调用CBase类的析构函数(析构函数调用顺序与构造函数相反)。也就是说,无论析构函数是不是虚函数,派生类对象被撤销时,肯定会依次上调基类的析构函数。
那么为什么CObject类要搞一个虚的析构函数呢?
因为多态的存在。
仍以上面的代码为例,如果main()中有如下代码:
答案:
在这个例子里,所有对象都存在于栈框中,当离开其所处的作用域时,该对象会被自动撤销,似乎看不出什么大问题。但是试想,如果CChild类的构造函数在堆中分配了内存,而其析构函数又不是virtual型的,那么撤销pBase时,将不会调用CChild::~CChild(),从而不会释放CChild::CChild()占据的内存,造成内存泄露。
将CObject的析构函数设为virtual型,则所有CObject类的派生类的析构函数都将自动变为virtual型,这保证了在任何情况下,不会出现由于析构函数未被调用而导致的内存泄露。这才是MFC将CObject::~CObject()设为virtual型的真正原因。
问题2答案:
虚函数采用一种虚调用的方法。虚调用是一种可以在只有部分信息的情况下工作的机制,特别允许我们调用一个只知道接口而不知道其准确对象类型的函数。但是如果要创建一个对象,你势必要知道对象的准确类型,因此构造函数不能为虚。
问题3答案:
不行,这是因为虚函数是有代价的:由于每个虚函数的对象都必须维护一个v表,因此在使用虚函数的时候都会产生一个系统开销。如果仅是一个很小的类,且不想派生其他类,那么根本没必要使用虚函数。
问题4解析:
我们可以先构造一个类,让它的析构函数是内联函数,如下所示:
该程序可以正确编译并得出结果。
答案:
析构函数可以是内联函数。
问题5答案:
(1)应该有两个“destructed”输出:
(2)带参数的构造函数,冒号后面是成员变量初始化列表(member initialization list)。
(3)合法。单个参数的构造函数如果不添加explicit关键字,会定义一个隐含的类型转换(从参数的类型转换到自己的类型);添加explicit关键字会消除这种隐含转换。
MFC类库中,CObject类的重要性不言自明。在CObject的定义中,我们看到一个有趣的现象,即CObject的析构函数是虚拟的。为什么MFC的编写者认为虚拟的析构函数是必要的?
问题2:
析构函数可以为virtual型,构造函数则不能。那么为什么构造函数不能为虚呢?
问题3:
如果虚函数是非常有效的,我们是否可以把每个函数都声明为虚函数?
问题4:
析构函数可以是内联函数吗?
问题5:
请看下面一段程序:
#include <iostream> #include <string> #include <vector> using namespace std; class B { private: int data; public: B() { cout << "default constructor" << endl; } ~B() { cout << "destructed" << endl; } B(int i) : data(i) { cout << "constructed by parameter " << data << endl; } }; B Play(B b) { return b; } int main(int argc, char *argv[]) { B temp = Play(5); return 0; }
问题:
(1)该程序输出的结果是什么?为什么会有这样的输出?
(2)B(int i):data(i),这种用法的专业术语叫什么?
(3)Play(5),形参类型是类,而5是常量,这样写合法吗?为什么?
=======================================================================================
问题1解析:
我们可以先构造一个类如下:
class Base { public: ~CBase() {} }; class CChild : public Base { public: ~CChild() {} }; int main() { Child c; return 0; }
上面代码在运行时,由于在生成CChild对象c时,实际上在调用CChild类的构造函数之前必须首先调用其基类CBase的构造函数,所以当撤销c时,也会调用CChild类析构函数之后,调用CBase类的析构函数(析构函数调用顺序与构造函数相反)。也就是说,无论析构函数是不是虚函数,派生类对象被撤销时,肯定会依次上调基类的析构函数。
那么为什么CObject类要搞一个虚的析构函数呢?
因为多态的存在。
仍以上面的代码为例,如果main()中有如下代码:
CBase * pBase; CChild c; pBase = &c;那么pBase指针被撤销时,调用的是CBase的析构函数还是CChild的呢?显然是CBase的(静态联编)析构函数。但如果把CBase类的析构函数改成virtual型,当pBase指针被撤销时,就会先调用CChild类的析构函数,再调用CBase类的析构函数。
答案:
在这个例子里,所有对象都存在于栈框中,当离开其所处的作用域时,该对象会被自动撤销,似乎看不出什么大问题。但是试想,如果CChild类的构造函数在堆中分配了内存,而其析构函数又不是virtual型的,那么撤销pBase时,将不会调用CChild::~CChild(),从而不会释放CChild::CChild()占据的内存,造成内存泄露。
将CObject的析构函数设为virtual型,则所有CObject类的派生类的析构函数都将自动变为virtual型,这保证了在任何情况下,不会出现由于析构函数未被调用而导致的内存泄露。这才是MFC将CObject::~CObject()设为virtual型的真正原因。
问题2答案:
虚函数采用一种虚调用的方法。虚调用是一种可以在只有部分信息的情况下工作的机制,特别允许我们调用一个只知道接口而不知道其准确对象类型的函数。但是如果要创建一个对象,你势必要知道对象的准确类型,因此构造函数不能为虚。
问题3答案:
不行,这是因为虚函数是有代价的:由于每个虚函数的对象都必须维护一个v表,因此在使用虚函数的时候都会产生一个系统开销。如果仅是一个很小的类,且不想派生其他类,那么根本没必要使用虚函数。
问题4解析:
我们可以先构造一个类,让它的析构函数是内联函数,如下所示:
#include <iostream> using namespace std; class A { public: void fun() { cout << "A" << endl; } ~A(); }; inline A::~A() { cout << "inline" << endl; } int main(int argc, char *argv[]) { A *niu = new A(); niu->fun(); delete niu; return 0; }
该程序可以正确编译并得出结果。
答案:
析构函数可以是内联函数。
问题5答案:
(1)应该有两个“destructed”输出:
constructed by parameter 5 // 在Play(5)处,5通过隐含的类型转换调用了B::B(int i) destructed // Play(5)返回时,参数的析构函数被调用 destructed // temp的析构函数调用;temp的构造函数调用的是编译器生成的拷贝构造函数
(2)带参数的构造函数,冒号后面是成员变量初始化列表(member initialization list)。
(3)合法。单个参数的构造函数如果不添加explicit关键字,会定义一个隐含的类型转换(从参数的类型转换到自己的类型);添加explicit关键字会消除这种隐含转换。
相关文章推荐
- 【C/C++】成员变量
- 【C/C++】类和结构
- C++中引用(&)的用法和应用实例===引用和多态的关系!!!!!!!!!!!!!!!!!
- 【C/C++】面向对象的基本概念
- 我的第一个C++程序
- 【C/C++】this指针
- DEV-C++
- 【C/C++】指针基本问题
- 【C/C++】运算符问题
- 作业《IOS_C语言》结构体、结构体数组
- LeetCode-Add Digits-解题报告
- C++名字空间详解
- C++名字空间详解
- 各种C++:Borland C++、Symantec C++、Microsoft VisualC++
- C语言中lseek()函数和fseek()函数的使用详解
- C语言中对文件最基本的读取和写入函数
- C语言中的输入输出函数详解与比较scanf,sscanf,printf,sprintf,fprintf
- 简要对比C语言中的dup()函数和dup2()函数
- CString简单介绍及使用
- 我理解的CPP