第5回:関数(3) (5/9)

第3,4回演習:解答例


関数の利用,関数の定義の仕方は,理解できましたか。ココまでのところを,まずしっかりと理解するようにしてください。
今週は,第3回の最後の課題「(5)関数からは,数字(値)を一つだけしかmain関数に戻すことはできない。複数の数字や配列をmain関数に戻す方法を考え,答えなさい。」について学習する。

キーワード:「配列」「値参照」「アドレス参照」


関数に配列を渡す

関数には,配列を丸ごとを渡すことができる。次の例で,確認しよう。

例:元になる10個の整数の和を求めるプログラム


#include <stdio.h>

void main()
{
    /* 変数定義 */
    int a[10] = {5,2,3,4,9,6,7,8,1,10};         /* 宣言と同時に初期化 */
    int i, sum;

    /* 初期化 */
    sum = 0;

    /* 処理 */
    for(i=0; i<10; i++){
        sum += a[i];        /* 配列の参照 */
    }

    /* 出力 */
    printf("合計 = %d ¥n", sum);
}

(関数版)


#include <stdio.h>

int sum(int b[], int n) /* 配列とデータ数を受け取り,和を返す関数 */
{
    int i, sum=0;

    for(i=0; i<n; i++){
        sum += b[i];
    }

    return sum;
}

void main()
{
    /* 変数定義 */
    int a[10] = {5,2,3,4,9,6,7,8,1,10};         /* 宣言と同時に初期化 */

    /* 出力 */
    printf("合計 = %d ¥n", sum(a, 10));
}

関数の定義部分で配列を受け取る際に,変数の定義の場合と異なり,配列の要素数を特定しない。 関数内では,特に意識することなく配列を操作できる。

 


複数の値を関数から戻したい または 関数で行った変更をメイン関数に伝えたい。

ココで問題です。二つの整数を渡すと,その和と積を答える関数を作成しなさい。

#include <stdio.h>

void sum_product(int c, int d, int wa, int seki) /* 戻り値ではだめなので */
{
    wa = c + d;
    seki = c * d;
}

void main()
{
    /* 変数定義 */
    int a=2, b=3; /* 宣言と同時に初期化 */
    int wa=0, seki=1;

    /* 関数の呼出 */
    sum_product(a, b, wa, seki);

    /* 出力 */
    printf("和 = %d ¥n", wa);
    printf("積 = %d ¥n", seki);
}

さて,これで意図した結果が得られるだろうか?


値渡し(printf関数のパターン)

これまで学習した関数の利用パターンでは,メイン関数から関数へを渡していた。これを値参照と呼ぶ。もう一度確認してみよう。

#include <stdio.h>

void sum_product(int c, int d, int wa, int seki) /* 戻り値ではだめなので */
{
    wa = c + d;
    seki = c * d;
}

void main()
{
    /* 変数定義 */
    int a=2, b=3; /* 宣言と同時に初期化 */
    int wa=0, seki=1;

    /* 関数の呼出 */
    sum_product(a, b, wa, seki);

    /* 出力 */
    printf("和 = %d ¥n", wa);
    printf("積 = %d ¥n", seki);
}

値参照

値参照による関数の呼び出しでは,呼び出し元から関数へ変数の値がコピー,複製されて渡される。
値参照では,先週学んだとおりmain関数とsum_product関数では変数名が同じ「wa」や「seki」でも関数の中でいくら変更を加えても,メイン関数と関数とでは別の箱を操作しているので,その結果は一方通行だった。

メモリー内のイメージ
  メモリーの番地例(アドレス) 最初の値
main関数のa
01
2
main関数のb
02
3
main関数のwa
03
0
main関数のseki
04
1
sum_product関数のc
05
main関数のaの値が渡される
sum_product関数のd
06
main関数のbの値が渡される
sum_product関数のwa
07
main関数のwaの値が渡される
sum_product関数のseki
08
main関数のsekiの値が渡される

 


printf関数とscanf関数

ここで,C言語の最初に習った二つの関数printfとscanfを観察してみよう。

printf("%d", n);

printf関数を実行すると,変数nの中身が画面に表示されるが,変数nは何も変化しなかった

scanf("%d", &n);

scanf関数を実行すると,キーボードから入力された値が変数nに代入された。scanf関数は戻り値は使っていない。

この二つの関数の違いは,どういうとだろう?


