C语言——管理系统
1 整体代码
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
#include <string.h>
enum Operator { Quit, Insert, Show, Search, Modify, Delete, Sort };
//1 创建系统
typedef struct MM {
char name[20];
int age;
int num;
char addr[20];
}MM;
typedef struct SystemInfo {
MM* arr;
int curSize;
int maxSize;
}SystemInfo;
SystemInfo* create_system(int maxSize) {
SystemInfo* s = (SystemInfo*)malloc(sizeof(SystemInfo));
assert(s);
s->curSize = 0;
s->maxSize = maxSize;
s->arr = (MM*)malloc(sizeof(MM) * maxSize);
return s;
}
//2 打印菜单和系统
void print_system(SystemInfo* s) {
printf("姓名\t年龄\t编号\t住址\n");
for (int i = 0; i < s->curSize; i++) {
printf("%s\t%d\t%d\t%s\n", s->arr[i].name, s->arr[i].age, s->arr[i].num, s->arr[i].addr);
}
}
void print_menu()
{
printf("----------------------------\n");
printf("\t0.退出系统\n");
printf("\t1.录入系统\n");
printf("\t2.显示信息\n");
printf("\t3.查找信息\n");
printf("\t4.修改信息\n");
printf("\t5.删除信息\n");
printf("\t6.排序信息\n");
printf("----------------------------\n");
}
//3 插入
void insert_system(SystemInfo* s, MM data) {
//自动增长
if (s->curSize == s->maxSize) {
s->maxSize *= 2;
MM* temp = realloc(s->arr, sizeof(MM) * s->maxSize);
s->arr = temp;
}
s->arr[s->curSize++] = data;
}
void insert_data(SystemInfo* s) {
while (true) {
MM temp;
printf("请输入MM信息,(用空格隔开)返回请按T\n(name,age,num,addr):");
while (getchar() != '\n');
int key1 = getchar();
if (key1 == 'T' || key1 == 't') {
break;
}
scanf_s("%s%d%d%s", temp.name, 20, &temp.age, &temp.num, temp.addr, 20);
insert_system(s, temp);
printf("是否继续输入(按Y继续/按N返回)");
while (getchar() != '\n');
int key = getchar();
if (key == 'N' || key == 'n') {
break;
}
}
}
//4 查找
//按3种方式查找
void search_by_name(SystemInfo* s) {
MM temp;
printf("请输入查找姓名:");
while (getchar() != '\n');
scanf_s("%s", temp.name, 20);
printf("姓名\t年龄\t编号\t住址\n");
for (int i = 0; i < s->curSize; i++) {
if (strcmp(s->arr[i].name, temp.name) == 0) {
printf("%s\t%d\t%d\t%s\n", s->arr[i].name, s->arr[i].age, s->arr[i].num, s->arr[i].addr);
break;
}
}
}
void search_by_age(SystemInfo* s) {
//MM temp;没用哈哈哈
int begin = 0;
int end = 0;
printf("请输入查找年龄的范围:(两个数中间用空格隔开)");
while (getchar() != '\n');
scanf_s("%d%d", &begin, &end);
printf("姓名\t年龄\t编号\t住址\n");
for (int i = 0; i < s->curSize; i++) {
if (s->arr[i].age >= begin && s->arr[i].age <= end) {
printf("%s\t%d\t%d\t%s\n", s->arr[i].name, s->arr[i].age, s->arr[i].num, s->arr[i].addr);
}
}
}
void search_by_address(SystemInfo* s) {
MM temp;
printf("请输入要查找的地址:");
while (getchar() != '\n');
scanf_s("%s", temp.addr, 20);
printf("姓名\t年龄\t编号\t住址\n");
for (int i = 0; i < s->curSize; i++) {
if (strcmp(s->arr[i].addr, temp.addr) == 0) {
printf("%s\t%d\t%d\t%s\n", s->arr[i].name, s->arr[i].age, s->arr[i].num, s->arr[i].addr);
}
}
}
void search_system(SystemInfo* s) {
while (true) {
printf("---------------\n");
printf("0.退出查找\n");
printf("1.按姓名查\n");
printf("2.按年龄区间\n");
printf("3.按住址查找\n");
printf("---------------\n");
int key = 0;
scanf_s("%d", &key);
switch (key) {
case 0:
printf("退出成功!");
break;
case 1:
search_by_name(s);
break;
case 2:
search_by_age(s);
break;
case 3:
search_by_address(s);
break;
default:
printf("无效选项,请重新选择。\n");
break;
}
if (key == 0)
break;
}
}
//5 修改
//一般做修改和删除类操作,通常是用唯一属性来作为参照
//姓名会重复,但编号不会
void modify_system(SystemInfo* s) {
int num = 0;
printf("请输入要修改的编号");
scanf_s("%d", &num);
//找位置
int pos = -1;
for (int i = 0; i < s->curSize; i++) {
if (num == s->arr[i].num) {
pos = i;
break;
}
}
if (pos == -1) {
printf("没有找到,无法修改!");
}
else {
printf("请输入新的信息:name,age,num,addr");
scanf_s("%s%d%d%s", s->arr[pos].name, 20, &s->arr[pos].age, &s->arr[pos].num, s->arr[pos].addr, 20);
printf("修改成功");
}
}
//6 删除
void delete_system(SystemInfo* s)
{
int num;
printf("输入删除mm的编号:");
scanf_s("%d", &num);
//找位置
int pos = -1;
for (int i = 0; i < s->curSize; i++)
{
if (s->arr[i].num == num)
{
pos = i;
break;
}
}
if (pos == -1)
{
printf("没有找到,无法删除!\n");
}
else
{
for (int i = pos; i < s->curSize - 1; i++)
{
s->arr[i] = s->arr[i + 1];
}
s->curSize--;
printf("删除成功!\n");
}
}
//7 排序
bool compare_age(MM one, MM two)
{
return one.age > two.age;
}
bool compare_name(MM one, MM two)
{
return strcmp(one.name, two.name) > 0;
}
void sort_system(SystemInfo* s, bool(*compare)(MM one, MM two))
{
for (int i = 0; i < s->curSize; i++)
{
for (int j = 0; j < s->curSize - i - 1; j++)
{
if (compare(s->arr[j], s->arr[j + 1]))
{
MM temp = s->arr[j];
s->arr[j] = s->arr[j + 1];
s->arr[j + 1] = temp;
}
}
}
}
//8 文件操作
void save_file(SystemInfo* s, const char* filename) {
FILE* fp = fopen(filename, "w");
for (int i = 0; i < s->curSize; i++)
{
fprintf(fp, "%s\t%d\t%d\t%s\n", s->arr[i].name, s->arr[i].age, s->arr[i].num, s->arr[i].addr);
}
fclose(fp);
}
void load_file(SystemInfo* s, const char* filename)
{
FILE* fp = fopen(filename, "r");
if (fp == NULL) //第一次打开文件会没有,加个判定
{
fp = fopen(filename, "w+");
fclose(fp);
}
else
{
MM temp;
while (fscanf(fp, "%s\t%d\t%d\t%s\n", temp.name, &temp.age, &temp.num, temp.addr) != EOF)
{
insert_system(s, temp);
}
fclose(fp);
}
}
// 按键交互
//enum Operator { Quit, Insert, Show, Search, Modify, Delete, Sort };
void keydown_system(SystemInfo* s) {
int key = 0;
scanf_s("%d", &key);
switch (key) {
case Quit:
printf("退出成功!!!");
save_file(s, "mm.txt");
exit(0);
break;
case Insert:
insert_data(s);
save_file(s, "mm.txt");
print_system(s);
break;
case Show:
print_system(s);
break;
case Search:
search_system(s);
break;
case Modify:
modify_system(s);
save_file(s, "mm.txt");
print_system(s);
break;
case Delete:
delete_system(s);
save_file(s, "mm.txt");
print_system(s);
break;
case Sort:
sort_system(s, compare_age);
print_system(s);
break;
default:
printf("无效选项,请重新选择。\n");
break;
}
}
int main() {
SystemInfo* s = create_system(10);
load_file(s, "mm.txt");
while (true) {
print_menu();
keydown_system(s);
system("pause");
system("cls");
}
return 0;
}
2 while (getchar() != ‘\n’);到底为什么这样用
在 C 语言中,while (getchar() != '\n');
这行代码确实常用于清除输入缓冲区中的换行符。这种用法通常出现在读取了一行文本之后,目的是清空缓冲区中剩余的换行符,以防止它影响后续的输入读取。
详细解释
当你使用 scanf
、fgets
或其他输入函数读取用户输入时,输入的数据(包括按下的回车键产生的换行符 '\n'
)都会被存储在输入缓冲区中。换行符标记了输入的结束,但通常会保留在缓冲区中。
例如,如果你使用 scanf("%s", string);
读取一个字符串,scanf
会读取连续的字符直到遇到空白字符(如空格、制表符或换行符),然后停止读取。这时,换行符仍然留在输入缓冲区中。
作用
while (getchar() != '\n');
这段代码的作用是:
- 读取字符:
getchar()
函数从输入缓冲区中读取下一个字符。 - 检查换行符:循环继续执行,直到
getchar()
读取到换行符'\n'
。 - 清空缓冲区:循环读取并丢弃缓冲区中的所有字符,直到换行符,有效地清空了缓冲区。
考虑以下情况:
- 用户输入了多个换行符,例如按了几次回车键。
- 输入缓冲区中可能包含两个或更多的换行符,如
\n\n\n
。
对于代码:
while (getchar() != '\n');
- 第一次循环:
getchar()
读取第一个换行符并丢弃它。 - 第二次循环:再次调用
getchar()
读取下一个换行符,继续丢弃,直到没有更多的换行符。
最终,当 getchar()
尝试读取下一个非换行符的字符时,循环结束。
通常使用 while (getchar() != '\n');
来清空输入缓冲区中的多余字符,是为了准备使用 getchar()
或类似的函数读取下一个用户输入的字符或字符串。
如果之后要输入的是整数,通常不需要清空缓冲区中的换行符
因为换行符对于整数输入没有影响。scanf
函数在读取整数时会忽略空白字符,包括空格、制表符和换行符。
3 格式化写文件的好处
格式化写文件的特点
- 可读性:格式化写入的文件更容易被人阅读和理解,因为它们包含描述性的文本和结构化的布局。
- 结构化:数据按照一定的格式排列,如使用制表符(
\t
)或逗号分隔,这有助于区分不同字段。 - 一致性:格式化写入有助于保持数据的一致性,因为每个记录都遵循相同的布局模式。
与其它写文件方法的区别
- 单个字符写入:使用
fputc
函数可以一次写入一个字符,这种方法不如fprintf
灵活,因为需要手动构造每一行的格式。 - 字符串写入:使用
fputs
函数可以一次写入一个字符串,这比fputc
更高效,但仍然需要手动管理数据的格式化。 - 二进制写入:使用
fwrite
函数可以高效地写入二进制数据,这种方法不关心数据的可读性,只关心数据的快速存储和恢复。 - 块写入:使用
fwrite
也可以将数据结构作为一个块写入,这种方法适用于需要高效读写大量数据的情况,但生成的文件不适合直接阅读。
4 形参 SystemInfo* s
这样写的原因:
- 指针传递:函数接受一个指向
SystemInfo
结构体的指针,而不是直接传递整个结构体。这样做的好处是减少了内存的复制,因为结构体可能很大,通过指针传递可以避免复制整个结构体,从而提高效率。 - 直接操作:通过指针,函数可以直接访问和操作原始数据结构,而不需要在函数内部创建数据的副本。
- 灵活性:指针传递提供了更多的灵活性,因为你可以传递任何
SystemInfo
类型的实例的地址,包括动态分配的内存地址。 - 一致性:在 C 语言中,通常使用指针来传递复杂的数据结构,这样可以保持代码的一致性和风格。
- 修改原数据:如果函数需要修改原始数据结构的内容,使用指针是必要的,因为这样可以直接修改原始数据,而不是修改副本。
5 文件存储操作
//文件存储操作
void save_file(SystemInfo* s,const char* filename)
{
FILE* fp = fopen(filename, "w");//清空方式,不然数据会重复增加
for (int i = 0; i < s->curSize; i++)
{
fprintf(fp, "%s\t%d\t%d\t%s\n", s->arr[i].name, s->arr[i].age, s->arr[i].num, s->arr[i].addr);
}
fclose(fp);
}
void load_file(SystemInfo* s, const char* filename)
{
FILE* fp = fopen(filename, "r");
if (fp == NULL) //第一次打开文件会没有,加个判定
{
fp = fopen(filename, "w+");
fclose(fp);
}
else
{
MM temp;
while (fscanf(fp, "%s\t%d\t%d\t%s\n", temp.name, &temp.age, &temp.num, temp.addr) != EOF)
{
insert_system(s, temp);
}
fclose(fp);
}
}
6 strcmp函数
int strcmp(const char *str1, const char *str2);
str1
和str2
是指向要比较的以 null 结尾的字符串的指针。
返回值:
- 负数:如果
str1
小于str2
(即str1
排在str2
前面)。 - 正数:如果
str1
大于str2
(即str1
排在str2
后面)。 - 0:如果两个字符串相等。
在 C 语言中,比较两个字符串是否相等时,不能使用 ==
操作符,因为 ==
用于比较指针是否相等,而不是它们指向的字符串内容。要比较两个字符串的内容,应该使用 strcmp
函数。
对于你提到的两个条件:
if (strcmp(s->arr[i].addr, temp.addr) == 0)
if (s->arr[i].addr == temp.addr)
第一个条件是正确的用法。strcmp
函数比较两个字符串,如果返回值是 0
,则表示两个字符串相等。
第二个条件是不正确的,因为 s->arr[i].addr
和 temp.addr
都是指向字符串的指针,if
语句实际上比较的是指针的值是否相等,而不是它们指向的字符串内容是否相等。
7 gets和puts
gets
函数曾经是一个在 C 语言中用来从标准输入读取字符串的函数,定义在 <string.h>
头文件中。它的使用非常简单,只需要提供一个字符数组作为参数。但是,gets
函数在读取输入时不会检查目标缓冲区的大小,这使得它极易造成缓冲区溢出,是一个非常不安全的函数。因此,gets
函数已经在 C11 标准中被彻底移除。
还是用scanf_s (%s)好
但输出可以用puts
puts
函数是 C 语言标准库中的一个函数,用于将字符串输出到标准输出(通常是屏幕),并在输出后添加一个换行符。puts
是安全的,因为它不会导致缓冲区溢出,但它要求你保证输入的字符串本身是以空字符('\0'
)结尾的。