C之我见——const关键字浅析

it2025-01-31  10

目录

0. 引言1. const定义的标识符不是常量1.1 使用const修饰的标识符定义数组1.2 使用指针变量修改const修饰的“常量” 2. const关键字的常见声明2.1 定义只读变量2.2 指向常量的非常量指针2.3 指向非常量的常量指针2.4 指向常量的常量指针2.5 指向常量的常量指针的指针2.6 const关键字修饰函数参数 3. 小结

本文使用运行环境如下: 操作系统:Ubuntu Linux 18.04 64 bit 编译环境:gcc version 7.5.0 (Ubuntu 7.5.0-3ubuntu1~18.04)

0. 引言

C/C++语言支持const关键字,const意为“常数,不变的”,C++中可用于定义真正的常量,但在C语言中使用const修饰的标识符并不是真正意义上的常量,为只读变量,本文讨论C语言const关键字的常见用法。

本文组织架构:

1. const定义的标识符不是常量

1.1 使用const修饰的标识符定义数组

const.c

#include <stdio.h> int main() { int arr[2] = {0}; return 0; }

以上代码段只在main函数中定义了一个长度为2的int数组,C规定数组在编译时必须确定长度,这里显式地用2这个字面常量定义数组长度,编译运行是没有问题的。

$ gcc const.c $ ./a.out

如果使用const修饰的变量定义数组,编译是否通过?

#include <stdio.h> int main() { const int i = 2; int arr[i] = {0}; return 0; }

编译:

$ gcc const.c const.c: In function ‘main’: const.c:7:5: error: variable-sized object may not be initialized int arr[i] = {0}; ^~~ const.c:7:19: warning: excess elements in array initializer int arr[i] = {0}; ^ const.c:7:19: note: (near initialization for ‘arr’)

显然,程序在编译阶段就停止了,const修饰的标识符i在编译阶段无法确定其值,它不是个常量。

1.2 使用指针变量修改const修饰的“常量”

const.c

#include <stdio.h> int main() { const int i = 2; printf("after, i = %d\n", i); int *p = (int *)&i; *p = 3; printf("after, i = %d\n", i); return 0; }

编译运行:

$ gcc const.c $ ./a.out before, i = 2 after, i = 3

可以看到使用const修饰的“常量”i的值被改变了,如果此时想直接通过赋值修改i的值

#include <stdio.h> int main() { const int i = 2; printf("before, i = %d\n", i); int *p = (int *)&i; *p = 3; printf("after, i = %d\n", i); i = 4;/* 编译报错,const修饰的变量量不能被赋值 */ printf("after2, i = %d\n", i); return 0; }

编译:

$ gcc const.c const.c: In function ‘main’: const.c:15:7: error: assignment of read-only variable ‘i’ i = 4; ^

编译是不能通过的,编译器提示这是个“只读变量”。

因此,C语言中使用const关键字修饰的标识符本质是只读变量,不能显式给const修饰的变量赋值,const关键字修饰的标识符并不是真正意义上的常量。

2. const关键字的常见声明

2.1 定义只读变量

cosnt int a = 1;/* 等价于int const a = 1; */

常用的只读变量的定义方式,不希望a被修改时添加const修饰。

2.2 指向常量的非常量指针

int a = 1; const int *p = &a; 从标识符p开始往左看,声明中有*号,则p为指针;标识符p旁边没有const关键字,则指针的指向可以被改变(可以对p赋值);const关键字贴近数据类型int,则指针指向的数据不可变(不能对*p赋值);

2.3 指向非常量的常量指针

int a = 1; int * const p = &a; 从标识符p开始往左看,声明中有*号,则p为指针;const关键字贴近标识符p,则指针的指向不可被改变(不能对p赋值);数据类型int旁边没有const关键字,则指针指向的数据可以被改变(可以对*p赋值);由于指针的指向不可被改变,p在定义时必须完成初始化,后续再对p赋值将编译失败。

2.4 指向常量的常量指针

int a = 1; const int * const p = &a; 从标识符p开始往左看,声明中有*号,则p为指针;const关键字贴近标识符p,则指针的指向不可被改变(不能对p赋值);const关键字贴近数据类型int,则指针指向的数据不可被改变(不能对*p赋值);由于指针的指向不可被改变,p在定义时必须完成初始化,后续再对p赋值将编译失败。

2.5 指向常量的常量指针的指针

int a = 1; const int * const p = &a; const int * const *pp = &p; 从标识符pp开始往左看,声明中有*号,则pp为指针;声明中有两个*号,则pp为指针的指针;将const int * const *pp分为两部分:const int * const 和 *pp,可以发现pp是const int * const类型的指针,而const int * const 类型本身就是个指针类型,这个类型指针指向的数据不可被改变、指针的指向不可被改变;const int * const为“指向常量的常量指针”类型,则const int * const *为“指向常量的常量指针的指针”类型;pp本身只是用于容纳const int * const类型的指针,因此pp本身不必须在定义时完成初始化,后续可以对pp赋值,pp的值为const int * const类型,因此不能对*pp赋值,也不可对**pp赋值。

2.6 const关键字修饰函数参数

void *memcpy(void *dest, const void *src, size_t n); 从src标识符往左看,声明中有*号,因此src为指针;src标识符旁没有const关键字,则指针的指向可以被改变(可以对src赋值);const关键字贴近数据类型void *,则指针指向的数据不可被改变(不能对*src赋值);

从以上对memcpy函数形参src的分析中可知,使用const关键字修饰形参指针,可以保护源数据在函数运行过程中不被意外修改,从而增强程序的健壮性。

3. 小结

本文首先从两个方面论证了const关键字在C语言中只能定义只读变量,接着分析了几种C语言中常用的const声明,在适当的位置使用const关键字能够增强程序的健壮性。

最新回复(0)