C言語による構造体とリンクリストを活用したデータ管理の実践

構造体配列の集計とソート処理

C言語において、複数の異なるデータ型をまとめる構造体は、学生の情報や商品在庫などの複合データを扱う際に不可欠です。ここでは、構造体配列を用いて成績の集計や売上データのランキング処理を実装します。

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

#define MAX_STUDENTS 5

typedef struct {
    int student_id;
    char full_name[32];
    char course_name[32];
    double continuous_assessment;
    double midterm_exam;
    double final_exam;
    double final_grade;
    char letter_grade[16];
} AcademicRecord;

void fetch_records(AcademicRecord records[], int count);
void render_records(const AcademicRecord records[], int count);
void evaluate_grades(AcademicRecord records[], int count);
int extract_failing(const AcademicRecord src[], AcademicRecord dest[], int count);
void rank_by_grade(AcademicRecord records[], int count);

int main() {
    AcademicRecord class_roster[MAX_STUDENTS];
    AcademicRecord failing_students[MAX_STUDENTS];
    int fail_count;

    fetch_records(class_roster, MAX_STUDENTS);
    evaluate_grades(class_roster, MAX_STUDENTS);
    
    fail_count = extract_failing(class_roster, failing_students, MAX_STUDENTS);
    rank_by_grade(class_roster, MAX_STUDENTS);

    printf("--- 全学生の成績一覧 ---\n");
    render_records(class_roster, MAX_STUDENTS);

    printf("\n--- 不合格の学生一覧 ---\n");
    render_records(failing_students, fail_count);

    return 0;
}

void fetch_records(AcademicRecord records[], int count) {
    for (int i = 0; i < count; i++) {
        scanf("%d %s %s %lf %lf %lf", 
              &records[i].student_id, records[i].full_name, records[i].course_name,
              &records[i].continuous_assessment, &records[i].midterm_exam, &records[i].final_exam);
    }
}

void render_records(const AcademicRecord records[], int count) {
    printf("ID\t名前\t\t科目\t平常\t中間\t期末\t総合\t等級\n");
    for (int i = 0; i < count; i++) {
        printf("%d\t%-10s\t%-6s\t%.0f\t%.0f\t%.0f\t%.1f\t%s\n",
               records[i].student_id, records[i].full_name, records[i].course_name,
               records[i].continuous_assessment, records[i].midterm_exam, 
               records[i].final_exam, records[i].final_grade, records[i].letter_grade);
    }
}

void evaluate_grades(AcademicRecord records[], int count) {
    for (int i = 0; i < count; i++) {
        records[i].final_grade = records[i].continuous_assessment * 0.2 + 
                                 records[i].midterm_exam * 0.2 + 
                                 records[i].final_exam * 0.6;
        
        if (records[i].final_grade >= 90) strcpy(records[i].letter_grade, "優秀");
        else if (records[i].final_grade >= 80) strcpy(records[i].letter_grade, "良好");
        else if (records[i].final_grade >= 70) strcpy(records[i].letter_grade, "標準");
        else if (records[i].final_grade >= 60) strcpy(records[i].letter_grade, "及格");
        else strcpy(records[i].letter_grade, "不合格");
    }
}

int extract_failing(const AcademicRecord src[], AcademicRecord dest[], int count) {
    int idx = 0;
    for (int i = 0; i < count; i++) {
        if (src[i].final_grade < 60.0) {
            dest[idx++] = src[i];
        }
    }
    return idx;
}

void rank_by_grade(AcademicRecord records[], int count) {
    for (int i = 0; i < count - 1; i++) {
        int max_idx = i;
        for (int j = i + 1; j < count; j++) {
            if (records[j].final_grade > records[max_idx].final_grade) {
                max_idx = j;
            }
        }
        if (max_idx != i) {
            AcademicRecord temp = records[i];
            records[i] = records[max_idx];
            records[max_idx] = temp;
        }
    }
}

ポインタによる配列走査と検索

構造体配列の要素にアクセスする際、インデックスではなくポインタ演算を用いることで、メモリ上の連続性を意識した効率的な走査が可能になります。

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

#define MAX_BOOKS 10
#define STR_LEN 80

typedef struct {
    char title[STR_LEN];
    char writer[STR_LEN];
} LibraryCatalog;

