C语言中const关键字

C语言中的const关键字表示这个变量的值在其整个作用域中都保持不变。

但const是在语义层面的检查,是编译器在编译期间,用语法检测来保证数据没有被修改。但在运行时,const修饰的变量和其他未被const修饰的普通变量一样是放在内存中的数据区。必要时可以通过修改内存来达到修改值的目的。

void main(){
	int a = 0;
	a = 1;
}

一个变量有两个维度来描述,一个是地址,另一个是这个地址上的值。上面这段代码,我们在变量a的地址上修改了它的值,由0修改为了1。

void main(){
	const int a = 0;
	a = 1; //编译时不允许通过
}

上面的代码结果是显而易见的,编译无法通过。因为代码虽然没有尝试修改a的地址,但修改了a的值。

既然const关键字限制了变量值的修改,那变量的地址呢?

void main(){
	int a = 0;
	&a = &a; // 编译器不允许通过
}

变量的地址本身是无法修改的,因为同样不被编译器允许,即使这个地址就是它自身的地址。


既然const修饰的普通变量值和地址都无法修改,那数组变量值修改呢?我们先来看没有const修饰的数组。

void main(){
	int a[] = { 0, 0 };
	a[0] = 1;
	a[1] = 1;
}

对于数组也需要分为两个维度,一个是数组的首地址,另一个是数组的值。对数组来说,数组名表示的是数组的首地址,而数组的值和数组的数据类型有关,但最终也表示数值。

void main(){
	const int a[] = { 0, 0 };
	a[0] = 1; // 编译时不允许通过
	a[1] = 1; // 编译时不允许通过
}

上面的数组用const修饰后,结果也很明显,编译无法通过。因为我们无法修改数组的值。

而对于数组的地址,自然也是无法编译通过。

void main(){
	int a[] = { 0, 0 };
	int b[] = { 1, 1 };
	a = b; // 编译无法通过
}

因为数组名代表的就是数组的首地址,“a = b”这条语句相当于把数组b的地址赋值给了数组a 的地址。而普通变量代表的是这个地址上的值,普通变量名的赋值操作是值对值,而数组名的赋值操作时地址对地址。

void main(){
	int* a = 0;
	int b[] = { 1, 1 };
	a = b; // a的值为数组b的地址
}

由于编译器的原因,这其中的细节被隐藏掉了,导致看起来不太习惯。这也是我们对数组初始化只有用for循环或内存操作memset()的原因。

说回const的话题。如果数组存放的是指针呢?能否修改其中的值?

void main(){
	const int* a[] = { 0 };
	int b = 0;
	a[0] = &b;
}

上面的代码是可以通过编译的,最终a[0]的值为整型变量b的地址。但如果尝试修改b的值呢?

void main(){
	const int* a[] = { 0 };
	int b = 0;
	a[0] = &b;
	*a[0] = 1; // 编译器不允许通过
}

上面这段代码编译器不允许通过,因为我们尝试修改了指向整型变量的地址上的值。我们回到普通的变量来看针对指针变量的情况。


首先我们要知道,变量的地址本身是无法修改的,因为同样不被编译器允许。

void main(){
	int a = 0;
	int b = 0;
	&a = &b; // 编译器不允许通过
}

既然const修饰的普通变量的值我们无法修改,那指针变量呢?

void main(){
	const int a = 0;
	int b = 0;
	a = &b; // 编译器不允许通过
}

上面这段代码很清晰,const修饰的整型变量a的值不允许修改,因此将b的地址赋值给a的操作是不被允许的,编译器自然无法通过。

但如果a是指向整型变量的指针类型呢?例如下面这段代码。

void main(){
	const int* a = 0;
	int b = 0;
	a = &b;
}

很奇怪的是,编译器通过了。指针变量a表示的是,这个变量存放着指向整型变量的地址。但这个地址的值是允许改变的。

那什么情况下不允许改变呢?

void main(){
	const int* a = 0;
	int b = 0;
	a = &b;
	*a = 1; // 编译器不允许通过
}

当我们尝试去修改指针变量a所指向的地址的值时,这个情况是不允许的。上面的代码相当于尝试修改变量b的值,但被编译器拒绝了。

当没有const修饰时,b的值可以被正确修改。

void main(){
	int* a = 0;
	int b = 0;
	a = &b;
	*a = 1; // 变量b的值被修改为了1
}

由此看来。const并不是完全限制当前变量的值的修改。

而是,当const修饰的变量是普通变量时,不允许修改其中的值。当const修饰的是指针变量时,不允许修改这个指针指向的地址上的值。

而针对多级指针,也是如此。

void main(){
	const int** a = 0; // 二级指针
	int* b = 0;
	int c = 0;
	a = &b;
	b = &c;
	** a = 1; // 编译不允许通过
}

上面二级指针变量a指向了b,b指向了c。如果没有const修饰,最终变量c的值为1。但当有const修饰时,编译器不允许代码修改最终的变量的值。