Effective C++学习笔记——条款03:尽可能使用const

 
  1. 今天开始学习条款三,详细解读一下,让自己有更大的收获。  

  const 多才多艺,可以用在classes外部修饰global和namespace 作用域中的常量。或修饰文件、函数、或区块作用域中被声明的为static的对象。还可以修饰classes内部的static和non-static成员变量,面对指针,你也可以指出指针自身、指针所指物,或两者都是const:如下代码: 

 
  1. // useConst.cpp : 定义控制台应用程序的入口点。  
  2. //   
  3. #include "stdafx.h"  
  4. #include <iostream>  
  5. using namespace std;  
  6. int _tmain(int argc, _TCHAR* argv[])  
  7. {  
  8. char greeting[]="Hello";  
  9. char* p1=greeting;  
  10. const char* p2=greeting;  
  11. charconst p3=greeting;  
  12. const charconst p4 =greeting;  
  13. return 0;  
  14. }   

      如果关键字const出现在星号左边,表示被指物是常量;如果出现在星号右边,表示指针自身是常量;如果出现在星号两边,表示被指物和指针两者都是常量。关键字const写在类型之前与之后意义相同。

    常量意味着是不可以改变的。

     另外在在使用指针的时候,const只要在*号之前,那么表达的都是指针所指的对象都是常量。比如代码:

  1. void fun1(const Widget* ptr);  
  2. void fun2( Widget const* ptr);   

     在讲解STL迭代器之前,先复习一下C++pirmer的内容,如下:

那么我就来给大家说一下在C++ PRIMER (第四版)中讲解的。进行比较

如果指针指向了const对象,则不允许用指针来改变其所指的const值,为了保证这个特性,c++语言强制要求指向const对象的指针也必须具有const特性。

const double * cptr;//cptr may point to a double thatis const

这里,的cptr是一个指向double类型const对象的指针,const先顶了cptr指针所指的对象类型,而并非cptr本身。即是cptr本身并不是const,再定义时候并不需要对它进行初始化,如果需要的话,可以给cptr重新赋值。使其指向另一个const对象。但不能通过cptr修改所指对象的值。

这样就是错的,*cptr=42;

把一个const对象的地址赋给一个普通的。非const对象的指针也会导致编译时的错误。

      在讲解STL迭代器之前,先复习一下C++pirmer的内容,如下:

     那么我就来给大家说一下在C++ PRIMER (第四版)中讲解的。进行比较

        如果指针指向了const对象,则不允许用指针来改变其所指的const值,为了保证这个特性,c++语言强制要求指向const对象的指针也必须具有const特性。

  1. const double * cptr;//cptr may point to a double thatis const  


    这里,的cptr是一个指向double类型const对象的指针,const先顶了cptr指针所指的对象类型,而并非cptr本身。即是cptr本身并不是const,再定义时候并不需要对它进行初始化,如果需要的话,可以给cptr重新赋值。使其指向另一个const对象。但不能通过cptr修改所指对象的值。

这样就是错的,

*cptr=42;

把一个const对象的地址赋给一个普通的。非const对象的指针也会导致编译时的错误。

 

  1. #include "stdafx.h"  
  2. #include <iostream>  
  3. using namespace std;  
  4. int _tmain(int argc, _TCHAR* argv[])  
  5. {  
  6. const double pi=3.14;  
  7. double* ptr=π //错误的。  
  8. const double* cptr=π  
  9. return 0;  
  10. }  

编译器报的错误是:

  1. 1>d:\workspace\visual studio 2005\projects\useconst\useconst\useconst.cpp(19) : error C2440: 'initializing' : cannot convert from 'const double *__w64 ' to 'double *'  

但是可以把非const对象的地址赋给指向const对象的指针。

现在回到Effective C++中,指出STL迭代器洗衣指针为根据塑摸出来,所以迭代器的作用就像个T*指针,声明迭代器为const就行声明指针为const一样。表示这个迭代器不得指向不同的东西,就是指针的值不可以改变,但是所指向的值是可以改动的。如果你希望迭代器所指向的东西不可被改动,你需要的是一个const_iterator:

