综合案例
13. 综合案例
在本章中,我们将通过几个综合案例,应用前面章节中学习到的 C 语言知识,深入理解和掌握 C 语言的实际应用。这些案例涵盖了不同的难点和应用场景,旨在帮助你巩固所学知识,并提升解决实际问题的能力。每个案例都包含详细的代码示例、完整的注释以及详细的解释,确保你能够全面理解每个程序的工作原理。
13.1 计算器程序
本案例将实现一个功能齐全的命令行计算器,支持基本的算术运算(加、减、乘、除)以及更复杂的运算,如取余和幂运算。计算器将具备用户友好的界面,能够连续进行多次计算,直到用户选择退出。
程序需求
-
基本功能
:
- 加法 (
+) - 减法 (
-) - 乘法 (
*) - 除法 (
/) - 取余 (
%) - 幂运算 (
^)
- 加法 (
-
用户界面
:
- 显示可用的操作符
- 提示用户输入操作符和操作数
- 显示计算结果
- 提供退出选项
-
错误处理
:
- 处理除以零错误
- 处理无效的操作符输入
- 处理输入格式错误
程序代码
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <locale.h>
#ifdef _WIN32
#include <io.h> // 包含 _setmode 和 _fileno
#include <fcntl.h> // 包含 _O_U8TEXT
#endif
// 函数声明
double add(double a, double b);
double subtract(double a, double b);
double multiply(double a, double b);
double divide(double a, double b);
int modulo(int a, int b);
double power_func(double a, double b);
void displayMenu();
int main() {
// 在Windows上设置控制台为UTF-8编码
#ifdef _WIN32
// 将标准输出设置为UTF-8模式
if (_setmode(_fileno(stdout), _O_U8TEXT) == -1) {
perror("设置标准输出为UTF-8失败");
return 1;
}
#endif
// 设置区域,支持宽字符
setlocale(LC_ALL, "");
char operator;
double num1, num2, result;
int int_num1, int_num2, mod_result;
int choice = 1;
// 使用宽字符输出中文标题
wprintf(L"===== 简易计算器 =====\n");
while(choice) {
displayMenu();
wprintf(L"请输入操作符: ");
scanf(" %c", &operator); // 注意在%c前加空格,跳过前面的换行符
// 判断操作符并执行相应的操作
switch(operator) {
case '+':
wprintf(L"请输入两个数(空格分隔): ");
if(scanf("%lf %lf", &num1, &num2) != 2) {
wprintf(L"输入格式错误,请输入两个数。\n");
// 清空输入缓冲区
while(getchar() != '\n');
break;
}
result = add(num1, num2);
wprintf(L"结果: %.2lf + %.2lf = %.2lf\n\n", num1, num2, result);
break;
case '-':
wprintf(L"请输入两个数(空格分隔): ");
if(scanf("%lf %lf", &num1, &num2) != 2) {
wprintf(L"输入格式错误,请输入两个数。\n");
while(getchar() != '\n');
break;
}
result = subtract(num1, num2);
wprintf(L"结果: %.2lf - %.2lf = %.2lf\n\n", num1, num2, result);
break;
case '*':
wprintf(L"请输入两个数(空格分隔): ");
if(scanf("%lf %lf", &num1, &num2) != 2) {
wprintf(L"输入格式错误,请输入两个数。\n");
while(getchar() != '\n');
break;
}
result = multiply(num1, num2);
wprintf(L"结果: %.2lf * %.2lf = %.2lf\n\n", num1, num2, result);
break;
case '/':
wprintf(L"请输入两个数(空格分隔): ");
if(scanf("%lf %lf", &num1, &num2) != 2) {
wprintf(L"输入格式错误,请输入两个数。\n");
while(getchar() != '\n');
break;
}
if(num2 == 0) {
wprintf(L"错误: 除数不能为零。\n\n");
break;
}
result = divide(num1, num2);
wprintf(L"结果: %.2lf / %.2lf = %.2lf\n\n", num1, num2, result);
break;
case '%':
wprintf(L"请输入两个整数(空格分隔): ");
if(scanf("%d %d", &int_num1, &int_num2) != 2) {
wprintf(L"输入格式错误,请输入两个整数。\n");
while(getchar() != '\n');
break;
}
if(int_num2 == 0) {
wprintf(L"错误: 除数不能为零。\n\n");
break;
}
mod_result = modulo(int_num1, int_num2);
wprintf(L"结果: %d %% %d = %d\n\n", int_num1, int_num2, mod_result);
break;
case '^':
wprintf(L"请输入底数和指数(空格分隔): ");
if(scanf("%lf %lf", &num1, &num2) != 2) {
wprintf(L"输入格式错误,请输入两个数。\n");
while(getchar() != '\n');
break;
}
result = power_func(num1, num2);
wprintf(L"结果: %.2lf ^ %.2lf = %.2lf\n\n", num1, num2, result);
break;
case 'q':
case 'Q':
wprintf(L"退出计算器。\n");
choice = 0;
break;
default:
wprintf(L"无效的操作符,请重新输入。\n\n");
}
}
// 使用宽字符输出中文关闭信息
wprintf(L"===== 计算器已关闭 =====\n");
return 0;
}
// 加法函数
double add(double a, double b) {
return a + b;
}
// 减法函数
double subtract(double a, double b) {
return a - b;
}
// 乘法函数
double multiply(double a, double b) {
return a * b;
}
// 除法函数
double divide(double a, double b) {
return a / b;
}
// 取余函数
int modulo(int a, int b) {
return a % b;
}
// 幂运算函数
double power_func(double a, double b) {
return pow(a, b);
}
// 显示操作菜单
void displayMenu() {
wprintf(L"请选择操作:\n");
wprintf(L" + : 加法\n");
wprintf(L" - : 减法\n");
wprintf(L" * : 乘法\n");
wprintf(L" / : 除法\n");
wprintf(L" %% : 取余\n");
wprintf(L" ^ : 幂运算\n");
wprintf(L" Q : 退出\n");
}
程序说明
- 函数定义:
- 加法、减法、乘法、除法、取余、幂运算:分别定义了对应的函数,简化主程序中的操作。
displayMenu:用于显示操作菜单,提示用户选择操作。
- 主函数:
- 使用一个
while循环,持续接受用户输入,直到用户选择退出(输入Q或q)。 - 通过
switch语句,根据用户输入的操作符执行相应的计算。 - 对每种操作,首先提示用户输入操作数,并进行输入格式和合法性检查。
- 特别注意处理除以零和数组越界等错误,确保程序的健壮性。
- 每次操作后,输出计算结果,并提供下一步操作的机会。
- 使用一个
- 错误处理:
- 使用
perror函数输出文件操作错误(在本例中未涉及文件操作,但保留以备扩展)。 - 检查用户输入是否符合预期格式,避免因输入错误导致程序异常。
- 使用
- 用户体验:
- 提供清晰的操作菜单,帮助用户理解可用的操作符。
- 支持连续计算,用户可以在一次运行中进行多次计算,直到选择退出。
示例运行
===== 简易计算器 =====
请选择操作:
+ : 加法
- : 减法
* : 乘法
/ : 除法
% : 取余
^ : 幂运算
Q : 退出
请输入操作符: +
请输入两个数(空格分隔): 10 20
结果: 10.00 + 20.00 = 30.00
请选择操作:
+ : 加法
- : 减法
* : 乘法
/ : 除法
% : 取余
^ : 幂运算
Q : 退出
请输入操作符: /
请输入两个数(空格分隔): 15 3
结果: 15.00 / 3.00 = 5.00
请选择操作:
+ : 加法
- : 减法
* : 乘法
/ : 除法
% : 取余
^ : 幂运算
Q : 退出
请输入操作符: %
请输入两个整数(空格分隔): 10 3
结果: 10 % 3 = 1
请选择操作:
+ : 加法
- : 减法
* : 乘法
/ : 除法
% : 取余
^ : 幂运算
Q : 退出
请输入操作符: Q
退出计算器。
===== 计算器已关闭 =====
13.2 文件统计工具
本案例将实现一个文件统计工具,能够统计指定文本文件中的行数、单词数和字符数。该工具将模仿 Unix/Linux 中的wc命令,提供命令行界面,允许用户输入文件路径,并输出统计结果。
程序需求
-
功能
:
- 统计文件中的行数
- 统计文件中的单词数
- 统计文件中的字符数
-
用户界面
:
- 提示用户输入文件路径
- 显示统计结果
-
错误处理
:
- 文件不存在或无法打开
- 处理大文件时的效率问题
程序代码
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <locale.h>
#include <string.h> // 添加了字符串处理函数头文件
#ifdef _WIN32
#include <io.h> // 包含 _setmode 和 _fileno
#include <fcntl.h> // 包含 _O_U8TEXT
#endif
// 函数声明
void countFileStatistics(const char *filename, int *lines, int *words, int *chars);
int main() {
// 在Windows上设置控制台为UTF-8编码
#ifdef _WIN32
// 将标准输出设置为UTF-8模式
if (_setmode(_fileno(stdout), _O_U8TEXT) == -1) {
perror("设置标准输出为UTF-8失败");
return 1;
}
#endif
// 设置区域,支持宽字符
setlocale(LC_ALL, "");
char filename[256];
int lines = 0, words = 0, chars = 0;
// 使用宽字符输出中文标题
wprintf(L"===== 文件统计工具 =====\n");
wprintf(L"请输入要统计的文件路径(或输入 'exit' 退出): ");
while(scanf("%255s", filename) == 1) {
// 检查是否退出
if(strcmp(filename, "exit") == 0 || strcmp(filename, "EXIT") == 0) {
wprintf(L"退出文件统计工具。\n");
break;
}
// 重置统计计数器
lines = words = chars = 0;
// 统计文件
countFileStatistics(filename, &lines, &words, &chars);
// 输出结果
// 使用宽字符输出函数wprintf
wprintf(L"文件: %hs\n", filename); // %hs 用于char*字符串
wprintf(L"行数: %d\n", lines);
wprintf(L"单词数: %d\n", words);
wprintf(L"字符数: %d\n\n", chars);
wprintf(L"请输入要统计的文件路径(或输入 'exit' 退出): ");
}
// 使用宽字符输出中文关闭信息
wprintf(L"===== 文件统计工具已关闭 =====\n");
return 0;
}
// 统计文件行数、单词数和字符数
void countFileStatistics(const char *filename, int *lines, int *words, int *chars) {
FILE *fp = fopen(filename, "r");
int c;
int in_word = 0; // 标志是否在单词中
if(fp == NULL) {
perror("打开文件失败");
return;
}
while((c = fgetc(fp)) != EOF) {
(*chars)++;
if(c == '\n') {
(*lines)++;
}
// 判断是否为单词的开始
if(isspace(c)) {
in_word = 0;
} else {
if(!in_word) {
in_word = 1;
(*words)++;
}
}
}
fclose(fp);
}
程序说明
-
函数定义:
-
countFileStatistics:
- 参数:
filename:要统计的文件路径。lines:指向行数计数器的指针。words:指向单词数计数器的指针。chars:指向字符数计数器的指针。
- 功能:
- 打开指定文件,逐字符读取内容。
- 统计行数、单词数和字符数。
- 处理单词的边界(通过空白字符判断单词的开始和结束)。
- 关闭文件。
- 参数:
-
-
主函数:
- 提示用户输入要统计的文件路径。
- 支持连续输入多个文件路径,直到用户输入
exit或EXIT退出。 - 调用
countFileStatistics函数进行统计,并输出结果。 - 错误处理:
- 如果文件无法打开,使用
perror输出错误信息,并提示用户重新输入。
- 如果文件无法打开,使用
-
错误处理:
- 检查文件是否成功打开,如果失败,输出错误信息。
- 处理用户输入错误,如文件路径错误或文件不存在。
-
用户体验:
- 提供简洁的命令行界面,方便用户输入和查看统计结果。
- 支持连续统计多个文件,无需重启程序。
示例运行
假设有一个文本文件sample.txt,内容如下:
Hello World!
This is a sample file.
It contains multiple lines,
words, and characters.
===== 文件统计工具 =====
请输入要统计的文件路径(或输入 'exit' 退出): sample.txt
文件: sample.txt
行数: 4
单词数: 11
字符数: 83
请输入要统计的文件路径(或输入 'exit' 退出): nonexistent.txt
打开文件失败: No such file or directory
文件: nonexistent.txt
行数: 0
单词数: 0
字符数: 0
请输入要统计的文件路径(或输入 'exit' 退出): exit
退出文件统计工具。
===== 文件统计工具已关闭 =====
13.3 学生成绩管理系统
本案例将实现一个简单的学生成绩管理系统,允许用户添加、查看、修改和删除学生记录。学生记录包括学生姓名、学号和成绩。数据将保存在一个二进制文件中,以实现数据的持久化存储。
程序需求
-
功能
:
- 添加新学生记录
- 查看所有学生记录
- 修改现有学生记录
- 删除学生记录
- 搜索学生记录(按学号或姓名)
- 保存数据到文件
- 从文件加载数据
-
数据存储
:
- 使用二进制文件存储学生记录,确保数据的完整性和安全性。
-
用户界面
:
- 提供命令行菜单,允许用户选择操作。
-
错误处理
:
- 处理文件操作错误
- 处理输入错误
- 确保数据一致性
程序代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <locale.h>
#include <wchar.h> // 宽字符支持
#ifdef _WIN32
#include <io.h> // 包含 _setmode 和 _fileno
#include <fcntl.h> // 包含 _O_U8TEXT
#endif
// 定义学生结构体
typedef struct {
char name[50];
int id;
float score;
} Student;
// 函数声明
void addStudent(const char *filename);
void viewStudents(const char *filename);
void modifyStudent(const char *filename);
void deleteStudent(const char *filename);
void searchStudent(const char *filename);
void displayMenu();
int main() {
// 在Windows上设置控制台为UTF-8编码
#ifdef _WIN32
// 将标准输出设置为UTF-8模式
if (_setmode(_fileno(stdout), _O_U8TEXT) == -1) {
perror("设置标准输出为UTF-8失败");
return 1;
}
#endif
// 设置区域,支持宽字符
setlocale(LC_ALL, "");
int choice;
const char *filename = "students.dat";
// 使用宽字符输出中文标题
wprintf(L"===== 学生成绩管理系统 =====\n");
while(1) {
displayMenu();
wprintf(L"请输入您的选择: ");
if(scanf("%d", &choice) != 1) {
wprintf(L"输入无效,请输入数字。\n");
// 清空输入缓冲区
while(getchar() != '\n');
continue;
}
switch(choice) {
case 1:
addStudent(filename);
break;
case 2:
viewStudents(filename);
break;
case 3:
modifyStudent(filename);
break;
case 4:
deleteStudent(filename);
break;
case 5:
searchStudent(filename);
break;
case 6:
wprintf(L"退出学生成绩管理系统。\n");
wprintf(L"===== 学生成绩管理系统已关闭 =====\n");
exit(0);
default:
wprintf(L"无效的选择,请重新输入。\n");
}
}
return 0;
}
// 显示菜单
void displayMenu() {
wprintf(L"\n请选择操作:\n");
wprintf(L"1. 添加新学生记录\n");
wprintf(L"2. 查看所有学生记录\n");
wprintf(L"3. 修改学生记录\n");
wprintf(L"4. 删除学生记录\n");
wprintf(L"5. 搜索学生记录\n");
wprintf(L"6. 退出\n");
}
// 添加新学生记录
void addStudent(const char *filename) {
FILE *fp = fopen(filename, "ab"); // 以追加二进制模式打开文件
Student s;
if(fp == NULL) {
perror("无法打开文件");
return;
}
wprintf(L"===== 添加新学生 =====\n");
wprintf(L"请输入学生姓名: ");
// 使用宽字符输入读取中文姓名
// 由于结构体中的name是char数组,使用多字节字符输入
scanf(" %[^\n]", s.name); // 读取包含空格的字符串
wprintf(L"请输入学生学号: ");
scanf("%d", &s.id);
wprintf(L"请输入学生成绩: ");
scanf("%f", &s.score);
// 写入学生记录到文件
fwrite(&s, sizeof(Student), 1, fp);
wprintf(L"学生记录已添加。\n");
fclose(fp);
}
// 查看所有学生记录
void viewStudents(const char *filename) {
FILE *fp = fopen(filename, "rb"); // 以二进制读取模式打开文件
Student s;
int count = 0;
if(fp == NULL) {
perror("无法打开文件");
return;
}
wprintf(L"===== 所有学生记录 =====\n");
while(fread(&s, sizeof(Student), 1, fp) == 1) {
wprintf(L"学生%d:\n", ++count);
wprintf(L" 姓名: %hs\n", s.name); // %hs 用于输出 char* 字符串
wprintf(L" 学号: %d\n", s.id);
wprintf(L" 成绩: %.2f\n\n", s.score);
}
if(count == 0) {
wprintf(L"没有学生记录。\n");
}
fclose(fp);
}
// 修改学生记录
void modifyStudent(const char *filename) {
FILE *fp = fopen(filename, "r+b"); // 以读写二进制模式打开文件
Student s;
int target_id;
int found = 0;
if(fp == NULL) {
perror("无法打开文件");
return;
}
wprintf(L"===== 修改学生记录 =====\n");
wprintf(L"请输入要修改的学生学号: ");
scanf("%d", &target_id);
while(fread(&s, sizeof(Student), 1, fp) == 1) {
if(s.id == target_id) {
wprintf(L"找到学生: %hs (学号: %d, 成绩: %.2f)\n", s.name, s.id, s.score);
wprintf(L"请输入新的姓名: ");
scanf(" %[^\n]", s.name);
wprintf(L"请输入新的成绩: ");
scanf("%f", &s.score);
// 获取当前文件指针的位置
long pos = ftell(fp);
// 移动文件指针到当前记录的起始位置
fseek(fp, pos - sizeof(Student), SEEK_SET);
// 写入修改后的记录
fwrite(&s, sizeof(Student), 1, fp);
wprintf(L"学生记录已更新。\n");
found = 1;
break;
}
}
if(!found) {
wprintf(L"未找到学号为 %d 的学生记录。\n", target_id);
}
fclose(fp);
}
// 删除学生记录
void deleteStudent(const char *filename) {
FILE *fp = fopen(filename, "rb"); // 以二进制读取模式打开原文件
FILE *temp = fopen("temp.dat", "wb"); // 打开临时文件用于存储删除后的数据
Student s;
int target_id;
int found = 0;
if(fp == NULL) {
perror("无法打开原文件");
return;
}
if(temp == NULL) {
perror("无法创建临时文件");
fclose(fp);
return;
}
wprintf(L"===== 删除学生记录 =====\n");
wprintf(L"请输入要删除的学生学号: ");
scanf("%d", &target_id);
while(fread(&s, sizeof(Student), 1, fp) == 1) {
if(s.id == target_id) {
wprintf(L"删除学生: %hs (学号: %d, 成绩: %.2f)\n", s.name, s.id, s.score);
found = 1;
// 不将该记录写入临时文件,实现删除效果
continue;
}
// 写入其他记录到临时文件
fwrite(&s, sizeof(Student), 1, temp);
}
if(!found) {
wprintf(L"未找到学号为 %d 的学生记录。\n", target_id);
} else {
wprintf(L"学生记录已删除。\n");
}
fclose(fp);
fclose(temp);
// 删除原文件
if(remove(filename) != 0) {
perror("删除原文件失败");
return;
}
// 重命名临时文件为原文件名
if(rename("temp.dat", filename) != 0) {
perror("重命名临时文件失败");
return;
}
}
// 搜索学生记录
void searchStudent(const char *filename) {
FILE *fp = fopen(filename, "rb"); // 以二进制读取模式打开文件
Student s;
int choice;
int target_id;
char target_name[50];
int found = 0;
if(fp == NULL) {
perror("无法打开文件");
return;
}
wprintf(L"===== 搜索学生记录 =====\n");
wprintf(L"选择搜索方式:\n");
wprintf(L"1. 按学号搜索\n");
wprintf(L"2. 按姓名搜索\n");
wprintf(L"请输入选择: ");
if(scanf("%d", &choice) != 1) {
wprintf(L"输入无效。\n");
while(getchar() != '\n');
fclose(fp);
return;
}
if(choice == 1) {
wprintf(L"请输入学生学号: ");
scanf("%d", &target_id);
while(fread(&s, sizeof(Student), 1, fp) == 1) {
if(s.id == target_id) {
wprintf(L"找到学生:\n");
wprintf(L" 姓名: %hs\n", s.name);
wprintf(L" 学号: %d\n", s.id);
wprintf(L" 成绩: %.2f\n", s.score);
found = 1;
break;
}
}
if(!found) {
wprintf(L"未找到学号为 %d 的学生记录。\n", target_id);
}
}
else if(choice == 2) {
wprintf(L"请输入学生姓名: ");
scanf(" %[^\n]", target_name);
while(fread(&s, sizeof(Student), 1, fp) == 1) {
if(strcmp(s.name, target_name) == 0) {
wprintf(L"找到学生:\n");
wprintf(L" 姓名: %hs\n", s.name);
wprintf(L" 学号: %d\n", s.id);
wprintf(L" 成绩: %.2f\n", s.score);
found = 1;
break;
}
}
if(!found) {
wprintf(L"未找到姓名为 %hs 的学生记录。\n", target_name);
}
}
else {
wprintf(L"无效的选择。\n");
}
fclose(fp);
}
程序说明
-
结构体定义:
Student:包含学生的姓名(字符串)、学号(整数)和成绩(浮点数)。
-
函数定义:
-
addStudent:
- 打开文件以追加二进制模式,添加新学生记录。
- 提示用户输入学生姓名、学号和成绩。
- 将新记录写入文件。
-
viewStudents:
- 打开文件以二进制读取模式,遍历并显示所有学生记录。
- 如果文件为空,提示无记录。
-
modifyStudent:
- 打开文件以读写二进制模式。
- 提示用户输入要修改的学生学号。
- 遍历文件,查找匹配的记录。
- 提示用户输入新的姓名和成绩,并更新记录。
-
deleteStudent:
- 打开原文件以二进制读取模式,打开临时文件以二进制写入模式。
- 提示用户输入要删除的学生学号。
- 遍历原文件,复制非目标记录到临时文件,实现删除效果。
- 关闭文件后,删除原文件,重命名临时文件为原文件名。
-
searchStudent:
- 打开文件以二进制读取模式。
- 提供按学号或按姓名搜索的选项。
- 根据用户选择,提示输入相应的搜索关键字,并遍历文件查找匹配的记录。
-
-
主函数:
- 显示菜单,提示用户选择操作。
- 根据用户输入,调用相应的函数执行操作。
- 支持连续操作,直到用户选择退出。
-
错误处理:
- 检查文件是否成功打开,如果失败,输出错误信息。
- 处理用户输入错误,如输入格式不正确。
- 确保文件操作的正确性,如修改和删除操作确保目标记录存在。
-
数据持久化:
- 使用二进制文件
students.dat存储学生记录,确保数据在程序运行结束后依然存在。
- 使用二进制文件
示例运行
===== 学生成绩管理系统 =====
请选择操作:
1. 添加新学生记录
2. 查看所有学生记录
3. 修改学生记录
4. 删除学生记录
5. 搜索学生记录
6. 退出
请输入您的选择: 1
===== 添加新学生 =====
请输入学生姓名: 张三
请输入学生学号: 1001
请输入学生成绩: 85.5
学生记录已添加。
请选择操作:
1. 添加新学生记录
2. 查看所有学生记录
3. 修改学生记录
4. 删除学生记录
5. 搜索学生记录
6. 退出
请输入您的选择: 1
===== 添加新学生 =====
请输入学生姓名: 李四
请输入学生学号: 1002
请输入学生成绩: 90.0
学生记录已添加。
请选择操作:
1. 添加新学生记录
2. 查看所有学生记录
3. 修改学生记录
4. 删除学生记录
5. 搜索学生记录
6. 退出
请输入您的选择: 2
===== 所有学生记录 =====
学生1:
姓名: 张三
学号: 1001
成绩: 85.50
学生2:
姓名: 李四
学号: 1002
成绩: 90.00
请选择操作:
1. 添加新学生记录
2. 查看所有学生记录
3. 修改学生记录
4. 删除学生记录
5. 搜索学生记录
6. 退出
请输入您的选择: 3
===== 修改学生记录 =====
请输入要修改的学生学号: 1001
找到学生: 张三 (学号: 1001, 成绩: 85.50)
请输入新的姓名: 张三丰
请输入新的成绩: 88.0
学生记录已更新。
请选择操作:
1. 添加新学生记录
2. 查看所有学生记录
3. 修改学生记录
4. 删除学生记录
5. 搜索学生记录
6. 退出
请输入您的选择: 2
===== 所有学生记录 =====
学生1:
姓名: 张三丰
学号: 1001
成绩: 88.00
学生2:
姓名: 李四
学号: 1002
成绩: 90.00
请选择操作:
1. 添加新学生记录
2. 查看所有学生记录
3. 修改学生记录
4. 删除学生记录
5. 搜索学生记录
6. 退出
请输入您的选择: 4
===== 删除学生记录 =====
请输入要删除的学生学号: 1002
删除学生: 李四 (学号: 1002, 成绩: 90.00)
学生记录已删除。
请选择操作:
1. 添加新学生记录
2. 查看所有学生记录
3. 修改学生记录
4. 删除学生记录
5. 搜索学生记录
6. 退出
请输入您的选择: 2
===== 所有学生记录 =====
学生1:
姓名: 张三丰
学号: 1001
成绩: 88.00
请选择操作:
1. 添加新学生记录
2. 查看所有学生记录
3. 修改学生记录
4. 删除学生记录
5. 搜索学生记录
6. 退出
请输入您的选择: 5
===== 搜索学生记录 =====
选择搜索方式:
1. 按学号搜索
2. 按姓名搜索
请输入选择: 1
请输入学生学号: 1001
找到学生:
姓名: 张三丰
学号: 1001
成绩: 88.00
请选择操作:
1. 添加新学生记录
2. 查看所有学生记录
3. 修改学生记录
4. 删除学生记录
5. 搜索学生记录
6. 退出
请输入您的选择: 6
退出学生成绩管理系统。
13.4 数据排序与查找
本案例将实现一个数据排序与查找程序,允许用户输入一组整数,然后选择不同的排序算法对数据进行排序,并提供查找功能。程序将实现以下功能:
-
排序算法
:
- 冒泡排序
- 选择排序
- 插入排序
- 快速排序
- 归并排序
-
查找算法
:
- 线性查找
- 二分查找
-
用户界面
:
- 提示用户输入数据
- 提供排序和查找的选项
- 显示排序后的数据和查找结果
-
错误处理
:
- 处理输入错误
- 确保数据有效性
程序代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <locale.h>
#include <wchar.h> // 宽字符支持
#ifdef _WIN32
#include <io.h> // 包含 _setmode 和 _fileno
#include <fcntl.h> // 包含 _O_U8TEXT
#endif
// 函数声明
void bubbleSort(int arr[], int n);
void selectionSort(int arr[], int n);
void insertionSort(int arr[], int n);
void quickSort(int arr[], int low, int high);
int partition(int arr[], int low, int high);
void mergeSort(int arr[], int left, int right);
void merge(int arr[], int left, int mid, int right);
void displayArray(int arr[], int n);
int linearSearch(int arr[], int n, int target);
int binarySearch(int arr[], int n, int target);
int compare(const void *a, const void *b);
void displayMenu();
int main() {
#ifdef _WIN32
// 设置标准输出为 UTF-8 模式
if (_setmode(_fileno(stdout), _O_U8TEXT) == -1) {
perror("设置标准输出为 UTF-8 失败");
return 1;
}
#endif
setlocale(LC_ALL, ""); // 设置区域支持宽字符
int n, choice, sort_choice, search_choice, target;
int *arr = NULL;
int sorted = 0; // 标志数组是否已排序
wprintf(L"===== 数据排序与查找工具 =====\n");
wprintf(L"请输入要输入的整数个数: ");
if (wscanf(L"%d", &n) != 1 || n <= 0) {
wprintf(L"输入无效,请输入一个正整数。\n");
return 1;
}
// 动态分配内存
arr = (int *)malloc(n * sizeof(int));
if (arr == NULL) {
wprintf(L"内存分配失败。\n");
return 1;
}
wprintf(L"请输入 %d 个整数(空格分隔): ", n);
for (int i = 0; i < n; i++) {
if (wscanf(L"%d", &arr[i]) != 1) {
wprintf(L"输入无效,请输入整数。\n");
free(arr);
return 1;
}
}
while (1) {
displayMenu();
wprintf(L"请输入您的选择: ");
if (wscanf(L"%d", &choice) != 1) {
wprintf(L"输入无效,请输入数字。\n");
while (getwchar() != L'\n'); // 清理缓冲区
continue;
}
switch (choice) {
case 1: // 数据排序
wprintf(L"选择排序算法:\n");
wprintf(L"1. 冒泡排序\n");
wprintf(L"2. 选择排序\n");
wprintf(L"3. 插入排序\n");
wprintf(L"4. 快速排序\n");
wprintf(L"5. 归并排序\n");
wprintf(L"请输入排序算法的选择: ");
if (wscanf(L"%d", &sort_choice) != 1) {
wprintf(L"输入无效。\n");
while (getwchar() != L'\n'); // 清理缓冲区
break;
}
switch (sort_choice) {
case 1:
bubbleSort(arr, n);
wprintf(L"使用冒泡排序后的数组:\n");
displayArray(arr, n);
sorted = 1;
break;
case 2:
selectionSort(arr, n);
wprintf(L"使用选择排序后的数组:\n");
displayArray(arr, n);
sorted = 1;
break;
case 3:
insertionSort(arr, n);
wprintf(L"使用插入排序后的数组:\n");
displayArray(arr, n);
sorted = 1;
break;
case 4:
quickSort(arr, 0, n - 1);
wprintf(L"使用快速排序后的数组:\n");
displayArray(arr, n);
sorted = 1;
break;
case 5:
mergeSort(arr, 0, n - 1);
wprintf(L"使用归并排序后的数组:\n");
displayArray(arr, n);
sorted = 1;
break;
default:
wprintf(L"无效的排序算法选择。\n");
}
break;
case 2: // 数据查找
wprintf(L"选择查找算法:\n");
wprintf(L"1. 线性查找\n");
wprintf(L"2. 二分查找\n");
wprintf(L"请输入查找算法的选择: ");
if (wscanf(L"%d", &search_choice) != 1) {
wprintf(L"输入无效。\n");
while (getwchar() != L'\n'); // 清理缓冲区
break;
}
wprintf(L"请输入要查找的整数: ");
if (wscanf(L"%d", &target) != 1) {
wprintf(L"输入无效,请输入整数。\n");
while (getwchar() != L'\n'); // 清理缓冲区
break;
}
if (search_choice == 1) {
int index = linearSearch(arr, n, target);
if (index != -1) {
wprintf(L"找到整数 %d,在数组中的索引为 %d。\n", target, index);
} else {
wprintf(L"未找到整数 %d。\n", target);
}
} else if (search_choice == 2) {
if (!sorted) {
wprintf(L"请先对数组进行排序,以使用二分查找。\n");
break;
}
int index = binarySearch(arr, n, target);
if (index != -1) {
wprintf(L"找到整数 %d,在数组中的索引为 %d。\n", target, index);
} else {
wprintf(L"未找到整数 %d。\n", target);
}
} else {
wprintf(L"无效的查找算法选择。\n");
}
break;
case 3: // 使用标准库函数 qsort
qsort(arr, n, sizeof(int), compare);
wprintf(L"使用 qsort 排序后的数组:\n");
displayArray(arr, n);
sorted = 1;
break;
case 4: // 退出程序
wprintf(L"退出程序。\n");
free(arr);
return 0;
default:
wprintf(L"无效的选择,请重新输入。\n");
}
}
return 0;
}
// 功能函数实现部分
void displayMenu() {
wprintf(L"\n===== 操作菜单 =====\n");
wprintf(L"1. 数据排序\n");
wprintf(L"2. 数据查找\n");
wprintf(L"3. 使用 qsort 进行排序\n");
wprintf(L"4. 退出\n");
}
void bubbleSort(int arr[], int n) {
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
void selectionSort(int arr[], int n) {
for (int i = 0; i < n - 1; i++) {
int minIdx = i;
for (int j = i + 1; j < n; j++) {
if (arr[j] < arr[minIdx]) {
minIdx = j;
}
}
int temp = arr[i];
arr[i] = arr[minIdx];
arr[minIdx] = temp;
}
}
void insertionSort(int arr[], int n) {
for (int i = 1; i < n; i++) {
int key = arr[i];
int j = i - 1;
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = key;
}
}
int partition(int arr[], int low, int high) {
int pivot = arr[high];
int i = low - 1;
for (int j = low; j < high; j++) {
if (arr[j] <= pivot) {
i++;
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
int temp = arr[i + 1];
arr[i + 1] = arr[high];
arr[high] = temp;
return i + 1;
}
void quickSort(int arr[], int low, int high) {
if (low < high) {
int pi = partition(arr, low, high);
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}
void merge(int arr[], int left, int mid, int right) {
int n1 = mid - left + 1;
int n2 = right - mid;
int *L = malloc(n1 * sizeof(int));
int *R = malloc(n2 * sizeof(int));
for (int i = 0; i < n1; i++)
L[i] = arr[left + i];
for (int j = 0; j < n2; j++)
R[j] = arr[mid + 1 + j];
int i = 0, j = 0, k = left;
while (i < n1 && j < n2) {
if (L[i] <= R[j]) {
arr[k] = L[i];
i++;
} else {
arr[k] = R[j];
j++;
}
k++;
}
while (i < n1) {
arr[k] = L[i];
i++;
k++;
}
while (j < n2) {
arr[k] = R[j];
j++;
k++;
}
free(L);
free(R);
}
void mergeSort(int arr[], int left, int right) {
if (left < right) {
int mid = left + (right - left) / 2;
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
merge(arr, left, mid, right);
}
}
void displayArray(int arr[], int n) {
wprintf(L"数组内容: ");
for (int i = 0; i < n; i++) {
wprintf(L"%d ", arr[i]);
}
wprintf(L"\n");
}
int linearSearch(int arr[], int n, int target) {
for (int i = 0; i < n; i++) {
if (arr[i] == target)
return i;
}
return -1;
}
int binarySearch(int arr[], int n, int target) {
int left = 0, right = n - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (arr[mid] == target)
return mid;
else if (arr[mid] < target)
left = mid + 1;
else
right = mid - 1;
}
return -1;
}
int compare(const void *a, const void *b) {
return (*(int *)a - *(int *)b);
}
程序说明
-
函数定义:
-
排序算法
:
bubbleSort:实现冒泡排序,通过多次交换相邻逆序对,逐步将最大元素“冒”到数组末端。selectionSort:实现选择排序,每次选择未排序部分的最小元素,放到已排序部分的末尾。insertionSort:实现插入排序,通过将未排序元素插入到已排序部分的正确位置。quickSort:实现快速排序,选择基准元素,将数组分为左右两部分,递归排序。mergeSort:实现归并排序,将数组分割成更小的部分,递归排序后合并。
-
查找算法
:
linearSearch:实现线性查找,从头到尾依次比较,找到目标元素返回其索引。binarySearch:实现二分查找,要求数组已排序,通过逐步缩小搜索范围找到目标元素。
-
辅助函数
:
displayArray:打印数组内容。compare:用于qsort的比较函数。
-
-
主函数:
- 提示用户输入要排序的整数个数,并动态分配内存存储数据。
- 提示用户输入数据,并存储到数组中。
- 进入操作循环,提供排序和查找的选项:
- 排序:用户选择排序算法,执行相应的排序,并显示排序后的数组。
- 查找:用户选择查找算法,输入目标值,执行查找,并显示结果。注意,二分查找要求数组已排序。
- 使用
qsort:调用标准库函数qsort对数组进行排序,展示其简便性。 - 退出:释放动态分配的内存,退出程序。
- 错误处理:
- 检查用户输入的有效性。
- 确保动态内存分配成功。
-
内存管理:
- 使用
malloc动态分配内存存储用户输入的数据。 - 在程序结束前,通过
free释放内存,避免内存泄漏。
- 使用
-
用户体验:
- 提供清晰的操作菜单,方便用户选择所需的功能。
- 提供详细的操作反馈,如排序后的数组内容和查找结果。
- 支持多次操作,直到用户选择退出。
示例运行
===== 数据排序与查找工具 =====
请输入要输入的整数个数: 8
请输入 8 个整数(空格分隔): 34 7 23 32 5 62 32 5
===== 操作菜单 =====
1. 数据排序
2. 数据查找
3. 使用 qsort 进行排序
4. 退出
请输入您的选择: 1
选择排序算法:
1. 冒泡排序
2. 选择排序
3. 插入排序
4. 快速排序
5. 归并排序
请输入排序算法的选择: 4
===== 快速排序 =====
使用快速排序后的数组:
数组内容: 5 5 7 23 32 32 34 62
===== 操作菜单 =====
1. 数据排序
2. 数据查找
3. 使用 qsort 进行排序
4. 退出
请输入您的选择: 2
选择查找算法:
1. 线性查找
2. 二分查找
请输入查找算法的选择: 2
请输入要查找的整数: 23
找到整数 23,在数组中的索引为 3。
===== 操作菜单 =====
1. 数据排序
2. 数据查找
3. 使用 qsort 进行排序
4. 退出
请输入您的选择: 3
使用标准库函数 qsort 进行排序。
使用 qsort 排序后的数组:
数组内容: 5 5 7 23 32 32 34 62
===== 操作菜单 =====
1. 数据排序
2. 数据查找
3. 使用 qsort 进行排序
4. 退出
请输入您的选择: 4
退出