【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 关键字也是个不错的编码习惯。合理的使用更可以使代码更加严谨,更加规范。



相关文章
|
4月前
|
存储 数据可视化 编译器
【C语言】union 关键字详解
联合体(`union`)是一种强大的数据结构,在C语言中具有广泛的应用。通过共享内存位置,联合体可以在不同时间存储不同类型的数据,从而节省内存。在嵌入式系统、硬件编程和协议解析等领域,联合体的使用尤为常见。理解和正确使用联合体可以使代码更加高效和灵活,特别是在内存受限的系统中。
233 3
【C语言】union 关键字详解
|
4月前
|
编译器 C语言
【C语言】extern 关键字详解
`extern` 关键字在C语言中用于跨文件共享变量和函数的声明。它允许你在一个文件中声明变量或函数,而在其他文件中定义和使用它们。理解 `extern` 的使用可以帮助你组织和管理大型项目的代码。
436 3
|
4月前
|
C语言
【C语言】break 关键字详解
- `break` 关键字用于提前退出循环体或 `switch` 语句的执行。 - 在 `for`、`while` 和 `do-while` 循环中,`break` 可以帮助程序在满足特定条件时退出循环。 - 在 `switch` 语句中,`break` 用于终止 `case` 代码块的执行,避免代码“穿透”到下一个 `case`。 - 注意 `break` 只会退出最内层的循环或 `switch` 语句,确保在嵌套结构中正确使用 `break` 以避免意外的控制流行为。
357 2
|
4月前
|
传感器 安全 编译器
【C语言】enum 关键字详解
`enum`关键字在C语言中提供了一种简洁而高效的方法来定义一组相关的常量。通过使用枚举,可以提高代码的可读性、可维护性,并减少错误的发生。在实际应用中,枚举广泛用于表示状态、命令、错误码等,为开发者提供了更清晰的代码结构和更方便的调试手段。通过合理使用枚举,可以编写出更高质量、更易维护的C语言程序。
199 2
|
4月前
|
缓存 安全 编译器
【C语言】volatile 关键字详解
`volatile` 关键字在 C 语言中用于防止编译器对某些变量进行优化,确保每次访问该变量时都直接从内存中读取最新的值。它主要用于处理硬件寄存器和多线程中的共享变量。然而,`volatile` 不保证操作的原子性和顺序,因此在多线程环境中,仍然需要适当的同步机制来确保线程安全。
180 2
|
4月前
|
存储 编译器 程序员
【C语言】auto 关键字详解
`auto` 关键字用于声明局部变量的自动存储类,其作用主要体现在变量的生命周期上。尽管现代C语言中 `auto` 的使用较少,理解其历史背景和作用对于掌握C语言的存储类及变量管理仍然很重要。局部变量默认即为 `auto` 类型,因此在实际编程中,通常不需要显式声明 `auto`。了解 `auto` 关键字有助于更好地理解C语言的存储类及其在不同场景中的应用。
230 1
|
4月前
|
C语言
【C语言】continue 关键字详解
`continue` 关键字在 C 语言中用于跳过当前循环中的剩余代码,并立即开始下一次迭代。它主要用于控制循环中的流程,使程序在满足特定条件时跳过某些代码。
353 1
【C语言】continue 关键字详解
|
4月前
|
存储 C语言
【C语言】static 关键字详解
`static` 关键字在C语言中用于控制变量和函数的作用域和生命周期。它可以用于局部变量、全局变量和函数,具有不同的效果。理解 `static` 关键字的用法有助于封装和管理代码,提高代码的可维护性和可靠性。
239 3
|
4月前
|
C语言
【C语言】return 关键字详解 -《回家的诱惑 ! 》
`return` 关键字在 C 语言中用于终止函数的执行,并将控制权返回给调用者。根据函数的类型,`return` 还可以返回一个值。它是函数控制流中的重要组成部分。
206 2
|
4月前
|
C语言
【C语言】sizeof 关键字详解
`sizeof` 关键字在C语言中用于计算数据类型或变量在内存中占用的字节数。它是一个编译时操作符,对性能没有影响。`sizeof` 可以用于基本数据类型、数组、结构体、指针等,了解和正确使用 `sizeof` 对于内存管理和调试程序非常重要。
181 2