如下所示代码:

  1. // useConst.cpp : 定义控制台应用程序的入口点。  
  2. //   
  3. #include "stdafx.h"  
  4. #include <iostream>  
  5. #include <vector>  
  6. using namespace std;  
  7. int _tmain(int argc, _TCHAR* argv[])  
  8. {  
  9. vector<int> vec(10);  
  10. for(vector<int> ::iterator iter=vec.begin();iter!=vec.end();iter++)  
  11. {  
  12. *iter=10;  
  13. }  
  14. for(const vector<int> ::iterator iter=vec.begin();iter!=vec.end();iter++)//c错误,iter是const,不能使用iter++  
  15. {  
  16. *iter=10;  
  17. }  
  18. for (vector<int> ::const_iterator citer=vec.begin();citer!=vec.end();citer++)citer++正确的。。。  
  19. {  
  20. *citer=10;///错误,不能改变。  
  21. }  
  22. return 0;  
  23. }  
  24. 报的错误如下:   
  25. 1>d:\workspace\visual studio 2005\projects\useconst\useconst\useconst.cpp(18) : error C2678: binary '++' : no operator found which takes a left-hand operand of type 'const std::_Vector_iterator<_Ty,_Alloc>' (or there is no acceptable conversion)  
  26. 1>d:\workspace\visual studio 2005\projects\useconst\useconst\useconst.cpp(24) : error C3892: 'citer' : you cannot assign to a variable that is const  

这两个迭代器const修饰的对象不一样,iterator修饰的是指针为常量,不能对指针值修改,const_iterator为const修饰的是指针所指的对像不能改变。

const最具威力的用法是在函数声明时的应用。一个函数声明式内,const可以和函数返回值、各参数、函数自身(如果是成员函数)产生关联。令函数返回一个常量值,可以降低因客户错误而造成的意外,而又不至于放弃安全性和高效性。有理数的operator*声明式:

下面有如下代码:

 

  1. // useConst.cpp : 定义控制台应用程序的入口点。  
  2.   
  3. //  
  4.   
  5. #include"stdafx.h"  
  6. #include<iostream>  
  7. #include<vector>  
  8. usingnamespacestd;  
  9.   
  10. classPoint  
  11. {  
  12. public:  
  13. Point():x(0.0),y(0.0){};  
  14. Point(doublem,doublen):x(m),y(n){};  
  15. inlinedoubleGetX() const { returnx; }  
  16. inlinedoubleGetY() const { returny; }  
  17. inlinedoubleSetX(intm) { x =m; }  
  18. inlinedoubleSetY(intn) { y = n; }  
  19. constfriendPointoperator-(Pointlhs,Pointrhs);  
  20. private:  
  21. doublex;  
  22. doubley;  
  23. };  
  24. constPointoperator-(Pointlhs,Pointrhs)  
  25. {  
  26. returnPoint(lhs.GetX()-rhs.GetX(),lhs.GetY()-rhs.GetY());  
  27. }  
  28. int_tmain(intargc, _TCHAR* argv[])  
  29. {  
  30. Pointpa(1,2);  
  31. Pointpb(1,2);  
  32. Pointpc(1,2);  
  33. (pa-pb)=pc;  
  34. return 0;  
  35. }  

报的错误是:

1>d:\workspace\visual studio 2005\projects\useconst\useconst\useconst.cpp(32) : error C2678: binary '=' : no operator found which takes a left-hand operand of type 'const Point' (or there is no acceptable conversion)

 

就如以上暴行一样。如书上所述

如果a和b是内置类型,这样的代码直截了当就是不合法。一个“良好的用户自定义类型”的特征是它们避免无端地与内置类型不兼容,因此允许对两值乘积做赋值动作也就没有什么意思。将operator*的回传值声明为const可以预防那个“没意思的赋值动作”,这就是该那么做的原因。

至于const参数,没有什么特别新颖的观念,不过像local const对象一样,你应该在必要使用的时候使用它们。除非你有需要改动的参数或local对象,否则请将它们声明为const。只不过多打6个字,却省下恼人的错误,像是“想要键入‘==’却意外键成‘=’”的错误,一如稍早所述。

const成员函数

 

       将const实施于成员函数的目的,是为了确认该成员函数可用于对const对象身上。这类成员函数重要的两个理由:第一,它们使class接口比较容易理解,得知哪个函数可以改动对象内容而哪个不行,很是重要。第二,使“操作const对象”成为可能。这对编写高效代码是个关键,如条款20所言,改善C++程序效率的一个根本办法是以pass by reference-to-const方式传递对象,而此技术可行的前提是,我们有const成员函数可用来处理取得(并经修饰而成)的const对象。

