C/C++编程中const的使用详解
1 概述:const和define的区别
先看一个典型的程序:
#include<iostream> using namespace std; int main() { int num = 1; #define t1 num + num #define t2 t1 % t1 cout << "t2 is " << t2 << endl; // t2 is 2 const int s1 = num + num; const int s2 = s1 % s1; cout << "s2 is " << s2 << endl; // s2 is 0 return 0; }
运行结果如注释所示,分别得到“t2 is 2”、“s2 is 0”???
分析原因:const定义的常量s1、s2,则s1的值是num+num,s2的值是s1%s1,所以最后结果为“s2 is 0”;而define定义的变量作替换后,C++把cout<<"t2 is "<<t2<<endl;语句译成了:cout<<"t2 is "<<num+num%num+num<<endl;所以结果为“t2 is 2”(1+0+1=2)
注意:define定义的内容,建议增加使用括号()
#define t1 (num + num) #define t2 (t1 % t1)
具体分析define与const的区别,如下定义:
const float PI = 3.14; #define PI (3.14)
- 类型的安全性检查:const常量有数据类型,而define定义宏常量没有数据类型。则编译器可以对前者进行类型安全检查,而对后者只进行字符替换,没有类型安全检查(字符替换时可能会产生意料不到的错误,如上面的程序所示);
- 调试:部分调度工具可以对const常量进行调度,但不能对宏常量进行调度;
- 编译器的处理方式不同:define宏是在预处理阶段展开,const常量则是编译运行阶段使用;
- 存储方式不同:define宏仅仅是展开,有几个地方使用则展开几次,不分配内存;const常量会在内存中分配地址(可以是堆中也可以是栈中);
- 效率:define定义的常量在内存中有若干个拷贝;const定义的常量在程序运行过程中只有一份拷贝,甚至不为普通const常量分配存储空间,而是将它们保存在符号表中,相当于没有了读内存的操作,使得效率也很高。
2. 修饰局部变量
const约束对象的访问性质:使得对象只能读,不能写:不允许修改对象的值.
const 是constant 的缩写,“恒定不变”的意思。被const 修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。所以很多C++程序设计书籍建议:“Use const whenever you need”。
const int n=5; int const n=5;
这两种写法是一样的,都是表示变量n的值不能被改变了,需要注意的是,用const修饰变量时,一定要给变脸初始化,否则之后就不能再进行赋值了。
// C const int num = 100; (int *)&num = 4; //可以间接修改
C的const表示修饰只读变量,但是我们可以恶意强制操作const对象。
接下来看看const用于修饰常量静态字符串,例如:
const char* str="fdsafdsa";
如果没有const的修饰,我们可能会在后面有意无意的写str[4]=’x’这样的语句,这样会导致对只读内存区域的赋值,然后程序会立刻异常终止。有了const,这个错误就能在程序被编译的时候就立即检查出来,这就是const的好处。让逻辑错误在编译期被发现。
如下修改都是不允许的。
// c++ const int i = 0; //是一个右值,有内存实体 i = 11; //错误:向只读变量‘i'赋值 const int a{1.1}; //错误:从“double”转换到“int”需要收缩转换,请初始化成相同类型的常量
3. 常量指针与指针常量
常量指针是指针指向的内容是常量,可以有一下两种定义方式。
const int * n; int const * n;
需要注意的是一下两点:
1、常量指针说的是不能通过这个指针改变变量的值,但是还是可以通过其他的引用来改变变量的值的。
int a=5; const int* n=&a; a=6;
指针常量是指指针本身是个常量,不能在指向其他的地址,写法如下:
int *const n;
需要注意的是,指针常量指向的地址不能改变,但是地址中保存的数值是可以改变的,可以通过其他指向改地址的指针来修改。
int a=5; int *p=&a; int* const n=&a; *p=8;
注意:区分常量指针和指针常量的关键就在于星号的位置,我们以星号为分界线,如果const在星号的左边,则为常量指针,如果const在星号的右边则为指针常量。如果我们将星号读作‘指针’,将const读作‘常量’的话,内容正好符合。int const * n;是常量指针,int *const n;是指针常量。
指向常量的常指针
是以上两种的结合,指针指向的位置不能改变并且也不能通过这个指针改变变量的值,但是依然可以通过其他的普通指针改变变量的值。
const int* const p;
4. 修饰函数的参数
根据常量指针与指针常量,const修饰函数的参数也是分为三种情况。
1、防止修改指针指向的内容
void StringCopy(char *strDestination, const char *strSource);
其中 strSource 是输入参数,strDestination 是输出参数。给 strSource 加上 const 修饰后,如果函数体内的语句试图改动 strSource 的内容,编译器将指出错误。
2、防止修改指针指向的地址
void swap ( int * const p1 , int * const p2 )
指针p1和指针p2指向的地址都不能修改。
3、以上两种的结合。
5. 修饰函数的返回值
如果给以“指针传递”方式的函数返回值加 const 修饰,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加const 修饰的同类型指针。
例如函数:
const char * GetString(void);
如下语句将出现编译错误:
char *str = GetString();
正确的用法是
const char *str = GetString();
6. 修饰全局变量
全局变量的作用域是整个文件,我们应该尽量避免使用全局变量,因为一旦有一个函数改变了全局变量的值,它也会影响到其他引用这个变量的函数,导致除了bug后很难发现,如果一定要用全局变量,我们应该尽量的使用const修饰符进行修饰,这样防止不必要的人为修改,使用的方法与局部变量是相同的。