C言語の基礎 |
5-1 関数の構造
関数waの定義部分を次に示す。メイン(関数を呼び出す側)は、次のようになる。
使用する関数は、宣言しておく(上リストの青色部分)必要がある。これをプロトタイプ宣言という。これまで使用してきた、標準ライブラリについては、ヘッダファイル(*.h)内で宣言されている。
5-2 データの受け渡し
関数どうしのデータの受け渡しをどのようにするかが関数作成の大きなポイントになる。簡単な例でみてみよう。
(1) 引数と返却値のない場合
"Hello C World"と表示する関数を作成する。関数の名前は、helloとする。/* sample38.c */ #include <stdio.h> int main(void) { void hello(void); /* ← 使用する関数を宣言する */ hello(); /* ← 関数を呼び出す */ } void hello(void) { puts("Hello C World"); }
(2) 引数のみある場合
入力した2つの整数の積を求めて表示する関数を作成する。関数名は、sekiとする。/* sample39.c */ #include <stdio.h> int main(void) { void seki(int, int); /* ← 使用する関数の宣言 */ int x, y; printf("Data1="); scanf("%d",&x); /* ← データ入力1 */ printf("\nData2="); scanf("%d",&y); /* ← データ入力2 */ putchar('\n'); seki(x, y); } void seki(int a, int b) { printf("Data1*Data2=%d\n",a * b); /* ←積を表示 */ }今まで、キーボードから数値を入力する処理を上のデータ入力1のようにprintfでメッセージを表示してからscanfで入力してきた。
/* sample40.c */ #include <stdio.h> #include <stdlib.h> int main(void) { void seki(int, int); /* ← 使用する関数の宣言 */ int x, y; char buff[80]; printf("Data1="); gets(buff); /* ← データ入力1 */ x = atoi(buff); /* ← 文字列を数値に変換する */ printf("\nData2="); gets(buff); /* ← データ入力2 */ y = atoi(buff); /* ← 文字列を数値に変換する */ putchar('\n'); seki(x, y); }引数として文字列を関数に渡す場合を考えてみる。例として、文字列の文字の個数を計算し表示する関数を作成する。
/* sample41.c */ #include <stdio.h> int main(void) { void count(char *); char buff[80], *pb; pb = buff; /* ← ポインタ変数の初期化 */ printf("String data="); gets(buff); count(buff); count(pb); count(&buff[0]); }
/* type 1 */
void count(char *str)
{
int i = 0;
while(*str++)
++i;
printf("\n%d ko desu\n", i);
}
|
/* type 2 */
void count(char str[])
{
int i = 0;
while(str[i] != '\0')
++i;
printf("\n%d ko desu\n", i);
}
|
文字列データは、配列に格納した。したがって文字列リテラルを引数として渡すことも、配列を引数として渡すことも、どちらも同じ方法で行える。
文字列データを引数として渡すには、その文字列データの先頭アドレスを引数として渡す。文字列リテラルは、それ自身が先頭アドレスを表す。配列の場合は、配列名が先頭アドレスを表す。
文字列データの場合は、必ず終わりが ナル文字'\0'であるから文字数を渡す必要はない。ただし、その他の配列データの時はデータの終わりを表すデータを決めておくか、配列の個数も引数として渡してやる必要がある。 sample41.cの場合、先頭アドレスは、次の3つの方法で表すことができる。
どの方法でもかまわない。
- buff
- 配列名は配列の先頭アドレスを表す。
- pb
- ポインタは6行目で pb = buff; で初期化されている。
- &buff[0]
- 配列の0番目のアドレスをアドレス演算子を用いて計算している。
関数側ではメインから渡されるデータがアドレスであるから、type 1のようにポインタ変数で受ける。また、type 2のように配列としても受け取ることができる。
(3) 返却値が1つある場合
前回と同じように入力した2つの整数の積を求める関数を作成する。関数名は、sekiとする。/* sample42.c */ #include <stdio.h> #include <stdlib.h> int main(void) { int seki(int, int); /* ← 使用する関数の宣言 */ int x, y, ans; char buff[80]; printf("Data1="); gets(buff); /* ← データ入力1 */ x = atoi(buff); /* ← 文字列を数値に変換する */ printf("\nData2="); gets(buff); /* ← データ入力2 */ y = atoi(buff); /* ← 文字列を数値に変換する */ putchar('\n'); ans = seki(x, y); /* ← @ */ printf("Data1*Data2=%d\n",ans); } int seki(int a, int b) { int x; x = a * b; /* ← この2行を次のように1行にしても結果は同じである */ return(x); /* return(a * b); */ }関数sekiを呼び出した結果、値が返ってくるので、それを受け取る変数を用意しておく。この場合、変数ansに値が代入される。
return文は、メインに値を返すとともに処理をメインに戻す。したがって呼び出された関数の処理を条件によって途中で中断する場合にも、このreturn文を使う。
int rei(void) { int i, t = 0; for(i = 1; i <= 10; ++i){ if(i <= 5) return(t); t = t + i; } } |
forループは10回繰り返すようになっているが、ループ内のif文によって変数iが5になるとreturn文でそれまでの和をメインに返し、処理もメインに戻る。
(4) 返却値が2つ以上必要な場合
関数を呼び出した側に値を返すにはreturn文を使うが、このreturnは値を1つしか返すことができない。そこで、ポインタを使用する。関数にその変数のアドレスを渡す。関数では、そのアドレスに格納されているデータを直接処理するので、呼びだした側と同じ変数を使用することに同じになる。例えば、2つの整数の和と積を求める関数を作成してみる。
/* sample43.c */ #include <stdio.h> #include <stdlib.h> int main(void) { void waseki(int, int, int *, int *); /* ← 使用する関数の宣言 */ int x, y, wa, seki; char buff[80]; printf("Data1="); gets(buff); /* ← データ入力1 */ x = atoi(buff); /* ← 文字列を数値に変換する */ printf("\nData2="); gets(buff); /* ← データ入力2 */ y = atoi(buff); /* ← 文字列を数値に変換する */ putchar('\n'); waseki(x, y, &wa, &seki); /* ← @ */ printf("WA=%d\n",wa); printf("SEKI=%d\n",seki); } void waseki(int a, int b, int *w, int *s) { *w = a + b; *s = a * b; }
@のように2つのデータx, yと結果を入れてもらう変数のアドレスを関数に渡す。
関数mainで次のように変数のための領域が確保される。
例えば、x = 2, y = 4とデータを入力したとする。各アドレスの内容は次のようになる。waとsekiの内容は不定である。
次に、waseki(x, y, &wa, &seki)で関数に渡される値は実際には次のようになる。
waseki(2, 4, 18, 1C)
処理は関数に移り、関数内で使用する変数の領域も確保される。
変数wとsには変数waとsekiのアドレスが格納されている。 ポインタのところで学習したように、*w = a + b;の*wはポインタ変数wに格納されているアドレスの内容を表すから、結果的にa + bの答え6がアドレス18番地からに格納される。
関数wasekiの処理が終わると関数mainにもどり、変数a, b, w, sの領域は消えてしまうが、計算結果は、変数waとsekiに残っている。 このようにして、返却値が2つ以上必要な場合には関数にアドレスを渡すことで実現できる。
次の「6 データ構造」で説明するが、構造体を使うことで複数の返却値や引数を処理することもできる。
(5) 外部変数の利用
今まで、関数内で使用する変数は、関数内で宣言してきた。したがって、関数内で宣言した変数はその関数内でのみ有効となる。/* sample44.c */ #include <stdio.h> int x; /* ← グローバル変数 */ int main(void) { void test(int); /* ← 使用する関数の宣言 */ int a; a = 100; x = 1000; printf("main1 a=%d x=%d\n", a ,x); test(a); printf("main3 a=%d x=%d\n", a, x); } void test(int a) { a = 200; x = 2000; printf("test2 a=%d x=%d\n", a, x); }
main1 a=100 x=1000 |
先の例と同じように、2つの整数の和と積を求める関数を作成してみる。
/* sample45.c */ #include <stdio.h> #include <stdlib.h> int wa, seki; /* ← グローバル変数 */ int main(void) { void waseki(int, int); /* ← 使用する関数の宣言 */ int x, y; char buff[80]; printf("Data1="); gets(buff); /* ← データ入力1 */ x = atoi(buff); /* ← 文字列を数値に変換する */ printf("\nData2="); gets(buff); /* ← データ入力2 */ y = atoi(buff); /* ← 文字列を数値に変換する */ putchar('\n'); seki(x, y); printf("WA=%d\n",wa); printf("SEKI=%d\n",seki); } void waseki(int a, int b) { wa = a + b; seki = a * b; }
関数の内で宣言されている変数は、その関数内だけで有効であるから、グローバル変数に対してローカル変数という。
Copyright © 2001-2003 Hiroshi Masuda |