int main() {
    LibraryCatalog inventory[MAX_BOOKS] = { 
        {"1984", "George Orwell"},
        {"Brave New World", "Aldous Huxley"},
        {"The World of Yesterday", "Stefan Zweig"}, 
        {"1587, a Year of No Significance", "Ray Huang"},
        {"A Silent Spring", "Rachel Carson"},
        {"One Hundred Years of Solitude", "Gabriel Garcia Marquez"},
        {"84 Charing Cross Road", "Helene Hanff"},
        {"Just Kids", "Patti Smith"}, 
        {"The Razor's Edge", "W. Somerset Maugham"},
        {"The Silent Majority", "Wang Xiaobo"} 
    };
    
    LibraryCatalog *ptr;
    char target_writer[STR_LEN];
    int is_found = 0;

    printf("--- 全図書情報 ---\n");
    for (ptr = inventory; ptr < inventory + MAX_BOOKS; ++ptr) {
        printf("%-35s | %s\n", ptr->title, ptr->writer);
    }

    printf("\n--- 著者名による検索 ---\n");
    printf("著者名を入力: ");
    scanf("%s", target_writer);
    
    for (ptr = inventory; ptr < inventory + MAX_BOOKS; ++ptr) {
        if (strcmp(ptr->writer, target_writer) == 0) {
            is_found = 1;
            printf("発見: %-35s | %s\n", ptr->title, ptr->writer);
        }
    }
    
    if (!is_found) {
        printf("該当する著者の図書の登録はありません。\n");
    }

    return 0;
}

動的メモリとリンクリストの構築

データ数が実行時にしか確定しない場合、配列ではなくリンクリストを用いて動的にメモリを確保します。ここでは、映画情報を格納するノードを順次生成し、リストを構築します。

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

#define STR_MAX 80

typedef struct MediaNode {
    char movie_title[STR_MAX];
    char director_name[STR_MAX];
    char release_region[STR_MAX];
    int release_year;
    struct MediaNode *next_node;
} MediaArchive;

MediaArchive* append_node(MediaArchive *head, int total_nodes);
void traverse_list(const MediaArchive *head);

int main() {
    int node_count;
    MediaArchive *list_head = NULL;
    MediaArchive *tail_ptr = NULL;

    printf("登録する映画の数目: ");
    scanf("%d", &node_count);

    // ダミーのヘッドノードを作成
    list_head = (MediaArchive *)malloc(sizeof(MediaArchive));
    list_head->next_node = NULL;
    tail_ptr = list_head;

    // 尾挿法(テールインサート)でノードを追加
    for (int i = 0; i < node_count; ++i) {
        MediaArchive *new_node = (MediaArchive *)malloc(sizeof(MediaArchive));
        printf("映画 %d の情報 (タイトル 監督 地域 年): ", i + 1);
        scanf("%s %s %s %d", new_node->movie_title, new_node->director_name, 
              new_node->release_region, &new_node->release_year);
        
        new_node->next_node = NULL;
        tail_ptr->next_node = new_node;
        tail_ptr = new_node;
    }

    printf("\n--- 登録済み映画リスト ---\n");
    traverse_list(list_head);

    // メモリ解放(簡易実装)
    MediaArchive *current = list_head;
    while (current != NULL) {
        MediaArchive *temp = current;
        current = current->next_node;
        free(temp);
    }

    return 0;
}

void traverse_list(const MediaArchive *head) {
    const MediaArchive *walker = head->next_node;
    while (walker != NULL) {
        printf("%-25s | %-20s | %-10s | %d\n", 
               walker->movie_title, walker->director_name, 
               walker->release_region, walker->release_year);
        walker = walker->next_node;
    }
}

日付演算、列挙型、および多条件ソート

構造体は日付のような関連する整数値のグループ化にも有効です。また、列挙型(enum)を用いると、アカウントの権限レベルのような限られた状態を型安全に表現できます。さらに、構造体の配列に対して複数の条件を組み合わせたカスタムソートを実装します。

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

// 日付構造体と演算
typedef struct {
    int year_val;
    int month_val;
    int day_val;
} CalendarDate;

void read_date(CalendarDate *d_ptr);
int calculate_day_of_year(const CalendarDate d);
int compare_two_dates(const CalendarDate d1, const CalendarDate d2);

// 列挙型とアカウント管理
typedef enum { ROLE_GUEST, ROLE_STUDENT, ROLE_ADMIN } UserRole;

typedef struct {
    char user_id[24];
    char pass_code[24];
    UserRole permission;
} SystemAccount;

