C言語における構造体とポインタを活用したデータ管理および連結リスト実装

構造体配列による学生成績の管理とソート処理

C言語におけるデータ集約処理では、複数の関連する変数をまとめる構造体(struct)が不可欠です。以下の実装では、学生の成績データを構造体配列で管理し、評価の算出、不合格者のフィルタリング、および成績順のソート処理を行っています。ソートアルゴリズムには、要素の交換を補助関数に分離したバブルソートを適用し、コードの可読性を向上させています。

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

#define CLASS_SIZE 3

typedef struct {
    int student_id;
    char full_name[32];
    char course_name[32];
    double attendance_score;
    double midterm_score;
    double final_score;
    double overall_score;
    char grade_rank[16];
} AcademicRecord;

void fetch_records(AcademicRecord records[], int count);
void display_records(AcademicRecord records[], int count);
void evaluate_grades(AcademicRecord records[], int count);
int extract_failed_students(AcademicRecord src[], AcademicRecord dest[], int count);
void sort_by_score(AcademicRecord records[], int count);
void swap_records(AcademicRecord *a, AcademicRecord *b);

int main() {
    AcademicRecord class_records[CLASS_SIZE];
    AcademicRecord failed_records[CLASS_SIZE];
    int failed_count;

    printf("学生の情報と成績を入力してください:\n");
    fetch_records(class_records, CLASS_SIZE);

    printf("\n成績の評価処理を実行中...\n");
    evaluate_grades(class_records, CLASS_SIZE);

    failed_count = extract_failed_students(class_records, failed_records, CLASS_SIZE);
    sort_by_score(class_records, CLASS_SIZE);
    
    printf("\n--- 学生成績ランキング ---\n");
    display_records(class_records, CLASS_SIZE);

    printf("\n--- 不合格者リスト ---\n");
    display_records(failed_records, failed_count);

    return 0;
}

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

void display_records(AcademicRecord records[], int count) {
    printf("-------------------------------------------------------------\n");
    printf("ID     名前       科目       平常点 中間  期末  総評  評価\n");
    for (int i = 0; i < count; i++) {
        printf("%-6d %-10s %-10s %-5.0f %-5.0f %-5.0f %-5.1f %s\n",
               records[i].student_id, records[i].full_name, records[i].course_name,
               records[i].attendance_score, records[i].midterm_score, records[i].final_score,
               records[i].overall_score, records[i].grade_rank);
    }
}

void evaluate_grades(AcademicRecord records[], int count) {
    for (int i = 0; i < count; i++) {
        records[i].overall_score = (records[i].attendance_score * 0.2) +
                                   (records[i].midterm_score * 0.2) +
                                   (records[i].final_score * 0.6);

        if (records[i].overall_score >= 90) strcpy(records[i].grade_rank, "優");
        else if (records[i].overall_score >= 80) strcpy(records[i].grade_rank, "良");
        else if (records[i].overall_score >= 70) strcpy(records[i].grade_rank, "中");
        else if (records[i].overall_score >= 60) strcpy(records[i].grade_rank, "可");
        else strcpy(records[i].grade_rank, "不可");
    }
}

int extract_failed_students(AcademicRecord src[], AcademicRecord dest[], int count) {
    int fail_idx = 0;
    for (int i = 0; i < count; i++) {
        if (src[i].overall_score < 60) {
            dest[fail_idx++] = src[i];
        }
    }
    return fail_idx;
}

void swap_records(AcademicRecord *a, AcademicRecord *b) {
    AcademicRecord temp = *a;
    *a = *b;
    *b = temp;
}

void sort_by_score(AcademicRecord records[], int count) {
    for (int i = 0; i < count - 1; i++) {
        for (int j = 0; j < count - 1 - i; j++) {
            if (records[j].overall_score < records[j+1].overall_score) {
                swap_records(&records[j], &records[j+1]);
            }
        }
    }
}

ポインタ演算を用いた書籍データの走査と検索

構造体配列の走査において、配列のインデックスアクセスではなくポインタ演算を利用することで、メモリ上の連続したデータへの効率的なアクセスが可能になります。以下のコードでは、ポインタのインクリメントを用いた配列のトラバースと、標準ライブラリによる文字列比較を実装しています。また、安全な文字列入力のためにfgetsを採用しています。

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

#define MAX_BOOKS 5
#define STR_LEN 64

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

int main() {
    BookCatalog library[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 Special Pig", "Xiaobo Wang"}
    };
    
    BookCatalog *ptr;
    char search_author[STR_LEN];

    printf("=== 蔵書一覧 ===\n");
    for (ptr = library; ptr < library + MAX_BOOKS; ptr++) {
        printf("%-35s | %-20s\n", ptr->title, ptr->writer);
    }

    printf("\n検索する著者名を入力: ");
    if (fgets(search_author, sizeof(search_author), stdin)) {
        search_author[strcspn(search_author, "\n")] = '\0'; // 改行文字を除去
        
        printf("\n--- 検索結果 ---\n");
        int found = 0;
        for (ptr = library; ptr < library + MAX_BOOKS; ptr++) {
            if (strcmp(ptr->writer, search_author) == 0) {
                printf("%-35s | %-20s\n", ptr->title, ptr->writer);
                found = 1;
            }
        }
        if (!found) printf("該当する書籍は見つかりませんでした。\n");
    }

    return 0;
}

