Linux講座にようこそ。このページは「C言語プログラミング入門 - 第9章.プログラムの部品化のための関数」です。

C言語プログラミング入門

9. プログラムの部品化のための関数(2/3)

9.3 関数間のデータの受け渡し

関数間のデータの受け渡しは前のページで簡単に説明した通り、通常、引数と返り値を使用します。このうち、引数を使ったデータの受け渡しについては2つの方法が有ります。ここでは主に、引数を使ったデータの受け渡しについて説明します。

9.3.1 値をコピーして渡す

値渡し(call by value)
【図9-2】値渡し(call by value)

引数を使ったデータの受け渡しの1つ目は、図9-2に示す値渡し(call by value)と呼ばれている方法です。前ページの例題プログラムは全て値渡しを使っています。

main関数でfunc関数を呼び出していますが、実引数として、変数para1とpara2を指定しています。そうすると、この2つの変数の値がfunc関数の仮引数用の変数arg1とarg2にコピーされます。これにより、変数para1とpara2の値が変数arg1とarg2に渡っていったことになります。

この方法の利点は実引数と仮引数がまったく別の変数になりますので、func関数で仮引数の変数arg1の値を変更しても、main関数の実引数の変数para1の値は影響を受けません。他の関数からの影響を少なくできますので、変数の誤操作による不良が少なくなる可能性が有ります。ただし、func関数からmain関数に引数を使って値を返すことは出来ませんので、値を返したい場合は返り値を使うしか方法はありません。

返り値はreturn文で簡単に返せますが、返せる値は1つだけです。

9.3.2 値をポインタで渡す

参照渡し(call by reference)
【図9-3】参照渡し(call by reference)

引数を使ったデータの受け渡しの2つ目は、図9-3に示す参照渡し(call by reference)と呼ばれている方法です。

func関数の呼び出しで実引数として変数para1とpara2の先頭アドレスを指定しています。(アドレス演算子(&)を使っていることに注目してください。)

func(&para1, &para2);

一方、func関数の仮引数の変数arg1とarg2はポインタ変数として宣言しています。

int func(int *arg1, int *arg2)

これにより、実引数の変数para1とpara2の先頭アドレスが仮引数の変数arg1とarg2にコピーされて、間接的に変数para1とpara2の値がfunc関数に渡っていったことになります。func関数での変数para1とpara2の値の操作は間接参照演算子(*)を使って、次のように行います。

v1 = *arg1; ← 参照(変数para1の値を変数v1に代入します)
*arg1 = *arg2 + 10; ← 更新(変数para2の値に10を加算した結果を変数para1に代入します)

参照渡しの場合、実引数の参照だけでなく更新も行うことが出来ます。更新が出来るということは呼出し元に値を返すことが出来るということになります。参照渡しの利点は引数を使って複数の値を呼び出し元に返すことが出来るということです。ただし、変数の誤操作による不良が発生する可能性が有りますので、注意して使ってください。

参照渡しは例題プログラムで既に使っています。scanf関数で入力する場合、入力データを受け取る変数(実引数)はアドレス演算子を指定していましたが、これはscanf関数からデータを返してもらうために参照渡しをしているためです。

9.3.3 配列の受け渡し

配列の受け渡し
【図9-4】配列の受け渡し

配列名はその配列の先頭アドレスを値としてもつポインタの定数ですので、実引数に配列名を指定すると、参照渡しになります。従って、呼び出された関数から呼び出し元の配列の更新が出来ます。

図9-4ではmain関数に配列a_paraが有り、func関数の呼び出しで実引数に配列名のa_paraを指定しています。

func(a_para); ← a_paraは配列です

これにより、配列a_paraの先頭アドレスがfunc関数の仮引数a_argにコピーされます。a_argは配列として宣言していますので、添字を使って操作が可能です。

v1 = a_arg[1]; ← 参照(配列a_paraの2番目の要素の値を変数v1に代入します)
a_arg[2] = v1; ← 更新(変数v1の値を配列a_paraの3番目の要素に代入します)

また、間接参照演算子(*)を使って、参照・更新を行うこともできます。(こちらの方法を使う場合は仮引数の宣言もポインタで行った方がプログラムが理解しやすくなるでしょう)

v1 = *(a_arg + 1); ← 参照(配列a_paraの2番目の要素の値を変数v1に代入します)
*(a_arg + 2) = v1; ← 更新(変数v1の値を配列a_paraの3番目の要素に代入します)

なお、実引数に配列の1要素だけを指定する場合は値渡しになります。ちょっとした違いですがインターフェース上は大きく異なりますので注意して下さい。

/* main関数のfunc関数の呼出し*/
func(a_para[0]); ← 実引数として配列a_paraの先頭要素の値を指定しています

