C: 实战:自定义类型综合

学了这么多,该动手造东西了。就像学做菜,光看食谱不够,得上灶台——把结构体、动态内存、文件操作串起来,做出能用的程序。

1. 项目一:学生成绩管理系统

(1) 需求分析

(2) 数据结构设计

C
typedef struct {
    char name[32];
    char id[16];
    int chinese;
    int math;
    int english;
} Student;

typedef struct {
    Student *data;
    int count;
    int capacity;
} StudentList;

StudentList 用动态数组管理学生,初始容量 4,不够时扩容。

(3) 核心实现

C
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    char name[32];
    char id[16];
    int chinese;
    int math;
    int english;
} Student;

typedef struct {
    Student *data;
    int count;
    int capacity;
} StudentList;

void list_init(StudentList *list) {
    list->capacity = 4;
    list->count = 0;
    list->data = (Student *)malloc(sizeof(Student) * list->capacity);
}

void list_free(StudentList *list) {
    free(list->data);
    list->data = NULL;
    list->count = 0;
    list->capacity = 0;
}

void list_expand(StudentList *list) {
    if (list->count < list->capacity) return;
    list->capacity *= 2;
    Student *tmp = (Student *)realloc(list->data, sizeof(Student) * list->capacity);
    if (tmp) list->data = tmp;
}

void list_add(StudentList *list, const Student *s) {
    list_expand(list);
    list->data[list->count++] = *s;
}

int total_score(const Student *s) {
    return s->chinese + s->math + s->english;
}

double avg_score(const Student *s) {
    return total_score(s) / 3.0;
}

Student *find_by_id(StudentList *list, const char *id) {
    for (int i = 0; i < list->count; i++) {
        if (strcmp(list->data[i].id, id) == 0) {
            return &list->data[i];
        }
    }
    return NULL;
}

int cmp_by_total(const void *a, const void *b) {
    int ta = total_score((const Student *)a);
    int tb = total_score((const Student *)b);
    return tb - ta;
}

void sort_by_total(StudentList *list) {
    qsort(list->data, list->count, sizeof(Student), cmp_by_total);
}

void print_student(const Student *s) {
    printf("%-6s %-10s 语文:%-3d 数学:%-3d 英语:%-3d 总分:%-4d 均分:%.1f\n",
           s->id, s->name, s->chinese, s->math, s->english,
           total_score(s), avg_score(s));
}

void print_all(const StudentList *list) {
    printf("学号   姓名       语文  数学  英语  总分  均分\n");
    printf("----------------------------------------------------------\n");
    for (int i = 0; i < list->count; i++) {
        print_student(&list->data[i]);
    }
}

int save_to_file(const StudentList *list, const char *filename) {
    FILE *fp = fopen(filename, "w");
    if (!fp) return -1;
    fprintf(fp, "%d\n", list->count);
    for (int i = 0; i < list->count; i++) {
        Student *s = &list->data[i];
        fprintf(fp, "%s %s %d %d %d\n", s->id, s->name,
                s->chinese, s->math, s->english);
    }
    fclose(fp);
    return 0;
}

int load_from_file(StudentList *list, const char *filename) {
    FILE *fp = fopen(filename, "r");
    if (!fp) return -1;
    int n;
    fscanf(fp, "%d", &n);
    for (int i = 0; i < n; i++) {
        Student s;
        fscanf(fp, "%s %s %d %d %d", s.id, s.name,
               &s.chinese, &s.math, &s.english);
        list_add(list, &s);
    }
    fclose(fp);
    return 0;
}

▶ 示例

C
int main(void) {
    StudentList list;
    list_init(&list);

    Student s1 = {"001", "张三", 85, 92, 78};
    Student s2 = {"002", "李四", 90, 88, 95};
    Student s3 = {"003", "王五", 72, 65, 80};

    list_add(&list, &s1);
    list_add(&list, &s2);
    list_add(&list, &s3);

    printf("--- 所有学生 ---\n");
    print_all(&list);

    sort_by_total(&list);
    printf("\n--- 按总分排序 ---\n");
    print_all(&list);

    Student *found = find_by_id(&list, "001");
    if (found) {
        printf("\n查找001: %s 总分=%d\n", found->name, total_score(found));
    }

    save_to_file(&list, "students.dat");
    printf("\n已保存到 students.dat\n");

    list_free(&list);
    return 0;
}
▶ 试一试
TEXT 📖 仅展示
--- 所有学生 ---
学号   姓名       语文  数学  英语  总分  均分
----------------------------------------------------------
001    张三        语文:85  数学:92  英语:78  总分:255  均分:85.0
002    李四        语文:90  数学:88  英语:95  总分:273  均分:91.0
003    王五        语文:72  数学:65  英语:80  总分:217  均分:72.3

