コンテンツにスキップ

第5章 配列

学生の学籍番号、野球選手の背番号、飛行機の座席番号 … 同じ種類の《もの》が集まっているときは、それらの一つ一つを名前で呼ぶよりも、番号で呼んで処理すると、分かりやすく便利なことがある。

というのも、百を超える飛行機の座席が、"鶴の席"、"松の席" … などとなっていて、その名前で処理をするのは、現実的でないからである。

本章では、同じ型のデータの集合を効率よく扱うための配列について学習する。

5-1 配列

同じ型の変数の集まりは、ひとまとめにして扱うと便利である。そのために利用するのが、配列である。

本節では、配列の基礎を学習する。

配列

まずは、5人の学生の点数を読み込んで、その合計点と平均点を求めて表示するプログラムを作る。List 5-1に示すのが、そのプログラムである。

// 5人の学生の点数を読み込んで合計点と平均点を表示
#include <stdio.h>

int main(void)
{
    int tensu1;      // 1番の点数
    int tensu2;      // 2番の点数
    int tensu3;      // 3番の点数
    int tensu4;      // 4番の点数
    int tensu5;      // 5番の点数
    int sum = 0;     // 合計点

    printf("5人の点数を入力せよ。\n");
    printf(" 1番 : ");    scanf("%d", &tensu1);    sum += tensu1;
    printf(" 2番 : ");    scanf("%d", &tensu2);    sum += tensu2;
    printf(" 3番 : ");    scanf("%d", &tensu3);    sum += tensu3;
    printf(" 4番 : ");    scanf("%d", &tensu4);    sum += tensu4;
    printf(" 5番 : ");    scanf("%d", &tensu5);    sum += tensu5;

    printf("合計点 : %5d\n", sum);
    printf("平均点 : %5.1f\n", (double)sum / 5);

    return 0;
}

実行例

5人の点数を入力せよ。
 1番 : 83
 2番 : 95
 3番 : 85
 4番 : 63
 5番 : 89
合計点 :   415
平均点 :  83.0

本プログラムでは、5人の点数が、5個のint型変数で表されている。そのため、変数名が違うだけのほぼ同じ処理を繰り返している。

さて、学生の人数が300人に増えたとする。点数用の変数を300個用意して、それぞれに名前を与えることになるだろう。300もの変数の管理が必要となるし、プログラムのタイプ時に変数名を打ち間違えないようにするのも大変である。

学籍番号のように『何番目』と指定できれば都合がよく、それを実現するのが、次の特徴をもつ配列(array)である。

重要

配列は、要素(element)と呼ばれる同一型の変数が直線状に連続して並んだものである。

配列の宣言(配列を使うための準備)

まずは宣言の方法を学習する。次のように、要素型(element type)、配列名(変数名=識別子)、要素数を与えて宣言する。

要素型 配列名[要素数];

なお、[ ]の中に置く要素数は、定数式とするのが原則である(Column 5-2:p.136)。

Fig.5-1は、要素型がint型で要素数が5の配列の宣言と、作られる配列のイメージである。

Fig.5-1 int型の変数を5個集めて作ったint[5]型の配列 Fig5-1

int a[5];

要素と添字

配列の要素は、すべて同一型の変数である(ある要素がint型で、別の要素がdouble型になるようなことはない)。

個々の要素のアクセス(読み書き)は、自由に行える。その際に使うのが、Table 5-1に示す添字演算子(subscript operator)である。

Table 5-1 添字演算子

添字演算子 説明
a[b] 配列aの先頭からb個後ろの要素をアクセスする。

演算子[ ]の中のオペランドは、添字(subscript)と呼ばれる。これは、"先頭要素から何個後ろの要素なのか"を表す整数値である(Fig.5-1とFig.5-2では、赤色の数値で示している)。

Fig.5-2 添字演算子と添字式 Fig5-2

そのため、各要素をアクセスする添字式は、先頭から順にa[0]、a[1]、a[2]、a[3]、a[4]となる。

要素数nの配列の要素はa[0]、a[1]、…、a[n - 1]であって、a[n]は存在しない。

Note

配列の宣言の[ ]は、単なる区切り子で、個々の要素をアクセスする[ ]は、演算子である。本書では、前者を黒字で、後者を青字で表している。

なお、a[-1]やa[n]などの存在しない要素をアクセスした際の動作は保証されない。誤ってアクセスしないように注意すること。

配列の走査

それでは、配列の要素に値を代入して表示する。List 5-2が、そのプログラムである。

// 配列の各要素に先頭から順に1~5を代入して表示
#include <stdio.h>

int main(void)
{
    int a[5];    // int[5]型の配列

    a[0] = 1;
    a[1] = 2;
    a[2] = 3;
    a[3] = 4;
    a[4] = 5;

    printf("a[0] = %d\n", a[0]);
    printf("a[1] = %d\n", a[1]);
    printf("a[2] = %d\n", a[2]);
    printf("a[3] = %d\n", a[3]);
    printf("a[4] = %d\n", a[4]);

    return 0;
}

実行結果

a[0] = 1
a[1] = 2
a[2] = 3
a[3] = 4
a[4] = 5

要素型がint型で、要素数が5の配列を作って、各要素に先頭から順に1、2、3、4、5を代入・表示している。Fig.5-3に示すのが、代入後の状態である。

Fig.5-3 添字と要素の値 Fig5-3

添字式に対しては、値を代入することも、値を取り出すことも可能である。本プログラムでは、全要素に対して、添字に1を加えた値を代入した上で、取り出して表示している。

本プログラムをfor文で書きかえると、配列を利用するメリットがはっきりする。それが、List 5-3に示すプログラムである。

// 配列の各要素に先頭から順に1~5を代入して表示(for文使用)
#include <stdio.h>

int main(void)
{
    int a[5];    // int[5]型の配列

    for (int i = 0; i < 5; i++)        // 要素に値を代入
        a[i] = i + 1;

    for (int i = 0; i < 5; i++)        // 要素の値を表示
        printf("a[%d] = %d\n", i, a[i]);

    return 0;
}

実行結果

a[0] = 1
a[1] = 2
a[2] = 3
a[3] = 4
a[4] = 5

5行にわたっていた代入が、単一のfor文に置きかえられてシンプルになっている。

そのfor文は、変数iの値を0から始めてインクリメントしながら5回の繰り返しを行う。

"開いて"書くと、右のようになって、List 5-2と同じ代入を行っていることが分かる。

