尽可能使用const
1. 含义
const关键字使得你可以指定一个语义约束(也就是指定一个不该被改动的对对象),const可以作用于任何对象,包括变量,函数,类等等。
2. 用法
结合*(指针)使用时, 如果关键字const出现在星号左边,表示被指物是常量。(这里的常量是指通过指针不可修改它的值,并不代表被指物本身就是常量) 如果关键字const出现在星号右边,表示指针是常量。 如果关键字const出现在星号左右两侧,表示指针和被指物都是常量。
如下是代码测试
int A=10;
int *PA=&A;
const int * PCA=&A;
int * const CPA=&A;
const int *const CPCA=&A;
FFunctionLibrary::Println(*PA);
*PA=100;
PA=&A;
FFunctionLibrary::Println(*PA);
FFunctionLibrary::Println(*PCA);
PCA=&A;
FFunctionLibrary::Println(*CPA);
*CPA=1;
FFunctionLibrary::Println(*CPA);
FFunctionLibrary::Println(*CPCA);
3. stl中的const
stl的迭代器基础是指针,所以迭代器的作用就像个T *指针。 声明迭代器为const就像T * const。表示指针不改变,可以改内容。 对应的,声明常量迭代器就像const T *,表示指针可变,不可修改内容。
如下是代码测试
std::vector<int>Vector;
for(int i=0;i<10;++i)
{
Vector.push_back(i);
}
const std::vector<int>::iterator Iterator=Vector.begin();
*Iterator=10;
std::vector<int>::const_iterator ConstIterator=Vector.begin();
ConstIterator++;
for (auto i=ConstIterator;i!=Vector.end();++i)
{
FFunctionLibrary::Println(*i);
}
4. 函数返回值中的const
在返回值中使用const可以避免一些错误。 我们先看一个例子
const? AConst& operator*(const AConst&Other)
{
this->IntNumber=this->IntNumber*Other.IntNumber;
return *this;
}
operator bool()
{
return true;
}
AConst A;
AConst B;
AConst C;
FFunctionLibrary::Println((A*B).IntNumber);
if (A*B=C)
{
FFunctionLibrary::Println("Ohhhhhhhhhhh");
}
我不知道为什么有人会对两个数值的乘积再做一次赋值,但我知道会有很多程序员会在无意识中那么做。(Moota:emmmm…)
假如你的类型支持隐式的bool类型转换,并且*运算符返回的是一般的引用,这样的代码编译是可以通过的,但逻辑上存在很大的问题。如果使用常引用返回值的函数可以避免这样的问题,对常引用赋值会直接报错。
5. const成员函数
确认该成员函数可以作用于const对象
5.1 为什么需要?
-
它使得class接口容易理解。这是因为得知哪个函数可以修改对象内容哪个不可以,是很重要的。 -
它们使得“操作const对象”成为可能,这对于编写高效的代码是个关键。 -
许多人漠视一件事实:两个成员函数如果只是是常量性不同,可以构成重载。 如下是测试代码 const int& operator[](int index)const
{
return IntNumber;
}
int& operator[](int index)
{
return IntNumber;
}
AConst Const;
Const[1]=99;
FFunctionLibrary::Println(Const.IntNumber);
const AConst ConstConst;
FFunctionLibrary::Println(ConstConst.IntNumber);
通过测试可以得知,常对象会调用带const修饰的函数,即常函数。
6. 额外的注意
对于非引用的类型,const意义不大,函数返回的将是一个副本,不会更改原来引用所指向的数据,想必这不是你所期望的行为。
7. Const的哲学
成员函数为const意味着什么? 这里有两个流派
- bitwise constness(又称physical constness)
- logical constness
有什么区别? bitwise const阵容的人相信,成员函数只有在不更改对象的任何成员变量时才可以说是const。这也容易实现,编译器只要检测成员变量的赋值操作即可。 然而这存在缺陷。 比如指针。我保证不修改指针指向谁,但我可以修改指针指向对象的值。 这种情况导出了logical constness。这一流派的人主张,一个const成员函数可以修改它所处理的对象的某些值,但只有在客户端侦测不到的情况下才得以如此。
比如在常函数里面除了对常变量的使用,还可能包含一些变量的使用。但是编译器不同意,这时候可以使用 mutable 关键字 ,修改变量的常量性。
const int ConstIntNumer=100;
mutable std::string UseConstIntNumberPeople="Moota";
void AConst::UseConstIntNumber(std::string Name) const
{
UseConstIntNumberPeople=Name;
}
8. 避免const和non-const实现的重复
对于大多数函数而言,可能就是返回值上具有常量和非常量之分,也就是说存在大量相同的代码,这是我们不想要看到的。 你可以选择把相同的代码封装成函数,但是你还是重复了一些代码,包括函数的调用,两次return语句。 一个比较理想的方法是使其中的一个调用另一个的实现。 而一般的,我们建议在non-const函数调用const函数的实现,为什么? 如果const函数调用non-const函数的实现,那是一件糟糕的事情,因为const函数承诺不修改其对象的逻辑状态,但如果去调用non-const函数,就可能冒着修改对象的风险,这是坏代码的前兆 。而且,如果这样的代码通过编译,意味着得先将this指针的const性质先去掉,这存在很大风险。
non-const转const测试代码
const int& operator[](int index)const
{
return IntNumber;
}
int& operator[](int index)
{
return const_cast<int&>(static_cast<const AConst&>(*this)[index]);
}
static_cast负责将*this 对象转换成常对象,这样才能调用常函数。 const_casr负责将常函数返回的常引用转换成普通引用。
总结
- 将某些东西声明为const可帮助编译器侦测出错误用法,const可被施加于任何作用域内的对象,函数参数,函数返回类型,成员函数本体等等。
- 编译器强制实施 bitwise constness,但是你编写程序时应该使用logic constness。
- 当const和non-const 成员函数有着实质等价的实现时,令non-const版本调用const版本可避免代码重复。
|