constconstant的缩写,为常量的意思,所以当变量被const修饰后就变为了常量,
不过这个“常量”并不是真正的常量,而是一个伪常量,后面会解释为什么是一个“伪常量”


一、const 修饰普通变量

1. 例子

int const a = 0; //等价于const int a = 0
a = 100;         //编译报错

编译时会报错,提示a是只读的,是不能被修改的,所以a = 100这句赋值语句是错误的,
a赋值的所有语句一概无法编译通过,如此a变相的就变成了一个内容不能被修改的“常量”

2. 只能通过初始化给值

由于const修改后不能赋值,那么就只能通过初始化来给值了

说到初始化,总觉的int a = 100这种才是初始化,实际上函数传参也是在初始化

int fun(const int a)
{
    printf("a = %d\n", a); //打印初始化的值10
    a = 200;               //这句话无法编译通过,因为const修饰的就不能被赋值
}


int main(void)
{
    fun(10);
    return 0;
}

传参时,其实初始化的过程等价于:const int a = 10;


二、const 修饰指针变量

三种写法,含义不同:

  1. int const *p 等价于 const int *p
    const都是在*前面,p所指向空间不能被修改,但是指针变量p本身可以修改
  2. int * const pp本身不能被修改,但是p所指向的空间可以被修改
    指向的空间可以被修改,但是指向不能发生改变,此时指针变量就是一个“常量”
  3. int const * const p 等价于 const int * const p
    为前面两种情况的综合情况,p本身不能修改,p所指向的空间也不可以
int a = 10;
int b = 20;

int const *p = &a;
*p = 200;  // *p 所指向空间不能被修改,所以编译时这句话会报错
p  = &b;   // p 本身可以被修改,所以这句话没问题
int a = 10;
int b = 20;

int * const p = &a;
*p = 200; // p指向的空间可以被修改
p  = &b;  // p不可以被修改,编译错误
int a = 10;
int b = 20;

int const * const p = &a;
*p = 200; //不能修改,编译报错
p = &b;   //不能修改,编译报错

三、const的实现原理

const实际上是编译器帮忙实现的常量,并不是真正的常量

1. 什么是编译器帮忙实现的?

因为一旦变量被const修饰后,当编译器检查到变量被赋值时,编译器就会报错并终止编译,
程序员必须将赋值语句删除,否则无法编译通过,如此就保证了const所修饰变量不会被修改,变相的让变量变成了常量

事实上const修饰的变量本身是可读可写的,并不是真正的常量,
因为真正常量是只读的,不能被改写

疑问:为什么const修饰的变量不是真正的只读的常量?

答:因为变量空间开辟于.data.bss、堆或者栈中,我们知道.data/.bss/栈/堆都是可读可写的,
所以变量也是可读可写,只是被const修饰后,编译器通过检查并阻止赋值语句的存在,
这使得表面上看起来就像是一个常量了,但实际上并不是真正的只读的常量,所以const修饰所实现的常量其实是“伪常量”

2. 真正的常量

空间为只读的才是真正的常量,前面的课程介绍过,真正的常量都在.text.rodata中,
因为.text.rodata是只读的,那么从只读的.text.rodata中所开辟的常量空间,必然也是只读的

我们可以通过一个例子来验证const实现的常量是一个“伪常量”,本质上它还是一个变量

#include <stdio.h>

int main(void)
{
    int const a = 10;
    int *p = &a;
    *p = 200;   // 根据指针,是可以直接赋值的
    printf("%d\n", a);

    return 0;
}

例子中a虽然是const的,但是通过a的地址去访问a的空间时,确是能修改的,
这就证明了a空间其实是可以被写的,所以a并不是真正的常量,其实它还是一个变量

疑问:aconst修饰后,为什么通过指针去修改,在编译时编译器不报错?

答:按照语法规则,编译器只负责检查a不被修改,但是例子并没有直接修改a
所以编译器就没有报错,说白了编译器被欺骗了


四、const有意义吗

当然意义,针对那些不希望被修改的变量,我们就可以使用const修饰,对变量加以限制,以防止被误改。
在函数传递指针的时候,const用到更是很频繁,有关这一点后面会介绍。


五、单片机中的 code 关键字

这一个并不是C语法的通用关键字,所以一般的C编译器并不能理解这个关键字,
这个开发单片机的C语言才支持的关键字,所以只有针对单片机开发的编译器才支持这个关键字

code也是用来定义常量的,与const的区别是,const只是编译器检查变相实现的,但是本质上还是变量,
const这个关键字属于通用语法,在开发单片机的C语言中也能使用,因为单片机的C也必须准守通用语法规则

不过使用code定义的却真的是一个只能读不能写的常量,因为使用code修饰以后,数据会和代码放到一起,
代码所处的空间只能读不能写,因此属于真正的常量

int code a = 100; //同样的,只能通过初始化给值,不能赋值

a就是和代码放在一起的,a是一个真正的只读的常量空间

当然这里对code的描述并不算100%准确,因为与单片机的存储器结构有关系,
但是大家对单片机并不熟悉,所以这里只能进行简单的描述