i が 0 のとき : a[0] = 0 + 1;
i が 1 のとき : a[1] = 1 + 1;
i が 2 のとき : a[2] = 2 + 1;
i が 3 のとき : a[3] = 3 + 1;
i が 4 のとき : a[4] = 4 + 1;

表示の箇所も同様である。5行にわたっていたコードが、単一のfor文に置きかえられている。

Fig5-01

Note

上記のコードにあるように、2個の値(添字と要素の値)を表示するため、printf関数の呼び出しが複雑になっている。

重要

配列の要素は、添字演算子 [] を使った添字式でアクセスする。添字式 a[i] を使うことで、配列 a の先頭から i 個後ろの要素を読み書きできる。

次は、要素型が int ではない配列を作ろう。List 5-4は、要素型が double 型で要素数が7の配列の全要素に0.0を代入して表示するプログラムである。

// List 5-4: 配列の全要素に0.0を代入して表示
#include <stdio.h>

int main(void)
{
    double x[7];      // double[7]型の配列

    for (int i = 0; i < 7; i++)       // 要素に値を代入
        x[i] = 0.0;

    for (int i = 0; i < 7; i++)       // 要素の値を表示
        printf("x[%d] = %.1f\n", i, x[i]);

    return 0;
}

実行結果:

x[0] = 0.0
x[1] = 0.0
x[2] = 0.0
x[3] = 0.0
x[4] = 0.0
x[5] = 0.0
x[6] = 0.0

一般に、要素型が Type 型である配列のことを『Type の配列』と呼ぶ。今回のプログラムの配列は、『double の配列』である。

なお、要素型が Type 型で要素数が n の配列の型は、『Type[n] 型』と表すのである。

重要

要素型が Type 型で要素数が n の配列の型は Type[n] 型である。

List 5-2の配列 a の型は int[5] 型で、List 5-4の配列 x の型は double[7] 型である。

Note

本書では、型に関する一般的な規則を示す際に、『Type 型』という表現を使う(Type という型が実在するのではない)。

なお、配列の要素を一つずつ順番になぞっていくことを走査(traverse)というのである。

演習 5-1

List 5-3を書きかえて、先頭から順に0、1、2、3、4を代入するプログラムを作成せよ。

演習 5-2

List 5-3を書きかえて、先頭から順に5、4、3、2、1を代入するプログラムを作成せよ。

配列の初期化

配列の各要素への値の設定を、代入でなく、初期化で行うように、List 5-3 (p.118) のプログラムを書きかえる。List 5-5が、そのプログラムである。

// 配列の各要素を先頭から順に1~5で初期化して表示
#include <stdio.h>

int main(void)
{
    int a[5] = {1, 2, 3, 4, 5};    // 初期化

    for (int i = 0; i < 5; i++)    // 要素の値を表示
        printf("a[%d] = %d\n", i, a[i]);

    return 0;
}

実行結果

a[0] = 1
a[1] = 2
a[2] = 3
a[3] = 4
a[4] = 5

配列aの宣言に着目する。与えられている初期化子は、各要素に対する初期化子をコンマ,で区切って順に並べたものを{ }で囲んだ形式である。

配列aの要素a[0]~a[4]が、先頭から順に1、2、3、4、5で初期化されていることは、実行結果からも確認できる。

さて、配列に与える初期化子については、やややこしい規則が数多くある。右ページのList 5-6を見ながら理解していく。

// 配列の要素の初期化
#include <stdio.h>

int main(void)
{
    int a[4] = {
        1868,       // 明治の最初の西暦年
        1912,       // 大正の最初の西暦年
        1926,       // 昭和の最初の西暦年
        1989,       // 平成の最初の西暦年
    };

    int b[] = {1, 2, 3};   // 要素数は3となる

    int c[5] = {1, 2};     // int c[5] = {1, 2, 0, 0, 0}; と同じ

    int d[5] = {0};        // 全要素を0で初期化

    // 要素の値を表示するコードは提示を省略

    return 0;
}

実行結果:

a[0] = 1868
a[1] = 1912
a[2] = 1926
a[3] = 1989

b[0] = 1
b[1] = 2
b[2] = 3

c[0] = 1
c[1] = 2
c[2] = 0
c[3] = 0
c[4] = 0

d[0] = 0
d[1] = 0
d[2] = 0
d[3] = 0
d[4] = 0

最後の初期化子の後ろにも,を置ける

配列aは、明治、大正、昭和、平成の最初の年を西暦で表した配列である。初期化子が縦に並べられており、最後の初期化子1989の後ろにも,が置かれている。

最後の初期化子の後ろのコンマは、置いても省略してもよい、という決まりである。

重要

配列に与える初期化子は、各要素向けの初期化子とコンマ,を並べたものを{ }で囲んだ形式である(最後の初期化子の後ろのコンマは省略できる)。

初期化子の個数から配列の要素数が自動的に決定する

配列bの宣言は、要素数が指定されていない。このように配列の要素数を与えずに宣言すると、初期化子の個数に基づいて、配列の要素数が自動的に決定される。

Note

配列bは、3個の初期化子が与えられているため、要素数は3となる。

{ }内に初期化子が与えられていない要素は0で初期化される

配列cでは、要素数が5と指定されているにもかかわらず、初期化子が2個しか与えられていない。{ }内に初期化子が与えられていない要素は0で初期化されるという規則が適用される。そのため、c[2]とc[3]とc[4]は、いずれも0で初期化される。

配列dも同様である。d[0]が0で初期化され、さらに、それ以降の要素d[1]~d[4]のすべての要素が0で初期化される(すなわち、全要素が0で初期化される)。

ここで注意すべきことがある。次の宣言では、全要素が0にならないことである。

int x[5];    // 全要素を不定値で初期化

配列xの全要素は、不定値(ゴミの値)で初期化される(p.14)。

重要

Type[n]型の配列aの全要素を0で初期化するには、次の形式で宣言する。

Type a[n] = {0};

なお、初期化子の個数が、配列の要素数を超えるとエラーになる。

int a[3] = {1, 2, 3, 4};    // エラー:初期化子が要素数より多い

また、初期化子を代入することはできない。次の例は、誤りである。

int a[3];
a = {1, 2, 3};    // エラー:初期化子は代入できない

Example

List 5-5を書きかえて、先頭から順に5、4、3、2、1で初期化するプログラムを作成せよ。

配列の要素に値を読み込む

次は、配列の要素の値を、キーボードから読み込むようにする。List 5-7に示すのは、int[5]型の配列の各要素に値を読み込んで、その値を表示するプログラムである。

