本文摘自 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变量的情况:
- 并行设备的硬件寄存器,如状态寄存器
- 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
- 多线程应用中被几个任务共享的变量
这是区分C程序员和嵌入式系统程序员最基本的问题。嵌入式系统程序员经常与硬件、中断、RTOS等打交道,这些都要求volatile变量,不懂volatile关键字将会带来灾难。
下面先来探讨几个问题:
- 一个参数既可以是const还可以是volatile吗?解释为什么。
- 一个指针可以是volatile吗?解释为什么。
- 下面的函数有错吗?
int square (volatile int * ptr) { return *ptr * *ptr; }
答案:
- 可以。例如只读的状态寄存器,它可以被volatile修饰,因为它可能会被意想不到的改变;它也可以被const修饰,因为程序不应该试图去修改它。
- 可以。但这种情况很少见。例如当一个中服务子程序修改一个指向buffer的指针时。
- 函数有错误。这段代码的目的是用来返回指针 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修饰的函数中也能被修改。