const && volatile && mutable

本文摘自 http://blog.csdn.net/wuliming_sc/article/details/3717017

const

const修饰普通变量或者指针

有两种写法修饰变量:

const TYPE value;
TYPE const value;

含义是类型为TYPE的变量value是不可变的,即常量。其实,对于一个非指针类型的TYPE,这两种写法都是一种含义——value值不可变。

例如:

const int value;
int const value;

但对于指针类型的TYPE,不同的写法含义会不同:

>指针本身是常量,不能改变:

(char*) const pointer;

>指针所指向的内容是常量:

const (char) *pointer;
(char) const *pointer;

>两者都是常量:

const char* const pointer;

识别const修饰的是指针还是指针所指向的内容,一个较为简单的方法是沿着 * 号划一条线:

若const位于 * 的左侧,则const用来修饰的是指针指向的内容,为常量;

若const位于 * 的右侧,则const用来修饰的是指针本身,指针本身的地址为常量。

const修饰函数参数

const用来修饰函数参数,表示函数内部不能修改参数的值(确切的说是形参的值,包括参数本身以及参数中包含的值都不能在函数内部修改)。

void function (const int param);    // 函数按值传递,这种使用方式无意义,等价于void function(int param)
void function (const char* p);      // 指针所指向的内容不能改变
void function (char* const p);      // 函数按值传递指针地址,函数内部不会改变,这种使用方式无意义
void function (const Class& value); // 引用的参数在函数内不可以改变

通过这些示例我们得出结论:修饰函数参数的const通常用于该参数是指针或引用的情况,若参数的参数采用按值传递,由于函数自动产生临时变量复制参数,这样参数就不需要使用const修饰来保护。

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

const修饰类对象表示该对象为常量对象,对象中的任何成员变量都不能被修改。同理对于修饰的对象指针或者对象引用。const修饰的对象,该对象中任何非const修饰的成员函数都不能被调用,因为任何非const的成员函数都有修改成员变量的企图。

 1 class A
 2 {
 3     void func1 ();
 4     void func2 () const
 5 };
 6 
 7 const A a;
 8 a.func1();    // 错误:常量对象不能调用非常量的成员函数
 9 a.func2();    // 正确
10 
11 const A* pA = new A();
12 pA->func1();    // 错误:指针指向的常量不能调用非常量的成员函数
13 pA->func2();    // 正确
14 
15 A b;
16 const A& lb = b;
17 lb.func1();    // 错误:引用的常量不能调用非常量的成员函数
18 lb.func2();    // 正确

const修饰数据成员

const数据成员只在类对象的生存期内是常量,对于类而言是可重设置的。类可以创建多个对象,不同的对象其const数据成员的值可以不同。所以不能在类的声明中初始化const数据成员,因为在类对象未创建时,编译器不知道const数据成员的具体值。

class A
{
    const int size = 100;    // 错误:不能在类中初始化const修饰的常量
    int array[size];         // 错误:未知的size
}

const数据成员的初始化只能在类构造函数的初始化列表中进行。若要在类中设定恒定的常量,可以通过枚举常量实现:

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

枚举常量不占用对象的存储空间,编译时即被赋值。但枚举常量的局限性在于隐含的数据类型是整数,不能表示浮点数等其他类型。

const修饰成员函数

const修饰类中的成员函数,该函数将不能改变对象的成员变量。一般把const写在成员函数之后。

class A
{
    void func() const;  // 不能修改对象的成员变量,也不能调用非const修饰的成员函数
};

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

const修饰的成员函数返回值

1. 若const修饰的函数返回值为类对象,则多用于操作符重载。通常,不建议用const修饰函数返回值类型为类对象或类对象引用。原因在于,若函数返回为const对象或者是const对象引用,则返回值具有const属性,返回实例只能访问类中的公有(保护)数据成员和const成员函数,并且不允许对其进行赋值操作。

2. 若采用“指针传递”方式的函数返回值加const修饰,则函数返回值(指针所指向的内容)不能被修改,该返回值只能被赋值给加const修饰同类型的指针。

const char * getString();
char* str = getString();             // 错误:常量被赋值给非常量指针
const char* cstr = getString();  // 正确

3. 函数的返回值采用“引用传递”。采用这种方式只出现在类的赋值函数中,目的是实现链式表达。

class A
{
    A & operater = (const A & a);    // 重载的赋值函数
};
A a, b, c;
a = b = c;    // 正确
(a = b) = c;  // 正确

若赋值函数的返回值使用了const修饰,则返回值的内容不允许被修改,这样 a = b = c 依然正确,而 (a = b) = c就不正确了。

const与define宏定义

1. 编译器处理方式不同

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

2. 类型安全检查不同

  • const常量有具体的类型,在编译阶段会进行类型检查
  • define宏没有类型,不做类型检查,仅仅是展开

