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

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

  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版本可避免代码重复

你可能感兴趣的:(C++,String,iterator,Class,reference,编译器)