2011年2月3日木曜日

[C/C++] 値渡しと参照渡し

以前に投稿したプログラムのように変数の値そのものを関数に渡すことを値渡しといいます.それに対して,変数が格納されているアドレスを関数に渡すことを参照渡しとしいます.
ここでは,2つの変数a, b の値を入れ替えるswap関数を作成することを考えながら,これらの違いを以下に示します.

単純に値の入れ替えを行うために作成したプログラム例を以下に示します.

#include <stdio.h>
void swap(double x, double y) ;

int main(void)
{
  double a = 1.5, b = 3.6 ;
  printf("a = %f \t b = %f \n", a, b) ;
  printf("Swap values of a and b \n") ;
  swap(a, b) ;

  printf("a = %f \t b = %f. ", a, b) ;
  return 0 ;
}

void swap(double x, double y)
{
  double tmp ;

  tmp = x ;
  x = y ;
  y = tmp ;

}

関数swapは値を入れ替えるだけで関数値を返す必要はないので,void型を利用して

void swap(double a, double b) ;

としています.このように値を返さない関数のことをvoid型関数と呼ぶときがあります.なお,void型関数は値を返さないので,以下のプログラム例にあるようにreturn文は書きません.

上記のプログラムをコンパイル,実行すると以下のようになります.
a = 1.500000  b = 3.600000 
Swap values of a and b 
a = 1.500000  b = 3.600000. 

a とb の値が入れ替わるプログラムを作成したにも関わらず,実行結果では値が入れ替わっていません.これは,swap関数の中(仮変数x, y)では値が入れ替わっているものの,その結果が実引数a, bに反映されていないためです(x, y, tmpはswap関数内のローカル変数).

変数aに値を代入するというのは,アドレスにある変数aに値を代入するということなので,値を入れ替えるには,アドレスを指定して変数がある場所を指定する必要があります.つまり,アドレスを関数に渡す必要があることになります.

変数を参照渡しにする場合は,実引数の方にアドレスを意味する"&"を付ける必要があります(scanf文で変数の前に&を付けるのも同じで, scanf("%d", &a) というのは,変数aの場所に値を書き込む指示).

関数の引数には変数の前に"*"を付ける必要があります.このように,変数の前に"*"が付いた変数,例えば,*x, *yとしたときのx, yをポインタ変数といいます.ポインタ変数はアドレスを記憶することができる変数です.

以下に,アドレス演算子とポインタ変数を使用して上記のプログラムを書き換えた例を示します.

#include <stdio.h>
void swap(double *x, double *y) ;

int main(void)
{
  double a = 1.5, b = 3.6 ;
  printf("a = %f \t b = %f \n", a, b) ;
  printf("Swap values of a and b \n") ;
  swap(&a, &b) ;

  printf("a = %f \t b = %f. \n", a, b) ;
  return 0 ;
}

void swap(double *x, double *y)
{
  double tmp ;

  tmp = *x ;
  *x = *y ;
  *y = tmp ;

}

上記のプログラムをコンパイル,実行すると以下のようになり,aとbの値が入れ替わっていることを確認できます.
a = 1.500000  b = 3.600000 
Swap values of a and b 
a = 3.600000  b = 1.500000. 

まとめると,

  • double a と宣言した場合のアドレスは &a で,値は a
  • double *a と宣言した場合のアドレスは a で,値は *a

となります.ちなみに,scanf文,printf文では以下のようになります.

<double aとした場合>
double a ;
scanf("%lf", &a) ;
printf("%f", a) ;

<double *aとした場合>
double *a ;
scanf("%lf", a) ;
printf("%f", *a) ;

aのアドレス&aをswap関数に渡すのであれば,swap関数の定義を swap( double &x, double &y ) としたくなるところですが,swap( double *x, double *y) としてポインタ変数を用いなければならないことに注意が必要です.

関数の再利用を考えた場合,お互いの関数はなるべく独立性が高い(相手に影響を与えない)ことが望まれます.しかし,参照渡しは,呼び出した関数の変数を書き換えることができるので,結果として関数の独立性を低くしてしまうので,参照渡しの関数を作る場合は注意が必要です.

0 件のコメント :

コメントを投稿