const是一个C语言的关键字,在关键字中有着举足轻重的地位,更是在面试过程中被高频提问的一个关键字。它限定一个变量不允许被改变,产生静态作用。并且使用const在一定程度上可以提高程序的安全性和可靠性。另外,对const的深刻理解,也可以在阅读别人代码的时候可以更好地理解代码本身的内容和意图。所以作为一名未来的程序员,还在等什么,赶快让我们探索const关键字吧!
const修饰的数据类型是指向类型,常类型的变量或对象的值是不能被更新的。
const关键字推出的初始目的,是为了取代预编译指令,消除它的缺点同时继承预编译指令的优点。
在平时我们写代码时当我们定义了一个整形变量a可以通过赋值来改变a所对应的值,当我们在整形变量a前加上const修饰结果如何呢?请看如下代码:
如图所示,在对const修饰变量进行赋值的时候会报错。
所以我们可以得出一个结论:const修饰的变量,不能直接被修改。
但是想要修改变量的值也不是毫无办法,我们可以通过指针的方式间接修改变量a:
#include
int main()
{
const int a = 10;
int *p = &a;//定义指针变量p来存放a的地址
printf("before : %d\n", a);
*p=20;//通过解引用的方式给a赋值
printf("after : %d\n", a);
return 0;
}
在vs2022中编译结果如下:
before : 10
after : 20
这就证明了const变量是可以被间接修改的。
看到这儿,大家可能会有一些疑惑,const这个关键字无论是从它本身单词(constant)还是它定义中所说的const修饰的变量或对象无法被修改,都是说它不能被改,这不是通过另外一种形式就被改掉了吗?那这个关键字有啥作用?不要着急,听我一一道来~~~
const修饰变量,我们从本文第一段代码就可以看出,当const修饰的函数被改时,编译器就会直接报错,这个程序本身是不会让你编译过去的,这也就避免了在编译过后运行的时候代码再出错误好许多。在另一个程度上const这个关键字,也有告诉代码的阅读者,或者是其他人这个变量不要改的作用。
总结一下,const修饰变量的作用可以分为两点:
1)让编译器进行修改式检查(语法检测作用)。
2)告诉代码阅读者这个变量不要改,也属于一种“自描述含义”。(在语法层面上的较为弱性的约束作用)。
那么const修饰的变量能否作为数组的一部分?
我们可以看到,const修饰的变量在vs2022中是不能编译过去的,即在标准C的环境下报错,但是在gnu标准扩展下是可以编译过去的,这里就不详细展示了。因为我们平时写的代码大多都是标准C,所以还是向标准看齐。
结论:const修饰的变量不能作为数组的一部分。
const修饰数组,和const修饰常量相同,数组元素无法进行二次赋值,让我们看一段代码:
当我们定义一个const修饰的数组,对它进行二次赋值时,编译程序,发生报错。
所以我们可以得出结论,const修饰数组,数组元素无法进行二次赋值。
解决方案:定义或说明一个只读数组采用如下格式:
int const a[5] = { 1, 2, 3, 4, 5};
count int a[5] = { 1, 2, 3, 4, 5};
//这两个数组其实本质上并没有什么区别。
//const 无论放在 int 左边右边其实都是可以的。
//但是我们一般习惯把 const 放在左边。
鉴于一些小伙伴可能还没有学习到指针,做一下简短科普:
我们经常会从别人口中或书中听到指针和指针变量,有时会把他们混淆,那我们来了解一下它们之间的区别。
指针和指针变量的区别:
1)指针就是地址。
2)指针变量用来保存地址。
指针也就是地址,存放在指针变量中,指针变量的大小为4个字节。
现在再通过类比的方式来认识一下对于指针变量左值右值的问题:
整型变量:
#include
int main()
{
int x;
x = 100;//x的空间,变量的属性,左值。
int y = x;//x的内容,数据的属性,右值。
}
指针变量:
#include
int main()
{
int *p= &a;
p = &b;//p指针变量空间,左值
q = p; //p内部的地址数据,右值
}
任何一个变量名。在不同的应用场景之中,代表不同的含义。(详情见注释)
解引用:
#include
int main()
{
int a = 10;
int *p = &a;
*p = 20;//拿出p中的地址,找到该地址所对应的变量a,把20放到a所对应的空间里。
int b = *p;//找到p变量拿出p变量里的内容,也就是地址,
//通过改地址找到该地址所标识的变量,把a的内容赋给p。
}
通过上段代码和注释我们也可以大致理解,解引用的过程。
总结一下就是,(类型相同)对指针解引用,指针所指向的目标,即上段代码中的*p就是a。
好了打住,科普就到这里,接下来进入正题:const 修饰指针
#include
{
int a = 10;
int *p = &a;
const int *p = &a;
*p = 100;//报错
p = 100;
//int const *p = &a;结果与本代码段相同并无本质上的区别
}
分析:我们可以看到此时 const 在p的左边。const 修饰的是p 。*p也就是p进行解引用,本质上就是a,即p指向的变量。*p的值不能被修改。也就是说,p指向的变量不可以直接被修改。
#include
int main()
{
int a = 10;
int *p = &a;
int * const p = &a;
*p = 100;
p = 100;//报错
}
分析:与第一段代码不同,const这次的位置在p的左边。const 修饰的是p。p为指针变量,p中存放的是a的地址。把100的地址赋给p出现报错。即 p 不可改。也就是说 p 的内容不可直接被修改,换种方法说也可以是 p 的指向不能被修改。
#include
int main()
{
int a = 10;
int *p = &a;
const int * const p = &a;
*p = 100;//报错
p = 100;//报错
}
分析:const 同时修饰 p 和*p所以,既不能修改 p 指向的变量也不能修改 p 的内容。
小经验:当把一个类型限定并不严格的变量,赋给一个类型限定严格的变量,编译器一般不会报错,反之,可能会报错。
用文字可能理解起来比较困难,接下来我们用一段代码来理解:
#include
int main()
{
int a = 10;
const int *p = &a;
int *q = p;
}
当我们编译后,会出现如下告警:
但是当我们写成如下形式,这个告警就消失了:
#include
int main()
{
int a = 10;
int *p = &a;
const int *q = p;
}
原因:第一种写法可以用通过*q来修改a,不安全,所以会报错。但是第二种写法 *q 被 const 修饰了,那么 a 的值也就无法被修改,这个代码也比较安全。
const 修饰符也可以修饰函数的参数,当不希望这个参数值在函数体内被意外改变时可以使用const 来修饰。
例如:void Fun(const int *p);
当然这样理解可能不到位,还是借助代码(详情可以看注释):
#include
void show(const int *_p)//加上const修饰表示a不能直接被改变
{
printf("values: %d\n",*_p);
*_p = 20;//报错 因为const修饰a无法直接被修改
}
int main()
{
int a = 10;
int *p = &a;
show(p);//调用函数
}
作用:告诉编译器函数参数在函数体内不能被改变,从而防止了使用者的无意或错误的修改。
const 修饰符也可以修饰函数的返回值,返回值不可以被改变。
上代码:
#include
const int *GetVal()
{
static int a = 10;
return &a;
}
int main()
{
int *p = GetVal();//出现告警不同的const修饰符
//const int *p = GetVal();
}
分析:主函数中定义了一个指针变量 p 用来接收返回值,GetVal 函数中定义一个 static 修饰的静态局部变量,返回 a 的地址。(使用 static 的原因:static 可以延长局部变量的生命周期,使静态局部变量 a 的空间在函数调用完毕之后,空间不被释放,局部变量的作用域不变。如果这边不加 static 的话,返回的a的地址就是一块空间被释放的地址,是无效的,会出现告警,严重的话会程序崩溃。 )这是用 int *p来接受会出现告警:
解决方案:在int *p前加上 const ,就如图中的代码所示。
接下来,在 p 接收了函数的返回值后,用*p = 100;来修改返回值会出现报错。
所以可以证明:const 修饰函数的返回值不可以被改变。