const关键字在c++中有非常重要的作用,它的使用的场合也比较多,这里我将它的使用总结一下,已备参考。
一:取代c中使用#define的常量定义
const int i=10;
这里需要说明的是默认情况下编译器不会为定义的常量分配内存,编译时会像用#define定义的常量一样将使用
常量的地方用定义时实际的值替换。但是有几种情况会强制编译器分配内存。
1. 使用extern关键字修饰常量定义时。 如:
extern const int i;
这里说明i是一个在另一个编译单元中(在另一个原文件中)定义的常量,那么这时就强制为其分配了内存。
2. 取一个常量的地址。如:
const int i=1;
const int *ptr=&i;
当然像这样的定义是不允许的:
const int i=1;
int *ptr=&i; //错误,因为不能将const的对象的地址赋给非const定义的指针。
还有要注意的是像这样的定义:int *ptr=(int *)i;这样编译器是不会报错的,也就是说这时似乎通过ptr可以改变常量
i的值。这是不会发生的,因为取了const常量的地址强制分配了内存,其实这时改变的是刚分配的内存中的值。
这之后引用的i的值还是为1,因为这时对i的引用编译器执行了常量替换。如下面代码与相对应的汇编代码的比较。
447: const int i=1;
00411248 mov dword ptr [ebp-10Ch],1
448: int *ptr=(int *)&i;
00411252 lea edx,[ebp-10Ch]
00411258 mov dword ptr [ebp-110h],edx
449: int j=i+1;
0041125E mov dword ptr [ebp-114h],2
上面最后一行可以看出进行了常量替换。
3. 常量在初始化时不能确定它的值。如:
const int i=cin.get();
4. 定义数组
const int i[] = {1,2,3,4};
这时const说明数组i所占用的地址空间是不能改变的,编译器在处理这样的定义时不能取得i中存储的内容,所以必须
分配内存在运行时存储它的值,这又带来另一个问题:
int j=i[0];这在编译时会报错,因为编译时编译器取得不了i中存储的内容。
二:对于指针的定义
1.指向常量的指针
const int *i;
这说明i是一个指针它指向的地址的内容是不可改变的。但是i的值是可以改变的,它还是可以指向别的地址。例如:
const int i=1;
int j;
const int *k=&i;
k=&j; //k可以再指向别的地址
//*k=2; //error C2166: l-value specifies const object
还有定义const int *i; 和 int const *i;是等价的。
2.常量指针
int * const i;
这说明i 是一个指针,他所指向的对象是可以改变的,但这个指针一旦初始化就不能再指向别的地址了。
int i=1,j=9;
int * const k=&i;
*k=3; //k指向的变量是可以改变的
//k=&j; //error C2166: l-value specifies const object
指向常量的指针和常量指针起初我们很容易混淆,但也有一个很好记易的方法,那就是看const关键字的位置:它在*
左面时是指向常量的指针,在*右面时是常量指针
三:隐藏的常量
有一些场合虽然程序中没有显式的声明,但其还是被系统作为常量来处理。
1.字符串常量
char *str="lizhengc";
在这个定义中并没有使用const关键字,但是字符串"lizhengc"是以常量处理的,我们不能写出这样的代码:
char *str="lizhengc";
*(cstr+2)='g';
cout<<cstr<<endl;
这样的代码虽然可以通过编译,但在运行时程序会立即崩溃。
2.临时对象
看下面的定义:
class foo {
private:
int i;
public:
foo(int ci):i(ci) {}
void modify() { i++;}
};
foo fun1() { return foo(1); }
void fun2(foo& f) { f.modify(); }
void main() {
//fun2(fun1()); //错误
}
上面fun2(fun1()); 的调用是错误的因为函数fun1返回一个临时的foo类型对象,这个临时对象被认为是const的,
同时fun2的形参类型是一个引用,意味着要将一个const的对象取地址赋值给非const的变量,在上一节中已经讨论过
这是不允许的。(这种情况不同的编译器也有不同的处理,有的编译器允许这种情况,也就是说它没有将临时量视作
const的)
四:函数定义中的const
1.看下面的函数定义:
int fun(const int i) {
i++; //错误
}
这里的const的意思是说明传递进来的参数i在函数中是不能改变的,这只是对函数的创建者的约束,在调用这个函数时
并不用理会这样的定义,不管我们传递const的或非const的参数都是可以的。
2.函数的返回值
const int fun(int i) { return ++i; }
这说明函数的返回值是不可以改变的,其实这看上去并没有什么意义,因为此函数的返回值本身就是一个数值,所以
无论如何它是无法被改变的,所以在函数返回内部类型时时没有必要加const的,所以下面的定义没有错误:
int j=9;
int k=fun(j); //const的返回值赋值给非const的变量
但如果函数返回一个类的对象,那么就
说明这个对象不可改变,你不能调用这个对象的函数或直接改变这个对象的某个值。
class foo {
private:
int i;
public:
foo(int ci):i(ci) {}
modify() { i++;}
}
const foo fun3() { return foo(1); }
foo x1=fun3(); //正确 生成foo类对象的副本赋给x1,无法改变原来的返回值
foo &x2=fun3(); //错误 相当于取const对象的地址赋给非const的引用,这是不允许的。
关于在类中使用const我将在下一篇中讨论。
五:类中的const
1.const定义的数据成员
class A {
private:
const int i;
public:
A(int size):i(size) { }
};
我们看到类A中的数据成员i被定义成const的,但在类A的构造中又在初始化列表中用构造函数的参数初始化了i,
所以用const定义的数据成员的意义为只在类的某个特定对象的生存周期内为常量,不同的对象有不同的常量i的值。
2.真正的类范围的常量
class B {
private:
static const int i;
int intArray[i];
public:
B(int size) { }
};
const int B::i=100; //初始化方式
类A中的数据成员i被定义成const的,同时也是静态的,这样就真正实现了类范围内的常量,不管建立多少类B的
对象,这些对象的i的值都是100,并且不能被改变。
3.const对象及成员函数
class C {
public:
int getSize() const { return size;}
void setSize(int sz) { size=sz; }
C(int sz):size(sz) { }
private:
int size;
};
void main() {
C aa(2);
const C bb(1);
aa.setSize(5);
int i=bb.getSize();
//bb.setSize(2); //错误 cosnt对象不能调用非const成员函数
}
从上面的代码中我们可以看到普通的对象可以调用const的和非const的成员函数,但用const定义的对象就不能
调用非const的成员函数。因为没有以const说明的成员函数被编译器认为是要修改成元员变量,所以不允许被cosnt
的对象调用。当然如果你要在一个const的成员函数中修改数据或调用另一个非const的成员函数将导致编译错误。
还有要注意的是如果只在类内定义const的成员函数,而在类的外部实现它,那必须两处定义都得加上const,如:
class D {
public:
int getSize() const;
void setSize(int sz) { size=sz; }
D(int sz):size(sz) { }
private:
int size;
};
int D::getSize() const { return size; }
如果遗漏一处那么编译器将认为他们是两个函数,而引起编译错误。
4.mutable关键字
class E {
public:
int getSize() const { j++; return size;}
void setSize(int sz) { size=sz; }
E(int sz):size(sz),j(0) { }
private:
int size;
mutable int j;
};
void main() {
const E ee(1);
int i=ee.getSize(); //成员j被改变
}
可能在一个const成员函数中我们还是希望改变某些数据成员,这时就可以使用mutable关键字定义它。这种方式我们
称作为按逻辑const。
5.volatile关键字
volatile关键字和const在语法上是一样的,他的意义为这个数据可能在编译器的控制范围外被改变,比如中断处理。那么
对于这个数据在编译时编译器将不执行任何优化(对于普通的数据在多次的使用时编译器可能使用已经被读入寄存器中的
值而不去再读取内存中的数据),每次使用这个数据程序都会再读取内存。这看起来用volatile关键字定义的数据是可变的,
但我们也可定义const的volatile数据,如:const volatile i;这说明在程序中不可被程序员改变,但它可能被外部的程序更改。