1.const修饰符概述
const修饰符允许你指定一个语义上的约束(即指定了一个只能读不能修改的对象),而编译器会强制实施这项约束。当你在对象前加入const进行修饰时,你就告诉了编译器或其他程序员这个对象的值只能保持不变。使用const关键字能够大大提高我们程序的健壮性。
const应用场合有很多。可以用它在类的外部修饰global或namespace作用域中的常量;可以用来修饰文件、函数、区块作用域中被声明为static的对象;也可以用来修饰类的内部的static和non-static成员变量。对于指针,你也可以指出指针自身、指针所指物或者两者都是或都不是const。
2.const修饰符在指针中的应用
常量指针:如果const出现在星号左边,表示指针所指物是常量。有两种等价的写法,见下例。
指针常量:如果const出现在星号右边,表示指针自身是常量。指向常量的常指针:如果const出现在星号两边,表示指针所指物和指针自身都是常量。
1const int *p; // 常量指针 2int const *p; // 常量指针 3 4int * const p; // 指针常量 5 6const int * const p; // 指向常量的常指针
STL中的迭代器是以指针为根据构建出来的,因此迭代器的作用就像一个T* 指针。声明迭代器为const就像声明指针为const一样(即声明了一个指针常量T* const 指针),它表示这个迭代器不能指向其他的东西,但是它所指的东西的值是可以改变的。如果你希望迭代器所指的东西不可以被修改(即希望STL能模拟出一个常量指针const T* 指针),你需要的是const_iteartor。如下例所示:
1std::vector<int> vec; 2.... 3 4const std::vector<int>::iterator iter = vec.begin(); // 定义了一个指针常量iter 5*iter = 10; // 指针所指的东西可以被修改,所以此句正确! 6++iter; // 指针自身不能修改,所以此句错误! 7 8std::vector<int>::const_iterator cIter = vec.begin(); // 定义了一个常量指针cIter 9*cIter = 10; // 指针所指的东西不能被修改,所以此句错误! 10++cIter; // 指针自身可以修改,所以此句正确!
3.const修饰符在函数中的应用
在一个函数声明内,const关键字可以在函数的返回值、函数的参数、函数自身(前提是这个函数是类的成员函数)中得到应用。
3.1 在函数返回值中的应用
令函数返回一个常量值,往往可以降低因客户误操作而造成的意外,而又不至于放弃安全性和高效性。如下例所示:
1class Rational{ 2... 3const Rational operator* (const Rational& lhs, const Rational& rhs); 4}; // 重载*运算符函数返回了一个常对象
为什么上述重载*运算符函数会返回一个常对象呢?原因是如果不这样的做的话,当程序员不小心打错一个字符(即把==错打成赋值运算符),就会变成对两个数值的乘积再做一次赋值情况。如下例所示:
1Rational a, b, c; 2... 3(a * b ) = c; // 此处应该是(a*b) == c,但是由于单纯的打字错误导致的。
如果a和b都是C++中的内置数据类型,上面的(a * b) = c直接会报错。将重载*运算符函数函数返回一个const,可以防止像(a * b) = c那样没意思的赋值操作,这也是函数返回值加const修饰的原因。
3.2 在函数参数中的应用
对于const修饰的函数参数,它就类似于local const对象一样,你可以在必要的场合使用它。除非你有需要改动的参数或local对象,否则请把它们声明了const。
3.3 const成员函数
const应用于成员函数上的目的是为了使该常成员函数可以作用于const对象上。常成员函数之所以重要的原因:a.它们可以使class接口比较容易被理解。因为可以得知哪个函数可以改动对象内容而哪个函数不可以。b.它们可以使操作const对象成为可能(常对象只能调用常成员函数,而普通对象既可以调用常成员函数,也可以调用非常成员函数)。
1#include <iostream> 2 3using namespace std; 4 5class square 6{ 7private: 8 int w, h; 9 10public: 11 int getArea() const; // 常成员函数 12 // int getArea(); // 非常成员函数 13 square(int w, int h) : w(w), h(h){}; 14}; 15 16int square::getArea() const 17{ 18 return w * h; 19} 20 21// int square::getArea() 22// { 23// return w * h; 24// } 25 26int main() 27{ 28 //const square a(3,4); // 常对象 29 //cout << a.getArea() << endl; 30 square b(2, 4); // 普通对象 31 cout << b.getArea() << endl; 32 33 return 0; 34}
c.常成员函数不能用来更新类的非静态成员变量。d.常成员函数不能调用该类中没有用const修饰的成员函数,只能调用常成员函数。因此,下面的程序将会报错。
1#include <iostream> 2 3using namespace std; 4 5class square 6{ 7private: 8 int w, h; 9 10public: 11 // int getArea() const; // 常成员函数 12 int getArea(); // 非常成员函数 13 square(int w, int h) : w(w), h(h){}; 14}; 15 16// int square::getArea() const 17// { 18// return w * h; 19// } 20 21int square::getArea() 22{ 23 return w * h; 24} 25 26int main() 27{ 28 const square a(3,4); // 常对象 29 cout << a.getArea() << endl; 30 // square b(2, 4); // 普通对象 31 // cout << b.getArea() << endl; 32 33 return 0; 34}
e.两个成员函数如果只是常量性的不同(成员函数是否加const修饰),可以被重载。
1#include <iostream> 2 3using namespace std; 4 5class square{ 6private: 7 int w, h; 8public: 9 int getArea() const; // 常成员函数,它与普通成员函数之间形成了重载关系 10 int getArea(); // 普通成员函数 11 square(int w, int h) : w(w), h(h){}; 12}; 13 14int square::getArea() const 15{ 16 return w * h; 17} 18 19int square::getArea() 20{ 21 return w * h; 22} 23 24int main() 25{ 26 const square a(3,4); 27 cout << a.getArea() << endl; 28 square b(2,4); 29 cout << b.getArea() << endl; 30 31 return 0; 32}
4.bitwise constness与logical constness
bitwise constness认为成员函数只有在不更改对象的任何成员变量(static除外)时,才可以是const。即它不更改对象内的任何一个bit。编译器只需要寻找成员变量的赋值动作即可检查出违反点。因此,常成员函数不可以更改对象内任何非静态成员变量。
然而,许多成员函数虽然不具备const性质却也能通过bitwise测试。例如,一个更改了指针所指物的成员函数虽然不能算是const,但如果只有指针自身隶属于对象,那么此函数也不会引起bitwise编译错误。如下所示:
1#include <iostream> 2#include <vector> 3#include <string> 4#include <cstring> 5 6 7// bitwise-constness 8class CTextBlock{ 9public: 10 CTextBlock(const char* str){ 11 pText = new char[strlen(str) + 1]; 12 memcpy(pText, str, strlen(str) + 1); 13 } 14 15 // 常成员函数 16 char& operator[] (std::size_t position) const{ 17 return pText[position]; 18 } 19 20 ~CTextBlock(){ 21 delete pText; 22 } 23private: 24 char* pText; 25}; 26 27int main(){ 28 const CTextBlock cctb("CurryCoder"); 29 char* pt = &cctb[0]; 30 *pt = 'C'; // 更改了指针所指向内容的成员函数,显然它不能算是常成员函数。但是,bitwise-const并不会报错! 31 32 std::cout << *pt << std::endl; 33 return 0; 34}
logical constness认为一个const成员函数可以修改它所处理的对象内的某些bits,但只有在客户端检测不出的情况下才能这样做。
下面的程序中,对常成员函数length()的修改显然不是bitwise const,因为textLength和lengthIsVaild都可能被修改。这两个数据被修改对const CTextBlock对象而言虽然可以接受,但编译器不同意。解决方法:利用C++的一个与与const相关的mutable。mutable释放掉非静态成员变量的bitwise constness约束。如下例所示:
1#include <iostream> 2#include <vector> 3#include <string> 4#include <cstring> 5 6// logical-constness 7class CTextBlock 8{ 9public: 10 CTextBlock(const char *str) 11 { 12 pText = new char[strlen(str) + 1]; 13 memcpy(pText, str, strlen(str) + 1); 14 } 15 16 // 常成员函数 17 char &operator[](std::size_t position) const 18 { 19 return pText[position]; 20 } 21 22 std::size_t length() const; 23 24 ~CTextBlock() 25 { 26 delete pText; 27 } 28 29private: 30 char *pText; 31 mutable std::size_t textLength; 32 mutable bool lengthIsVaild; 33}; 34 35std::size_t CTextBlock::length() const 36{ 37 if (!lengthIsVaild) 38 { 39 textLength = strlen(pText); 40 lengthIsVaild = true; 41 } 42 return textLength; 43} 44 45int main() 46{ 47 const CTextBlock cctb("CurryCoder"); 48 char *pt = &cctb[0]; 49 std::cout << "长度是: " << cctb.length() << std::endl; 50 return 0; 51}
5.在const和non-const成员函数中避免代码重复
当const成员函数与non-const成员函数中存在大量重复性的代码时,可以用其中的non-const成员函数调用const常成员函数。注意:使用const成员函数调用non-const成员函数是不可以的!如下例所示:
1#include <iostream> 2#include <vector> 3#include <string> 4 5class TextBlock 6{ 7public: 8 TextBlock(const std::string &s) : text(s) {} 9 // const成员函数 10 const char &operator[](std::size_t position) const 11 { 12 // 边界检验 13 // 志记数据访问 14 // 检验数据完整性 15 // .... 16 return text[position]; 17 } 18 // non-const成员函数 19 char &operator[](std::size_t position) 20 { 21 // 边界检验 22 // 志记数据访问 23 // 检验数据完整性 24 // .... 25 return text[position]; 26 } 27 28private: 29 std::string text; 30}; 31 32 33/* 34const成员函数与non-const成员函数中存在大量重复性的代码,因此使用在non-const成员函数中调用const成员函数即可。注意:反过来则不行。 35当中涉及两个转型动作: 36 第一次用来为*this添加const(强迫执行了一次安全转型static_cast),第二次是从const operator[]的返回值中移除const(只能用const_cast完成,没有其他选择) 37 */ 38 39class TextBlock 40{ 41public: 42 TextBlock(const std::string &s) : text(s) {} 43 // const成员函数 44 const char &operator[](std::size_t position) const 45 { 46 // 边界检验 47 // 志记数据访问 48 // 检验数据完整性 49 // .... 50 return text[position]; 51 } 52 // non-const成员函数 53 char &operator[](std::size_t position) 54 { 55 // 边界检验 56 // 志记数据访问 57 // 检验数据完整性 58 // .... 59 return const_cast<char&> (static_cast<const TextBlock&> (*this)[position]); 60 } 61 62private: 63 std::string text; 64};
6.总结
(1).将某些东西声明为const可以帮助编译器检测出错误用法。const可以应用在任何作用域内的对象、函数参数、函数返回类型、成员函数本体上。
(2).编译器强制实施bitwise constness,但你编写程序的时候应该使用概念上的常量性(即logical constness)。(3).当const和non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可避免代码重复,但反过来调用则不可以。
7.参考资料
[1].https://www.cnblogs.com/meituanqishoualin/p/11607564.html[2].https://www.cnblogs.com/guyan/archive/2012/03/16/2400705.html