C学习教程,从入门到精通,C语言学生信息管理系统 — 知识点详解(18)

C语言学生信息管理系统 --- 知识点详解


一、结构体(struct)

1.1 知识点概述

结构体是C语言中最重要的自定义数据类型之一,它允许我们将多个不同类型的数据组合在一起,形成一个整体。在学生信息管理系统中,每个学生具有姓名、学号、成绩等多种属性,这些属性类型各不相同,非常适合用结构体来表示。

1.2 结构体的定义

c 复制代码
struct 结构体名 {
    数据类型 成员名1;
    数据类型 成员名2;
    数据类型 成员名3;
    ...
};  /* 注意:此处的分号不能省略 */

语法要点:

  • struct 是定义结构体的关键字
  • 结构体名是自定义的标识符
  • 大括号内是结构体的成员列表
  • 每个成员可以是任意合法的数据类型(包括基本类型、数组、指针等)
  • 定义末尾必须有分号 ;
  • 定义结构体只是创建了一个"模板",并不会分配内存

1.3 结构体的实际应用

在学生信息管理系统中,通常定义如下结构体:

c 复制代码
struct Student {
    char id[20];        /* 学号,使用字符数组存储 */
    char name[20];      /* 姓名,使用字符数组存储 */
    float score[3];     /* 三门课程的成绩,使用浮点型数组存储 */
    float average;      /* 平均成绩 */
    float total;        /* 总分 */
};

1.4 使用 typedef 简化结构体

c 复制代码
#include <stdio.h>
#include <string.h>

/* 使用typedef为结构体起别名 */
typedef struct {
    char id[20];        /* 学号 */
    char name[20];      /* 姓名 */
    float score[3];     /* 三科成绩 */
    float total;        /* 总分 */
    float average;      /* 平均分 */
} STU;                  /* STU就是这个结构体的别名 */

int main() {
    STU stu1;                       /* 直接用STU声明变量,无需写struct */
    strcpy(stu1.id, "2024001");      /* 用strcpy赋值字符串成员 */
    strcpy(stu1.name, "张三");       /* 用strcpy赋值字符串成员 */
    stu1.score[0] = 90.0;           /* 第一科成绩赋值 */
    stu1.score[1] = 85.5;           /* 第二科成绩赋值 */
    stu1.score[2] = 92.0;           /* 第三科成绩赋值 */

    /* 计算总分 */
    stu1.total = stu1.score[0] + stu1.score[1] + stu1.score[2];
    /* 计算平均分 */
    stu1.average = stu1.total / 3.0;

    /* 输出学生信息 */
    printf("学号: %s\n", stu1.id);              /* 输出学号 */
    printf("姓名: %s\n", stu1.name);            /* 输出姓名 */
    printf("总分: %.1f\n", stu1.total);         /* 输出总分,保留1位小数 */
    printf("平均分: %.1f\n", stu1.average);     /* 输出平均分 */

    return 0;                                   /* 程序正常结束 */
}

运行结果:

复制代码
学号: 2024001
姓名: 张三
总分: 267.5
平均分: 89.2

1.5 结构体变量的三种初始化方式

c 复制代码
/* 方式1:定义时初始化 */
STU stu1 = {"2024001", "张三", {90, 85, 92}, 267, 89.0};

/* 方式2:逐个成员赋值 */
STU stu2;
strcpy(stu2.id, "2024002");     /* 字符串成员用strcpy */
strcpy(stu2.name, "李四");
stu2.score[0] = 88.0;           /* 数值成员直接赋值 */
stu2.score[1] = 76.5;
stu2.score[2] = 91.0;
stu2.total = stu2.score[0] + stu2.score[1] + stu2.score[2];
stu2.average = stu2.total / 3.0;

/* 方式3:指定成员初始化(C99标准) */
STU stu3 = {
    .id = "2024003",
    .name = "王五",
    .score = {95, 88, 90}
};

1.6 结构体数组

在管理系统中,需要存储多个学生的信息,因此使用结构体数组:

c 复制代码
#include <stdio.h>
#include <string.h>

/* 定义学生结构体 */
typedef struct {
    char id[20];        /* 学号 */
    char name[20];      /* 姓名 */
    float score[3];     /* 三科成绩 */
    float total;        /* 总分 */
    float average;      /* 平均分 */
} STU;

int main() {
    /* 定义结构体数组,最多存储100个学生 */
    STU students[100];
    int count = 0;      /* 记录当前学生数量 */

    /* 录入第一个学生信息 */
    strcpy(students[0].id, "2024001");
    strcpy(students[0].name, "张三");
    students[0].score[0] = 90.0;
    students[0].score[1] = 85.5;
    students[0].score[2] = 92.0;
    students[0].total = students[0].score[0] + students[0].score[1] + students[0].score[2];
    students[0].average = students[0].total / 3.0;
    count++;            /* 学生数量加1 */

    /* 录入第二个学生信息 */
    strcpy(students[1].id, "2024002");
    strcpy(students[1].name, "李四");
    students[1].score[0] = 88.0;
    students[1].score[1] = 76.5;
    students[1].score[2] = 91.0;
    students[1].total = students[1].score[0] + students[1].score[1] + students[1].score[2];
    students[1].average = students[1].total / 3.0;
    count++;            /* 学生数量加1 */

    /* 遍历并输出所有学生信息 */
    printf("%-12s %-10s %-8s %-8s %-8s %-8s %-8s\n",
           "学号", "姓名", "成绩1", "成绩2", "成绩3", "总分", "平均分");
    printf("-----------------------------------------------------------\n");

    int i;  /* 循环变量,单独声明以兼容C89 */
    for (i = 0; i < count; i++) {
        printf("%-12s %-10s %-8.1f %-8.1f %-8.1f %-8.1f %-8.1f\n",
               students[i].id,            /* 第i个学生的学号 */
               students[i].name,          /* 第i个学生的姓名 */
               students[i].score[0],      /* 第i个学生的第1科成绩 */
               students[i].score[1],      /* 第i个学生的第2科成绩 */
               students[i].score[2],      /* 第i个学生的第3科成绩 */
               students[i].total,         /* 第i个学生的总分 */
               students[i].average);      /* 第i个学生的平均分 */
    }

    return 0;   /* 程序正常结束 */
}

运行结果:

复制代码
学号         姓名       成绩1    成绩2    成绩3    总分     平均分
-----------------------------------------------------------
2024001      张三       90.0     85.5     92.0     267.5    89.2
2024002      李四       88.0     76.5     91.0     255.5    85.2

1.7 结构体指针

c 复制代码
#include <stdio.h>
#include <string.h>

/* 定义学生结构体 */
typedef struct {
    char id[20];    /* 学号 */
    char name[20];  /* 姓名 */
    float score;    /* 成绩 */
} STU;

int main() {
    STU stu1;                   /* 定义结构体变量 */
    STU *p = &stu1;             /* 定义结构体指针,指向stu1 */

    /* 通过结构体变量访问成员,使用 "." 运算符 */
    strcpy(stu1.id, "2024001");
    strcpy(stu1.name, "张三");
    stu1.score = 95.5;

    /* 通过结构体指针访问成员,使用 "->" 运算符 */
    printf("学号: %s\n", p->id);       /* 等价于 (*p).id */
    printf("姓名: %s\n", p->name);     /* 等价于 (*p).name */
    printf("成绩: %.1f\n", p->score);  /* 等价于 (*p).score */

    return 0;   /* 程序正常结束 */
}

要点总结:

  • 用 . 运算符通过结构体变量访问成员
  • 用 -> 运算符通过结构体指针访问成员
  • p->member 等价于 (*p).member,但更简洁常用

二、预处理指令

2.1 知识点概述

预处理指令是C语言编译过程的第一步,在编译之前 由预处理器处理。预处理指令都以 # 开头,行末不需要分号。在学生信息管理系统中,常用的预处理指令包括文件包含 #include 和宏定义 #define。

2.2 文件包含指令 #include

c 复制代码
/* 方式1:用尖括号包含系统头文件
   编译器在系统目录中查找头文件 */
#include <stdio.h>      /* 标准输入输出头文件 */
#include <stdlib.h>     /* 标准库头文件,包含exit()、system()等 */
#include <string.h>     /* 字符串操作头文件,包含strcpy()、strcmp()等 */

/* 方式2:用双引号包含自定义头文件
   编译器先在当前目录查找,找不到再到系统目录查找 */
#include "student.h"    /* 包含自定义的学生信息头文件 */

两者区别:

形式 查找路径 适用场景
<文件名> 系统头文件目录 标准库头文件
"文件名" 先当前目录,再系统目录 用户自定义头文件

2.3 宏定义 #define

2.3.1 无参数的宏定义
c 复制代码
/* 无参数宏定义:用一个标识符代表一个字符串 */
#define PI 3.14159          /* 定义圆周率 */
#define MAX_STU 100         /* 定义最大学生数 */
#define MAX_NAME 20         /* 定义姓名最大长度 */
#define MAX_ID 20           /* 定义学号最大长度 */
#define COURSE_NUM 3        /* 定义课程数量 */
#define FILENAME "student.dat"  /* 定义数据文件名 */

使用示例:

c 复制代码
#include <stdio.h>

