第11章では、自作関数の使い方と作り方を説明しましたが、
ここでは、関数から情報を返す方法として、戻り値を使った方法を説明しました。
戻り値を使って情報を返すのが、最も簡単な方法であることは間違いありませんが、
この方法では、常に1つの情報しか返すことが出来ません。
2つ以上の情報を返したい時などは不便です。
そのような場合には、ポインタ型の引数を使って情報を返すことが出来ます。
ポインタ型の引数と言っても、別段特別なことではありません。
単に、引数の型がポインタ型であるだけで、普通の引数となんら変わりません。
C言語では、関数へ情報を渡す場合、必ず元の変数の値のコピーを渡します。
この様な方法を値渡しと呼び、元の変数の値が変更されないことが特徴です。
ポインタ型の引数であっても、値のコピーが渡される原則に違いはありません。
それでもポインタ型を使うのは、ポインタ型はアドレスを受け取ることが出来るからです。
関数を呼び出す時に、既に存在する変数のアドレスを指定すれば、
呼び出された関数で、受け取ったアドレスをポインタ変数に代入すれば、
後はポインタ変数を通常変数モードに切り替えて、返す情報を代入出来ます。
返された情報は、呼び出し側で指定した変数に記憶されていることになります。
次のプログラムは、実際にポインタ型の引数を使って情報を返す例です。
このプログラムの実行結果は、次のようになるかもしれません。#include <stdio.h> void func(int *pvalue); /* プロトタイプ宣言 */ int main(void) { int value = 10; printf("&value = %p\n",&value); func(&value); /* アドレスを渡す */ printf("value = %d\n",value); return 0; } void func(int *pvalue) { printf("pvalue = %p\n",pvalue); *pvalue = 100; /* 通常変数モードに切り替えて代入 */ return; }
このプログラムでは、関数を呼び出す時に、変数valueのアドレスを渡しています。
&value = 0F68
pvalue = 0F68
value = 100
ポインタ変数にアドレス値が代入されている場合には、
通常変数モードに切り替えてそのメモリを自由に読み書き出来るのだから、
結果として、呼び出された関数から、呼び出し元の変数の中身を書き換えられるわけです。
これまで&付きで呼び出していた関数は、全て同様の仕組みです。
この使い方が、C言語での最もポピュラーなポインタの使い方です。
これまでは取り扱ってきませんでしたが、配列を引数にすることも出来ます。
しかし、配列の場合、通常の引数とは異なる性質が多く、扱いにくくなります。
とりあえず、今まで通りの方法で配列型の引数を持つ関数を作ってみます。
引数はint型で要素10の配列とし、配列に代入された値の平均を求める関数を作ります。
今まで通りの方法で実装すると、次の通りになります。
このプログラムの実行結果は次の通りになります。#include <stdio.h> int getaverage(int data[10]); int main(void) { int average,array[10] = {15,78,98,15,98,85,17,35,42,15}; average = getaverage(array); printf("%d\n",average); return 0; } int getaverage(int data[10]) { int i,average = 0; for (i = 0;i < 10;i++) { average += data[i]; } return average / 10; }
関数内では、配列の要素番号0〜9までの値を変数に加算して、
49
この様に、一見すると配列も引数として渡せるように見えます。
前項では、配列を引数として使う方法を説明しましたが、
この関数は、今までの引数ではあり得なかった、奇妙な性質を持っています。
まず、配列の要素数は無視されてしまいます。
次のプログラムは、わざと要素数5の配列を渡してみる例です。
このプログラムの実行結果は次のようになるかもしれません。#include <stdio.h> int getaverage(int data[10]); int main(void) { int average,array[5] = {15,98,98,17,42}; /* 要素数が5 */ average = getaverage(array); printf("%d\n",average); return 0; } int getaverage(int data[10]) { int i,average = 0; for (i = 0;i < 10;i++) { average += data[i]; } return average / 10; }
引数の型は10要素になっているにもかかわらず、5個しか要素のない配列が渡せます。
202380394
更におかしな現象として、関数内で配列の値を変えると呼び出し側まで変化します。
次のプログラムは、関数内で配列の値を変更してみる例です。
このプログラムの実行結果は次の通りになります。#include <stdio.h> int getaverage(int data[10]); int main(void) { int average,array[10] = {15,78,98,15,98,85,17,35,42,15}; printf("array[3] = %d\n",array[3]); average = getaverage(array); printf("array[3] = %d\n",array[3]); printf("%d\n",average); return 0; } int getaverage(int data[10]) { int i,average = 0; for (i = 0;i < 10;i++) { average += data[i]; } data[3] = 111; /* 引数の配列の値を変更 */ return average / 10; }
今までの引数では、呼び出された関数の中で引数の値を変更しても、
array = 15
array = 111
49
前項では、配列型の引数の持つ奇妙な性質を説明しました。
あのような現象は、配列が値渡しされていれば、絶対にあり得ないことです。
つまり、逆に言えば、配列自体は値渡しされていないのです。
しかし、実際に関数に配列を渡して平均値を計算することには成功しています。
つまり、なんらかの形で、配列が渡されていることは間違いのない事実です。
この点について検証するために、少し実験を行ってみましょう。
まず、前項で、配列型の引数では要素数は無視されていることはわかりました。
それならばいっそ、要素数を指定しなければどうなるでしょうか?
つまり、関数を、次のように変更してみるのです。
この様に書き換えて実行しても、何の問題もなく動作します。int getaverage(int data[])
しかし、要素数を無視して、どうやって配列の値を渡しているのでしょうか?
普通に考えれば、配列を渡す場合、要素数の数だけ値をコピーすることになります。
しかし、要素数を無視している以上、そのような方法は使えません。
ここで、もう1つの実験を行ってみたいと思います。
前項で、呼び出された関数で配列の値を変更すると、呼び出し元まで変化しましたが、
この現象は、ポインタ型の引数を使った時と良く似ています。
つまり、配列ではなくアドレスを渡しているのではないかとも考えられます。
試しに、関数を、次のように変更してみました。
驚くべきことに、これでも、何の問題もなく動作しました。int getaverage(int *data);
配列の先頭のアドレスを渡すだけならば、要素数など全く関係ありません。
また、呼び出された関数での配列は、呼び出し元と同じメモリ領域を指すことになるので、
呼び出された関数で配列の値を変えると、呼び出し元も変更されるのは当然です。
このことについてまとめると、まず、次の3つは同じ意味の仮引数宣言です。
ただし、この3つが同じ意味になるのは関数の仮引数宣言の場合のみです。
そして、関数の中では、dataはいずれもポインタ型の変数です。int getaverage(int data[10]); int getaverage(int data[]) int getaverage(int *data);
[ どれにするか ]
この3つが同じ意味だと、どれを使って良いのか迷う人もいるかもしれませんが、
筆者としては、2番目のように要素数を省略した形を使うことを進めます。
何故なら、3番目の宣言は、普通のポインタ型と紛らわしいからです。
2番目の宣言であれば、配列を受け取ることが明示的にわかります。
1番目の宣言は、C言語に慣れた人たちには幼稚な宣言に見えます。