/* func関数の定義 */
int func(int arg1) ← 実引数の値をint型の変数として受け取ります

9.3.4 例題

例題1

肥満度(BMI)と、その値の範囲が正常か否かを出力するプログラムです。CalcBmi関数はdouble型の体重と身長を引数として受け取り、肥満度を返り値として返すと共に、正常か否かを引数で返します。

  1. #include <stdio.h>
  2. int main(void)
  3. {
  4.     double  weight;
  5.     double  height;
  6.     double  bmi;
  7.     char    judge;
  8.     int     return_code;
  9.     /* CalcBmi関数のプロトタイプ宣言 */
  10.     double CalcBmi(double BmiWeight, double BmiHeifht, char *BmiJudge);
  11.  
  12.     printf("肥満度(BMI)の計算を行います\n");
  13.     printf("体重(kg)と身長(cm)を入力してください ==> ");
  14.     scanf("%lf%lf", &weight, &height);
  15.  
  16.     bmi = CalcBmi(weight, height, &judge);     /* 肥満度(BMI)を返り値として取得 */
  17.     if(bmi > 0.0)
  18.     {
  19.         printf("肥満度は%.2fです。", bmi);
  20.         switch(judge)
  21.         {
  22.             case 'n':
  23.                 printf("正常です。\n");
  24.                 break;
  25.             case 'l':
  26.                 printf("低すぎます。\n");
  27.                 break;
  28.             case 'h':
  29.                 printf("高すぎます。\n");
  30.                 break;
  31.             default:
  32.                 printf("\n");
  33.         }
  34.         return_code = 0;
  35.     }
  36.     else
  37.     {
  38.         printf("入力した値が不当です\n");
  39.         return_code = 1;
  40.     }
  41.  
  42.     return return_code;
  43. }
  44.  
  45. /* 体重と身長を元に肥満度(BMI)の計算を行iい、正常かどうかの判定を行う*/
  46. double CalcBmi(double pWeight, double pHeight, char *pJudge)
  47. {
  48.     double  bmi;
  49.  
  50.     /* 体重と身長の値をチェック */
  51.     if (pWeight > 0.0 && pHeight > 0.0)
  52.     {
  53.         /* 身長をセンチメートルからメートルに変換 */
  54.         pHeight = pHeight / 100.0;
  55.         /* 肥満度(BMI) = 体重(kg) / 身長(m) / 身長(m) */
  56.         bmi = pWeight / pHeight / pHeight;
  57.         /* 肥満度が18.5〜25.5の範囲であれば正常 */
  58.         if(bmi >= 18.5 && bmi <= 25.0)
  59.         {
  60.             *pJudge = 'n';             /* 正常 */
  61.         }
  62.         else
  63.         {
  64.             if(bmi > 25.0)
  65.             {
  66.                 *pJudge = 'h';         /* 高い */
  67.             }
  68.             else
  69.             {
  70.                 *pJudge = 'l';         /* 低い */
  71.             }
  72.         }
  73.     }
  74.     else
  75.     {
  76.         bmi = 0.0;
  77.         *pJudge = ' ';
  78.     }
  79.  
  80.     return bmi;                        /* 肥満度(BMI)を返り値として返す */
  81. }
$ ./ex09_4.prg
肥満度(BMI)の計算を行います
体重(kg)と身長(cm)を入力してください ==> 71.5 174.5
肥満度は23.48です。正常です。
$
$ ./ex09_4.prg
肥満度(BMI)の計算を行います
体重(kg)と身長(cm)を入力してください ==> 71.5 163.5
肥満度は26.75です。高すぎます。
$
10行目
CalcBmi関数の第3引数をポインタとして宣言します。つまり、参照渡しになります。
16行目
CalcBmi関数の第3引数は参照渡しですので、変数judgeにアドレス演算子(&)を付けることにより、変数judgeの先頭アドレスを指定します。
20行目
CalcBmi関数により、変数judgeに肥満度の値が正常か否かが文字として設定されていますので、その文字により結果を出力します。(変数judgeの値が'n'は正常、'l'は低すぎる、'h'は高すぎることを表します)
46行目
CalcBmi関数の第3引数は参照渡しですので、ポインタとして宣言します。
60、66、70行目
第3引数のpJudgeに間接参照演算子(*)を付けることにより、呼び出し元の変数judgeに肥満度の値が正常か否かの文字を設定します。

例題2