--- 按总分排序 ---
学号   姓名       语文  数学  英语  总分  均分
----------------------------------------------------------
002    李四        语文:90  数学:88  英语:95  总分:273  均分:91.0
001    张三        语文:85  数学:92  英语:78  总分:255  均分:85.0
003    王五        语文:72  数学:65  英语:80  总分:217  均分:72.3

查找001: 张三 总分=255

已保存到 students.dat

2. 项目二:通讯录管理

(1) 需求分析

(2) 数据结构设计

C
typedef struct {
    char name[32];
    char phone[16];
    char email[40];
    char group[16];
} Contact;

typedef struct {
    Contact *data;
    int count;
    int capacity;
} ContactList;

(3) 核心实现

C
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    char name[32];
    char phone[16];
    char email[40];
    char group[16];
} Contact;

typedef struct {
    Contact *data;
    int count;
    int capacity;
} ContactList;

void clist_init(ContactList *list) {
    list->capacity = 4;
    list->count = 0;
    list->data = (Contact *)malloc(sizeof(Contact) * list->capacity);
}

void clist_free(ContactList *list) {
    free(list->data);
    list->data = NULL;
    list->count = 0;
    list->capacity = 0;
}

void clist_expand(ContactList *list) {
    if (list->count < list->capacity) return;
    list->capacity *= 2;
    Contact *tmp = (Contact *)realloc(list->data, sizeof(Contact) * list->capacity);
    if (tmp) list->data = tmp;
}

void clist_add(ContactList *list, const Contact *c) {
    clist_expand(list);
    list->data[list->count++] = *c;
}

int clist_remove(ContactList *list, const char *name) {
    for (int i = 0; i < list->count; i++) {
        if (strcmp(list->data[i].name, name) == 0) {
            list->data[i] = list->data[list->count - 1];
            list->count--;
            return 0;
        }
    }
    return -1;
}

Contact *clist_find(ContactList *list, const char *name) {
    for (int i = 0; i < list->count; i++) {
        if (strcmp(list->data[i].name, name) == 0) {
            return &list->data[i];
        }
    }
    return NULL;
}

void clist_filter_by_group(const ContactList *list, const char *group) {
    printf("--- 分组: %s ---\n", group);
    for (int i = 0; i < list->count; i++) {
        if (strcmp(list->data[i].group, group) == 0) {
            printf("%s  %s  %s\n", list->data[i].name,
                   list->data[i].phone, list->data[i].email);
        }
    }
}

void print_contact(const Contact *c) {
    printf("%-8s %-14s %-24s %s\n", c->name, c->phone, c->email, c->group);
}

void print_all_contacts(const ContactList *list) {
    printf("%-8s %-14s %-24s %s\n", "姓名", "电话", "邮箱", "分组");
    printf("---------------------------------------------------------\n");
    for (int i = 0; i < list->count; i++) {
        print_contact(&list->data[i]);
    }
}

int save_contacts(const ContactList *list, const char *filename) {
    FILE *fp = fopen(filename, "w");
    if (!fp) return -1;
    fprintf(fp, "%d\n", list->count);
    for (int i = 0; i < list->count; i++) {
        Contact *c = &list->data[i];
        fprintf(fp, "%s %s %s %s\n", c->name, c->phone, c->email, c->group);
    }
    fclose(fp);
    return 0;
}

int load_contacts(ContactList *list, const char *filename) {
    FILE *fp = fopen(filename, "r");
    if (!fp) return -1;
    int n;
    fscanf(fp, "%d", &n);
    for (int i = 0; i < n; i++) {
        Contact c;
        fscanf(fp, "%s %s %s %s", c.name, c.phone, c.email, c.group);
        clist_add(list, &c);
    }
    fclose(fp);
    return 0;
}

