引数にポインタを使う意味
引数をポインタとすることで、関数に渡した変数の中身そのものを操作できるようになります。ポインタを使った場合と使わない場合との違いを把握しておきましょう。
値渡しと参照渡し
リスト1のようにした場合、関数を呼び出した側で用いている変数を引数に設定しても、その変数が保持している値自体は変化しません。呼び出した関数に渡されるのは変数そのものではなく『変数の値だけ』だからです。
一方、リスト2のような形で関数の引数にポインタを使うと、呼び出し側で引数として渡した変数の値そのものが書き換えられてしまいます。変数のアドレスが関数に渡されるため、その「アドレスに保持されている値=呼び出し側の変数の値そのもの」が処理の対象となるからです。
Visual Basicでは、前者のような引数の渡し方を「値渡し」、後者のような渡し方を「参照渡し」と呼んでいます ※1 。文字通り、前者は変数の値だけを関数に渡し、後者は変数の場所を参照する値(アドレス)を渡す──という形です。参照渡しを行うと、参照された側=呼び出し側の変数が処理の対象となります。
この表現は、ポインタという機能を持たないVisual Basicで、ByVal(値渡し)/ByRef(参照渡し)キーワードを用いた引数の渡し方に対して付けられたもので、Cでは「値渡し/参照渡し」という呼び方は用いません。ポインタの示すアドレスもまた値なので、引数として値を直接渡すか、ポインタを介して『変数のアドレスという値』を渡すか──という違いでしかないためです。本コラムでは、引数の扱われ方を分かりやすくするために、この表現を使いました
ポインタでアドレスを渡す
文字列の処理ではchar型の配列が対象となります。配列では個々の要素が持つ値だけを操作しても意味がありません。配列という「値のつながった形」にこそ意味があるからです。
そのため、文字列はポインタを使ってアドレスを関数に渡し、引数に設定した文字列そのものを処理しなければなりません。配列名は先頭要素のアドレスを持っているので、文字列を処理する関数では配列名を引数に設定することになります。先頭に&記号が付いていないので、一見するとアドレスを渡しているようには見えないだけです。
2つの値を入れ替える関数
参照渡しについて、もう少し説明しておきましょう。Cの入門書などでこの部分を解説するとき、よく例に用いられるのが2つの引数の値を入れ替えるswap関数です。ここでは、int型の値を処理するiswapという関数を考えてみましょう。
2つの変数(aとb)の保持する値を入れ替えるには、一時的に値を保存する同じ型の変数(tmp)を用意し、以下のような手順で代入を繰り返せば実現します。
1.tmpにaの値を代入
2.aにbの値を代入
3.bにtmp値を代入
(このときtmpは1.で代入されたaの値を保持している)
値だけが処理される
この処理をいわゆる値渡しで記述したのがリスト3です。以下のようなソースでこの関数を呼び出しても、変数xとyの値は変わりません。先述したように、iswap関数の中では呼び出し側の変数xとyの保持している『値だけ』が処理されているためです。
int x, y;
x = 10;
y = 100;
iswap(x, y); ---- xとyの値は入れ替わらない
リスト3:2つの引数の値を入れ替える関数
iswap(int a, int b)
{
int tmp;
tmp = a;
a = b;
b = tmp;
}
変数そのものを処理する
引数の持つ値を入れ替えるには、ポインタを使ったいわゆる参照渡しを用いてリスト4のように記述します。
int x, y;
x = 10;
y = 100;
swap(&x, &y);
関数の定義では仮引数をポインタとし、呼び出し側では引数xとyの前に&記号を付けてアドレスを渡します。これで、呼び出し側の変数xとyの値は入れ替わります。
単に数値データを計算するだけなら、値を渡すだけで用は済みます。しかし「変数の値を入れ替える」処理では、値を保持している『変数そのもの』を関数に渡さなければなりません。そのような場合に、引数をポインタで渡す仕組みを使います。
リスト4:2つの引数の値を入れ替える関数
iswap(int *a, int *b)
{
int tmp;
tmp = *a;
*a = *b;
*b = tmp;
}
|