// 配列の要素に値を読み込んで表示
#include <stdio.h>

int main(void)
{
    int x[5];    // int[5]型の配列

    for (int i = 0; i < 5; i++) {    // 要素に値を読み込む
        printf("x[%d] : ", i);
        scanf("%d", &x[i]);
    }

    for (int i = 0; i < 5; i++)        // 要素の値を表示
        printf("x[%d] = %d\n", i, x[i]);

    return 0;
}

実行例

x[0] : 17
x[1] : 38
x[2] : 52
x[3] : 41
x[4] : 63
x[0] = 17
x[1] = 38
x[2] = 52
x[3] = 41
x[4] = 63

キーボードから読み込んだ値を格納するためにscanf関数を使うのは、配列でない普通の変数の場合と同じである(変数の前に&を置く)。

Tip

読込先がx[i]であるから、scanf関数に与える第2引数は、&を置いた&x[i]となる。

配列の全要素の並びを反転する

値を読み込んだり表示したりするだけでは、つまらないだろう。今度は、配列の要素の並びを反転する。右ページのList 5-8が、そのプログラムである。

// 配列の全要素の並びを反転する
#include <stdio.h>

int main(void)
{
    int x[7];    // int[7]型の配列

    for (int i = 0; i < 7; i++) {    // 要素に値を読み込む
        printf("x[%d] : ", i);
        scanf("%d", &x[i]);
    }

    for (int i = 0; i < 3; i++) {    // 要素の並びを反転
        int t    = x[i];
        x[i]     = x[6 - i];
        x[6 - i] = t;
    }

    puts("反転しました。");
    for (int i = 0; i < 7; i++)        // 要素の値を表示
        printf("x[%d] = %d\n", i, x[i]);

    return 0;
}

実行例

x[0] : 1
x[1] : 6
x[2] : 2
x[3] : 7
x[4] : 3
x[5] : 9
x[6] : 8
反転しました。
x[0] = 8
x[1] = 9
x[2] = 3
x[3] = 7
x[4] = 2
x[5] = 6
x[6] = 1

水色のfor文でint[7]型配列xの要素を反転している。ここで行うことをまとめたのが、Fig.5-4である。

Fig.5-4 配列要素の反転 Fig5-4

次に示すように、《2値の交換》を3回行っている。

  • x[0]の値とx[6]の値を交換
  • x[1]の値とx[5]の値を交換
  • x[2]の値とx[4]の値を交換

Tip

for文の繰り返しの過程では、iの値が0、1、2と変化して、6 - iの値が6、5、4と変化する。

それでは、2値の交換の手順を、右ページのFig.5-5を見ながら理解していく。ここでは、aとbの2値の交換のために、余分な変数tを用意してやりくりしている。

Fig.5-5 2値の交換 Fig5-3

具体的には、次のようになっている。

  1. t = a; aの値をtに保存。
  2. a = b; bの値をaに代入。
  3. b = t; tに保存していた最初のaの値をbに代入。

本プログラムでは、aに相当するのがx[i]で、bに相当するのがx[6 - i]である。

注意

2値の交換を次のように行うことはできない。

a = b;
b = a;
これだと、二つの変数aとbの値が、代入前のbの値になってしまう。

オブジェクト形式マクロ

本章冒頭の List 5-1(p.116)で考えた成績処理のプログラムを、配列を用いて作り直そう。List 5-9 がそのプログラムであり、5 人の点数を配列 tensu で表している。

// 5人の学生の点数を読み込んで合計点と平均点を表示
#include <stdio.h>

int main(void)
{
    int tensu[5];    // 学生の点数
    int sum = 0;     // 合計点

    printf("5人の点数を入力せよ。\n");
    for (int i = 0; i < 5; i++) {
        printf("%2d番:", i + 1);
        scanf("%d", &tensu[i]);
        sum += tensu[i];
    }

    printf("合計点:%5d\n", sum);
    printf("平均点:%5.1f\n", (double)sum / 5);

    return 0;
}

実行例

5人の点数を入力せよ。
1番:83
2番:95
3番:85
4番:63
5番:89
最高点:95
最低点:63

Tip

点数の入力を促す際の「1 番:」や「2 番:」では、添字 i に 1 を加えた i + 1 を表示していることに注意しよう。

さて、学生の人数が 8 人に増えたとする。エディタで『5 ⇒ 8 の一括置換』を行うとよさそうであるが、実は、そうはいかない。というのも、学生の人数の 5 は、8 に置換すべきである一方で、表示の桁数の 5 は、置換してはならないからである。

すなわち、『選択的な置換(置換すべき箇所のみの置換)』を行う必要がある。

このような変更に柔軟に対応できるように書きかえたのが、右ページの List 5-10 である。

赤色の部分は、オブジェクト形式マクロ(object-like macro)を定義するための特殊な宣言であり、#define 指令(#define directive)と呼ばれる(通常の式や文とは異なる)。

その #define 指令は、次の形をしている。

#define  a  b    // この指令以降の a を b に置換せよ

この指令は、『この指令以降の a を b に置換せよ。』という指示である。そのため、a が b に置換されたうでプログラムが翻訳・実行されることになる。

なお、置換の対象となる a は、マクロ名(macro name)と呼ばれる。マクロ名は、変数名と区別しやすくするために、大文字とする慣習がある。

本プログラムでは、マクロ名は NUMBER であり、プログラム中の NUMBER が 5 に置換される。

さて、学生の人数の変更を考えていたのであった。変更は容易である。マクロ NUMBER を定義する #define 指令を、次のように書きかえるだけである("chap05/List0510a.c")。

#define NUMBER  8    // 学生の人数

これで、プログラム中の NUMBER が翻訳時に 8 に置換される。

Tip

配列の要素数が 8 となり、8 個の点数を読み込んで、それらの合計点と平均点が表示される。

オブジェクト形式マクロを利用するメリットは、次のとおりである。

  • 値の管理を 1 箇所に集約できる。
  • 定数に名前が与えられるため、プログラムが読みやすくなる。

Tip

プログラム中に直接書かれた 5 や 8 などの定数は、マジックナンバーと呼ばれる(何を表すための数値なのか、よく分からない数、という意味である)。オブジェクト形式マクロを導入すると、プログラム中のマジックナンバーを除去できる。

重要

プログラムにはマジックナンバー(秘密の数値)を埋め込まず、オブジェクト形式マクロによって名前を与えよう。

