条款03:尽可能使用const
Use const whenever possible
因为该条款的内容较多,因此分成三章来进行学习记录。
const的基本用法
首先,对于const,它允许你指定一个语义约束,也就是说指定一个不该被改动的对象,而编译器会强制实施这样的约束。
const允许你告诉编译器和其他的程序员:哪些值应该保持不变!。
如果一个值确实不应该被改变,就应该利用const的特性来表达出来。
对于const可以修饰的对象:
- const可以在classes外部修饰global或者namespace作用域中的常量;
- const可以修饰文件、函数、或区域作用域(block scope)中被声明为static的对象;
- const可以修饰classes内部的static和non-static成员变量;
- const可以修饰指针自身、指针所指物,或两者都是/都不是。
当const修饰指针相关的对象时:
char greeting[] = "Hello";
char* p = greeting; //non-const指针,non-const数据
const char* p = greeting; //non-const指针,const数据
char* const p = greeting; //const指针,non-const数据
const char* const p = greeting; //const指针,const数据
在上面的例子中,可以看出const修饰指针和数据的规则:
- 如果const出现在星号左边,表示被指物是常量;
- 如果const出现在星号右边,表示指针自身是常量;
- 如果const出现在星号的两边,表示被指物和指针两者都是常量。
如果被指物是常量,const既可以写在类型之前,也可以写在类型之后、星号之前,这两种写法的意义是相同的:
void f1(cosnt Widget* pw);//f1获得一个指针,指向一个常量的(不变的)Widget对象
void f2(Widget const * pw);//f2也是一样的
这里总结来说,const所修饰的,始终是紧挨着它的左边的东西。当const出现在最左端时,这是它所修饰的是就是紧挨着它的右边的东西。
STL迭代器的const用法
在STL的迭代器里,都是以指针为基础而塑造出来的,因此,迭代器的作用就像一个T*指针。
声明迭代器为const, 等价于声明指针为const(即声明一个T* const指针),表示这个迭代器不可以指向其他不同的东西(指针自身是const),但是它所指的东西的值是可以改动的(数据不是const)。
如果你希望迭代器所指的东西不可以被改动(即希望STL模拟一个const T* 指针),则需要的是const_iterator:
//以vector容器为例
std::vector<int> vec;
...
const std::vector<int>::iterator iter = vec.begin();//iter的作用像一个T* const
*iter = 10; //正确的语法,改变iter所指物
//++iter; //错误的写法,因为在上面的定义中,指针iter是const类型的,不可以被改变
std::vector<int>::const_iterator cIter = vec.begin();//cIter的作用像一个const T*
//*cIter = 10; //错误的写法,因为此时指针cIter所指的数据是const,即*cIter是const
++cIter; //正确的语法,改变cIter
函数的const用法
在函数的声明,const也是使用的。
在一个函数的声明内,const可以和函数返回值、各个参数、函数本身(如果是成员函数) 而产生关联。
令函数返回一个常量值,可以降低因为客户错误而造成的意外,而又不至于放弃安全性和高效性。
例子,关于有理数的*operator声明式:
class Rational { ... }; //声明有理数
const Rational operator* (const Rational& lhs, const Rational& rhs);
在上面的声明中,要求返回一个const对象,为什么?
假如如果有如下的操作:
Rational a, b, c;
...
(a * b) = c; //在a * b的结果上调用operator=
有可能这样的写法是因为程序员的笔误:
if(a * b = c) ... //是想做一个比较操作,而不小心将“==”写成了“=”
对于这种操作——对于各乘积再赋值,是没有任何道理的。如果a,b,c是内置类型,这个操作时直接报错的。
但是Rational是用户自定义类型,一个“良好的用户自定义类型”的特征就是它们避免无端地与内置类型不兼容。
因此,需要将operator* 的回传值声明为const,就可以预防这个“没有任何意义的赋值动作”。
而对于const参数,和local const对象是一样的,在必要使用的时候去使用。除非你需要改动参数或者local对象,否则,尽量将它们声明为const,这样可以避免很多不必要的错误。