一、常量与变量:程序世界的“不变”与“变”
各位同学,欢迎来到《C语言数据类型与运算符精讲》的第一章。今天咱们聊聊程序里最基础的两个概念——常量和变量。
说白了,程序就是处理数据的。有些数据是固定的,比如圆周率π;有些数据是变化的,比如用户的输入。C语言里,前者叫常量,后者叫变量。这个区分,我当年刚学的时候觉得很简单,但后来在项目里踩过不少坑,才发现里面门道挺多。
1.1 常量:那些“雷打不动”的值
常量,就是程序运行过程中值不会改变的量。C语言里常量有三种形式:字面常量、const常量、#define宏常量。咱们一个一个说。
1.1.1 字面常量
字面常量是最直接的——你直接在代码里写出来的值。比如:
int a = 10; // 10 就是整型字面常量
float b = 3.14; // 3.14 是浮点型字面常量
char c = 'A'; // 'A' 是字符型字面常量
char *s = "hello"; // "hello" 是字符串字面常量
这些值写死了,没法改。你总不能把 10 改成 20 吧?那叫改代码,不是改常量。
1.1.2 const常量
const是C语言的关键字,用来声明一个“只读”的变量。注意我的措辞——它本质上还是变量,只是你不能通过赋值去修改它。
const int MAX_SIZE = 1024;
// MAX_SIZE = 2048; // 编译报错!不能修改
我个人习惯用const来定义那些“逻辑上不该变”的值。比如缓冲区大小、配置参数等。为什么不用#define?别急,下面就说。
const int n = 5; int arr[n]; 在C89里是编译不过的。但在C99之后可以了。这个细节,我当年面试时被问过,印象深刻。
1.1.3 #define宏常量
#define是预处理器指令,它在编译之前就把代码里的宏名替换成对应的值。说白了,就是文本替换。
#define PI 3.1415926
#define BUFFER_SIZE 256
int main() {
double area = PI * r * r;
char buffer[BUFFER_SIZE];
return 0;
}
宏常量没有类型检查,就是纯粹的替换。这既是优点也是缺点。优点是灵活,缺点是不安全。比如:
#define MUL(a, b) a * b
int result = MUL(2 + 3, 4); // 实际展开为 2 + 3 * 4 = 14,不是20!
这种坑,我踩过不止一次。所以现在写宏,我习惯把参数都加上括号:#define MUL(a, b) ((a) * (b))。
| 常量类型 | 特点 | 适用场景 |
|---|---|---|
| 字面常量 | 直接写在代码里,简单直观 | 临时值、一次性使用的数值 |
| const常量 | 有类型检查,只读变量 | 需要类型安全的常量,如函数参数 |
| #define宏常量 | 无类型检查,文本替换 | 数组大小、条件编译、简单宏函数 |
1.2 变量:程序里的“活水”
变量,就是值可以改变的量。每个变量都有三个属性:名字、类型、值。定义变量时,你得告诉编译器这三样东西。
1.2.1 变量的定义与初始化
定义变量很简单:
int count; // 定义,未初始化
count = 0; // 赋值
int total = 100; // 定义并初始化
char grade = 'A'; // 字符变量
double pi = 3.14; // 浮点变量
这里有个重要区别:初始化是在定义时给值,赋值是定义之后给值。未初始化的局部变量,它的值是随机的——也就是所谓的“垃圾值”。
int sum;
for(int i = 0; i < 10; i++) {
sum += i; // sum初始值未知!
}
这种错误很难排查,因为每次运行结果可能都不一样。我的建议是:定义变量时就初始化,养成习惯。
1.2.2 变量的作用域
作用域,说白了就是变量在代码里“能管到”的范围。C语言里主要有三种作用域:
- 全局作用域:定义在函数外面的变量,整个文件都能访问。
- 局部作用域:定义在函数或代码块内部的变量,只能在该块内访问。
- 文件作用域:用static修饰的全局变量,只在本文件内可见。
#include <stdio.h>
int global_var = 10; // 全局变量,整个程序可见
void func() {
int local_var = 20; // 局部变量,只在func内可见
static int static_var = 0; // 静态局部变量,生命周期贯穿整个程序
static_var++;
printf("static_var = %d\n", static_var);
}
int main() {
func(); // 输出 static_var = 1
func(); // 输出 static_var = 2
// printf("%d", local_var); // 编译报错!local_var不在作用域内
return 0;
}
你看,static局部变量虽然作用域只在函数内,但它的生命周期是整个程序运行期间。每次调用func,static_var的值都会保留。这个特性,我在写状态机的时候经常用。
1.2.3 变量的生命周期
生命周期,就是变量从创建到销毁的这段时间。和内存分配方式密切相关:
| 存储类型 | 生命周期 | 存储位置 |
|---|---|---|
| 全局变量 | 程序启动到结束 | 数据段(.data或.bss) |
| static局部变量 | 程序启动到结束 | 数据段 |
| 普通局部变量 | 进入函数到退出函数 | 栈 |
| 动态分配变量 | malloc到free | 堆 |
这里有个容易混淆的点:作用域和生命周期不是一回事。全局变量的作用域是整个文件,生命周期是整个程序;static局部变量的作用域是函数内部,但生命周期也是整个程序。你想想看,是不是这个理?
1.3 知识体系总览
说了这么多,咱们用一张图来总结本章的核心内容:
这张图把本章的核心内容串起来了。左边是常量,右边是变量,下面是作用域和生命周期这两个关键概念。你把它记在脑子里,后面学指针、数组、函数的时候,会少走很多弯路。
好了,第一章就到这里。记住:常量是程序的“骨架”,变量是程序的“血肉”。把这两个概念吃透,后面的路就好走了。
公众号:蓝海资料掘金营,微信deep3321