Tip

文字列リテラルや文字定数内の繰り字、変数名などの識別子の一部としての繰り(たとえば、"NUMBER = "や NUMBER_i 内の NUMBER)は、置換の対象外である。

配列要素の最大値と最小値

次は、点数の最高点と最低点を求める、すなわち、配列要素の最大値と最小値の両方を求めることにしよう。List 5-11 に示すのが、そのプログラムである。

// 学生の点数を読み込んで最高点と最低点を表示
#include <stdio.h>

#define NUMBER  5    // 学生の人数

int main(void)
{
    int tensu[NUMBER];  // 学生の点数
    int max, min;       // 最高点と最低点

    printf("%d人の点数を入力せよ。\n", NUMBER);
    for (int i = 0; i < NUMBER; i++) {
        printf("%2d番:", i + 1);
        scanf("%d", &tensu[i]);
    }

    min = max = tensu[0];
    for (int i = 1; i < NUMBER; i++) {
        if (tensu[i] > max) max = tensu[i];
        if (tensu[i] < min) min = tensu[i];
    }

    printf("最高点:%d\n", max);
    printf("最低点:%d\n", min);

    return 0;
}

実行例

5人の点数を入力せよ。
1番:83
2番:95
3番:85
4番:63
5番:89
最高点:95
最低点:63

代入式の評価

まずは①に着目する。2 個の代入演算子 = が使われており、初めての形式である。これを理解するために、まずは、int 型変数 n に対する、次の代入式を考える。

n = 2.95

整数 n は、小数点以下の部分を格納できないため、代入後の値は 2 となる。

さて、代入式について、次のことを必ず知っておく必要がある。

重要

代入式を評価して得られるのは、代入後の左オペランドの型と値である。

すなわち、代入式 n = 2.95 を評価して得られるのは、代入後の左オペランド n の型と値である『int 型の 2』である(Fig.5-6 ①)。

Fig.5-6 代入式の評価 Fig5-6

なお、代入は右側から行われる(p.221)ことから、プログラム①は次のように解釈される。

min = (max = tensu[0]);

この代入の様子を示したのが、Fig.5-7 である(実行例のように tensu[0] を 83 とする)。

Fig.5-7 多重の代入式の評価 Fig5-7

代入式 max = tensu[0] を評価した値は、①の代入後の max の型と値である『int 型の 83』である。

その 83 が②によって min に代入される。

その結果、min と max の両方に、tensu[0] の値 83 が代入されるというわけである。

このような多重の代入は、熟練者が好んで使う。たとえば、代入式『a = b = 0』は、変数 a と b の両方に 0 を代入する。とても便利である。

注意

これは代入の話であって、初期化子を伴う宣言には適用されない。二つの変数 a と b を、次のように同時に宣言することはできない。

int a = b = 0;    // エラー:このような初期化はできない
正しくは、コンマで区切って、
int a = 0, b = 0;
とするか、2 行にわけて以下のように宣言する。
int a = 0;
int b = 0;

プログラムに戻ろう。①と②によって、最大値と最小値の両方を同時に求めている。

それらを分離して、さらに for 文を開いて記述すると、次のようになる。

// tensu[0]~tensu[4]の最大値を求める
max = tensu[0];
if (tensu[1] > max) max = tensu[1];
if (tensu[2] > max) max = tensu[2];
if (tensu[3] > max) max = tensu[3];
if (tensu[4] > max) max = tensu[4];

// tensu[0]~tensu[4]の最小値を求める
min = tensu[0];
if (tensu[1] < min) min = tensu[1];
if (tensu[2] < min) min = tensu[2];
if (tensu[3] < min) min = tensu[3];
if (tensu[4] < min) min = tensu[4];

最大値を求める手続きは、3 値の最大値を求める手続き(p.57)と同じである。対象の整数が 3 個から 5 個に増えて、バラバラの変数が配列の要素に置きかわっているだけである。

Tip

比較のための > 演算子が < に変更されていることを除くと、最小値を求める手続きも同様である。

Example

List 5-8 (p.123) の配列の要素数をオブジェクト形式マクロで定義するように変更したプログラムを作成せよ。要素の交換を行う回数に関する規則性を見つけ出す必要がある。

Example

変数 a が double 型で、変数 b が int 型であるとする。次の代入によって、それぞれの変数の値はどうなるかを説明せよ。

a = b = 1.5;

配列の要素数

これまでの成績処理のプログラムは、学生の人数が5人であった。人数の変更を行う際は、プログラムを書きかえて翻訳・実行し直すことになる。配列の要素数を多めにしておいて、先頭側の必要な部分のみを使う、という方法も考えられる。

この方針で作ったのが、List 5-12に示すプログラムである。

// 学生の点数を読み込んで分布を表示
#include <stdio.h>

#define NUMBER  120     // 人数の上限

int main(void)
{
    int num;                        // 実際の人数
    int tensu[NUMBER];              // 学生の点数
    int bunpu[11] = {0};            // 点数の分布

    printf("人数を入力せよ:");

    do {
        scanf("%d", &num);
        if (num < 1 || num > NUMBER)
            printf("\a1~%dで入力せよ:", NUMBER);
    } while (num < 1 || num > NUMBER);

    printf("%d人の点数を入力せよ。\n", num);
    for (int i = 0; i < num; i++) {
        printf("%2d番 : ", i + 1);
        do {
            scanf("%d", &tensu[i]);
            if (tensu[i] < 0 || tensu[i] > 100)
                printf("\a0~100で入力せよ:");
        } while (tensu[i] < 0 || tensu[i] > 100);
        bunpu[tensu[i] / 10]++;
    }

    puts("\n---分布グラフ---");
    printf("      100 : ");

    for (int j = 0; j < bunpu[10]; j++)
        putchar('*');
    putchar('\n');

    for (int i = 9; i >= 0; i--) {            // 100点未満
        printf("%3d ~%3d : ", i * 10, i * 10 + 9);
        for (int j = 0; j < bunpu[i]; j++)
            putchar('*');
        putchar('\n');
    }

    return 0;
}

実行例

人数を入力せよ:125
1~120で入力せよ:15
15人の点数を入力せよ。
 1番 : 17
 2番 : 38
 3番 : 19
 4番 : 95
 5番 : 100
 6番 : 62
 7番 : 77
 8番 : 45
 9番 : 69
