C++中const、volatile、mutable的用法

const关键字

const修饰普通变量和指针

const修饰变量,通常有两种写法:
const TYPE value;
TYPE const value;

这两种写法在本质上是同样的。它的含义是:const修饰的类型为TYPE的变量value是不可变的。对于一个非指针的类型TYPE,不管怎么写,都是一个含义,即value值不可变。 例如:

const int nValue;    //nValue是const
int const nValue;    //nValue是const

可是对于指针类型的TYPE,不一样的写法会有不一样状况:

l  指针自己是常量不可变
(char*) const pContent;

l  指针所指向的内容是常量不可变
const (char) *pContent;
(char) const *pContent;

l  二者都不可变
const char* const pContent;

识别const究竟是修饰指针仍是指针所指的对象,还有一个较为简便的方法,也就是沿着*号划一条线:
若是const位于*的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量;
若是const位于*的右侧,const就是修饰指针自己,即指针自己是常量。 
程序员


const修饰函数参数

const修饰函数参数是它最普遍的一种用途,它表示在函数体中不能修改参数的值(包括参数自己的值或者参数其中包含的值):

void function(const int Var);     //传递过来的参数在函数内不能够改变(无心义,该函数以传值的方式调用)
void function(const char* Var);   //参数指针所指内容为常量不可变
void function(char* const Var);   //参数指针自己为常量不可变(也无心义,var自己也是经过传值的形式赋值的)
void function(const Class& Var); //引用参数在函数内不能够改变

参数const一般用于参数为指针或引用的状况若输入参数采用“值传递”方式,因为函数将自动产生临时变量用于复制该参数,该参数本就不须要保护,因此不用const修饰
面试


const修饰类对象/对象指针/对象引用

const修饰类对象表示该对象为常量对象,其中的任何成员都不能被修改。对于对象指针和对象引用也是同样。
const修饰的对象,该对象的任何非const成员函数都不能被调用,由于任何非const成员函数会有修改为员变量的企图。

例如:
安全

class AAA 
{ 
   void func1(); 
   void func2() const; 
} 
 
const AAA aObj; 
aObj.func1(); 错误 
aObj.func2(); 正确 
 
const AAA* aObj = new AAA(); 
aObj->func1(); 错误 
aObj->func2(); 正确


const修饰数据成员

const数据成员只在某个对象生存期内是常量,而对于整个类而言倒是可变的。由于类能够建立多个对象,不一样的对象其const数据成员的值能够不一样。因此不能在类声明中初始化const数据成员,由于类的对象未被建立时,编译器不知道const 数据成员的值是什么,例如:
多线程

class A 
{ 
    const int size = 100; //错误 
    int array[size];       //错误,未知的size 
}

const数据成员的初始化只能在类的构造函数的初始化列表中进行。要想创建在整个类中都恒定的常量,能够用类中的枚举常量来实现,例如:
函数

class A 
{ 
… 
  enum {size1=100, size2 = 200 }; 
  int array1[size1]; 
  int array2[size2]; 
… 
}

枚举常量不会占用对象的存储空间,他们在编译时被所有求值。可是枚举常量的隐含数据类型是整数,其最大值有限,且不能表示浮点数。
测试

const修饰成员函数

const修饰类的成员函数,用const修饰的成员函数不能改变对象的成员变量。通常把const写在成员函数的最后:
优化

class A 
{ 
   … 
  void function()const; //常成员函数, 它不改变对象的成员变量. 也不能调用类中任何非const成员函数。 
}

对于const类对象/指针/引用,只能调用类的const成员函数。spa

const 修饰成员函数的返回值

1、通常状况下,函数的返回值为某个对象时,若是将其声明为const时,多用于操做符的重载。一般,不建议用const修饰函数的返回值类型为某个对象或对某个对象引用的状况。缘由以下:若是返回const对象,或返回const对象的引用,则返回值具备const属性,返回实例只能访问类A中的公有(保护)数据成员和const成员函数,而且不容许对其进行赋值操做,这在通常状况下不多用到。