/* 宏定义常量 */
#define MAX_STU 100     /* 最大学生数量 */
#define COURSE_NUM 3    /* 课程数量 */

int main() {
    /* 使用宏定义的常量来声明数组大小 */
    float scores[COURSE_NUM];   /* 数组大小由宏定义决定 */
    int count = 0;              /* 当前学生计数 */

    /* 当学生数量不超过最大限制时,允许录入 */
    if (count < MAX_STU) {
        printf("可以继续录入学生信息\n");
        printf("最多可录入 %d 名学生\n", MAX_STU);   /* 宏在预处理时被替换 */
        printf("每人 %d 门课程\n", COURSE_NUM);       /* 宏在预处理时被替换 */
    }

    return 0;   /* 程序正常结束 */
}

运行结果:

复制代码
可以继续录入学生信息
最多可录入 100 名学生
每人 3 门课程
2.3.2 带参数的宏定义
c 复制代码
/* 带参数的宏定义 */
#define MAX(a, b) ((a) > (b) ? (a) : (b))       /* 求两个数中的较大值 */
#define MIN(a, b) ((a) < (b) ? (a) : (b))       /* 求两个数中的较小值 */
#define SWAP(a, b) { float t = a; a = b; b = t; }  /* 交换两个float变量的值 */

使用示例:

c 复制代码
#include <stdio.h>

/* 带参数宏定义:计算两个数中的最大值 */
/* 注意:每个参数和整个表达式都要用括号括起来,避免运算符优先级问题 */
#define MAX(a, b) ((a) > (b) ? (a) : (b))

int main() {
    float score1 = 85.5;    /* 第一门课成绩 */
    float score2 = 92.0;    /* 第二门课成绩 */

    /* 用宏求两门课的最高分 */
    float best = MAX(score1, score2);
    printf("最高分: %.1f\n", best);     /* 输出92.0 */

    /* 宏也可以直接用在表达式中 */
    printf("两门课分差: %.1f\n", MAX(score1, score2) - MIN(score1, score2));

    return 0;   /* 程序正常结束 */
}

运行结果:

复制代码
最高分: 92.0
两门课分差: 6.5

2.4 条件编译

c 复制代码
/* 条件编译:根据条件决定编译哪些代码 */
#ifndef STUDENT_H       /* 如果没有定义STUDENT_H */
#define STUDENT_H       /* 则定义STUDENT_H(防止头文件被重复包含) */

/* 头文件的内容写在这里 */
typedef struct {
    char id[20];
    char name[20];
    float score[3];
} STU;

#endif  /* 条件编译结束 */

三种条件编译形式:

c 复制代码
/* 形式1:#ifdef ------ 如果定义了某个宏 */
#ifdef DEBUG
    printf("调试信息:当前学生数量为 %d\n", count);  /* 只在DEBUG模式下输出 */
#endif

/* 形式2:#ifndef ------ 如果没有定义某个宏(常用于头文件保护) */
#ifndef MAX_STU
    #define MAX_STU 100     /* 如果未定义,则定义它 */
#endif

/* 形式3:#if ------ 根据常量表达式 */
#if COURSE_NUM > 3
    printf("课程数量较多\n");       /* COURSE_NUM大于3时编译此行 */
#else
    printf("课程数量正常\n");       /* 否则编译此行 */
#endif

2.5 预处理模块综合示例

c 复制代码
#include <stdio.h>      /* 标准输入输出 */
#include <stdlib.h>     /* 标准库函数 */
#include <string.h>     /* 字符串操作函数 */

/* 宏定义:系统配置参数 */
#define MAX_STU 100             /* 最大学生数量 */
#define MAX_NAME 20             /* 姓名最大长度 */
#define MAX_ID 20               /* 学号最大长度 */
#define COURSE_NUM 3            /* 课程门数 */
#define FILENAME "student.dat"  /* 数据存储文件名 */

/* 带参数宏 */
#define TOTAL(s) (s[0] + s[1] + s[2])          /* 计算总分 */
#define AVERAGE(s) (TOTAL(s) / COURSE_NUM)      /* 计算平均分 */

/* 结构体定义 */
typedef struct {
    char id[MAX_ID];            /* 学号 */
    char name[MAX_NAME];        /* 姓名 */
    float score[COURSE_NUM];   /* 各科成绩 */
    float total;                /* 总分 */
    float average;              /* 平均分 */
} STU;

int main() {
    STU stu = {"2024001", "张三", {90, 85, 92}};  /* 初始化结构体 */

    /* 使用宏计算总分和平均分 */
    stu.total = TOTAL(stu.score);       /* 展开后为:stu.score[0]+stu.score[1]+stu.score[2] */
    stu.average = AVERAGE(stu.score);   /* 展开后为:TOTAL(stu.score)/3 */

    printf("学生信息管理系统 v1.0\n");
    printf("最多管理 %d 名学生,每人 %d 门课程\n", MAX_STU, COURSE_NUM);
    printf("数据保存文件: %s\n\n", FILENAME);
    printf("学号: %s\n", stu.id);
    printf("姓名: %s\n", stu.name);
    printf("总分: %.1f\n", stu.total);
    printf("平均分: %.1f\n", stu.average);

    return 0;   /* 程序正常结束 */
}

运行结果:

复制代码
学生信息管理系统 v1.0
最多管理 100 名学生,每人 3 门课程
数据保存文件: student.dat

学号: 2024001
姓名: 张三
总分: 267.0
平均分: 89.0

三、文件操作

3.1 知识点概述

文件操作是学生信息管理系统的核心 功能。系统需要将学生数据持久化保存到文件中,以便程序关闭后数据不会丢失。C语言通过 <stdio.h> 中提供的文件操作函数来实现读写。

3.2 FILE 指针

c 复制代码
/* FILE 是C语言中定义的结构体类型,用于描述文件的各种信息 */
FILE *fp;   /* 定义FILE类型的指针变量 */

所有文件操作都需要通过 FILE 指针进行,它是文件操作的"句柄"。

3.3 文件打开与关闭

c 复制代码
/* fopen() 打开文件
   参数1:文件路径(字符串)
   参数2:打开模式(字符串)
   返回值:成功返回FILE指针,失败返回NULL */
FILE *fp = fopen("student.dat", "rb");

/* fclose() 关闭文件
   参数:FILE指针
   返回值:成功返回0,失败返回EOF(-1) */
int result = fclose(fp);

常用文件打开模式:

模式 含义 文件不存在时
"r" 只读(文本模式) 打开失败
"w" 只写(文本模式),清空原内容 创建新文件
"a" 追加写(文本模式) 创建新文件
"rb" 只读(二进制模式) 打开失败
"wb" 只写(二进制模式),清空原内容 创建新文件
"ab" 追加写(二进制模式) 创建新文件
"r+" 读写(文本模式) 打开失败
"rb+" 读写(二进制模式) 打开失败
"wb+" 读写(二进制模式),清空原内容 创建新文件

3.4 判断文件是否成功打开

c 复制代码
#include <stdio.h>
#include <stdlib.h>     /* 包含exit()函数 */

int main() {
    /* 以只读方式打开文件 */
    FILE *fp = fopen("student.dat", "rb");

    /* 判断文件是否成功打开 */
    if (fp == NULL) {
        printf("文件打开失败!\n");
        exit(1);        /* 异常退出程序,返回状态码1 */
    }

    printf("文件打开成功!\n");

    /* 操作完成后关闭文件 */
    fclose(fp);         /* 关闭文件,释放资源 */

    return 0;           /* 程序正常结束 */
}

3.5 文本文件读写函数

3.5.1 fprintf 和 fscanf(格式化读写)
c 复制代码
#include <stdio.h>
#include <string.h>

/* 定义学生结构体 */
typedef struct {
    char id[20];    /* 学号 */
    char name[20];  /* 姓名 */
    float score;    /* 成绩 */
} STU;

int main() {
    STU stu_write = {"2024001", "张三", 95.5};   /* 要写入的学生 */
    STU stu_read;                                  /* 用于读取的学生 */
    FILE *fp;                                      /* 文件指针 */

    /* ---- 写入文件 ---- */
    fp = fopen("test.txt", "w");       /* 以写模式打开文本文件 */
    if (fp == NULL) {
        printf("文件打开失败!\n");
        return 1;                       /* 返回非0值表示异常 */
    }
    /* fprintf: 向文件中按格式写入数据,用法类似printf */
    fprintf(fp, "%s %s %.1f\n",
            stu_write.id,               /* 写入学号 */
            stu_write.name,             /* 写入姓名 */
            stu_write.score);           /* 写入成绩 */
    fclose(fp);                         /* 关闭文件 */

    /* ---- 从文件读取 ---- */
    fp = fopen("test.txt", "r");       /* 以读模式打开文本文件 */
    if (fp == NULL) {
        printf("文件打开失败!\n");
        return 1;
    }
    /* fscanf: 从文件中按格式读取数据,用法类似scanf */
    fscanf(fp, "%s %s %f",
           stu_read.id,                /* 读取到学号 */
           stu_read.name,              /* 读取到姓名 */
           &stu_read.score);           /* 读取到成绩,注意取地址符& */
    fclose(fp);                         /* 关闭文件 */

    /* 输出读取到的数据 */
    printf("学号: %s\n", stu_read.id);
    printf("姓名: %s\n", stu_read.name);
    printf("成绩: %.1f\n", stu_read.score);

    return 0;   /* 程序正常结束 */
}

