練習:独自型の総合
必要な知識は十分身につきました。いよいよ何かを作る番です。料理と同じで、レシピを読むだけでは不十分で、実際にキッチンに立つ必要があります。構造体、動的メモリ、ファイル入出力を組み合わせて動くプログラムを作りましょう。
プロジェクト1:学生成績管理システム
要件
- 学生の追加(名前、学籍番号、3科目の成績)
- 全学生情報の表示
- 学籍番号による検索
- 各学生の合計点と平均点の計算
- 合計点によるソート
- ファイルへの保存/ファイルからの読み込み
- 動的メモリ管理(学生数は固定ではない)
データ構造の設計
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で、満杯になると拡張します。
コア実装
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;
}
例
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;
}
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
プロジェクト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;
コア実装
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;
}
例
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;
}
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
主要パターンの復習
動的配列パターン
両プロジェクトで同じ動的配列パターンを使っています:
- 構造体に
データポインタ + 要素数 + 容量を持たせる initで初期メモリを割り当てるaddで容量を確認し、満杯ならreallocで2倍に拡張するfreeでメモリを解放しNULLに設定する
これはC言語で動的コレクションを実装する最も一般的な方法です。一度習得すれば、どんな場面でも再利用できます。
ファイル入出力パターン
C
fprintf(fp, "%d\n", count);
for (int i = 0; i < count; i++) {
fprintf(fp, "field1 field2 ...\n", ...);
}
読み込み時はまず要素数を読み、次にレコードをループで読み込みます。%s は空白で止まるため、フィールドに空白が含まれないようにするか、fscanf の固定フォーマットを使ってください。
削除のテクニック
配列の中間要素を削除する際、最後の要素で上書きしてから count-- します。これにより多くの要素の移動を避けられますが、順序は保持されません。順序が必要な場合は memmove で後続要素を前にシフトしてください。
❓ よくある質問
Q 容量を1ずつ増やすのではなく2倍にするのはなぜですか?
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] のような固定幅フォーマットを使ってください。📖 まとめ
- 動的配列は
ポインタ + 要素数 + 容量の構造体を使い、2倍拡張戦略を採用します - CRUD(作成、読み取り、更新、削除)はデータ管理の中核であり、すべてのプロジェクトはこの4つの操作を中心に展開します
- ファイル保存ではまずレコード数を書き、次に各レコードを書きます。読み込みではまずレコード数を読み、次に各レコードを読みます
- 中間要素の削除には末尾と入れ替え(高速だが順序非保持)かシフト(順序保持)が使えます
- 構造体、動的メモリ、ファイル入出力の組み合わせがCプロジェクトの基本パターンです
📝 練習問題
- 学生成績管理システムに「成績編集」機能を追加してください。学籍番号と新しい成績を入力して対応する学生レコードを更新します
- 連絡先マネージャーに「電話番号による検索」機能を追加し、保存時に名前順でソートされるように変更してください(挿入時に順序を維持)
- 両プロジェクトを複数ファイル構造に分割してください。各プロジェクトに少なくとも
.h宣言ファイルと.c実装ファイルを用意し、対応するMakefileを作成してください



