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

条款3:尽可能使用const

2012-09-05 11:14 429 查看
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版本,来减少代码的工作量。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息