很多刚开始学 C 语言的同学,打开教材看到“变量”“常量”这两个词时,通常会觉得特别简单,但真正写起代码又会发现很多说不清的问题:为什么有的值能改,有的值一改就报错?为什么int a;只能是声明,而int a = 10;才算定义?const和#define都能定义常量,它们到底差在哪里?
这些疑惑并不是因为你笨,而是因为教材往往把一个完整的知识链条拆得太碎。本篇文章就把 C 语言中最基础也最重要的“变量”和“常量”系统地讲一遍,从概念、语法、代码示例,到编译错误、常见误区和工程上的推荐写法,一步步拆开。内容面向零基础入门读者,也适合刚上完 C 语言课、想回头把基础补扎实的同学。
1. 为什么要先搞懂变量与常量
1.1 程序本质上是在处理数据
写程序不只是“敲代码”那么简单。无论你写的是一个计算器、一个学生成绩管理系统,还是一个嵌入式设备的控制程序,程序的核心工作其实只有两件事:处理数据和控制流程。
处理数据就必然涉及一个问题:数据放在哪里?怎么表示?怎么修改?怎么保持不变?
C 语言作为一门接近底层的高级语言,它要求程序员对数据的存储方式有清晰的认识。变量和常量正是 C 语言中表示数据的最基本单位。如果这两个概念没有理清楚,后续学习指针、数组、函数参数传递、结构体时,都会感觉像在迷雾里走路。
1.2 从一道最简单的题目说起
来看一个非常经典的 C 语言入门题目:
输入两个整数,交换它们的值,并输出。
这个题目需要解决两件事:
- 用什么来保存用户输入的两个整数?——用变量。
- 交换过程中需不需要一个临时空间?——需要,这个临时空间也是一个变量。
如果不用变量,这个题目几乎无法完成,因为你没有“容器”来存放输入的数据。变量就像一个贴了标签的盒子,你可以随时查看盒子里的内容,也可以把旧内容倒掉,放入新内容。
而常量则像一个“焊死”的值。它始终如一,用来保证程序中某些关键数值不会被意外修改。
1.3 学习目标和前置知识
本文会围绕以下几个目标展开:
- 理解变量是什么,变量名、变量值、内存地址之间的关系;
- 掌握变量的定义、声明、初始化和赋值;
- 理解 C 语言中常量的分类,包括字面常量、
const常量、#define宏常量; - 能看懂编译错误中的常见提示,比如“未定义标识符”“不能向常量赋值”;
- 了解变量作用域和生命周期等进阶概念,为后续学习打基础。
前置知识很简单:电脑上有一个能编译 C 语言的开发环境,比如 Dev-C++、Visual Studio、Code::Blocks,或者你只是用命令行配合 GCC 也可以。如果你还没有安装环境,也不用急,可以先阅读代码逻辑,等环境就绪后再把代码运行一遍。
2. 环境准备与第一个完整例子
2.1 C 语言开发环境说明
C 语言对开发环境的要求并不高。无论你用的是 Windows、Linux 还是 macOS,只要能找到一个 C 编译器,就可以开始学习。
常见选择:
| 环境 | 说明 |
|---|---|
| Dev-C++ | 适合 Windows 初学者,安装简单,自带编译器 |
| Visual Studio | Windows 下功能强大,社区版免费,需要安装“使用 C++ 的桌面开发”工作负载 |
| Code::Blocks | 跨平台,配置相对轻量 |
| GCC + VS Code | Linux/macOS 下常用,灵活度高 |
| 在线编译器 | 如果暂时不想装环境,可以用在线 C 语言编译器快速验证小段代码 |
版本说明:C 语言有多种标准,如 C89/C90、C99、C11、C17 等。本文示例尽量兼容主流编译器,以 C99 之后的常见用法为主。不同编译器对个别语法支持有差异,但变量、常量这些核心语法在所有标准中差别不大。
2.2 一个最小的“变量+常量”示例
下面这段代码展示了变量和常量最简单的配合使用。
#include <stdio.h> int main() { // 定义一个整型变量 int age = 18; // 修改变量的值 age = 20; // 输出变量的当前值 printf("age = %d\n", age); return 0; }运行结果:
age = 20这段代码包含几个关键信息:
int是数据类型,表示“整数类型”;age是变量名;= 18是初始化操作,在变量定义的同时给变量赋初值;- 第二行的
age = 20;是赋值操作,它把变量age中的值从 18 改成 20; printf中的%d是格式占位符,表示以十进制整数形式输出后面的变量age的值。
这里有一个初学者很喜欢问的问题:为什么第一次写int age = 18;叫“初始化”,第二次写age = 20;叫“赋值”?
答案要从 C 语言的语法上说:
- 在变量定义时用
=给出初始值,称为“初始化”; - 在变量定义之后,再次使用
=修改值,称为“赋值”。
区别在于“变量是否已经存在”。第一次创建盒子时放入东西,是初始化;盒子已经存在,重新放入新东西,是赋值。
2.3 如何编译运行
如果你使用 GCC,可以把上面的代码保存为variable.c,然后执行:
gcc variable.c -o variable ./variable如果是 Windows 下的 GCC,则执行:
gcc variable.c -o variable.exe variable.exe如果编译通过,说明你的环境没有问题。接下来就可以进入正题,深入理解变量与常量。
3. 变量:数据的“命名容器”
3.1 什么是变量
从专业角度来说,变量是 C 语言中用于存储数据的一块内存区域,这块区域有一个名字,叫做变量名;里面存放的数据叫变量的值。
从生活的角度理解,变量就像是一个贴了标签的盒子:
- 盒子有一个名字,通过名字可以找到这个盒子;
- 盒子里可以放东西,也就是数据;
- 盒子里的东西可以更换;
- 盒子有大小限制,不能什么都往里塞。
C 语言里的变量在使用前必须先“声明类型”,这也是 C 语言与 Python 等动态语言很不一样的地方。在 C 语言中,每个变量都有固定的数据类型,这个类型决定了:
- 变量占多大的内存空间;
- 变量的值能表示什么范围;
- 变量可以进行哪些运算。
比如:
int number = 100; // 整数类型变量 float price = 19.98; // 单精度浮点类型变量 char letter = 'A'; // 字符类型变量在定义时,int、float、char被称为“类型说明符”。它们告诉编译器:请给这个变量分配适合的大小和解释方式。
3.2 变量名与标识符命名规则
变量名属于 C 语言中的“标识符”。C 语言规定,标识符只能由字母、数字、下划线组成,而且不能以数字开头。此外,关键字(如int、return、if等)不能作为变量名。
合法的变量名示例:
age score_1 _student count2 maxValue不合法的变量名示例:
int 2count; // 错误:不能以数字开头 int my-name; // 错误:不能包含减号 - int int; // 错误:int 是关键字这里给零基础读者推荐一套通用的命名习惯:
- 变量名尽量“见名知意”,比如用
studentCount表示学生数量,而不是用s; - 多单词组合可以用小驼峰形式,如
totalScore; - 或者用下划线分隔,如
total_score; - 常量的命名通常全部大写,单词之间用下划线连接,如
MAX_SIZE。
3.3 变量的定义与声明
在 C 语言中,“定义”和“声明”经常被放在一起说,但它们并不完全相同。
- 定义:为变量分配内存空间,并可以同时初始化。例如:
int count; count = 10;这里int count;就是定义。它告诉编译器:我需要一块能够保存int类型数据的空间,名字叫count。
- 声明:告诉编译器“这个变量是存在的,但它的定义在别处”。声明通常出现在多文件项目或头文件中,配合
extern关键字使用。
例如在一个源文件中定义:
int globalScore = 90;在另一个文件中声明并使用:
extern int globalScore; void printScore() { printf("%d\n", globalScore); }初学者先不需要深入多文件编程,但需要知道一个核心结论:变量必须先定义(或者先声明)再使用。如果没有定义就直接使用,编译器会报“未声明的标识符”错误。
错误示例:
#include <stdio.h> int main() { number = 100; // 错误:number 未定义 printf("%d\n", number); return 0; }编译时会看到类似error: 'number' undeclared的提示。解决方法是先定义变量,再赋值使用。
3.4 变量的初始化与赋值
定义变量之后,变量里面存的是什么值?答案是“不确定的”。在 C 语言中,如果只定义变量而不进行初始化,变量的值是内存中残留的随机值,称为“未初始化的值”。
例如:
#include <stdio.h> int main() { int value; printf("value = %d\n", value); return 0; }这段代码在编译时可能会产生一个警告,运行结果也是不确定的,可能是 0,可能是某个奇怪的大数字。这在不同的编译器、不同系统下结果不同。
所以在实际开发中,强烈建议在定义变量时就给一个初始值:
int value = 0;这样程序的行为才是确定可控的。你可能会在某些竞赛代码中看到“变量不初始化直接用”的写法,但那是压缩代码的风格,不适合正常的程序设计和工程开发。
3.5 整型变量的内存大小与取值范围
C 语言中,同一数据类型的字节大小可能因平台不同而有差异。以常见的 32 位 / 64 位桌面系统为例:
char // 通常 1 字节,用来存放字符或小整数 short // 通常 2 字节 int // 通常 4 字节 long // Windows 下通常 4 字节,Linux 64 位下通常 8 字节 long long // 通常 8 字节int的典型取值范围是-2147483648 ~ 2147483647,也就是约 -21 亿到 21 亿。如果你要存储更大的整数,就需要用long long。
从入门角度来说,你暂时不必死记所有取值范围,但一定要知道一件事:给变量赋值时,不能超过该类型所能容纳的范围。超过范围会造成“溢出”,得到的结果可能会让你摸不着头脑。
#include <stdio.h> int main() { int a = 2147483647; a = a + 1; printf("a = %d\n", a); return 0; }在有符号整型溢出时,结果可能是-2147483648,这在逻辑上非常反直觉。处理实际项目时,必须仔细考虑类型范围。
4. 常量:程序中不可变的值
4.1 什么是常量
常量,就是在程序运行过程中值不会被修改的量。和变量不同,常量一旦确定,就不能在代码中重新赋值。
C 语言中的常量可以分为两类:
- 字面常量:直接写在代码中的值,如
10、3.14、'A'、"hello"; - 符号常量:通过
#define或const定义的名字,用来代表一个固定的值。
下面分别举例说明。
4.2 字面常量
所谓“字面常量”,就是见字如面,看到代码里的这个数,它的值就是它本身。
#include <stdio.h> #define PI 3.14159 int main() { int count = 100; // 100 是整型字面常量 float ratio = 0.618; // 0.618 是浮点型字面常量 char grade = 'A'; // 'A' 是字符常量 printf("count = %d\n", count); printf("ratio = %f\n", ratio); printf("grade = %c\n", grade); printf("PI = %f\n", PI); return 0; }运行结果:
count = 100 ratio = 0.618000 grade = A PI = 3.141590在printf格式控制中,%d输出整数,%f输出浮点数,%c输出字符。初学者需要特别注意:float在printf中通常以%f输出,double也使用%f。这些细节在写代码时会经常遇到。
4.3#define宏常量
#define是 C 语言的预处理指令,它可以在编译之前做文本替换。通俗地说,它让编译器在所有出现PI的地方,把它替换成3.14159。
#include <stdio.h> #define MAX_STUDENTS 50 #define PI 3.14159 int main() { int studentCount = 30; printf("最多允许的学生数量:%d\n", MAX_STUDENTS); printf("当前学生数量:%d\n", studentCount); printf("PI 的值:%f\n", PI); return 0; }注意:
#define通常写在文件的顶部;- 行末不需要分号;
- 宏名习惯上使用大写字母,以便和普通变量区分;
#define是在编译之前由预处理器完成的纯文本替换,不是变量,因此不能对它赋值。
如果对#define定义的宏常量赋值,编译时会报错。例如:
#define MAX 100 int main() { MAX = 200; // 错误! return 0; }编译后会提示类似assignment of read-only location或直接说MAX不是可修改的左值。原因很简单:宏在编译前已经被替换成了100,你写的其实是100 = 200;,这显然不合法。
4.4const常量
const是 C 语言中的一个类型修饰符,用来限定一个变量“只读”。在变量定义前加上const,之后就不能再给这个变量赋值了。
#include <stdio.h> int main() { const int MAX_SCORE = 100; printf("最高分:%d\n", MAX_SCORE); // MAX_SCORE = 200; // 错误:不能向 const 变量赋值 return 0; }如果去掉上面那行注释,编译时会看到错误信息:
error: assignment of read-only variable 'MAX_SCORE'这说明编译器保护了const变量,不允许修改。
4.5#define和const的区别
这是很多入门同学容易混淆的地方,也是面试题中的常见考点。两者都能定义“常量”,但本质完全不同。
| 对比项 | #define | const |
|---|---|---|
| 本质 | 预处理指令,进行文本替换 | 类型修饰符,定义只读变量 |
| 编译器处理阶段 | 编译前预处理 | 编译阶段 |
| 是否有数据类型 | 没有数据类型 | 有具体数据类型 |
| 是否占用内存 | 通常不分配变量空间(替换为字面量) | 占用内存,是一个变量 |
| 是否支持类型检查 | 不支持 | 支持 |
| 能否用于定义数组大小 | 可以 | C89 中不行,C99 之后部分情况可以 |
简单来说,#define做的是一种“无脑替换”,const更像是一个“受到保护的变量”。
在 C 语言中,const常量并不能像 C++ 里那样在“所有情况”下都作为编译期数组大小。例如某些旧版本的 C 编译器不允许:
const int N = 10; int arr[N]; // 有些编译器会报错,要求数组大小是常量表达式虽然 C99 之后引入了变长数组(VLA),部分编译器允许这样写,但从可移植性和工程稳健性出发,当你要定义数组大小或者switch的case标签时,优先使用#define或者枚举常量,而不是const变量。这一点非常实用。
4.6 枚举常量
除了前面提到的两种方式,C 语言还提供了一种专门用于定义整型常量的语法:enum。
#include <stdio.h> enum Weekday { MON = 1, TUE, WED, THU, FRI }; int main() { enum Weekday today = WED; printf("today = %d\n", today); return 0; }运行结果:
today = 3枚举的特点:
- 枚举中第一个成员如果没有指定值,默认从 0 开始;
- 如果
MON = 1,后面的成员自动依次加 1,所以TUE是 2,WED是 3; - 枚举可以增强代码可读性,让魔法数字变成有含义的名字;
- 枚举成员是编译期常量,可以用在
case标签和数组大小定义中。
5. 变量的作用域与生命周期
5.1 局部变量
定义在函数内部的变量称为局部变量,它的作用域从定义处开始,到当前代码块结束。离开作用域之后,这个变量就不能再被访问了。
#include <stdio.h> void test() { int x = 10; // 局部变量 printf("x = %d\n", x); } int main() { // 在这里无法访问 x,因为 x 定义在 test 函数内部 test(); return 0; }如果你试图在main函数中直接使用x,编译器会报错:'x' undeclared。
5.2 全局变量
定义在函数外部、头文件或源文件顶部的变量称为全局变量。它的作用域从定义处到文件结束,理论上可以被定义之后的所有函数访问。
#include <stdio.h> int globalValue = 100; // 全局变量 void printGlobal() { printf("globalValue = %d\n", globalValue); } int main() { printf("main 中访问:%d\n", globalValue); globalValue = 200; printGlobal(); return 0; }运行结果:
main 中访问:100 globalValue = 200全局变量虽然方便,但在实际项目中要谨慎使用。因为多个函数都可以修改同一个全局变量,随着代码量增长,程序的执行流程会变得难以追踪。经验法则是:能用局部变量解决的就不要用全局变量。
5.3 变量的生命周期
- 局部变量的生命周期从进入代码块开始,到离开代码块结束。
- 全局变量的生命周期从程序启动时开始,到程序结束时才结束。
- static 局部变量比较特殊,它虽然只在定义的函数内部可见,但生命周期是整个程序运行期间。static 局部变量只会被初始化一次,函数每次调用时会保留上一次修改的值。
#include <stdio.h> void counter() { static int count = 0; // 只初始化一次 count++; printf("count = %d\n", count); } int main() { counter(); counter(); counter(); return 0; }运行结果:
count = 1 count = 2 count = 3如果把static去掉:
void counter() { int count = 0; count++; printf("count = %d\n", count); }运行结果就会变成:
count = 1 count = 1 count = 1这个例子可以帮助你直观理解“生命周期”和“初始化次数”的差异。static局部变量在 C 语言中经常用于统计函数被调用的次数、实现状态保持等场景。
5.4 变量发生“遮蔽”怎么办
当内层代码块中定义的变量和外层变量同名时,内层变量会遮蔽外层变量。
#include <stdio.h> int main() { int num = 10; { int num = 20; printf("内层 num = %d\n", num); } printf("外层 num = %d\n", num); return 0; }运行结果:
内层 num = 20 外层 num = 10这种写法在语法上合法,但可读性较差。项目开发中建议避免同名变量嵌套,否则容易产生逻辑混乱。
6. 核心数据类型详解
6.1 整数类型家族
C 语言中的整数类型包括char、short、int、long、long long,并可以配合signed和unsigned使用。
#include <stdio.h> int main() { signed int a = -100; // 有符号整数,可以为负 unsigned int b = 200; // 无符号整数,不能为负 char c1 = 'A'; // 字符 unsigned char c2 = 200; // 无符号字符 printf("a = %d\n", a); printf("b = %u\n", b); printf("c1 = %c\n", c1); printf("c2 = %u\n", c2); return 0; }关于无符号数,必须提醒一个经典坑点:无符号数和有符号数混用的时候,可能会发生意想不到的结果。例如:
#include <stdio.h> int main() { unsigned int a = 10; int b = -20; if (a + b > 0) { printf("结果是正数\n"); } else { printf("结果是负数\n"); } return 0; }这里因为 C 语言会进行隐式类型转换,b可能被转换成无符号整数参与运算,导致a + b的结果变成一个很大的正整数,从而输出“结果是正数”。初学者不必现在就把所有转换规则背熟,但心里要有这根弦:有符号和无符号混用是有风险的。
6.2 浮点类型家族
浮点类型用于存储小数,包括float、double、long double。它们的区别主要是精度和内存大小不同。
#include <stdio.h> int main() { float f = 3.14f; double d = 3.14159265358979; printf("f = %f\n", f); printf("d = %lf\n", d); return 0; }说明:
- 浮点常量默认是
double类型;如果希望它作为float,可以在数字后面加f,例如3.14f; printf中输出float和double都可以使用%f;double的精度高于float,实际工程中更常用double;- 不要用浮点数直接比较是否相等,因为浮点数在二进制中可能无法精确表示,例如
0.1 + 0.2并不精确等于0.3。
6.3 字符类型
C 语言的char类型本质上是一个占用 1 字节的整数类型,它可以存放字符,也可以存放小范围的整数。
#include <stdio.h> int main() { char ch = 'A'; printf("ch = %c\n", ch); printf("ch 的 ASCII 码 = %d\n", ch); return 0; }运行结果:
ch = A ch 的 ASCII 码 = 65这段代码说明了字符在内存中的存储方式:字符实际上是以 ASCII 码值存储的,'A'对应整数 65。所以char可以参与整数运算,例如ch + 1会得到'B'。
7. 变量与常量使用的常见错误
7.1 没有定义变量就直接使用
int main() { printf("%d\n", number); return 0; }错误提示:error: 'number' undeclared。
解决思路:在使用变量前先定义并初始化。
int main() { int number = 0; printf("%d\n", number); return 0; }7.2 向常量赋值
const int MAX = 100; MAX = 200; // 编译错误或者:
#define MAX 100 MAX = 200; // 预编译后变成 100 = 200,编译错误解决思路:要想设计一个允许修改的变量,就不要定义成const。常量的目的是保护数据不被修改,因此赋值操作必须从代码中移除。
7.3 变量未初始化就使用
变量未初始化时,它的值不确定。这在大型项目中可能导致偶发性 bug,表现为“有时候结果正确,有时候结果错误”。比如:
int total; total = total + 10; // 错误:total 原本的值未知解决思路:定义时立刻初始化。
int total = 0; total = total + 10;7.4 命名冲突或使用关键字
int main = 0; // 不推荐,虽然有的编译器允许,但容易造成混乱 int return = 1; // 错误:return 是关键字解决思路:避开 C 语言关键字,同时避免与库函数同名的变量,例如不要定义名为printf或main的变量。
7.5 数据类型范围溢出
给int类型的变量赋一个超过其表示范围的值,结果不可预期。初学者计算时尤其容易遇到,例如计算斐波那契数列、阶乘、累加和时。
解决思路:预估数值范围,必要时使用long long或unsigned long long。
long long sum = 1000000000LL;这里的LL后缀表示这是一个long long类型的常量。
8. 完整实战:变量与常量综合训练
下面设计一个综合小练习,把变量、常量、输入输出、简单计算结合起来。目的是加深对变量与常量区别的理解。
题目:输入一个圆的半径,计算圆的周长和面积。要求圆周率使用常量定义。
8.1 代码实现
#include <stdio.h> #define PI 3.14159 int main() { double radius = 0.0; double circumference = 0.0; double area = 0.0; printf("请输入圆的半径:"); scanf("%lf", &radius); circumference = 2 * PI * radius; area = PI * radius * radius; printf("半径 = %.2f\n", radius); printf("周长 = %.2f\n", circumference); printf("面积 = %.2f\n", area); return 0; }8.2 代码说明
PI使用#define定义,代表圆周率常量。由于它不占变量空间,只能读取,不能修改,非常适合表示数学常数;radius、circumference、area都是double类型的变量,分别存放用户输入的半径、计算得到的周长和面积;scanf是输入函数,%lf表示读取一个double类型的数据;变量名前必须加&取地址符,初学者经常会漏掉这一点;%.2f表示输出浮点数时保留两位小数。
8.3 运行演示
假设输入半径为 5,运行效果如下:
请输入圆的半径:5 半径 = 5.00 周长 = 31.42 面积 = 78.54如果把PI重新赋值会怎样?由于PI是宏常量,你根本写不出“对 PI 赋值”的合法代码。这就是常量在程序设计中的意义:它把那些不应该被修改的数值固定下来,减少程序出错的可能。
8.4 扩展思考
如果把上面代码中的#define PI 3.14159改成:
const double PI = 3.14159;程序也可以正常工作。这两种写法都可行,但要注意它们的技术差异。若把PI用于数组大小,#define更加合适;若希望获得类型检查和调试器支持,const更有优势。选择哪一种,取决于具体场景。
9. C 语言程序设计中的常见困惑解答
9.1int a;和int a = 0;有什么区别
int a;只是定义了一个变量,编译器为它分配空间,但空间里的值是随机的。int a = 0;则是在定义时把变量 a 的值初始化为 0。
在项目开发中,定义变量时尽量随手初始化,这可以避免很多由“未知初始值”引发的问题。
9.2 变量名能是中文吗
部分编译器(如较新的 GCC、Visual Studio)允许使用中文字符作为标识符,但 C 语言标准本身并不保证这一点。为了保证代码可移植性,不推荐用中文作为变量名。
9.3 为什么有的代码数组下标会写成10,有的写成MAX_SIZE
10是字面常量,MAX_SIZE是符号常量。直接使用“魔法数字”会让代码难以维护。比如代码里出现了几十个10,你很难判断哪些代表“数组长度”,哪些代表“超时秒数”。使用有意义的符号常量可以极大提升可读性。
#define MAX_SIZE 10 #define TIMEOUT 10虽然两者的值都是 10,但含义完全不同。符号常量在工程中非常重要。
9.4const变量和普通变量在内存中有什么区别吗
从汇编角度看,const变量不一定被放在只读区域,具体取决于编译器优化。但在 C 语言语法层面,const变量是“只读”的,不允许赋值。不要依赖“const 变量一定在只读内存”这种假设,而应该把它当作一种代码约束机制。
9.5 C 语言里有没有“字符串变量”
C 语言本身并没有单独的字符串类型。字符串通常用字符数组来表示,例如:
char name[20] = "CSDN";这里的name是一个字符数组变量,可以修改里面的字符。字符数组的知识会在后续“数组”和“指针”专题中展开。你现在只需要建立概念:C 语言中的字符串不是一个基本数据类型,它是“字符的集合”。
10. 编程练习与自测
建议在阅读完本文后,亲自动手完成下面几道练习题。
练习一:输出个人信息
定义三个变量分别保存年龄、身高、性别首字母,并输出。
#include <stdio.h> int main() { int age = 20; double height = 175.5; char gender = 'M'; printf("年龄:%d\n", age); printf("身高:%.1f\n", height); printf("性别:%c\n", gender); return 0; }练习二:温度转换
编写程序,把摄氏温度转换为华氏温度。转换公式为:
F = C * 9 / 5 + 32要求C使用变量保存,9、5、32可以理解为公式中的固定常数。
#include <stdio.h> int main() { double celsius = 100.0; double fahrenheit = celsius * 9 / 5 + 32; printf("摄氏温度:%.2f\n", celsius); printf("华氏温度:%.2f\n", fahrenheit); return 0; }运行结果:
摄氏温度:100.00 华氏温度:212.00练习三:观察 const 的报错
自己写一段对const变量赋值的代码,观察编译器报错信息。这个练习的目的是让你对编译错误不再恐惧。
#include <stdio.h> int main() { const int DAYS_PER_WEEK = 7; DAYS_PER_WEEK = 8; // 故意写错,观察编译结果 printf("%d\n", DAYS_PER_WEEK); return 0; }编译器会告诉你的核心错误是:不能给只读变量赋值。记住这个错误样式,以后看到类似提示就不会慌。
11. 变量的进阶概念预览
变量这个主题看似基础,但往深处走,会牵连出很多 C 语言的重要内容。如果你已经掌握了上面的内容,可以继续了解以下进阶方向。
11.1 指针变量
指针变量是 C 语言的灵魂,也是很多初学者畏惧的内容。其实指针变量也是一种变量,只不过它存的不是普通数值,而是另一块内存的地址。
int number = 10; int *p = &number; // p 是一个指针变量,存放 number 的地址理解“变量是带名字的内存盒子”之后,再理解指针就顺理成章了:普通变量里面装的是数据,指针变量里面装的是地址。
11.2 数组变量
数组是一种可以存储多个相同类型数据的变量。它相当于一排编号连续的内存空间。
int scores[5] = {90, 85, 78, 92, 88};数组是一种复合数据类型,需要结合循环、下标来访问。它是 C 语言中最重要的数据结构之一。
11.3 结构体变量
结构体允许你把不同类型的数据打包成一个整体,可以用来模拟现实中的复杂对象。例如可以用结构体来保存一个学生的学号、姓名、成绩。
struct Student { int id; char name[20]; double score; };11.4 变量与函数的传参
函数参数本质上也是变量。C 语言中默认采用“值传递”方式:函数内部修改形参,不会影响实参。如果需要函数内部修改外部变量,可以传递指针。这一块概念需要结合指针学习。
12. 工程实践中的常见疑问与回答
12.1 多人协作时变量命名怎么统一
在团队项目中,变量命名不能只凭个人喜好。建议在项目开始前约定一个统一的命名规范,例如:
- 局部变量尽量短小清晰:
count、index、sum; - 全局变量使用带有前缀的命名,如
g_config表示全局配置; - 常量全部大写:
MAX_BUFFER_SIZE; - 不允许出现
a、b、tmp1、data2这样的无意义命名(除非在极小的教学示例中)。
命名看似是小问题,但却是代码可维护性的基石。
12.2 全局变量越少越好吗
是的。在一个中大型系统中,大量的全局变量会让函数之间的耦合关系变得隐蔽,你很难确定某个值是在哪里被修改的。调试时只能逐个函数检查,效率极低。推荐的思路是:
- 优先使用局部变量;
- 需要在多个函数间共享的状态,优先考虑通过参数传递;
- 真正需要全局可见的配置,建议用常量或专门的配置结构体管理。
12.3 使用printf输出变量时,格式符写错了会怎样
格式符与变量类型不匹配是未定义行为。比如用%d输出double类型变量,可能会输出一个毫无意义的值。编译器通常不会报错,但运行结果完全不可控。
编写代码时,要养成长时间审视格式符的习惯:
int对应%d;unsigned int对应%u;long long对应%lld;float和double使用%f;char作为字符输出用%c,作为整数输出用%d;- 字符串使用
%s。
12.4 变量名和常量名冲突怎么办
如果在一个函数里同时存在全局常量和局部变量同名,局部变量会遮蔽全局常量。这种行为在编译时通常会有警告,但这说明代码设计已经不够清晰了。实际开发中,建议给常量加上统一的前缀或全部大写,和普通变量形成明显区分。
13. 调试时如何观察变量值
学习 C 语言的早期阶段,很多同学遇到“程序结果不对”,首先选择“盯着代码看半天”,这其实效率很低。更科学的做法是学会用调试器观察变量。
如果你使用 Visual Studio 或 Code::Blocks,可以在代码某一行设置断点,然后单步执行,在调试窗口中查看变量的当前值。如果使用 VS Code + GCC,可以配置调试任务,也可以达到类似效果。
在调试器里你重点关注三件事:
- 变量当前的值是多少;
- 这个值是在哪一行代码被修改的;
- 变量的内存地址是多少。
下面这段代码可以作为调试练习:
#include <stdio.h> int main() { int a = 5; int b = 10; int sum = 0; sum = a + b; printf("sum = %d\n", sum); return 0; }在sum = a + b;这一行设置断点,单步执行后观察a、b、sum的变化,你会对变量的读写过程形成非常直观的记忆。
14. 写给零基础读者的建议
如果你是完全零基础的小白,学到这里可能会觉得信息量有点大。这是正常的,变量和常量虽然是最基础的概念,但它牵扯到数据类型、内存、作用域、编译原理等很多方面,不可能只看一遍就完全掌握。
给你几条实用的学习建议:
第一,**先把代码敲一遍。**看十遍不如敲一遍。不要复制粘贴,每个字符都自己敲一遍,熟悉代码的手感。
第二,**刻意制造错误。**比如把int写成in、把变量名写错、把%d写成%s,然后观察编译器和运行结果会怎么反馈。这能帮你快速建立“错误现象”和“错误原因”之间的关联。
第三,**用调试器观察变量值变化。**这是初学者最容易跳过的一步,也是进步最快的一步。程序出错时,调试器能告诉你每一行代码执行后变量的真实状态。
第四,**不要死记硬背。**类型、取值范围、格式符这些内容不是靠背就能掌握的,而是靠写错、改正、总结,再写错、再改正这样反复循环才能内化。
第五,**先关注核心语法,不追求一次看懂所有细节。**C 语言中变量相关的知识可以延伸得非常深,例如左值、右值、类型转换、存储类说明符等,这些概念学到中级阶段再理解也来得及。零基础阶段只要做到“能定义、能赋值、能输出、能看懂常见报错”,就已经完成阶段性目标。
如果你希望按照系统路线深入学习,可以沿着下面这个顺序推进:
- 变量与常量(本文)
- 运算符与表达式
- 分支结构(if / switch)
- 循环结构(while / for / do-while)
- 数组
- 函数
- 指针
- 结构体与联合体
- 文件读写
- 递归、链表等进阶数据结构
学校慕课平台上常见的翁恺 C 语言课程也基本遵循类似的知识脉络。你可以在学完每一个章节后,找几道 Online Judge(比如洛谷、PTA 等平台)上的简单题练手,把学到的语法转换成解题能力。
最后补充一个很多入门者都容易忽略的认知:C 语言的变量和常量语法本身并不难,难的是建立“程序 = 数据 + 算法”的思维。变量和常量就是你管理数据的起点。把这一块基础打好,再去看指针、数组、函数、结构体,会发现它们都是“变量”这个概念在不同维度上的延伸。下一次当你写下一个int count = 0;时,如果能清楚地意识到这行代码在做哪些事情,这篇文章的目的就达到了。