▶ 示例

C
int main(void) {
    ContactList list;
    clist_init(&list);

    Contact c1 = {"张三", "13800001111", "zhangsan@mail.com", "同事"};
    Contact c2 = {"李四", "13900002222", "lisi@mail.com", "朋友"};
    Contact c3 = {"王五", "15000003333", "wangwu@mail.com", "同事"};
    Contact c4 = {"赵六", "18600004444", "zhaoliu@mail.com", "家人"};

    clist_add(&list, &c1);
    clist_add(&list, &c2);
    clist_add(&list, &c3);
    clist_add(&list, &c4);

    printf("--- 全部联系人 ---\n");
    print_all_contacts(&list);

    clist_filter_by_group(&list, "同事");

    Contact *found = clist_find(&list, "李四");
    if (found) {
        printf("\n查找李四: %s %s\n", found->phone, found->email);
    }

    clist_remove(&list, "王五");
    printf("\n--- 删除王五后 ---\n");
    print_all_contacts(&list);

    save_contacts(&list, "contacts.dat");
    printf("\n已保存到 contacts.dat\n");

    clist_free(&list);
    return 0;
}
▶ 试一试
TEXT 📖 仅展示
--- 全部联系人 ---
姓名     电话           邮箱                    分组
---------------------------------------------------------
张三     13800001111    zhangsan@mail.com       同事
李四     13900002222    lisi@mail.com           朋友
王五     15000003333    wangwu@mail.com         同事
赵六     18600004444    zhaoliu@mail.com        家人

--- 分组: 同事 ---
张三  13800001111  zhangsan@mail.com
王五  15000003333  wangwu@mail.com

查找李四: 13900002222 lisi@mail.com

--- 删除王五后 ---
姓名     电话           邮箱                    分组
---------------------------------------------------------
张三     13800001111    zhangsan@mail.com       同事
李四     13900002222    lisi@mail.com           朋友
赵六     18600004444    zhaoliu@mail.com        家人

已保存到 contacts.dat

3. 综合要点回顾

(1) 动态数组的模式

两个项目都用了相同的动态数组模式:

  1. 结构体包含 data 指针 + count + capacity
  2. init 分配初始内存
  3. add 时检查容量,不够就 realloc 扩容
  4. free 释放内存,置 NULL

这是 C 语言中最常用的动态集合实现方式,掌握后可以复用到各种场景。

(2) 文件读写的模式

C
fprintf(fp, "%d\n", count);
for (int i = 0; i < count; i++) {
    fprintf(fp, "字段1 字段2 ...\n", ...);
}

读取时先读个数,再循环读取每条记录。注意用 %s 读字符串时字段间不能有空格,或者用 fscanf 配合固定格式。

(3) 删除的技巧

删除数组中间元素时,用最后一个元素覆盖被删元素,然后 count--。这不需要移动大量元素,但不保持原顺序。如需保持顺序,用 memmove 移动后续元素。

❓ 常见问题

Q 动态扩容为什么乘 2 而不是加 1?
A 乘 2 是均摊 O(1) 策略,总拷贝次数远少于每次加 1。这是算法分析中的经典结论,C++ vector 也用这个策略。
Q 文件保存用二进制还是文本?
A 文本格式可读、可手动编辑、跨平台;二进制更紧凑、读写快但不可读。教学项目用文本格式更直观。
Q 删除元素用末尾覆盖不保序怎么办?
A 如果需要保持顺序,用 memmove 把后面的元素前移:memmove(&data[i], &data[i+1], (count-i-1)*sizeof(T))。或者改用链表结构。
Q 姓名含空格怎么办?
A scanf/fscanf%s 遇空格停止。改用 fgets 读整行,或约定用下划线代替空格,或用固定宽度的 %31[^\n] 格式。

📖 小节

📝 作业

  1. 为学生成绩管理系统添加"修改成绩"功能:输入学号和新成绩,更新对应学生记录
  2. 为通讯录添加"按电话号码查找"功能,并将通讯录改为按姓名排序存储(插入时保持有序)
  3. 将两个项目拆分成多文件结构:每个项目至少有 .h 声明文件和 .c 实现文件,编写对应的 Makefile
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