10 C 语言常量详解:常量定义方式、字面量常量、标识符常量、#define 与 const 区别

10 C 语言常量详解:常量定义方式、字面量常量、标识符常量、#define 与 const 区别

1 初识常量

1.1 什么是常量

常量(Constant)是指在程序运行过程中其值不会发生变化的数据量。常量通常用于表示固定不变的数值或逻辑控制中所需的固定值,例如数学常数 π(圆周率)、程序中用于判断条件的标志值等。

使用常量可以提高程序的可读性和可维护性,同时避免因误修改关键数据而导致程序错误。

1.2 常量的分类

根据定义方式的不同,常量可以分为以下两大类:

字面量常量:直接在代码中以固定值形式出现的常量,无需事先声明即可直接使用。常见的字面量常量包括:

整型常量:如 666浮点型常量:如 66.66字符型常量:如 'A'字符串型常量:如 "Hello, World!"布尔型常量:如 true、false例如:int number = 666; 在这个语句中,666 是一个整型字面量常量。

标识符常量:通过一个标识符(即名称)来引用的常量值。这种形式的常量需要在使用前进行定义或声明,常见的定义方式包括:

使用 #define 宏定义的常量。使用 const 关键字定义的常量。枚举类型(enum)定义的常量。

📚 扩展:魔法数字(magic numbers)

魔法数字是指程序中直接出现的、没有明确解释的字面数值,如 3、100 等。它们通常用于表示状态、类型或特定逻辑含义,但由于缺乏命名说明,会使代码难以理解和维护。应使用命名常量替代魔法数字,以提高代码的可读性和可维护性。

标识符常量通过命名提高了代码的可读性和可维护性,同时避免了 “魔法数字” 带来的理解困难。

2 常量的定义方式

字面量常量可以直接在程序中使用,无需定义。而标识符常量需要通过特定方式定义,常见的定义方式包括宏定义、const 关键字等。通常约定,常量名使用全大写字母命名,以便与变量区分。

2.1 使用 #define 定义常量

基本概念

#define 是预处理指令,用于定义宏。在常量定义中,它用于创建一个符号常量,即用一个标识符代表一个固定的值。

使用 #define 定义的常量在程序预处理阶段会被替换为对应的值,这一过程称为宏替换,即用宏体替换所有宏名。

语法格式

#define 常量名 常量值

// 例如:

#define PI 3.14159

#define MAX_SIZE 100

#define 语句不以分号结尾。若误加分号,分号将被视为常量值的一部分,可能导致语法或逻辑错误。#define 通常应写在函数外部,不建议在 main 函数内部使用。尽管某些编译器支持此类用法,但为了代码的可移植性和规范性,应避免这样做。

案例演示

首先,我们创建一个名为 “Chapter3_constant” 的文件夹,并用 VS Code 打开。接着,在该文件夹中新建一个名为 “1define.c” 的源文件,具体操作如下所示:

然后编写如下代码:

#include

// 使用宏定义定义符号常量 PI,代表 3.14,用于表示圆周率

#define PI 3.14

int main()

{

double area; // 定义一个双精度浮点型变量,用于存储圆的面积

double r = 1.2; // 定义圆的半径,初始值为 1.2

// 根据圆的面积公式:面积 = π * 半径 * 半径,计算圆的面积

area = PI * r * r;

// 输出圆的面积,保留到小数点后两位

printf("圆的面积是(保留到小数点后两位) : %.2f\n", area);

// 将圆的半径扩大为原来的二倍,即变为 2.4

r = 2.4;

// 再次根据圆的面积公式计算圆的面积

area = PI * r * r;

// 输出此时圆的面积,由于半径扩大为原来的二倍,面积会扩大为原来的四倍

printf("圆的面积是(保留到小数点后两位) : %.2f\n", area);

return 0;

}

程序在 64 位 Windows 系统(VS Code 环境)中的运行结果如下:

执行时机:预处理阶段

对于 #define 指令,预处理器在预处理阶段会执行一项关键的文本替换操作:逐行扫描整个源代码文件,查找所有与宏名称匹配的标识符,并将其直接替换为宏定义的值。这一过程称为宏替换。