10番 : 81
11番 : 83
12番 : 51
13番 : 42
14番 : 36
15番 : 61
---分布グラフ---
      100 : *
 90 ~ 99 : *
 80 ~ 89 : **
 70 ~ 79 : *
 60 ~ 69 : ***
 50 ~ 59 : *
 40 ~ 49 : **
 30 ~ 39 : **
 20 ~ 29 : 
 10 ~ 19 : **
  0 ~  9 : 

点数用の配列tensuの要素数NUMBERは120である。ただし、プログラムの実行時に、1以上NUMBER以下の人数を変数numに読み込んで、配列の先頭側のnum個の要素のみを利用する。

Tip

実行例の場合、numに読み込まれているのは15であるから、120個の要素中の先頭15個の要素、すなわちtensu[0]~tensu[14]を使う。

さて、本プログラムでは、点数を格納するtensuとは別に、点数の分布を格納するためにint[11]型の配列bunpuを利用している。

分布を求める赤色の部分は、配列bunpuの添字がtensu[i] / 10となっていて、その要素の値をインクリメントしている。

『整数 / 整数』で小数部が切り捨てられることをうまく利用して、次のように分布をカウントアップしているのである。

  • tensu[i]が 0~ 9のとき : bunpu[0]をインクリメント。
  • tensu[i]が10~19のとき : bunpu[1]をインクリメント。
  • ... 中略 ...
  • tensu[i]が80~89のとき : bunpu[8]をインクリメント。
  • tensu[i]が90~99のとき : bunpu[9]をインクリメント。
  • tensu[i]が100のとき : bunpu[10]をインクリメント。

配列のtensuの全要素に対して、この処理を繰り返す。

  • 実行例の場合、次のように分布が求められていく。
  • 17点を読み込む ⇒ tensu[0] / 10は1 ⇒ bunpu[1]を0から1にインクリメント。
  • 38点を読み込む ⇒ tensu[1] / 10は3 ⇒ bunpu[3]を0から1にインクリメント。
  • 19点を読み込む ⇒ tensu[2] / 10は1 ⇒ bunpu[1]を1から2にインクリメント。 ... 以下省略 ...

Example

右に示すように、配列に格納するデータ数と要素の値を読み込んで、その値を表示するプログラムを作成せよ。表示の形式は、全要素の値をコンマとスペースで区切ったものを{ }で囲んだものとする。

なお、配列の要素数は、List 5-12と同様に、オブジェクト形式マクロとして定義しておくこと。

データ数:4
 1番:23
 2番:74
 3番:9
 4番:835
{23, 74, 9, 835}

Example

List 5-12の分布グラフの表示を逆順(0~9、10~19、…、100の順)に行うプログラムを作成せよ。

Example

右に示すように、演習 5-7の分布グラフの表示を縦方向に行うプログラムを作成せよ。

Fig5-02

配列のコピー

次に学習するのは、配列のコピーである。List 5-13に示すのが、そのプログラムである。

// 配列の全要素を別の配列にコピー
#include <stdio.h>

int main(void)
{
    int a[5];        // コピー元配列
    int b[5];        // コピー先配列

    for (int i = 0; i < 5; i++) {    // 要素に値を読み込む
        printf("a[%d] : ", i);
        scanf("%d", &a[i]);
    }

    for (int i = 0; i < 5; i++)
        b[i] = a[i];

    puts(" a    b");
    puts("---------");
    for (int i = 0; i < 5; i++)
        printf("%4d%4d\n", a[i], b[i]);

    return 0;
}

実行例

a[0] : 17
a[1] : 32
a[2] : 55
a[3] : 46
a[4] : 62
  a    b
---------
  17  17
  32  32
  55  55
  46  46
  62  62

配列のコピーを行っているのは、1のfor文である。Fig.5-8に示すように、配列aの全要素の値を、bの要素に代入している。

Fig.5-8 配列のコピー Fig5-8

Tip

二つの配列を同時に走査して、b[0] = a[0];からb[4] = a[4];までを順に実行する。

なお、単純代入演算子=では、配列の代入は行えない。すなわち、

b = a;    // エラー:配列の代入はできない

によって配列をコピーしようとしても、エラーとなる("chap05/List0513x.c")。

重要

代入演算子によって、配列を代入することはできない。コピーは、繰り返し文などを用いた全要素の逐一代入で行う。

なお、2のfor文では、二つの配列を同時に走査して全要素の値を表示している。

Example

List 5-13を書きかえて、配列aの要素の並びを逆順にしたものをbにコピーするプログラムを作成せよ。

条件を満たす要素のコピー

このプログラムを応用して、配列aの要素のうち、正の要素のみを配列bにコピーするように書きかえたのがList 5-14である。

// 配列の要素のうち正の要素を別の配列にコピー
#include <stdio.h>

int main(void)
{
    int a[5];        // コピー元配列
    int b[5];        // コピー先配列

    for (int i = 0; i < 5; i++) {    // 要素に値を読み込む
        printf("a[%d] : ", i);
        scanf("%d", &a[i]);
    }

    int count = 0;                    // コピーした要素数
    for (int i = 0; i < 5; i++)
        if (a[i] > 0)                // 正であれば
            b[count++] = a[i];        // コピー

    for (int i = 0; i < count; i++)
        printf("b[%d] = %d\n", i, b[i]);

    return 0;
}

実行例

a[0] : 17
a[1] : -5
a[2] : 55
a[3] : 15
a[4] : -62
b[0] = 17
b[1] = 55
b[2] = 15

正の要素のコピーを行うのが、水色の部分である。

  • コピーした要素数を格納するための変数countを0で初期化する。
  • 配列aの全要素を走査するfor文で、着目要素a[i]が正であればコピーを行う。 代入先はb[count]であって、代入直後にcountをインクリメントする。
  • 実行例の場合、次のように3回の代入が行われる。 iが0のとき:b[0] = a[0]; ※代入後にcountが0から1へとインクリメントされる iが2のとき:b[1] = a[2]; ※代入後にcountが1から2へとインクリメントされる iが3のとき:b[2] = a[3]; ※代入後にcountが2から3へとインクリメントされる

Column 5-1 警告

プログラムが文法的に誤りではないものの、何らかのミスが潜んでいる可能性がある(たとえば、関数原型宣言(p.157)が与えられていない関数を呼び出している)ときなどに、多くの処理系は、警告メッセージを発する。

警告warningの発音をカタカナで表すと『ウォーニング』が近いのであるが、多くのC言語の書籍では「ワーニング」と書かれている。

ちなみに「ワーニング」と読むのならばStar Warsは「スターワーズ」に、warming upは「ワーミングアップ」となってしまう。

