第 2 讲 · C 语言编程基础框架
一个最小的程序可以拆成 IPO:Input(输入)→ Process(处理)→ Output(输出)。第二讲做的事情,就是把这三个盒子里最基本的零件配齐。
1. 先读懂一个完整程序
#include <stdio.h>
int main(void)
{
int a, b;
scanf("%d%d", &a, &b);
printf("%d\n", a + b);
return 0;
}
#include <stdio.h>引入标准输入输出函数的声明;main是程序入口;int a, b定义两个整型变量;scanf从标准输入读取数据;printf把结果写到标准输出;return 0表示程序正常结束。
大括号形成语句块,分号结束一条普通语句。缩进不改变 C 的语义,却直接影响代码是否容易看懂。
2. 常量:写在程序里的固定值
2.1 整数与进制
int a = 19; /* 十进制 */
int b = 023; /* 八进制,十进制的 19 */
int c = 0x13; /* 十六进制,十进制的 19 */
前导 0 在 C 整数常量中不是装饰。010 是八进制的 10,也就是十进制 8。实际代码中若没有特殊目的,整数优先写十进制。
2.2 浮点常量
double x = 0.28;
double y = 2.8e-1;
float z = 0.28f;
没有后缀的浮点常量默认是 double;后缀 f 或 F 表示 float。1.6e-2 表示 。
2.3 字符、ASCII 与转义
字符常量用单引号:
char digit = '5';
char lower = 'a';
printf("%d\n", digit); /* 常见 ASCII 环境下输出 53 */
printf("%c\n", lower + 2); /* 输出 c */
字符在机器里保存为整数编码。ASCII 中数字字符、大小写字母各自连续,因此:
int value = digit - '0'; /* '5' 变成整数 5 */
char upper = lower - 'a' + 'A';
几个常用转义字符:
| 写法 | 含义 |
|---|---|
\n | 换行 |
\t | 水平制表 |
\0 | 编码为 0 的空字符 |
\\ | 反斜线 |
\'、\" | 单引号、双引号 |
\x61、\141 | 十六进制、八进制编码的字符,均为 a |
要用 printf 输出百分号,应写 %%。
2.4 字符串不是一个字符
字符串常量用双引号,并以 \0 结束:
char s[] = "x";
这个数组实际包含 'x' 和 '\0' 两个元素。'x' 是一个字符值,"x" 是一段字符序列,二者不可互换。
3. 变量:带类型、名字和存储空间的数据
int count = 0;
double radius = 1.5;
char grade = 'A';
变量必须先定义再使用。局部变量若没有初始化,其初值不确定:
int sum;
printf("%d\n", sum); /* 错:读取未初始化的局部变量 */
3.1 标识符规则
变量名、函数名等标识符:
- 由字母、数字和下划线组成;
- 不能以数字开头;
- 区分大小写;
- 不能使用
int、return、if等关键字; a[2]、sum*count、sin(25)是表达式,不是名字。
命名风格可以用 studentScore、student_score 等,关键是含义明确并在同一项目中保持一致。课件还介绍了匈牙利、骆驼、下划线和 Pascal 命名法;这些是风格,不是语法规则。
3.2 类型与范围
C 标准只规定了各种整数类型的相对大小和最小范围,具体字节数应以当前平台的 sizeof 为准:
printf("%zu\n", sizeof(int));
常见 64 位桌面环境里,char 为 1 字节、int 和 float 为 4 字节、double 和 long long 为 8 字节,但不要把“常见”误当成语言的绝对保证。
signed 表示有符号整数,unsigned 只表示非负数。数据范围不够时,计算会溢出:
int a = 1000000000;
int b = 2000000000;
long long sum = (long long)a + b;
强制转换写在运算前,才能让加法以更宽的类型完成。
4. 格式化输入输出
4.1 常用格式符
| 数据 | scanf | printf |
|---|---|---|
int | %d | %d |
unsigned int | %u | %u |
long long | %lld | %lld |
float | %f | %f(会提升为 double) |
double | %lf | %f |
char | %c | %c |
| 字符串 | %s | %s |
scanf 需要变量地址:
int age;
double height;
if (scanf("%d%lf", &age, &height) == 2) {
printf("age=%d, height=%.2f\n", age, height);
}
格式符和实参类型不匹配会导致未定义行为,而不只是“显示不好看”。
4.2 空白与字符输入陷阱
数值格式符会跳过前导空白,%c 不会。刚读完数字后,缓冲区里的换行可能被 %c 读走:
char op;
scanf(" %c", &op); /* 前面的空格表示跳过任意空白 */
不要随手在格式串末尾写 \n。scanf("%d\n", &x) 会继续等待下一个非空白字符,看起来像程序“卡住”。
4.3 单组、多组与 EOF
单组输入直接读一次;多组输入可利用返回值:
int a, b;
while (scanf("%d%d", &a, &b) == 2) {
printf("%d\n", a + b);
}
EOF 是输入结束标记。scanf 成功读取几项,就返回几;在读取任何项之前遇到输入结束,才返回 EOF。
4.4 控制输出外观
printf("%8d\n", 42); /* 字段宽度 8 */
printf("%.3f\n", 3.14159); /* 小数点后 3 位 */
printf("%-8dend\n", 42); /* 左对齐 */
OJ 会逐字符核对答案,多一个提示语、少一个换行都可能判错。
5. 算术、赋值与类型转换
5.1 运算符
a + b; a - b; a * b; a / b; a % b;
% 只用于整数。整数除法会向零截断:
printf("%d\n", 7 / 3); /* 2 */
printf("%.2f\n", 7.0 / 3); /* 2.33 */
复合赋值把“读旧值、计算、写回”合在一起:
sum += x; /* sum = sum + x */
value *= 2;
i++ 和 ++i 单独成句时都让 i 加一;放进复杂表达式时语义更难读。初学阶段应把更新拆成独立语句,避免在同一表达式里多次修改同一变量。
5.2 隐式与显式转换
混合运算通常把较窄类型提升到较宽类型:
double avg = (double)sum / count;
若写成 sum / count 后再赋给 double,整数除法已经发生,小数部分无法挽回。
把宽类型赋给窄类型可能丢失信息:
int n = 3.9; /* 通常得到 3 */
signed char c = 200; /* 结果依实现和表示范围而定 */
6. 关系、逻辑与短路
六个关系运算符为 <、<=、>、>=、==、!=。逻辑运算符为 &&、||、!。C 中 0 为假,非 0 为真,关系和逻辑表达式结果是 0 或 1。
if (0 <= n && n <= 10) {
/* n 在区间 [0, 10] */
}
不能写成数学式 0 <= n <= 10。C 会先计算 0 <= n 得到 0 或 1,再拿它与 10 比较,结果几乎总是真。
&& 和 || 会短路:
if (denominator != 0 && numerator / denominator > 2) {
/* 只有分母非零时才执行除法 */
}
短路既能提高效率,也能保护后半个表达式不会访问非法数据。
7. 优先级:不确定就加括号
只需牢牢记住一条主线:
括号 → 一元运算 → 乘除模 → 加减 → 关系 → 相等 → && → || → 赋值
同一优先级还涉及结合方向。与其背完整大表,不如用括号表达意图:
if ((a & 1) == 0) { /* 后续位运算会用到 */ }
赋值 = 和判断相等 == 是高频笔误。编译器即使允许 if (x = 0),含义也是“给 x 赋 0,再把 0 当条件”。
8. 一维数组初步
int scores[6] = {90, 75, 88};
数组有 6 个元素,下标为 0~5;未显式给出的后三个元素初始化为 0。
int sum = 0;
for (int i = 0; i < 6; ++i) {
sum += scores[i];
}
数组不检查边界。scores[6] 语法能通过编译,但访问了不属于数组的空间。
9. 给常量起名字:const 与宏
const double PI = 3.141592653589793;
#define MAX_STUDENTS 200
const 创建带类型的只读对象;对象式宏在预处理阶段做文本替换。宏表达式必须加足括号:
#define SQUARE(x) ((x) * (x))
即便如此,SQUARE(i++) 仍会修改 i 两次,所以带副作用的实参不要交给会重复展开参数的宏。现代 C 中能用 const 或函数表达的内容,通常比复杂宏更安全。
10. 把知识串成一个例子
读取若干成绩,直到输入结束,输出平均分和及格人数:
#include <stdio.h>
int main(void)
{
int score;
int count = 0;
int passed = 0;
long long sum = 0;
while (scanf("%d", &score) == 1) {
sum += score;
count++;
if (score >= 60) {
passed++;
}
}
if (count > 0) {
printf("average=%.2f\n", (double)sum / count);
printf("passed=%d\n", passed);
}
return 0;
}
这段代码同时用到了类型、初始化、格式化输入输出、循环、选择、类型转换和逻辑边界。基础语法真正掌握的标志,就是能解释每一行为什么这样写,而不是能背下它。