相信很多初学者或者学了很久C语言的朋友,对const关键字的理解还是存在着一知半解的状态,今天我就讲讲const关键字的用法及其面试技巧。
先说说技巧,怎么区分const修饰的是什么:
理解的时候,在定义或声明中跳过或者忽略掉数据类型(int,char,double等),const后面的内容即为修饰的内容。
比如:
普通类型:
const int p;//忽略int,即 const后面是p,修饰的即为p
int const p;//同样的分析方法,这句跟上面的分析一致
指针类型:
const int *p1; //去掉int,const修饰的是 *
int const *p2; //去掉int,const修饰的是 *,等同于上面一句
int* const p3; //去掉int,const修饰的是p3,区别于上面那种情况
const int* const p4; //去掉int,const即修饰*,又修饰p4,区别上面两种情况
说完了怎么看const修饰的内容,谈谈怎么理解这些情况:
1、普通变量:
变量名的本质:一段连续内存空间的别名。
理解好这句话对下面的理解很重要。
比如:
定义了 :int p = 10;
则系统会分配4个字节的内存,这块内存我们命名为p,其地址是不确定的,这里我们假定是0x2222,于是在p生存的周期内,这块从0x2222开始的地址的内存空间就被命名为p,p = 10 即向这块内存内写入10。所以说变量名的本质:一段连续内存空间的别名。
接下来,言归正传,对于普通变量,const修饰时,仅仅改变的是变量的属性,即将原来变量的可读写性变成了只读性。
int a1; //a1是可读可写的,既可以被赋值,又可以赋值其他变量
const int a2; //加了const修饰后,a2只能被读取,即可以用于给其他变量赋值,但不可被赋值
a2 = b; //错误,a2不可以被写,重新修改
b = a2;//正确,a2可以被读,传值给另一变量
可以这么理解:普通变量加const修饰是为了防止变量被修改。
但有个例外:如果该变量为全局变量,则不能通过指针修改,但如果是局部变量,则还是可以通过指针修改的。
原因是:全局变量在全局静态区,内容不能被修改,但局部变量本身仍在栈区,可以使用指针修改。
const int a = 10; //const修饰全局变量必须初始化
void fun(){
const int b; //const修饰局部变量可以不初始化
int *p = &b; //可以利用指针指向b对应的内存块,对该内存块修改
*p = 20;
printf("%d , %d",*p,b); //结果为 20 ,20
p = &a; //可以利用指针指向a对应的内存块
*p = 20; //(此句运行出错)但不可以通过指针修改全局变量的内容
a = 20; //(此句运行出错)因为a为全局常量,不能修改
}
总结一下:对于全局常量,不可用指针修改常量,但对于局部常量,仍可以用指针修改常量。
讲完了普通变量,面试大多时候是问指针变量,所以接下来重点讲const修饰指针变量。
利用最前面分析的方法,可以知道const修饰指针变量有三种情况:
①const修饰 * 号
②const修饰变量
③const既修饰 * 号又修饰变量
只要掌握了前两种情况,最后第三种自然明了。
先说说const修饰 * 号,该情况为(局部变量):
const int *p_a;//与int const *p_a;等价
int a = 10;
int b = 20;
p_a = &a;
*p_a = 20; //错误
a =20; //正确
p_a = &b; //正确
当const修饰*号时,表示不能通过指针p_a修改p_a指向的内容。这句话有3层意思(对应上述代码三种情况):
①当p_a指向a时,不能通过指针p_a取*号修改a中的内容
②a中的内容仍可以由a自身修改
③p_a指向可以改变,即p_a可以指向b
可以这么记忆:*号是指针用来对指向的内存操作(读写)的,当对*加了const修饰,意味着*号的操作只剩下只读的功能,也就是只能使用*号来读取指针指向的内容,而失去了写的特性,这点与普通变量的情况类似。
再说说const修饰变量的情况:
void fun(){
int a = 10;
int b = 20;
int* const p_a = &a;
*p_a = 20; //正确
a = 30; //正确
p_a = &b; //错误
}
当const修饰变量p_a时,表示不能改变p_a的内容,即改不了指针的指向。这句话有3层意思(对应上述代码三种情况):
①当p_a指向a时,能通过指针p_a取*号修改a中的内容
②a中的内容仍可以由a自身修改
③p_a指向不可以改变,即p_a不可以修改,指向b或者其他变量
最好的理解便是:指针变量也是变量,变量名的本质:一段连续内存空间的别名。理解好了这句,也就明白了,const修饰了这个变量,也就是修饰了这块内存空间,使得这块内存空间的可读写性改为了可读性,也就是指针指向固定,无法更改。
看到这里,如果前面的都了解了,那么你就会明白了用const修饰,实际上就是将对应的可读写改成了只读性质。
那么,最后一种情况就是,const既修饰*号,又修饰变量的情况了:
void fun(){
int a = 10;
int b = 20;
const int* const p = &a; //必须初始化
*p = 20; //错误
p = &b; //错误
a = 20; //正确
}
如果明白了前两种情况,那么最后这种情况就好理解了(对应上述代码三种情况)
①const修饰*号限定了指针对内存的操作只能为只读
②const修饰变量p限定了指针的指向,p不能指向其他变量
③a或b本身的变量可以自身修改。
至于为什么必须初始化,如果理解了上述内容,那也就明白了。说下不初始化的情况,编译器(我用的VS2017)仍能通过,但有警告,p没有明确初始化,并不知道其指向哪里,由于该变量既不能修改指向,又不能修改指向的内容,所以此时该指针也就没有作用了,这种做法是不好的。
好了,上述内容和情况的分析都是针对局部变量的,如果能充分理解,那么const关键词也就掌握了,至于const修饰全局变量的问题,涉及到内存区域的问题,能理解内存区域也就能充分理解。