运行结果:

复制代码
学号: 2024001
姓名: 张三
成绩: 95.5

3.6 二进制文件读写函数

3.6.1 fwrite(二进制写入)
c 复制代码
/* fwrite 函数原型 */
size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);
/* 参数说明:
   ptr    ------ 要写入的数据的起始地址
   size   ------ 每个数据元素的字节数(通常用sizeof获得)
   count  ------ 要写入的数据元素个数
   stream ------ FILE指针
   返回值 ------ 成功写入的元素个数 */
3.6.2 fread(二进制读取)
c 复制代码
/* fread 函数原型 */
size_t fread(void *ptr, size_t size, size_t count, FILE *stream);
/* 参数说明:
   ptr    ------ 存放读取数据的目标地址
   size   ------ 每个数据元素的字节数
   count  ------ 要读取的数据元素个数
   stream ------ FILE指针
   返回值 ------ 成功读取的元素个数 */
3.6.3 二进制读写完整示例
c 复制代码
#include <stdio.h>
#include <string.h>

/* 定义学生结构体 */
typedef struct {
    char id[20];    /* 学号 */
    char name[20];  /* 姓名 */
    float score;    /* 成绩 */
} STU;

int main() {
    STU students[3];    /* 学生数组 */
    STU read_buf[3];    /* 读取缓冲区 */
    int count = 3;      /* 学生数量 */
    FILE *fp;           /* 文件指针 */
    int i;              /* 循环变量 */

    /* 初始化3个学生数据 */
    strcpy(students[0].id, "2024001");
    strcpy(students[0].name, "张三");
    students[0].score = 90.5;

    strcpy(students[1].id, "2024002");
    strcpy(students[1].name, "李四");
    students[1].score = 85.0;

    strcpy(students[2].id, "2024003");
    strcpy(students[2].name, "王五");
    students[2].score = 92.0;

    /* ---- 二进制方式写入文件 ---- */
    fp = fopen("student.dat", "wb");   /* 以二进制写模式打开文件 */
    if (fp == NULL) {
        printf("文件打开失败!\n");
        return 1;
    }
    /* 将整个结构体数组一次性写入文件 */
    fwrite(students, sizeof(STU), count, fp);
    /* 参数说明:
       students     ------ 数组首地址
       sizeof(STU)  ------ 每个元素占的字节数
       count        ------ 写入3个元素
       fp           ------ 目标文件 */
    fclose(fp);                         /* 关闭文件 */
    printf("写入 %d 条学生记录到文件\n", count);

    /* ---- 二进制方式从文件读取 ---- */
    fp = fopen("student.dat", "rb");   /* 以二进制读模式打开文件 */
    if (fp == NULL) {
        printf("文件打开失败!\n");
        return 1;
    }
    /* 从文件中读取数据到结构体数组 */
    int read_count = fread(read_buf, sizeof(STU), count, fp);
    /* 参数说明:
       read_buf     ------ 存放读取数据的目标数组
       sizeof(STU)  ------ 每个元素占的字节数
       count        ------ 最多读取3个元素
       fp           ------ 源文件
       返回值       ------ 实际读取到的元素个数 */
    fclose(fp);                         /* 关闭文件 */

    /* 输出读取到的所有学生信息 */
    printf("从文件读取到 %d 条记录:\n", read_count);
    for (i = 0; i < read_count; i++) {
        printf("学号:%-10s 姓名:%-8s 成绩:%.1f\n",
               read_buf[i].id,
               read_buf[i].name,
               read_buf[i].score);
    }

    return 0;   /* 程序正常结束 */
}

运行结果:

复制代码
写入 3 条学生记录到文件
从文件读取到 3 条记录:
学号:2024001   姓名:张三      成绩:90.5
学号:2024002   姓名:李四      成绩:85.0
学号:2024003   姓名:王五      成绩:92.0

3.7 其他常用文件操作函数

3.7.1 rewind ------ 将文件指针重置到文件开头
c 复制代码
rewind(fp);
/* 等价于 fseek(fp, 0, SEEK_SET); */
/* 将文件内部位置指针重新指向文件起始位置 */
3.7.2 fseek ------ 移动文件指针到指定位置
c 复制代码
/* fseek 函数原型 */
int fseek(FILE *stream, long offset, int whence);
/* 参数说明:
   stream ------ FILE指针
   offset ------ 偏移量(字节数)
   whence ------ 起始位置,有三个取值:
              SEEK_SET(值为0)------ 从文件开头
              SEEK_CUR(值为1)------ 从当前位置
              SEEK_END(值为2)------ 从文件末尾
   返回值 ------ 成功返回0,失败返回非0 */

使用示例:

c 复制代码
/* 将文件指针移动到第2个学生记录的位置(从0开始计数,即第3个) */
fseek(fp, 2 * sizeof(STU), SEEK_SET);

/* 将文件指针移动到文件末尾 */
fseek(fp, 0, SEEK_END);

/* 获取文件大小(字节数) */
long file_size = ftell(fp);
printf("文件大小: %ld 字节\n", file_size);

/* 计算文件中存储了多少条学生记录 */
long record_count = file_size / sizeof(STU);
printf("文件中有 %ld 条学生记录\n", record_count);
3.7.3 ftell ------ 获取当前文件指针位置
c 复制代码
long pos = ftell(fp);
/* 返回文件指针的当前位置(相对于文件开头的字节数) */

3.8 文件操作综合示例:判断文件是否有内容

c 复制代码
#include <stdio.h>
#include <stdlib.h>

int main() {
    FILE *fp;

    /* 尝试以读写模式打开文件 */
    fp = fopen("student.dat", "rb");

    if (fp == NULL) {
        /* 文件不存在,这是首次使用系统 */
        printf("文件不存在,将创建新文件\n");
        fp = fopen("student.dat", "wb");    /* 创建新文件 */
        if (fp == NULL) {
            printf("创建文件失败!\n");
            exit(1);                        /* 创建失败则退出 */
        }
        printf("文件创建成功!\n");
        fclose(fp);                         /* 关闭文件 */
    } else {
        /* 文件存在,检查是否有内容 */
        fseek(fp, 0, SEEK_END);            /* 将指针移到文件末尾 */
        long size = ftell(fp);             /* 获取文件大小 */

        if (size == 0) {
            printf("文件存在但没有内容\n");
        } else {
            printf("文件存在,大小为 %ld 字节\n", size);
            printf("文件中有数据可供读取\n");
        }

        fclose(fp);                         /* 关闭文件 */
    }

    return 0;   /* 程序正常结束 */
}

四、字符串操作函数

4.1 知识点概述

在学生信息管理系统中,学号和姓名都是字符串类型,需要用到 <string.h> 中的各种字符串处理函数。

4.2 常用字符串函数详解

4.2.1 strcpy ------ 字符串复制
c 复制代码
/* 函数原型 */
char *strcpy(char *dest, const char *src);
/* 将src指向的字符串复制到dest指向的内存空间
   注意:dest空间必须足够大,否则会溢出 */
c 复制代码
char name[20];
strcpy(name, "张三");       /* 将"张三"复制到name数组中 */
printf("%s\n", name);       /* 输出:张三 */
4.2.2 strcmp ------ 字符串比较
c 复制代码
/* 函数原型 */
int strcmp(const char *s1, const char *s2);
/* 比较规则:逐字符比较ASCII码值
   返回值:
     < 0 ------ s1 < s2(s1排在s2前面)
     = 0 ------ s1 == s2(两个字符串完全相同)
     > 0 ------ s1 > s2(s1排在s2后面) */
c 复制代码
char id1[20] = "2024001";
char id2[20] = "2024002";

int result = strcmp(id1, id2);  /* 比较两个学号 */
if (result == 0) {
    printf("学号相同\n");
} else if (result < 0) {
    printf("id1 排在 id2 前面\n");
} else {
    printf("id1 排在 id2 后面\n");
}
4.2.3 strcat ------ 字符串连接
c 复制代码
/* 函数原型 */
char *strcat(char *dest, const char *src);
/* 将src字符串追加到dest字符串末尾 */
c 复制代码
char greeting[50] = "你好,";
strcat(greeting, "欢迎使用学生管理系统!");
printf("%s\n", greeting);   /* 输出:你好,欢迎使用学生管理系统! */
4.2.4 strlen ------ 求字符串长度
c 复制代码
/* 函数原型 */
size_t strlen(const char *s);
/* 返回字符串中字符的个数(不含结束符'\0') */
c 复制代码
char name[20] = "张三";
int len = strlen(name);     /* 返回的是字符个数,不是字节数 */
printf("名字长度: %d\n", len);  /* 输出:名字长度: 2 */
4.2.5 strstr ------ 字符串查找
c 复制代码
/* 函数原型 */
char *strstr(const char *haystack, const char *needle);
/* 在haystack中查找needle子串
   找到则返回首次匹配的位置指针,找不到返回NULL */
