今天开始学习条款三,详细解读一下,让自己有更大的收获。
const 多才多艺,可以用在classes外部修饰global和namespace 作用域中的常量。或修饰文件、函数、或区块作用域中被声明的为static的对象。还可以修饰classes内部的static和non-static成员变量,面对指针,你也可以指出指针自身、指针所指物,或两者都是const:如下代码:
// useConst.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include <iostream> using namespace std; int _tmain(int argc, _TCHAR* argv[]) { char greeting[]="Hello"; char* p1=greeting; const char* p2=greeting; char* const p3=greeting; const char* const p4 =greeting; return 0; }
如果关键字const出现在星号左边,表示被指物是常量;如果出现在星号右边,表示指针自身是常量;如果出现在星号两边,表示被指物和指针两者都是常量。关键字const写在类型之前与之后意义相同。
常量意味着是不可以改变的。
另外在在使用指针的时候,const只要在*号之前,那么表达的都是指针所指的对象都是常量。比如代码:
void fun1(const Widget* ptr); 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特性。
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对象的指针也会导致编译时的错误。
#include "stdafx.h" #include <iostream> using namespace std; int _tmain(int argc, _TCHAR* argv[]) { const double pi=3.14; double* ptr=π //////错误的。 const double* cptr=π return 0; }
编译器报的错误是:
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:
如下所示代码:
// useConst.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include <iostream> #include <vector> using namespace std; int _tmain(int argc, _TCHAR* argv[]) { vector<int> vec(10); for(vector<int> ::iterator iter=vec.begin();iter!=vec.end();iter++) { *iter=10; } for(const vector<int> ::iterator iter=vec.begin();iter!=vec.end();iter++)//////c错误,iter是const,不能使用iter++ { *iter=10; } for (vector<int> ::const_iterator citer=vec.begin();citer!=vec.end();citer++)////citer++正确的。。。 { *citer=10;///////错误,不能改变。 } return 0; } 报的错误如下: 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) 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*声明式:
下面有如下代码:
// useConst.cpp : 定义控制台应用程序的入口点。 // #include"stdafx.h" #include<iostream> #include<vector> usingnamespacestd; classPoint { public: Point():x(0.0),y(0.0){}; Point(doublem,doublen):x(m),y(n){}; inlinedoubleGetX() const { returnx; } inlinedoubleGetY() const { returny; } inlinedoubleSetX(intm) { x =m; } inlinedoubleSetY(intn) { y = n; } constfriendPointoperator-(Pointlhs,Pointrhs); private: doublex; doubley; }; constPointoperator-(Pointlhs,Pointrhs) { returnPoint(lhs.GetX()-rhs.GetX(),lhs.GetY()-rhs.GetY()); } int_tmain(intargc, _TCHAR* argv[]) { Pointpa(1,2); Pointpb(1,2); Pointpc(1,2); (pa-pb)=pc; return 0; }
报的错误是:
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,用来表现一大块文字:
// useConst.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include <iostream> #include <string> using namespace std; class TextBlock { public: TextBlock():text(NULL){} TextBlock(string str):text(str){} const char& operator[](size_t position) const //operator[] for const 对象 { return text[position]; } char& operator[](size_t position) //operator[] for non-const 对象 { return text[position]; } void print(const TextBlock& ctb); private: string text; }; void TextBlock::print(const TextBlock &ctb) { cout<<ctb[0]; } int _tmain(int argc, _TCHAR* argv[]) { TextBlock tb("hello"); cout<<tb[0]<<endl; const TextBlock ctb("world"); cout<<ctb[0]<<endl; tb.print(tb); return 0; }
只要重载operator[]并对不同的版本给予不同的返回类型,就可以令const和non-const TextBlocks获得不同的处理:那么以下代码错误的原因就是
std::cout << tb[0]; //没问题读一个non-const TextBlock tb[0] = 'x'; //没问题写一个non-const TextBlock std::cout << ctb[0]; //没问题读一个const TextBlock 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,无论内部是否重新进行计算。
class CTextBlock { public: std::size_t length() const; private: char* pText; std::size_t textLength; //最近一次计算的文本区块长度。 bool lengthIsValid; //目前的长度是否有效。 }; std::size_t CTextBlock::length() const { if(!lengthIsValid) { textLength = std::strlen(pText); //错误!在const成员函数内不能赋值给textLength和lengthIsValid。 lengthIsValid = true; } return textLength; }
length的实现当然不是bitwise const,因为textLength和lengthIsValid都可能被修改。这两笔数据被修改对const CTextBlock对象而言虽然可以接受,但编译器不同意。它们坚持bitwise constness,怎么办?解决方法很简单:利用C++的一个与const相关的摆动场:mutable(可变的)。mutable释放掉non-static成员变量的bitwise constness约束:
class CTextBlock { public: std::size_t length() const; private: char* pText; std::size_t textLength; //这些成员变量可能总是会被改变,即使在const成员函数内。 bool lengthIsValid; }; std::size_t CTextBlock::length() const { if(!lengthIsValid) { textLength = std::strlen(pText); //现在可以这样, lengthIsValid = true; //也可以这样。 } return textLength; }
总结一下几点:
1.将某些东西声明为const可帮助编译器侦测出错误用法.const可被施加于任何作用域的对象,函数参数,函数返回类型,成员函数本体.
2.编译器强制实施bitwise constness,但你编写程序时应该使用"概念上的常量性"(conceptual constness);
3.当const和non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可避免代码重复