需要特别注意的是:

宏替换是纯文本替换,不涉及语法分析或类型检查。替换后的代码才会交由编译器进行后续的编译处理。因此,如果宏替换后产生了语法错误或类型不匹配问题,这些错误将在编译阶段才会被发现。

以上面这个程序为例,#define PI 3.14 这行代码定义了一个名为 PI 的宏,其替换文本为 3.14。在预处理阶段,预处理器会遍历源代码,查找所有的 PI,并将它们替换为 3.14。这一过程在编译之前(即预处理阶段)就已经完成,所以编译器实际看到的代码是已经完成替换的源代码。随后,编译器会对这些经过替换的代码进行编译,最终生成目标代码或可执行文件。

为了更直观地观察预处理过程,我们可以在终端窗口中输入以下预处理指令:

gcc -E .\1define.c -o .\1define.i

执行该指令后,会生成一个名为 1define.i 的文件。通过查看该文件的内容,我们可以发现代码中的 PI 已经被全部替换成了对应的数值 3.14。 这一现象表明,#define 定义的宏常量在预处理阶段就被进行了文本替换。

注意事项:纯文本替换

由于 #define 指令的宏替换只是纯粹的文本替换,所以在使用时需要注意可能发生的意外情况,比如宏展开导致的运算符优先级问题。例如:

#include

// 纯粹的文本替换

#define PI 3 + 2

int main()

{

int i = PI * 2; // 3 + 2 * 2 = 7

printf("i = %d\n", i); // i = 7 而不是 10

return 0;

}

程序在 64 位 Windows 系统(VS Code 环境)中的运行结果如下:

上面这个程序的最终输出结果是 i = 7 而不是 i = 10,原因在于宏常量 PI 是通过纯文本替换实现的:

在预处理阶段,PI 被直接替换为 3 + 2,因此表达式 PI * 2 实际上变为 3 + 2 * 2。由于乘法运算符(*)的优先级高于加法运算符(+),所以表达式将按 3 + (2 * 2) 的顺序进行计算,最终结果就为 7。

2.2 使用 const 定义常量

基本概念

const 是 C 语言中的关键字,用于声明一个具有固定值的常量变量。使用 const 定义的常量在声明时必须进行初始化,且其值在程序运行期间不可更改。

该特性自 C89(也称 ANSI C 或 C90)标准引入,并在后续的 C99、C11 等标准中得以保留和增强。

与 #define 宏常量相比,const 常量具有明确的数据类型,因此在编译阶段会参与类型检查,有助于提前发现潜在的错误,提升代码的安全性与可靠性。因此,在现代 C 编程中,推荐优先使用 const 来定义常量。

语法格式

使用 const 定义常量的基本格式如下:

const 数据类型 常量名 = 常量值; // 注意后面有分号 ;

// 例如:

const double PI = 3.14159;

const int MAX_SIZE = 100;

// 在程序中,PI 和 MAX_SIZE 都是常量,其值在初始化后不可修改

注意:语句以分号 ; 结尾。

案例演示

首先,我们创建一个名为 “3const.c” 的源文件,然后编写如下代码:

#include

// 使用 const 关键字定义常量 PI

const double PI = 3.14; // const 定义常量时,需要加分号

int main()

{

double area; // 声明变量 area 用于存储圆的面积

double r = 1.2; // 声明并初始化变量 r 为圆的半径

// 计算圆的面积并赋值给变量 area

area = PI * r * r;

// 使用 printf 函数输出圆的面积,保留两位小数

printf("圆的面积是(保留到小数点后两位) : %.2f\n", area);

return 0;

}

程序在 64 位 Windows 系统(VS Code 环境)中的运行结果如下:

执行时机:编译阶段

与 #define 不同,const 常量是在编译阶段进行处理,而非预处理阶段。

为了验证这一点,我们可以在终端窗口中输入以下预处理指令:

gcc -E .\3const.c -o .\3const.i

执行该指令后,通过查看生成的 3const.i 文件内容,会发现代码中的 PI 并没有被替换成对应的数值,而是依然以常量的形式存在。 这表明,const 常量的处理是发生在后续的编译阶段,而不是像 #define 那样在预处理阶段就进行文本替换。

