1 C言語入門 第4週 プログラミング言語Ⅰ(実習を含む。), 計算機言語Ⅰ・計算機言語演習Ⅰ, 情報処理言語Ⅰ(実習を含む。) 2 先週の復習1 3 参考 条件演算子 三項演算子(?:) • 書式: 真 • 条件式 ? 式1 : 式2 式1 条件式 偽 式2 condexprtest.c int i; printf("i = "); scanf("%d", &i); printf("%s\n",i ? "true" : "false"); $ gcc condexptest.c && ./a i = 1 true $ ./a i = 0 false 参考: [1] pp.63-66, 256-257. 4 閏年(leap year)の判定 • 閏年の求め方 • 西暦を4で割り切れるなら閏年? 真 閏年 閏年 判定式 偽 平年 5 閏年(leap year)の判定 • グレゴリオ暦における閏年の定義 • 判別したい年を西暦で表した際 • 4で割り切れる場合は閏年 (条件1) • 但し100で割り切れる場合は平年 (条件2) • 但し400で割り切れる場合は閏年 (条件3) 6 閏年(leap year)の判定 • 4で割れる(割り切れる)とは? • 4で割った余りが0ということ • 4で割り切れる • 4で割り切れない : year % 4 == 0 : year % 4 != 0 % : 剰余算演算子 等値演算子 == : 等しい != : 等しくない • C言語では0は偽、0以外は真だったから • 以下のようにも書けるが・・・ • 4で割り切れる • 4で割り切れない ! : 論理否定演算子 : !(year % 4) : (year % 4) ぱっと見て意味の分かり易い書き方をしましょう 7 演算子の優先度 優先度 高 演算子 ( ) [ ] -> 備考 左から右→ . 右から左← 単項演算子 左から右→ 二項演算子 左から右→ 二項演算子 左から右→ bitシフト 左から右→ 関係演算子 左から右→ 等値演算子 & 左から右→ bit毎のAND ^ 左から右→ bit毎のXOR | 左から右→ bit毎のOR && 左から右→ 論理演算子(AND) || 左から右→ 論理演算子(OR) ?: 右から左← 三項演算子 右から左← 代入演算子 ! ~ ++ * / % + - << < == = 低 結合規則 , -- + - * & (type) sizeof >> <= > >= != += -= *= /= %= &= ^= |= <<= >>= 左から右→ [1] p.65. より 8 閏年(leap year)の判定 • プログラムの仕様 • 判別したい西暦をキーボードから入力 • int 型の変数 year に格納 • int 型の変数 leap_year_flag に 平年なら 0 閏年なら 1 を格納 • 閏年か否かを表示 9 閏年(leap year)の判定 • 雛形 is_leap_year_template.c int year; int leap_year_flag; printf("year = "); scanf("%d", &year); // Write here your routine to detect the leap year. if (leap_year_flag) { printf("%d is leap year.\n", year); } else { printf("%d is not leap year.\n", year); } 10 閏年(leap year)の判定 真 条件1 閏年 4で 割れる 偽 平年 11 閏年(leap year)の判定 真 条件1 +条件2 真 平年 100で 割れる 4で 割れる 偽 閏年 偽 平年 12 閏年(leap year)の判定 真 条件1 +条件2 +条件3 真 完成 真 閏年 400で 割れる 100で 割れる 偽 平年 4で 割れる 偽 閏年 偽 平年 13 閏年(leap year)の判定 • 3重のif-else文による実装 • 条件1を実装 if (year % 4 == 0) { leap_year_flag = 1; } else { leap_year_flag = 0; } 14 閏年(leap year)の判定 • 3重のif-else文による実装 • 条件2を追加 if (year % 4 == 0) { if (year % 100 == 0) { leap_year_flag = 0; } else { leap_year_flag = 1; } } else { leap_year_flag = 0; } 15 閏年(leap year)の判定 • 3重のif-else文による実装 • 条件3を追加して完成 なんかごちゃごちゃしていて 分かり難い もっと分かり易く 書けないか? is_leap_year_1_1.c 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; } 16 閏年(leap year)の判定 • 3重のif-else文による実装 • 初期値を与え 例外のみを記述してみる • 条件1を実装 leap_year_flag = 0; if (year % 4 == 0) { leap_year_flag = 1; } 17 閏年(leap year)の判定 • 3重のif-else文による実装 • 初期値を与え 例外のみを記述してみる • 条件2を追加 leap_year_flag = 0; if (year % 4 == 0) { leap_year_flag = 1; if (year % 100 == 0) { leap_year_flag = 0; } } 18 閏年(leap year)の判定 • 3重のif-else文による実装 • 初期値を与え 例外のみを記述してみる • 条件3を追加して完成 元の定義に近い書き方? 若干読み易くなった? 更に分かり易く 書けないか? is_leap_year_1_2.c leap_year_flag = 0; if (year % 4 == 0) { leap_year_flag = 1; if (year % 100 == 0) { leap_year_flag = 0; if (year % 400 == 0) { leap_year_flag = 1; } } } 19 閏年(leap year)の判定 • 展開したif文による実装 • 100 と 400 は 4 の倍数 400 は 100 の倍数 である点に着目すると ネスト(入れ子)を展開可能 if文のロジック以外に 各条件間の数学的関係による 暗黙の前提が分からないと 読めないコードになるかも? そういう意味では 良くないコード? 適切なコメント等が必要? is_leap_year_2_1.c leap_year_flag = 0; if (year % 4 == 0) { leap_year_flag = 1; } if (year % 100 == 0) { leap_year_flag = 0; } if (year % 400 == 0) { leap_year_flag = 1; } year % 4 != 0 および year % 100 != 0 の場合には影響を与えない year % 4 != 0 の場合には影響を与えない 20 閏年(leap year)の判定 • 展開したif文による実装 • 前頁のコードから省略可能な { } を省略 is_leap_year_2_2.c int leap_year_flag = if (year % 4 == 0) if (year % 100 == 0) if (year % 400 == 0) 0; leap_year_flag = 1; leap_year_flag = 0; leap_year_flag = 1; 実行する処理が 1つだけの場合は 省略出来る。 綺麗で見易い。 しかし、定義通りになっていないため、 100で割り切れた場合、4で割り切れなかった場合に影響を与えない 400で割り切れた場合、4,100で割り切れなかった場合に影響を与えない ことがわかっていないと混乱するかもしれない? 21 閏年(leap year)の判定 • 3重の条件演算子による実装 • 条件1を実装 leap_year_flag = year % 4 == 0 ? 1 : 0; • 条件2を追加 leap_year_flag = year % 4 == 0 ? (year % 100 == 0 ? 0 : 1) : 0; • 条件3を追加して完成 is_leap_year_3_1.c leap_year_flag = year % 4 == 0 ? (year % 100 == 0 ? (year % 400 == 0 ? 1 : 0) : 1) : 0; 条件演算子は便利だけど 下手に多用すると読み難くなるので注意 ネスト(入れ子)が深くなると if-else文以上に読み難い 22 閏年(leap year)の判定 • 3重の条件演算子による実装 • 条件1を実装 leap_year_flag = year % 4 == 0 ? 1 : 0; is_leap_year_2_2.c と同じ方法で 入れ子の条件を展開する例 • 条件2を追加 leap_year_flag = year % 4 == 0 ? 1 : 0; leap_year_flag = year % 100 == 0 ? 0 : leap_year_flag; • 条件3を追加して完成 is_leap_year_3_2.c leap_year_flag = year % 4 == 0 ? 1 : 0; leap_year_flag = year % 100 == 0 ? 0 : leap_year_flag; leap_year_flag = year % 400 == 0 ? 1 : leap_year_flag; これなら is_leap_year_2_2.c の方が読み易いかも? 23 論理演算 • AND, OR, NOT • 条件を論理演算する場合に使う • AND: • OR: • NOT: X && Y X || Y !X X Y X && Y X || Y FALSE FALSE 0 0 FALSE TRUE 0 1 TRUE FALSE 0 1 TRUE TRUE 1 1 !X 1 0 24 閏年(leap year)の判定 • 西暦全体の集合のうち • X: 4で割り切れる集合(Y,Z を包含) • Y: 100で割り切れる集合(Z を包含) • Z: 400で割り切れる集合 X Y Z !Y Y 25 閏年(leap year)の判定 • 論理演算で 考えると !X || (Y && !Z) X Y !X (X && !Y) || Z X && !Y C言語の演算子の優先度的には X && !Y || Z でも良いが ( ) を付けた方が理解が容易かつ 誤解の余地が生じない Y && !Z Z Z 26 閏年(leap year)の判定 • if-else文と論理演算による実装 is_leap_year_4_1.c if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) { leap_year_flag = 1; } else { leap_year_flag = 0; } • 論理演算による実装 is_leap_year_4_2.c leap_year_flag = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0; 27 閏年(leap year)の判定 • 全ての条件が網羅出来ているか? • 条件に漏れはないか? year % 100 == 0 year % 4 == 0 year % 100 != 0 year % 100 == 0 year % 4 != 0 year % 100 != 0 × の個所は 条件を満たす year が存在しない year % 400 == 0 閏年 year % 400 != 0 平年 year % 400 == 0 × year % 400 != 0 閏年 year % 400 == 0 × year % 400 != 0 × year % 400 == 0 × year % 400 != 0 平年 2_1, 2_2, 3_2 の例で year % 100 != 0 や year % 400 != 0 が 考慮不要なことが確認出来る。 28 閏年(leap year)の判定 • 全ての条件が網羅出来ているか? 今の場合前の条件への追加条件なので • 条件に漏れはないか? 全ての組み合わせを考える必要はない year % 100 == 0 year % 4 == 0 year % 100 != 0 year % 100 == 0 year % 4 != 0 year % 100 != 0 year % 400 == 0 閏年 year % 400 != 0 平年 year % 400 == 0 year % 400 != 0 閏年 year % 400 == 0 year % 400 != 0 year % 400 == 0 year % 400 != 0 平年 29 閏年(leap year)の判定 • 全ての条件が網羅出来ているか? • 条件に漏れはないか? year % 100 == 0 year % 100 != 0 × の個所は 条件を満たす year が存在しない year % 4 == 0 year % 4 != 0 year % 400 == 0 閏年 × year % 400 != 0 平年 × year % 400 == 0 × × year % 400 != 0 閏年 平年 長くなる場合は横に展開しても良い 30 閏年(leap year)の判定 • 全ての条件が網羅出来ているか? • 条件に漏れはないか? × の個所は 条件を満たす year が存在しない year % 4 == 0 year % 100 == 0 year % 400 == 0 閏年 year % 4 == 0 year % 100 == 0 year % 400 != 0 平年 year % 4 == 0 year % 100 != 0 year % 400 == 0 × year % 4 == 0 year % 100 != 0 year % 400 != 0 閏年 year % 4 != 0 year % 100 == 0 year % 400 == 0 × year % 4 != 0 year % 100 == 0 year % 400 != 0 × year % 4 != 0 year % 100 != 0 year % 400 == 0 × year % 4 != 0 year % 100 != 0 year % 400 != 0 平年 この書き方は Excel 等で sort して調べるのに向いている 31 条件演算子の応用 • 真偽値による文字列の切り替え is_leap_year_template.c if (leap_year_flag) { printf("%d is leap year.\n", year); } else { printf("%d is not leap year.\n", year); } printf("%d is%s leap year.\n", year, leap_year_flag ? "" : " not"); leap_year_flag の値を見て直接 %s に埋め込む文字列を変更 32 同じ処理でも実装は様々 • 同じ処理でも複数の異なる実装が可能 • 効率や可読性等、何を重視するか? • 読み易い、理解しやすいコードを推奨 33 教科書 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 関数 34 教科書 pp.149-160. (ユーザー定義)関数 • 関数は自分で作ることが出来る • 機能、分量等、ある程度まとまった処理は 関数にまとめる • 可読性が向上する • 再利用が容易になる • 関数の定義の書式: C言語の関数は 0個以上の引数と 1つの戻り値を持つ。 引数や戻り値が不要な場合は 戻り値の型 関数名(引数の宣言, ...) void を宣言するよう { 推奨されている。 // 関数に行わせる処理 // ... return 戻り値; // 戻り値の型がvoidの場合は不要 } 35 教科書 pp.149-160. (ユーザー定義)関数の例 • 閏年の判定を関数にまとめてみる is_leap_year_with_func_4_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; printf("year = "); scanf("%d", &year); 関数の定義 自分で作成した関数を サブルーチンとして呼ぶ printf("%d is%s leap year.\n", year, is_leap_year(year) ? "" : " not"); return EXIT_SUCCESS; } 36 教科書 pp.153-160. (ユーザー定義)関数の例 • 関数を使う前にはパラメータの型と数が 分かっている必要がある(コンパイラの都合) #include <stdio.h> #include <stdlib.h> int main() { int year; printf("year = "); 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. 37 教科書 pp.149-160. 関数のプロトタイプ宣言 • 関数は使う前にプロトタイプ宣言する #include <stdio.h> #include <stdlib.h> int is_leap_year(int year); 関数のプロトタイプ宣言 戻り値の型、引数の数と型を宣言する int main() { int year; printf("year = "); scanf("%d", &year); 関数のプロトタイプ宣言さえしておけば 関数の定義はどこにあっても良い。 例えば外部のファイルにあっても良い。 printf("%d is%s leap year.\n", year, is_leap_year(year) ? "" : " not"); return EXIT_SUCCESS; } 38 教科書 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_4_2.c 関数本体の定義 #include "is_leap_year_func.h" int is_leap_year(int year) { return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0; } 39 教科書 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; printf("year = "); scanf("%d", &year); ヘッダファイルの include 外部ファイルで定義した 関数を呼び出し printf("%d is%s leap year.\n", year, is_leap_year(year) ? "" : " not"); return EXIT_SUCCESS; } 40 分割コンパイル • コンパイルする複数のCのソースファイルを コンパイラに与えれば良い mintty + bash + GNU C $ gcc is_leap_year_main.c is_leap_year_func_4_2.c コマンドプロンプト + Borland C++ >bcc32 is_leap_year_main.c is_leap_year_func_4_2.c 与えるのはCのソースファイルのみ ヘッダファイルは #include 文により 自動的に取り込まれる 41 教科書 pp.203-206. 関数の演習 • is_leap_year_func_4_2.c を参考に以下のファイ ルの処理を関数にしてファイルに分離してみま しょう。 • • • • • • • is_leap_year_1_1.c → is_leap_year_func_1_1.c is_leap_year_1_2.c → is_leap_year_func_1_2.c is_leap_year_2_1.c → is_leap_year_func_2_1.c is_leap_year_2_2.c → is_leap_year_func_2_2.c is_leap_year_3_1.c → is_leap_year_func_3_1.c is_leap_year_3_2.c → is_leap_year_func_3_2.c is_leap_year_4_1.c → is_leap_year_func_4_1.c 教科書 pp.161-169. 変数のスコープ(有効範囲) • 大域(グローバル)変数 • プログラム全体からアクセス可能 • 局所(ローカル)変数 • ブロック内からのみアクセス可能 C言語では { } で 囲まれた部分がブロック 42 43 教科書 pp.161-169. 変数のスコープ(有効範囲) • ローカル変数は { } の中のみで有効 scopetest.c 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; } 44 教科書 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 45 プリプロセッサ • 条件付きコンパイル #if 定数式 #elif 定数式 #else #endif #if 0 // ここはコンパイルされない #endif 条件に合った箇所のみを コンパイラに渡す構文 定数式の結果は0と非0の真偽値 #if defined(__BORLANDC__) // __BORLANDC__ という名前のマクロが // 定義されていた場合のみコンパイルされる #endif #if defined(__BORLANDC__) && 0x0551 <= __BORLANDC__ // ... #endif 定数式なら 演算も可能 46 プリプロセッサ • 条件付きコンパイル defined( ) の短縮表記 #ifdef __BORLANDC__ // ... #endif #if #ifndef __BORLANDC__ // ... #endif #if !defined( ) の短縮表記 誤:define 正:defined 47 プリプロセッサ • 定義済みマクロ __LINE__ __FILE__ __func__ __FUNC__ 現在の行番号 現在のファイル名 現在の関数名(C99) 現在の関数名(Borland C++) #ifdef __BORLANDC__ #define __func__ __FUNC__ #endif Borland C++ で C99互換の定義済みマクロが 使えるようにする 条件付きコンパイル 48 教科書 pp.203-206. 関数をファイルに分離する • 作業用変数を用いる場合 is_leap_year_func_1_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; } ここは is_leap_year_1_1.c そのまま return leap_year_flag; 結果を戻り値として返す ローカル変数 ブロック内の作業用。 関数外に 同じ名前の変数があっても 競合せず安全に使える。 49 関数の差し替え • 関数のプロトタイプ宣言が同じなら関数は差 し替えて使える。 mintty + bash + GNU C $ gcc is_leap_year_main.c is_leap_year_func_4_2.c mintty + bash + GNU C $ gcc 実装は異なるが プロトタイプ宣言が同じ関数を 定義したファイルで差し替え is_leap_year_main.c is_leap_year_func_1_1.c 50 参考文献 • [1] B.W.カーニハン/D.M.リッチー著 石田晴久 訳、プログラミング言語C 第2版 ANSI 規格準 拠、共立出版(1989)
© Copyright 2025 ExpyDoc