Practice: Custom Types

You've learned enough — now it's time to build something. Like learning to cook: reading recipes isn't enough; you have to get in the kitchen. Combine structs, dynamic memory, and file I/O to create working programs.

Project 1: Student Grade Management System

Requirements

Data Structure Design

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 manages students with a dynamic array — initial capacity is 4, expanding when full.

Core Implementation

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 Chinese:%-3d Math:%-3d English:%-3d Total:%-4d Avg:%.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("ID     Name        Chinese Math English Total Avg\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;
}

Example

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

    Student s1 = {"001", "Zhang", 85, 92, 78};
    Student s2 = {"002", "Li", 90, 88, 95};
    Student s3 = {"003", "Wang", 72, 65, 80};

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

    printf("--- All Students ---\n");
    print_all(&list);

    sort_by_total(&list);
    printf("\n--- Sorted by Total ---\n");
    print_all(&list);

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

    save_to_file(&list, "students.dat");
    printf("\nSaved to students.dat\n");

    list_free(&list);
    return 0;
}
▶ Try it Yourself
TEXT
--- All Students ---
ID     Name        Chinese Math English Total Avg
----------------------------------------------------------
001    Zhang       Chinese:85  Math:92  English:78  Total:255  Avg:85.0
002    Li          Chinese:90  Math:88  English:95  Total:273  Avg:91.0
003    Wang        Chinese:72  Math:65  English:80  Total:217  Avg:72.3

--- Sorted by Total ---
ID     Name        Chinese Math English Total Avg
----------------------------------------------------------
002    Li          Chinese:90  Math:88  English:95  Total:273  Avg:91.0
001    Zhang       Chinese:85  Math:92  English:78  Total:255  Avg:85.0
003    Wang        Chinese:72  Math:65  English:80  Total:217  Avg:72.3

Found 001: Zhang Total=255

Saved to students.dat

Project 2: Contact Management

Requirements

Data Structure Design

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;

Core Implementation

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("--- Group: %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", "Name", "Phone", "Email", "Group");
    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;
}

Example

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

    Contact c1 = {"Zhang", "13800001111", "zhangsan@mail.com", "Coworker"};
    Contact c2 = {"Li", "13900002222", "lisi@mail.com", "Friend"};
    Contact c3 = {"Wang", "15000003333", "wangwu@mail.com", "Coworker"};
    Contact c4 = {"Zhao", "18600004444", "zhaoliu@mail.com", "Family"};

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

    printf("--- All Contacts ---\n");
    print_all_contacts(&list);

    clist_filter_by_group(&list, "Coworker");

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

    clist_remove(&list, "Wang");
    printf("\n--- After Deleting Wang ---\n");
    print_all_contacts(&list);

    save_contacts(&list, "contacts.dat");
    printf("\nSaved to contacts.dat\n");

    clist_free(&list);
    return 0;
}
▶ Try it Yourself
TEXT
--- All Contacts ---
Name     Phone           Email                    Group
---------------------------------------------------------
Zhang    13800001111     zhangsan@mail.com        Coworker
Li       13900002222     lisi@mail.com            Friend
Wang     15000003333     wangwu@mail.com          Coworker
Zhao     18600004444     zhaoliu@mail.com         Family

--- Group: Coworker ---
Zhang  13800001111  zhangsan@mail.com
Wang  15000003333  wangwu@mail.com

Found Li: 13900002222 lisi@mail.com

--- After Deleting Wang ---
Name     Phone           Email                    Group
---------------------------------------------------------
Zhang    13800001111     zhangsan@mail.com        Coworker
Li       13900002222     lisi@mail.com            Friend
Zhao     18600004444     zhaoliu@mail.com         Family

Saved to contacts.dat

Key Patterns Review

The Dynamic Array Pattern

Both projects use the same dynamic array pattern:

  1. Struct contains data pointer + count + capacity
  2. init allocates initial memory
  3. add checks capacity and uses realloc to expand when full
  4. free releases memory and sets to NULL

This is the most common way to implement dynamic collections in C. Master it once, and you can reuse it in any scenario.

The File I/O Pattern

C
fprintf(fp, "%d\n", count);
for (int i = 0; i < count; i++) {
    fprintf(fp, "field1 field2 ...\n", ...);
}

When reading, first read the count, then loop through the records. Note that %s stops at whitespace — either ensure fields don't contain spaces, or use fscanf with a fixed format.

The Delete Trick

When deleting a middle element from an array, overwrite it with the last element, then count--. This avoids moving many elements but doesn't preserve order. If order matters, use memmove to shift subsequent elements forward.

❓ FAQ

Q Why double the capacity instead of adding 1?
A Doubling is an amortized O(1) strategy — the total number of copies is far fewer than incrementing by 1 each time. This is a classic result from algorithm analysis, and C++'s vector uses the same strategy.
Q Should I save files in binary or text format?
A Text format is readable, manually editable, and cross-platform. Binary is more compact and faster to read/write but not human-readable. Text is more intuitive for learning projects.

Q: What if I need to preserve order when using the swap-with-last delete? A: Use memmove to shift elements forward: memmove(&data[i], &data[i+1], (count-i-1)*sizeof(T)). Or switch to a linked list structure.**

Q: What if a name contains spaces? A: scanf/fscanf with %s stops at whitespace. Use fgets to read an entire line, or agree to use underscores instead of spaces, or use a fixed-width format like %31[^\n].**

📖 Summary

📝 Exercises

  1. Add an "edit scores" feature to the student grade management system: input a student ID and new scores to update the corresponding student record
  2. Add a "search by phone number" feature to the contact manager, and change the storage to keep contacts sorted by name (maintain order on insertion)
  3. Split both projects into multi-file structures: each project should have at least a .h declaration file and a .c implementation file, with a corresponding Makefile
100%

🙏 帮我们做得更好

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

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