注意事项:不可变性

在 C 语言里,使用 const 声明的变量具有不可变性,即其值在初始化后不能被修改。因此,必须在声明时对其进行初始化,否则后续无法通过赋值修改其值,这是强制要求。

未初始化的后果:

如果未初始化的 const 变量未被使用,编译器可能不会报错(但可能会警告 “未使用的变量”)。如果尝试使用未初始化的 const 变量(如读取或赋值),编译器会直接报错,如下所示:

作用域与生命周期

const 常量的定义位置决定了它的作用域和生命周期。常见的定义位置有两种:

1. 在 main 函数外部定义(全局作用域)

作用域:该常量在整个文件中都可被访问(从定义位置开始),也可通过外部声明在其他文件中使用。生命周期:具有静态存储期,即在程序启动时分配内存,程序结束时释放内存。

#include

// 在 main 函数外面定义 const 常量

const int globalConst = 100;

void printGlobalConst()

{

// 在这个函数里面能够访问 globalConst

printf("Global const: %d\n", globalConst); // 100

}

int main()

{

// 在 main 函数里面能够访问 globalConst

printf("Main: Global const: %d\n", globalConst); // 100

// 调用函数

printGlobalConst();

return 0;

}

程序在 64 位 Windows 系统(VS Code 环境)中的运行结果如下:

2. 在 main 函数内部定义(局部作用域)

作用域:该常量只能在定义它的函数内部访问。生命周期:具有自动存储期,即在函数调用时创建,函数返回时销毁。

#include

// 定义一个函数,尝试访问 main 函数中的局部 const 常量(这将不会成功)

void tryAccessLocalConst()

{

// 以下代码会导致编译错误,因为 localConst 在此作用域中未定义

printf("Trying to access localConst: %d\n", localConst); // 错误:localConst 未定义

}

int main()

{

// 在 main 函数里面定义 const 常量

const int localConst = 200;

// 只能在 main 函数内部访问 localConst

printf("Local const in main: %d\n", localConst);

// 调用另一个函数

tryAccessLocalConst(); // 这个函数将尝试(但无法)访问 localConst

return 0;

}

在 tryAccessLocalConst 函数中,尝试访问 main 函数中定义的局部 const 常量 localConst 会导致编译错误。这是因为 localConst 是在 main 函数内部定义的,其作用域仅限于 main 函数内部,其他函数无法访问。

关于作用域、生命周期的详细讲解将在后续章节中展开,此处仅为初步了解。

3 #define 与 const 的区别

在 C 语言中,#define 和 const 都可用于定义常量,但它们在执行时机、类型检查、作用域、存储方式、调试支持等方面存在显著差异。以下是对两者的主要区别进行的详细对比。

3.1 执行时机

定义方式执行阶段描述#define预处理阶段

是预处理指令,在编译前由预处理器进行纯文本替换,不涉及语法或语义分析。

例如 #define PI 3.14,预处理器会将代码中所有 PI 替换为 3.4。

const编译阶段是 C 语言关键字,其定义的常量在编译阶段被处理,编译器会进行类型检查和语义分析。例如 const int max = 100; 。

3.2 类型检查

定义方式是否需要类型类型检查#define无需指定类型不进行类型检查,仅做文本替换,可能导致类型不匹配错误。const必须指定类型编译器会进行严格的类型检查,增强程序的安全性和可维护性。

3.3 作用域

定义方式作用域描述#define没有作用域限制宏定义在整个源文件中都有效(除非使用 #undef 取消定义)。const具有块作用域

其作用域取决于定义位置:

1. 在函数内定义的 const 常量仅在该函数内可见。

2. 在函数外定义的则具有文件作用域。

3.4 存储方式

定义方式是否分配内存描述#define不分配内存仅是文本替换,没有实际的存储位置。const编译时分配内存通常被分配在只读数据段,值在整个程序运行期间保持不变。

3.5 定义位置

定义方式定义位置#define通常定义在 main 函数外部,全局生效。const可在函数内部或外部定义,具有更高的灵活性。

3.6 调试便利性与代码可读性

