条款三:
尽可能使用const
为什么我们推荐尽可能使用const呢?因为在预编译文件中定义宏和const效果相似,但是宏定义很麻烦而且超容易出错,更重要的是预编译器可能会很早拿走里面的东西,导致在编译器执行的时候找不到宏定义的内容,调试错误需要好久,为了避免这种麻烦,我们推荐尽可能的使用const!const作为C语言和C++语言中的一个神技,或多或少都有其独特的脾气,今天让我们探索一下const关键字吧!
条例一:const如果出现在指针左边,指针指向的对象是const对象,如const int* a=&b,也可以写成int const*a=&b,表明b是常量,其实b不一定是常量如果出现在指针号右边,则表示该指针是const,不能指向其他对象,如int const a=&b,注意这里有一个显著问题,就是
int b=10;
const int*a=&b;
b=100;
cout<<*a<<endl;
想必大家都知道答案了吧,就是100,为什么呢?说好的a指针指向的对象是常量呢?各位稍安勿躁,条例一指的是在当作为函数参数时候的特性,当不作为函数参数时候,由于b本来就是变量,随时都可以变,而a只是指向b的一个普通指针而已,相当于忽略掉const属性,所以当然可以改变呀!
条例二:const成员函数
由于const那么好用,下面我们讲一下const在类中的运用
class TextBlock{
public:
const char& operator[](std::size_t position) const{
return text[position];
}
//第一个const表明operator指向的是const对象,第二个const表明该重载函数不能对类的成员变量进行修改;
char& operator[](std::size_t position){
return text[position];
}
//这个重载函数只是简单的表示重载运算符,适用于非const对象;
private:
std::string text;
};
从以下代码的运行中,可以观察到,可以观察到第一个operator[]代表const对象,同时成员变量无法被修改,第二个operator[]代表非const变量,同时成员变量可以被修改。、
#include <iostream>
#include <cstring>
using namespace std;
class TextBlock{
public:
TextBlock(string s){
this->text = s;
}
const char& operator[](std::size_t position)const{
cout << "我为const代言" << endl;
return text[position];
}
char& operator[](std::size_t position){
cout << "我为non-const代言" << endl;
return text[position];
}
private:
std::string text;
};
int main(){
const TextBlock tb("hello");
cout << tb[0] << endl;
TextBlock tb1("hello");
cout << tb1[0] << endl;
tb1[0] = 'c';
cout << tb1[0] << endl;
return 0;
}
同样的,我们可以将string替换为char*表示,代码如下:
class TextBlock{
public:
...
cosnt& operator[](std::size_t position) const{
return pText[position];
}
private:
char* pText;
};
const TextBlock cctb("hello");
char* pc=&cctb[0];
*pc='J';//cctb现在有了"Jello"这样的内容
观察上面的代码,为什么此时cctb[0]的值就可以改了呢?注意operator[]的第二个const指的是不能修改成员变量的值,在这里成员变量的值是pText,pText有没有指向其他地址呀,没有吧,即成员变量的值没有被改变呀,因此肯定可以修改呀!
如果我们想要在不准备修改成员变量的成员函数中想要修改成员变量呢?有些拗口,请看如下代码:
class CTextBlock{
public:
...
std::size_t length() const;
private:
char* pText;
std::size_t textLength;
bool LengthIsValid;
}
std::size_t CTextBlock::length() const{
if(!lengthIsValid){
textLength=std::strlen(pText);
lengthIsValid=true;
}
return textLength;
}
上面代码有木有错呢,当然有错,在const成员函数中修改成员变量,如果我们想要其变为正确的怎么办呢?答案很简单,就是将想要更改的变量声明为mutable即可。
条例三:
在const和non-const成员函数中避免重复
怎么解决呢?C++给出的建议是在non-const成员函数中调用const成员函数,为什么不能烦着来呢?const版本中调用non-const版本,注意const成员函数承诺不改变其对象的逻辑状态,non-const成员函数并没有这样的承诺,如果const调用non-const函数就是冒了这样的奉献,因此我们不这样调用,具体怎样实现的呢?请看如下代码:
class TextBlock{
public:
...
const char& operator[](std::size_t position) const{
...
return text[position];
}
char& operator[](std::size_t position){
return const_cast<char&>(static_cast<const TextBlock&>(*this)[position]]);
...
}
条例4:顶层const和底层const作为重载函数须知
顶层const不影响传入函数的对象,一个拥有顶层const的形参无法和一个没有拥有顶层const的形参区分开来
Record lookup(Phone);
Record lookup(const Phone);
Record lookUp(Phone*);
Record lookUp(Phone* const);
等由于都是顶层const所以每一组的第一个和第二个都一样,无法区分;
底层const确实可以区分的,如果形参是某种类型的指针或者引用,则通过区分其指向的是常量对象还是非常量对象可以实现函数重载,此时const是底层的。
Record lookup(Account&);//作用与Account引用
Record lookup(const Account&);//作用于常量引用
Record lookup(Account*);//作用于指向Account的指针
Record lookup(const Account*);//作用于指向常量的指针
为了更清楚地理解,我编写了如下的函数:
void record(int &a){
cout << "hahaha:" << a << endl;
}
void record(const int&a){
cout << a << endl;
}
int a = 10;
const int b = 100;
int &a1 = a;
const int &b1 = b;
record(a1);
record(b1);
可以看看运行结果:
PS:
突然发现自己一直对顶层const和底层const的理解有误,紧急补充一下哈~
顶层const:指针本身是个常量,即const出现在指针右侧;
底层const:指针所指向的对象是个常量,即const出现在指针左侧;