sub1(a, a);
printf("(sub1) %d\n", a);
a = 0;
a = sub2(a, a);
printf("(sub2) %d\n", a);
a = 0;
sub3(&a, &a);
printf("(sub3) %d\n", a);
return 0;
}
void sub1(int x, int y) {
x++;
y++;
}
int sub2(int x, int y) {
x++;
y++;
return y;
}
void sub3(int *x, int *y) {
(*x)++;
(*y)++;
}
10.5. 配列を関数パラメータとして受け渡す 155
• 関数本体の中では、仮引数の配列要素の参照はこれまでと全く同じ様な書き方をする。
• 実引数側では、
a または &a[0]
という書き方をする。
配列名は、
計算機内部では先頭要素を指す定数ポインタとして扱われている。
例題 10.16 (一次元配列の一部を関数パラメータとして受け渡す) double型一次元
配列の部分要素列についての情報を引数として受け取り、その部分配列内の要素の総 和を計算して返す関数sum() を定義せよ。 そして、
v[k]= 249−k (k = 0〜49)
という風に値の設定された大きさ50のdouble型一次元配列について、この関数を用 いて、
v[0]+v[1]+v[2]+· · ·+v[49], v[40]+v[41]+v[42]+· · ·+v[49], v[20]+v[21]+v[22]+· · ·+v[39]
の値を計算して出力するCプログラムを作成せよ。
(考え方) 素直に考えるなら、部分配列内の要素の総和を計算する関数sum() には
1 配列の名前(i.e.先頭要素の番地), 2 総和の始めとなる配列要素の添字番号, 3 総和を締めくくる配列要素の添字番号
の3つを引数として引き渡すことが頭に浮かぶ。もちろん、これは妥当な考えで、関数
sum() も使い易くなる。(=⇒ 具体的なプログラミングは演習問題として残しておく。)
しかし、配列を関数パラメータとして受け渡しする場合、実際に受け渡されるのは配列要 素へのポインタ(i.e.番地)に他ならない。 呼ばれる関数側としては、そのポインタが実 際に関数を呼んだ側で確保された配列の先頭番地を指しているかどうかは重要ではなく、
そのポインタが指す領域以降に然るべき型のデータ領域が十分に長く(i.e.関数実行の間 に配列アクセス違反を起こさない程度に)確保されていれば良いだけである。 従って、逆 に、これさえ守れば良い訳で、もし
double型配列の名前 a と大きさ size を引数として受け取り、
a[0]+a[1]+· · ·+a[size-1] を計算して返す関数 double sum(double a[], int size)
が定義できているなら、この関数をsum(&v[from], size)という風に使うことも許される はずで、この呼び出しによって部分配列の総和v[from]+v[from+1]+· · ·+v[from+size-1]
が計算されることになる。 '
&
$
% 確認:
配列要素 v[from]〜v[from+size-1] が sum()を呼び出す側で確保 されていれば、確かに、
3 &v[from]は配列要素を指すポインタで、
3 &v[from]番地以降にも同じ型のデータが十分長く続いている。
呼び出された側の関数が実際にメモリ確保された領域だけを使うよう にするのはプログラマの責任である。
(プログラミング) (部分)配列の要素の総和を計算する関数 sum() は、引数として配列 の名前(先頭要素の番地)a と大きさ sizeを受け取り、a[0]+· · ·+a[size-1] を計算して 返すものとする。この関数sum() の中では、途中までの総和a[0]+· · ·+a[i] (0≤i <size) を保持するために関数名と同じ sum という名前のdouble型局所変数を用意する。 ま た、main()関数の中では、配列要素 v[0]〜v[49] に対する値の設定は数学関数 pow() を使うのではなく
v[49] ←− 1,
v[48] ←− v[49]×2, v[47] ←− v[48]×2,
...
という風に行うことにして、プログラムを構成した。このCプログラムと、これをコン パイル/実行している様子を次に示す。(下線部はキーボードからの入力を表す。)
[motoki@x205a]$ nl func-bind-part-of-array.c Enter 1 #include <stdio.h>
2 double sum(double a[], int size);
3 int main(void) 4 {
5 int i;
6 double v[50];
7 v[49] = 1.0;
8 for (i=48; i>=0; --i) 9 v[i] = v[i+1] * 2.0;
10 printf("v[0] +v[1] + ... +v[49] = %16.0f\n", sum(v, 50));
11 printf("v[40]+v[41]+ ... +v[49] = %16.0f\n", sum(&v[40], 10));
12 printf("v[40]+v[41]+ ... +v[49] = %16.0f\n", sum(v+40, 10));
13 printf("v[20]+v[21]+ ... +v[39] = %16.0f\n", sum(v+20, 20));
14 return 0;
15 }
16 /*---*/
17 /* double型配列(もしくは配列の断片)の要素の総和を計算して返す */
18 /*---*/
19 /* (仮引数) a : double型配列 */
20 /* size : double型配列 a の大きさ */
21 /* (関数値) : a[0]+a[1]+a[2]+...+a[size-1] */
22 /*---*/
23 double sum(double a[], int size) 24 {
25 int i;
10.5. 配列を関数パラメータとして受け渡す 157
26 double sum=0.0;
27 for (i=0; i < size; ++i) 28 sum += a[i];
29 return sum;
30 }
[motoki@x205a]$ gcc func-bind-part-of-array.c Enter [motoki@x205a]$ ./a.out Enter
v[0] +v[1] + ... +v[49] = 1125899906842623 v[40]+v[41]+ ... +v[49] = 1023 v[40]+v[41]+ ... +v[49] = 1023 v[20]+v[21]+ ... +v[39] = 1073740800 [motoki@x205a]$
ここで、
• 10.2節で説明した名前の有効範囲の規則により、sum という名前 は、プログラムの24
〜30行目のブロックの中では26行目で宣言された局所変数として解釈され、そのブ ロックの外では2行目と23行目で宣言された関数名として解釈される。
• プログラムの11行目 は、一次元配列の一部v[40]〜v[49]の先頭番地とその大きさ
を関数sum()に引き渡している。
'
&
$
% 補足:
関数側では受け渡されるものが元々(呼出し側で)配列として確保さ れたものかどうかのチェックはしない。仮引数として与えられるも のを配列の先頭番地と見なして処理を進めるだけである。
• プログラムの12行目 も11行目と同じ処理をしている。一次元配列の場合、配列名は 計算機内部では先頭要素を指す定数ポインタとして扱われているので、v+40 は先頭 から40個後の要素を指すポインタ値である。
'
&
$
% 補足:
ポインタに関する算術は普通の算術演算とは異る。v+40の番地は実際には
&v[0]+40×sizeof(double)番地、すなわち &v[40]番地 である。
□演習 10.17 (一次元配列の一部を関数パラメータとして受け渡す) double型配列の名前
a と2つの配列添字from, toを引数として受け取り、a[from]+a[from+1]+· · ·+a[to] を 計算して返す関数double sum(double a[], int from, int to)を定義することによっ
て、例題10.16のプログラムを再構成してみよ。
□演習 10.18 (整列化の関数) 例題9.6のプログラム(バブル整列法)の中の整列化を行っ ている部分(17〜24行目)を関数化して、プログラムを再構成してみよ。
'
&
$
% Hint:
一般的な整列化の関数を構成するのであれば、データの入った配列(の 先頭番地)とその大きさは引数として受け渡す必要があります。
多次元配列を関数の引数として受渡しする方法:
• 仮引数側では、1次元目を除く全ての次元の大きさを指定して、
データ型 配列名[][ 大きさ] · · · [ 大きさ]
または データ型 配列名[ 大きさ][ 大きさ] · · · [ 大きさ] または データ型 (*配列名)[ 大きさ] · · ·[ 大きさ] という書き方をする。 '
&
$
% 2次元目以降の次元の大きさを指定する理由:
そうしないと、コンパイラが配列の添字の値からその配列要素の番 地を割り出せないからである。 また、1次元目の配列の大きさにつ いては明示する必要はない。 明示したとしても捨てられる。
• 関数本体の中では、仮引数の配列要素の参照はこれまでと全く同じ様な書き方をする。
• 実引数側では、
a または &a[0]
という書き方をする。
'
&
$
% 例えば:
aが3次元配列の場合、配列名aは計算機内部では2次元配 列a[0]を指す定数ポインタとして扱われている。
例 10.19 (多次元配列を関数の仮引数とする場合) 3次元配列int a[7][9][2]を引数と して受渡したい時には、仮引数部は次のように書く。
int a[][9][2]
int a[7][9][2]
int (*a)[9][2] ←−明示的な書き方
いずれか
例題 10.20 (行列の積を計算する関数) 例題 9.8 では、3×3 実数値行列 A = [aij], B = [bij] の要素を読み込み、これらの行列の積 AB を計算してその結果を 表示するプログラムを提示した。このプログラムの中の行列の積を計算する部分を関 数化し、プログラム全体を再構成してみよ。
(考え方) 計算結果は単一の数値ではないので、これを関数の戻り値とするのは無理があ る。(”構造体”を使えばできないこともない。) そこで、乗算対象の行列データを保持す る2つの2次元配列a[][], b[][]だけでなく、計算結果のデータを格納する2次元配列 productAB[][] (の先頭番地) も関数引数として受け渡す、
void matrix multiplication(double x[][3], double y[][3],
double productXY[][3]);
という関数を考え、この中で
(配列productXYの表す行列)←− (配列xの表す行列) × (配列yの表す行列) という処理を行なうようにすれば良い。
(プログラミング) 次の通り。
[motoki@x205a]$ nl matrix-multiplication-function.c Enter
1 /* 3×3実数値行列 A=[a_ij], B=[b_ij] の要素を読み込み、 */
2 /* 行列の積 AB を計算してその要素を2次元状に見易く出力する */
3 /* Cプログラム */
10.5. 配列を関数パラメータとして受け渡す 159
4 #include <stdio.h>
5 #define N (3)
6 void matrix_multiplication(double x[][N], double y[][N],
7 double productXY[][N]);
8 int main(void) 9 {
10 double a[N][N], b[N][N], productAB[N][N];
11 int i, j;
12 /* 行列 A,B の各要素を入力する */
13 for (i=0; i<N; i++) {
14 printf("行列 A の %d 行目の要素を順に入力して下さい: ", i);
15 for (j=0; j<N; j++)
16 scanf("%lf", &a[i][j]);
17 }
18 printf("\n");
19 for (i=0; i<N; i++) {
20 printf("行列 B の %d 行目の要素を順に入力して下さい: ", i);
21 for (j=0; j<N; j++)
22 scanf("%lf", &b[i][j]);
23 }
24 /* 行列の積 AB の各要素を計算して配列 productAB に記録する */
25 matrix_multiplication(a, b, productAB);
26 /* 行列の積 AB の結果を表示する */
27 printf("\n行列の積 AB の結果:\n");
28 for (i=0; i<N; i++) { 29 printf(" ");
30 for (j=0; j<N; j++)
31 printf(" %12.5g", productAB[i][j]);
32 printf("\n");
33 }
34 return 0;
35 }
36 /*---*/
37 /* 行列の積を計算 */
38 /*---*/
39 /* (仮引数) x : double型2次元配列 */
40 /* y : double型2次元配列 */
41 /* productXY : double型2次元配列, 計算結果の格納場所 */
42 /* (関数値) : なし */
43 /* (機能) : (x[][]の表す行列)×(y[][]の表す行列) の計算を */
44 /* 行ないその結果を productXY[][] に格納する。 */
45 /*---*/
46 void matrix_multiplication(double x[][N], double y[][N],
47 double productXY[][N])
48 {
49 int i, j, k;
50 for (i=0; i<N; i++) { 51 for (j=0; j<N; j++) { 52 productXY[i][j] = 0.0;
53 for (k=0; k<N; k++)
54 productXY[i][j] += x[i][k]*y[k][j];
55 }
56 } 57 }
[motoki@x205a]$