天天看点

const相关用法(Effective C++_3)

一、构造操作和赋值操作的区别

如果一个新对象被定义,一定会有一个构造函数被调用,不可能调用赋值操作;如果没有新对象定义,就不会有构造函数调用,那么就只能调用赋值操作;

class Demo;
Demo w1;//调用默认构造函数
Demo w2(w1);//调用复制构造函数
w1=w2;//调用赋值操作

Demo w3=w2//调用复制构造函数
           

二、尽量使用const,enums代替宏(Effective C++_2)

三、const的用法(Effective C++_3)

1、常变量

const float PI=;    //定义了常变量PI
const int Number_of_Student=; //定义了常变量Number_of_Student
           

常变量必须只能在说明时进行初始化;常变量初始化之后,不允许再被赋值;常变量必须先说明后使用。

2、引用使用const

(1)const引用是指向const对象的引用。

const int i = ; 
const int &ref = i; 
           

可以读取ref,但不能修改。这样做是有意义的,因为i本身就不可修改,当然也不能通过ref来修改了。所以也就有将const变量赋值给非const引用是非法的。

(2)非const引用是指向非const类型变量的引用。

const引用可以初始化为不同类型的对象或者右值(如字面值常量),但非const引用不可以。

// legal for const references only 
int i = ; 
const int & ref = ; 
const int & ref1 = r + i; 
double d = ; 
const int &ref2 = d; //注意这里的类型不同
           

以绑定到不同类型的ref2为例解释原因,编译器会把ref2相关的代码转换如下:

int temp = d; 
const int &ref2 = temp; // bind ref2 to temporary 
           

ref2实际上是绑定到一个临时变量上,如果ref2不为const,那么按道理就可以通过修改ref2而修改d的值,但实际上d并不会改变。所以为了避免这个问题,ref2只能是const。

非const引用只能绑定到与该引用同类型的对象,const引用则可以绑定到不同但相关的类型的对象或绑定到右值。

2、指针使用const

const出现在*的左边,表示所指之物是常量,出现在右边,表示指针本身是常量,如果出现在两边,表示所指之物和指针本身均是常量

const char* p="greeting";//所指物是常量,不可改变
char* const p="greeting";//指针本身是常量
const char* const p="greeting"//所指物和指针本身均是常量
           

注意:

const char* p="greeting";与下面的等价
char const* p="greeting"
           

3、迭代器使用const

假如你希望迭代器所指之物不可改变,你需要使用const_iterator:

vector<int> vec;
......
vector<int>::const_iterator iter=vec.begin();
*iter=;//错误,*iter是个常量
iter++;//可以
           

如果你希望迭代器本身不改变,你需要使用const,声明迭代器为const就像声明指针为const一样(T* const),表示这个迭代器不得指向其他东西,但是其所指之物可以发生改变

vector<int> vec;
......
const vector<int>::iterator iter=vec.begin();
*iter=;//可以
iter++;//错误,iter是个const
           

4、函数声明,包括函数返回值、参数使用const

(1)函数参数使用const,可以保证传入的值,在函数内部操作时,不改变原有的值

(2)函数的返回值为const

可以防止不适当的操作,比如

class Ration{};
const Ration operator*(const Ration& lhs,const Ration& rhs);
......
Ration a,b,c;
.......
if(a*b=c)
{
......
}//编译错误
           

其实上面是想做一个比较操作,但是由于手误,写成了赋值操作,那么在编译的时候就会不能通过,能找出这个错误

5、成员函数使用const

(1)const用于成员函数是为了,该成员函数能作用于const对象上

class Demo{
void print(){cout<<"success"}
};
......
Demo obj;
obj.print()//正确,可以调用
const Demo obj2;
obj2.print()//错误,不能调用
           

假如,把上面函数声明为const

void print()const{cout<<"success"}
......
obj2.print()//正确,可以调用
           

(2)const成员函数重要的原因

第一:可以使类的接口比较容易理解,因为可以知道,哪个函数能改动对象内容,哪个函数不能改动(const成员函数内部,不能改动对象内容,非const成员函数内可以改变);

第二:使操作const对象成为可能,正如条款20所述,改善C++程序效率的一个办法是使用传指向常量的引用或指针,而这一计算的前提是,我们拥有const成员函数来处理取得的const对象;

下面代码可以体现这一点:

class TextBlock{
const char& operator[](size_t position)const{
return text[position];
}
private:
string text;
};
......
void print(const TextBlock& ctb){
cout<<ctb[];//传进来的参数是const对象,只有const成员函数才能处理,而operator[]是const成员函数
}
           

(3)成员函数仅常量性不同,也可以被重载(不包括返回值是否为常量)

考虑以下class:

class TextBlock{
const char& operator[](size_t position)const{
return text[position];
}
char& operator[](size_t position){
return text[position];
}
private:
string text;
};
......

TextBlock tb("hello");
cout<<tb[];\\调用非const成员函数operator[]

const TextBlock ctb("hello");
cout<<ctb[];\\调用const成员函数operator[]
           

再考虑以下例子

cout<<tb[];\\正确,读一个非const对象
tb[]='x';\\正确,写一个非const对象
cout<<ctb[];\\正确,读一个const对象
ctb[]='x';\\错误,ctb[]返回类型是const
           

注意,

ctb[0]='x'

错误的原因只因返回类型所致,调用[]操作符本身无问题;同时也要注意,非const的operator[]这个版本返回类型是char&,并不是char,如果是char,那么

tb[0]=’x’`

无法通过编译,那是因为,如果函数的返回类型是个内置型,那么改动函数的返回值从来不合法;

(4)编译器强制实现bitwise constness,但怎么实现概念上的constness

对于const修饰的成员函数分为两种观点: logic constness and bitwise constness.

bitwise constness:它不更改对象的任一bit,即const成员函数不更改对象内的任何非静态成员变量;

不幸的是,很多不遵守bitwise constness定义的成员函数也可以通过bitwise测试。特别是,一个“修改了指针所指向的数据”的成员函数,其行为显然违反了bitwise constness定义,但如果对象中仅包含这个指针,这个函数也是bitwise const的,编译时会通过。这种情况,是反直观的,考虑以下代码

class CTextBlock
{
public:
......
 char& operator[](std::size_t position)const//bitwise constness声明,但其实不合适     
   {return pText[position];}
private:
   char* pText;
};
我们可以这么做:
const CTextBlock cctb("hello");
char* pc = &cctb[];
*pc = 'J'          //现在cctb为"Jello"
           

虽然operator[]实现的代码并不改变对象的成员变量ptext,但是通过指针我们终究是改变了其对象的内容;

这种情况,推出来了logical constness,这一派认为,一个const成员函数可以修改它所处理的对象内的bit,但只在客户端侦测不出的情况下才得如此;

众所周知,我们在const成员函数中不能修改成员变量,但当需要修改时就需要另外一个关键字—mutable. mutable释放掉non-static成员变量的logical constness约束(编译器认定的,即在const内不能修改成员变量,这时就需要mutable;考虑以下代码:

class CTextBlock{
public:
    int length()const;
private:
    char* pText;
    mutable int textLength;
    mutable bool lengthIsValid;
};
int CTextBlock::length()const
{
    if(!lengthIsValid)
    {
        textLength = std::strlen(pText);
        lengthIsValid = true;
    }
    return textLength;

}
           

由于length是const成员函数,所以在内部改变成员变量textLength、lengthIsValid是不合法的,但是单我们在成员变量名前加上mutable,在const成员函数内部改变成员变量,编译器就可以通过;

(5)在非const成员函数内调用const成员函数,避免代码过度重复

class TestBlock
{
private:
string text;
public:
...
const char& operator[](size_t position) const
{
…
return text[position];
}

char& operator[](size_t position)
{
…//操作跟上面相同
return text[position];
}
};
           

从上面代码中可以看出,代码的重复度十分高,为了避免这中情况,我们可以在非const成员函数内调用const成员函数,来实现非const成员函数,这样就可以避免代码的重复性;下面代码实现:

char& operator[] (size_t position)
{
return const_cast<char&>(
static_cast<const TestBlock&>(*this)[postion]
);
}
           

上述代码有两个转型动作,(*this)转换为const TestBlock类型,假如不转换,编译器会调用非const成员函数,即自己本身,这样是个死循环;第二次是将返回类型转换为非常量char&,const_cast可以去除常量性;

注意:不能在const成员函数内实现非const成员函数,即上述避免代码重复性的操作,不能反向进行

参考:Effective C++ 3rd(侯捷译)、C++ Prime 4rd

注:转载请注明出处http://blog.csdn.net/zhangchen1003/article/details/48212693

继续阅读