アドレス渡し(scanf関数のパターン)

scanf関数は,どうして関数での変更がメイン関数に伝わるのだろう。秘密は,関数呼び出し時に変数名につける「&」である。変数名の前に「&」をつけると,その変数が利用しているメモリの番地(アドレス)を相手側に教えることができる。受け取る関数側も,場所を教えてもらうため変数名の前に「*」をつけて扱う。この方法をアドレス参照と呼ぶ。

#include <stdio.h>

void sum_product(int c, int d, int *wa, int *seki) /* アドレスで受け取る変数にはをつける */
{
    *wa = c + d;
    *seki = c * d;
}

void main()
{
    /* 変数定義 */
    int a=2, b=3; /* 宣言と同時に初期化 */
    int wa=0, seki=1;

    /* 関数の呼出 */
    sum_product(a, b, &wa, &seki);  /* wa, seki に答えを入れてほしいので,場所を教える */

    /* 出力 */
    printf("和 = %d ¥n", wa);
    printf("積 = %d ¥n", seki);
}

値参照

アドレス参照による関数の呼び出しでは,呼び出し元から関数へ変数のしまわれている場所が渡される。値の隠し場所を連絡したので,関数側でその場所を変更することで,メイン関数へ戻ると値が変更されている。

メモリー内のイメージ
  メモリーの番地例(アドレス) 最初の値
main関数のa
01
2
main関数のb
02
3
main関数のwa,
sum_product関数の*wa
03
0
main関数のseki,
sum_product関数の*seki
04
1
sum_product関数のc
05
main関数のaの値が渡される
sum_product関数のd
06
main関数のbの値が渡される

(参考)sum_product関数での関数名は,wa, seki である必要はない。下記の関数定義でも同じ。

void sum_product(int c, int d, int *sum, int *product) /* アドレスで受け取る変数にはをつける */
{
    *sum = c + d;
    *product = c * d;
}

先週のプログラムを少し変更した以下のプログラムで,どのような結果が得られるか,考えなさい.


#include <stdio.h>
 
void funca(int *x)
{
	printf("funca_xの初期値:%d¥n", *x);

	*x += 5;
	
	printf("funca_xの結果:%d¥n", *x);
}
 
void main()
{
	int x = 3;
	
	printf("main_xの初期値:%d¥n",x); /*  main内のxの初期値の表示  */
	
	funca(&x);
	
	printf("main_xの結果:%d¥n",x);	/*  funca実行後のmain内のxの値の表示  */
}

画面に表示される結果

main_xの初期値:3
funca_xの初期値:?
funca_xの結果:?
main_xの結果:?

それぞれなんと表示されるだろう。まずは考えて見て,次にプログラムを実行して確認すること。


演習課題

(1)配列を渡すと,配列の平均値を計算する関数を用いて,実数をキーボードから10個入力すると,平均値を答えるプログラムを作成せよ。

#include <stdio.h>

float average(float $$$$$, int n)  /* 平均の計算 */
{
    int i;
    float sum = 0;

    for(i=0;i<n;i++){
        sum += $$$$$;
    }

    return sum / n;
}

void main()
{
    int i;
    float data[10];	

    for(i=0; i<10; i++){
        scanf("%f", &(data[i]));
    }

    printf("平均値:%f¥n", average($$$$$, 10));
}

(2)長方形の幅と高さを入力すると,面積と外周を求める関数.
ヒント:void area_out(float width, float height, float *area, float *out)...

(3)運動時の加速度と時間を入力すると,速度と距離を求める関数.
ヒント:void v_s(float a, float t, float *v, float *s)...

(4)抵抗と電流を入力すると,電圧と電力を求める関数.
ヒント:void v_p(float r, float i, float *v, float *p)...

(5)関数内で配列に変更を加えるとどうなるか,確認する。

#include <stdio.h>

void init(int b[], int n) /* 配列とデータ数を受け取り,配列を0で初期化する関数 */
{
    int i;

    for(i=0; i<n; i++){
        b[i] = 0;
    }
}

void main()
{
    /* 変数定義 */
    int i;
    int a[10] = {5,2,3,4,9,6,7,8,1,10};

    /* 関数呼び出し */
    init(a, 10);

    /* 出力 */
    for(i=0; i<10; i++){
        printf("%d番目の値:%d¥n", i+1, a[i]);
    }
}