二、若是给采用“指针传递”方式的函数返回值加const修饰,那么函数返回值(即指针所指的内容)不能被修改,该返回值只能被赋给加const 修饰的同类型指针:
const char * GetString(void);
以下语句将出现编译错误:
char *str=GetString();
正确的用法是:
const char *str=GetString();
三、函数返回值采用“引用传递”的场合很少,这种方式通常只出如今类的赙值函数中,目的是为了实现链式表达。如:
操作系统

class A 
{ 
    … 
    A &operate= (const A &other); //赋值函数 
}

A a,b,c; //a,b,c为A的对象

a=b=c;   //正常
(a=B)=c; //不正常,可是合法
若赋值函数的返回值加const修饰,那么该返回值的内容不容许修改,上例中a=b=c依然正确。(a=b)=c就不正确了。
线程

const常量与define宏定义的区别

l  编译器处理方式不一样

define宏是在预处理阶段展开。
const常量是编译运行阶段使用。

l  类型和安全检查不一样

define宏没有类型,不作任何类型检查,仅仅是展开。
const常量有具体的类型,在编译阶段会执行类型检查。

l  存储方式不一样

define宏仅仅是展开,有多少地方使用,就展开多少次,不会分配内存。
const常量会在内存中分配(能够是堆中也能够是栈中)。

volatile关键字

volatile的本意是“易变的”, volatile关键字是一种类型修饰符,用它声明的类型变量表示能够被某些编译器未知的因素更改,好比操做系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就再也不进行优化,从而能够提供对特殊地址的稳定访问。

当要求使用volatile 声明的变量的值的时候,系统老是从新从它所在的内存读取数据,即便它前面的指令刚刚从该处读取过数据。并且读取的数据马上被寄存。例如:

volatile int i=10; 
int a = i; 
... //其余代码,并未明确告诉编译器,对i进行过操做 
int b = i;

volatile 指出 i是随时可能发生变化的,每次使用它的时候必须从i的地址中读取,于是编译器生成的汇编代码会从新从i的地址读取数据放在b中。而优化作法是,因为编译器发现两次从i读数据的代码之间的代码没有对i进行过操做,它会自动把上次读的数据放在b中。而不是从新从i里面读。这样以来,若是i是一个寄存器变量或者表示一个端口数据就容易出错,因此说volatile能够保证对特殊地址的稳定访问。

注意,在vc6中,通常调试模式没有进行代码优化,因此这个关键字的做用看不出来。下面经过插入汇编代码,测试有无volatile关键字,对程序最终代码的影响。首先用classwizard建一个win32 console工程,插入一个voltest.cpp文件,输入下面的代码:

#include <stdio.h> 
void main() 
{ 
    int i=10; 
    int a = i;
    printf("i= %d/n",a); 
    //下面汇编语句的做用就是改变内存中i的值,可是又不让编译器知道 
    __asm { 
      mov dword ptr [ebp-4], 20h 
    } 
    int b = i; 
    printf("i= %d/n",b); 
}

而后,在调试版本模式运行程序,输出结果以下:
i = 10
i = 32 


而后,在release版本模式运行程序,输出结果以下:
i = 10
i = 10
输出的结果明显代表,release模式下,编译器对代码进行了优化,第二次没有输出正确的i值。下面,咱们把 i的声明加上volatile关键字,看看有什么变化:

#include <stdio.h> 
 
void main() 
{ 
volatile int i=10; 
int a = i; 
 
printf("i= %d/n",a); 
__asm { 
  mov dword ptr [ebp-4], 20h 
} 

int b = i; 
printf("i= %d/n",b); 
}


分别在调试版本和release版本运行程序,输出都是:
i = 10
i = 32
这说明这个关键字发挥了它的做用!

关于volatile的补充信息:

一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都当心地从新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:

    1). 并行设备的硬件寄存器(如:状态寄存器)
    2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
    3). 多线程应用中被几个任务共享的变量