c 复制代码
char name[20] = "张三丰";
char *pos = strstr(name, "张三");  /* 在"张三丰"中查找"张三" */
if (pos != NULL) {
    printf("找到了!\n");           /* "张三丰"中包含"张三" */
}

五、输入输出与缓冲区处理

5.1 知识点概述

在学生信息管理系统中,需要频繁地从键盘读取用户输入。C语言的输入函数需要注意缓冲区残留数据的问题。

5.2 scanf 与缓冲区

c 复制代码
#include <stdio.h>

int main() {
    int num;
    char name[20];

    printf("请输入一个整数: ");
    scanf("%d", &num);          /* 读取整数 */

    /*
     * 问题:scanf("%d") 读取数字后,回车键'\n'残留在缓冲区
     * 如果接下来用gets()或scanf("%s")读取,会直接读到'\n'
     * 解决方案:用getchar()吃掉残留的'\n'
     */
    getchar();                  /* 消耗掉缓冲区中的'\n' */

    printf("请输入姓名: ");
    scanf("%s", name);          /* 现在可以正确读取姓名了 */

    printf("整数: %d, 姓名: %s\n", num, name);

    return 0;
}

5.3 清空输入缓冲区

c 复制代码
/* 方法:循环读取并丢弃缓冲区中的所有字符,直到遇到换行符或EOF */
void clear_buffer() {
    int c;                      /* 用int类型存储字符,因为EOF是-1 */
    while ((c = getchar()) != '\n' && c != EOF) {
        /* 逐个读取并丢弃,不做任何处理 */
    }
}

5.4 gets 和 fgets

c 复制代码
#include <stdio.h>
#include <string.h>

int main() {
    char name[20];

    printf("请输入姓名: ");

    /* fgets 从标准输入读取一行
       参数1:存放结果的字符数组
       参数2:最多读取的字符数(含'\0')
       参数3:输入流(stdin表示键盘)
       注意:fgets会把换行符'\n'也读进来 */
    fgets(name, sizeof(name), stdin);

    /* 去除fgets读入的换行符 */
    int len = strlen(name);         /* 获取字符串长度 */
    if (len > 0 && name[len - 1] == '\n') {
        name[len - 1] = '\0';      /* 将末尾的换行符替换为结束符 */
    }

    printf("你输入的姓名是: %s\n", name);

    return 0;
}

六、菜单界面设计(switch-case)

6.1 知识点概述

学生信息管理系统采用菜单驱动 的交互方式,用户通过输入数字选择功能。这需要用到循环结构和 switch-case 分支结构。

6.2 switch-case 语句详解

c 复制代码
switch (表达式) {
    case 常量1:
        语句1;
        break;          /* 跳出switch */
    case 常量2:
        语句2;
        break;
    case 常量3:
    case 常量4:
        语句3;          /* case 3和case 4执行相同代码 */
        break;
    default:
        默认语句;        /* 所有case都不匹配时执行 */
        break;
}

语法要点:

  • switch 后面的表达式必须是整型或字符型
  • case 后面必须是常量表达式,不能是变量
  • 每个 case 末尾的 break 不能省略,否则会"穿透"到下一个case
  • default 是可选的,用于处理所有case都不匹配的情况

6.3 完整的菜单界面示例

c 复制代码
#include <stdio.h>
#include <stdlib.h>

/* 函数声明 */
void menu(void);        /* 显示主菜单 */

int main() {
    int choice;         /* 用户选择的功能编号 */

    while (1) {         /* 无限循环,直到用户选择退出 */
        menu();         /* 显示菜单 */

        printf("\n请输入您的选择(0-8): ");
        scanf("%d", &choice);       /* 读取用户选择 */
        getchar();                  /* 消耗回车符 */

        switch (choice) {
            case 1:
                printf(">> 录入学生信息\n\n");
                /* 调用录入函数(此处为示意) */
                break;              /* 跳出switch */

            case 2:
                printf(">> 查询学生信息\n\n");
                /* 调用查询函数 */
                break;

            case 3:
                printf(">> 删除学生信息\n\n");
                /* 调用删除函数 */
                break;

            case 4:
                printf(">> 修改学生信息\n\n");
                /* 调用修改函数 */
                break;

            case 5:
                printf(">> 插入学生信息\n\n");
                /* 调用插入函数 */
                break;

            case 6:
                printf(">> 学生成绩排序\n\n");
                /* 调用排序函数 */
                break;

            case 7:
                printf(">> 显示所有学生信息\n\n");
                /* 调用显示函数 */
                break;

            case 8:
                printf(">> 保存到文件\n\n");
                /* 调用保存函数 */
                break;

            case 0:
                printf("感谢使用,再见!\n");
                exit(0);            /* 退出程序 */

            default:
                printf("输入错误,请重新选择!\n\n");
                break;              /* 处理无效输入 */
        }
    }

    return 0;
}

/* 显示主菜单函数 */
void menu(void) {
    printf("===================================\n");
    printf("    学生信息管理系统 v1.0\n");
    printf("===================================\n");
    printf("    1. 录入学生信息\n");
    printf("    2. 查询学生信息\n");
    printf("    3. 删除学生信息\n");
    printf("    4. 修改学生信息\n");
    printf("    5. 插入学生信息\n");
    printf("    6. 学生成绩排序\n");
    printf("    7. 显示所有学生信息\n");
    printf("    8. 保存到文件\n");
    printf("    0. 退出系统\n");
    printf("===================================\n");
}

七、函数的定义与调用

7.1 知识点概述

学生信息管理系统采用模块化设计,将每个功能封装为独立的函数,使代码结构清晰、易于维护。

7.2 函数的定义

c 复制代码
/* 语法格式 */
返回值类型 函数名(参数列表) {
    函数体;
    return 返回值;  /* void类型函数可省略 */
}

7.3 函数声明(函数原型)

c 复制代码
/* 当函数定义在调用之后,需要在文件开头声明函数原型 */
void inputStudent(STU students[], int *count);              /* 录入学生信息 */
void displayAll(STU students[], int count);                 /* 显示所有学生 */
int searchById(STU students[], int count, char *id);        /* 按学号查找 */
void deleteStudent(STU students[], int *count, int index);  /* 删除学生 */
void sort(STU students[], int count);                       /* 排序 */

7.4 向函数传递结构体数组

c 复制代码
#include <stdio.h>
#include <string.h>

/* 结构体定义 */
typedef struct {
    char id[20];    /* 学号 */
    char name[20];  /* 姓名 */
    float score;    /* 成绩 */
} STU;

/* 函数声明 */
void inputStudent(STU students[], int *count);  /* 录入函数 */
void displayAll(STU students[], int count);     /* 显示函数 */

int main() {
    STU students[100];      /* 学生数组,最多100人 */
    int count = 0;          /* 当前学生数量 */

    inputStudent(students, &count);     /* 录入学生,传入数组和计数的地址 */
    displayAll(students, count);        /* 显示所有学生 */

    return 0;
}

/* 录入一个学生信息
   参数1:学生数组
   参数2:学生数量的指针(通过指针修改实参的值) */
void inputStudent(STU students[], int *count) {
    printf("请输入学号: ");
    scanf("%s", students[*count].id);       /* 读取学号到当前空位 */

    printf("请输入姓名: ");
    scanf("%s", students[*count].name);     /* 读取姓名 */

    printf("请输入成绩: ");
    scanf("%f", &students[*count].score);   /* 读取成绩 */

    (*count)++;     /* 学生数量加1(通过指针修改外部变量) */
    printf("录入成功!当前共 %d 名学生\n", *count);
}

/* 显示所有学生信息
   参数1:学生数组(只读,不修改)
   参数2:当前学生数量 */
void displayAll(STU students[], int count) {
    int i;
    printf("\n%-12s %-10s %-8s\n", "学号", "姓名", "成绩");
    printf("--------------------------------\n");
    for (i = 0; i < count; i++) {
        printf("%-12s %-10s %-8.1f\n",
               students[i].id,
               students[i].name,
               students[i].score);
    }
}

运行结果:

复制代码
请输入学号: 2024001
请输入姓名: 张三
请输入成绩: 95.5
录入成功!当前共 1 名学生

学号         姓名       成绩
--------------------------------
2024001      张三       95.5

八、录入学生信息模块

8.1 知识点概述

录入模块是系统的基础功能,负责从键盘获取学生信息并存储到结构体数组中。需要处理两种情况:文件中无数据(首次使用)和文件中已有数据(追加录入)。

8.2 录入时文件中无内容

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_STU 100     /* 最大学生数量 */
#define FILENAME "student.dat"  /* 数据文件名 */

/* 学生结构体 */
typedef struct {
    char id[20];        /* 学号 */
    char name[20];      /* 姓名 */
    float score[3];     /* 三科成绩 */
    float total;        /* 总分 */
    float average;      /* 平均分 */
} STU;

