`
897371388
  • 浏览: 523313 次
文章分类
社区版块
存档分类
最新评论

《Effective C++》Item4:确定对象被使用前已先被初始化

 
阅读更多


Questions:

(1)为什么需要在对象使用前初始化?
(2)关于默认构造函数的几点认识?
(3)为什么要使用参数初始化表进行初始化?


----------------------------------------------------------------------------------------------------------------------------------------------->


Answers:

(1)为什么需要在对象使用前初始化?

读取未初始化的值会导致不明确的行为,最终导致不可测试的程序结果,比如下面这个例子:

#include "stdafx.h"
#include <iostream>

using namespace std;

class Point{
private:
	int x, y;
public:
	Point(){}
	Point(int a, int b){
		x = a;
		y = b;
	}
	void print(){
		cout<<"x = " << x << " y = " << y<<endl;
	}
	int getX(){
		return x;
	}
	int getY(){
		return y;
	}
};

int _tmain(int argc, _TCHAR* argv[])
{
	Point p;
	p.print();
	return 0;
}

输出结果为:


因此,要确保每一个构造函数都将对象的每一个成员初始化。

以上程序修改后为:

#include "stdafx.h"
#include <iostream>

using namespace std;

class Point{
private:
	int x, y;
public:
	Point(){
		x = 0;
		y = 0;
	}
	Point(int a, int b){
		x = a;
		y = b;
	}
	void print(){
		cout<<"x = " << x << " y = " << y<<endl;
	}
	int getX(){
		return x;
	}
	int getY(){
		return y;
	}
};

int _tmain(int argc, _TCHAR* argv[])
{
	Point p;
	p.print();
	return 0;
}

(2)关于默认构造函数的几点认识?


a.个类必须有一个构造函数,否则没法创建对象;
b.若程序没有提供任何构造函数,则C++提供一个默认的构造函数,该默认构造函数是无参构造函数,函数体为空,它仅负责创建对象,不做任何初始化的工作;
c.只要程序定义了一个构造函数(不管是无参还是有参构造),C++就不再提供默认的默认构造函数。即如果为类定义了一个带参的构造函数,还想要无参构造函数,就必须自己定义;
d.与变量定义类似,在用默认构造函数创建对象时,如果创建的是全局对象或静态对象,则对象的位模式全为0,否则,对象值是随机的。

(3)为什么要使用参数初始化表进行初始化?

C++引入构造函数的目的在于对变量的初始化做出强制,因为人们发现,程序的错误,很大程度上来说,都是由变量没有初始化造成的。
因此,一种很好的编程习惯就是,在定义变量的时候直接初始化。而在C++中,在变量定义时,系统会自动调用变量的构造函数对变量进行初始化。只要将相应的初始化代码放在构造函数中,就能够保证该类在实例化对象的时候能够给对象赋以理想的初值。
而事实远远没有这么简单。

C++规定:对象的成员变量的初始化动作发生在进入构造函数本体之前。初始化的发生时间是这些成员的default构造函数被自动调用之时。

实际上,程序在进入构造函数的大括号体时,就已经完成了变量的定义(也就是分配内存空间)。这就难免会造成这样的一种情况:在对象被构造时,程序进入构造函数大括号体之前,首先调用对象的default构造函数(进行初始化,即分配内存空间并赋初始值),然后进入构造函数体内,重新对变量进行赋值。这无疑降低了程序的效率。

看如下使用构造函数初始化的例子:

// InitializeTest.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <iostream>
#include <list>

using namespace std;

class PhoneNumber{
private:
	string number;
public:
	PhoneNumber(string number){
		this->number = number;
	}
};

class ABEntry
{
public:
	ABEntry(){
		theName = "abc";
		theAddress = "abc";
		numTimesConsulted = -1;
	}

	ABEntry(const std::string& name, const std::string& address, const std::list<PhoneNumber>& phones){  
		theName = name;  
		theAddress = address;  
		thePhones = phones;  
		numTimesConsulted = 0;  
	}  

private:
	string theName;
	string theAddress;
	list<PhoneNumber> thePhones;
	int numTimesConsulted;
};

int main(int argc, _TCHAR* argv[])
{
	list<PhoneNumber> phones;
	PhoneNumber number("1234567");
	phones.push_back(number);

	ABEntry abeEntry;
	return 0;
}

我们在程序的第48行加上断点,然后进行单步调试,先调用无参构造函数ABEntry(),进入之后并不是先对大括号内的3个成员变量直接赋值,而是先调用default构造函数分别初始化4个成员变量(分配存储空间),初始化顺序与变量的声明顺序一致,即按照theName、theAddress、thePhone、numTimesConsulted的顺序分别调用string()、string()、list()进行初始化,因为numTimesConsulted是内置内型,所以不会进行初始化,执行这3个构造函数之后theName和theAddress的值都为“”,thePhone为null,这个过程结束之后才进入花括号内部,将实参的值分别赋给3个成员变量。所以整个过程包括初始化和复制两个过程,大大降低了效率。

因此,C++的设计者引入了构造函数初始化列表的概念。也就是说,在构造函数的初始化列表之中进行的是对变量的定义与初始化(分配内存空间),而不是简单的赋值。这样就能够解决上述的效率以及对构造函数的选择的问题。


// InitializeTest.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <iostream>
#include <list>

using namespace std;

class PhoneNumber{
private:
	string number;
public:
	PhoneNumber(string number){
		this->number = number;
	}
};

class ABEntry
{
public:
	ABEntry():theName("abc"), theAddress("abc"), numTimesConsulted(0){}
	ABEntry(const string& name, const string& address, list<PhoneNumber>& phones)
		:theName(name), theAddress(address), thePhones(phones), numTimesConsulted(0){}
private:
	string theName;
	string theAddress;
	list<PhoneNumber> thePhones;
	int numTimesConsulted;
};

int main(int argc, _TCHAR* argv[])
{
	list<PhoneNumber> phones;
	PhoneNumber number("1234567");
	phones.push_back(number);

	ABEntry abeEntry("wanjun", "beijing", phones);
	return 0;
}


该版本和上一个赋值的版本效果相同,但该版本的效率高。原因如下:
a.基于赋值的版本首先调用default构造函数为成员变量设初值,然后立刻在对他们赋予新值。
b.default构造函数的一切作为因此浪费了。
c.成员初始化列表避免了这一问题。

若想要default构造一个成员变量,也可以使用成员初始值列表,只要制定nothing做为初始化实参即可。如:
ABEntry::ABEntry()
:theName(), //call default constructor of theName
theAddress(), //do the same thing for theAddress
thePhones(), //do the same thing for thePhones
numTimesConsulted(0) explicit initialize the build-in type member 0
{
}

因为在初始化列表中发生的是成员变量的定义,也就是分配空间,因此初始化列表中构造的顺序与实际编译器定义变量的顺序可能是不一致的,也就是说,编译器不会按照设计者在初始化列表中初始化变量的顺序来为变量分配内存空间。初始化列表中变量的初始化顺序时其在类体内定义的顺序。
同样的,因为const型成员变量只能在被定义的同时被初始化而不能够再次被赋值,它也只能被放在构造函数的初始化列表中。
因此,C++的设计者引入了构造函数初始化列表的概念。也就是说,在构造函数的初始化列表之中进行的是对变量的定义与初始化(分配内存空间),而不是简单的赋值。这样就能够解决上述的效率以及对构造函数的选择的问题。


分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics