最近开始看《Effective C++》,为了方便以后回顾,特意做了笔记。若本人对书中的知识点理解有误的话,望请指正!!!
-
指针与
const
。见条款02 定义常量指针 部分 -
迭代器与
const
迭代器在功能上相当于指向某类型T的指针
T*
因此,如果想定义某迭代器指向一个常数,使用
const iterator
是不可以的,这样只相当于定义一个迭代器为一个常量(T* const)
,例如:std::vector<int> v; const std::vector<int>::iterator it = v.begin(); //注意,此声明只表示迭代器本身是常量 *it = 10; //编译通过,迭代器是常量,但数据可以被修改 ++it; //编译失败!因为const迭代器不允许被改变! //解决办法:使用 const_iterator const std::vector<int>::const_iterator it = v.begin(); //使用了const_iterator类型 *it = 10; //编译失败,数据不允许被改变! ++it; //编译通过,迭代器本身可以被改变
-
函数返回常量值
令函数返回常量值,可以降低程序员不小心造成的错误,而又不至于放弃安全性和高效性。
class Rational{...}; const Rational operator*(const Rational &lhs, const Rational &rhs);
可以避免
if ((a * b) = c)
这样的错误,其实想比较(a * b)
,c
两者是否相等的,但少写了个=
。 -
const
成员函数const
成员函数可以使用类中的所有成员变量,但是不能修改它们的值(直接告诉用户这个成员函数是只读的),这种措施主要还是为了保护数据而设置的。const
成员函数也称为常成员函数。用
const
修饰的对象只能调用它自身的常成员函数,因为不被const
修饰的成员函数可能会修改其他成员数据,打破const
关键字的限制。两个成员函数如果只是常量性不同,可以被重载。如下面的
operator[]
class TextBlock{ public: ... const char& operator[](size_t pos) const { return text[pos];//operator[] for const 对象,const char&是返回值,最后面的const修饰成员函数 } char& operator[](size_t pos) { return text[pos];//operator[] for non-const 对象 } private: std::string text; }; // Text t("Hello"); const Text ct("Hello"); std::cout<<t[0]; //调用了不加const修饰的索引操作符 std::cout<<ct[0]; //调用了const版本, 但如果只有不加const的操作符,将会报错discard qualifier t[0] = 'x'; //成立,但注意此索引操作符必须声明为引用才可以支持赋值操作 ct[0] = 'x'; //错误!常量不能被修改。operator[]的调用没有错误,错误是对返回的const char&赋值
-
成员函数的常量性( Constness )
C++标准对成员函数"常量性"的规定是数据常量性(bitwise constness),即不允许常量对象的成员数据被修改。C++编译器对此的检测也十分简单粗暴,只检查该成员函数有没有给成员数据的赋值操作。
但如下情形,即使修改了某个数据,也可以通过编译器的检测:
const Text ct("Hello"); //构造某常量对象 char* pc = &ct[0]; //取其指针 *pc = 'K'; //通过指针修改常量对象,编译不会报错,结果为"Kello"
数据常量性还有另一个局限性,例如:
class Text{ public: std::sizt_t length() const; private: char* pText; std::size_t length; bool lengthValid; .... }; std::size_t Text::length() const{ if(!lengthValid){ //做某些错误检测 length = std::strlen(pText); lengthValid = true; } return length; //这行才是代码核心 }
在这段代码中,length()函数要做某些错误检测,因此可能会修改成员数据(如上述 lengthValid 的修改)。即使真正的功能核心只是返回字符长度,编译器依然认为你可能会修改某些成员数据而报错。
因此,更好的方法是逻辑常量性(Logical constness),即允许某些数据被修改,只要这些改动不会反映在外,例如,以上问题可以用
mutable
关键字来解决:mutable std::size_t length; mutable bool lengthValid;
这样成员函数 length() 就可以顺利通过编译。
此外注意,除
mutable
之外,静态成员(static
)也可以被const
成员函数修改。 -
在定义常量与非常量成员函数时,避免代码重复
这里说的代码重复指的是常量与非常量成员函数里面的实现中的代码重复,如下述代码中的边界检验、日志记录访问记录、检验数据完整性。
class TextBlock{ public: ... const char &operator[](std::size_t position) const { ... //边界检验 ... //日志记录访问记录 ... //检验数据完整性 return text[position]; } char &operator[](std::size_t position) { ... //边界检验 ... //日志记录访问记录 ... //检验数据完整性 return text[postion]; } ... private: std:string text; }
解决办法:常量成员函数照旧,而非常量成员函数调用常量成员函数,如下述代码
class TextBlock{ public: ... const char& operator[](std::size_t pos) const { ... //边界检验 ... //日志记录访问记录 ... //检验数据完整性 return text[position]; } char& operator[](std::size_t pos){ return const_cast<char&>( static_cast<const Text&>(*this) [position] ); } /* 该非常量成员函数中有两个类型转换动作: 第一个是将 *this 从其原始类型 TextBlock& 转换为 const TextBlock&,即为它加上了const; 第二个是从 const operator[] 的返回值中移除 const */ ... private: std:string text; }
注意:用常量成员函数调用非常量成员函数是不合适的,因为非常量成员函数方法有可能修改数据,而 常量成员函数调用之后,就可能也会修改数据,不符合常量成员函数语义。
Note:
- 将某些东西声明为
const
可帮助编译器侦测出错误用法- 指针,迭代器,引用,局部变量,全局变量,成员函数,返回值都可以使用
const
来实现数据只读的目的- 当不可避免需要修改
const
成员变量,在其前面加上mutable
关键字- 当常量成员函数和非常量成员函数的实现有大量重复代码,可以使用非常量成员函数调用常量成员函数避免代码重复