许多人漠视一件事实:两个成员函数如果只是常量性(constness)不同,可以被重载。这是在是一个重要的C++特性。考虑以下class,用来表现一大块文字:

  1. // useConst.cpp : 定义控制台应用程序的入口点。  
  2. //   
  3. #include "stdafx.h"  
  4. #include <iostream>  
  5. #include <string>  
  6. using namespace std;  
  7. class TextBlock {  
  8. public:  
  9. TextBlock():text(NULL){}  
  10. TextBlock(string str):text(str){}  
  11. const char& operator[](size_t position) const //operator[] for const 对象  
  12. {  
  13. return text[position];  
  14. }  
  15. char& operator[](size_t position) //operator[] for non-const 对象  
  16. {   
  17. return text[position];   
  18. }  
  19. void print(const TextBlock& ctb);  
  20. private:  
  21. string text;  
  22. };  
  23. void TextBlock::print(const TextBlock &ctb)  
  24. {  
  25. cout<<ctb[0];  
  26. }  
  27. int _tmain(int argc, _TCHAR* argv[])  
  28. {  
  29. TextBlock tb("hello");  
  30. cout<<tb[0]<<endl;  
  31. const TextBlock ctb("world");  
  32. cout<<ctb[0]<<endl;  
  33. tb.print(tb);  
  34. return 0;  
  35. }  


 

只要重载operator[]并对不同的版本给予不同的返回类型,就可以令const和non-const TextBlocks获得不同的处理:那么以下代码错误的原因就是

  1. std::cout << tb[0]; //没问题读一个non-const TextBlock  
  2. tb[0] = 'x'//没问题写一个non-const TextBlock  
  3. std::cout << ctb[0]; //没问题读一个const TextBlock  
  4. ctb[0] = 'x'//错误!写一个const TextBlcok   


 

还有我觉得比较重要一点就是:non-const operator[]的返回类型是个reference to char,不是char。如果operator[]只是返回一个char,下面句子是不能编译的:

tb[0]='x';

这是因为,如果函数的返回类型是个内置类型,那么改动函数返回值从来就不是合法的。即使合法,改动的是其副本,不是值本身。

}

许多情况即使成员函数不具备const性质却依然可以通过编译器的测试。以上面TextBlock的 const char& operator[](size_tpostion) const为例,看下面代码的操作。它本身是bitwise constiness,但是它返回的char&却可以修改内部的值,完全违背了bitwise的概念。
TextBlock str("String");
str[0] = ‘A’;
还有假设这样的情况,TextBlock 需要返回字符串的长度。只有调用ReGetLength方法才会重新计算获得当前的字符长度,而在const下,是不允许修改_length的值。但对于客户端来说这应该是个const,无论内部是否重新进行计算。

  1. class CTextBlock {  
  2. public:  
  3.    
  4. std::size_t length() const;  
  5. private:  
  6. char* pText;  
  7. std::size_t textLength; //最近一次计算的文本区块长度。  
  8. bool lengthIsValid; //目前的长度是否有效。  
  9. };  
  10. std::size_t CTextBlock::length() const  
  11. {  
  12. if(!lengthIsValid) {  
  13. textLength = std::strlen(pText); //错误!在const成员函数内不能赋值给textLength和lengthIsValid。  
  14. lengthIsValid = true;  
  15. }  
  16. return textLength;  
  17. }  

 

        length的实现当然不是bitwise const,因为textLength和lengthIsValid都可能被修改。这两笔数据被修改对const CTextBlock对象而言虽然可以接受,但编译器不同意。它们坚持bitwise constness,怎么办?解决方法很简单:利用C++的一个与const相关的摆动场:mutable(可变的)。mutable释放掉non-static成员变量的bitwise constness约束:

  1. class CTextBlock {  
  2. public:  
  3.   
  4. std::size_t length() const;  
  5. private:  
  6. char* pText;  
  7. std::size_t textLength; //这些成员变量可能总是会被改变,即使在const成员函数内。  
  8. bool lengthIsValid;  
  9. };  
  10. std::size_t CTextBlock::length() const  
  11. {  
  12. if(!lengthIsValid) {  
  13. textLength = std::strlen(pText); //现在可以这样,  
  14. lengthIsValid = true//也可以这样。  
  15. }  
  16. return textLength;  
  17. }  

 

总结一下几点:

1.将某些东西声明为const可帮助编译器侦测出错误用法.const可被施加于任何作用域的对象,函数参数,函数返回类型,成员函数本体.

2.编译器强制实施bitwise constness,但你编写程序时应该使用"概念上的常量性"(conceptual constness);

3.当const和non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可避免代码重复

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值