C语言单词背记闯关游戏 — 核心知识点详解
一、预处理模块设计
1.1 文件包含指令 #include
知识点说明:
#include 是C语言的预处理指令,用于在编译之前将指定的头文件内容插入到当前源文件中。头文件中包含了标准库函数的声明、宏定义、类型定义等内容。
两种形式:
#include <stdio.h> // 从系统标准库路径中查找头文件
#include "myheader.h" // 先从当前目录查找,再从系统路径查找
常用头文件:
| stdio.h | 标准输入输出函数(printf, scanf, fopen等) |
| stdlib.h | 通用工具函数(system, exit, rand, srand等) |
| string.h | 字符串处理函数(strcmp, strcpy, strlen等) |
| time.h | 时间相关函数(time, clock等) |
| conio.h | 控制台输入输出(getch, kbhit等,Windows特有) |
| windows.h | Windows API函数(光标定位等) |
1.2 宏定义 #define
知识点说明:
#define 用于定义宏常量和宏函数。在预处理阶段,编译器会将所有宏名替换为对应的值或表达式。
无参宏(宏常量):
#define 标识符 替换文本
- 宏名通常用大写字母,以区分变量
- 宏定义末尾不加分号
- 宏定义是简单的文本替换,不进行类型检查
带参宏(宏函数):
#define 标识符(参数列表) 替换文本
- 参数和整个表达式建议加括号,避免运算优先级问题
- 带参宏在预处理阶段展开,不涉及函数调用开销
入门案例:
#include <stdio.h>
// 定义宏常量:游戏总关数
#define TOTAL_ROUNDS 5
// 定义宏常量:每关单词数量
#define WORDS_PER_ROUND 4
// 定义宏常量:答对一题的得分
#define SCORE_PER_WORD 20
// 带参宏:计算某一关的满分(括号防止优先级错误)
#define ROUND_FULL_SCORE(n) ((n) * SCORE_PER_WORD)
int main() {
// 使用宏常量
int rounds = TOTAL_ROUNDS; // 预处理后替换为:int rounds = 5;
int words = WORDS_PER_ROUND; // 预处理后替换为:int words = 4;
// 使用带参宏
int fullScore = ROUND_FULL_SCORE(WORDS_PER_ROUND); // 替换为:((4) * 20)
printf("游戏总关数: %d\\n", rounds); // 输出:游戏总关数: 5
printf("每关单词数: %d\\n", words); // 输出:每关单词数: 4
printf("每关满分: %d 分\\n", fullScore); // 输出:每关满分: 80 分
return 0; // 程序正常结束
}
1.3 条件编译指令
知识点说明:
条件编译允许根据条件决定哪些代码参与编译,哪些代码被忽略。常用于防止头文件重复包含、跨平台代码适配等场景。
防止头文件重复包含:
#ifndef 标识符 // 如果没有定义该标识符
#define 标识符 // 则定义它
// 头文件内容
#endif // 条件编译结束
执行逻辑:
二、游戏开始界面显示
2.1 格式化输出函数 printf
知识点说明:
printf 是C语言中最常用的输出函数,定义在 stdio.h 中,用于将格式化的数据输出到标准输出设备(通常是屏幕)。
函数原型:
int printf(const char *format, ...);
- format:格式控制字符串,包含普通字符和格式说明符
- …:可变参数,对应格式说明符的值
- 返回值:成功输出的字符总数
常用格式说明符:
| %d | 十进制整数 | printf("%d", 42) → 42 |
| %f | 浮点数(默认6位小数) | printf("%f", 3.14) → 3.140000 |
| %.2f | 保留2位小数 | printf("%.2f", 3.14) → 3.14 |
| %c | 单个字符 | printf("%c", 'A') → A |
| %s | 字符串 | printf("%s", "hello") → hello |
| %5d | 右对齐,宽度5 | printf("%5d", 42) → 42 |
| %-5d | 左对齐,宽度5 | printf("%-5d", 42) → 42 |
转义字符:
| \\n | 换行 |
| \\t | 水平制表符(Tab) |
| \\\\ | 反斜杠本身 |
| \\" | 双引号本身 |
| \\0 | 空字符(字符串结束标志) |
2.2 控制台光标定位(Windows平台)
知识点说明:
在Windows控制台程序中,可以通过Windows API函数 SetConsoleCursorPosition 将光标移动到指定位置,从而在屏幕任意位置输出内容,实现界面的精确定位显示。
关键结构体和函数:
// COORD 结构体定义(在 windows.h 中)
typedef struct {
SHORT X; // 列号(从0开始)
SHORT Y; // 行号(从0开始
} COORD;
// 获取标准输出句柄
HANDLE GetStdHandle(DWORD nStdHandle);
// 设置光标位置
BOOL SetConsoleCursorPosition(HANDLE hConsoleOutput, COORD dwCursorPosition);
自定义光标定位函数封装:
#include <windows.h>
// 函数功能:将控制台光标移动到(x, y)位置
// 参数 x:列号(水平方向,从左到右递增)
// 参数 y:行号(垂直方向,从上到下递增)
void gotoxy(int x, int y) {
COORD coord; // 定义坐标结构体变量
coord.X = x; // 设置列坐标
coord.Y = y; // 设置行坐标
// GetStdHandle(STD_OUTPUT_HANDLE) 获取标准输出设备句柄
// SetConsoleCursorPosition 设置光标到指定坐标位置
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
}
2.3 清屏函数 system("cls")
知识点说明:
system() 函数定义在 stdlib.h 中,用于执行操作系统命令。system("cls") 是Windows平台下的清屏命令。
#include <stdlib.h>
system("cls"); // Windows下清屏
// system("clear"); // Linux/Mac下清屏
2.4 入门案例:简单的开始界面
#include <stdio.h> // 包含标准输入输出函数
#include <stdlib.h> // 包含system函数
#include <windows.h> // 包含Windows API函数
// 自定义函数:将光标定位到(x, y)坐标位置
void gotoxy(int x, int y) {
COORD coord; // 定义坐标结构体
coord.X = x; // 设置水平坐标
coord.Y = y; // 设置垂直坐标
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
}
int main() {
system("cls"); // 清屏,确保界面干净
// 在第10行第25列开始显示游戏标题
gotoxy(25, 10);
printf("================================");
gotoxy(25, 11);
printf("= =");
gotoxy(25, 12);
printf("= C语言单词背记闯关游戏 =");
gotoxy(25, 13);
printf("= =");
gotoxy(25, 14);
printf("================================");
// 在下方显示提示信息
gotoxy(30, 16);
printf("按任意键开始游戏…");
getchar(); // 等待用户按下回车键
return 0; // 程序正常结束
}
三、积分规则界面显示
3.1 字符数组与字符串
知识点说明:
C语言中没有专门的"字符串"类型,字符串用字符数组来存储。字符串以空字符 '\\0' 作为结束标志。
字符串的两种初始化方式:
// 方式1:逐字符初始化(注意手动加 '\\0')
char str1[6] = {'H', 'e', 'l', 'l', 'o', '\\0'};
// 方式2:用字符串常量初始化(编译器自动添加 '\\0')
char str2[] = "Hello"; // 实际占用6个字节(5个字符 + 1个 '\\0')
重要区别:
- char str[] = "Hello"; — 数组内容可修改
- char *str = "Hello"; — 指向字符串常量,内容不可修改
字符串长度与数组大小的关系:
char s[] = "abc";
// sizeof(s) = 4 (数组实际大小:3个字符 + 1个 '\\0')
// strlen(s) = 3 (字符串有效长度:不计算 '\\0')
3.2 字符串长度函数 strlen
函数原型:
#include <string.h>
size_t strlen(const char *str);
- 功能:返回字符串中 '\\0' 之前的字符个数(不含 '\\0')
- 参数:以 '\\0' 结尾的字符串
- 返回值:字符串的有效长度(无符号整数)
示例:
#include <stdio.h>
#include <string.h>
int main() {
char word[] = "apple"; // 定义字符串
int len = strlen(word); // 计算字符串长度
printf("单词: %s\\n", word); // 输出:单词: apple
printf("长度: %d\\n", len); // 输出:长度: 5
return 0;
}
3.3 格式化输出实现规则界面
入门案例:积分规则显示界面
#include <stdio.h> // 包含printf函数
#include <stdlib.h> // 包含system函数
#include <windows.h> // 包含Windows API
// 光标定位函数
void gotoxy(int x, int y) {
COORD coord; // 定义坐标结构体
coord.X = x; // 设置列坐标
coord.Y = y; // 设置行坐标
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
}
int main() {
system("cls"); // 清屏
// 标题
gotoxy(30, 3);
printf("*** 游戏规则 ***");
// 水平分隔线
gotoxy(20, 5);
printf("—————————————-");
// 规则内容(逐行显示)
gotoxy(22, 7);
printf("1. 游戏共有 5 关,每关 4 个单词");
gotoxy(22, 9);
printf("2. 每关给出一个英文单词");
gotoxy(22, 11);
printf("3. 从4个中文释义中选出正确答案");
gotoxy(22, 13);
printf("4. 每答对一题得 20 分");
gotoxy(22, 15);
printf("5. 每关满分 100 分");
gotoxy(22, 17);
printf("6. 答错不扣分,但不能获得该题分数");
// 水平分隔线
gotoxy(20, 19);
printf("—————————————-");
// 提示
gotoxy(28, 21);
printf("按任意键开始游戏…");
getchar(); // 等待用户输入
return 0; // 程序正常结束
}
四、单词背记游戏主功能实现
4.1 二维字符数组(二维字符串数组)
知识点说明:
二维字符数组可以存储多个字符串,每一行存储一个字符串。在单词游戏中,通常用二维数组存储单词及其对应的中文释义。
声明与初始化:
// 声明一个3行20列的二维字符数组,可以存储3个长度不超过19的字符串
char words[3][20] = {
"apple", // words[0] = "apple"
"banana", // words[1] = "banana"
"cherry" // words[2] = "cherry"
};
内存布局示意:
words[0]: 'a' 'p' 'p' 'l' 'e' '\\0' …(剩余空间)
words[1]: 'b' 'a' 'n' 'a' 'n' 'a' '\\0' …
words[2]: 'c' 'h' 'e' 'r' 'r' 'y' '\\0' …
访问单个字符串:
printf("%s\\n", words[0]); // 输出整个字符串:apple
printf("%c\\n", words[0][2]); // 输出单个字符:p
4.2 strcmp 字符串比较函数
知识点说明:
C语言中不能直接用 == 比较两个字符串的内容(== 比较的是地址),必须使用 strcmp 函数逐字符比较。
函数原型:
#include <string.h>
int strcmp(const char *str1, const char *str2);
返回值规则:
| 0 | 两个字符串完全相同 |
| > 0 | str1 大于 str2(按ASCII码比较) |
| < 0 | str1 小于 str2(按ASCII码比较) |
比较规则:
- 从第一个字符开始,逐个比较对应位置的字符的ASCII码值
- 遇到不同的字符或 '\\0' 时停止比较
- 如果所有字符都相同且同时遇到 '\\0',返回 0
入门案例:
#include <stdio.h>
#include <string.h>
int main() {
char answer[20]; // 存储用户输入的答案
char correct[20] = "apple"; // 存储正确答案
printf("请输入 apple 的中文意思: ");
scanf("%s", answer); // 读取用户输入
// strcmp 返回 0 表示两个字符串相同
if (strcmp(answer, correct) == 0) {
printf("回答正确!\\n"); // 字符串匹配
} else {
printf("回答错误!正确答案是: %s\\n", correct); // 字符串不匹配
}
return 0;
}
4.3 strcpy 字符串复制函数
知识点说明:
strcpy 将源字符串复制到目标字符数组中(包括 '\\0')。
函数原型:
#include <string.h>
char *strcpy(char *dest, const char *src);
- dest:目标字符数组(必须足够大)
- src:源字符串
- 返回值:目标数组的首地址
示例:
#include <stdio.h>
#include <string.h>
int main() {
char dest[20]; // 目标数组,需要足够大
char src[] = "Hello"; // 源字符串
strcpy(dest, src); // 将 src 复制到 dest
printf("dest = %s\\n", dest); // 输出:dest = Hello
return 0;
}
注意: 不能用赋值号 = 直接给字符数组赋值字符串(初始化时可以,赋值时不行):
char s[20];
// s = "Hello"; // 错误!数组名是常量地址,不能被赋值
strcpy(s, "Hello"); // 正确!使用 strcpy 函数
4.4 随机数函数 rand 和 srand
知识点说明:
在单词游戏中,需要随机选择题目,这就需要用到随机数。
相关函数:
#include <stdlib.h>
int rand(void); // 返回一个 0 到 RAND_MAX 之间的伪随机整数
void srand(unsigned int seed); // 设置随机数种子
rand() 的特点:
- 返回范围:0 ~ RAND_MAX(至少32767)
- 每次调用返回一个伪随机数
- 如果种子相同,生成的随机数序列完全相同
srand() 的作用:
- 设置随机数生成器的起始值(种子)
- 种子不同 → 生成不同的随机数序列
- 常用 time(0)(当前时间戳)作为种子,保证每次运行不同
生成指定范围的随机数:
// 生成 [min, max] 范围内的随机整数
rand() % (max – min + 1) + min
入门案例:
#include <stdio.h>
#include <stdlib.h> // 包含 rand() 和 srand()
#include <time.h> // 包含 time()
int main() {
// 用当前时间作为随机数种子(只需调用一次)
srand((unsigned int)time(NULL));
// 生成10个 1~100 之间的随机数
int i;
for (i = 0; i < 10; i++) {
int num = rand() % 100 + 1; // rand()%100 得到 0~99,+1 得到 1~100
printf("%d ", num);
}
printf("\\n");
// 生成 0~3 的随机数(用于从4个选项中随机选一个位置)
int pos = rand() % 4; // 结果为 0, 1, 2, 3 中的一个
printf("随机选项位置: %d\\n", pos);
return 0;
}
4.5 switch-case 多分支选择语句
知识点说明:
switch 语句根据一个整型表达式的值,跳转到对应的 case 分支执行。适用于多分支且判断条件为离散值的场景。
语法格式:
switch (表达式) {
case 常量1:
语句块1;
break; // 跳出switch
case 常量2:
语句块2;
break;
// … 可以有多个case
default: // 所有case都不匹配时执行
语句块n;
break;
}
执行流程:
注意事项:
- case 后面必须是常量表达式(整数或字符),不能是变量
- break 不可遗漏,否则会发生穿透
- default 可以省略
入门案例:
#include <stdio.h>
int main() {
int choice; // 存储用户选择
printf("请选择答案(输入1-4的数字): ");
scanf("%d", &choice);
switch (choice) {
case 1:
printf("你选择了选项 A\\n");
break;
case 2:
printf("你选择了选项 B\\n");
break;
case 3:
printf("你选择了选项 C\\n");
break;
case 4:
printf("你选择了选项 D\\n");
break;
default:
printf("输入无效,请输入1-4之间的数字\\n");
break;
}
return 0;
}
4.6 数组元素的随机排列(打乱顺序)
知识点说明:
为了让每关的选项顺序不同,需要将选项数组中的元素随机打乱。常用 Fisher-Yates 洗牌算法 的简化版本。
算法思路:
从数组最后一个元素开始,依次与前面某个随机位置的元素交换
入门案例:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
int main() {
// 定义4个选项字符串
char options[4][30] = {
"苹果", // 选项0
"香蕉", // 选项1
"汽车", // 选项2
"书本" // 选项3
};
srand((unsigned int)time(NULL)); // 初始化随机种子
// Fisher-Yates 洗牌算法(简化版)
// 从最后一个元素开始,随机选一个前面的元素交换
int i;
for (i = 3; i > 0; i—) {
int j = rand() % (i + 1); // 生成 0~i 的随机索引
// 交换 options[i] 和 options[j]
char temp[30]; // 临时数组用于交换
strcpy(temp, options[i]); // temp = options[i]
strcpy(options[i], options[j]); // options[i] = options[j]
strcpy(options[j], temp); // options[j] = temp
}
// 输出打乱后的选项
printf("打乱后的选项顺序:\\n");
for (i = 0; i < 4; i++) {
printf(" %d. %s\\n", i + 1, options[i]);
}
return 0;
}
4.7 循环结构(for、while、do-while)
知识点说明:
循环结构用于重复执行一段代码。在单词游戏中,需要循环遍历每一关、每一道题目。
4.7.1 for 循环
语法:
for (初始化表达式; 循环条件; 更新表达式) {
循环体语句;
}
执行流程:
示例:
// 遍历5关,每关4道题
int round, question;
for (round = 0; round < 5; round++) { // 外层循环:5关
printf("第 %d 关\\n", round + 1);
for (question = 0; question < 4; question++) { // 内层循环:4道题
printf(" 题目 %d\\n", question + 1);
}
}
4.7.2 while 循环
语法:
while (循环条件) {
循环体语句;
}
特点: 先判断条件,再决定是否执行循环体(可能一次都不执行)。
4.7.3 do-while 循环
语法:
do {
循环体语句;
} while (循环条件); // 注意:这里有分号!
特点: 先执行一次循环体,再判断条件(至少执行一次)。
入门案例(do-while实现输入验证):
#include <stdio.h>
int main() {
int choice;
do {
printf("请输入1-4选择答案: ");
scanf("%d", &choice);
if (choice < 1 || choice > 4) {
printf("输入无效,请重新输入!\\n");
}
} while (choice < 1 || choice > 4); // 条件为真则继续循环
printf("你选择了: %d\\n", choice);
return 0;
}
4.8 条件判断(if-else 嵌套与逻辑运算符)
知识点说明:
在游戏主功能中,需要大量使用条件判断来判断用户答案是否正确、是否过关等。
逻辑运算符:
| && | 逻辑与(都为真才为真) | (a > 0) && (a < 10) | a在1-9范围内为真 |
| || | 逻辑或(一个为真就为真) | (a == 1) || (a == 2) | a为1或2时为真 |
| ! | 逻辑非(取反) | !(a == 0) | a不为0时为真 |
入门案例:判断是否过关
#include <stdio.h>
int main() {
int score = 80; // 当前得分
int level = 1; // 当前关卡
// 多条件判断
if (score >= 60 && level < 5) {
printf("恭喜!通过第 %d 关,得分: %d\\n", level, score);
printf("进入下一关!\\n");
} else if (score >= 60 && level == 5) {
printf("恭喜!通关所有关卡!总得分: %d\\n", score);
} else {
printf("很遗憾,得分 %d 未达到60分,闯关失败!\\n", score);
}
return 0;
}
4.9 getch 函数(无回显读取按键)
知识点说明:
getch() 从键盘读取一个字符,但不会回显到屏幕上,也不需要按回车确认。适用于游戏中的按键操作(如按数字键选择答案)。
#include <conio.h> // Windows平台特有头文件
char ch = getch(); // 读取一个按键,不回显,无需回车
与 getchar 的区别:
| 头文件 | conio.h | stdio.h |
| 回显 | 不回显 | 回显到屏幕 |
| 缓冲 | 无缓冲,立即返回 | 有缓冲,按回车后返回 |
| 平台 | Windows特有 | 所有平台 |
示例:
#include <stdio.h>
#include <conio.h>
int main() {
printf("请按任意键继续…");
char ch = getch(); // 等待按键,不显示按下的键
printf("\\n你按下了: %c\\n", ch); // 显示按下的字符
// 利用getch实现数字选择
printf("请选择(1-4): ");
ch = getch(); // 直接读取数字键
printf("%c\\n", ch); // 回显选择
if (ch >= '1' && ch <= '4') {
printf("你选择了选项 %c\\n", ch);
} else {
printf("无效选择\\n");
}
return 0;
}
4.10 综合案例:单词选择题核心逻辑
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <conio.h>
#include <windows.h>
// 光标定位函数
void gotoxy(int x, int y) {
COORD coord;
coord.X = x;
coord.Y = y;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
}
int main() {
// 定义单词数据:英文单词 + 对应中文释义
char english[4][20] = {
"apple", // 单词0
"banana", // 单词1
"computer", // 单词2
"teacher" // 单词3
};
// meanings[i] 是 english[i] 的正确中文释义
char meanings[4][30] = {
"苹果", // apple 的释义
"香蕉", // banana 的释义
"计算机", // computer 的释义
"老师" // teacher 的释义
};
// 干扰释义(用于生成错误选项)
char wrongMeanings[4][30] = {
"汽车",
"桌子",
"天空",
"河流"
};
srand((unsigned int)time(NULL)); // 初始化随机种子
int score = 0; // 总得分
int i, j;
// 循环出题(遍历每个单词)
for (i = 0; i < 4; i++) {
system("cls"); // 清屏
// 显示当前题目
gotoxy(30, 3);
printf("第 %d 题 (共 4 题)", i + 1);
gotoxy(30, 5);
printf("单词: %s", english[i]); // 显示英文单词
gotoxy(30, 6);
printf("请选择正确的中文释义:");
// 准备4个选项:1个正确 + 3个错误
char options[4][30]; // 4个选项
int correctPos = rand() % 4; // 随机决定正确答案放在哪个位置
// 将正确答案放入随机位置
strcpy(options[correctPos], meanings[i]);
// 填充其余3个错误选项
int wrongIndex = 0; // 错误选项索引
for (j = 0; j < 4; j++) {
if (j != correctPos) {
// 跳过正确答案的位置,填入错误选项
while (strcmp(wrongMeanings[wrongIndex], meanings[i]) == 0) {
wrongIndex++; // 避免错误选项与正确答案重复
}
strcpy(options[j], wrongMeanings[wrongIndex]);
wrongIndex++;
}
}
// 显示4个选项
for (j = 0; j < 4; j++) {
gotoxy(32, 8 + j * 2);
printf("%d. %s", j + 1, options[j]);
}
// 获取用户选择
gotoxy(30, 17);
printf("你的选择(1-4): ");
char ch = getch(); // 无回显读取按键
printf("%c\\n", ch); // 回显选择
int userChoice = ch – '0'; // 将字符'1'-'4'转换为整数1-4
// 判断答案是否正确
if (userChoice – 1 == correctPos) {
// 用户选择的索引 == 正确答案的索引
gotoxy(30, 19);
printf("回答正确!+20分");
score += 20; // 加分
} else {
gotoxy(30, 19);
printf("回答错误!正确答案是: %s", meanings[i]);
}
gotoxy(30, 21);
printf("当前得分: %d", score);
gotoxy(30, 23);
printf("按任意键继续…");
getch(); // 等待用户按键后继续下一题
}
// 显示最终结果
system("cls");
gotoxy(30, 10);
printf("游戏结束!最终得分: %d / 80", score);
getchar(); // 等待回车
return 0;
}
五、读取和存储游戏最高分
5.1 文件指针 FILE *
知识点说明:
C语言中所有文件操作都通过文件指针进行。FILE 是标准库定义的结构体类型,包含了文件操作所需的各种信息(文件位置、缓冲区、状态等)。
#include <stdio.h>
FILE *fp; // 声明一个 FILE 类型的指针变量
文件指针的作用:
- 指向一个打开的文件
- 所有后续的读写操作都通过该指针进行
- 文件操作完成后必须关闭
5.2 文件打开函数 fopen
函数原型:
FILE *fopen(const char *filename, const char *mode);
- filename:文件路径(字符串)
- mode:打开模式
- 返回值:成功返回文件指针,失败返回 NULL
常用打开模式:
| "r" | 只读 | 打开失败 | 从头读取 |
| "w" | 只写 | 创建新文件 | 清空文件内容 |
| "a" | 追加写 | 创建新文件 | 在末尾追加 |
| "r+" | 读写 | 打开失败 | 从头读写 |
| "w+" | 读写 | 创建新文件 | 清空后读写 |
| "a+" | 读和追加 | 创建新文件 | 可读,写在末尾 |
5.3 文件关闭函数 fclose
函数原型:
int fclose(FILE *fp);
- 关闭文件,释放缓冲区资源
- 成功返回 0,失败返回 EOF
- 每次打开文件后必须关闭
5.4 格式化文件读写 fprintf 和 fscanf
知识点说明:
fprintf 和 fscanf 的用法与 printf 和 scanf 基本相同,只是多了一个文件指针参数,操作对象从屏幕/键盘变成了文件。
fprintf — 向文件写入格式化数据:
int fprintf(FILE *fp, const char *format, ...);
// 成功返回写入的字符数,失败返回负数
fscanf — 从文件读取格式化数据:
int fscanf(FILE *fp, const char *format, ...);
// 返回成功读取的数据项个数
入门案例:保存和读取最高分
#include <stdio.h>
// 函数功能:将最高分保存到文件中
// 参数 score:要保存的分数值
void saveHighScore(int score) {
FILE *fp; // 声明文件指针
fp = fopen("highscore.txt", "w"); // 以写模式打开文件
if (fp == NULL) { // 检查文件是否成功打开
printf("错误:无法打开文件进行写入!\\n");
return; // 打开失败则返回
}
fprintf(fp, "%d", score); // 将分数以整数格式写入文件
fclose(fp); // 关闭文件
printf("最高分 %d 已保存到文件\\n", score);
}
// 函数功能:从文件中读取最高分
// 返回值:读取到的最高分,如果文件不存在则返回0
int loadHighScore() {
FILE *fp; // 声明文件指针
int highScore = 0; // 默认最高分为0
fp = fopen("highscore.txt", "r"); // 以读模式打开文件
if (fp == NULL) { // 文件不存在(首次运行时)
return 0; // 返回默认值0
}
fscanf(fp, "%d", &highScore); // 从文件读取整数到 highScore
fclose(fp); // 关闭文件
return highScore; // 返回读取到的最高分
}
int main() {
// 读取历史最高分
int highest = loadHighScore();
printf("历史最高分: %d\\n", highest);
int currentScore = 85; // 假设当前游戏得分为85
printf("当前得分: %d\\n", currentScore);
// 如果当前分数超过历史最高分,则更新
if (currentScore > highest) {
printf("新纪录!保存新的最高分…\\n");
saveHighScore(currentScore); // 保存新记录
} else {
printf("未超过历史最高分\\n");
}
// 再次读取验证
printf("现在最高分: %d\\n", loadHighScore());
return 0;
}
5.5 文件操作的错误处理
知识点说明:
文件操作可能因为各种原因失败(磁盘满、权限不足、路径错误等),因此每次文件操作后都应该检查返回值。
FILE *fp = fopen("data.txt", "r");
// 必须检查 fopen 返回值
if (fp == NULL) {
printf("文件打开失败!\\n");
// 可以选择退出程序
exit(1); // exit(0) 正常退出,exit(非0) 异常退出
}
// … 进行文件操作 …
fclose(fp); // 操作完成后关闭文件
5.6 综合案例:完整的最高分管理模块
#include <stdio.h>
#include <stdlib.h>
#define SCORE_FILE "gamescore.dat" // 定义存储最高分的文件名
// 结构体:存储游戏记录
typedef struct {
int highScore; // 历史最高分
char playerName[20]; // 最高分保持者姓名
int playCount; // 游戏总次数
} GameRecord;
// 函数功能:从文件加载游戏记录
// 参数 record:指向 GameRecord 结构体的指针
void loadRecord(GameRecord *record) {
FILE *fp = fopen(SCORE_FILE, "r"); // 尝试打开文件
if (fp == NULL) {
// 文件不存在,初始化默认值
record->highScore = 0; // 最高分为0
strcpy(record->playerName, "无"); // 无记录保持者
record->playCount = 0; // 游戏次数为0
return;
}
// 从文件读取记录的三个字段
fscanf(fp, "%d %s %d",
&(record->highScore), // 读取最高分
record->playerName, // 读取玩家姓名
&(record->playCount)); // 读取游戏次数
fclose(fp); // 关闭文件
}
// 函数功能:将游戏记录保存到文件
// 参数 record:指向 GameRecord 结构体的指针
void saveRecord(GameRecord *record) {
FILE *fp = fopen(SCORE_FILE, "w"); // 以写模式打开文件
if (fp == NULL) {
printf("保存失败:无法写入文件!\\n");
return;
}
// 将记录的三个字段写入文件
fprintf(fp, "%d %s %d",
record->highScore, // 写入最高分
record->playerName, // 写入玩家姓名
record->playCount); // 写入游戏次数
fclose(fp); // 关闭文件
}
int main() {
GameRecord record; // 定义游戏记录结构体变量
loadRecord(&record); // 加载历史记录
printf("历史最高分: %d (保持者: %s)\\n",
record.highScore, record.playerName);
printf("历史游戏次数: %d\\n", record.playCount);
// 模拟新一局游戏
int newScore = 90; // 模拟新得分
record.playCount++; // 游戏次数加1
if (newScore > record.highScore) {
printf("新纪录!请输入你的名字: ");
scanf("%s", record.playerName); // 记录新纪录保持者
record.highScore = newScore; // 更新最高分
saveRecord(&record); // 保存到文件
printf("记录已保存!\\n");
}
return 0;
}
六、游戏结束界面显示
6.1 综合运用光标定位进行界面布局
知识点说明:
游戏结束界面需要显示:最终得分、是否刷新纪录、历史最高分、是否重新游戏等信息。通过 gotoxy 精确定位各元素位置,配合 printf 输出,实现美观的界面布局。
6.2 sprintf 格式化到字符串
知识点说明:
sprintf 的用法与 printf 相同,但输出目标不是屏幕,而是一个字符数组(字符串)。常用于将数字等数据格式化为字符串以便进一步处理。
#include <stdio.h>
int sprintf(char *str, const char *format, ...);
示例:
char buffer[50]; // 存储格式化结果的字符数组
int score = 85;
sprintf(buffer, "最终得分: %d 分", score); // 将格式化内容写入buffer
printf("%s\\n", buffer); // 输出:最终得分: 85 分
6.3 getchar 字符输入函数
知识点说明:
getchar() 从标准输入读取一个字符(包括回车符),有缓冲机制,需要按回车确认。
#include <stdio.h>
int getchar(void); // 返回读取到的字符(int类型),失败返回EOF
常见用法:
// 用法1:等待用户按回车
printf("按回车键继续…");
getchar(); // 等待回车
// 用法2:读取字符
char ch = getchar(); // 读取一个字符
注意: scanf 读取数据后缓冲区可能残留换行符 '\\n',后续 getchar() 会直接读到这个换行符。解决方法:
scanf("%d", &num);
getchar(); // 吃掉缓冲区中残留的换行符
ch = getchar(); // 现在才能正确等待用户输入
6.4 综合案例:游戏结束界面完整实现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <conio.h>
#include <windows.h>
// 光标定位函数
void gotoxy(int x, int y) {
COORD coord;
coord.X = x;
coord.Y = y;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
}
// 函数功能:显示游戏结束界面
// 参数 score:本局得分
// 参数 highScore:历史最高分
// 返回值:用户选择 1=重新开始,0=退出游戏
int showGameOver(int score, int highScore) {
system("cls"); // 清屏
// ======== 绘制边框 ========
gotoxy(20, 4);
printf("╔══════════════════════════════════════╗"); // 顶部边框
int i;
for (i = 5; i <= 18; i++) { // 左右边框
gotoxy(20, i);
printf("║ ║");
}
gotoxy(20, 19);
printf("╚══════════════════════════════════════╝"); // 底部边框
// ======== 标题 ========
gotoxy(33, 6);
printf("游 戏 结 束");
// ======== 水平分隔线 ========
gotoxy(23, 8);
printf("──────────────────────────────────");
// ======== 显示得分 ========
gotoxy(28, 10);
printf("本局得分: %d 分", score);
// ======== 判断是否刷新纪录 ========
gotoxy(28, 12);
if (score > highScore) {
printf("★ 恭喜!刷新历史最高分!★"); // 新纪录
gotoxy(28, 13);
printf(" 历史最高分: %d → %d", highScore, score);
} else {
printf("历史最高分: %d 分", highScore); // 未刷新
gotoxy(28, 13);
printf("还需努力哦!");
}
// ======== 水平分隔线 ========
gotoxy(23, 15);
printf("──────────────────────────────────");
// ======== 操作选项 ========
gotoxy(25, 17);
printf("1. 再来一局 0. 退出游戏");
// ======== 获取用户选择 ========
gotoxy(28, 21);
printf("请输入选择: ");
char ch = getch(); // 无回显读取按键
printf("%c", ch); // 回显选择
if (ch == '1') {
return 1; // 返回1表示重新开始
} else {
return 0; // 返回0表示退出
}
}
int main() {
int currentScore = 75; // 模拟当前得分
int highestScore = 60; // 模拟历史最高分
// 调用结束界面函数
int choice = showGameOver(currentScore, highestScore);
if (choice == 1) {
printf("\\n即将开始新一局…\\n");
// 这里可以调用游戏重新开始的逻辑
} else {
printf("\\n感谢游玩,再见!\\n");
}
return 0;
}
七、补充知识点
7.1 typedef 类型别名
知识点说明:
typedef 可以为已有的数据类型定义一个新的名字,提高代码可读性。
typedef 原类型名 新类型名;
示例:
typedef int Score; // Score 等价于 int
typedef char String20[20]; // String20 等价于 char[20]
Score s = 100; // 等价于 int s = 100;
String20 name = "hello"; // 等价于 char name[20] = "hello";
7.2 结构体 struct 基础
知识点说明:
结构体允许将不同类型的数据组合在一起,形成一个新的复合数据类型。
定义与使用:
// 定义结构体类型
struct Word {
char english[30]; // 英文单词
char chinese[30]; // 中文释义
int difficulty; // 难度等级
};
// 声明结构体变量并初始化
struct Word w1 = {"apple", "苹果", 1};
// 访问结构体成员用 "."
printf("单词: %s\\n", w1.english);
printf("释义: %s\\n", w1.chinese);
printf("难度: %d\\n", w1.difficulty);
用 typedef 简化结构体:
typedef struct {
char english[30];
char chinese[30];
int difficulty;
} Word; // Word 现在是类型名,不需要 struct 关键字
Word w1 = {"apple", "苹果", 1}; // 声明更简洁
7.3 函数的定义与调用
知识点说明:
将重复使用的功能封装成函数,可以提高代码复用性和可维护性。
函数定义的一般形式:
返回类型 函数名(参数类型 参数名, ...) {
函数体;
return 返回值; // void类型函数可省略return
}
入门案例:封装得分计算函数
#include <stdio.h>
// 函数功能:判断答案是否正确并计算得分
// 参数 correctAnswer:正确答案的索引(0~3)
// 参数 userAnswer:用户选择的索引(0~3)
// 参数 currentScore:当前累计得分
// 返回值:更新后的总得分
int checkAnswer(int correctAnswer, int userAnswer, int currentScore) {
if (correctAnswer == userAnswer) { // 判断是否答对
printf("回答正确!+20分\\n");
return currentScore + 20; // 答对加20分,返回新分数
} else {
printf("回答错误!正确答案是第 %d 个选项\\n", correctAnswer + 1);
return currentScore; // 答错不加分,返回原分数
}
}
// 函数功能:判断是否通关
// 参数 score:当前总得分
// 参数 totalRounds:总关数
// 返回值:1表示通关,0表示未通关
int isPass(int score, int totalRounds) {
int passScore = totalRounds * 60; // 通关最低分 = 关数 × 60
if (score >= passScore) {
return 1; // 达到通关分数
}
return 0; // 未达到
}
int main() {
int score = 0; // 初始化总分为0
// 模拟3道题的回答
score = checkAnswer(2, 2, score); // 正确答案是2,用户选了2(对)
score = checkAnswer(0, 3, score); // 正确答案是0,用户选了3(错)
score = checkAnswer(1, 1, score); // 正确答案是1,用户选了1(对)
printf("\\n最终得分: %d\\n", score); // 输出:最终得分: 40
// 判断是否通关
if (isPass(score, 3)) {
printf("恭喜通关!\\n");
} else {
printf("未通关,继续努力!\\n");
}
return 0;
}
7.4 程序暂停函数 Sleep
知识点说明:
Sleep() 函数(Windows平台)让程序暂停指定的毫秒数,常用于游戏中的延迟效果。
#include <windows.h>
Sleep(1000); // 暂停1000毫秒(即1秒)
- 参数单位是毫秒(1秒 = 1000毫秒)
- 注意 S 大写
- Linux平台下对应函数为 sleep(秒数)
示例(倒计时效果):
#include <stdio.h>
#include <windows.h>
int main() {
int i;
for (i = 3; i >= 1; i—) {
printf("\\r%d…", i); // \\r 回到行首,覆盖输出
fflush(stdout); // 刷新输出缓冲区
Sleep(1000); // 暂停1秒
}
printf("\\r开始! \\n"); // 覆盖之前的倒计时文字
return 0;
}
7.5 ASCII码与字符转换
知识点说明:
在C语言中,字符在内存中以ASCII码值存储。字符 '0' 到 '9' 的ASCII码是连续的(48~57),可以利用这一特性进行字符与数字的转换。
// 字符转数字
char ch = '5';
int num = ch – '0'; // num = 5('5'的ASCII码53 – '0'的ASCII码48 = 5)
// 数字转字符
int n = 7;
char c = n + '0'; // c = '7'(7 + 48 = 55,即字符'7'的ASCII码)
常用ASCII码值:
| '0' ~ '9' | 48 ~ 57 |
| 'A' ~ 'Z' | 65 ~ 90 |
| 'a' ~ 'z' | 97 ~ 122 |
| ' '(空格) | 32 |
| '\\0'(空字符) | 0 |
八、知识点总览
| 1 | 文件包含与宏定义 | 预处理模块 | #include, #define, #ifndef |
| 2 | 格式化输出 | 游戏开始界面 | printf, 转义字符, 格式说明符 |
| 3 | 光标定位 | 开始/结束界面 | gotoxy, COORD, SetConsoleCursorPosition |
| 4 | 二维字符数组 | 主功能 | char arr[行][列] |
| 5 | 字符串操作 | 主功能 | strcmp, strcpy, strlen |
| 6 | 随机数 | 主功能 | rand, srand, time |
| 7 | 多分支选择 | 主功能 | switch-case-break |
| 8 | 循环结构 | 主功能 | for, while, do-while |
| 9 | 条件判断与逻辑运算 | 主功能 | if-else, &&, ||, ! |
| 10 | 按键输入 | 主功能/结束界面 | getch, getchar |
| 11 | 文件指针与文件操作 | 最高分存储 | FILE *, fopen, fclose |
| 12 | 格式化文件读写 | 最高分存储 | fprintf, fscanf |
| 13 | 清屏与系统命令 | 各界面模块 | system("cls") |
| 14 | 结构体 | 补充 | struct, typedef |
| 15 | 函数定义与调用 | 补充 | 返回类型、参数、return |
| 16 | 程序暂停 | 补充 | Sleep |
| 17 | ASCII码转换 | 补充 | 字符与整数互转 |





