程序设计基础
算法流程图、控制结构、编译与解释、数据类型、运算符与表达式求值。
1. 算法流程图
流程图是用图形符号表示算法步骤的直观工具。
1.1 基本符号
| 符号名称 | 形状 | 功能 | 示例用途 |
|---|---|---|---|
| 起止框 | 圆角矩形 | 表示算法的开始或结束 | 开始 / 结束 |
| 处理框 | 矩形 | 表示一个处理步骤 | sum = sum + i |
| 判断框 | 菱形 | 表示条件判断 | i > 100? |
| 输入输出框 | 平行四边形 | 表示数据的输入或输出 | 输入n / 输出sum |
| 流程线 | 箭头 | 表示执行方向 | → ↓ ← ↑ |
1.2 流程图示例:求1到N的和
┌───────┐
│ 开始 │
└───┬───┘
▼
┌──────────┐
│ 输入 N │
└────┬─────┘
▼
┌──────────┐
│ sum = 0 │
│ i = 1 │
└────┬─────┘
▼
┌───────────┐
│ i <= N ? │──── 否 ──► ┌──────────┐
└─────┬─────┘ │ 输出 sum │
是 ▼ └────┬─────┘
┌──────────────┐ ▼
│ sum = sum + i│ ┌───────┐
│ i = i + 1 │ │ 结束 │
└──────┬───────┘ └───────┘
│
└───► (回到判断框)
1.3 用代码对应流程图
// 上述流程图对应的C语言代码
#include <stdio.h>
int main() {
int N;
scanf("%d", &N); // 输入 N
int sum = 0;
int i = 1;
while (i <= N) { // 判断框: i <= N ?
sum = sum + i; // 处理框
i = i + 1; // 处理框
}
printf("%d\n", sum); // 输出框
return 0;
}
2. 三种基本控制结构
任何复杂程序都可以由三种基本控制结构组合而成,这被称为结构化程序设计的理论基础。
2.1 顺序结构
语句按书写顺序依次执行,是最基本的控制结构。
// 顺序结构:交换两个变量的值
int a = 3, b = 5;
int temp;
temp = a; // 第1步
a = b; // 第2步
b = temp; // 第3步
printf("a=%d, b=%d\n", a, b); // a=5, b=3
2.2 选择结构
根据条件选择不同的执行路径。
if-else 语句:
// 判断奇偶
int num = 7;
if (num % 2 == 0) {
printf("%d 是偶数\n", num);
} else {
printf("%d 是奇数\n", num);
}
多重 if-else:
// 成绩等级判定
int score = 85;
if (score >= 90) {
printf("优秀\n");
} else if (score >= 80) {
printf("良好\n");
} else if (score >= 70) {
printf("中等\n");
} else if (score >= 60) {
printf("及格\n");
} else {
printf("不及格\n");
}
switch-case 语句:
// 根据运算符执行计算
char op = '+';
int a = 10, b = 3;
switch (op) {
case '+': printf("%d\n", a + b); break;
case '-': printf("%d\n", a - b); break;
case '*': printf("%d\n", a * b); break;
case '/':
if (b != 0) printf("%d\n", a / b);
else printf("除数不能为0\n");
break;
default: printf("不支持的运算符\n"); break;
}
switch中的break不能省略,否则会穿透执行后续所有 case,直到遇到 break 或 switch 结束。
2.3 循环结构
重复执行某段代码,直到条件不满足为止。
while 循环(先判断后执行):
// 计算1+2+3+...+100
int sum = 0, i = 1;
while (i <= 100) {
sum += i;
i++;
}
printf("sum = %d\n", sum); // 5050
do-while 循环(先执行后判断,至少执行一次):
// 猜数字游戏(至少猜一次)
int secret = 42;
int guess;
do {
printf("请输入猜测的数字: ");
scanf("%d", &guess);
if (guess > secret) printf("太大了\n");
else if (guess < secret) printf("太小了\n");
} while (guess != secret);
printf("猜对了!\n");
for 循环(计数型循环):
// 打印九九乘法表
for (int i = 1; i <= 9; i++) {
for (int j = 1; j <= i; j++) {
printf("%d×%d=%-4d", j, i, i * j);
}
printf("\n");
}
break 与 continue:
// break: 跳出整个循环
for (int i = 0; i < 10; i++) {
if (i == 5) break; // i=5时跳出循环
printf("%d ", i); // 输出: 0 1 2 3 4
}
// continue: 跳过本次迭代,继续下一次
for (int i = 0; i < 10; i++) {
if (i % 2 == 0) continue; // 跳过偶数
printf("%d ", i); // 输出: 1 3 5 7 9
}
2.4 三种循环对比
| 特性 | while | do-while | for |
|---|---|---|---|
| 执行次数 | 可能0次 | 至少1次 | 可能0次 |
| 适用场景 | 不确定次数 | 至少执行一次 | 已知循环次数 |
| 初始化位置 | 循环外 | 循环外 | for语句内 |
3. 程序编译与解释
高级语言需要转换为机器码才能执行,有两种主要方式:编译和解释。
3.1 编译(Compilation)
将源代码一次性翻译为机器码(目标文件),然后执行。
源代码(.c) → [编译器] → 目标文件(.o) → [链接器] → 可执行文件(.exe)
// hello.c
#include <stdio.h>
int main() {
printf("Hello, World!\n");
return 0;
}
# 编译过程
gcc -E hello.c -o hello.i # 预处理:展开宏和头文件
gcc -S hello.i -o hello.s # 编译:生成汇编代码
gcc -c hello.s -o hello.o # 汇编:生成目标文件
gcc hello.o -o hello # 链接:生成可执行文件
./hello # 执行
3.2 解释(Interpretation)
逐行读取源代码并即时执行,不生成独立的可执行文件。
源代码(.py) → [解释器] → 逐行执行
# hello.py
print("Hello, World!")
# 直接运行
python hello.py
3.3 编译与解释对比
| 特性 | 编译型语言 | 解释型语言 |
|---|---|---|
| 执行方式 | 先编译后执行 | 边解释边执行 |
| 执行速度 | 快(直接运行机器码) | 慢(需要实时翻译) |
| 跨平台性 | 需要为不同平台编译 | 只要有解释器即可运行 |
| 调试体验 | 修改后需重新编译 | 修改后直接运行 |
| 错误发现 | 编译时发现语法错误 | 运行时才发现错误 |
| 典型语言 | C, C++, Go, Rust | Python, JavaScript, Ruby |
3.4 混合方式
许多现代语言采用编译+解释的混合方式:
Java: 源代码(.java) → 字节码(.class) → JVM解释/JIT编译执行
C#: 源代码(.cs) → IL字节码 → CLR解释/JIT编译执行
Python: 源代码(.py) → 字节码(.pyc) → Python虚拟机执行
JIT(Just-In-Time)编译在程序运行时将热点代码编译为机器码,兼顾了启动速度和执行效率。
4. 变量命名规则
4.1 通用命名规则
| 规则 | 合法示例 | 非法示例 |
|---|---|---|
| 由字母、数字、下划线组成 | score, max_val | my-name, 2nd |
| 不能以数字开头 | val2 | 2val |
| 不能使用关键字 | my_int | int |
| 区分大小写 | Name ≠ name | — |
4.2 命名风格
| 风格 | 格式 | 适用语言 | 示例 |
|---|---|---|---|
| 驼峰命名(小驼峰) | 首单词小写,后续首字母大写 | Java, JS, C# | studentName |
| 帕斯卡命名(大驼峰) | 每个单词首字母大写 | C#, 类名 | StudentName |
| 蛇形命名 | 单词间用下划线连接 | C, Python, Rust | student_name |
| 全大写 | 蛇形命名+全大写 | 常量 | MAX_SIZE |
// C语言命名示例
int student_count; // 蛇形命名:变量
#define MAX_SIZE 100 // 全大写:常量
void calculate_sum(); // 蛇形命名:函数
// JavaScript命名示例
let studentName; // 小驼峰:变量
const MAX_SIZE = 100; // 全大写:常量
function calculateSum() {
// 小驼峰:函数
// ...
}
4.3 命名原则
- 见名知义:
age比a好,studentCount比sc好 - 避免缩写:
temperature比temp好(除非是通用缩写如id、url) - 布尔变量用 is/has 前缀:
isValid、hasPermission - 函数名用动词开头:
getName、setData、calculateTotal
5. 基本数据类型
5.1 四种基本数据类型
| 类型 | 关键字 | 占用空间 | 取值范围 | 示例 |
|---|---|---|---|---|
| 整型 | int | 4字节(通常) | -2³¹ ~ 2³¹-1 | 42, -7 |
| 浮点型 | float/double | 4/8字节 | 见IEEE 754章节 | 3.14, -0.5 |
| 字符型 | char | 1字节 | -128 | 'A', '0' |
| 布尔型 | bool | 1字节 | true/false | true, false |
5.2 整型的不同大小
#include <stdio.h>
#include <limits.h>
int main() {
// 各整型的大小和范围
printf("char: %zu字节, 范围: %d ~ %d\n",
sizeof(char), CHAR_MIN, CHAR_MAX);
printf("short: %zu字节, 范围: %d ~ %d\n",
sizeof(short), SHRT_MIN, SHRT_MAX);
printf("int: %zu字节, 范围: %d ~ %d\n",
sizeof(int), INT_MIN, INT_MAX);
printf("long: %zu字节, 范围: %ld ~ %ld\n",
sizeof(long), LONG_MIN, LONG_MAX);
return 0;
}
典型输出(64位系统):
| 类型 | 字节数 | 范围 |
|---|---|---|
| char | 1 | -128 ~ 127 |
| short | 2 | -32,768 ~ 32,767 |
| int | 4 | -2,147,483,648 ~ 2,147,483,647 |
| long | 4/8 | 取决于平台 |
| long long | 8 | -9.2×10¹⁸ ~ 9.2×10¹⁸ |
5.3 有符号与无符号
signed char sc = -1; // 范围: -128 ~ 127
unsigned char uc = 255; // 范围: 0 ~ 255
// 无符号整型:只有正数,范围翻倍
unsigned int ui = 4294967295U; // 范围: 0 ~ 4,294,967,295
// 注意:无符号数不会变负
unsigned int x = 0;
x = x - 1; // 不是-1,而是4294967295(回绕)
5.4 类型转换
// 隐式转换:小类型 → 大类型(安全)
int i = 42;
double d = i; // int → double,值不变
// 隐式转换:大类型 → 小类型(可能丢失数据)
double pi = 3.14159;
int n = pi; // double → int,n = 3(小数部分被截断)
// 显式转换(强制类型转换)
double result = (double)1 / 2; // 0.5(而非整数除法的0)
6. 常量与字面量
6.1 字面量
字面量是源代码中直接写出的固定值:
42 // 整型字面量
3.14 // 浮点型字面量(默认double)
3.14f // 浮点型字面量(float)
'A' // 字符字面量
"Hello" // 字符串字面量
true // 布尔字面量
不同进制的整型字面量:
int dec = 255; // 十进制
int oct = 0377; // 八进制(前缀0)
int hex = 0xFF; // 十六进制(前缀0x)
int bin = 0b11111111; // 二进制(C23标准,前缀0b)
6.2 常量
常量是程序运行期间不可修改的值:
// 方式1: const 关键字(推荐)
const double PI = 3.14159265;
const int MAX_STUDENTS = 50;
// 方式2: 宏定义(C语言传统方式)
#define PI 3.14159265
#define MAX_STUDENTS 50
// 区别:
// const: 有类型检查,占用内存,调试时可查看
// #define: 无类型检查,文本替换,调试时不可见
// 枚举常量
enum Color {
RED, // 0
GREEN, // 1
BLUE // 2
};
enum Weekday {
MON = 1,
TUE = 2,
WED = 3,
THU = 4,
FRI = 5,
SAT = 6,
SUN = 7
};
7. 运算符优先级与结合性
7.1 常用运算符
| 类别 | 运算符 | 示例 |
|---|---|---|
| 算术 | + - * / % | a + b, x % 3 |
| 自增自减 | ++ — | i++, --j |
| 关系 | > < >= <= == != | a > b, x == 0 |
| 逻辑 | && || ! | a && b, !flag |
| 位运算 | & | ^ ~ << >> | a & 0xFF |
| 赋值 | = += -= *= /= %= | x += 1 |
| 条件 | ? : | a > b ? a : b |
| 逗号 | , | a = 1, b = 2 |
7.2 优先级表(从高到低)
| 优先级 | 运算符 | 结合性 | 说明 |
|---|---|---|---|
| 1 | () [] -> . | 左→右 | 最高优先级 |
| 2 | ! ~ ++ — + - * & | 右→左 | 单目运算符 |
| 3 | * / % | 左→右 | 乘除取余 |
| 4 | + - | 左→右 | 加减 |
| 5 | << >> | 左→右 | 移位 |
| 6 | < <= > >= | 左→右 | 关系 |
| 7 | == != | 左→右 | 相等性 |
| 8 | & | 左→右 | 按位与 |
| 9 | ^ | 左→右 | 按位异或 |
| 10 | | | 左→右 | 按位或 |
| 11 | && | 左→右 | 逻辑与 |
| 12 | || | 左→右 | 逻辑或 |
| 13 | ? : | 右→左 | 条件 |
| 14 | = += -= *= /= %= 等 | 右→左 | 赋值 |
| 15 | , | 左→右 | 逗号 |
记忆口诀:单目(2) > 算术(3-4) > 移位(5) > 关系(6-7) > 位运算(8-10) > 逻辑(11-12) > 条件(13) > 赋值(14) > 逗号(15)
7.3 易混淆的优先级陷阱
// 陷阱1: & 优先级低于 ==
int flags = 5;
if (flags & 0x1 == 1) { } // 错!等价于 flags & (0x1 == 1)
if ((flags & 0x1) == 1) { } // 对!先位与再比较
// 陷阱2: * 优先级高于 +
int a = 2, b = 3, c = 4;
int result = a + b * c; // 2 + 12 = 14,不是 5 * 4 = 20
// 陷阱3: ++ 优先级高于 *
int arr[] = {10, 20, 30};
int *p = arr;
int val = *p++; // 先取 *p (=10),再 p++。val = 10
// vs
int val2 = (*p)++; // 先取 *p,再 (*p)++。val2 = 10, arr[0] = 11
8. 表达式求值与短路求值
8.1 表达式求值
表达式由运算符和操作数组成,求值时遵循优先级和结合性规则:
int a = 2, b = 3, c = 4;
// 步骤分解: a + b * c - a / b
// 1. b * c = 12 (* 优先级高于 +)
// 2. a / b = 0 (/ 优先级高于 +,整数除法 2/3=0)
// 3. a + 12 = 14 (从左到右)
// 4. 14 - 0 = 14 (最终结果)
8.2 短路求值
逻辑运算符 && 和 || 具有短路求值特性:当结果已经可以确定时,不再计算后续表达式。
逻辑与(&&)的短路:
// 如果第一个条件为假,整个表达式必为假,不再计算第二个条件
int x = 0;
if (x != 0 && 10 / x > 1) { // x != 0 为假,10/x 不会执行
printf("不会执行\n"); // 避免了除零错误
}
逻辑或(||)的短路:
// 如果第一个条件为真,整个表达式必为真,不再计算第二个条件
int* ptr = NULL;
if (ptr == NULL || *ptr > 0) { // ptr == NULL 为真,*ptr 不会执行
printf("指针为空\n"); // 避免了空指针解引用
}
8.3 短路求值的实际应用
// 应用1: 安全地访问可能为空的指针
if (ptr != NULL && ptr->value > 0) {
// 只有ptr非空时才访问ptr->value
}
// 应用2: 数组越界保护
if (index >= 0 && index < size && arr[index] == target) {
// 先检查范围,再访问数组
}
// 应用3: 简洁的条件赋值
// 如果文件打开失败,不执行读取
FILE* fp = fopen("data.txt", "r");
if (fp && fscanf(fp, "%d", &value) == 1) {
printf("读取成功: %d\n", value);
}
8.4 注意:避免在短路表达式中使用副作用
int i = 0;
// 危险!如果 a > 0 为真,i++ 不会执行
if (a > 0 || i++) {
// i 的值取决于 a > 0 的结果
}
// 安全写法:将副作用分离
i++;
if (a > 0 || i > 1) {
// 行为可预测
}
8.5 逗号表达式
逗号运算符按从左到右顺序依次求值,整个表达式的值是最后一个子表达式的值:
int a, b, c;
c = (a = 1, b = 2, a + b); // a=1, b=2, c=3
// 常用于 for 循环
for (int i = 0, j = 10; i < j; i++, j--) {
printf("i=%d, j=%d\n", i, j);
}
9. 小结
| 主题 | 核心要点 | | ------------ | ---------------------------------------------- | --- | -------------------- | | 流程图 | 起止框/处理框/判断框/输入输出框/流程线 | | 控制结构 | 顺序、选择(if/switch)、循环(while/for) | | 编译与解释 | 编译一次执行多次 vs 边解释边执行 | | 变量命名 | 见名知义,统一风格,避免缩写 | | 数据类型 | 整型/浮点型/字符型/布尔型,注意范围和转换 | | 常量与字面量 | const(推荐)vs #define,枚举常量 | | 运算符优先级 | 单目 > 算术 > 关系 > 逻辑 > 赋值,有疑问加括号 | | 短路求值 | && 遇假即停, | | 遇真即停,注意副作用 |
掌握程序设计基础是编写正确代码的前提。优先级记不清时就加括号,短路求值要善用但不要滥用副作用。