3. 存储方式不同

  • const常量会在内存中分配(堆栈中)
  • define宏仅仅是展开,不会分配内存

volatile

本意是“易变的”,该关键字是一种类型修饰符,它修饰的变量可以被编译器未知的因素更改(如操作系统、硬件或者其他线程等)。编译器对访问volatile修饰的变量的代码不再进行优化,从而提供了对特殊地址的稳定访问。当请求使用volatile修饰的变量时,即便前面的指令刚刚读取过数据,系统也会重新从它所在的内存读取数据,并且读取后会立刻被寄存。

volatile int i = 10;
int a = i;
// do something:并未明确通知编译器,对i进行过操作
int b = i;

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

在Visual C++6.0中,一般调试模式没有进行代码优化,所以这个关键字的作用看不出来。下面通过嵌入汇编代码,测试volatile关键字对程序最终代码的影响。首先用classwizard创建win32 console工程,输入代码:

 1 void main()
 2 {
 3     int i = 10;
 4     int a = i;
 5     printf("i = %d
", a);
 6     // 嵌入改变内存中 i 的值的汇编代码,但不让编译器知道
 7     __asm {
 8         mov dword ptr [ebp - 4], 20h
 9     }
10     int b = i;
11     printf("i = %d
", b);
12 }

在调试版本(Debug)模式下运行程序,结果如下:

i = 10

i = 32

在Release版本模式下运行程序,结果如下:

i = 10

i = 10

输出的结果表明,Release模式下,编译器对代码进行了优化。如果在声明 i 之前加上volatile关键字:

volatile int i = 10;

分别在调试版本和Release版本运行程序,输出都是:

i = 10

i = 32

这说明该关键字发挥作用了!

定义为volatile的变量可能会被意想不到的改变,这样编译器不会假设这个变量的值了。也就是说,优化器不对其调用进行优化,每次使用该变量时必须重新读取这个变量的值,而不是使用保存在寄存器里的备份。

下面列举使用volatile变量的情况:

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

这是区分C程序员和嵌入式系统程序员最基本的问题。嵌入式系统程序员经常与硬件、中断、RTOS等打交道,这些都要求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类型的参数,编译器将产生类似下面的代码:
1 int square (volatile int * ptr)
2 {
3     int a = *ptr;
4     int b = *ptr;
5     return a * b;
6 }

    由于*ptr的值可能被意想不到的改变,因此 a 和 b 可能是不同的。这样,返回的结果可能不是所期望的平方值!正确的代码如下:

1 int square (volatile int * ptr)
2 {
3     int a = *ptr;
4     return a * a;
5 }

mutable

mutable意思是“可变的”,与constant(C++,const)是反义词。在C++中,为突破const限制而设置mutable关键词。mutable只能修饰类的非静态数据成员,被该关键词修饰的变量将永远处于可变的状态,即使是在const函数中。

假如类的成员函数不会改变对象的状态,那么一个函数一般会声明为const。但有些时候,我们需要在const函数中修改一些跟类状态无关的数据成员,那么这个成员就应该被mutable来修饰。

 1 class A
 2 {
 3 public:
 4     void output() const;
 5 };
 6 
 7 void output() const
 8 {
 9     cout << "output for this test!" << endl;
10 }
11 
12 void outputTest(const A& a)
13 {
14     a.output();
15 }

类A的成员函数output是用来输出的,不会修改类的状态,所以被声明为const。

函数outputTest也是用来输出的,里面调用了对象a的output输出方法,为了防止在函数中调用成员函数修改任何成员变量,所以参数也被const修饰。

假如现在,我们需要添加一个功能:计算每个对象的输出次数。假如用来计数的是普通变量的话,那么在const成员函数output里面是不能修改该变量的值的;而该变量跟对象的状态无关,所以应该为了修改该变量而去掉output的const属性。这个时候就应该使用mutable关键字了,只要用这个关键字修饰该变量,所有问题就迎刃而解了。

 1 class A
 2 {
 3 public:
 4     A();
 5     ~A();
 6 
 7     void output () const;
 8     int getOutputTimes () const;
 9 
10 private:
11     mutable int m_iTimes;
12 };
13 
14 A::A()
15 {
16     m_iTimes = 0;
17 }
18 
19 A::~A() { }
20 
21 void A::output() const
22 {
23     cout << "output for this test!" << endl;
24     m_iTimes++;
25 }
26 
27 int A::getOutputTimes() const
28 {
29     return m_iTimes;
30 }
31 
32 void outputTest(const A& a)
33 {
34     cout << a.getOutputTimes() << endl;
35     a.output();
36     cout << a.getOutPutTimes() << endl;
37 }

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

原文地址:https://www.cnblogs.com/yooyoo/p/4717928.html