int main() {
    STU students[MAX_STU];  /* 学生数组 */
    int count = 0;          /* 当前学生数量 */
    FILE *fp;               /* 文件指针 */

    /* 检查文件是否存在且有内容 */
    fp = fopen(FILENAME, "rb");         /* 尝试以二进制读模式打开 */
    if (fp == NULL) {
        /* 文件不存在 ------ 首次使用系统 */
        printf("欢迎首次使用,数据文件将自动创建\n");
    } else {
        /* 文件存在,检查是否有内容 */
        fseek(fp, 0, SEEK_END);        /* 移动到文件末尾 */
        long size = ftell(fp);         /* 获取文件大小 */

        if (size == 0) {
            printf("数据文件为空,请开始录入学生信息\n");
        } else {
            /* 文件有内容,读取已有数据 */
            rewind(fp);                 /* 将文件指针重置到开头 */
            count = fread(students, sizeof(STU), MAX_STU, fp);
            printf("已加载 %d 条学生记录\n", count);
        }
        fclose(fp);                     /* 关闭文件 */
    }

    /* 录入新学生信息 */
    char continue_input = 'y';          /* 是否继续录入的标志 */
    while (continue_input == 'y' || continue_input == 'Y') {
        if (count >= MAX_STU) {         /* 检查是否超出容量 */
            printf("学生数量已达上限 %d!\n", MAX_STU);
            break;                      /* 跳出录入循环 */
        }

        printf("\n--- 录入第 %d 个学生 ---\n", count + 1);

        printf("请输入学号: ");
        scanf("%s", students[count].id);    /* 录入学号 */

        printf("请输入姓名: ");
        scanf("%s", students[count].name);  /* 录入姓名 */

        int i;
        students[count].total = 0;          /* 初始化总分 */
        for (i = 0; i < 3; i++) {           /* 循环录入3科成绩 */
            printf("请输入第%d科成绩: ", i + 1);
            scanf("%f", &students[count].score[i]);
            students[count].total += students[count].score[i];  /* 累加总分 */
        }
        students[count].average = students[count].total / 3.0;  /* 计算平均分 */

        count++;    /* 学生数量加1 */

        printf("录入成功!当前共 %d 名学生\n", count);
        printf("是否继续录入?(y/n): ");
        getchar();                      /* 消耗回车 */
        scanf("%c", &continue_input);   /* 读取用户选择 */
    }

    /* 保存数据到文件 */
    fp = fopen(FILENAME, "wb");         /* 以二进制写模式打开(覆盖原内容) */
    if (fp == NULL) {
        printf("保存失败!\n");
        return 1;
    }
    fwrite(students, sizeof(STU), count, fp);   /* 写入所有学生数据 */
    fclose(fp);                                 /* 关闭文件 */
    printf("\n数据已保存到文件 %s\n", FILENAME);

    return 0;   /* 程序正常结束 */
}

8.3 录入时文件中有内容(追加)

c 复制代码
/* 追加录入的核心逻辑 */
void appendStudent(STU students[], int *count) {
    FILE *fp;

    /* 先从文件加载已有数据 */
    fp = fopen(FILENAME, "rb");
    if (fp != NULL) {
        /* 读取文件中的所有记录 */
        *count = fread(students, sizeof(STU), MAX_STU, fp);
        fclose(fp);
    }

    /* 在已有数据后面追加新记录 */
    if (*count < MAX_STU) {
        printf("请输入新学生的学号: ");
        scanf("%s", students[*count].id);

        printf("请输入新学生的姓名: ");
        scanf("%s", students[*count].name);

        int i;
        students[*count].total = 0;
        for (i = 0; i < 3; i++) {
            printf("请输入第%d科成绩: ", i + 1);
            scanf("%f", &students[*count].score[i]);
            students[*count].total += students[*count].score[i];
        }
        students[*count].average = students[*count].total / 3.0;

        (*count)++;     /* 数量加1 */

        /* 保存全部数据到文件(覆盖写入) */
        fp = fopen(FILENAME, "wb");
        if (fp != NULL) {
            fwrite(students, sizeof(STU), *count, fp);
            fclose(fp);
            printf("追加成功!\n");
        }
    } else {
        printf("学生数量已达上限!\n");
    }
}

九、查询学生信息模块

9.1 知识点概述

查询模块支持按学号或姓名查找学生。核心是遍历数组 并使用 strcmp 进行字符串匹配。

9.2 按学号查询

c 复制代码
#include <stdio.h>
#include <string.h>

#define MAX_STU 100

/* 学生结构体 */
typedef struct {
    char id[20];    /* 学号 */
    char name[20];  /* 姓名 */
    float score[3]; /* 三科成绩 */
    float total;    /* 总分 */
    float average;  /* 平均分 */
} STU;

/* 函数:按学号查找学生
   参数1:学生数组
   参数2:当前学生数量
   参数3:要查找的学号
   返回值:找到返回数组下标,找不到返回-1 */
int searchById(STU students[], int count, char *id) {
    int i;  /* 循环变量 */
    for (i = 0; i < count; i++) {
        /* strcmp返回0表示两个字符串相同 */
        if (strcmp(students[i].id, id) == 0) {
            return i;       /* 找到了,返回下标 */
        }
    }
    return -1;              /* 遍历完没找到,返回-1 */
}

/* 函数:显示一个学生的完整信息
   参数:学生结构体变量 */
void displayOne(STU stu) {
    int i;
    printf("\n---- 学生详细信息 ----\n");
    printf("学号: %s\n", stu.id);
    printf("姓名: %s\n", stu.name);
    for (i = 0; i < 3; i++) {
        printf("第%d科成绩: %.1f\n", i + 1, stu.score[i]);
    }
    printf("总分: %.1f\n", stu.total);
    printf("平均分: %.1f\n", stu.average);
    printf("---------------------\n");
}

int main() {
    STU students[MAX_STU];  /* 学生数组 */
    int count = 0;          /* 学生数量 */
    char search_id[20];     /* 要查找的学号 */
    int index;              /* 查找结果的下标 */

    /* 为了演示,手动设置一些测试数据 */
    strcpy(students[0].id, "2024001");
    strcpy(students[0].name, "张三");
    students[0].score[0] = 90; students[0].score[1] = 85; students[0].score[2] = 92;
    students[0].total = 267; students[0].average = 89.0;
    count++;

    strcpy(students[1].id, "2024002");
    strcpy(students[1].name, "李四");
    students[1].score[0] = 78; students[1].score[1] = 92; students[1].score[2] = 88;
    students[1].total = 258; students[1].average = 86.0;
    count++;

    /* 查询操作 */
    printf("请输入要查询的学号: ");
    scanf("%s", search_id);             /* 读取要查找的学号 */

    index = searchById(students, count, search_id);     /* 执行查找 */

    if (index != -1) {
        printf("找到了!该学生是第 %d 条记录\n", index + 1);
        displayOne(students[index]);    /* 显示该学生信息 */
    } else {
        printf("未找到学号为 %s 的学生\n", search_id);
    }

    return 0;   /* 程序正常结束 */
}

运行结果:

复制代码
请输入要查询的学号: 2024001
找到了!该学生是第 1 条记录

---- 学生详细信息 ----
学号: 2024001
姓名: 张三
第1科成绩: 90.0
第2科成绩: 85.0
第3科成绩: 92.0
总分: 267.0
平均分: 89.0
---------------------

9.3 按姓名查询(模糊查找)

c 复制代码
/* 函数:按姓名查找(支持模糊匹配)
   参数1:学生数组
   参数2:当前学生数量
   参数3:要查找的姓名关键字
   返回值:找到的记录数量 */
int searchByName(STU students[], int count, char *keyword) {
    int i;
    int found = 0;          /* 找到的记录计数 */

    printf("\n%-12s %-10s %-8s %-8s %-8s\n",
           "学号", "姓名", "总分", "平均分", "排名");
    printf("----------------------------------------------\n");

    for (i = 0; i < count; i++) {
        /* strstr在students[i].name中查找keyword子串
           如果找到,返回非NULL指针 */
        if (strstr(students[i].name, keyword) != NULL) {
            printf("%-12s %-10s %-8.1f %-8.1f\n",
                   students[i].id,
                   students[i].name,
                   students[i].total,
                   students[i].average);
            found++;        /* 匹配记录数加1 */
        }
    }

    return found;
}

十、删除学生信息模块

10.1 知识点概述

删除操作的原理是:找到目标学生后,将其后面的所有元素依次前移一位,覆盖被删除的元素,同时将总数量减1。

10.2 删除算法图解

复制代码
删除前(删除下标为1的"李四"):
下标:  0       1       2       3
数据:  张三    李四    王五    赵六
                      ↑ 后面的元素前移

删除后:
下标:  0       1       2
数据:  张三    王五    赵六

10.3 完整删除示例

c 复制代码
#include <stdio.h>
#include <string.h>

#define MAX_STU 100

typedef struct {
    char id[20];    /* 学号 */
    char name[20];  /* 姓名 */
    float score;    /* 成绩 */
} STU;

/* 显示所有学生 */
void displayAll(STU students[], int count) {
    int i;
    printf("\n%-5s %-12s %-10s %-8s\n", "序号", "学号", "姓名", "成绩");
    printf("-------------------------------------\n");
    for (i = 0; i < count; i++) {
        printf("%-5d %-12s %-10s %-8.1f\n",
               i + 1,
               students[i].id,
               students[i].name,
               students[i].score);
    }
    printf("-------------------------------------\n");
    printf("共 %d 名学生\n\n", count);
}

/* 删除指定下标的学生
   参数1:学生数组
   参数2:学生数量的指针
   参数3:要删除的学生下标 */