入力した半角文字列をStrRev関数で逆順にして出力します。StrRev関数は引数で指定した文字列を逆順にして返すと共に、文字数を返り値として返します。(入力文字は半角で100文字以下とし、スペースやタブ等の空白文字は含みません)

  1. #include <stdio.h>
  2. int main(void)
  3. {
  4.     char    a_rev[101];
  5.     int     length;
  6.     int     return_code;
  7.     /* StrRev関数のプロトタイプ宣言 */
  8.     int StrRev(char Rev[]);
  9.  
  10.     printf("文字列を入力してください ==> ");
  11.     scanf("%s", a_rev);
  12.  
  13.     length = StrRev(a_rev);
  14.     if(length > 0)
  15.     {
  16.         printf("逆順:%s\n", a_rev);
  17.         return_code = 0;
  18.     }
  19.     else
  20.     {
  21.         printf("逆順にできません\n");
  22.         return_code = 1;
  23.     }
  24.  
  25.     return return_code;
  26. }
  27.  
  28. /* 引数の文字列を逆順にして、文字数を返す */
  29. int StrRev(char pRev[])
  30. {
  31.     int     cur_index;
  32.     int     rev_index;
  33.     int     length;
  34.     char    save_char;
  35.  
  36.     /* 文字列の最後の文字を探す */
  37.     for(rev_index = 0;
  38.         pRev[rev_index] != '\0' && rev_index <= 100;
  39.         ++rev_index)
  40.         ;
  41.     if(rev_index >= 1 && rev_index <= 100)
  42.     {
  43.         length = rev_index;            /* 文字数設定 */
  44.         --rev_index;
  45.         /* 文字の入れ替え */
  46.         for(cur_index = 0; cur_index < rev_index; ++cur_index)
  47.         {
  48.             save_char = pRev[cur_index];
  49.             pRev[cur_index] = pRev[rev_index];
  50.             pRev[rev_index] = save_char;
  51.  
  52.             --rev_index;
  53.         }
  54.     }
  55.     else
  56.     {
  57.         length = 0;
  58.     }
  59.  
  60.     return length;                     /* 文字数を返り値として返す */
  61. }
$ ./ex09_5.prg
文字列を入力してください ==> HelloWorld.
逆順:.dlroWolleH
$
8行目
StrRev関数のプロトタイプ宣言です。引数はchar型の配列(文字列)です
13行目
実引数に入力した文字列(配列a_rev)を指定してStrRev関数を呼び出します。逆順にした文字列は実引数に指定した配列a_revに返り、返り値として文字数が返ります。
29行目
StrRev関数の仮引数は配列で、返り値はint型です。
38行目
文字列の最後までカウントしていきます。文字数は最大100文字とします。
48〜50行目
文字列を逆順にするために、後ろの文字と入れ替えます。

例題3

例題2と同じ機能ですが、StrRev関数の文字列を取り扱う部分をポインタで記述しました。

  1. #include <stdio.h>
  2. int main(void)
  3. {
  4.     char    a_rev[101];
  5.     int     length;
  6.     int     return_code;
  7.     /* StrRev関数のプロトタイプ宣言 */
  8.     int StrRev(char Rev[]);
  9.  
  10.     printf("文字列を入力してください ==> ");
  11.     scanf("%s", a_rev);
  12.  
  13.     length = StrRev(a_rev);
  14.     if(length > 0)
  15.     {
  16.         printf("逆順:%s\n", a_rev);
  17.         return_code = 0;
  18.     }
  19.     else
  20.     {
  21.         printf("逆順にできません\n");
  22.         return_code = 1;
  23.     }
  24.  
  25.     return return_code;
  26. }
  27.  
  28. /* 引数の文字列を逆順にして、文字数を返す */
  29. int StrRev(char *pRev)
  30. {
  31.     char    *p_cur;
  32.     char    *p_rev;
  33.     int     length = 0;
  34.     char    save_char;
  35.  
  36.     /* 文字列の最後の文字を探す */
  37.     for(p_rev = pRev;
  38.         *p_rev != '\0' && p_rev <= (pRev + 100);
  39.         ++p_rev)
  40.     {
  41.         ++length;                      /* 文字数カウント */
  42.     }
  43.     if(p_rev > pRev && p_rev <= (pRev + 100))
  44.     {
  45.         --p_rev;
  46.         /* 文字の入れ替え */
  47.         for(p_cur = pRev; p_cur < p_rev; ++p_cur)
  48.         {
  49.             save_char = *p_cur;
  50.             *p_cur = *p_rev;
  51.             *p_rev = save_char;
  52.  
  53.             --p_rev;
  54.         }
  55.     }
  56.     else
  57.     {
  58.         length = 0;
  59.     }
  60.  
  61.     return length;                     /* 文字数を返り値として返す */
  62. }
29行目
仮引数をchar型のポインタで宣言します。
37行目
添字の代わりにポインタを使います。以降、すべてポインタの演算を使います。