連結リストによる動画情報の動的メモリ管理

データ数が事前に確定しない場合、配列ではなく連結リストを用いた動的メモリ確保が有効です。ここでは、ダミーノード(センチネル)を導入した単方向連結リストを構築しています。ダミーノードを先頭に配置することで、先頭への挿入や削除におけるエッジケース(NULLポインタの参照など)を排除し、ロジックを単純化できます。

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

#define STR_LIMIT 64

typedef struct CinemaNode {
    char movie_title[STR_LIMIT];
    char director_name[STR_LIMIT];
    char country[STR_LIMIT];
    int release_year;
    struct CinemaNode *next_node;
} CinemaNode;

void print_list(CinemaNode *head);
CinemaNode *build_list(int node_count);

int main() {
    int total_movies;
    CinemaNode *dummy_head;

    printf("登録する映画の数を入力: ");
    if (scanf("%d", &total_movies) != 1) return 1;

    // ダミーノードの作成
    dummy_head = (CinemaNode *)malloc(sizeof(CinemaNode));
    if (!dummy_head) return 1;
    dummy_head->next_node = NULL;

    dummy_head = build_list(dummy_head, total_movies);

    printf("\n=== 登録された映画一覧 ===\n");
    print_list(dummy_head);

    // メモリ解放
    CinemaNode *current = dummy_head;
    while (current) {
        CinemaNode *temp = current;
        current = current->next_node;
        free(temp);
    }

    return 0;
}

CinemaNode *build_list(CinemaNode *head, int node_count) {
    for (int i = 1; i <= node_count; i++) {
        CinemaNode *new_node = (CinemaNode *)malloc(sizeof(CinemaNode));
        if (!new_node) exit(1);
        
        printf("%d作目の情報 (タイトル 監督 国 年): ", i);
        scanf("%63s %63s %63s %d", new_node->movie_title, new_node->director_name, 
              new_node->country, &new_node->release_year);
        
        // 先頭挿入法(ダミーノードの直後に挿入)
        new_node->next_node = head->next_node;
        head->next_node = new_node;
    }
    return head;
}

void print_list(CinemaNode *head) {
    CinemaNode *current = head->next_node; // ダミーノードをスキップ
    while (current) {
        printf("%-20s %-20s %-15s %d\n", 
               current->movie_title, current->director_name, 
               current->country, current->release_year);
        current = current->next_node;
    }
}

売上データ集計とqsortによるソート適用

大規模なデータセットのソートには、標準ライブラリのqsort関数を利用するのがC言語におけるベストプラクティスです。以下の実装では、書籍の売上データを構造体で管理し、販売部数に基づく降順ソートと、売上総額の集計を行っています。比較関数を分離することで、汎用性の高いソート処理を実現しています。

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

#define INVENTORY_SIZE 5

typedef struct {
    char isbn_code[24];
    char book_title[64];
    char author_name[64];
    double unit_price;
    int copies_sold;
} SalesRecord;

void display_inventory(SalesRecord inventory[], int size);
double calculate_total_revenue(SalesRecord inventory[], int size);
int compare_by_sales(const void *a, const void *b);

int main() {
    SalesRecord inventory[INVENTORY_SIZE] = {
        {"978-1-234-56789-0", "The Fountainhead", "Ayn Rand", 84.0, 59},
        {"978-2-345-67890-1", "Li Bai in San Francisco", "Tan Xiayang", 48.0, 16},
        {"978-3-456-78901-2", "Diary of a Stranger", "Zhou Yifang", 72.6, 27},
        {"978-4-567-89012-3", "Chip History", "Wang Bo", 74.9, 49},
        {"978-5-678-90123-4", "Data-Driven Decision", "Douglas Hubbard", 49.0, 42}
    };

    qsort(inventory, INVENTORY_SIZE, sizeof(SalesRecord), compare_by_sales);

    printf("=== 書籍販売ランキング ===\n");
    display_inventory(inventory, INVENTORY_SIZE);

    printf("\n売上総額: %.2f\n", calculate_total_revenue(inventory, INVENTORY_SIZE));

    return 0;
}

int compare_by_sales(const void *a, const void *b) {
    const SalesRecord *rec_a = (const SalesRecord *)a;
    const SalesRecord *rec_b = (const SalesRecord *)b;
    return rec_b->copies_sold - rec_a->copies_sold; // 降順
}