void deleteStudent(STU students[], int *count, int index) {
    int i;

    /* 边界检查 */
    if (index < 0 || index >= *count) {
        printf("删除失败:下标越界!\n");
        return;     /* 直接返回,不执行删除 */
    }

    /* 核心操作:将index之后的元素依次前移一位 */
    for (i = index; i < *count - 1; i++) {
        students[i] = students[i + 1];  /* 结构体整体赋值 */
    }

    (*count)--;     /* 学生数量减1 */
    printf("删除成功!\n");
}

int main() {
    STU students[MAX_STU] = {
        {"2024001", "张三", 90.0},
        {"2024002", "李四", 85.0},
        {"2024003", "王五", 92.0},
        {"2024004", "赵六", 78.0}
    };
    int count = 4;          /* 当前4名学生 */
    char del_id[20];        /* 要删除的学号 */
    int index = -1;         /* 查找结果 */
    int i;

    printf("删除前:\n");
    displayAll(students, count);

    printf("请输入要删除的学生学号: ");
    scanf("%s", del_id);

    /* 查找要删除的学生 */
    for (i = 0; i < count; i++) {
        if (strcmp(students[i].id, del_id) == 0) {
            index = i;      /* 找到了,记录下标 */
            break;          /* 跳出查找循环 */
        }
    }

    if (index != -1) {
        printf("找到学生: %s (%s)\n", students[index].name, students[index].id);
        deleteStudent(students, &count, index);     /* 执行删除 */

        printf("\n删除后:\n");
        displayAll(students, count);                /* 显示删除后的列表 */
    } else {
        printf("未找到学号为 %s 的学生\n", del_id);
    }

    return 0;
}

运行结果:

复制代码
删除前:

序号  学号         姓名       成绩
-------------------------------------
1     2024001      张三       90.0
2     2024002      李四       85.0
3     2024003      王五       92.0
4     2024004      赵六       78.0
-------------------------------------
共 4 名学生

请输入要删除的学生学号: 2024002
找到学生: 李四 (2024002)
删除成功!

删除后:

序号  学号         姓名       成绩
-------------------------------------
1     2024001      张三       90.0
2     2024003      王五       92.0
3     2024004      赵六       78.0
-------------------------------------
共 3 名学生

十一、修改学生信息模块

11.1 知识点概述

修改操作的流程:先查找目标学生,找到后让用户输入新的信息覆盖原有数据。

11.2 完整修改示例

c 复制代码
#include <stdio.h>
#include <string.h>

typedef struct {
    char id[20];    /* 学号 */
    char name[20];  /* 姓名 */
    float score[3]; /* 三科成绩 */
    float total;    /* 总分 */
    float average;  /* 平均分 */
} STU;

/* 函数:修改指定学生的成绩
   参数1:学生数组
   参数2:学生数量
   参数3:要修改的学生学号 */
void modifyStudent(STU students[], int count, char *target_id) {
    int i;
    int found = 0;          /* 是否找到的标志 */

    for (i = 0; i < count; i++) {
        if (strcmp(students[i].id, target_id) == 0) {
            /* 找到了目标学生 */
            found = 1;

            printf("找到学生: %s (%s)\n", students[i].name, students[i].id);
            printf("当前成绩: %.1f, %.1f, %.1f\n",
                   students[i].score[0],
                   students[i].score[1],
                   students[i].score[2]);

            /* 选择修改项目 */
            printf("\n请选择修改项目:\n");
            printf("1. 修改姓名\n");
            printf("2. 修改成绩\n");
            printf("3. 修改全部信息\n");
            printf("请输入选择: ");

            int choice;
            scanf("%d", &choice);

            switch (choice) {
                case 1:
                    printf("请输入新姓名: ");
                    scanf("%s", students[i].name);  /* 直接覆盖旧姓名 */
                    break;

                case 2: {
                    int j;
                    students[i].total = 0;          /* 重置总分 */
                    for (j = 0; j < 3; j++) {
                        printf("请输入第%d科新成绩: ", j + 1);
                        scanf("%f", &students[i].score[j]);
                        students[i].total += students[i].score[j];
                    }
                    students[i].average = students[i].total / 3.0;
                    break;
                }

                case 3:
                    printf("请输入新姓名: ");
                    scanf("%s", students[i].name);
                    {
                        int j;
                        students[i].total = 0;
                        for (j = 0; j < 3; j++) {
                            printf("请输入第%d科新成绩: ", j + 1);
                            scanf("%f", &students[i].score[j]);
                            students[i].total += students[i].score[j];
                        }
                        students[i].average = students[i].total / 3.0;
                    }
                    break;

                default:
                    printf("无效选择!\n");
                    return;
            }

            printf("\n修改成功!更新后的信息:\n");
            printf("学号: %s\n", students[i].id);
            printf("姓名: %s\n", students[i].name);
            printf("总分: %.1f\n", students[i].total);
            printf("平均分: %.1f\n", students[i].average);
            break;      /* 找到并修改后,跳出查找循环 */
        }
    }

    if (!found) {
        printf("未找到学号为 %s 的学生\n", target_id);
    }
}

int main() {
    STU students[100] = {
        {"2024001", "张三", {90, 85, 92}, 267, 89.0},
        {"2024002", "李四", {78, 92, 88}, 258, 86.0}
    };
    int count = 2;
    char target_id[20];

    printf("请输入要修改的学生学号: ");
    scanf("%s", target_id);

    modifyStudent(students, count, target_id);  /* 调用修改函数 */

    return 0;
}

十二、插入学生信息模块

12.1 知识点概述

插入操作是指在指定位置(如按学号顺序插入)添加新学生。插入的核心是将插入位置之后的所有元素后移一位,腾出空间给新元素。

12.2 插入算法图解

复制代码
插入前(在下标1处插入"钱七"):
下标:  0       1       2
数据:  张三    李四    王五
              ↑ 这里开始后移

后移过程:
下标:  0       1       2       3
数据:  张三    ???     李四    王五

插入后:
下标:  0       1       2       3
数据:  张三    钱七    李四    王五

12.3 完整插入示例

c 复制代码
#include <stdio.h>
#include <string.h>

#define MAX_STU 100

typedef struct {
    char id[20];    /* 学号 */
    char name[20];  /* 始名 */
    float score;    /* 成绩 */
} STU;

/* 显示所有学生 */
void displayAll(STU students[], int count) {
    int i;
    printf("\n%-5s %-12s %-10s %-8s\n", "序号", "学号", "姓名", "成绩");
    printf("-------------------------------------\n");
    for (i = 0; i < count; i++) {
        printf("%-5d %-12s %-10s %-8.1f\n",
               i + 1, students[i].id, students[i].name, students[i].score);
    }
    printf("-------------------------------------\n\n");
}

/* 函数:在指定位置插入学生
   参数1:学生数组
   参数2:学生数量的指针
   参数3:要插入的位置(下标) */
void insertStudent(STU students[], int *count, int pos) {
    int i;

    /* 边界检查 */
    if (*count >= MAX_STU) {
        printf("学生数量已达上限,无法插入!\n");
        return;
    }
    if (pos < 0 || pos > *count) {
        printf("插入位置无效!有效范围: 0 ~ %d\n", *count);
        return;
    }

    /* 核心操作:将pos及之后的元素后移一位
       注意:必须从最后一个元素开始后移,否则会覆盖数据 */
    for (i = *count - 1; i >= pos; i--) {
        students[i + 1] = students[i];  /* 结构体整体赋值 */
    }

    /* 在腾出的位置录入新学生信息 */
    printf("请输入新学生的学号: ");
    scanf("%s", students[pos].id);

    printf("请输入新学生的姓名: ");
    scanf("%s", students[pos].name);

    printf("请输入新学生的成绩: ");
    scanf("%f", &students[pos].score);

    (*count)++;     /* 学生数量加1 */
    printf("插入成功!\n");
}

/* 函数:按学号顺序找到插入位置
   参数1:学生数组
   参数2:当前学生数量
   参数3:新学生的学号
   返回值:应该插入的位置(下标) */
int findInsertPos(STU students[], int count, char *new_id) {
    int i;
    for (i = 0; i < count; i++) {
        /* 如果新学号比当前学生的学号小,就插入到当前位置 */
        if (strcmp(new_id, students[i].id) < 0) {
            return i;       /* 返回插入位置 */
        }
    }
    return count;           /* 新学号最大,插在末尾 */
}

int main() {
    STU students[MAX_STU] = {
        {"2024001", "张三", 90.0},
        {"2024003", "王五", 92.0},
        {"2024004", "赵六", 78.0}
    };
    int count = 3;
    STU new_stu;

    printf("插入前:\n");
    displayAll(students, count);

    /* 先获取新学生学号,用于确定插入位置 */
    printf("请输入新学生的学号: ");
    scanf("%s", new_stu.id);

    /* 按学号顺序找到插入位置 */
    int pos = findInsertPos(students, count, new_stu.id);
    printf("将插入到位置 %d\n", pos);

    /* 在该位置插入新学生 */
    /* 由于findInsertPos已经读取了学号,这里手动填入 */
    /* 为演示完整流程,这里重新实现插入 */
    int i;
    for (i = count - 1; i >= pos; i--) {
        students[i + 1] = students[i];      /* 后移 */
    }
    strcpy(students[pos].id, new_stu.id);   /* 填入学号 */
    printf("请输入新学生的姓名: ");
    scanf("%s", students[pos].name);
    printf("请输入新学生的成绩: ");
    scanf("%f", &students[pos].score);
    count++;

    printf("\n插入后:\n");
    displayAll(students, count);

    return 0;
}

