第5回:関数(2) (6/1)


今週は,関数のつづきです。関数を呼び出す側と,呼び出される側とに分けて考えていきましょう。

キーワード:「関数」「引数」「戻り値」「値参照」「アドレス参照」


関数呼び出し

第3回の演習課題3を見直そう。問題は,

    int a=2, b=3;
    int *pa, *pb;
    
    pa = &a;
    pb = &b;

のとき,次の二つの演算の違いを説明しなさい。

    
    a = b;
    
    pa = pb;
    

であった。 ここでは,二つの命令をメモリのイメージ図で説明する。

ポインタのコピー

プログラムで,確認してみること。

    int a=2, b=3;
    int *pa, *pb;
    
    pa = &a;
    pb = &b;

    pa = pb;

    printf("a = %d¥n", a);
    printf("*pa = %d¥n", *pa);
    

次に前回の課題(4)について確認しよう。前提となるのは,関数へ渡せるのは,「数字」のみである。

(4)これまで使ってきたprintf()関数で,配列を関数に渡す方法はすでに学んでいた。アドレスとポインタ変数を参考に,関数へ配列に入ったデータをまとめて渡す方法を考え,答えなさい。

void main()
{
	char a[]="abc"; 
	printf("%s", a);   /* 関数へ,文字列(文字の配列)を渡している。ここで渡しているのは何でしょう? */
} 

相手に,配列を受け渡すためには,二つの方法が考えられる。

1.配列の中身を一つ一つ渡す → あまり,配列を使うありがたみがない!

2.配列のデータがしまわれている場所を教える → これだ!!

printf()関数へ文字列(文字の配列)を受け渡すには,文字列の先頭のアドレスを教えてあげれば良い。


関数へ,データを受け渡す方法には,このように,

・データそのものを渡す : a = b のイメージ

・アドレスを教える : pa = pb のイメージ

の二つの方法がある。前者を「値参照」,後者を「アドレス参照」と呼び,適宜使い分ける。


呼び出される側

呼び出す側の都合がわかったところで,呼び出される関数では,どうなるのか考える。

値を渡されて呼び出される場合の例は,前回演習した。

#include <stdio.h>
int test(int c)
{
    c *= c; /* この関数内で値を変更 */
    return c;
}

void main()
{
    int a, b;

    printf("aの値を入力してください:"); scanf("%d", &a);

    b = test(a);

    printf("結果 %d ¥n", b);
}

値参照による関数の呼び出しでは,呼び出し元から関数へ変数の値がコピー,複製されて渡される。

値参照

 


配列を関数に受け渡したい場合などに用いる,アドレス参照では,関数に変数の格納されている「場所」を伝え, その「場所」のデータを関数内で操作するようにする。変数の格納されている場所,即ちアドレスを扱うためには, ポインタ変数を利用する。

#include <stdio.h>
int test2(int *p)        /*  変数のアドレスを受け取る(ポインタ変数)  */
{
    *p *= *p;             /*  p の指す中身に変更を加える */
    return *p;            /*  値を返す  */
}

void main()
{
    int a, b;
    
    printf("aの値を入力してください:"); scanf("%d", &a);

    b = test2(&a);        /* 変数aのアドレスを関数に渡す */
    
    printf("結果 %d ¥n", b);
}

アドレス参照


配列の受け渡し

ポインタを用いて,関数に変数のアドレスを渡せることがわかった。では,同じ方法を使って,関数に配列を渡してみよう。

以下は,配列を受け渡しする典型的な例である。配列名には,配列の先頭のアドレスが格納されているので,ポインタで受け取ることができる。

配列を渡す場合は,配列の先頭アドレスだけではなく,配列の要素数を同時に渡すと,どのような要素数の配列が渡されても,関数内でうまく処理ができる。 (アドレスのみだと,受け取った関数内で配列の個数が分からない.)


#include <stdio.h>
 
void zero_init(int *p, int n)       /*  要素数 n 個,配列の先頭のアドレス p を引数とする関数  */
{
    int i;
 
    for(i = 0; i < n; i++) {
        *(p+i) = 0;     /*  p[i] = 0; という書き方も可 */
    }
    
    return;
}
 
void main()
{
    int i;
    int  ary[] = {10, 20, 30, 40, 50};
 
    zero_init(ary, 5);
 
    for(i =0; i < 5; i++){
        printf("ary[%d] = %d¥n", i, ary[i]);
    }
}
 

このプログラムは,配列 ary の先頭から n 個の要素の値を 0 に設定する関数で,配列の初期化などの際に用いる。 関数には,配列の先頭要素のアドレス(ary)が渡されるので,関数側ではポインタ(int *p)で受け取っている。 この例では関数に戻り値が無いが,関数でのデータ変更は,main 関数においても反映されている。


演習課題

(1)長方形の二辺の長さ(整数)を渡すと,面積を計算して戻す関数を作成し,キーボードから二辺の長さを入力すると,面積を計算するプログラムを作成せよ.


**** rectarea(***********) /* 面積の計算 */
{




    return ***;
}

void main()
{
    int a, b;

    scanf("%d", &a);
    scanf("%d", &b);

    printf("面積:%d¥n", rectarea(a,b));
}

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


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

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

    return sum;
}

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

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

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