【C语言】刨根问底 - 深剖const关键字

简介: 【C语言】刨根问底 - 深剖const关键字

1.定义

const修饰的数据类型是指向类型,常类型的变量或对象的值是不能被更新的。




2.目的

const关键字推出的初始目的,是为了取代预编译指令,消除它的缺点同时继承预编译指令的优点。




3.作用


3.1 const 修饰变量


在平时我们写代码时当我们定义了一个整形变量a可以通过赋值来改变a所对应的值,当我们在整形变量a前加上const修饰结果如何呢?请看如下代码:

a0b3000083724371adac55fad52f4165.png


如图所示,在对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修饰的变量能否作为数组的一部分?


e2c715d23740416da82f14ba47438353.png


我们可以看到,const修饰的变量在vs2022中是不能编译过去的,即在标准C的环境下报错,但是在gnu标准扩展下是可以编译过去的,这里就不详细展示了。因为我们平时写的代码大多都是标准C,所以还是向标准看齐。


结论:const修饰的变量不能作为数组的一部分。




3.2 const  修饰数组


const修饰数组,和const修饰常量相同,数组元素无法进行二次赋值,让我们看一段代码:

48602a01630e476bb353df8ce0aaf872.png


当我们定义一个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)指针变量用来保存地址。


6e56d9dd274740f0a538f2c15f54affb.png


指针也就是地址,存放在指针变量中,指针变量的大小为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;//报错
}



当我们编译后,会出现如下告警:


6326d3f57f7f440585a61084ed36bb1e.png

但是当我们写成如下形式,这个告警就消失了:

#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来接受会出现告警:  


b6c54c6dd39349cf9291e334727e0185.pngb6c54c6dd39349cf9291e334727e0185.pngb6c54c6dd39349cf9291e334727e0185.pngb6c54c6dd39349cf9291e334727e0185.png

b6c54c6dd39349cf9291e334727e0185.png

解决方案:在int *p前加上 const ,就如图中的代码所示。


接下来,在 p 接收了函数的返回值后,用*p = 100;来修改返回值会出现报错。


所以可以证明:const 修饰函数的返回值不可以被改变。




结语


以上就是笔者总结的一些关于 const 的一些使用注意点和作用,在写代码的过程中使用 const 关键字也是个不错的编码习惯。合理的使用更可以使代码更加严谨,更加规范。



相关文章
|
19天前
|
C语言
C语言学习记录——模拟字符串相关函数(strcpy、strlen、strcat)相关知识-const、typedef
C语言学习记录——模拟字符串相关函数(strcpy、strlen、strcat)相关知识-const、typedef
13 1
|
1天前
|
C语言
【C语言】:const的使用方法
【C语言】:const的使用方法
4 0
|
1月前
|
编译器 C语言 C++
从C语言到C++⑤(第二章_类和对象_中篇)(6个默认成员函数+运算符重载+const成员)(下)
从C语言到C++⑤(第二章_类和对象_中篇)(6个默认成员函数+运算符重载+const成员)
12 1
|
1月前
|
算法 编译器 API
C语言易混淆、简单算法、结构体题目练习、常见关键字总结-1
C语言易混淆、简单算法、结构体题目练习、常见关键字总结
|
1月前
|
存储 C语言
【C语言】数据:数据类型关键字
【C语言】数据:数据类型关键字
|
1月前
|
存储 编译器 C语言
c语言中static关键字的作用
c语言中static关键字的作用
|
1月前
|
编译器 C语言 C++
从C语言到C++⑤(第二章_类和对象_中篇)(6个默认成员函数+运算符重载+const成员)(中)
从C语言到C++⑤(第二章_类和对象_中篇)(6个默认成员函数+运算符重载+const成员)
16 0
|
1月前
|
编译器 C语言 C++
从C语言到C++⑤(第二章_类和对象_中篇)(6个默认成员函数+运算符重载+const成员)(上)
从C语言到C++⑤(第二章_类和对象_中篇)(6个默认成员函数+运算符重载+const成员)
12 0
|
1月前
|
编译器 C语言 C++
从C语言到C++③(第一章_C++入门_下篇)内联函数+auto关键字(C++11)+范围for+nullptr(下)
从C语言到C++③(第一章_C++入门_下篇)内联函数+auto关键字(C++11)+范围for+nullptr
24 0
|
1月前
|
存储 安全 编译器
从C语言到C++③(第一章_C++入门_下篇)内联函数+auto关键字(C++11)+范围for+nullptr(上)
从C语言到C++③(第一章_C++入门_下篇)内联函数+auto关键字(C++11)+范围for+nullptr
29 0