関数の利用,関数の定義の仕方は,理解できましたか。ココまでのところを,まずしっかりと理解するようにしてください。
今週は,第3回の最後の課題「(5)関数からは,数字(値)を一つだけしかmain関数に戻すことはできない。複数の数字や配列をmain関数に戻す方法を考え,答えなさい。」について学習する。
キーワード:「配列」「値参照」「アドレス参照」
関数には,配列を丸ごとを渡すことができる。次の例で,確認しよう。
#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);
}
さて,これで意図した結果が得られるだろうか?
これまで学習した関数の利用パターンでは,メイン関数から関数へ値を渡していた。これを値参照と呼ぶ。もう一度確認してみよう。
#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の値が渡される |
ここで,C言語の最初に習った二つの関数printfとscanfを観察してみよう。
printf("%d", n);
printf関数を実行すると,変数nの中身が画面に表示されるが,変数nは何も変化しなかった。
scanf("%d", &n);
scanf関数を実行すると,キーボードから入力された値が変数nに代入された。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]);
}
}