定义方式调试支持可读性#define调试器通常无法识别宏的实际值,调试困难。复杂宏定义可能降低代码可读性。const调试器可识别常量值,便于调试。明确声明类型和值,提高代码清晰度。

3.7 综合对比表

对比维度#defineconst执行时机预处理阶段,纯文本替换编译阶段,进行类型检查和语义分析类型检查无类型信息,不进行类型检查必须指定类型,编译器进行类型检查作用域无作用域,整个源文件有效具有块作用域,取决于定义位置存储方式不分配内存,仅是文本替换编译时分配内存,值在整个程序运行期间不变定义位置通常在 main 函数外定义可在函数内或外定义,灵活性更高调试便利性调试时难以追踪实际值调试器可识别并显示常量值代码可读性复杂宏定义可能降低可读性明确声明类型和值,提升代码清晰度适用场景简单文本替换、全局常量定义需要类型安全、作用域控制或函数内常量定义

📌 建议:

虽然 #define 使用简单且适用于全局常量定义,但由于其缺乏类型检查和作用域控制,容易引入潜在错误。在现代 C 编程中,推荐优先使用 const 来定义常量,以获得更好的类型安全、可维护性和调试支持。

4 编程实操

4.1 计算圆的面积与周长

编写一个 C 程序,分别使用 #define 和 const 定义圆周率 π 的值(如 3.14159)。程序提示用户输入圆的半径,然后使用两种方式分别计算并输出对应的圆面积和周长,结果保留两位小数。

#include

// 使用 #define 定义 π 为宏常量

#define PI_MACRO 3.14159

// 使用 const 定义另一个 π 为常量变量

const double PI_CONST = 3.14159;

int main()

{

// 定义半径变量

double r;

// 提示用户输入半径

printf("请输入圆的半径: ");

// 读取输入

// 注意:读取 double 类型浮点数需要使用 %lf 格式说明符

scanf("%lf", &r);

// 使用宏常量计算

double area1 = PI_MACRO * r * r;

double circum1 = 2 * PI_MACRO * r;

// 输出宏计算结果

printf("\n使用 #define 定义的 π 计算:\n");

printf(" 圆的面积: %.2f\n", area1);

printf(" 圆的周长: %.2f\n", circum1);

// 使用 const 常量计算

double area2 = PI_CONST * r * r;

double circum2 = 2 * PI_CONST * r;

// 输出 const 计算结果

printf("\n使用 const 定义的 π 计算:\n");

printf(" 圆的面积: %.2f\n", area2);

printf(" 圆的周长: %.2f\n", circum2);

return 0;

}

程序在 64 位 Windows 系统(VS Code 环境)中的运行结果如下:

4.2 常量化数据管理

编写一个 C 程序,使用 #define 定义程序的最大用户数和最大连接数,使用 const 定义程序版本号,并在主函数中输出这些常量的值。

#include

/* 常量名通常建议使用全大写字母 */

#define USERS_MAX 100 // 最大用户数

#define CONN_MAX 50 // 最大连接数

int main()

{

const int VERSION = 1; // 程序版本号

printf("程序配置:\n");

printf(" 最大用户数: %d\n", USERS_MAX);

printf(" 最大连接数: %d\n", CONN_MAX);

printf(" 程序版本号: %d\n", VERSION);

return 0;

}

程序在 64 位 Windows 系统(VS Code 环境)中的运行结果如下:

相关推荐

电热水壶干烧坏了如何处理 超详细的拆解修理图文教程
365bet新手开户指南

电热水壶干烧坏了如何处理 超详细的拆解修理图文教程

📅 07-20 👁️ 6199
大王卡一个月多少流量(大王卡一个月用多少话费)
凉爽PPT模板
365bet在线体育

凉爽PPT模板

📅 08-23 👁️ 1934
1.什么是显著性检验? 2.为什么要做显著性检验? 3.怎么做显著性检验?
国产动画电影,何时才能不被“剧情”拖后腿?
365bet新手开户指南

国产动画电影,何时才能不被“剧情”拖后腿?

📅 08-16 👁️ 2965
绡的字典解释
365bet在线体育

绡的字典解释

📅 08-20 👁️ 4794