运行结果(假设输入学号2024002,姓名钱七,成绩88):

复制代码
插入前:

序号  学号         姓名       成绩
-------------------------------------
1     2024001      张三       90.0
2     2024003      王五       92.0
3     2024004      赵六       78.0
-------------------------------------

请输入新学生的学号: 2024002
将插入到位置 1
请输入新学生的姓名: 钱七
请输入新学生的成绩: 88

插入后:

序号  学号         姓名       成绩
-------------------------------------
1     2024001      张三       90.0
2     2024002      钱七       88.0
3     2024003      王五       92.0
4     2024004      赵六       78.0
-------------------------------------

十三、学生成绩排序模块

13.1 知识点概述

排序模块对学生按成绩进行排列,最常用的是冒泡排序。在管理系统中,排序的对象是结构体数组,每次"交换"实际上是交换整个结构体。

13.2 冒泡排序算法

基本思想: 相邻元素两两比较,如果顺序错误就交换,每一轮将一个最值"冒泡"到正确位置。

13.3 冒泡排序图解

复制代码
原始数据: [90, 78, 85, 92, 88]

第1轮:比较相邻元素,将最大值冒泡到末尾
  [90,78] → 交换 → [78,90,85,92,88]
  [90,85] → 交换 → [78,85,90,92,88]
  [90,92] → 不换 → [78,85,90,92,88]
  [92,88] → 不换 → [78,85,90,88,92]  ← 92到位

第2轮:
  [78,85] → 不换 → [78,85,90,88,92]
  [85,90] → 不换 → [78,85,90,88,92]
  [90,88] → 交换 → [78,85,88,90,92]  ← 90到位

第3轮:
  [78,85] → 不换 → [78,85,88,90,92]
  [85,88] → 不换 → [78,85,88,90,92]  ← 88到位

第4轮:
  [78,85] → 不换 → [78,85,88,90,92]  ← 排序完成

13.4 完整排序示例

c 复制代码
#include <stdio.h>
#include <string.h>

#define MAX_STU 100

typedef struct {
    char id[20];    /* 学号 */
    char name[20];  /* 姓名 */
    float score[3]; /* 三科成绩 */
    float total;    /* 总分 */
    float average;  /* 平均分 */
} STU;

/* 显示所有学生 */
void displayAll(STU students[], int count) {
    int i;
    printf("\n%-5s %-12s %-10s %-8s %-8s\n",
           "排名", "学号", "姓名", "总分", "平均分");
    printf("----------------------------------------------\n");
    for (i = 0; i < count; i++) {
        printf("%-5d %-12s %-10s %-8.1f %-8.1f\n",
               i + 1,
               students[i].id,
               students[i].name,
               students[i].total,
               students[i].average);
    }
    printf("----------------------------------------------\n\n");
}

/* 冒泡排序 - 按总分降序排列
   参数1:学生数组
   参数2:学生数量
   参数3:排序方式 1=升序 0=降序 */
void bubbleSort(STU students[], int count, int order) {
    int i, j;       /* 循环变量 */
    STU temp;       /* 临时变量,用于交换 */

    /* 外层循环:控制比较的轮数,共需要count-1轮 */
    for (i = 0; i < count - 1; i++) {
        /* 内层循环:每轮比较的次数
           第i轮需要比较 count-1-i 次
           因为每轮结束后,末尾已经排好了一个元素 */
        for (j = 0; j < count - 1 - i; j++) {

            /* 比较相邻两个学生的总分 */
            if (order == 1) {
                /* 升序:如果前面的总分大于后面的,交换 */
                if (students[j].total > students[j + 1].total) {
                    temp = students[j];             /* 暂存第j个元素 */
                    students[j] = students[j + 1];  /* 第j个 = 第j+1个 */
                    students[j + 1] = temp;         /* 第j+1个 = 暂存值 */
                }
            } else {
                /* 降序:如果前面的总分小于后面的,交换 */
                if (students[j].total < students[j + 1].total) {
                    temp = students[j];
                    students[j] = students[j + 1];
                    students[j + 1] = temp;
                }
            }
        }
    }
}

int main() {
    STU students[MAX_STU] = {
        {"2024003", "王五", {95, 88, 90}, 273, 91.0},
        {"2024001", "张三", {90, 85, 92}, 267, 89.0},
        {"2024004", "赵六", {88, 92, 85}, 265, 88.3},
        {"2024002", "李四", {78, 92, 88}, 258, 86.0}
    };
    int count = 4;
    int choice;

    printf("排序前:\n");
    displayAll(students, count);

    printf("请选择排序方式:\n");
    printf("1. 按总分升序排列(从低到高)\n");
    printf("2. 按总分降序排列(从高到低)\n");
    printf("请输入选择: ");
    scanf("%d", &choice);

    if (choice == 1) {
        bubbleSort(students, count, 1);     /* 升序排序 */
        printf("\n按总分升序排列:\n");
    } else {
        bubbleSort(students, count, 0);     /* 降序排序 */
        printf("\n按总分降序排列:\n");
    }

    displayAll(students, count);            /* 显示排序结果 */

    return 0;
}

运行结果:

复制代码
排序前:

排名  学号         姓名       总分     平均分
----------------------------------------------
1     2024003      王五       273.0    91.0
2     2024001      张三       267.0    89.0
3     2024004      赵六       265.0    88.3
4     2024002      李四       258.0    86.0
----------------------------------------------

请选择排序方式:
1. 按总分升序排列(从低到高)
2. 按总分降序排列(从高到低)
请输入选择: 2

按总分降序排列:

排名  学号         姓名       总分     平均分
----------------------------------------------
1     2024003      王五       273.0    91.0
2     2024001      张三       267.0    89.0
3     2024004      赵六       265.0    88.3
4     2024002      李四       258.0    86.0
----------------------------------------------

13.5 选择排序(补充)

c 复制代码
/* 选择排序:每次选出最小(大)的元素放到前面
   参数1:学生数组
   参数2:学生数量 */
void selectionSort(STU students[], int count) {
    int i, j;
    int min_idx;        /* 当前轮次中最小元素的下标 */
    STU temp;

    /* 外层循环:从第0个位置到倒数第2个位置 */
    for (i = 0; i < count - 1; i++) {
        min_idx = i;    /* 假设当前位置的元素最小 */

        /* 内层循环:在未排序部分中找到真正的最小值 */
        for (j = i + 1; j < count; j++) {
            if (students[j].total < students[min_idx].total) {
                min_idx = j;    /* 更新最小值下标 */
            }
        }

        /* 如果最小值不在当前位置,则交换 */
        if (min_idx != i) {
            temp = students[i];
            students[i] = students[min_idx];
            students[min_idx] = temp;
        }
    }
}

十四、显示所有学生信息模块

14.1 知识点概述

显示模块遍历结构体数组,以表格形式输出所有学生的完整信息。需要用到格式化输出控制。

14.2 printf 格式化输出详解

格式符 含义 示例
%s 字符串 printf("%s", "张三")
%d 十进制整数 printf("%d", 42)
%f 浮点数 printf("%f", 3.14)
%-10s 左对齐,宽度10 printf("%-10s", "张三")
%8d 右对齐,宽度8 printf("%8d", 100)
%.1f 保留1位小数 printf("%.1f", 92.5)
%8.1f 宽度8,保留1位小数 printf("%8.1f", 92.5)

对齐方式说明:

  • %-10s:负号表示左对齐,宽度为10个字符,不足的右边补空格
  • %10s:正号(默认)表示右对齐,宽度为10个字符,不足的左边补空格

14.3 完整显示示例

c 复制代码
#include <stdio.h>
#include <string.h>

#define MAX_STU 100
#define COURSE_NUM 3

typedef struct {
    char id[20];            /* 学号 */
    char name[20];          /* 姓名 */
    float score[COURSE_NUM]; /* 各科成绩 */
    float total;            /* 总分 */
    float average;          /* 平均分 */
} STU;

/* 显示表头 */
void displayHeader() {
    printf("\n");
    printf("%-4s %-12s %-8s", "序号", "学号", "姓名");
    int i;
    for (i = 0; i < COURSE_NUM; i++) {
        printf(" %-6s", "成绩");    /* 动态输出课程列 */
    }
    printf("  %-8s %-8s\n", "总分", "平均分");

    /* 打印分隔线 */
    printf("------------------------------------------------------------------\n");
}

/* 显示所有学生信息
   参数1:学生数组
   参数2:当前学生数量 */
