const关键字到底该怎么用
原文地址:https://www.yanbinghu.com/2019/01/28/7442.html
前言
我们都知道使用const关键字限定一个变量为只读,但它是真正意义上的只读吗?实际中又该如何使用const关键字?在解答这些问题之前,我们需要先理解const关键字的基本使用。本文说明C中的const关键字,不包括C++。
基本介绍
const是constant的简写,是不变的意思。但并不是说它修饰常量,而是说它限定一个变量为只读。
修饰普通变量
例如:
const int NUM = 10; //与int const NUM等价
NUM = 9; //编译错误,不可再次修改
由于使用了const修饰NUM,使得NUM为只读,因此尝试对NUM再次赋值的操作是非法的,编译器将会报错。正因如此,如果需要使用const修饰一个变量,那么它只能在开始声明时就赋值,否则后面就没有机会了(后面会讲到一个特殊情况)。
修饰数组
例如使用const关键字修饰数组,使其元素不允许被改变:
const int arr[] = {0,0,2,3,4}; //与int const arr[]等价
arr[2] = 1; //编译错误
试图修改arr的内容的操作是非法的,编译器将会报错:
error: assignment of read-only location ‘arr[2]’
修饰指针
修饰指针的情况比较多,主要有以下几种情况:
1.const 修饰 *p,指向的对象只读,指针的指向可变:
int a = 9;
int b = 10;
const int *p = &a;//p是一个指向int类型的const值,与int const *p等价
*p = 11; //编译错误,指向的对象是只读的,不可通过p进行改变
p = &b; //合法,改变了p的指向
这里为了便于理解,可认为const修饰的是*p,通常使用*对指针进行解引用来访问对象,因而,该对象是只读的。
2.const修饰p,指向的对象可变,指针的指向不可变:
int a = 9;
int b = 10;
int * const p = &a;//p是一个const指针
*p = 11; //合法,
p = &b; //编译错误,p是一个const指针,只读,不可变
3.指针不可改变指向,指向的内容也不可变
int a = 9;
int b = 10;
const int * const p = &a;//p既是一个const指针,同时也指向了int类型的const值
*p = 11; //编译错误,指向的对象是只读的,不可通过p进行改变
p = &b; //编译错误,p是一个const指针,只读,不可变
看完上面几种情况之后是否会觉得混乱,并且难以记忆呢?我们使用一句话总结:
const放在*的左侧任意位置,限定了该指针指向的对象是只读的;const放在*的右侧,限定了指针本身是只读的,即不可变的。
如果还不是很好理解,我们可以这样来看,去掉类型说明符,查看const修饰的内容,上面三种情况去掉类型说明符int之后,如下:
const *p; //修饰*p,指针指向的对象不可变
* const p; //修饰p,指针不可变
const * const p; //第一个修饰了*p,第二个修饰了p,两者都不可变
const右边修饰谁,就说明谁是不可变的。上面的说法仅仅是帮助理解和记忆。借助上面这种理解,就会发现以下几种等价情况:
const int NUM = 10; //与int const NUM等价
int a = 9;
const int *p = &a;//与int const *p等价
const int arr[] = {0,0,2,3,4}; //与int const arr[]等价
const关键字到底有什么用
前面介绍了这么多内容,是不是都常用呢?const关键字到底有什么用?
修饰函数形参
实际上,为我们可以经常发现const关键字的身影,例如很多库函数的声明:
char *strncpy(char *dest,const char *src,size_t n);//字符串拷贝函数
int *strncmp(const char *s1,const char *s2,size_t n);//字符串比较函数
通过看strncpy函数的原型可以知道,源字符串src是只读的,不可变的,而dest并没有该限制。我们通过一个小例子继续观察:
//test.c
#include<stdio.h>
void myPrint(const char *str);
void myPrint(const char *str)
{
str[0] = 'H';
printf("my print:%s\n",str);
}
int main(void)
{
char str[] = "hello world";
myPrint(str);
return 0;
}
在这个例子中,我们不希望myPrint函数修改传入的字符串内容,因此入参使用了const限定符,表明传入的字符串是只读的,因此,如果myPrint函数内部如果尝试对str进行修改,将会报错:
$ gcc -o test test.c
test.c:6:12: error: assignment of read-only location ‘*str’
str[0] = 'H';
因此,我们自己在编码过程中,如果确定传入的指针参数仅用于访问数据,那么应该将其声明为一个指向const限定类型的指针,避免函数内部对数据进行意外地修改。
修饰全局变量
我们知道,使用全局变量是一种不安全的做法,因为程序的任何部分都能够对全局数据进行修改。而如果对全局变量增加const限定符(假设该全局数据不希望被修改),就可以避免被程序其他部分修改。这里有两种使用方式。
第一种,在a文件中定义,其他文件中使用外部声明,例如:
a.h
//a.h
const int ARR[] = {0,1,2,3,4,5,6,7,8,9}; //定义int数组
b.c
//b.c
extern const int ARR[]; //注意,这里不能再对ARR进行赋值
//后面可以使用ARR
第二种,在a文件中定义,并使用static修饰,b文件包含a文件,例如:
a.h
//a.h
static const int ARR[] = {0,1,2,3,4,5,6,7,8,9}; //定义int数组
b.c
//b.c
#include<a.h>
//后面可以使用ARR
注意,这里必须使用static修饰,否则多个文件包含导致编译会出现重复定义的错误。有兴趣的可以尝试一下。
const修饰的变量是真正的只读吗?
使用const修饰之后的变量真的是完全的只读吗?看下面这个例子:
#include <stdio.h>
int main(void)
{
const int a = 2018;
int *p = &a;
*p = 2019;
printf("%d\n",a);
return 0;
}
运行结果:
2019
可以看到,我们通过另外定义一个指针变量,将被const修饰的a的值改变了。那么我们不禁要问,const到底做了什么呢?它修饰的变量是真正意义上的只读吗?为什么它修饰的变量的值仍然可以改变?
#include<stdio.h>
int main(void)
{
int a = 2019;
//const int a = 2019;
printf("%d\n",a);
return 0;
}
我们分别获取有const修饰和无const修饰的汇编代码。
无const修饰,汇编代码:
.LC0:
.string "%d\n"
main:
push rbp
mov rbp, rsp
sub rsp, 16
mov DWORD PTR [rbp-4], 2019
mov eax, DWORD PTR [rbp-4]
mov esi, eax
mov edi, OFFSET FLAT:.LC0
mov eax, 0
call printf
mov eax, 0
leave
ret
有const修饰,汇编代码:
.LC0:
.string "%d\n"
main:
push rbp
mov rbp, rsp
sub rsp, 16
mov DWORD PTR [rbp-4], 2019
mov eax, DWORD PTR [rbp-4]
mov esi, eax
mov edi, OFFSET FLAT:.LC0
mov eax, 0
call printf
mov eax, 0
leave
ret
我们发现,并没有任何差异!当然这一个例子并不能说明所有的问题。但是我们要知道的是,const关键字告诉了编译器,它修饰的变量不能被改变,如果代码中发现有类似改变该变量的操作,那么编译器就会捕捉这个错误。
那么它在实际中的意义之一是什么呢?帮助程序员提前发现问题,避免不该修改的值被意外地修改,但是无法完全保证不被修改!例如我们可以通过对指针进行强转:
#include<stdio.h>
void myPrint(const char *str);
void myPrint(const char *str)
{
char *b = (char *)str;
b[0] = 'H';
printf("my print:%s\n",b);
}
int main(void)
{
char str[] = "hello world";
myPrint(str);
return 0;
}
运行结果:
my print:Hello world
也就是说,const关键字是给编译器用的,帮助程序员提早发现可能存在的问题。
但是实际中永远不要写这样的代码!
总结
介绍了这么多,关键点如下:
- const关键字让编译器帮助我们发现变量不该被修改却被意外修改的错误。
- const关键字修饰的变量并非真正意义完完全全的只读。
- 对于不该被修改的入参,应该用const修饰,这是const使用的常见姿势。
- const修饰的变量只能正常赋值一次。
- 不要试图将const数据的地址赋给普通指针。
- 不要忽略编译器的警告,除非你很清楚在做什么。
- 虽然可以通过某种不正规途径修改const修饰的变量,但是永远不要这么做。
思考
- 与#define相比,const有什么优点?
- const关键字到底该什么时候用?
微信公众号【编程珠玑】:专注但不限于分享计算机编程基础,Linux,C语言,C++,算法,数据库等编程相关[原创]技术文章,号内包含大量经典电子书和视频学习资源。欢迎一起交流学习,一起修炼计算机“内功”,知其然,更知其所以然。
const关键字到底该怎么用的更多相关文章
- const关键字:终于拥有真正的常量声明语句
本文首发于个人网站:const关键字:终于拥有真正的常量声明语句 你好,今天大叔想和你唠扯唠扯 ES6 新增的关键字 -- const.在说 const 关键字之前,大叔先和你唠唠大叔自己对 cons ...
- PHP的final关键字、static关键字、const关键字
在PHP5中新增加了final关键字,它可以加载类或类中方法前.但不能使用final标识成员属性,虽然final有常量的意思,但在php中定义常量是使用define()函数来完成的. final关键字 ...
- C++中const关键字的使用总结
C++中使用const关键字来修饰常量,下面从两个方面总结:变量和成员函数. 变量:const可以修饰普通变量.指针(数组)和结构体. 1.const修饰普通变量是最简单的情形.这样的用法多为在程序中 ...
- final关键字+const关键字
final关键字 1.如果我们希望某个类不被其它的类来继承(可能因为安全考虑),可以使用final. 例题 <? final class A{} class B extends A{};//会报 ...
- C++学习11 类和new、delete操作符 类与const关键字
如果你是Java.C#.PHP程序员,那么会对 new 非常熟悉,在这些编程语言中,只能通过 new 来创建对象. 在C++中,你可以像定义变量一样来创建对象,如: Student stu; //对象 ...
- C++中的const关键字
http://blog.csdn.net/eric_jo/article/details/4138548 C++中的const关键字的用法非常灵活,而使用const将大大改善程序的健壮性,本人根据各方 ...
- 陈正冲老师讲c语言之const关键字
1.const 关键字也许该被替换为 readolny const是constant的缩写,是恒定不变的意思,也翻译为常量.常数等.很不幸,正是因为这一点,很多人都认为被const修饰的值是常量.这是 ...
- C语言之头文件,static与const关键字
[前言] 最近几个月在做一个C语言代码重构的项目,过程中也让我对之前在书本上学习到的东西有些补充和巩固,在本博中总结记录下,梳理下零碎的知识点和经验也加深印象,书写是为了更好地思考.平时也都是用印象笔 ...
- static和const关键字的作用
static关键字至少有下列n个作用: (1)函数体内static变量的作用范围为该函数体,不同于auto变量,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值: (2)在模块内的stat ...
随机推荐
- .NET Core实战项目之CMS 第十三章 开发篇-在MVC项目结构介绍及应用第三方UI
作为后端开发的我来说,前端表示真心玩不转,你如果让我微调一个位置的样式的话还行,但是让我写一个很漂亮的后台的话,真心做不到,所以我一般会选择套用一些开源UI模板来进行系统UI的设计.那如何套用呢?今天 ...
- 【Zara原创】SqlSugar4轻量级ORM框架的使用指南
前言:sqlSugar出生已经有3年之久了,从1.0到现在的4.x的版本,为了以后方便使用SqlSugar,所以特意花了2个小时来叙述它. 关于SqlSugar 性能:性能最好的ORM之一,具有超越D ...
- Lucene 06 - 使用Lucene的Query API查询数据
目录 1 Query对象的创建(方式一): 使用子类对象 1.1 常用的Query子类对象 1.2 常用的Query子类对象使用 1.2.1 使用TermQuery 1.2.2 使用NumericRa ...
- 【Python3爬虫】猫眼电影爬虫(破解字符集反爬)
一.页面分析 首先打开猫眼电影,然后点击一个正在热播的电影(比如:毒液).打开开发者工具,点击左上角的箭头,然后用鼠标点击网页上的票价,可以看到源码中显示的不是数字,而是某些根本看不懂的字符,这是因为 ...
- 【Java基础】【03运算符&if语句】
03.01_Java语言基础(逻辑运算符的基本用法)(掌握) A:逻辑运算符有哪些 &,|,^,! &&,|| B:案例演示 逻辑运算符的基本用法 注意事项: a:逻辑运算符一 ...
- DriverManager 驱动管理器类简介 JDBC简介(三)
驱动程序管理器是负责管理驱动程序的,驱动注册以后,会保存在DriverManager中的已注册列表中 后续的处理就可以对这个列表进行操作 简言之,驱动管理器,就是字面含义,主要负责就是管理 驱动 概述 ...
- 理解Python闭包概念
闭包并不只是一个python中的概念,在函数式编程语言中应用较为广泛.理解python中的闭包一方面是能够正确的使用闭包,另一方面可以好好体会和思考闭包的设计思想. 1.概念介绍 首先看一下维基上对闭 ...
- 服务器SSH连接时间设置
用SSH客户端连接linux服务器时,经常会出现与服务器会话连接中断现象,造成这个问题的原因便是SSH服务有自己独特的会话连接机制. 解决方案: 1.设置服务器向SSH客户端连接会话发送频率和时间 v ...
- .Net Core 实践 - 使用log4net记录日志(2)
实现目标:将log4net的相关操作封装成一个 .Net Standard类库 demo地址:https://github.com/PuzzledAlien/log4net_demo/tree/mas ...
- webstorm快捷键大全(亲自整理)
Ctrl+/ 或 Ctrl+Shift+/ 注释(// 或者/*…*/ ) Shift+F6 重构-重命名 Ctrl+X 删除行 Ctrl+D 复制行 Ctrl+G 查找行 Ctrl+Shift+Up ...