const与指针结合
const与迭代器结合
以此类推,由指针实现的迭代器也有两种类型:
const与函数的返回值,参数,函数自身结合与返回值结合:
const与类的成员函数结合,构成const成员函数。const成员函数不能改变对象的值。如果的确是这样,那么尽量将函数写为const函数,因为:
2.它们是得“操作const对象成为可能”。为什么要操作const对象呢?说来话长。我们先看一个例子:
其中,is_same函数完成比较两个学生是否是同一个人的操作:
但是,这个函数的效率很低,原因在于:这个函数是按值传递的。这意味着会有一个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函数的确不会修改这个指针,但是我们可以轻易地通过指针来修改这个对象的其他内容。此时,最好不要把它声明为const函数。假如我们的类需要跟C API通信,那么就得使用char*来存储书籍内容了:
此时虽然将下标操作符声明为const函数,但是编译器依然允许我们改动的内容,反正我们并没有改变指针的指向:
这样的const函数声明就会遭人误解,还是不要声明为好。
但有的时候,我们又希望某些成员变量在const函数中也能被改动,怎么办呢?就是使用mutable关键字来声明这个变量:
最后,我们还要说一个问题。就是如果一个函数的const版本和非const版本的功能类似,那么能不能通过一个调用另外一个呢?答案是可以的:我们可以在非const版本中调用const版本,而不要(而不是不能,无语ing)在const版本中调用非const版本。后者是显然的,const函数既然承诺不修改数据成员,那么调用可以修改数据成员的非const版本就显得名不副实。前者需要仔细的推敲:
这一小节的内容差不多就是这么多了,总的来说:
1.能用const的地方尽量用,这样编译器会帮你检查很多错误。
2.如果如果一个函数有const版本和非const版本,那么我们可以使用非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版本,来减少代码的工作量。