我认为这是区分C程序员和嵌入式系统程序员的最基本的问题。嵌入式系统程序员常常同硬件、中断、RTOS等等打交道,所用这些都要求volatile变量。不懂得volatile内容将会带来灾难。假设被面试者正确地回答了这是问题(嗯,怀疑这否会是这样),我将稍微深究一下,看一下这家伙是否是直正懂得volatile的重要性:

    1). 一个参数既能够是const还能够是volatile吗?解释为何。
    2). 一个指针能够是volatile 吗?解释为何。
    3). 下面的函数有什么错误:

int square(volatile int *ptr) 
{ 
    return *ptr * *ptr; 
}


    下面是答案:

    1). 是的。一个例子是只读的状态寄存器。它是volatile由于它可能被意想不到地改变。它是const由于程序不该该试图去修改它。

    2). 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。

    3). 这段代码的有个恶做剧。这段代码的目的是用来返指针*ptr指向值的平方,可是,因为*ptr指向一个volatile型参数,编译器将产生相似下面的代码:

   int square(volatile int *ptr) 
    { 
         int a,b; 
         a = *ptr; 
         b = *ptr; 
         return a * b; 
     }

    因为*ptr的值可能被意想不到地该变,所以a和b多是不一样的。结果,这段代码可能返不是你所指望的平方值!正确的代码以下:

     long square(volatile int *ptr) 
     { 
            int a; 
            a = *ptr; 
            return a * a; 
     }

mutable关键字

mutalbe的中文意思是“可变的,易变的”,跟constant(既C++中的const)是反义词。在C++中,mutable也是为了突破const的限制而设置的。被mutable修饰的变量(mutable只能因为修饰类的非静态数据成员),将永远处于可变的状态,即便在一个const函数中。

咱们知道,假如类的成员函数不会改变对象的状态,那么这个成员函数通常会声明为const。可是,有些时候,咱们须要在const的函数里面修改一些跟类状态无关的数据成员,那么这个数据成员就应该被mutalbe来修饰。下面是一个小例子:

class ClxTest 
{ 
 public: 
  void Output() const; 
}; 
 
void ClxTest::Output() const 
{ 
 cout << "Output for test!" << endl; 
} 
 
void OutputTest(const ClxTest& lx) 
{ 
 lx.Output(); 
}


类ClxTest的成员函数Output是用来输出的,不会修改类的状态,因此被声明为const。

函数OutputTest也是用来输出的,里面调用了对象lx的Output输出方法,为了防止在函数中调用成员函数修改任何成员变量,因此参数也被const修饰。

假如如今,咱们要增添一个功能:计算每一个对象的输出次数。假如用来计数的变量是普通的变量的话,那么在const成员函数Output里面是不能修改该变量的值的;而该变量跟对象的状态无关,因此应该为了修改该变量而去掉Output的const属性。这个时候,就该咱们的mutable出场了,只要用mutalbe来修饰这个变量,全部问题就迎刃而解了。下面是修改过的代码:

class ClxTest 
{ 
 public: 
  ClxTest(); 
  ~ClxTest(); 
  void Output() const; 
  int GetOutputTimes() const;
 private: 
  mutable int m_iTimes; 
}; 
 
ClxTest::ClxTest() 
{ 
 m_iTimes = 0; 
} 
 
ClxTest::~ClxTest() 
{} 

void ClxTest::Output() const 
{ 
 cout << "Output for test!" << endl; 
 m_iTimes++; 
} 
 
int ClxTest::GetOutputTimes() const 
{ 
 return m_iTimes; 
} 
 
void OutputTest(const ClxTest& lx) 
{ 
 cout << lx.GetOutputTimes() << endl; 
 lx.Output(); 
 cout << lx.GetOutputTimes() << endl; 
}

计数器m_iTimes被mutable修饰,那么它就能够突破const的限制,在被const修饰的函数里面也能被修改。

相关文章
相关标签/搜索