京大理学部 物理科学課題演習 C1 （その１）

#関数の使い方

　みなさんは関数の使い方を知っていますか？たくさんプログラムを書くようになると、同じような処理をいろいろなプログラムで行うことがよくあります。このような場合以前に作ったプログラムを利用できると大変便利です。後々再利用することを考えた場合、一連のまとまった手続きは関数にまとめておくとよいでしょう。また一つのプログラムが非常に長くなる場合も、ひとまとまりの処理を関数にまとめておくと、プログラムが大変読みやすくなります。

　さて行列の積を計算する部分は後で使う可能性が高いので、試しに関数にしてみましょう。今後関数は頻繁に使うことになるので、関数の使い方を知らない人は、今のうちに勉強しておきましょう。以下では簡単な使い方の解説をします。詳しくは適当な教科書を見てください。

　関数の名前は `matprd` としましょう。この関数を呼ぶときに、計算するべき行列 $A, B$ と計算結果を入れる行列 $C$ の配列を関数に引き渡します。配列の大きさは `#define` 文で定義された値を使うことにします。メイン関数でのこの関数の呼び方は

```c
matprd(a, b, c);
```

となります。関数でこの引数を受け取るには

```c
void matprd(float x[N][L], float y[L][M], float z[N][M]);
```

のようにします。呼ぶ側と受ける側の引数名が同じである必要はありません。以下にプログラム例を示します。

```c
#include <stdio.h>

#define N 3
#define L 3
#define M 3

void matprd(float x[N][L], float y[L][M], float z[N][M]);

int main(void)
{
    static float a[N][L] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0};
    static float b[L][M] = {10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0};
    static float c[N][M];

    int i, j;

    matprd(a, b, c);

    for (i = 0; i < N; i++) {
        for (j = 0; j < M; j++) {
            printf("%10.2f", c[i][j]);
        }
        printf("\n");
    }
    return 0;
}

/* 行列の積を求める関数 */
void matprd(float x[N][L], float y[L][M], float z[N][M])
{
    int i, j, k;

    for (i = 0; i < N; i++) {
        for (j = 0; j < M; j++) {
            z[i][j] = 0.0;
            for (k = 0; k < L; k++) {
                z[i][j] += x[i][k] * y[k][j];
            }
        }
    }
}
```

　一応できましたが、多少不満が残ります。上のプログラムでは配列の大きさが `#define` 文で関数に渡されています。例えば `#define` 文以外で配列の大きさを決めているプログラムにはこの関数をそのまま移植することはできません。このように、関数外部の構造に依存するプログラムは汎用性が劣ります。~~C には整合配列というのがありませんから、配列の大きさを引数で渡すことはできません。~~<font color="Red">（すみません私の勉強不足でした。C99 以降では配列のサイズを引数で渡すことも可能です。詳しくはコメント欄をご参照ください。ポインタの使い方の例として、以下はそのまま残します。）</font>そこで登場するのがポインタです。関数の側で配列の先頭の番地をポインタで受ければ、次のようなプログラムが書けます。

```c
#include <stdio.h>

#define N 3
#define L 3
#define M 3

void matprd(float *xpt, float *ypt, float *zpt, int n, int l, int m);

int main(void)
{
    static float a[N][L] = {{1.0, 2.0, 3.0}, {4.0, 5.0, 6.0}, {7.0, 8.0, 9.0}};
    static float b[L][M] = {{10.0, 11.0, 12.0}, {13.0, 14.0, 15.0}, {16.0, 17.0, 18.0}};
    static float c[N][M];

    int i, j;

    matprd(&a[0][0], &b[0][0], &c[0][0], N, L, M);

    for (i = 0; i < N; i++) {
        for (j = 0; j < M; j++) {
            printf("%10.2f", c[i][j]);
        }
        printf("\n");
    }
    return 0;
}

/* 行列の積を求める関数（ポインタ版） */
void matprd(float *xpt, float *ypt, float *zpt, int n, int l, int m)
{
    int i, j, k;

    for (i = 0; i < n; i++) {
        for (j = 0; j < m; j++) {
            *(zpt + i*m + j) = 0.0;
            for (k = 0; k < l; k++) {
                *(zpt + i*m +j) += *(xpt + i*l + k) * *(ypt + k*m + j);
            }
        }
    }
}
```

　配列 `a` の成分 `a[i][j]` はメモリ上で `a[0][0], a[0][1],..., a[0][L-1], a[1][0],...` といった具合にならんでいますから、配列 `a` の先頭の番地を受け取ったポインタ `xpt` を使って、配列 `a` の $(i,j)$ 成分 `a[i][j]` を `*(xpt + i*l + j)` のように参照できるわけです。これで、引数だけで全ての情報を関数に引き渡すことができました。

##課題
　計算結果を出力する部分も関数にしなさい。関数名は `output` とします。呼ぶ側は

```c
output(&c[0][0], N, M);
```

関数側は

```c
void output(float *cpt, int n, int m);
```

となります。ただし行列 $A$ と $B$ も表示したければ、それぞれ引数として渡さなければなりません。