您的位置:首页 > 编程语言 > C语言/C++

Effective C++——》条款5:了解C++默默编写并调用哪些函数 .

2014-04-11 16:53 453 查看
这些函数包括:默认构造函数拷贝构造函数,赋值构造函数,以及析构函数。这些函数都属于public部分

一个常见的面试题就是问“一个空类占几个字节”,想当然的是0个字节,但事实上类要区分不同的对象,比如:

1 EmptyClass obj1;
2 EmptyClass obj2;


即便是空类,也要能识别obj1和obj2的不同,声明了就要要分配地址,所以空类仍然要占字节数,一般占一个字节

还有一个针对空类的问题是“一个空类里面有什么”,就是想问编译器为这个空类自动生成了哪些成员函数。

很容易想到的是生成了默认的构造函数和析构函数,事实上还有拷贝构造函数和赋值运算符,所以,总共生成了四个成员函数。具体地说,就是你表面上写了

1 Class EmptyClass
2 {
3 };


但实际编译器为你加了四个成员函数,所以看起来像这样:

1 Class EmtpyClass
2 {
3 public:
4
5 // 构造函数
6 EmtpyClass(){}
7
8
9
10 // 析构函数
11 ~EmptyClass(){}
12
13
14
15 // 拷贝构造函数
16 EmptyClass(const EmptyClass& obj)
17 {
18          …
19 }
20
21
22
23 // 赋值运算符重载
24 EmptyClass& operator= (const EmptyClass& obj)
25 {
26          …
27 }
28
29 };
30
31


拷贝构造函数和赋值运算符的函数体内容由成员变量决定,假设有成员变量var1和var2,那么拷贝构造函数和赋值运算法的函数体像这样:

1 EmptyClass(const EmptyClass& obj):var1(obj.var1), var2(obj.var2){}
2
3
4
5 EmptyClass& operator= (const EmptyClass& obj)
6 {
7          var1 = obj.var1;
8          var2 = obj.var2;
9          return *this;
10 }


赋值运算符要返回自身*this,是因为考虑到可以出现连等的情况,比如obj1 = obj2 = obj3,另外,这里都使用了自身类的引用,即EmptyClass&,这里的引用是必须要加的,这是因为:

(1) 引用修饰形参时,可以避免实参对形参的拷贝,一方面可以节省空间和时间资源,更为重要的是若实参对形参拷贝了,又会调用一次拷贝构造函数,这样拷贝构造函数就会一遍又一遍的被调用,造成无穷递归。

(2) 引用修饰返回值时,可以使返回的对象原地修改。比如(a=b) ++,这样返回的a对象还可以进行自增操作,如果不加引用,则因为生成的是原对象的拷贝,所以这样的自增操作并不使a本体自增。

对初学者而言,还要注意区分什么时候调用的是赋值运算符,什么时候调用的是拷贝构造函数。比如:

EmptyClass a(b); // 调用的是拷贝构造函数


EmptyClass a = b; // 调用的是拷贝构造函数


1 EmptyClass a;
2 a = b; // 调用的是赋值运算符


这里注意一下第二个和第三个例子,同样是等号,但却调用了不同的成员函数,重要的区别就要看是不是在这句话中新产生一个对象,第二个例子新产生一个对象,所以调用的是拷贝构造,第三个例子a在“=”前已经诞生了,所以调用的是赋值运算符。

本书中还讲到了一个特殊的情况,就是成员变量是const的,或者是引用,比如:

1 class SampleClass
2 {
3 private:
4          const int var1;  X
5          double& var2;    X
6 };


这时候编译器会报错,告诉你无法提供合适的构造函数,因为对于const变量以及reference,需要在声明的时候初始化,而编译器提供的默认构造函数显然无法做到这点。可以改成下面这样:

1 class SampleClass
2 {
3 private:
4          const int var1;
5          double& var2;
6
7 public:
8          SampleClass(const int a = 0, double b = 0):var1(a), var2(b){}
9
10 };


编译器不会报错了,但是如果像这样:

1 SampleClass obj1;
2 SampleClass obj2;
3 obj2 = obj1;


编译器会提示“operator =”函数在“SampleClass”中不可用,这说明编译器同样没有为SampleClass生成赋值运算符,因为var1和var2在初始化后,值就不能再改变了。但:

1 SampleClass obj1;
2 SampleClass obj2(obj1);


却是可以编译通过的,这是因为编译器可以生成默认的拷贝构造函数:

SampleClass(const SampleClass& s): var1(s.var1), var2(s.var2){}


这种生成方式并不会破坏const和reference的特性。

综上,编译器总是尽量地去生成这四个成员函数,但如果成员变量出现了const和reference,则编译器会拒绝生成默认的构造函数和赋值运算符重载函数。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: