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
- Add students (name, ID, three subject scores)
- Display all student information
- Search by student ID
- Calculate each student's total and average score
- Sort by total score
- Save to file / load from file
- Dynamic memory management — student count is not fixed
Data Structure Design
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
#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
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;
}
--- 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
- Add contacts (name, phone, email, group)
- Search by name
- Filter by group
- Delete a contact
- Modify contact information
- Save to file / load from file
Data Structure Design
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
#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
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;
}
--- 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:
- Struct contains
data pointer + count + capacity initallocates initial memoryaddchecks capacity and usesreallocto expand when fullfreereleases 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
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: What if I need to preserve order when using the swap-with-last delete? A: Use
memmoveto 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/fscanfwith%sstops at whitespace. Usefgetsto read an entire line, or agree to use underscores instead of spaces, or use a fixed-width format like%31[^\n].**
📖 Summary
- Dynamic arrays use a
pointer + count + capacitystruct, with a 2x expansion strategy - CRUD (create, read, update, delete) is the core of data management — every project revolves around these four operations
- File saving writes the record count first, then each record; reading loads the count first, then each record
- Deleting a middle element can use swap-with-last (fast but unordered) or shift (order-preserving)
- Combining structs, dynamic memory, and file I/O is the fundamental pattern for C projects
📝 Exercises
- Add an "edit scores" feature to the student grade management system: input a student ID and new scores to update the corresponding student record
- 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)
- Split both projects into multi-file structures: each project should have at least a
.hdeclaration file and a.cimplementation file, with a corresponding Makefile



