diff --git a/public/chap16_array.drawio.svg b/public/chap16_array.drawio.svg new file mode 100644 index 0000000..5f0c47c --- /dev/null +++ b/public/chap16_array.drawio.svg @@ -0,0 +1,4 @@ + + + +
a [ 4 ]
a [ 4 ]
a [ 3 ]
a [ 3 ]
a [ 2 ]
a [ 2 ]
a [ 1 ]
a [ 1 ]
a [ 0 ]
a [ 0 ]
先頭要素
先頭要素
末尾要素
末尾要素
Text is not SVG - cannot display
\ No newline at end of file diff --git a/public/chap16_array_with_subscript.drawio.svg b/public/chap16_array_with_subscript.drawio.svg new file mode 100644 index 0000000..2fb534e --- /dev/null +++ b/public/chap16_array_with_subscript.drawio.svg @@ -0,0 +1,4 @@ + + + +
a [ 4 ]
a [ 4 ]
先頭要素(添字:0
先頭要素(添字:0)
末尾要素(添字:要素数 - 1
末尾要素(添字:要素数 - 1)
a [ 3 ]
a [ 3 ]
a [ 2 ]
a [ 2 ]
a [ 1 ]
a [ 1 ]
a [ 0 ]
a [ 0 ]
Text is not SVG - cannot display
\ No newline at end of file diff --git a/public/chap18_multi_arr.drawio.svg b/public/chap18_multi_arr.drawio.svg new file mode 100644 index 0000000..84e7776 --- /dev/null +++ b/public/chap18_multi_arr.drawio.svg @@ -0,0 +1,4 @@ + + + +
c [ 1 ]
c [ 1 ]
c [ 0 ]
c [ 0 ]
c [ 1 ] [ 2 ]
c [ 1 ] [ 2 ]
c [ 1 ] [ 1 ]
c [ 1 ] [ 1 ]
c [ 1 ] [ 0 ]
c [ 1 ] [ 0 ]
c [ 0 ] [ 2 ]
c [ 0 ] [ 2 ]
c [ 0 ] [ 1 ]
c [ 0 ] [ 1 ]
c [ 0 ] [ 0 ]
c [ 0 ] [ 0 ]
c の要素は2個
c の要素は2個
c [ 0 ] の要素は3個
c [ 0 ] の要素は3個
c [ 1 ] の要素は3個
c [ 1 ] の要素は3個
int  c [ 2 ] [ 3 ] ;
int  c [ 2 ] [ 3...
構成要素は6個
構成要素は6個
Text is not SVG - cannot display
\ No newline at end of file diff --git a/src/content/docs/textbook/c-lang/beginner/16--array.mdx b/src/content/docs/textbook/c-lang/beginner/16--array.mdx new file mode 100644 index 0000000..1daed78 --- /dev/null +++ b/src/content/docs/textbook/c-lang/beginner/16--array.mdx @@ -0,0 +1,430 @@ +--- +title : 配列 +slug: http://localhost:4321/textbook/c-lang/beginner/array +--- + +import { Aside } from '@astrojs/starlight/components'; +import { Tabs, TabItem } from '@astrojs/starlight/components'; +import { Card } from '@astrojs/starlight/components'; + + +同じ型の変数の集まりは、ひとまとめにして表現することができます。 +そのために利用する**配列**の基礎について学んでいきましょう。 + + +## 配列とは +--- + + +まずは、3人の学生の点数について、その合計値と平均値を算出するプログラム`list16_01.c`を作成してみましょう。 + +```c title="list16_01.c" +#include + +int main(void) { + int score1 = 70; // 1 人目の点数 + int score2 = 100; // 2 人目の点数 + int score3 = 85; // 3 人目の点数 + + printf("1人目の点数: %3d\n", score1); + printf("2人目の点数: %3d\n", score2); + printf("3人目の点数: %3d\n", score3); + + int sum = 0; // 合計点(初期値:0) + sum += score1; + sum += score2; + sum += score3; + + printf("合計点: %d\n", sum); + printf("平均点: %.1f\n", (double)sum / 3); + + return 0; +} +``` + +**実行結果** +``` +1人目の点数: 70 +2人目の点数: 100 +3人目の点数: 85 +合計点: 255 +平均点: 85.0 +``` + +このプログラム`list16_01.c`では、3人の点数がそれぞれ`int`型の3つの変数(`score1`・`score2`・`score3`)として表されています。 + +さて、今後プログラムを組んでいくうえで、学生の人数が100人に増えた場合を考えましょう。 +このプログラムのようなやり方では、点数を格納するための変数を100つも用意することになるでしょう。 +100もの変数を管理する必要がありますし、なにより変数の定義が大変に思われるかもしれません。 + +そこで利用するのが**配列**(array)になります。 +**配列**は、同じ型の値の集合を1つの変数として表現することができます。 +また、配列に格納されるひとつひとつの値を**要素**(element)と呼びます。 + + + + +## 配列の宣言 +--- + +まずは配列を宣言していきましょう。 +配列の宣言では、次のように、**要素型(element type)**、**配列名**(変数名)、**要素数**を与えることで実現します。 + +```c +要素型 配列名[要素数]; +``` + +例として、要素型が`int`型で要素数が`5`の配列を宣言してみます。 + +```c +int a[5]; +``` + +これによって、`int`型の5つの値を格納することができる1つの変数を、配列名`a`として宣言することができました。 + +![](/public/chap16_array.drawio.svg) + + + + +## 要素と添字 +--- + +配列が持つ個々の要素への**アクセス**は、**添字演算子**(subscript operator)を用いることで実現します。 +扱う配列の配列名が`a`である場合、添字演算子は`a[b]`として表されます。 +これは、配列`a`の先頭から`b`個後ろの要素を意味します。 + +また、演算子`[]`内のオペランドは、**添字**(subscript)と呼ばれます。 +これは、“先頭要素から何個後ろの要素なのか”を表す整数値になります。 +そのため、要素数が`n`である配列の添字の範囲が、`0`から`n - 1`までの整数値となることに注意しましょう。 + +![](/public/chap16_array_with_subscript.drawio.svg) + +実際に、冒頭のプログラム`list16_01.c`を、配列を用いて表現してみましょう。 + +```c title="list16_02.c" +#include + +int main(void) { + // 配列の宣言 + int scores[3]; // int型の 3 つの値を格納する配列 + + // 要素へのアクセス + scores[0] = 70; // 1 人目の点数 + scores[1] = 100; // 2 人目の点数 + scores[2] = 85; // 3 人目の点数 + + printf("1人目の点数: %3d\n", scores[0]); + printf("2人目の点数: %3d\n", scores[1]); + printf("3人目の点数: %3d\n", scores[2]); + + int sum = 0; // 合計点(初期値:0) + sum += scores[0]; + sum += scores[1]; + sum += scores[2]; + + printf("合計点: %d\n", sum); + printf("平均点: %.1f\n", (double)sum / 3); + + return 0; +} +``` + +**実行結果** +``` +1人目の点数: 70 +2人目の点数: 100 +3人目の点数: 85 +合計点: 255 +平均点: 85.0 +``` + + + + +## 配列の初期化 +--- + +配列の各要素の値の設定を、代入ではなく初期化によって実現してみましょう。 +配列の初期化子は、各要素に対する初期化子をコンマ(`,`)で区切って順に並べたものを`{}`で囲んだ形式で与えます。 + +まずは、プログラム`list16_02.c`を、配列の初期化を用いて表現してみましょう。 + +```c title="list16_03.c" +#include + +int main(void) { + int scores[3] = {70, 100, 85}; // 配列の初期化 + + printf("1人目の点数: %3d\n", scores[0]); + printf("2人目の点数: %3d\n", scores[1]); + printf("3人目の点数: %3d\n", scores[2]); + + int sum = 0; // 合計点(初期値:0) + sum += scores[0]; + sum += scores[1]; + sum += scores[2]; + + printf("合計点: %d\n", sum); + printf("平均点: %.1f\n", (double)sum / 3); + + return 0; +} +``` + +**実行結果** +``` +1人目の点数: 70 +2人目の点数: 100 +3人目の点数: 85 +合計点: 255 +平均点: 85.0 +``` + +配列の宣言と各要素への代入を1行で表現することができました。 + +また、配列に与える初期化子の規則を確認していきましょう。 + +配列に与える初期化子内の、最後の初期化子の後ろのコンマ(`,`)は省略可能です。 +最後の初期化子の後ろにコンマを記述する形式には、初期化の追加や削除にともなって、コンマを記述したり削除したりしなくてよくなるというメリットがあります。 + +```c +int a[3] = { + 1, + 2, + 3, // 最後の初期化子の後ろのコンマ(,)は省略可能 +}; +/** + * a[0]: 1 + * a[1]: 2 + * a[2]: 3 + */ + ``` + +次の配列`b`のような、要素数を指定しない場合は、`{}`内の初期化子の個数に基づいて、配列の要素数が自動的に決定されます。 + +```c +int b[] = {1, 2, 3}; // 要素数は自動的に 3 になる +/** + * b[0]: 1 + * b[1]: 2 + * b[2]: 3 + */ +``` + +次の配列`c`を確認すると、`{}`内に初期化子が与えられていない要素は、`0`で初期化されることがわかります。 + +```c +int c[3] = {1, 2}; // int c[] = {1, 2, 0}; と同じ +/** + * c[0]: 1 + * c[1]: 2 + * c[2]: 0 + */ +``` + +次の配列`d`の場合では、`d[0]`が`0`で初期化され、つづく`d[1]`、`d[2]`は初期化子が与えられないため`0`で初期化されています。 + +```c +int d[3] = {0}; // 全要素を 0 で初期化 +/** + * d[0]: 0 + * d[1]: 0 + * d[2]: 0 + */ +``` + + + + +## 配列の走査 +--- + +**走査**(traverse)とは、配列の要素をひとつずつ順番になぞっていくことを意味します。 +`for`文を用いた配列の走査について確認しましょう。 + +配列を先頭から順に走査するには、`for`文のカウンタ用変数を、配列の添字として利用することで実現できます。 +プログラム`list16_03.c`を、配列と`for`文を用いて表現してみましょう。 + +```c title="list16_04.c" +#include + +int main(void) { + int scores[3] = {70, 100, 85}; // 配列の初期化 + + int sum = 0; // 合計点(初期値:0) + + // 配列の走査 + for (int i = 0; i < 3; i++) { + printf("%d人目の点数: %3d\n", i + 1, scores[i]); + sum += scores[i]; + } + + printf("合計点: %d\n", sum); + printf("平均点: %.1f\n", (double)sum / 3); + + return 0; +} +``` + +**実行結果** +``` +1人目の点数: 70 +2人目の点数: 100 +3人目の点数: 85 +合計点: 255 +平均点: 85.0 +``` + +`for`文を用いることで、添字のみが異なるような命令を、一括で記述することができました。 +命令や繰返し処理を工夫することで、さまざまなプログラムに応用することができます。 + +さて、実際に練習問題を解きながら、配列を学んでいきましょう。 + + + +### 練習問題1 + +キーボードからの入力により、5人の身長(`double`型)を配列`heights`に格納してください。 +また、入力された身長の平均値を算出して出力してください. + +**実行結果** +``` +heights[0]? 170.0 +heights[1]? 168.2 +heights[2]? 186.9 +heights[3]? 152.0 +heights[4]? 170.5 +average: 169.520000 +``` +ファイル名: `16_issue1.c` +
+ヒント(キーボードによる入力受付けについて) + +
+
+ヒント(配列の要素の平均値について) + +
+
+模範解答 +```c title="16_issue1.c" +#include + +int main(void) { + double heights[5]; + + // 身長の入力 + for (int i = 0; i < 5; i++) { + printf("heights[%d]? ", i); + scanf("%lf", &heights[i]); + } + + double sum = 0; // 身長の合計値(初期値:0) + + // 配列の走査による合計値の算出 + for (int i = 0; i < 5; i++) { + sum += heights[i]; + } + + // 平均値の出力 + printf("average: %f\n", sum / 5); + + return 0; +} +``` +
+
+ + + +### 練習問題2 + +以下の配列`arr`が与えられているとき、この配列の要素の最小値と最大値をそれぞれ求めなさい。 +```c +int arr[] = {170, 168, 186, 152, 171}; +``` + +**実行結果** +``` +min: 152 +max: 186 +``` +ファイル名: `16_issue2.c` +
+ヒント + +
+
+模範解答 +```c title="16_issue2.c" +#include + +int main(void) { + int arr[] = {170, 168, 186, 152, 171}; + + // 最小値と最大値(初期値として適当な要素を格納しておく) + int min = arr[0]; + int max = arr[0]; + + for (int i = 0; i < 5; i++) { + if (min > arr[i]) { + // min よりも小さな値を見つけた場合、min を更新 + min = arr[i]; + } + if (max < arr[i]) { + // max よりも大きな値を見つけた場合、max を更新 + max = arr[i]; + } + } + + printf("min: %d\n", min); + printf("max: %d\n", max); + + return 0; +} +``` +
+
diff --git a/src/content/docs/textbook/c-lang/beginner/17--character-string.mdx b/src/content/docs/textbook/c-lang/beginner/17--character-string.mdx new file mode 100644 index 0000000..2a706d7 --- /dev/null +++ b/src/content/docs/textbook/c-lang/beginner/17--character-string.mdx @@ -0,0 +1,265 @@ +--- +title : 文字と文字列 +slug: http://localhost:4321/textbook/c-lang/beginner/character-string +--- + +import { Aside } from '@astrojs/starlight/components'; +import { Tabs, TabItem } from '@astrojs/starlight/components'; +import { Card } from '@astrojs/starlight/components'; + + +一連の文字の並びを表すものを文字列と呼びます。 +そんな文字列の基本を学んでいきましょう。 + + +## 文字列リテラル +--- + +`"hoge"`のような、文字の並びを二重引用符`"`で囲んだものを**文字列リテラル**(string literal)と呼びます。 + +文字列リテラルの末尾には、**ヌル文字**(null character)という値`0`(8進拡張表記では`\0`)の文字が付いています。 +そのため、`"hoge"`のような見かけ上4文字の文字列リテラルは、ヌル文字を含めた5文字分の記憶域を占有していることに注意しましょう。 +また、見かけ上0文字の文字列リテラル`""`も同様に、ヌル文字を含めた1文字分の記憶域を占有しています。 + + +## 文字列 +--- + +**文字列**(string)とは、複数の文字を並べたデータのようなものです。 + +文字列をオブジェクトとして格納する場合、要素型が`char`である配列を利用します。 + +```c +// 配列の宣言 +char str[6]; +``` +```c +// 初期化をともなう配列の宣言 +char str[] = "abcde"; // {'a', 'b', 'c', 'd', 'e', '\0'} と同じ +``` + + + + +## 文字および文字列の入出力 +--- + +単一の文字の表示のために使う関数が`putchar`関数になります。 +実引数として、`()`の中には、表示すべき文字を与える必要があります。 + + + +文字列の出力(印字)は次のようにして実現します。 + +```c title="list17_01.c" +#include + +int main(void) { + // "hoge" + char str[] = {'h', 'o', 'g', 'e', '\0'}; + + // 文字列の出力(%s) + printf("str: %s\n", str); + // 文字の出力(%c) + printf("str[0]: %c\n", str[0]); + // putchar を用いた文字の出力 + putchar(str[1]); + + putchar('\n'); // 改行文字 + + return 0; +} +``` + +**実行結果** +``` +str: hoge +str[0]: h +o +``` + +また、文字列の入力は次のようにして実現します。 + +```c +char str[128]; + +// 文字列の入力(%s) +scanf("%s", str); +``` +```c +// 文字の入力(%c) +scanf("%c", &str[0]); +``` + + +## 文字列の代入 +--- + +代入演算子`=`による文字列の代入ができないことに注意しましょう。 + +```c +char str[6]; +str = "abcde"; // エラー +``` + +面倒ではありますが、1文字ごとに代入するようにしましょう。 +なお、1文字を扱う場合は、文字を一重引用符`'`で囲みます。 + +```c +char str[6]; +str[0] = 'a'; +str[1] = 'b'; +str[2] = 'c'; +str[3] = 'd'; +str[4] = 'e'; +str[5] = '\0'; +``` + + + +### 練習問題1 + +以下の文字列リテラルについて、空白(`' '`)を除いた文字数をカウントして出力してください。 + +``` +"the quick brown fox jumps over the lazy dog" +``` + +**実行結果** +``` +文字数: 35 +``` +ファイル名: `17_issue1.c` +
+ヒント(文字列リテラルの走査) + +
+
+模範解答 +```c title="17_issue1.c" +#include + +int main(void) { + char str[] = "the quick brown fox jumps over the lazy dog"; + + // 文字数 + int cnt = 0; + + // ヌル文字になるまで文字列リテラルを走査 + for (int i = 0; str[i] != '\0'; i++) { + if (str[i] != ' ') { + // 空白ではない場合 + cnt++; + } + } + + printf("文字数: %d\n", cnt); + + return 0; +} +``` +
+
+ + + +### 練習問題2 + +キーボードから入力された文字列の文字数をカウントしてください。 +また、入力された文字列を逆順に出力してください。 + +**実行結果** +``` +str? nyancat +文字数: 7 +tacnayn +``` +ファイル名: `17_issue2.c` +
+ヒント(文字列の入力) + +
+
+ヒント(文字列を逆順に出力) + +
+
+模範解答 +```c title="17_issue2.c" +#include + +int main(void) { + char str[128]; + int len = 0; // 文字数 + + // 文字列の入力(%s) + printf("str? "); + scanf("%s", str); + + // 文字数のカウント + for (int i = 0; str[i] != '\0'; i++) { + len++; + } + printf("文字数: %d\n", len); + + // 逆順に印字 + for (int i = len - 1; i >= 0; i--) { + putchar(str[i]); + } + putchar('\n'); // 改行 + + return 0; +} +``` +
+
diff --git a/src/content/docs/textbook/c-lang/beginner/18--multidimensional-array.mdx b/src/content/docs/textbook/c-lang/beginner/18--multidimensional-array.mdx new file mode 100644 index 0000000..a76f78e --- /dev/null +++ b/src/content/docs/textbook/c-lang/beginner/18--multidimensional-array.mdx @@ -0,0 +1,253 @@ +--- +title : 多次元配列 +slug: http://localhost:4321/textbook/c-lang/beginner/multidimensional-array +--- + +import { Aside } from '@astrojs/starlight/components'; +import { Tabs, TabItem } from '@astrojs/starlight/components'; +import { Card } from '@astrojs/starlight/components'; + + +`int`や`double`などの単一型の変数を集めたものを配列といいました。 +実は、その配列を集めることで『配列の配列』をつくることができます。 +実際に、『配列の配列』について学んでいきましょう。 + + +## 多次元配列 +--- + +配列の考え方を応用することで、配列の要素自体が《配列》である配列をつくることができます。 + +配列を要素型とする配列を**2次元配列**と呼び、この2次元配列を要素型とする配列を**3次元配列**と呼びます。 +2次元以上の配列を総称したものが**多次元配列**(multidimensional array)となります。 + + +## 2次元配列 +--- + +例として、3つの要素をもつ2つの配列を考えてみましょう。 +1次元配列の宣言は、次のようにして実現することができました。 + +```c +int a[3] = {1, 2, 3}; +int b[3] = {4, 5, 6}; +``` + +これら2つの配列を要素にもつ配列(二次元配列)を宣言する場合、次のようになります。 + +```c +int c[2][3] = { + {1, 2, 3}, + {4, 5, 6} +}; +``` + +![](/public/chap18_multi_arr.drawio.svg) + + + + +## 2次元配列とその走査 +--- + +多次元配列の走査では、多重ループを用いて実現します。 + +`for`文による二重ループを用いた、二次元配列の走査を確認してみましょう。 +次のプログラム`list18_01.c`は、二次元配列によって九九の表を再現したものになります。 + +```c title="list18_01.c" +#include + +#define ROWS 9 // 行数 +#define COLS 9 // 列数 + +int main(void) { + int table[9][9]; + + // 二次元配列の初期化 + for (int i = 0; i < ROWS; i++) { + for (int j = 0; j < COLS; j++) { + table[i][j] = (i + 1) * (j + 1); + } + } + + // 九九表の出力 + for (int i = 0; i < ROWS; i++) { + for (int j = 0; j < COLS; j++) { + printf("%3d", table[i][j]); + } + putchar('\n'); // 改行 + } + + return 0; +} +``` + +**実行結果** +``` + 1 2 3 4 5 6 7 8 9 + 2 4 6 8 10 12 14 16 18 + 3 6 9 12 15 18 21 24 27 + 4 8 12 16 20 24 28 32 36 + 5 10 15 20 25 30 35 40 45 + 6 12 18 24 30 36 42 48 54 + 7 14 21 28 35 42 49 56 63 + 8 16 24 32 40 48 56 64 72 + 9 18 27 36 45 54 63 72 81 +``` + + + +### 練習問題1 + +次の表は、3人の学生のテストの点数をまとめたものになります。 +この表の値を二次元配列`scores[3][5]`で初期化し、再現してください。 +また、二次元配列のすべての要素を出力してください。 + +| 学籍番号 | 国語 | 社会 | 算数 | 理科 | 英語 | +| -------- | ---- | ---- | ---- | ---- | ---- | +| 0001 | 58 | 46 | 100 | 89 | 64 | +| 0002 | 98 | 96 | 32 | 38 | 72 | +| 0003 | 34 | 62 | 28 | 88 | 25 | + +**実行結果** +``` +58, 46, 100, 89, 64, +98, 96, 32, 38, 72, +34, 62, 28, 88, 25, +``` +ファイル名: `18_issue1.c` +
+ヒント(二次元配列の初期化) + +
+
+ヒント(二次元配列の走査) + +
+
+模範解答 +```c title="18_issue1.c" +#include + +int main(void) { + int scores[3][5] = { + {58, 46, 100, 89, 64}, // 学籍番号 0001 + {98, 96, 32, 38, 72}, // 学籍番号 0002 + {34, 62, 28, 88, 25} // 学籍番号 0003 + }; + + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 5; j++) { + printf("%d, ", scores[i][j]); + } + printf("\n"); // 改行 + } + + return 0; +} +``` +
+
+ + + +### 練習問題2 + +練習問題1の表にある3人の学生の平均点を、一次元配列`avg[3]`を用いることで算出して格納しなさい。 + +**実行結果** +``` +0001: 71.4 +0002: 67.2 +0003: 47.4 +``` +ファイル名: `18_issue2.c` +
+ヒント + +
+
+模範解答 +```c title="18_issue2.c" +#include + +int main(void) { + int scores[3][5] = { + {58, 46, 100, 89, 64}, // 学籍番号 0001 + {98, 96, 32, 38, 72}, // 学籍番号 0002 + {34, 62, 28, 88, 25} // 学籍番号 0003 + }; + + double avg[3] = {0}; + + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 5; j++) { + avg[i] += scores[i][j]; // 合計点数を算出 + } + avg[i] /= 5; // 平均点数を算出 + } + + // 結果の出力 + for (int i = 0; i < 3; i++) { + printf("%04d: %.1f\n", i + 1, avg[i]); + } + + return 0; +} +``` +
+
diff --git a/src/content/docs/textbook/c-lang/beginner/19--function-definition.mdx b/src/content/docs/textbook/c-lang/beginner/19--function-definition.mdx new file mode 100644 index 0000000..6826766 --- /dev/null +++ b/src/content/docs/textbook/c-lang/beginner/19--function-definition.mdx @@ -0,0 +1,455 @@ +--- +title : 自作関数の設計 +slug: http://localhost:4321/textbook/c-lang/beginner/function-definition +--- + +import { Aside } from '@astrojs/starlight/components'; +import { Tabs, TabItem } from '@astrojs/starlight/components'; +import { Card } from '@astrojs/starlight/components'; + + +プログラムは、多くの機能の組合わせによって構成されます。 +ひとつひとつの機能を提供する基本単位が関数になります。 +自作関数の設計について学んでいきましょう。 + + +## 関数定義と関数呼出し +--- + +**関数**(function)とは、値を引数として受け取り、何らかの処理を行う命令のことを指します。 +特に、今まで扱ってきた`printf`関数や`puts`関数、`scanf`関数などは、C言語によって標準で提供されており、これを**ライブラリ関数**(library function)と呼びます。 + +実は、関数は自作することができます。 +まずは、関数定義について確認していきましょう。 + +関数定義は次のようにして実現することができます。 + +```c +返却値型 関数名(仮引数型並び) { + 処理の内容 + return 返却値 +} +``` + +なお、呼び出される関数は、呼び出す箇所よりも上の行に記述される必要があります。 +そのため、関数定義は`main`関数よりも上の行に記述しましょう。 + +例として、2つの整数値を受け取り、最大値を返す関数を作成してみましょう。 + +```c title="list19_01.c" +#include + +int max2(int n1, int n2) { + if (n1 > n2) + return n1; + else + return n2; +} + +int main(void) { + int a = 3; + int b = 7; + + printf("max: %d\n", max2(a, b)); + + return 0; +} +``` + +**実行結果** +``` +max: 7 +``` + +`main`関数上に、`max2`関数を定義しました。 + +`main`関数内にて、実引数`a, b`を与えて`max2`関数を呼び出しています。 +次に、仮引数`n1, n2`には実引数の値が代入され、自作関数内の処理へと進みます。 +最後に、関数呼出しの式`max2(a, b)`は、2値の比較によって返却された値に置換され、実行されます。 + +また、`return`文は関数の実行を終了して、プログラムの流れを呼出し元に戻すとともに値を返却する仕組みであることを覚えておきましょう。 + + + + +## 自作関数内での関数呼出し +--- + +関数を組み合わせることで、プログラムを少ない記述量で実現することができます。 + +例として、次の4つの整数値の最大値を求めるプログラム`list17_02`をみてみましょう。 + +```c title="list19_02.c" +#include + +int max2(int n1, int n2) { + if (n1 > n2) + return n1; + else + return n2; +} + +int max4(int n1, int n2, int n3, int n4) { + return max2(max2(n1, n2), max2(n3, n4)); +} + +int main(void) { + int a = 3; + int b = 7; + int c = 2; + int d = 5; + + printf("max: %d\n", max4(a, b, c, d)); + + return 0; +} +``` + +**実行結果** +``` +max: 7 +``` + +プログラムの処理の流れは、次のとおりです。 + +1. `main`関数から処理が始まり、変数`a, b, c, d`を定義 +2. `max4(a, b, c, d)`を呼び出す +3. `max2(n1, n2)`と`max2(n3, n4)`を呼び出す +4. `max2(n1, n2)`と`max2(n3, n4)`の戻り値を実引数として、`max2(max2(n1, n2), max2(n3, n4))`が呼び出される +5. 4値の最大値を戻り値として、`max4(a, b, c, d)`に置換され、結果を出力する + +プログラムの処理の流れと戻り値に注意して、関数を活用していきましょう。 + + +## 引数を受け取らない関数 +--- + +受け取る仮引数がない関数を定義する場合、仮引数型並びを`void`(『空の』を意味します)とします。 +実際に、次のプログラム`list19_03.c`を確認してみましょう。 + +```c title="list19_03.c" +#include + +// 自然数を標準入力によって受け取り返却する関数 +int scanN(void) { + int n; + + do { + printf("natural number? "); + scanf("%d", &n); + + if (n <= 0) + printf("error!\n"); + } while (n <= 0); + + return n; +} + +int main(void) { + // 関数の呼出し + int n = scanN(); + + printf("n: %d\n", n); + + return 0; +} +``` + +**実行結果** +``` +natural number? 0 +error! +natural number? -3 +error! +natural number? 5 +n: 5 +``` + +`scanN`関数は、主に自然数を標準入力によって受け取り返却する関数になります。 +そのため、仮引数を必要としません。 + +このような関数を設計する場合、仮引数並びとして`void`を記述することを忘れないようにしましょう。 + + + + +## 値を返さない関数 +--- + +値を返さない関数を定義する場合も、関数型を`void`とします。 +実際に、次のプログラム`list19_04.c`を確認してみましょう。 + +```c title="list19_04.c" +#include + +// 自然数を標準入力によって受け取り返却する関数 +int scanN(void) { + int n; + + do { + printf("natural number? "); + scanf("%d", &n); + + if (n <= 0) + printf("error!\n"); + } while (n <= 0); + + return n; +} + +// 与えた整数値だけ * を出力する関数 +void putStars(int n) { + for (int i = 0; i < n; i++) + putchar('*'); + putchar('\n'); // 改行 +} + +int main(void) { + putStars(scanN()); + + return 0; +} +``` + +**実行結果** +``` +natural number? 0 +error! +natural number? -3 +error! +natural number? 5 +***** +``` + +`printStars`関数は、与えた整数値だけ`*`を出力する関数になります。 +そのため、戻り値は存在しません。 + +このような関数を設計する場合、返却値型として`void`を記述することを忘れないようにしましょう。 + + + + +## 関数原型宣言 +--- + +コンパイラがプログラムを先頭から末尾へと読み進める都合上、関数の定義は、その関数を呼び出す箇所よりも上の行に記述する必要がありました。 +実は、関数を宣言することによって、関数の定義を呼出し箇所よりも下の行に記述することができます。 + +この宣言は、関数の使用ともいうべき、関数の返却値型/関数名/仮引数を記述する必要があることから、**関数原型宣言**(function prototype declaration)と呼ばれます。 + +実際に、プログラム`list19_01.c`を、関数原型宣言を用いて書き換えましょう。 + +```c title="list19_05.c" {3} +#include + +int max2(int, int); + +int main(void) { + int a = 3; + int b = 7; + + printf("max: %d\n", max2(a, b)); + + return 0; +} + +int max2(int n1, int n2) { + if (n1 > n2) + return n1; + else + return n2; +} +``` + +関数の使用に関する宣言を`main`関数の上部に記述したことで、`main`関数の下部に関数の実体の定義を記述することができました。 + + + +### 練習問題1 + +次のプログラムの`TODO`を補うことで、実行結果に合うように関数を呼出してください。 + +```c +#include + +// 与えた整数値だけ * を出力する関数 +void putStars(int n) { + for (int i = 0; i < n; i++) + putchar('*'); + putchar('\n'); // 改行 +} + +int main(void) { + int n = 5; + + // TODO: ループ文などを用いて putStars 関数を呼出してください + + return 0; +} +``` + +**実行結果** +`n = 1`のとき、 +``` +* +``` + +`n = 3`のとき、 +``` +* +** +*** +``` + +`n = 5`のとき、 +``` +* +** +*** +**** +***** +``` +ファイル名: `19_issue1.c` +
+ヒント + +
+
+模範解答 +```c title="19_issue1.c" +#include + +// 与えた整数値だけ * を出力する関数 +void putStars(int n) { + for (int i = 0; i < n; i++) + putchar('*'); + putchar('\n'); // 改行 +} + +int main(void) { + int n = 5; + + for (int i = 1; i <= n; i++) { + putStars(i); + } + + return 0; +} +``` +
+
+ + + +### 練習問題2 + +自然数`n`を受け取り、その階乗`n!`を算出する関数`factorial`を作成してください。 + +なお、階乗`n!`とは、`1`から`n`までの自然数の積のことを指します。 + +```c +#include + +// 与えられた整数値の階乗を求める関数 +int factorial(int n) { + // TODO: ここにプログラムを記述してください +} + +void issue1902(int n) { + printf("%d! = %d\n", n, factorial(n)); +} + +int main(void) { + int n = 5; + + issue1902(n); + + return 0; +} +``` + +**実行結果** +``` +1! = 1 +``` +``` +5! = 120 +``` +``` +10! = 3628800 +``` +ファイル名: `19_issue2.c` +
+ヒント + +
+
+模範解答 +```c title="19_issue2.c" +#include + +// 与えられた整数値の階乗を求める関数 +int factorial(int n) { + int ans = 1; // 初期値を 1 とする + + for (int i = 1; i <= n; i++) { + ans *= i; + } + + return ans; +} + +void issue1902(int n) { + printf("%d! = %d\n", n, factorial(n)); +} + +int main(void) { + int n = 5; + + issue1902(n); + + return 0; +} +``` +
+
diff --git a/src/content/docs/textbook/c-lang/beginner/20--recursive-functio-call.mdx b/src/content/docs/textbook/c-lang/beginner/20--recursive-functio-call.mdx new file mode 100644 index 0000000..23c1d95 --- /dev/null +++ b/src/content/docs/textbook/c-lang/beginner/20--recursive-functio-call.mdx @@ -0,0 +1,191 @@ +--- +title : ex. 再帰関数呼出し +slug: http://localhost:4321/textbook/c-lang/beginner/recursive-functio-call +--- + +import { Aside } from '@astrojs/starlight/components'; +import { Tabs, TabItem } from '@astrojs/starlight/components'; +import { Card } from '@astrojs/starlight/components'; + + +関数は、その関数内で自身と同じ関数を呼び出すことができます。 +この呼出しは再帰関数呼出しと呼ばれ、プログラムの簡略化に役立ちます。 +再帰関数呼出しの基本を学んでいきましょう。 + + +## 再帰 +--- + +自分自身を含んでいたり、自分自身によって定義されたりする事象は、**再帰的**(recursive)であるといえます。 + +再帰的な現象の一例として、合わせ鏡があります。 +合わせ鏡では、鏡に映る鏡の中に鏡が映り、さらにその中の鏡にもまた鏡が映る……、といった具合に果てしなく続いていきます。 + +再帰の考え方を効率的に活用することで、繰返し処理のともなうプログラムを簡潔かつ効率的に記述することができます。 + + +## 関数の再帰的定義 +--- + +再帰を扱う例として、階乗値を求める関数を作成してみましょう。 + +まずは、非負の整数`n`の階乗を、再帰的に定義してみます。 + +- 階乗`n!`の定義(`n`は非負の整数であるとする) + - `0! = 1` + - `n > 0`ならば、`n! = n * (n - 1)!` + +たとえば、`3`の階乗は`3 * 2!`で表され、この計算式の過程に使われる式`2!`は`2 * 1!`で表されます。 +したがって、`3! = 3 * 2! = 3 * (2 * 1!) = 3 * 2 * (1 * 0!)`となります。 +ここで、`0!`の定義`0! = 1`より、`3!`は最終的に`3! = 3 * 2 * 1 * 1 = 6`として算出されることになります。 + +この定義を実現したものが、次のプログラム`list20_01.c`になります。 + +```c title="list20_01.c" +#include + +int factorial(int n) { + if (n > 0) + return n * factorial(n - 1); + else + return 1; +} + +int main(void) { + int n = 3; + + printf("%d! = %d\n", n, factorial(n)); + + return 0; +} +``` + +**実行結果** +``` +3! = 6 +``` + +このプログラムは、次の手順によって実行されていきます。 + +1. 関数呼出し式`factorial(3)`によって、関数`factorial`を呼び出します。 +この関数は、仮引数`n`に`3`を受け取ることで、返却値`3 * factorial(2)`を返します。 +この乗算を行うためには、`factorial(2)`の値が必要になるため、実引数`2`を渡して関数`factorial`を呼び出します。 + +2. 呼び出された関数`factorial`は、仮引数`n`に`2`を受け取ります。 +この関数の返却値`2 * factorial(1)`の乗算を行うために、実引数`1`を渡して関数`factorial`を呼び出します。 + +3. 呼び出された関数`factorial`は、仮引数`n`に`1`を受け取ります。 +この関数の返却値`1 * factorial(0)`の乗算を行うために、実引数`0`を渡して関数`factorial`を呼び出します。 + +4. 呼び出された関数`factorial`は、仮引数`n`に`0`を受け取ります。 +そのため、返却値として`1`を返します。 +なお、この時点で初めて`return`文が実行されます。 + +5. 返却値`1`を受け取った関数`factorial`は、`1 * factorial(0)`すなわち`1 * 1`を返却値として返します。 + +6. 返却値`1`を受け取った関数`factorial`は、`2 * factorial(1)`すなわち`2 * 1`を返却値として返します。 + +7. 返却値`2`を受け取った関数`factorial`は、`3 * factorial(2)`すなわち`3 * 2`を返却値として返します。 + +この手順によって、`3`の階乗値`6`を得ることができます。 + +このような関数の再帰的な呼び出しを、**再帰関数呼出し**(recursive functio call)と呼びます。 + + + +### 練習問題1 + +異なる`n`個の整数から、`r`個の整数を取り出す組合わせの数`C(n, r)` を求める関数を作成してください。 + +```c +#include + +int combination(int n, int r) { + // TODO: ここにプログラムを記述してください +} + +int main(void) { + int n = ***, r = ***; // TODO: 好きな正の整数値を入力してください(ただし、n >= r を満たすこと) + + printf("C(%d, %d) = %d\n", n, r, combination(n, r)); + + return 0; +} +``` + +なお、`C(n, r)`は次のように定義されます。 + +`C(n, r) = C(n - 1, r - 1) + C(n - 1, r)`(ただし、`C(n, 0) = C(n, n) = 1`、`C(n, 1) = n`) + +**実行結果** +``` +C(6, 3) = 20 +``` +``` +C(6, 1) = 6 +``` +``` +C(6, 0) = 1 +``` +ファイル名: `20_issue1.c` +
+ヒント1 + +
+
+ヒント2 + +
+
+ヒント3 + +
+
+模範解答 +```c title="20_issue1.c" +#include + +int combination(int n, int r) { + if (r == 0 || r == n) { + return 1; // C(n, 0) = C(n, n) = 1 + } else if (r == 1) { + return n; // C(n, 1) = n + } else { + return combination(n - 1, r - 1) + combination(n - 1, r); + } +} + +int main(void) { + int n = 6, r = 3; + + printf("C(%d, %d) = %d\n", n, r, combination(n, r)); + + return 0; +} +``` +
+
diff --git a/src/content/docs/textbook/c-lang/beginner/21--tic-tac-toe.mdx b/src/content/docs/textbook/c-lang/beginner/21--tic-tac-toe.mdx new file mode 100644 index 0000000..ffd539a --- /dev/null +++ b/src/content/docs/textbook/c-lang/beginner/21--tic-tac-toe.mdx @@ -0,0 +1,546 @@ +--- +title : 「まるばつゲーム」の作成 +slug: http://localhost:4321/textbook/c-lang/beginner/tic-tac-toe +next: false +--- + +import { Aside } from '@astrojs/starlight/components'; +import { Tabs, TabItem } from '@astrojs/starlight/components'; +import { Steps } from '@astrojs/starlight/components'; + + +これまで学習してきたことを踏まえて、簡単な「まるばつゲーム」を作成していきましょう。 + + +## アルゴリズム +--- + +まずは、「まるばつゲーム」の簡単なアルゴリズムを確認しましょう。 + + +1. ゲーム開始 + +2. 『まる』(または『ばつ』)の人が、自身のマークを9マスのグリッド内の空欄に刻む + ``` + X . . + . O X + O . . + ``` +3. 刻んだマークが三目並んだ場合、『まる』(または『ばつ』)の人の勝利として、Step.6へ + ``` + X . O + . O X + O O X + ``` +4. グリッド内に空欄が存在しない場合、勝負は引き分けとして、Step.6へ + ``` + X O X + O O X + O X O + ``` +5. 『ばつ』(または『まる』)の人にターンを移して、Step.2へ + +6. ゲーム終了 + + + +## プログラムの設計 +--- + +上記のアルゴリズムから、必要になりそうな機能(関数)や変数などを考えていきましょう。 + +- 盤面を標準出力する関数 +- 盤上のマークをカウントする関数 +- 勝利を判定する関数 + +- 盤を表す2次元配列 +- 現在のターンを示す変数 +- 勝利フラグ + +アルゴリズムにのっとり、これらを組み合わせていくことで、「まるばつゲーム」を作成していきましょう。 + + +## プログラムの作成 +--- + + +1. `main`関数内に、必要になりそうな変数を宣言します。 + + なお、「まるばつゲーム」の盤を2次元配列で宣言するにあたって、`0`ならば空欄、`1`ならば『まる』が、`2`ならば『ばつ』が刻まれていることにします。 + オブジェクト形式マクロを用いることで、これらの定数にあらかじめ名前をつけておきましょう。 + + ```c + #include + + #define BLANK 0 // から + #define CIRCLE 1 // まる('O') + #define CROSS 2 // ばつ('X') + + #define BOARD_SIZE 3 // 盤の行列数 + + // 盤面を表示する関数 + /* Step.3 にて記述 */ + + // 盤面を表す2次元配列にアクセスする関数 + /* Step.4 にて記述 */ + + // 任意のマークをカウントする関数 + /* Step.5 にて記述 */ + + // 勝利判定用の関数 + /* Step.6 にて記述 */ + + int main(void) { + // 盤を表す2次元配列 + int board[BOARD_SIZE][BOARD_SIZE] = { + {BLANK, BLANK, BLANK}, + {BLANK, BLANK, BLANK}, + {BLANK, BLANK, BLANK} + }; + // 現在のターンを示す変数(1: まる, 2: ばつ) + int currPlayer = CIRCLE; + // 勝利フラグ(0: 引き分け, 1: まるの勝利, 2: ばつの勝利) + int isWin = 0; + + /* Step.2 にて記述 */ + + return 0; + } + ``` + + 勝利フラグ`isWin`は、`0`で初期化しています。 + ゲームの過程で勝者が決まったときに、勝者に対応する整数値で更新する予定です。 + +2. 繰返し構文を用いて、ゲームの一連の流れを実現していきます。 + + ```c + while ( /* 繰返し処理の条件式(Step.5 にて記述) */ ) { + if (currPlayer == CIRCLE) + printf("【まるのターン】"); + else + printf("【ばつのターン】"); + + // 設置する座標の入力受付け + int pos; + printf("設置する座標を入力してください(1〜9):"); + scanf("%d", &pos); + + // 対応する座標にマークを設置 + /* Step.4 にて記述 */ + + // 勝利判定 + /* Step.6 にて記述 */ + + // 現在のターンの更新 + currPlayer = currPlayer % 2 + 1; // 1 -> 2, 2 -> 1 + } + + // 勝敗結果の表示 + printf("【結果】"); + if (isWin == CIRCLE) { + // isWin == 1 + puts("まるの勝利!"); + } else if (isWin == CROSS) { + // isWin == 2 + puts("ばつの勝利!"); + } else { + // isWin == 0 + puts("引き分け……"); + } + ``` + + マークを刻むときに標準入力されるグリッドの座標番号は、`1`から`9`の整数値で指定することにします。 + + ``` + 【ばつのターン】設置する座標を入力してください(1〜9):8 + ====== + 1 2 3 + 4 5 6 + 7 X 9 + ====== + ``` + + おおまかなアルゴリズムの流れに沿ったプログラムを組むことができました。 + あとは、自作関数を定義することで、必要な機能を補っていきましょう。 + +3. 盤面を表示する関数を作成します。 + + ```c + // 盤面を表示する関数 + void printBoard(int board[BOARD_SIZE][BOARD_SIZE]) { + puts("======"); + for (int i = 0; i < BOARD_SIZE; i++) { + for(int j = 0; j < BOARD_SIZE; j++) { + switch (board[i][j]) { + case CIRCLE: // まる + printf(" O"); + break; + case CROSS: // ばつ + printf(" X"); + break; + default: // 空欄(座標番号を表示) + printf("%2d", i * BOARD_SIZE + j + 1); + break; + } + } + putchar('\n'); + } + puts("======"); + } + ``` + + 盤面を表す2次元配列を仮引数として受け取り、その内容を標準出力しています。 + マークが空欄の場合は、座標番号を表示するようにしています。 + +4. 入力された座標番号に対応する配列の要素にアクセスして、値を書き換えられるようにします。 + + そのために、配列の要素にアクセスするための関数を作成します。 + + ```c + // 座標番号に対応する要素(マーク)を返す関数 + int getMark(int board[BOARD_SIZE][BOARD_SIZE], int pos) { + return board[(pos - 1) / BOARD_SIZE][(pos - 1) % BOARD_SIZE]; + } + + // 座標番号に対応する要素にマークを代入する関数 + void setMark(int board[BOARD_SIZE][BOARD_SIZE], int pos, int mark) { + board[(pos - 1) / BOARD_SIZE][(pos - 1) % BOARD_SIZE] = mark; + } + ``` + + `[(pos - 1) / BOARD_SIZE][(pos - 1) % BOARD_SIZE]`では、与えられた座標番号(`1`から`9`までの整数値)に対して、対応する2次元配列の添字を算出しています。 + なお、対応関係は次のようになっています。 + + ``` + [0][0],[0][1],[0][2] // 1,2,3 + [1][0],[1][1],[1][2] // 4,5,6 + [2][0],[2][1],[2][2] // 7,8,9 + ``` + + この関数を利用することで、実際にマークを設定していきましょう。 + + ```c + if (pos >= 1 && pos <= 9 && getMark(board, pos) == BLANK) { + // 入力された座標番号が正常、かつ対応するマークが空欄である場合 + setMark(board, pos, currPlayer); + + // 盤面の表示 + printBoard(board); + } else { + puts("【エラー】プログラムを終了します."); + + return 0; // 強制終了 + } + ``` + + なお、今回は簡略化のため、正しい入力が認められなかった場合には、プログラムを強制終了するようにしています。 + +5. 盤上のマークをカウントする関数を作成します。 + + ```c + // 任意のマークをカウントする関数 + int countMark(int board[BOARD_SIZE][BOARD_SIZE], int target) { + int cnt = 0; + + for (int i = 0; i < BOARD_SIZE; i++) { + for(int j = 0; j < BOARD_SIZE; j++) { + if (board[i][j] == target) + cnt++; + } + } + + return cnt; + } + ``` + + 盤面を表す2次元配列と任意のマークを表す整数値を仮引数として受け取り、そのマークが配列内にいくつ存在するのかをカウントして返す関数になります。 + + この関数を`while`文の条件式内で呼び出すことで、空欄(`BLANK`)の数をカウントして、その値が`0`であるときに繰返し処理を終了する仕組みにしましょう。 + + ```c + while(countMark(board, BLANK) != 0) { + /* 省略 */ + } + ``` + +6. 勝利を判定する関数を作成します。 + + ```c + // 3目の並びを判定する関数 + int checkLine(int board[BOARD_SIZE][BOARD_SIZE], int mark, int pattern[]) { + // 1パターン分の座標番号の走査 + for (int i = 0; i < BOARD_SIZE; i++) { + if (getMark(board, pattern[i]) != mark) { + // mark と合致しない場合 + return 0; + } + } + + // 1パターン分のすべて座標番号が mark と合致した場合 + return 1; + } + + // 勝利を判定する関数 + int checkWin(int board[BOARD_SIZE][BOARD_SIZE], int mark) { + // 3目の並びのパターン + int patterns[8][BOARD_SIZE] = { + {1, 2, 3}, + {4, 5, 6}, + {7, 8, 9}, + {1, 4, 7}, + {2, 5, 8}, + {3, 6, 9}, + {1, 5, 9}, + {3, 5, 7} + }; + + // すべてのパターンの走査 + for (int i = 0; i < 8; i++) { + if (checkLine(board, mark, patterns[i]) == 1) { + // 3目の並びに合致するパターンをみつけた場合 + return 1; + } + } + + return 0; + } + ``` + + 関数呼出しに関しては、`main`関数内から`checkWin`関数を呼出し、`checkWin`関数内から`checkLine`関数を呼び出すことになります。 + + 仮引数の`mark`には、現在のターンとなるプレイヤーのマークが渡されます。 + + 2次元配列`patterns`には、3目の並びとなるすべてのパターンが格納されています。 + `board`の要素の並びがいずれかのパターンに合致するかどうかを、`checkLine`関数を呼び出すことで判定します。 + + `checkLine`関数では、仮引数として渡された`pattern`(1パターン分)に合致しない場合は即座に`0`を、合致する場合は`1`を返します。 + また、`checkWin`関数では、最大で8回呼び出される`checkLine`関数が`1`を返した場合は即座に`1`を、そうでない場合は`0`を返します。 + + この仕組みによって、勝利の判定を実現しています。 + + では、`checkWin`関数を`main`関数内から呼び出してみましょう。 + + ```c + if (checkWin(board, currPlayer) == 1) { + // 勝利フラグの更新 + isWin = currPlayer; + break; + } + ``` + + `checkWin`関数が`1`を返した場合、勝利フラグ`isWin`を、現在ターンのプレイヤーのマークを代入することで塗り替えています。 + また、`break`文によって`while`文を脱出して、勝敗結果を表示するフェーズまでプログラムを進行させます。 + + + +## 完成品 +--- + + + +```c +#include + +#define BLANK 0 // から +#define CIRCLE 1 // まる('O') +#define CROSS 2 // ばつ('X') + +#define BOARD_SIZE 3 // 盤の行列数 + +// 盤面を表示する関数 +void printBoard(int board[BOARD_SIZE][BOARD_SIZE]) { + puts("======"); + for (int i = 0; i < BOARD_SIZE; i++) { + for(int j = 0; j < BOARD_SIZE; j++) { + switch (board[i][j]) { + case CIRCLE: // まる + printf(" O"); + break; + case CROSS: // ばつ + printf(" X"); + break; + default: // 空欄 + printf("%2d", i * BOARD_SIZE + j + 1); + break; + } + } + putchar('\n'); + } + puts("======"); +} + +// 座標番号に対応する要素(マーク)を返す関数 +int getMark(int board[BOARD_SIZE][BOARD_SIZE], int pos) { + return board[(pos - 1) / BOARD_SIZE][(pos - 1) % BOARD_SIZE]; +} + +// 座標番号に対応する要素にマークを代入する関数 +void setMark(int board[BOARD_SIZE][BOARD_SIZE], int pos, int mark) { + board[(pos - 1) / BOARD_SIZE][(pos - 1) % BOARD_SIZE] = mark; +} + +// 任意のマークをカウントする関数 +int countMark(int board[BOARD_SIZE][BOARD_SIZE], int target) { + int cnt = 0; + + for (int i = 0; i < BOARD_SIZE; i++) { + for(int j = 0; j < BOARD_SIZE; j++) { + if (board[i][j] == target) + cnt++; + } + } + + return cnt; +} + +// 3目の並びを判定する関数 +int checkLine(int board[BOARD_SIZE][BOARD_SIZE], int mark, int pattern[]) { + // 1パターン分の座標番号の走査 + for (int i = 0; i < BOARD_SIZE; i++) { + if (getMark(board, pattern[i]) != mark) { + // mark と合致しない場合 + return 0; + } + } + + // 1パターン分のすべて座標番号が mark と合致した場合 + return 1; +} + +// 勝利を判定する関数 +int checkWin(int board[BOARD_SIZE][BOARD_SIZE], int mark) { + // 3目の並びのパターン + int patterns[8][BOARD_SIZE] = { + {1, 2, 3}, + {4, 5, 6}, + {7, 8, 9}, + {1, 4, 7}, + {2, 5, 8}, + {3, 6, 9}, + {1, 5, 9}, + {3, 5, 7} + }; + + // すべてのパターンの走査 + for (int i = 0; i < 8; i++) { + if (checkLine(board, mark, patterns[i]) == 1) { + // 3目の並びに合致するパターンをみつけた場合 + return 1; + } + } + + return 0; +} + +int main(void) { + // 盤を表す2次元配列 + int board[BOARD_SIZE][BOARD_SIZE] = { + {BLANK, BLANK, BLANK}, + {BLANK, BLANK, BLANK}, + {BLANK, BLANK, BLANK} + }; + // 現在のターンを示す変数(1: まる, 2: ばつ) + int currPlayer = CIRCLE; + // 勝利フラグ(0: 引き分け, 1: まるの勝利, 2: ばつの勝利) + int isWin = 0; + + while (countMark(board, BLANK) != 0) { + if (currPlayer == CIRCLE) + printf("【まるのターン】"); + else + printf("【ばつのターン】"); + + // 設置する座標の入力受付け + int pos; + printf("設置する座標を入力してください(1〜9):"); + scanf("%d", &pos); + + if (pos >= 1 && pos <= 9 && getMark(board, pos) == BLANK) { + // 入力された座標番号が正常、かつ対応するマークが空欄である場合 + setMark(board, pos, currPlayer); + + // 盤面の表示 + printBoard(board); + } else { + puts("【エラー】プログラムを終了します."); + + return 0; // 強制終了 + } + + if (checkWin(board, currPlayer) == 1) { + // 勝利フラグの更新 + isWin = currPlayer; + break; + } + + // 現在のターンの更新 + currPlayer = currPlayer % 2 + 1; // 1 -> 2, 2 -> 1 + } + + // 勝敗結果の表示 + printf("【結果】"); + if (isWin == CIRCLE) { + // isWin == 1 + puts("まるの勝利!"); + } else if (isWin == CROSS) { + // isWin == 2 + puts("ばつの勝利!"); + } else { + // isWin == 0 + puts("引き分け……"); + } + + return 0; +} +``` + + +``` +【まるのターン】設置する座標を入力してください(1〜9):1 +====== + O 2 3 + 4 5 6 + 7 8 9 +====== +【ばつのターン】設置する座標を入力してください(1〜9):2 +====== + O X 3 + 4 5 6 + 7 8 9 +====== +【まるのターン】設置する座標を入力してください(1〜9):3 +====== + O X O + 4 5 6 + 7 8 9 +====== +【ばつのターン】設置する座標を入力してください(1〜9):4 +====== + O X O + X 5 6 + 7 8 9 +====== +【まるのターン】設置する座標を入力してください(1〜9):5 +====== + O X O + X O 6 + 7 8 9 +====== +【ばつのターン】設置する座標を入力してください(1〜9):6 +====== + O X O + X O X + 7 8 9 +====== +【まるのターン】設置する座標を入力してください(1〜9):7 +====== + O X O + X O X + O 8 9 +====== +【結果】まるの勝利! +``` + + + +これにて、「まるばつゲーム」のプログラム作成は終了になります。