目录
1 const的含义
const是constant的简写,是不变的意思,用来限定一个变量为只读(Read-only
)。换句话说,它限定一个变量为只读,并不是修饰常量。
2 const常见的修饰
2.1 修饰普通变量
const int NUM = 10; //与int const NUM等价
NUM = 9; //编译错误,不可再次修改
解释:由于使用了const修饰NUM,使得NUM为只读,因此尝试对NUM再次赋值的操作是非法的,编译器将会报错。也就是const变量需要在开始声明时就赋值
。
blog_test.c:7:6: error: assignment of read-only variable ‘NUM’
NUM = 9; //编译错误,不可再次修改
2.2 修饰数组
const int arr[] = {0,0,2,3,4}; //与int const arr[]等价
arr[2] = 1; //编译错误
解释:const关键字修饰数组,使其元素不允许被改变。
blog_test.c:6:9: error: assignment of read-only location ‘arr[2]’
arr[2] = 1; //编译错误
2.3 修饰指针–const在*右边修饰指针,在左边修饰变量
其实const用在指针的情况是很常见的,所以还是重点分析指针的情况。
(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的指向
blog_test.c:29:5: error: assignment of read-only location ‘*p’
*p = 11; //编译错误,指向的对象是只读的,不可通过p进行改变
(2):const修饰p,指向的对象可变,指针的指向不可变
int a = 9;
int b = 10;
int * const p = &a;//p是一个const指针
*p = 11; //合法,
p = &b; //编译错误,p是一个const指针,只读,不可变
blog_test.c:43:4: error: assignment of read-only variable ‘p’
p = &b; //合法,改变了p的指向
(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放在的右侧,限定了指针本身是只读的,即不可变的。
blog_test.c:54:5: error: assignment of read-only location ‘*p’
*p = 11; //编译错误,指向的对象是只读的,不可通过p进行改变
blog_test.c:55:4: error: assignment of read-only variable ‘p’
p = &b; //编译错误,p是一个const指针,只读,不可变
3 const的使用地方
(1):修饰函数形参
比如一些库函数中:
char *strncpy(char *dest,const char *src,size_t n);//字符串拷贝函数
int *strncmp(const char *s1,const char *s2,size_t n);//字符串比较函数
我们可以知道,源字符串src是只读的,不可变的,而dest并没有该限制。这符合复制的场景,或者说作为程序员得清楚的知道那些变量是只读?读写?
比如:
#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函数内部如果尝试对str进行修改,将会报错,因为我们预先已经限定了str是const
error: assignment of read-only location ‘*str’
str[0] = 'H';
(2):修饰全局变量
我们知道,使用全局变量是一种不安全的做法
,因为程序的任何部分都能够对全局数据进行修改。而如果对全局变量增加const限定符(假设该全局数据不希望被修改),就可以避免被程序其他部分修改。这里有两种使用方式。
(2.1):在a文件中定义,其他文件中使用外部声明。比如:
//a.h
const int ARR[] = {0,1,2,3,4,5,6,7,8,9}; //定义int数组
//b.c
extern const int ARR[]; //注意,这里不能再对ARR进行赋值
//后面可以使用ARR
(2.2):在a文件中定义,并使用static修饰,b文件包含a文件
//a.h
static const int ARR[] = {0,1,2,3,4,5,6,7,8,9}; //定义int数组
//b.c
#include<a.h>
//后面可以使用ARR
4 一个问题?const修饰的变量是真正的只读吗?
我们看下面的例子:
#include <stdio.h>
int main(void)
{
const int a = 2018;
int *p = &a;
*p = 2019;
printf("%d\n",a);
return 0;
}
编译输出:
2019
可以看到,我们通过另外定义一个指针变量(*p),将被const修饰的a的值改变了
?。那么我们不禁要问,const到底做了什么呢?它修饰的变量是真正意义上的只读吗?为什么它修饰的变量的值仍然可以改变?
下面的证据不足以完全正确的解释这个问题,但从侧面说明了一些问题;
#include<stdio.h>
int main(void)
{
int a = 2019;
//const int a = 2019;
printf("%d\n",a);
return 0;
}
无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
结论:相比较而言,发现两者汇编代码一致的(当然这并不能说明所有情况,或者你用其他的编译器clang等会得出不一样的结论,但是在这里我们重点不是这个问题),const关键字告诉了编译器,它修饰的变量不能被改变,如果代码中发现有类似改变该变量的操作,那么编译器就会捕捉这个错误。
5 const帮助程序员提前发现问题,避免不该修改的值被意外地修改
正如上面所看到的,const帮助程序员提前发现问题!!!
,但是你可以通过其他方法改变const限定的值。这是极其重要的思想,
最后总结一些关键点;
(1):const关键字让编译器帮助我们发现变量不该被修改却被意外修改的错误。
(2):不要试图将const数据的地址赋给普通指针,正如上面我们看到的。
(3):对于不该被修改的入参,应该用const修饰,这是const使用的常见姿势。
(4):const修饰的变量只能正常赋值一次。
(5):onst关键字修饰的变量并非真正意义完完全全的只读。
(6):不要忽略编译器的警告,除非你很清楚在做什么。
(7):虽然可以通过某种不正规途径修改const修饰的变量,但是永远不要这么做。
本文摘自公众号“编程珠玑”,你可以关注,阅读原文。
参见:
https://mp.weixin.qq.com/s/66mDpnHgrnenOGcdQ41ySA