本人在读《C陷阱与缺陷》时遇到了这样一个问题:(a++)++,因为a++的结果不能作为左值,所以不能编译器不会接受a++作为后面的++运算符的操作数。想到左值,不禁难以想起const的用法,又写下了下面一些关于const的用法及详解
前言:C90标准新增了const关键字,用于限定一个变量为只读,const类型限定符声明的是变量,不是常量,使其成为一个只读值,也就是说可以在计算中使用它,也可以打印它,但是不能改变它的值,并且,const推出的初始目的就是为了取代预编译指令,消除它的缺点同时继承它的优点。
目录:1.什么是const?
2.const与#define指令的对比
3.const与static的对比
4.const的用法
1.修饰局部变量
2.修饰全局变量
3.变量指针,指针变量与指向常量的常指针
4.拓展到作为函数参数的思考
一.我们先来介绍一下什么是const:
我们通常用类型和存储类别来描述一个变量。C90还新增了两个属性:恒常性(constancy)和易变形(volatility),两个关键字const和volatile,以这两个关键字创建的类型是限定类型(qualified type)今天我们所讲的关键字const就是来声明恒常性的,通常有很多地方也把修饰的变量叫做常变量(即给一个变量赋予恒常性,处理时当常量来看待)。
C99标准为类型限定符增加了一个新属性,他们具有幂等性质,意思是在一条声明中多次使用同一个限定符,多余的限定符将被忽略:
const const const int n=6;
//与const int n=6;相同
有了这个新属性,就可以编写类似下面代码:
typedef const int zip;
const zip q=8;
二.const与#define指令相比的优点:
如果一个变量被const修饰,那么他的值就不能再改变,那么有人会提出这样的疑问,C语言中不是有#define嘛?干嘛还要用const呢?所以比较之后又如下优点:
1.预编译指令只是对值进行简单的替换,不能进行类型检查。
2.可以保护被修饰的变量,防止意外修改,增强程序的健壮性。
3.在C++语言中编译器通常不为普通的const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。
三.const与static的对比
static修饰的变量为静态变量,存储再全局(静态)区,生命周期从程序编译到运行结束。
const修饰的变量不会存放在全局(静态)区,而是取决于它定义的地方,局部定义的就存在栈区,全局定义的就存放在静态区。
四.const修饰局部变量的用法如下:
1.const修饰过的变量不能直接的改变。
const int nochange; //限定nochange的值不能改变,系统为其自动分配一个值,大多数情况为0
nochange = 12; //编译器不允许
2.可以初始化const变量。
初始化后就不能改变他的值了,也就是不能再次重定义或者再次初始化这个变量。
const int nochange = 12;//初始化nochange没问题
3.const关键字创建不允许修改的数组:
const int arr[10]={0,1,2,3,4,5,6,7,8,9}
如果使用C99之后的编译器,还可以创建如下数组:
const int a=10;
int arr[a]={1,2,3,4,5,6,7,8,9};
此时数组内有a=10个元素,这个a我们不能改变它了,但是这个数组我们并没有用const修饰,所以我们可以进行以下操作:
arr[4]=5;
如果我们不希望数组可以改变,那我们得在数组前加一个const限定符:const int arr[a]
4.const修饰一个常量静态字符串:
const char *str="helloworld"
如果没有const修饰那么可以有意无意的将str[1]='x'这样的操作,但是加上const之后,这个错误就能在程序被编译的时候立即检查出来,这就是const的好处。让逻辑错误能够在编译器发现。
const修饰全局变量的用法如下:
使用全局变量是一种冒险的方法,因为这样做暴露了数据,程序的任何部分都能修改数据。如果把数据设置为const,就可以避免这样的危险,因此使用const限定符声明全局变量很合理。可以创建const变量,const数组,const结构。
当然,在文件之间共享const数据要小心。可以采用两个策略:
第一,遵循外部变量的使用规则,即在一个文件中使用定义式声明。在其他文件中使用引用式声明(即用extern关键字)。
第二,把const变量放在一个头文件中,然后在其他文件中包含此头文件即可。但是这种方案必须要求在头文件中使用关键字static声明全局const变量。因为如果去掉static变量,那么假设两个文件 file1.c 和 file2.c 使用了同一个头文件constant.h 那么将导致每个文件中都有一个相同的标识符的定义式声明,C标准不允许(虽然有些编译器允许)这么做。加上static关键字实际上是给每个文件都提供了一个单独的数据副本,每个副本只对该文件可用。
两种方案,方案二使用头文件的好处是方便你偷懒,不用惦记着在一个文件中使用定义式声明,另一个文件中使用引用式声明,只需要包含同一个头文件即可,但是缺点也有,就是导致数据是重复的,如果头文件中某个static const修饰的变量过于庞大,就会出现一些问题。
变量指针,指针变量与指向常量的常指针:
常量指针是指针指向的内容是常量,下面两种定义方式是相同的:
const int *n;
int const *n;
常量指针说的是不能通过这个指针改变变量的值(但是可以通过其他方式改变变量的值)。
int a=5;
const int* n=&a;
a=6;
printf("%d",a); //此时打印出来a=6
*n=7;//报错,*n是不可修改的左值
printf(“%d”,a); //通过指针解引用的方式找到a的值去改变,这种方法不可行,因为定义了常量指针
还有一点重要的是常量指针指向的值不能改变,但是这部意味着指针本身不能改变,常量指针也可以指向其他地方的地址。
int a=5;
int b=6;
const int*n=&a;
n=&b;
指针常量是指指针本身是个常量,不能再指向其他的地址,写法如下: int * const n;
如下是使用的例子
int a=5;
int b=6;
int *const n=&a;
printf("%d",a);
*n=7;
printf("%d",a); //此时可以打印出来a的值为7,也就是可以通过指针解引用的方式改变指向的值
n=&b; //此时报错,不允许,因为定义了指针常量
我们不难发现指针常量和常量指针的区别就在于星号的位置:
int const *n是常量指针 int * const n是指针常量
指向常量的常指针
是以上两者的结合,写法如下
const int * const ptr;
指针ptr指向的位置不能改变,并且也不能通过指针ptr改变变量的值,但是依然可以通过其他的变量指针(例如再定义一个指针*p指向同一个值a,可以通过指针*p来操作这个值)改变变量的值。
拓展到作为函数参数的思考:
根据以上的讨论那么const修饰函数的参数也有三种用法:
1.防止修改指针指向的内容:
int Add(const int *a,const int *b,int sum);
其中a和b是输入的参数,sum是待输出的参数。给a和b加上const修饰后,如果函数体内的语句试图改动a和b的内容,编译器将报错。
2.防止修改指针指向的地址:
int swap(int *const p1,int * const p2);
指针p1和指针p2指向的地址都不能改变。
3.即防止修改指针指向的内容,又防止修改指针指向的地址;
例如 void sort(int const *const p1,int const *const p2 );
拓展到作为函数返回值的思考:
C标准定义了如下规则
把const数据或非const数据的地址初始化为指向const的指针或为其赋值的合法的。
int arr1[3]={0,1,2};
const int arr2[3]={3,4,5};
const int *a=arr1; //合理
a=arr2; //合理
a=&arr1[2]; //合理
但是只能把非const数据的地址赋给普通指针。
int arr1[3]={0,1,2};
const int arr2[3]={3,4,5};
int *a=arr1; //合理
a=arr2; //不合理
a=&arr1[2]; //合理
这个规则十分合理,否则,通过指针就能改变const数组中的数据。
总结:const限定符经常常用,学以致用const是变成大佬路上必须要学会的操作!但是众多细节不能忽略,仔细分析const用在哪里,联系它的不同用法。
最后还有一点关于引起我思考问题的解答,(a++)这个操作是创建一个a的副本进行运算,原来的a再自增,所以参加运算的那部分地址是临时创建的,不能作为左值。但是(++a)是直接对a进行自增操作再运用的,所以这个可以用来作为左值。
创作不易,麻烦各位读者点个赞或者关注激励一下作者!