void mask_and_display_accounts(const SystemAccount accounts[], int count);

// 多条件ソート(連絡先リスト)
typedef struct {
    char contact_name[32];
    char phone_number[16];
    int is_emergency; 
} PhoneBookEntry;

void sort_contacts(PhoneBookEntry entries[], int count);
void print_contacts(const PhoneBookEntry entries[], int count);

int main() {
    // 1. 日付テスト
    CalendarDate test_date;
    printf("日付を入力 (YYYY-MM-DD): ");
    read_date(&test_date);
    printf("この年の %d 日目です。\n", calculate_day_of_year(test_date));

    // 2. アカウント表示テスト
    SystemAccount users[] = {
        {"U001", "pass123", ROLE_STUDENT},
        {"A999", "root!@#", ROLE_ADMIN},
        {"U002", "qwerty", ROLE_GUEST}
    };
    printf("\n--- アカウント一覧 ---\n");
    mask_and_display_accounts(users, 3);

    // 3. 連絡先ソートテスト
    PhoneBookEntry contacts[] = {
        {"Yamada", "090-1111", 0},
        {"Suzuki", "080-2222", 1},
        {"Tanaka", "070-3333", 0},
        {"Kato", "090-4444", 1}
    };
    printf("\n--- ソート前の連絡先 ---\n");
    print_contacts(contacts, 4);
    
    sort_contacts(contacts, 4);
    printf("\n--- ソート後の連絡先 (緊急優先・名前順) ---\n");
    print_contacts(contacts, 4);

    return 0;
}

void read_date(CalendarDate *d_ptr) {
    scanf("%d-%d-%d", &d_ptr->year_val, &d_ptr->month_val, &d_ptr->day_val);
}

int calculate_day_of_year(const CalendarDate d) {
    int days_per_month[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    int total_days = 0;

    if ((d.year_val % 4 == 0 && d.year_val % 100 != 0) || (d.year_val % 400 == 0)) {
        days_per_month[2] = 29;
    }

    for (int m = 1; m < d.month_val; m++) {
        total_days += days_per_month[m];
    }
    return total_days + d.day_val;
}

int compare_two_dates(const CalendarDate d1, const CalendarDate d2) {
    if (d1.year_val != d2.year_val) return (d1.year_val < d2.year_val) ? -1 : 1;
    if (d1.month_val != d2.month_val) return (d1.month_val < d2.month_val) ? -1 : 1;
    if (d1.day_val != d2.day_val) return (d1.day_val < d2.day_val) ? -1 : 1;
    return 0;
}

void mask_and_display_accounts(const SystemAccount accounts[], int count) {
    for (int i = 0; i < count; i++) {
        printf("ID: %-10s | PASS: ", accounts[i].user_id);
        for (int j = 0; j < strlen(accounts[i].pass_code); j++) {
            printf("*");
        }
        
        printf(" | ROLE: ");
        switch (accounts[i].permission) {
            case ROLE_ADMIN: printf("Administrator\n"); break;
            case ROLE_STUDENT: printf("Student\n"); break;
            default: printf("Guest\n"); break;
        }
    }
}

void sort_contacts(PhoneBookEntry entries[], int count) {
    for (int i = 0; i < count - 1; i++) {
        for (int j = 0; j < count - 1 - i; j++) {
            int should_swap = 0;
            
            // 条件1: 緊急連絡先ではないものが前にある場合
            if (entries[j].is_emergency == 0 && entries[j + 1].is_emergency == 1) {
                should_swap = 1;
            }
            // 条件2: どちらも緊急、またはどちらも通常の場合、名前の辞書順
            else if (entries[j].is_emergency == entries[j + 1].is_emergency) {
                if (strcmp(entries[j].contact_name, entries[j + 1].contact_name) > 0) {
                    should_swap = 1;
                }
            }

            if (should_swap) {
                PhoneBookEntry temp = entries[j];
                entries[j] = entries[j + 1];
                entries[j + 1] = temp;
            }
        }
    }
}

void print_contacts(const PhoneBookEntry entries[], int count) {
    for (int i = 0; i < count; i++) {
        printf("%-15s | %-15s", entries[i].contact_name, entries[i].phone_number);
        if (entries[i].is_emergency) {
            printf(" [緊急]");
        }
        printf("\n");
    }
}

タグ: C言語 構造体 リンクリスト ポインタ 列挙型

10月9日 06:57 投稿