1 C言語入門 第8週 プログラミング言語Ⅰ(実習を含む。), 計算機言語Ⅰ・計算機言語演習Ⅰ, 情報処理言語Ⅰ(実習を含む。) 2 関数定義と関数呼び出し 3 教科書 pp.149-160. 関数の宣言・定義・利用 • 書式: 関数の宣言 戻り値の型 関数名(引数の宣言, ...); .h ファイルへ書き出す 関数の定義 戻り値の型 関数名(引数の宣言, ...) { // 関数に行わせる処理 // ... return 戻り値; // 戻り値の型がvoidの場合は不要 } .c ファイルへ書き出す 関数の利用 変数名 = 関数名(引数, ...); 適宜呼び出す 4 教科書 pp.149-160. 関数(サブルーチン) • C 言語は処理を関数にしてまとめる • main も関数 コマンドライン引数の数 c_template.c #include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { // Here is a main routine. コマンドライン引数を格納した 複数の文字列へのポインタ 関数名、引数、戻り値の定義 関数の本体 return EXIT_SUCCESS; } フルセットの main 関数 5 教科書 pp.149-160. (ユーザー定義)関数 • 関数は自分で作ることが出来る • 機能、分量等、ある程度まとまった処理は 関数にまとめる • 可読性が向上する • 再利用が容易になる • 関数の定義の書式: C言語の関数は 0個以上の引数と 0または1個の戻り値を持つ。 引数や戻り値が不要な場合は 戻り値の型 関数名(引数の宣言, ...) void を宣言するよう { 推奨されている。 // 関数に行わせる処理 // ... return 戻り値; // 戻り値の型がvoidの場合は不要 } return 文 return 戻り値; • 関数から抜けて呼び出し元へ戻り値を渡す return_ex1.c int sub() { return 123; printf("hello\n"); } return文を実行すると 呼び出し元に戻るので それ以降の文は 実行されない void main() { int x = sub(); printf("%d\n", x); } return文に与えた値が 関数の戻り値になり 演算に使用される mintty + bash + GNU C $ gcc return_ex1.c && ./a 123 return 文 • 戻り値の型は必要に応じて適切な型を選ぶ return_ex2.c 例えばもし 浮動小数点数の値を 戻したいなら 戻り値の型は doubleでないといけない int sub() { return 1.23; } この例では戻り値の型はintなので return文にdoubleを与えても 戻り値はint型にキャストされるので 整数になる void main() { double x = sub(); printf("%f\n", x); } 訂正2015-06-26 誤: double = sub(); 正: double x = sub(); mintty + bash + GNU C $ gcc return_ex2.c && ./a 1.000000 関数のプロトタイプ宣言 • 関数は使う前に宣言されている必要がある prototype_ex1_err.c void main() { double x = sub(); printf("%f\n", x); } double sub() { return 1.23; } 未宣言の関数の引数や戻り値は int型を仮定して暗黙的に宣言される 異なる引数や戻り値で同名の関数は 関数名が競合するため利用出来ない mintty + bash + GNU C $ gcc prototype_ex1_err.c prototype_ex1_err.c:9:8: エラー: ‘sub’ と型が競合しています double sub() ^ prototype_ex1_err.c:5:14: 備考: 前の ‘sub’ の暗黙的な宣言はここです double x = sub(); ^ 関数のプロトタイプ宣言 • 関数は使う前に宣言されている必要がある prototype_ex1.c double sub(); void main() { double x = sub(); printf("%f\n", x); } double sub() { return 1.23; } int型以外の引数や戻り値を持つ関数は あらかじめプロトタイプ宣言されていなければならない mintty + bash + GNU C $ gcc prototype_ex1.c && ./a 1.230000 10 教科書 pp.149-160. (ユーザー定義)関数の例 • 閏年の判定を関数にまとめてみる leap_year_func_ex4_2.c #include <stdio.h> #include <stdlib.h> int is_leap_year(int year) { return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0; } int main() { int year; fprintf(stderr, "year = ?\b"); scanf("%d", &year); 関数の定義 自分で作成した関数を サブルーチンとして呼ぶ printf("%d is%s leap year.\n", year, is_leap_year(year) ? "" : " not"); return EXIT_SUCCESS; } 11 教科書 pp.153-160. (ユーザー定義)関数の例 • 関数を使う前にはパラメータの型と数が 分かっている必要がある(コンパイラの都合) #include <stdio.h> #include <stdlib.h> int main() { int year; fprintf(stderr, "year = ?\b"); scanf("%d", &year); 使う時点で不明な戻り値の型はintを仮定される。 引数については何も仮定しない。 もし、実際に定義されている関数の型が異なると 型が競合してエラーになる。 printf("%d is%s leap year.\n", year, is_leap_year(year) ? "" : " not"); return EXIT_SUCCESS; } 型が不明なので、このタイミングで int is_leap_year(int year) int is_leap_year(int); が仮定される。 { return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0; } [1] pp.86-89. 12 教科書 pp.149-160. 関数のプロトタイプ宣言 • 関数は使う前にプロトタイプ宣言する #include <stdio.h> #include <stdlib.h> int is_leap_year(int year); 関数のプロトタイプ宣言 戻り値の型、引数の数と型を宣言する int main() { int year; fprintf(stderr, "year = ?\b"); scanf("%d", &year); 関数のプロトタイプ宣言さえしておけば 関数の定義はどこにあっても良い。 例えば外部のファイルにあっても良い。 printf("%d is%s leap year.\n", year, is_leap_year(year) ? "" : " not"); return EXIT_SUCCESS; } 13 教科書 pp.203-206. 関数をファイルに分離する • 再利用性が高まる(コピペしなくて良くなる) is_leap_year_func.h ヘッダファイル #ifndef IS_LEAP_YEAR_FUNC_H #define IS_LEAP_YEAR_FUNC_H include ガード(二重includeを防ぐ) int is_leap_year(int year); #endif 関数の宣言 他所から使う際に、 関数名、引数、戻り値の 情報を得るために必要 is_leap_year_func_ex4_2.c 関数本体のソースファイル #include "is_leap_year_func.h" 関数本体の定義 int is_leap_year(int year) { return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0; } 14 教科書 pp.203-206. ファイルに分離した関数の利用 • main 関数の部分 is_leap_year_main.c #include <stdio.h> #include <stdlib.h> #include "is_leap_year_func.h" int main() { int year; fprintf(stderr, "year = ?\b"); scanf("%d", &year); ヘッダファイルの include 外部ファイルで定義した 関数を呼び出し printf("%d is%s leap year.\n", year, is_leap_year(year) ? "" : " not"); return EXIT_SUCCESS; } 15 分割コンパイル • コンパイルする複数のCのソースファイルを コンパイラに与えれば良い mintty + bash + GNU C $ gcc is_leap_year_main.c is_leap_year_func_ex4_2.c コマンドプロンプト + Borland C++ >bcc32 is_leap_year_main.c is_leap_year_func_ex4_2.c 与えるのはCのソースファイルのみ ヘッダファイルは #include 文により 自動的に取り込まれる 16 プリプロセッサ • 条件付きコンパイル #if 定数式 #elif 定数式 #else #endif #if 0 // ここはコンパイルされない #endif 条件に合った箇所のみを コンパイラに渡す構文 定数式の結果は0と非0の真偽値 #if defined(__BORLANDC__) // __BORLANDC__ という名前のマクロが // 定義されていた場合のみコンパイルされる #endif #if defined(__BORLANDC__) && 0x0551 <= __BORLANDC__ // ... #endif 定数式なら 演算も可能 17 プリプロセッサ • 条件付きコンパイル defined( ) の短縮表記 #ifdef __BORLANDC__ // ... #endif #if #ifndef __BORLANDC__ // ... #endif #if !defined( ) の短縮表記 18 プリプロセッサ • 定義済みマクロ __LINE__ __FILE__ __func__ __FUNC__ 現在の行番号 現在のファイル名 現在の関数名(C99) 現在の関数名(Borland C++) #ifdef __BORLANDC__ #define __func__ __FUNC__ #endif Borland C++ で C99互換の定義済みマクロが 使えるようにする 条件付きコンパイル 19 教科書 pp.203-206. 関数をファイルに分離する • 作業用変数を用いる場合 is_leap_year_func_ex1_1.c #include "is_leap_year_func.h" int is_leap_year(int year) { int leap_year_flag; } ローカル変数の宣言 if (year % 4 == 0) { if (year % 100 == 0) { if (year % 400 == 0) { leap_year_flag = 1; } else { leap_year_flag = 0; } } else { leap_year_flag = 1; } } else { leap_year_flag = 0; } ここは leap_year_practice_2.c そのまま return leap_year_flag; 結果を戻り値として返す ローカル変数 ブロック内の作業用。 ブロック外に 同じ名前の変数があっても 競合せず安全に使える。 ブロック(複文) { } で囲まれた領域の事 20 関数の差し替え • 関数のプロトタイプ宣言が同じなら関数は差 し替えて使える。 mintty + bash + GNU C $ gcc is_leap_year_main.c is_leap_year_func_ex4_2.c mintty + bash + GNU C $ gcc 実装は異なるが プロトタイプ宣言が同じ関数を 定義したファイルで差し替え is_leap_year_main.c is_leap_year_func_ex1_1.c 教科書 pp.161-169. 変数のスコープ(有効範囲) • 大域(グローバル)変数 • プログラム全体からアクセス可能 • 局所(ローカル)変数 • ブロック内からのみアクセス可能 C言語では { } で 囲まれた部分がブロック 21 22 教科書 pp.161-169. 変数のスコープ(有効範囲) • ローカル変数は{}の中のみで有効 scopetest.c 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 int gl = 100; 関数外で宣言するとグローバル変数 void sub(int lo) 関数の引数はローカル変数 { { ブロックを作成するとローカル変数の有効範囲を制限出来る int lo = 400; printf("%-4s : %3d: gl=%d, lo=%d\n", __func__, __LINE__, ++gl, ++lo); } printf("%-4s : %3d: gl=%d, lo=%d\n", __func__, __LINE__, ++gl, ++lo); } int main() { int lo = 200; sub(300); printf("%-4s : %3d: gl=%d, lo=%d\n", __func__, __LINE__, ++gl, ++lo); return EXIT_SUCCESS; } 23 教科書 pp.161-169. 変数のスコープ(有効範囲) • ローカル変数は{}の外側に影響していない scopetest.c 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 int gl = 100; void sub(int lo) { { int lo = 400; printf("%-4s : %3d: gl=%d, lo=%d\n", __func__, __LINE__, ++gl, ++lo); } printf("%-4s : %3d: gl=%d, lo=%d\n", __func__, __LINE__, ++gl, ++lo); } int main() mintty + bash + GNU C { $ gcc scopetest.c && ./a int lo = 200; sub(300); sub : 14: gl=101, lo=401 printf("%-4s : %3d: gl=%d, lo=%d\n", __func__, __LINE__, ++gl, ++lo); return EXIT_SUCCESS; } sub : main : 16: gl=102, lo=301 23: gl=103, lo=201 24 教科書 pp.203-206. 関数の演習 • is_leap_year_func_ex1_1.c、 is_leap_year_func_ex4_2.c を参考に以下のファイルの 処理を関数にしてファイルに分離してみましょう。 • • • • • • leap_year_ex1_2.c → is_leap_year_func_ex1_2.c leap_year_ex2_1.c → is_leap_year_func_ex2_1.c leap_year_ex2_2.c → is_leap_year_func_ex2_2.c leap_year_ex3_1.c → is_leap_year_func_ex3_1.c leap_year_ex3_2.c → is_leap_year_func_ex3_2.c leap_year_ex4_1.c → is_leap_year_func_ex4_1.c • is_leap_year_main.c と一緒にコンパイルしてみて動作 を確認してみましょう。 25 参考文献 • [1] B.W.カーニハン/D.M.リッチー著 石田晴久 訳、プログラミング言語C 第2版 ANSI 規格準 拠、共立出版(1989)
© Copyright 2025 ExpyDoc