こんにちは、コンジ(@pippi_kon)です。
この記事では『C言語でのクイズゲームの作り方』をご紹介しています。
前回は、問題を愚直に表示するというシンプルな作りだったのを「ループ処理」でスッキリさせる方法をご紹介しました。
プログラムが短くなり見やすくなりましたが、問題文や選択肢を配列に詰め込んだせいでちょっとわかりづらくなってしまいました。
『q[i][0]』←ぱっと見で何を指しているかわからないですよね。
なので今回は、構造体を活用してプログラムの可読性(わかりやすさ)をアップさせます。
今回の目標
今回作成するプログラムの出力結果です。
[第1問]
リンゴは英語で何と言う?
1:apple 2:orange 3:banana
>>> 1
正解!
[第2問]
大正->昭和->○○->令和
○○に入る年号は?
1:慶応 2:明治 3:平成
>>> 2
不正解…
正解は 3:平成 です。
[第3問]
世界三大珍味はどれ?
1:イカスミ 2:キャビア 3:チーズ
>>> 2
正解!
出力結果自体は前回と変わりませんが、中身のプログラム処理は変わります。
問題文の「リンゴは英語で何と言う?」や選択肢の「apple」などは、char型の多次元配列に格納されていました。
これらを構造体に格納するように変更しています。
プログラム全文
今回作成したプログラムのご紹介です。
のちほど、プログラムの解説を行います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> typedef struct { char mondai[100]; char sentaku[3][100]; int answer_no; }quiz_t; int main(void) { int ans; int i; quiz_t quiz[3]; strncpy(quiz[0].mondai, "リンゴは英語で何と言う?", sizeof(quiz[0].mondai)); strncpy(quiz[0].sentaku[0], "apple", sizeof(quiz[0].sentaku[0])); strncpy(quiz[0].sentaku[1], "orange", sizeof(quiz[0].sentaku[1])); strncpy(quiz[0].sentaku[2], "banana", sizeof(quiz[0].sentaku[2])); quiz[0].answer_no = 1; strncpy(quiz[1].mondai, "大正->昭和->○○->令和\n○○に入る年号は?", sizeof(quiz[1].mondai)); strncpy(quiz[1].sentaku[0], "慶応", sizeof(quiz[1].sentaku[0])); strncpy(quiz[1].sentaku[1], "明治", sizeof(quiz[1].sentaku[1])); strncpy(quiz[1].sentaku[2], "平成", sizeof(quiz[1].sentaku[2])); quiz[1].answer_no = 3; strncpy(quiz[2].mondai, "世界三大珍味はどれ?", sizeof(quiz[2].mondai)); strncpy(quiz[2].sentaku[0], "イカスミ", sizeof(quiz[2].sentaku[0])); strncpy(quiz[2].sentaku[1], "キャビア", sizeof(quiz[2].sentaku[1])); strncpy(quiz[2].sentaku[2], "チーズ", sizeof(quiz[2].sentaku[2])); quiz[2].answer_no = 2; for (i = 0; i < 3; i++) { printf("[第%d問]\n", i + 1); printf("%s\n", quiz[i].mondai); printf("1:%s 2:%s 3:%s\n", quiz[i].sentaku[0], quiz[i].sentaku[1], quiz[i].sentaku[2]); printf(">>> "); scanf("%d", &ans); if (ans == quiz[i].answer_no) { printf("正解!\n\n"); } else { printf("不正解...\n"); printf("正解は %d:%s です。\n\n", quiz[i].answer_no, quiz[i].sentaku[quiz[i].answer_no - 1]); } } return 0; } |
プログラムの解説
ヘッダーの追加(strncpy関数用)
3 |
#include <string.h> |
新しく「string.h」をインクルードしています。
これはのちに出てくる「strncpy関数」を使用するために必要となります。
なお、atoi関数用に追加した「stdlib.h」は、今回atoi関数を使用しなくなったため削除しました。
構造体定義
5 6 7 8 9 |
typedef struct { char mondai[100]; char sentaku[3][100]; int answer_no; }quiz_t; |
char型の多次元配列に格納していた問題文・選択肢・答えを構造体に格納するようにしました。
こうすることで、プログラム内で何問目のどの要素を指しているのかがわかりやすくなります。
例えば、前回の『q[i][0]』は、配列のi番目の要素0番目という意味で、それが何を指しているのかがわかりにくかったです。
しかし、構造体を使用することで『quiz[i].mondai』という名前で指定することができるようになり、配列i番目の問題文ということがわかりやすくなりました。
要素 | 意味 |
---|---|
mondai[100] | クイズの問題文 |
sentaku[3][100] | クイズの選択肢 sentaku[0]:選択肢1 sentaku[1]:選択肢2 sentaku[2]:選択肢3 |
answer | クイズの答えの番号 |
上記の構造体を使うために以下の変数定義も追加しました。
15 |
quiz_t quiz[3]; |
問題文・選択肢・答えの設定
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
strncpy(quiz[0].mondai, "リンゴは英語で何と言う?", sizeof(quiz[0].mondai)); strncpy(quiz[0].sentaku[0], "apple", sizeof(quiz[0].sentaku[0])); strncpy(quiz[0].sentaku[1], "orange", sizeof(quiz[0].sentaku[1])); strncpy(quiz[0].sentaku[2], "banana", sizeof(quiz[0].sentaku[2])); quiz[0].answer_no = 1; strncpy(quiz[1].mondai, "大正->昭和->○○->令和\n○○に入る年号は?", sizeof(quiz[1].mondai)); strncpy(quiz[1].sentaku[0], "慶応", sizeof(quiz[1].sentaku[0])); strncpy(quiz[1].sentaku[1], "明治", sizeof(quiz[1].sentaku[1])); strncpy(quiz[1].sentaku[2], "平成", sizeof(quiz[1].sentaku[2])); quiz[1].answer_no = 3; strncpy(quiz[2].mondai, "世界三大珍味はどれ?", sizeof(quiz[2].mondai)); strncpy(quiz[2].sentaku[0], "イカスミ", sizeof(quiz[2].sentaku[0])); strncpy(quiz[2].sentaku[1], "キャビア", sizeof(quiz[2].sentaku[1])); strncpy(quiz[2].sentaku[2], "チーズ", sizeof(quiz[2].sentaku[2])); quiz[2].answer_no = 2; |
作成した構造体に問題文・選択肢・答えを格納します。
答え(answer_no)は整数型なのでそのまま値を代入しています。
問題文(mondai)や選択肢(sentaku)は文字列型なので、通常の代入ではなく『strncpy関数』を使用しています。
なぜstrcpy関数ではなくstrncpy関数(nがついている)なのか。
それはプログラムの安全性の確保のためです。
strncpy関数では、コピーするデータのサイズを指定することができます。
意図していないデータ破壊を防ぐためにおすすめの関数です。
例えば、問題文が300バイトあったとします。
問題文の格納先の変数はmondai[100]で100バイト分の領域しかありません。
このmondaiに対してstrcpy関数で問題文を格納すると、100バイトしか用意していない空間に300バイトのデータをコピーすることになるので、データ破壊が起きてしまいます。
一方、strncpy関数ならコピーする最大サイズを指定することが可能。
最大サイズにコピー先の変数のサイズを指定しておけば、データ破壊を起こす心配がありません。
上の例でいうと、最大サイズに「sizeof(quiz[0].mondai)」と指定しておくことで、たとえ問題文が300バイトあったとしても先頭100バイト分しかコピーされません。
ただ、データ破壊は起きませんが問題文が欠落するのはそれはそれで問題です。
この辺りは次回以降ケアしていきます。
構造体メンバーに置き換え
35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
for (i = 0; i < 3; i++) { printf("[第%d問]\n", i + 1); printf("%s\n", quiz[i].mondai); printf("1:%s 2:%s 3:%s\n", quiz[i].sentaku[0], quiz[i].sentaku[1], quiz[i].sentaku[2]); printf(">>> "); scanf("%d", &ans); if (ans == quiz[i].answer_no) { printf("正解!\n\n"); } else { printf("不正解...\n"); printf("正解は %d:%s です。\n\n", quiz[i].answer_no, quiz[i].sentaku[quiz[i].answer_no - 1]); } } |
前回の多次元配列(q[][])を使用していた箇所を構造体メンバーに置き換えています。
46 |
printf("正解は %d:%s です。\n\n", quiz[i].answer_no, quiz[i].sentaku[quiz[i].answer_no - 1]); |
答えの番号と選択肢の配列番号がずれているので、「quiz[i].answer_no – 1」のように調整しています。
最後に
今回は、『構造体を活用してプログラムの可読性(わかりやすさ)をアップ』させてみました。
構造体を使用することで、変数名をわかりやすくすることができたとともに、問題ごとの塊(問題文・選択肢・答え)を作ることができました。
では今回はここまで。おつかれさまでした!

(2022/08/13 07:42:19時点 Amazon調べ-詳細)
(2022/08/13 07:42:19時点 Amazon調べ-詳細)