void displayAll(STU students[], int count) {
    int i, j;

    if (count == 0) {
        printf("\n当前没有学生记录!\n\n");
        return;
    }

    displayHeader();    /* 先显示表头 */

    /* 遍历并显示每个学生的信息 */
    for (i = 0; i < count; i++) {
        printf("%-4d %-12s %-8s",
               i + 1,                  /* 序号(从1开始) */
               students[i].id,         /* 学号 */
               students[i].name);      /* 姓名 */

        /* 显示各科成绩 */
        for (j = 0; j < COURSE_NUM; j++) {
            printf(" %-6.1f", students[i].score[j]);
        }

        /* 显示总分和平均分 */
        printf("  %-8.1f %-8.1f\n",
               students[i].total,
               students[i].average);
    }

    printf("------------------------------------------------------------------\n");
    printf("共 %d 名学生\n\n", count);
}

int main() {
    STU students[MAX_STU] = {
        {"2024001", "张三", {90, 85, 92}, 267, 89.0},
        {"2024002", "李四", {78, 92, 88}, 258, 86.0},
        {"2024003", "王五", {95, 88, 90}, 273, 91.0}
    };
    int count = 3;

    displayAll(students, count);

    return 0;
}

运行结果:

复制代码
序号 学号         姓名     成绩   成绩   成绩    总分     平均分
------------------------------------------------------------------
1    2024001      张三     90.0   85.0   92.0    267.0    89.0
2    2024002      李四     78.0   92.0   88.0    258.0    86.0
3    2024003      王五     95.0   88.0   90.0    273.0    91.0
------------------------------------------------------------------
共 3 名学生

十五、主函数设计与程序整合

15.1 知识点概述

主函数是整个程序的入口点,负责协调各个模块。完整的主函数需要在启动时加载文件数据,运行时根据用户选择调用各功能模块,退出前保存数据。

15.2 完整的主函数框架

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* ==================== 宏定义 ==================== */
#define MAX_STU 100             /* 最大学生数量 */
#define COURSE_NUM 3            /* 课程门数 */
#define FILENAME "student.dat"  /* 数据文件名 */

/* ==================== 结构体定义 ==================== */
typedef struct {
    char id[20];                /* 学号 */
    char name[20];              /* 姓名 */
    float score[COURSE_NUM];   /* 各科成绩 */
    float total;                /* 总分 */
    float average;              /* 平均分 */
} STU;

/* ==================== 全局变量 ==================== */
STU students[MAX_STU];          /* 全局学生数组 */
int count = 0;                  /* 当前学生数量 */

/* ==================== 函数声明 ==================== */
void menu(void);                        /* 显示主菜单 */
void loadData(void);                    /* 从文件加载数据 */
void saveData(void);                    /* 保存数据到文件 */
void inputStudent(void);                /* 录入学生信息 */
void searchStudent(void);               /* 查询学生信息 */
void deleteStudent(void);               /* 删除学生信息 */
void modifyStudent(void);               /* 修改学生信息 */
void insertStudent(void);               /* 插入学生信息 */
void sortStudents(void);                /* 学生成绩排序 */
void displayAll(void);                  /* 显示所有学生 */
void clearInput(void);                  /* 清空输入缓冲区 */

/* ==================== 主函数 ==================== */
int main() {
    int choice;     /* 用户菜单选择 */

    loadData();     /* 程序启动时从文件加载数据 */

    while (1) {     /* 主循环 */
        menu();     /* 显示菜单 */

        printf("请输入您的选择(0-8): ");
        scanf("%d", &choice);
        clearInput();               /* 清空输入缓冲区 */

        switch (choice) {
            case 1: inputStudent(); break;      /* 录入 */
            case 2: searchStudent(); break;     /* 查询 */
            case 3: deleteStudent(); break;     /* 删除 */
            case 4: modifyStudent(); break;     /* 修改 */
            case 5: insertStudent(); break;     /* 插入 */
            case 6: sortStudents(); break;      /* 排序 */
            case 7: displayAll(); break;        /* 显示 */
            case 8: saveData(); break;          /* 保存 */
            case 0:
                saveData();                     /* 退出前自动保存 */
                printf("\n数据已保存,感谢使用,再见!\n");
                exit(0);                        /* 退出程序 */
            default:
                printf("\n输入无效,请重新选择!\n\n");
                break;
        }
    }

    return 0;
}

/* ==================== 功能函数实现 ==================== */

/* 显示主菜单 */
void menu(void) {
    printf("╔═══════════════════════════════════╗\n");
    printf("║      学生信息管理系统 v1.0        ║\n");
    printf("╠═══════════════════════════════════╣\n");
    printf("║  1. 录入学生信息                  ║\n");
    printf("║  2. 查询学生信息                  ║\n");
    printf("║  3. 删除学生信息                  ║\n");
    printf("║  4. 修改学生信息                  ║\n");
    printf("║  5. 插入学生信息                  ║\n");
    printf("║  6. 学生成绩排序                  ║\n");
    printf("║  7. 显示所有学生信息              ║\n");
    printf("║  8. 保存到文件                    ║\n");
    printf("║  0. 退出系统                      ║\n");
    printf("╚═══════════════════════════════════╝\n");
}

/* 从文件加载数据 */
void loadData(void) {
    FILE *fp = fopen(FILENAME, "rb");   /* 以二进制读模式打开 */
    if (fp != NULL) {
        /* 读取文件中的所有学生记录 */
        count = fread(students, sizeof(STU), MAX_STU, fp);
        fclose(fp);
        printf("已从文件加载 %d 条学生记录\n\n", count);
    } else {
        printf("数据文件不存在,将创建新文件\n\n");
    }
}

/* 保存数据到文件 */
void saveData(void) {
    FILE *fp = fopen(FILENAME, "wb");   /* 以二进制写模式打开(覆盖) */
    if (fp != NULL) {
        fwrite(students, sizeof(STU), count, fp);   /* 写入全部数据 */
        fclose(fp);
        printf("数据已保存到 %s\n", FILENAME);
    } else {
        printf("保存失败!\n");
    }
}

/* 清空输入缓冲区 */
void clearInput(void) {
    int c;
    while ((c = getchar()) != '\n' && c != EOF) {
        /* 丢弃缓冲区中的所有字符 */
    }
}

/* 以下是各功能函数的具体实现
   (此处为框架示意,完整实现在前面各模块中已详细讲解)
*/

/* 录入学生信息 */
void inputStudent(void) {
    /* 实现代码参见第八节 */
}

/* 查询学生信息 */
void searchStudent(void) {
    /* 实现代码参见第九节 */
}

/* 删除学生信息 */
void deleteStudent(void) {
    /* 实现代码参见第十节 */
}

/* 修改学生信息 */
void modifyStudent(void) {
    /* 实现代码参见第十一节 */
}

/* 插入学生信息 */
void insertStudent(void) {
    /* 实现代码参见第十二节 */
}

/* 学生成绩排序 */
void sortStudents(void) {
    /* 实现代码参见第十三节 */
}

/* 显示所有学生信息 */
void displayAll(void) {
    /* 实现代码参见第十四节 */
}

十六、知识点总结

知识点 核心内容 对应模块
结构体(struct) 自定义数据类型,typedef别名,结构体数组,结构体指针 所有模块
预处理指令 #include文件包含,#define宏定义,条件编译#ifndef 预处理模块
文件操作 fopen/fclose打开关闭,fread/fwrite二进制读写,fprintf/fscanf文本读写,fseek/ftell/rewind 录入、保存、加载
字符串函数 strcpy复制,strcmp比较,strstr查找,strlen长度 查询、删除、修改
输入输出 scanf格式输入,printf格式输出,getchar缓冲区处理 所有模块
switch-case 多分支选择,break穿透,default默认 主菜单
函数设计 函数定义、声明、调用,参数传递(数组、指针) 所有模块
冒泡排序 相邻比较、交换,升序/降序控制 排序模块
数组操作 元素前移(删除),元素后移(插入),遍历查找 删除、插入、查询
循环控制 while(1)无限循环,for遍历,break/continue 所有模块

以上就是学生信息管理系统涉及的全部核心知识点。每个知识点都配有入门级的代码示例和逐行注释,从基础的结构体定义到完整的文件读写操作,循序渐进地覆盖了项目所需的全部C语言语法和编程技巧。

相关推荐
ha_lydms1 小时前
AnalyticDB MySQL 技术架构
大数据·hadoop·mysql·adb·spark·dataworks·1024程序员节
zh路西法1 小时前
【四足强化学习经典算法】(一):RMA——盲视运动控制与快速适应模块
pytorch·强化学习·1024程序员节·四足机器人·dagger·pyhon·rma
太理摆烂哥1 小时前
MySQL索引
1024程序员节
爱读书的小胖1 小时前
LeetCode 55 跳跃游戏 C 语言解题分析
c语言·数据结构·算法·leetcode·1024程序员节
Sakura_j_h_1 小时前
Three.js 单文件离线机械臂工作台:爆炸图、透明外壳与剖面
前端·机械臂·webgl·three.js·1024程序员节·esbuild
花间相见1 小时前
【计算基础|MySQL14】MySQL 锁(上):种类与加锁规则
1024程序员节
花间相见1 小时前
【计算基础|MySQL15】MySQL 锁(下):锁全表与死锁
1024程序员节
sdm0704271 小时前
back_insert_iterator-尾插迭代器类
1024程序员节
破芽1 小时前
learn-claude-code-s05-s11-学习笔记
笔记·学习