• 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修饰的函数中也能被修改。 

  • 相关阅读:
    JPA各种类型映射处理
    HTML URL编码
    C# 温故而知新:Stream篇(二)
    数据集
    C#可调用API接口来获取窗口句柄,代码如下:
    C# 温故而知新:Stream篇(三)
    SQL的主键和外键约束
    C# 温故而知新: 线程篇(三)
    C# 温故而知新:Stream篇(四)
    C# 温故而知新:Stream篇(—)
  • 原文地址:https://www.cnblogs.com/yooyoo/p/4717928.html
Copyright © 2020-2023  润新知