5-2 多次元配列

変数を集めたのが配列であった。その配列を集めると『配列の配列』となり、それが本節で学習する多次元配列である。

多次元配列

前節で学習した配列の要素は、intやdoubleなどの単一型であった。実は、配列の要素自体が《配列》である配列も作れる。

配列を要素型とするのが2次元配列であり、2次元配列を要素型とするのが3次元配列である。もちろん、4次元、5次元、6次元といった配列も作れる。

2次元以上の配列の総称が、多次元配列(multidimensional array)である。

重要

多次元配列は、配列を要素とする配列である。

なお、前節で学習した『要素型が配列ではない配列』は、多次元配列と区別するために、1次元配列と呼ばれる。

Fig.5-9に示すのが、2次元配列を派生する(作り出す)過程である。派生は2段階である。

Fig.5-9 1次元配列と2次元配列の派生 Fig5-9

a → b:int型を3個まとめて1次元配列を派生。 b → c:1次元配列を4個まとめて2次元配列を派生。

それぞれの型は、次のとおりである。

a:int型 b:int[3]型  "int"を要素型とする要素数3の配列 c:int[4][3]型 《"int"を要素型とする要素数3の配列》を要素型とする要素数4の配列

2次元配列は、要素が縦横に並んで、行と列で構成される表のイメージである。そのため、図cの配列は、『4行3列の2次元配列』と呼ばれる。

その4行3列の2次元配列の宣言と内部構造を示したのが、Fig.5-10である。多次元配列の宣言では、最初にまとめる要素数(2次元配列の場合は列数)を木尾側に置く。

Fig.5-10 4行3列の2次元配列 Fig5-10

Tip

要素数を逆にしたint a[3][4];だと、3行4列の2次元配列となる。

《"int"を要素型とする要素数4の配列》を要素型とする要素数3の配列

配列aの要素はa[0]、a[1]、a[2]、a[3]の4個である。いずれも、int型が3個まとめられたint[3]型の配列である。すなわち、要素の要素がint型である。

配列でない次元まで分解した要素のことを、本書では構成要素と呼ぶ。各構成要素をアクセスする添字式は、添字演算子[ ]を連続して適用したa[i][j]という形式である。

なお、添字が0から始まることは1次元配列と共通である。そのため、配列aの構成要素をアクセスする添字式は、a[0][0]、a[0][1]、a[0][2]、...、a[3][2]の計12個である。

1次元配列と同様に、多次元配列の全要素/全構成要素は記憶域上に直線状に連続して並ぶ。

構成要素の並びでは、まず末尾側の添字が順に0、1、...と増えていき、それから先頭側の添字が0、1、...と増えていく順番である。

a[0][0] a[0][1] a[0][2] a[1][0] a[1][1] a[1][2] ... a[3][0] a[3][1] a[3][2]

そのため、たとえばa[0][2]の直後にa[1][0]が位置する、あるいは、a[2][2]の直後にa[3][0]が位置する、といったことが保証される。

重要

多次元配列の構成要素は、末尾側の添字が優先的に増えていく順に並ぶ。

2次元配列を応用したプログラムを作る。List 5-15に示すのは、テストの点数の合計を求めるプログラムである。学生は4人で、教科は3科目であって、そのテストが2回行われているとして、科目別の合計を求めて表示する。

// 4人の学生の3科目のテスト2回分の合計を求めて表示
#include <stdio.h>

int main(void)
{
    int tensu1[4][3] = { {91, 63, 78}, {67, 72, 46}, {89, 34, 53}, {32, 54, 34} };
    int tensu2[4][3] = { {97, 67, 82}, {73, 43, 46}, {97, 56, 21}, {85, 46, 35} };
    int sum[4][3];    // 合計

    // 2回分の点数の合計を求める
    for (int i = 0; i < 4; i++) {        // 4人分の
        for (int j = 0; j < 3; j++)      // 3科目の
            sum[i][j] = tensu1[i][j] + tensu2[i][j];    // 2回分を加算
    }

    // 1回目の点数を表示
    puts("1回目の点数");
    for (int i = 0; i < 4; i++) {
        for (int j = 0; j < 3; j++)
            printf("%4d", tensu1[i][j]);
        putchar('\n');
    }

    // 2回目の点数を表示
    puts("2回目の点数");
    for (int i = 0; i < 4; i++) {
        for (int j = 0; j < 3; j++)
            printf("%4d", tensu2[i][j]);
        putchar('\n');
    }

    // 合計点を表示
    puts("合計点");
    for (int i = 0; i < 4; i++) {
        for (int j = 0; j < 3; j++)
            printf("%4d", sum[i][j]);
        putchar('\n');
    }

    return 0;
}

実行結果

1回目の点数
  91  63  78
  67  72  46
  89  34  53
  32  54  34
2回目の点数
  97  67  82
  73  43  46
  97  56  21
  85  46  35
合計点
 188 130 160
 140 115  92
 186  90  74
 117 100  69

tensu1とtensu2が1回目と2回目の点数を格納する配列で、sumは合計点を格納する配列である。いずれも4行3列の2次元配列であり、12個の構成要素に点数を格納する。

具体的には、右ページのFig.5-11に示すように、各行が学生に対応して、各列が科目に対応している。たとえば、tensu1[2][1]は、3番の学生の英語の1回目の点数を表し、tensu2[3][2]は、4番の学生の数学の2回目の点数を表す。

Fig.5-11 4行3列の2次元配列に格納されたテストの点数 Fig5-11

Tip

2次元配列tensu1とtensu2は初期化子付きで宣言されているので、すべての構成要素が与えられた初期化子の値で初期化される。

一方、加算先の配列sumの宣言には初期化子が与えられていないので、全要素が不定値となる。

点数の加算を行うのが水色の2重ループである。tensu1[i][j]とtensu2[i][j]の値を加えた値をsum[i][j]に代入する作業を、4行3列の全構成要素に対して繰り返す。

Tip

図に示しているsumの構成要素の値は、合計を求めた後の値である。

本プログラムでは、テストが2回であったので、2個の2次元配列を利用した。もし、テストの回数が15回であれば、2次元配列を15個用意するのではなく、2次元配列を15個集めた3次元配列を利用したほうがよさそうである。

その場合、点数の配列は、次のように宣言することになる。

int tensu[15][4][3];    // 15回分の4人の3科目の点数

3次元配列の各構成要素をアクセスする添字式は、添字演算子[ ]を3重に適用した形式となる。

