C语言基础回顾一:从第一个程序到流程控制
一、C语言常见概念C语言是一门面向过程的编译型语言程序从main函数开始执行经过预处理、编译、汇编、链接四个阶段最终生成可执行文件。理解这个流程是排查编译错误的基础知识——当你看到编译错误时问题出在语法层面看到链接错误时往往是函数声明与定义不匹配或缺少目标文件。一个标准的C程序结构如下头文件包含、全局声明、main函数、其他函数定义。main函数的返回值表示程序的退出状态0 代表正常结束非0值通常表示发生了异常。虽然现代编译器对省略return 0;有一定宽容度但养成良好的返回值习惯有助于理解进程退出码的含义也为后续学习操作系统中进程状态码的概念打下基础。printf 与 scanf 的格式控制printf和scanf是 C 语言最常用的输入输出函数它们的第一个参数是格式控制字符串后续参数是要输出或读取的变量。格式控制符必须与变量类型严格匹配这是初学者最容易忽视的规则。%d对应int%f对应float和double%c对应char%s对应字符串。如果类型不匹配比如用%f输出int类型的变量编译器通常只会发出警告但运行时会产生不可预期的结果——可能输出乱码也可能读取错误的内存数据。这是因为printf是一个可变参数函数编译器在调用时不会检查后续参数的类型是否与格式字符串匹配。scanf的使用有一个特别容易遗漏的细节变量名前必须加取地址符。scanf需要知道变量的内存地址才能将读取到的数据写入进去。但数组名本身代表的就是地址所以scanf(%s, str)不需要加而scanf(%d, a)必须加。这种不一致性常常让新手困惑记住一个原则除了数组名和字符串常量给scanf传变量时一律加。#include stdio.h int main() { int age; printf(请输入年龄: ); scanf(%d, age); // 不可省略 printf(你的年龄是 %d 岁\n, age); return 0; }二、数据类型与变量基本类型的内存占用C语言的数据类型决定了变量在内存中占据的空间大小和解释方式。char占 1 字节short通常占 2 字节int在大多数现代平台上占 4 字节long在 Windows 上通常是 4 字节、在 Linux 64 位上是 8 字节long long固定为 8 字节。float占 4 字节double占 8 字节。这些大小不是 C 标准强制规定的而是由平台和编译器决定。sizeof运算符可以在编译时获取某个类型或变量在当前平台上的实际字节数这是编写可移植代码时的重要工具。变量初始化的重要性C语言有一个让很多初学者栽跟头的特性局部变量不会自动初始化为 0。当你声明int sum;时sum中存储的是该内存位置之前留下的随机值也就是所谓的垃圾值。如果直接用它参与运算结果完全不可预期。这种错误在编译时不会报错运行时也可能不会立即崩溃而是产生看似合理实则错误的计算结果极具隐蔽性难以发现。正确的做法是声明时立即赋初值。如果想将数组全部元素清零最简洁的写法是int arr[100] {0};——只给第一个元素显式赋值为 0编译器会自动将剩余元素也初始化为 0。整数溢出沉默的杀手整数溢出是 C 语言中一类非常危险的错误。当无符号整数溢出时C 标准明确定义为回绕wrap around即超过最大值后从 0 重新开始。但当有符号整数溢出时属于未定义行为Undefined Behavior编译器可以做出任何处理——可能回绕变成负数也可能直接优化掉这段代码甚至导致程序崩溃。#include stdio.h #include limits.h int main() { int max INT_MAX; // 2147483647 int overflow max 1; // 未定义行为可能变成 -2147483648 printf(溢出结果: %d\n, overflow); return 0; }赋值与比较的混淆if (a 0)和if (a 0)看起来很像但意义完全不同。前者将 0 赋值给a然后判断表达式的值0 为假后者才是比较a是否等于 0。这种错误在语法上是完全合法的编译器通常只会发出警告因此很容易在代码审查中被忽略。一种防御性编程习惯是将常量写在比较运算符的左边if (0 a)。这样如果误写成if (0 a)编译器会直接报错因为不能给常量赋值。三、分支与循环if-else 与三元运算符if-else是最基本的分支结构。编写多分支判断时建议按照条件范围从大到小或从小到大排列逻辑更清晰。三元运算符condition ? a : b是if-else的简洁写法适合简单的赋值场景但嵌套过深会严重降低可读性。switch 的穿透特性switch语句配合case使用时每个case末尾的break至关重要。如果遗漏case会穿透到下一个case继续执行。虽然有时故意利用穿透让多个case共享同一段逻辑但更多情况下是程序员的疏忽导致一个条件满足后连续执行了后续所有分支的逻辑。#include stdio.h int main() { int day 2; // 忘记 break 的错误版本 switch(day) { case 1: printf(周一\n); case 2: printf(周二\n); // 从这里开始执行 case 3: printf(周三\n); // 继续穿透 case 4: printf(周四\n); // 继续穿透 } // 输出周二 周三 周四 return 0; }循环的边界思维for、while、do-while三种循环各有适用场景。for适合已知循环次数的场景while适合条件驱动的场景do-while保证至少执行一次循环体。循环中最常见的错误是边界问题。访问数组arr[5]时有效下标是 0 到 4写成i 5或i 6都会导致越界。另一个隐蔽的陷阱是浮点数比较由于浮点数在计算机中不能精确表示很多十进制小数如 0.1直接用比较两个浮点数是否相等往往是不可靠的。正确的做法是用误差范围判断fabs(a - b) 1e-6。#include stdio.h #include math.h int main() { double a 0.1 0.2; double b 0.3; if (a b) { printf(相等\n); // 可能不会执行 } if (fabs(a - b) 1e-9) { printf(在误差范围内相等\n); // 正确 } return 0; }四、小结基础阶段的核心是建立变量有类型、类型有大小、访问有边界的思维习惯。编译器的警告信息如变量未使用、类型转换可能丢失数据往往是排查错误的最佳线索建议在编译时开启-Wall选项将警告视为错误来对待。养成初始化变量、检查边界、验证输入的习惯能让你的代码从一开始就站在可靠的基础上。