条款3:尽可能使用const

const与指针结合

const与指针结合有两种情况:一个常指针:指向不能变;指向常对象的指针:可以指向其他的变量,但这些变量必须是const类型。怎么区别它们呢?const在*左边,则是一个指向常对象的指针,而const在*的右边,则是指针的指向不能变。举个例子:

	int a = 10;
	int b = 5;
	const int *pa = &a;
	//(*pa) = 10;	指针指向的对象是常量,不能通过指针修改对象
	int * const pb = &b;
	//pb = &a;		常指针,不能修改指向

const与迭代器结合
以此类推,由指针实现的迭代器也有两种类型:

	vector<int> ivec;
	for(int i = 0; i < 3;++i)
		ivec.push_back(i);
	vector<int>::const_iterator citer = ivec.begin();
	//*citer = 10;		不能通过const_iterator修改指向的对象
	++citer;				//可以修改指向
	const vector<int>::iterator iter = ivec.begin();
	*iter = 10;
	//++iter;			不能改变指向

const与函数的返回值,参数,函数自身结合与返回值结合:

为什么要让一个函数返回一个const值呢?举一个例子可以说明,假如我们定义了一个类,并重载了乘法操作符*,和赋值操作符=。那么对于这个类的3个对象a,b,c,下面的操作就变得合法了:(a*b) = c;但是如果乘法操作的返回值是const类型的,那么(a*b) = c就不合法了。
很多人都会对这个小的修改不屑一顾,因为正常人都不会这么写程序,但是很多时候,我们都会将if((a*b) == c)写成(a*b) = c!这时候,这个操作就派上了用场,编译器会直接检查出这个操作的错误。


const与类的成员函数结合,构成const成员函数。const成员函数不能改变对象的值。如果的确是这样,那么尽量将函数写为const函数,因为:

1.它使类的接口更加容易理解,让人们一看就知道哪个函数是改变对象内容的,哪个不是:

class Test
{
private:
	int val;
	
public:
	Test(int i = 10):val(i){}
	int getVal()const;
	void setVal(int);
};

值得注意的是,如果声明为const函数,那么在定义时,也要加上const:

#include "item2.h"

int Test::getVal()const
{
	return val;
}

void Test::setVal(int i)
{
	val = i;
}

2.它们是得“操作const对象成为可能”。为什么要操作const对象呢?说来话长。我们先看一个例子:

class Person
{
public:
	Person(string nm = "mao",string add = "china"):name(nm),address(add){}
private:
	string name;
	string address;
};

class Student:public Person
{
public:
	Student(int i = 0):schoolNumber(i){}
	bool is_same(Student);
private:
	int schoolNumber;

};

其中,is_same函数完成比较两个学生是否是同一个人的操作:

bool Student::is_same(Student s)
{
	return schoolNumber == s.schoolNumber;
}

但是,这个函数的效率很低,原因在于:这个函数是按值传递的。这意味着会有一个Student类型的实参复制给s。这就会调用s的构造函数,s的构造函数会调用Person的构造函数,而Person的构造函数会调用那两个string对象的构造函数。有没有办法提高他的效率呢?有的,就是pass by refrence to const:bool is_same(Student &)const;具体来说,因为原函数传递的是值,并不会修改对象的数据成员,所以可以把它替换为使用const函数;其次,传递引用时,并不会发生对象的复制,也就没有构造函数,析构函数的调用了。
扯远了一点,我们再回到问题的开始:因为pass by refrence to const是更有效的作法,所以我们会经常这样做,但是这样做的前提,就是需要把函数定义为const函数。

const函数还有一点需要注意的是,他可以与非const函数发生重载:

class TextBook
{
public:
	TextBook(string bookNumber = "newbook",string content = "this is a book"):isbn(bookNumber),test(content){}
	char &operator[](size_t pos){return test[pos];}
	const char &operator[](size_t pos)const{return test[pos];}
private:
	string isbn;
	string test;
};

那么

	TextBook book("effective c++","item3");
	cout<<book[1]<<endl;		//调用非const操作符
	const TextBook book_read_only;	
	cout<<book_read_only[1]<<endl;//调用const操作符[]
	//book_read_only[1] = "aaa";	//错误,无法修改

关于const函数,还有一个值得思辨的地方,就是假如这个传递给函数的参数是一个指针,那么const函数的确不会修改这个指针,但是我们可以轻易地通过指针来修改这个对象的其他内容。此时,最好不要把它声明为const函数。假如我们的类需要跟C API通信,那么就得使用char*来存储书籍内容了:

class TextBook
{
public:
	TextBook(char* content = "aaa")
	{
		int i = 0;
		while(content[i] != NULL)
		{
			
			++i;
		}
		ptest = (char*)malloc(sizeof(char) * i);
		int j = 0;
		for( j = 0 ; j < i;++j)
			ptest[j] = content[j];
		ptest[j] = NULL;
	}
	char& operator[](size_t pos)const{return ptest[pos];}
private:
	char* ptest;
};

此时虽然将下标操作符声明为const函数,但是编译器依然允许我们改动的内容,反正我们并没有改变指针的指向:

	TextBook book("item3");
	cout<<book[0]<<endl;
	book[0] = 'I';
	cout<<book[0]<<endl;

这样的const函数声明就会遭人误解,还是不要声明为好。
但有的时候,我们又希望某些成员变量在const函数中也能被改动,怎么办呢?就是使用mutable关键字来声明这个变量:

	int getRemainingNumber()const{return total--;}
private:
	char* ptest;
	mutable int total;

最后,我们还要说一个问题。就是如果一个函数的const版本和非const版本的功能类似,那么能不能通过一个调用另外一个呢?答案是可以的:我们可以在非const版本中调用const版本,而不要(而不是不能,无语ing)在const版本中调用非const版本。后者是显然的,const函数既然承诺不修改数据成员,那么调用可以修改数据成员的非const版本就显得名不副实。前者需要仔细的推敲:

	const char& operator[](size_t pos)const{return content[pos];}
	char& operator[](size_t pos)
	{
		return const_cast<char&>(static_cast<const TextBook&>(*this)[pos]);
		//首先,将*this由原来的TextBook&转化为const TextBook&
		//然后,调用const char& operator[](size_t pos)const,如果没有第一部,则会自己递归调用自己!
		//最后,使用const_cast<char&>去掉const类型
	}

这一小节的内容差不多就是这么多了,总的来说:
1.能用const的地方尽量用,这样编译器会帮你检查很多错误。
2.如果如果一个函数有const版本和非const版本,那么我们可以使用非const版本来调用const版本,来减少代码的工作量。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值