構成要素は、先頭からtensu[0][0][0]、tensu[0][0][1]、···、tensu[14][3][2]の順に並ぶ。

Tip

1次元配列や2次元配列と同様に、全構成要素が連続した領域に配置される。

Example

4行3列の行列と3行4列の行列の積を求めるプログラムを作成せよ。各構成要素の値はキーボードから読み込むこと。

Example

6人の2科目(国語と数学)の点数を読み込んで、科目ごとの合計点と平均点、学生ごとの合計点と平均点を求めるプログラムを作成せよ。

Example

2回分の点数を3次元配列tensuに格納するようにList 5-15を書きかえたプログラムを作成せよ。

Column 5-2 配列に関する補足

ここでは、配列に関して、いくつかの点を補足学習する。

可変長配列(VLA = variable length array)

配列を定義する際は、要素数を定数とするのが原則であることを、p.117で学習した。

標準Cの第2版では、その制限が緩和され、要素数を変数とした可変長配列が定義できるようになっている。たとえば、次のようなコードが許される(標準Cの第1版ではエラーとなる)。

// 標準C第2版での配列の宣言(第1版ではエラー)
void func(int n)
{
    int a[n];    // 要素数nの配列(要素数は実行時に決定する)
    //--- 中略 ---//
}

この言語拡張に対して、私は当初から疑問をもっていた(C言語の設計思想と相容れないからである)。

事実、可変長配列は、標準Cの第3版から"オプション扱い"となって、コンパイラはサポートしなくてよいことになっている(コンパイラが可変長配列をサポートしない場合は、__STDC_NO_VLA__というマクロが定義される)。

要素指示子(designator)

標準Cの第2版では、配列に与える初期化子についても拡張が行われている。要素指示子(指示付き初期化子)を使うことで、配列の任意の要素に対する初期化子の指定が行える。

次に示すのが、宣言の一例である。

int a[] = {[2] = 5, 9, [6] = 3, 1};

この宣言により、a[2]が5、その次の要素が9、a[6]が3、その次の要素が1で初期化される。最後の初期化子が8番目の要素a[7]に対するものであることから、配列aの要素数は自動的に8となる。また、初期化子が不足する要素は0で初期化されるので、次の宣言と同等である。

int a[8] = {0, 0, 5, 9, 0, 0, 3, 1};

なお、要素数が1000の配列の最後の要素のみを1で初期化して、それ以外の要素を0で初期化するのであれば、次のようになる。

int a[1000] = {[999] = 1};

第12章で学習する構造体型のオブジェクトを宣言する際に与える初期化子でも、要素指示子が利用できるようになっている。

ここまでに紹介した二つの機能は、C++には取り入れられていない。プログラミング言語C++の開発者であるBjarne Stroustrup氏は、著書の中で次のように述べられている(※)

C言語がC89からC99に進化したときに、C++は機能として取っているVLA(可変長配列:variable-length array)と、冗長である指示付き初期化子(designated initializer)以外の、ほとんどの新機能を取り込んだ。

可変長配列は、言語設計上のミスと考えるべきである。正式に取り入れられたのが、標準Cの第2版ぢみということもあり、その利用はおすすめできない。

配列の動的な生成

配列の要素数をプログラム実行時に決定する必要がある場合は、calloc関数とfree関数を使って実現する(標準Cの第1版を含め、すべてのバージョンに対応する手法である)。

List 5C-1に示すのが、プログラム例である。

// int型の配列を動的に生成して破棄
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int na;         // 配列aの要素数

    printf("要素数:");
    scanf("%d", &na);

    int *a = calloc(na, sizeof(int));    // 要素数naのint型配列を生成

    if (a == NULL)
        puts("記憶域の確保に失敗しました。");
    else {
        printf("%d個の整数を入力してください。\n", na);
        for (int i = 0; i < na; i++) {
            printf("a[%d] : ", i);
            scanf("%d", &a[i]);
        }

        printf("各要素の値は次のとおりです。\n");
        for (int i = 0; i < na; i++)
            printf("a[%d] = %d\n", i, a[i]);

        free(a);    // 要素数naのint型配列を破棄
    }

    return 0;
}

実行例

要素数:5
5個の整数を入力してください。
a[0] : 1
a[1] : 7
a[2] : 2
a[3] : 4
a[4] : 6
各要素の値は次のとおりです。
a[0] = 1
a[1] = 7
a[2] = 2
a[3] = 4
a[4] = 6

本プログラムの理解のためには、入門書である本書の学習の対象外の知識が必要であるので、ここでは、要点のみを簡単に解説する。

  • 変数 a は、int 型ではなく、(第 10 章で学習する)ポインタである。
  • calloc 関数は、プログラムの実行時に記憶域を確保する関数である(特殊な空き領域から、メモリを借りてくる関数である)。確保されたオブジェクトの生存期間は、割付け記憶域期間となる。 第 1 引数に与えるのは、確保する配列の要素数で、第 2 引数に与えるのは、要素の大きさ(バイト数)である。
  • 上記の関数の返却値を変数 a に入れることによって、ポインタ a は配列の先頭要素を指すポインタとなる(ポインタは、あたかも配列のように振る舞う:p.296)。
  • free 関数は、確保していた記憶域を解放する関数である(借りていたメモリを返す関数である)。

詳細は、『明解C言語シリーズの、他の書籍で学習していただけると幸いである。

まとめ

  • 同一型のオブジェクトを集めて、記憶域上に連続して一直線に並べたものが、配列である。配列は、要素型、要素数、与えられた名前で特徴付けられる。

  • 要素型がTypeである配列は、Typeの配列と呼ばれる。なお、要素数がnのTypeの配列の型はType[n]型である。

  • 配列の個々の要素は、添字演算子[ ]を使った添字式でアクセスする。[ ]の中に与える添字は、先頭要素から何個後ろに位置するのかを表す整数値である。要素数がnの配列の要素をアクセスする式は、先頭から順にa[0]、a[1]、...、a[n - 1]である。

  • オブジェクト形式マクロは、#define指令を使って定義する。

    #define  a  b
    
    は、この指令以降のaをbに置換せよ。置換対象のaはマクロ名と呼ばれる。

  • オブジェクト形式マクロで定数に名前を与えると、マジックナンバーを除去できる。

  • 配列の宣言時には、要素数を定数式として与えるのが基本である。オブジェクト形式マクロで要素数を表す値を定義しておくと、要素数の変更が柔軟に行える。

  • 配列の要素を一つずつ順になぞっていくことを、走査という。

  • 配列に与える初期化子は、個々の要素に対する初期化子○、△、□を先頭から順に並べたものを{ }で囲んだ{○、△、□、}という形式である。最後のコンマは省略できる。 要素数の指定を省略した場合は、初期化子の個数に基づいて要素数が決定する。また、指定された要素数に対して{ }内の初期化子が不足する場合、初期化子が与えられていない要素は0で初期化される。

  • 配列を要素とする配列が、多次元配列である。多次元配列を配列でない次元まで分解した要素が、構成要素である。個々の構成要素は、次元数の数だけ添字演算子[ ]を多重に適用した式によってアクセスできる。

  • 多次元配列の構成要素は、末尾側の添字が優先的に増えていく順に並ぶ。

  • 代入式を評価すると、代入後の左オペランドの型と値が得られる。

  • 代入演算子=によって配列の全要素を丸ごとコピーすることはできない。

