计算机是人造的一个逻辑电路构成的世界。
计算机软件则是使用计算机内的各种逻辑电路的工具。
如何更好的使用计算机这个逻辑电路为我所用?
诞生了汇编,c/c++语言,python,lua等各种各样的语言。
每种语言,以及语言中的每个设计,都是有原因的,有用处的。
本文就对c中的const关键字的存在做一下探讨。
const,常量的意思,恒量的意思,一直不变的量,不可改变的量。
为什么c语言中要设计这样一个关键字呢?
在const之前,其实有常量的,用#define PI 3.14即可定义出一个常量。
那为什么会有const呢?因为除了有常量,还有变量的。
int float pi=3.14中的pi就是变量,pi是一个可以变化的量。
一个程序中,常量的使用较为简单,变量的使用非常多,特别是临时变量。
所以积累了大量对变量的维护经验,比如变量检查,给变量申请内存,释放内存等。
而常量呢?因为恒定不变,所以就直接没有分配内存,只是作为一个操作符号的存在,对常量的维护很少。
操作符号不分配内存空间,与变量不统一管理,一些时候会很不方便。【后面我找些例子做补充,说明一下常量的不方便】
那么能不能有一种变量,它具有常量的性质?
常量的性质:不可改变性。
所以我们要设计一种策略,让变量不可改变,变量不可改变后,变量就具有了常量的性质。
这种策略具体就是:将程序中对变量改变的语句视为非法,视为错误。
具体:当代码被编译时,先看变量pi是否需要当作常量用,如果需要,那就检查所有代码中,是否有改变变量pi的语句,如果有,就报错,提示:这个是read-only的变量,不能用这个语句,如下:
如图所示,pfloat,*pfloat_2,pfloat_3,*pfloat_3都是常量,代码中出现赋值assignment语句后,编译器就会报错。
通过限定一个变量的修改语句来实现一个变量具有常量的属性:不可改变性。
完整的策略:程序员把一个变量前加const关键字,编译器遇到有const关键字的变量时,就特殊对待一下,检查一下程序中是否有对这个变量的修改语句,如果有,就报错。
就这样,const关键字就诞生了,const 关键字告诉编译器: 程序员让这个变量是不可改变的,你帮我检查下,程序员写的代码中有没有试图改变这个变量的语句,如果有,你就报错,提示。
如下代码:程序员定义了pi为const的变量后,又用了pi=3.14*PI;来改变pi的值,编译器很快就帮忙检查出来了,并报错:assignment of read-only variable 'pi'.对只读的变量'pi'做了赋值操作。
为了让变量pi具有常量的性质,把const写在变量pi前,就不能再对变量pi进行assignment赋值了。
const出现以前,常量是用#define PI 3.14来定义出来的。PI就是常量。这个常量不用申请内存空间,直接写在代码里。
const出现以后,变量也可以表现的像常量一样,不可改变。但是变量与其它变量很多性质相似的,但有变量与其它变量的区别:不能被特定语句修改,另外也不再申请存储空间。
这就是const背后的策略了。
在使用const这个关键字的时候,写法是自由的。比如对于一个float pi;如果我们害怕它被修改,那么就可以将其改为const float pi 或者 float const pi.
const float pi 和float const pi 一摸一样,都是限定pi是read-only的,在程序的任何位置都不能使用pi=0.314对pi重新给值的,编译器只要查询到"pi="的语句就会报错,只要查询到"&pi"就会报错,因为这两句都可以对pi的值进行修改,编译器就是要实现变量pi的read-only,所以就会报错。但是,pi本质上是一种放在内存中的变量,如果能够绕过编译器的规则,其实还是可以修改pi的值的。加上const之后,编译器就会对pi这个变量的操作用规则过滤一下,这个规则就是:禁止所有能够改变bi值的语句,发现就报错。
有一种特殊的变量,指针。const一个指针也稍微有点特殊。
对于一个指针float * fpi,如果我们想将其设置为readonly,就比较麻烦一些。
比如有变量 float pi =3.14,而指针 float * fpi=π
这个fpi 就牵涉到两个值:fpi=pi的地址&pi,*fpi=pi的值3.14,即pi的地址和pi的值。
指针fpi与变量pi的地址和和pi的值相关。
所以,对于const指针fpi,应该该设计怎样的策略呢?我们的目的是为了让fpi=pi&,*fpi=3.14后,就不能再做修改了,所以,要限制"fpi=xxx"语句出现,也要限制"*fpi=xxx"出现,这些语句一旦出现,编译器会帮忙报错的。
但是,由于很多时候,其实没有必要既限制 "fpi=xxx"又限制"*fpi=xxx"。
有时候要求fpi只能指向当前的地址,不能改变,但是当前地址的内容*fpi是可以改变的。这时候就要单独限制"fpi=xxx"语句的出现了,不在限制“*fpi=xxx"语句。
单独限制"fpi=xxx"就行了,有时候单独限制"*fpi=xxx"就行了。从的地址发快递,地址不变,快递物品可变。
有时候要求对当前地址的内容*fpi不能改变,但是这个地址是可以改的,无论地址如何该,但是就是不能改变地址的内容,这时候就需要单独限制"*fpi=xxx"了,不再限制"fpi=xxx". 比如快递员根据地址投递包裹,快递员可以改变包裹的地址,但是不能改变包裹里面的内容。
对于平台来说,一个包裹,一个地址都是不能变的,包裹内容和地址都不能改变。内容和地址都不能变,就是语句“fpi=xxx"和语句"*fpi=xxx"都要限制。
总结:由于指针牵涉到两个值:地址和地址内的内容,所以,const用在指针上,就比较复杂一些,有三种情况:
1 地址不变,内容可变。限制"fpi=xxx",不限制"*fpi=xxx",对于float * fpi;使用const关键字的写法: float * const fpi;要把const插入在*号后面,fpi前面,编译器就会帮忙限定地址不可改变。
2 地址可变,内容不变。不限制"fpi=xxx",限制"*fpi=xxx",对于float * fpi;使用const的写法:float const * fpi;或者const float * fpi. 要把const放到*前面的任意位置,编译器就会帮忙限定内容不可改变。
3 地址不变,内容不变。限制"fpi=xxx",限制"*fpi=xxx",对于float * fpi;使用const的写法:const float * const fpi 或者 float const * const fpi. 要把const 分别插入到*前和*后fpi前,整个语句里插入连个const,编译器就会帮忙限定地址和内容都不可改变。
补充:
当连着申请多个变量时,如 int *p1,p2,*p3时,也可以用const一次性将多个变量设置为read-only.
再使用const前, 我们先要明确一下p1,p2,p3的具体类型。
int *p1,p2,*p3中,p1是整型指针,p2时整型,p3是整型指针。编译器认为(*p1)是个整体,(*p3)是个整体。
所以:
const int *p1,p2,*p3;时,(*p1)是read-only的,p2是read-only的,(*p3)时read-only的
const int * const p1,p2,*p3时,(*const p1)是一个整理,是read-only的,而这个整体内部 const p1代表着p1也是read-only的; p2 和(*p3)还和上句一样。
编译器的规则:凡是出现*,就把*和p1之间都看作一个整体。
不出现*时,就只有一个意思,如:
const int p1,p2,p3,p4,表示p1,p2,p3,p4都是read-only的变量。
const使变量不可改变,不用申请内存,还能像变量一样去debug,去让编译器检查正确性。
不可改变:满足设计需求。
不申请内存:降低内存消耗,提高程序效率
去debug:方便调试,提高代码编写效率
让编译器检查正确性:编译器可帮忙避免一些bug.
最后,const还扩充了指针的使用方式,用#define 去定义常量时,指针就没办法不可改变了,指针也就没办法规定地址或者内容单独不可改变。