1.定义
const修饰的数据类型是指向类型,常类型的变量或对象的值是不能被更新的。
2.目的
const关键字推出的初始目的,是为了取代预编译指令,消除它的缺点同时继承预编译指令的优点。
3.作用
3.1 const 修饰变量
在平时我们写代码时当我们定义了一个整形变量a可以通过赋值来改变a所对应的值,当我们在整形变量a前加上const修饰结果如何呢?请看如下代码:
如图所示,在对const修饰变量进行赋值的时候会报错。
所以我们可以得出一个结论:const修饰的变量,不能直接被修改。
但是想要修改变量的值也不是毫无办法,我们可以通过指针的方式间接修改变量a:
#include<stdio.h>
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修饰的变量不能作为数组的一部分。
3.2 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 放在左边。
3.3 const 修饰指针
鉴于一些小伙伴可能还没有学习到指针,做一下简短科普:
我们经常会从别人口中或书中听到指针和指针变量,有时会把他们混淆,那我们来了解一下它们之间的区别。
指针和指针变量的区别:
1)指针就是地址。
2)指针变量用来保存地址。
指针也就是地址,存放在指针变量中,指针变量的大小为4个字节。
现在再通过类比的方式来认识一下对于指针变量左值右值的问题:
整型变量:
#include<stdio.h>
int main()
{
int x;
x = 100;//x的空间,变量的属性,左值。
int y = x;//x的内容,数据的属性,右值。
}
指针变量:
#include<stdio.h>
int main()
{
int *p= &a;
p = &b;//p指针变量空间,左值
q = p; //p内部的地址数据,右值
}
任何一个变量名。在不同的应用场景之中,代表不同的含义。(详情见注释)
解引用:
#include<stdio.h>
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<stdio.h>
{
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<stdio.h>
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<stdio.h>
int main()
{
int a = 10;
int *p = &a;
const int * const p = &a;
*p = 100;//报错
p = 100;//报错
}
分析:const 同时修饰 p 和*p所以,既不能修改 p 指向的变量也不能修改 p 的内容。
小经验:当把一个类型限定并不严格的变量,赋给一个类型限定严格的变量,编译器一般不会报错,反之,可能会报错。
用文字可能理解起来比较困难,接下来我们用一段代码来理解:
#include<stdio.h>
int main()
{
int a = 10;
const int *p = &a;
int *q = p;
}
当我们编译后,会出现如下告警:
但是当我们写成如下形式,这个告警就消失了:
#include<stdio.h>
int main()
{
int a = 10;
int *p = &a;
const int *q = p;
}
原因:第一种写法可以用通过*q来修改a,不安全,所以会报错。但是第二种写法 *q 被 const 修饰了,那么 a 的值也就无法被修改,这个代码也比较安全。
3.4 const 修饰函数的参数
const 修饰符也可以修饰函数的参数,当不希望这个参数值在函数体内被意外改变时可以使用const 来修饰。
例如:void Fun(const int *p);
当然这样理解可能不到位,还是借助代码(详情可以看注释):
#include<stdio.h>
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);//调用函数
}
作用:告诉编译器函数参数在函数体内不能被改变,从而防止了使用者的无意或错误的修改。
3.5 const 修饰函数的返回值
const 修饰符也可以修饰函数的返回值,返回值不可以被改变。
上代码:
#include<stdio.h>
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 修饰函数的返回值不可以被改变。