Fig5-12 Fig5-13

演習問題

  1. 温度変換表の生成
  2. 数字の出現回数カウント
  3. 多項式の計算
  4. 行列の積

演習問題6-1:温度変換表の生成

問題の説明

摂氏温度(-20℃から50℃まで10℃間隔)を華氏温度に変換する表を生成するプログラムを作成してください。変換には以下の公式を使用します: 華氏温度 = 摂氏温度 × 9/5 + 32

期待される結果

温度変換表(摂氏 → 華氏)
-------------------------
 摂氏(℃) | 華氏(°F)
-------------------------
     -20 |     -4.0
     -10 |     14.0
       0 |     32.0
      10 |     50.0
      20 |     68.0
      30 |     86.0
      40 |    104.0
      50 |    122.0

ヒント

  • 配列の宣言と初期化方法に注意しましょう。初期化子リストを使って配列の値を設定できます。
  • 浮動小数点演算では、整数同士の除算(9/5)ではなく、浮動小数点リテラル(9.0/5.0)を使うことで正確な結果が得られます。
  • printfの書式指定子(%8d%8.1fなど)を変更して、表示形式をカスタマイズできます。
  • 配列の要素数を変数として定義しておくと、配列のサイズを変更する際に便利です。

演習問題6-2:数字の出現回数カウント

問題の説明

整数の配列内に各数字(0〜9)が何回出現するかをカウントし、アスタリスク(*)を使った簡単な棒グラフで視覚化するプログラムを作成してください。

期待される結果

元の配列: 7 3 5 7 8 2 5 9 4 7 3 8 5 1 7

数字の出現回数:
数字 0:  (0回)
数字 1: * (1回)
数字 2: * (1回)
数字 3: ** (2回)
数字 4: * (1回)
数字 5: *** (3回)
数字 6:  (0回)
数字 7: **** (4回)
数字 8: ** (2回)
数字 9: * (1回)

ヒント

  • 配列のインデックスとして別の配列の値を使う手法に注目しましょう。これは配列を使ったカウント方法の基本テクニックです。
  • 二重ループを使って、視覚的なグラフを表示する方法を理解しましょう。
  • 配列のインデックスは0から始まることを常に意識してください。
  • 入力配列に0〜9以外の数字が含まれる場合、範囲外アクセスが発生する可能性があるので注意が必要です。

演習問題6-3:多項式の計算

問題の説明

ホーナー法(Horner's method)を使用して多項式の値を効率的に計算するプログラムを作成してください。例えば、P(x) = 3x^4 + 2x^3 - 5x^2 + x - 7 の多項式について、x = 2 を代入したときの値を計算します。

ホーナー法について

ホーナー法は、多項式を評価するための効率的なアルゴリズムです。n次の多項式を計算する際に、通常の方法では最大でO(n²)の乗算が必要ですが、ホーナー法ではO(n)の乗算で済みます。

例えば、P(x) = 3x^4 + 2x^3 - 5x^2 + x - 7 を計算する場合:

通常の方法:

  • 3 * (x^4) = 3 * (xxx*x) ← 4回の乗算

  • 2 * (x^3) = 2 * (xxx) ← 3回の乗算

  • 5 * (x^2) = 5 * (x*x) ← 2回の乗算

合計して9回の乗算が必要

ホーナー法: 多項式を次のように変形します: P(x) = ((((3)x + 2)x - 5)x + 1)x - 7

これを内側から計算していきます:

  1. result = 3

  2. result = result * x + 2 = 3 * x + 2

  3. result = result * x - 5 = (3 * x + 2) * x - 5

  4. result = result * x + 1 = ((3 * x + 2) * x - 5) * x + 1

  5. result = result * x - 7 = (((3 * x + 2) * x - 5) * x + 1) * x - 7

わずか4回の乗算で済みます。これが、ホーナー法の効率性です。

期待される結果

P(2.0) = 39.0

ヒント

  • ホーナー法は多項式計算の効率的なアルゴリズムです。通常の計算方法と比較して、必要な乗算の回数が少なくなります。
  • 配列の添字に注意してください。係数配列では添字が小さいほど低次の項(定数項に近い項)を表します。
  • 多項式の係数を逆順(高次の項から低次の項へ)に処理することがホーナー法の鍵です。
  • プログラムを修正して、ユーザーから多項式の次数、係数、xの値を入力できるようにしてみましょう。
  • 計算の各ステップを表示するように拡張すると、ホーナー法の仕組みをより深く理解できます。

演習問題6-4:行列の積

問題の説明

2つの行列の積を計算するプログラムを作成してください。行列Aは2行3列、行列Bは3行4列で、これらの積である行列Cは2行4列になります。行列の乗算規則に従って正確に計算してください。

期待される結果

行列A (2×3):
  1  2  3
  4  5  6

行列B (3×4):
  7  8  9 10
 11 12 13 14
 15 16 17 18

行列C = A×B (2×4):
  74  80  86  92
 173 188 203 218

ヒント

  • 行列の乗算では、Aの行とBの列の内積を計算します。そのため、Aの列数とBの行数が等しくなければなりません。
  • 結果の行列Cのサイズは、Aの行数 × Bの列数となります。
  • 行列の乗算には三重ループが必要です:Aの行、Bの列、そして内積の計算のための添字。
  • 行列の初期化には、多次元配列を使用します。{}内に行ごとの値を{}で囲んで指定します。
  • 結果の行列Cは初期化時に0で初期化することが重要です。そうしないと、内積の計算時に不定値が加算されます。