Effective C++
目录
规则2:尽量使用const、enum、inline替换#define
2、定义一个类的专属常量:在类内使用const和static
需要在实现文件中再定义一次已经在类内声明了的static变量
3、令函数返回一个const对象,往往可以避免客户因为错误使用而造成的意外,如下面的代码:
4、const类内成员函数:在函数的参数列表后加上一个const---mutable引入
使用const_cast为对象去掉const属性和使用static_cast为对象加上const属性的方法
规则2:尽量使用const、enum、inline替换#define
1、使用const替换#define
1 #define ASPECT_RATIO 1.653 //大写常用语宏,但是每次出现ASPECT_RATIO的地方,预处理器都会盲目的将ASPECT_RATIO替换为1.653,导致目标代码出现多个1.653
2 const double AspectRatio = 1.653; //此时在目标代码中只出现一个存储1.653的地方
2、定义一个类的专属常量:在类内使用const和static
1 class GamePlayer{
2 private:
3 static const int NumTurns = 5; //static确保了所有类对象共享NumTurns(NumTurns只有一份)、const确保了NumTurns不可以被修改
4 int scores[NumTurns];
5 }; //放在头文件中
需要在实现文件中再次定义在类中已经声明了的static变量的情况:
上面是NumTurns的声明式而非定义式。通常C++要求你给任何东西提供一个定义式,但如果它是一个类内static、const变量,只要不取它的地址,则可以声明并使用它而无需提供定义式。但是你需要取它的地址或编译器不明确的简直要看到一个定义式,那么需要提供下面的定义式:
1 class GamePlayer{
2 private:
3 static const int NumTurns;
4 int scores[NumTurns];
5 }; //放在头文件中
6 const int GamePlayer::NumTurns = 5; //NumTurns的定义式,放在实现文件而非头文件中,在定义式中获得初值
上述代码存在的问题:万一你的编译器错误的不允许类内static、const变量完成初值设定,而此时类内的scores需要使用该变量以确定数组内元素的个数,此时可以使用枚举:
1 class GamePlayer{
2 private:
3 enum {NumTurns = 5}; //注意此时NumTurns是一个枚举,此时对NumTurns取地址是不合法的
4 int scores[NumTurns];
5 }; //放在头文件中
3、使用inline定义内联函数,替换#define
引入:
1 #define CALL_WITH_MAX(a,b) f((a) > (b) ? (a) : (b)) //f是只带一个参数的函数
2 int a = 5, b = 0;
3 CALL_WITH_MAX(++a,b); //在判断(++a) > (b)的时候a被累加一次,发现成立,然后再执行(++a),并将++a的结果做为f的参数。即a被累加了两次
4 CALL_WITH_MAX(++a,b+10); //在判断(++a) > (b)的时候a被累加一次,发现不成立,然后将b做为f的参数。即a被累加了一次
在调用函数f之前,a的递增此时居然取决于它被拿来和谁比较,可以使用内联函数和模板来代替:
1 template<typename T>
2 inline void callWithMax(const T & a, const T & b){
3 f(a > b ? a : b);
4 }
规则3:尽量可能使用const
1、const用于指针中
1 char greeting1[] = "Hello";
2 char greeting2[] = "GoodMorning";
3 char* p = greeting1; //non-const pointer(p可以改变指向,p=greeting2合法),non-const data(p[0]='G'合法)
4 const char* p = greeting1; //non-const pointer(p可以改变指向,p=greeting2合法),const data(p[0]='G'不合法)
5 char* const p = greeting1; //const pointer(p不可以改变指向,p=greeting2不合法),non-const data(p[0]='G'合法)
6 const char* const p = greeting1; //const pointer(p不可以改变指向,p=greeting2不合法),non-const data(p[0]='G'不合法)
其中const char* p = greeting1; 也可以写成:
char const * p = greeting1;
2、const用于STL中的迭代器中
1 const用于迭代器表示这个迭代器不可以指向不同的东西,但是它指向的东西是可以改变的,例如:
2 std::vector<int> vec;
3 ...
4 const std::vector<int>::iterator iter = vec.begin(); //表示iter只可以指向vec容器内的第一个元素,不可以指向其他的元素,但是iter指向的元素本身是可以被改变的
5 *iter = 10; //合法 //等价于T* const (char* const)
6 ++iter; //不合法
如果你希望迭代器所指向的东西不可以被改变,那么可以使用const_iterator:
1 std::vector<int> vec;
2 ...
3 const std::vector<int>::const_iterator iter = vec.begin(); //等价于const T* (const char*)
4 *iter = 10; //不合法
5 ++iter; //合法
3、令函数返回一个const对象,往往可以避免客户因为错误使用而造成的意外,如下面的代码:
1 class Rational{...};
2 const Rational operator*(const Rational & lhs, const Rational & rhs);
返回一个const Rational类对象的原因是避免客户出现下面的错误:
1 int main(){
2 Rational a,b,c;
3 ...
4 (a*b)=c;
5 (a*b)将调用operator*()函数,然后返回一个const类型的Rational类对象,然后将c赋给该const Rational类对象是不合法的!
6 }
需要补充个的知识点:
1 const int num = 5;
2 num = 6; //不合法!!
4、const类内成员函数:在函数的参数列表后加上一个const
4.1两个成员函数如果只是常量性不同,则可以被重载,举例如下:
1 class TextBlock{
2 public:
3 ....
4 const char & operator[](std::size_t position) const{ //类内const成员函数
5 return text[position];
6 }
7 const char & operator[](std::size_t position){ //类内non-const成员函数
8 return text[position];
9 }
10 private:
11 std::string text;
12 };
13
14 int main(){
15 TextBlock tb("Hello");
16 std::cout << tb[0]; //调用类内non-const成员函数
17
18 const TextBlock ctb("Hello");
19 std::cout << ctb[0]; //调用类内const成员函数
20
21 return 0;
22 }
上面的这个例子有点造作,下面的这个例子才是真实环境下的类内const成员函数使用方法:
1 void print(const TextBlock & ctb){
2 std::cout << ctb[0]; //调用类内const成员函数
3 ...
4 }
对于上面的代码,将私有变量原型的类型string改为char*,在类外使用一个指针指向了类内成员变量,那么就有可能修改该const对象中的变量,如下面的代码:
1 class CTextBlock{
2 public:
3 ....
4 const char & operator[](std::size_t position) const{ //类内const成员函数
5 return text[position];
6 }
7 const char & operator[](std::size_t position){ //类内non-const成员函数
8 return text[position];
9 }
10 private:
11 char* pText;
12 };
13 int main(){
14 const CTextBlock cctb("Hello");
15 char* pc = &cctb[0];
16 *pc = 'J'; //此时cctb类对象中的*pText=Jello
17
18 return 0;
19 }
上面的程序表明,你创建了一个常量对象并设以初值,并对它只调用const成员函数,但是你最终还是改变了它的值。
写到这里,需要停下来思考一个问题:成员函数如果是const意味着什么?
bitwise constness学派认为:不可以更改对象中任何成员变量(static变量除外)才可以说是const成员函数;
logical constness学派认为:在编译器侦测不出来的情况下,可以修改对象内的某一位,使编译器侦测不来的方法是使用mutable。
不符合bitwise constness观点的代码:
1 class CTextBlock{
2
3 std::size_t length() const;
4 private:
5 char* pText;
6 std::size_t textLength; //需要在length()类内修改,但是不符合bitwise constness观点
7 bool lengthIValid; //需要在length()类内修改,但是不符合bitwise constness观点
8 };
9 std::size_t CTextBlock::length() const{
10 if(!lengthIValid){
11 textLength = std::strlen(pText); //在const成员函数内修改了类成员,不符合bitwise constness观点
12 lengthIValid = true; //在const成员函数内修改了类成员,不符合bitwise constness观点
13 }
14 return textLength;
15 }
由于不符合bitwise constness观点,以上代码会出现编译错误,使用mutable关键字修改类内需要在const成员函数内修改的类成员变量,修改方法如下:
1 class CTextBlock{
2
3 std::size_t length() const;
4 private:
5 char* pText;
6 mutable std::size_t textLength; //需要在length()类内修改,故将textLength声明为mutable变量
7 mutable bool lengthIValid; //需要在length()类内修改,故将textLength声明为mutable变量
8 };
9 std::size_t CTextBlock::length() const{
10 if(!lengthIValid){
11 textLength = std::strlen(pText); //在const成员函数内修改了mutable类成员textLength,是合法的
12 lengthIValid = true; //在const成员函数内修改了mutable类成员lengthIValid,是合法的
13 }
14 return textLength;
15 }
如下面的代码,存在代码重复的问题:
1 class TextBlock{
2 public:
3 ....
4 const char & operator[](std::size_t position) const{ //类内const成员函数
5 ... //边界检验
6 ... //数据访问
7 ... //数据完整性检验
8 return text[position];
9 }
10 const char & operator[](std::size_t position){ //类内non-const成员函数
11 ... //边界检验
12 ... //数据访问
13 ... //数据完整性检验
14 return text[position];
15 }
16 private:
17 std::string text;
18 };
于是在const operator[]()中和non-const operator[]()成员函数中,存在着代码重复的问题,解决方法是在non-const operator[]()成员函数中调用const operator[](),那么就需要为该对象加上const属性,然后调用const operator[]()函数,最后再去掉const属性。其中为对象加上const属性方法是使用static_cast,为对象去掉const属性方法是使用const_cast,修改方法如下:
1 class TextBlock{
2 public:
3 ....
4 const char & operator[](std::size_t position) const{ //类内const成员函数
5 ... //边界检验
6 ... //数据访问
7 ... //数据完整性检验
8 return text[position];
9 }
10 char & operator[](std::size_t position){ //类内non-const成员函数
11 return
12 const_cast<char &>( //3、将const operator[]()函数返回值的const属性去掉
13 static<const TextBlock &>(*this) //1、由于调用的是non-const属性的operator[](),所以*this的原型是TextBlock &,无const属性,所以static<const TextBlock &>(*this)为*this加上const属性
14 [position] //2、调用const operator[]()
15 );
16 }
17 private:
18 std::string text;
19 };
为何不可以使用const成员函数调用non-const成员函数?
const成员函数承诺不修改类内变量,但non-const成员函数没有这样的承诺,如果使用const成员函数调用non-const成员函数就有可能发生在const成员函数中修改某一个类内变量是非法的,但是跑到non-const去修改该类内变量就是合法的了。