はじめに
本記事では、C言語の構造体、共用体、および構造体ポインタを活用した具体的なプログラミング例を解説します。各セクションで異なるデータ構造の特性を活かした実装方法を学びます。
構造体変数の活用:年間経過日数の算出
年月日を管理する構造体を定義し、入力された日付がその年の何日目に当たるかを計算するプログラムを作成します。
実装コード
#include <stdio.h>
typedef struct {
int 西暦;
int 月;
int 日;
} 日付情報;
int main() {
日付情報 対象日;
int 月別日数[12] = {31,28,31,30,31,30,31,31,30,31,30,31};
int 経過日数 = 0;
printf("年月日を入力してください: ");
scanf("%d %d %d", &対象日.西暦, &対象日.月, &対象日.日);
// 閏年判定
if ((対象日.西暦 % 4 == 0 && 対象日.西暦 % 100 != 0) || (対象日.西暦 % 400 == 0)) {
月別日数[1] = 29; // 2月を29日に設定
}
for (int idx = 0; idx < 対象日.月 - 1; idx++) {
経過日数 += 月別日数[idx];
}
経過日数 += 対象日.日;
printf("%d年%d月%d日はその年の%d日目です。\n",
対象日.西暦, 対象日.月, 対象日.日, 経過日数);
return 0;
}
解説
この実装では、まず閏年を判定して2月の日数を動的に調整しています。月ごとの日数を配列で管理することで、コードの可読性を向上させています。入力された月の前月までの日数を累積加算し、最後に入力日を加えることで年間経過日数を求めます。
構造体配列の活用:投票集計システム
複数の候補者に対する投票結果を構造体配列で管理し、得票数を集計する例です。
実装コード
#include <stdio.h>
#include <string.h>
#define 候補者数 6
#define 投票者数 10
typedef struct {
char 名前[20];
int 得票数;
} 候補者データ;
int main() {
候補者データ 候補リスト[候補者数] = {
{"田中", 0}, {"佐藤", 0}, {"鈴木", 0},
{"高橋", 0}, {"伊藤", 0}, {"山本", 0}
};
char 投票先[20];
for (int i = 0; i < 投票者数; i++) {
printf("候補者名を入力: ");
scanf("%s", 投票先);
for (int j = 0; j < 候補者数; j++) {
if (strcmp(投票先, 候補リスト[j].名前) == 0) {
候補リスト[j].得票数++;
break;
}
}
}
printf("\n--- 開票結果 ---\n");
for (int k = 0; k < 候補者数; k++) {
printf("%s: %d票\n", 候補リスト[k].名前, 候補リスト[k].得票数);
}
return 0;
}
解説
候補者情報を構造体配列で保持し、各投票ごとに名前照合を行って該当候補の得票数をインクリメントします。strcmp関数による文字列比較で正確な候補者識別を実現しています。配列の初期化時に得票数を0に設定することで、集計前の状態を明確にしています。
共用体の活用:異なる属性を持つデータ管理
学生と教師という異なる属性を持つデータを、共用体を使って効率的に管理する方法を示します。
実装コード
#include <stdio.h>
#include <stdlib.h>
typedef union {
int 所属クラス;
char 役職名[20];
} 追加情報;
typedef struct {
int 識別番号;
char 氏名[30];
char 種別; // 's'学生, 't'教師
追加情報 詳細;
} 個人レコード;
int main() {
個人レコード データ[2];
for (int i = 0; i < 2; i++) {
printf("ID、名前、種別(s/t)を入力: ");
scanf("%d %s %c", &データ[i].識別番号, データ[i].氏名, &データ[i].種別);
switch (データ[i].種別) {
case 's':
printf("クラス番号を入力: ");
scanf("%d", &データ[i].詳細.所属クラス);
break;
case 't':
printf("役職を入力: ");
scanf("%s", データ[i].詳細.役職名);
break;
default:
printf("エラー: 無効な種別です。\n");
exit(1);
}
}
printf("\n--- 登録情報 ---\n");
printf("ID\t\t氏名\t\t種別\t\t詳細\n");
for (int i = 0; i < 2; i++) {
printf("%d\t\t%s\t\t%c\t\t",
データ[i].識別番号, データ[i].氏名, データ[i].種別);
if (データ[i].種別 == 's') {
printf("%dクラス\n", データ[i].詳細.所属クラス);
} else {
printf("%s\n", データ[i].詳細.役職名);
}
}
return 0;
}
解説
共用体を使うことで、学生のクラス番号(int型)と教師の役職名(char配列)を同一メモリ領域で管理できます。種別フィールドでどのメンバーを使用するかを判定し、適切なデータへアクセスします。これにより、メモリ効率の良いデータ構造を実現しています。
構造体ポインタの活用:ジョセフス問題の解決
構造体を使って円環リストを構築し、ジョセフス問題を解決するアルゴリズムを実装します。
実装コード
#include <stdio.h>
#define 最大人数 100
typedef struct {
int 番号;
int 次の要素;
} 要素ノード;
int main() {
要素ノード 円環[最大人数];
int 総数, 開始位置, 間隔;
printf("人数、開始位置、間隔を入力: ");
scanf("%d %d %d", &総数, &開始位置, &間隔);
// 円環リストの初期化
for (int i = 0; i < 総数; i++) {
円環[i].番号 = i + 1;
円環[i].次の要素 = (i + 1) % 総数;
}
int 現在 = 開始位置 - 1;
int 生存者 = 総数;
printf("出圈順序: ");
while (生存者 > 1) {
// 間隔-1回進む
for (int cnt = 0; cnt < 間隔 - 1; cnt++) {
現在 = 円環[現在].次の要素;
}
// 現在の要素を排除
printf("%d ", 円環[現在].番号);
円環[現在].番号 = 0; // 排除マーク
// 次の有効な要素を探す
while (円環[円環[現在].次の要素].番号 == 0) {
円環[現在].次の要素 = 円環[円環[現在].次の要素].次の要素;
}
現在 = 円環[現在].次の要素;
生存者--;
}
// 最後の生存者
for (int i = 0; i < 総数; i++) {
if (円環[i].番号 != 0) {
printf("→ 最終生存者: %d\n", 円環[i].番号);
break;
}
}
return 0;
}
解説
配列ベースの円環リストを構築し、各要素の次の要素フィールドで次の有効な要素を指すようにします。排除された要素は番号を0に設定し、スキップ処理を実装しています。これにより、動的メモリ確保を使わずに効率的にジョセフス問題を解決できます。