void display_inventory(SalesRecord inventory[], int size) {
    printf("%-20s %-25s %-20s %-8s %-5s\n", "ISBN", "タイトル", "著者", "単価", "販売数");
    for (int i = 0; i < size; i++) {
        printf("%-20s %-25s %-20s %-8.2f %-5d\n", 
               inventory[i].isbn_code, inventory[i].book_title, 
               inventory[i].author_name, inventory[i].unit_price, 
               inventory[i].copies_sold);
    }
}

double calculate_total_revenue(SalesRecord inventory[], int size) {
    double total = 0.0;
    for (int i = 0; i < size; i++) {
        total += inventory[i].unit_price * inventory[i].copies_sold;
    }
    return total;
}

日付構造体と閏年判定アルゴリズム

日付の差分計算や年内の経過日数算出は、システム開発で頻出する要件です。条件分岐のネストを避けるため、各月の日数を配列で保持し、閏年判定ロジックを独立した関数として実装しています。これにより、コードの保守性とテストの容易性が大幅に向上します。

#include <stdio.h>

typedef struct {
    int yyyy;
    int mm;
    int dd;
} CalendarDate;

int is_leap_year(int year);
int get_day_of_year(CalendarDate dt);
int compare_dates(CalendarDate d1, CalendarDate d2);

int main() {
    CalendarDate date1, date2;
    
    printf("日付1を入力 (YYYY-MM-DD): ");
    scanf("%d-%d-%d", &date1.yyyy, &date1.mm, &date1.dd);
    
    printf("日付2を入力 (YYYY-MM-DD): ");
    scanf("%d-%d-%d", &date2.yyyy, &date2.mm, &date2.dd);

    printf("\n%d-%02d-%02d はその年の %d 日目です。\n", 
           date1.yyyy, date1.mm, date1.dd, get_day_of_year(date1));

    int cmp = compare_dates(date1, date2);
    if (cmp < 0) printf("日付1 は 日付2 より前です。\n");
    else if (cmp > 0) printf("日付1 は 日付2 より後です。\n");
    else printf("両方の日付は同じです。\n");

    return 0;
}

int is_leap_year(int year) {
    return (year % 400 == 0) || (year % 4 == 0 && year % 100 != 0);
}

int get_day_of_year(CalendarDate dt) {
    int days_in_month[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    if (is_leap_year(dt.yyyy)) {
        days_in_month[2] = 29;
    }
    
    int total_days = 0;
    for (int i = 1; i < dt.mm; i++) {
        total_days += days_in_month[i];
    }
    return total_days + dt.dd;
}

int compare_dates(CalendarDate d1, CalendarDate d2) {
    if (d1.yyyy != d2.yyyy) return d1.yyyy - d2.yyyy;
    if (d1.mm != d2.mm) return d1.mm - d2.mm;
    return d1.dd - d2.dd;
}

列挙型と構造体を用いたアカウント情報のマスキング

ユーザー情報をコンソールに出力する際、パスワードなどの機密データはマスキング処理が必須です。以下のコードでは、アカウントの役割を列挙型(enum)で定義し、パスワード文字列をアスタリスクに置換して安全に表示するロジックを実装しています。文字列操作にはmemsetを利用し、効率的なマスキング処理を行っています。

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

typedef enum {
    ROLE_ADMIN,
    ROLE_STUDENT,
    ROLE_INSTRUCTOR
} UserRole;

typedef struct {
    char user_id[24];
    char secret_key[24];
    UserRole access_level;
} SystemAccount;

void print_accounts(SystemAccount accounts[], int count);
const char* get_role_string(UserRole role);

int main() {
    SystemAccount users[] = {
        {"A1001", "password123", ROLE_STUDENT},
        {"A1002", "securePass!", ROLE_STUDENT},
        {"X1009", "adminRoot99", ROLE_ADMIN},
        {"C1155", "teach2024xx", ROLE_INSTRUCTOR}
    };
    int user_count = sizeof(users) / sizeof(SystemAccount);

    print_accounts(users, user_count);

    return 0;
}

const char* get_role_string(UserRole role) {
    switch (role) {
        case ROLE_ADMIN: return "Administrator";
        case ROLE_STUDENT: return "Student";
        case ROLE_INSTRUCTOR: return "Instructor";
        default: return "Unknown";
    }
}

void print_accounts(SystemAccount accounts[], int count) {
    printf("%-15s %-20s %-15s\n", "User ID", "Password", "Role");
    printf("---------------------------------------------------\n");
    
    for (int i = 0; i < count; i++) {
        char masked_pass[24];
        int pass_len = strlen(accounts[i].secret_key);
        
        // パスワードをアスタリスクでマスキング
        memset(masked_pass, '*', pass_len);
        masked_pass[pass_len] = '\0';

        printf("%-15s %-20s %-15s\n", 
               accounts[i].user_id, 
               masked_pass, 
               get_role_string(accounts[i].access_level));
    }
}

タグ: C言語 構造体 ポインタ 連結リスト アルゴリズム

8月13日 16:49 投稿