Dokusyo-nissi Bessitu 2004-11-06 φ(-_-) ■ [lang]"K & R" 2nd Rev. だいぶ忘れてる . . . 1. /* thermo_table.c */ 2. #include 3. /* 4. * 摂氏 - 華氏対照表 5. */ 6. main() 7. { 8. float fahrenheit, centi_glade; /* 変数の宣言 */ 9. int lower, upper, step; 10. lower = 0; /* 各変数の初期化 */ 11. upper = 300; 12. step = 20; 13. fahrenheit = lower; /* 初期化 - float へ変換 */ 14. while (fahrenheit <= upper) { 15. centi_glade = (5.0 / 9.0) * (fahrenheit - 32.0); 16. printf("%3.0f F %6.1f C\n", fahrenheit, centi_glade); 17. fahrenheit = fahrenheit + step; /* 1 step分インクリメント */ 18. } 19. } %3.0f ←桁数が 3 で小数点は表示しない。 %6.1f ←桁数が 6 で小数点は 1ケタまで。 2004-11-09 φ(-_-) ■ [lang]プログラミング言語 C まずは第1章をよく読まないとネ。メモメモ . . . ・ 1つの Cプログラムはその大小にかかわらず、関数と変数とからなりたつ。 ・関数には実行すべき計算のプロセスを指示する文と、計算で扱われる値を格納す る変数とが含まれる。 ・関数の間でデータをやりとりする 1つの方法に、呼びだし側の関数から相手の関 数に対して引数と呼ばれる値のリストを渡すやりかたがある。 ・関数名の後の () はこの'引数のリスト'を指定するものである。(p8) ・関数につくブロック {} は関数を構成する文を指定するものである。 ・二重引用符(ダブルクォーテーション)で囲まれる任意の数の文字の列を文字列ま たは文字列定数と呼ぶ。*2(p9) ・ Cプログラムではすべての変数は使用される前に、関数の始めで宣言しておかな ければならない。 ・この宣言によって変数の型を記憶させる。それは型名と変数のリストとからなる 。(p11) *1:足踏み状態 . . . ともいう *2:シングルクォーテーションで囲むのは文字 2004-11-11 φ(-_-) ■ [lang]プログラミング言語 C 入出力については、 ・ところで printf() は C言語の機能の一部ではない。C自体には入力も出力も定義 されていないからである。 ・ printf() は単に Cプログラムで通常に使われる標準ライブラリの一つの有用な 関数であるにすぎない。(p14) ふ〜ん、そうなんだ。 次は繰り返し文について、 ・ while と for についてはどちらを選んでもよいので、どちらかわかりやすいほ うを使えばいい。 ・通例 for のほうが while よりコンパクトでありループを制御する文を一ヶ所に まとめておけるので、初期化とインクリメントとがあって互いに関連づけられるル ープには (for のほうが) 適している。(p17) 一ヶ所にまとめておけるというのは . . (i = 0; i < MAX; i++) ←これのこと。 ・プログラムの中に 300 とか 20 というマジックナンバー*1を埋め込むのはよいと はいえない。あとでそのプログラムを読む人にとってはほとんど意味がわからない 場合が多いし、数字自体変更することも困難である。 ・ Cにはそうしたジャマな数値を避けることのできる方式がある。プログラムの始 めに置く #define 構文がそれで、これを使えば特定の文字列*2を記号名すなわち記 号定数として定義することができる↓ #define 記号名置き換えるテキスト ・コンパイラはそうした記号定数が引用符なしでプログラムにでてくると、自動的 に記号名に対応した文字列*3に置き換えてくれる。(p18) ↑マクロですネ。 *1:←固定された数値 *2:←数値には限らないでよい *3:←数の場合が多い . . 2004-11-14 φ(-_-) ■ [lang]プログラミング言語 C 文字の入出力全般について、 ・テキストの入力あるいは出力は、それがどこで発生したかあるいはどこに出力す るかにはよらずに*1、文字のストリーム(流れ)として扱われる。 ・テキストストリームとは行ごとに分割された文字の連なり*2である。各行はゼロ またはそれ以上の個数の文字列の最後に改行記号がついた形をとる。(p19) ↑fgets() や fputs() のことですネ。 ・標準ライブラリには一度に1つの文字を読み書きする関数が用意されている。(← getchar() や putchar() のこと) ・ getchar() と putchar() があれば、(標準)入出力についてそれ以外何も知らな くても、役に立つプログラムをかなりたくさん書くことができる。(p19) つぎは getchar() の返り値がなぜ int型なのか、 ・キーボードや画面上で文字として扱われるものは他のすべてのデータと同じく、 内部的には単にビットパターンとして格納される。 ・文字データの格納には特に char型が設けられているが、任意の整数型も使える。 この int型を (getchar で使うのには) 微妙だが重要な理由がある。 ・ getchar() が返す任意の値*3を保持するためには、十分大きなデータ型で宣言し なければならない。 ・それには任意の char(文字) に加えて、入力の終りを示す EOF*4も保持するに十 分な大きさでなければならないから、char型(←1バイト)では不可能なので、int型 を使うのである。(p20) 1. main() 2. { 3. int c; /* 返り値は int型 */ 4. while ((c = getchar()) != EOF) { 5. putchat(); 6. } 7. } ・このプログラムでは文字を一つとって c に代入した後、その文字がファイルの終 わりかどうか while で調べている。 ・ EOF でなければ while の構成文が実行され文字がプリントされる。同様に while が繰り返され、入力の終わりにくると while は終了し、したがって main() も終了する。 ・上のプログラムでは入力を1つの行にまとめている - getchar() の呼出しが 1箇 所だけ - から短くなっている。 ・できたプログラムはより簡潔になり、この慣用句をマスターしてしまえば読みや すくもなる。 ・ただし使いすぎると逆にわかりにくいプログラムになってしまうので注意。(p21) *1:通例、入出力関数の「引数」で指定する *2:←ストリング ? *3:←入力した文字の *4:←End of File、値は -1 2004-11-15 φ(-_-) ■ [lang]プログラミング言語 C ・ (ファイルを入力するとそのファイルの)バイト数、行数、単語数を表示する有用 なプログラムを考えてみよう。 ・ただしこの場合の単語は「単に空白や改行やタブを含まない文字の連なり」と、 アバウトに定義しておく。 ・このプログラムは UNIX のユーティリティプログラム(←コマンドのこと)"wc"の 基幹となる部分である。(p23) 1. /* primitive_wc.c */ 2. #include 3. #define IN 1 4. #define OUT 0 5. /* IN,OUT は単語の状態(ステート)をあらわす */ 6. /* 単語の中→ 1, 単語の外→ 0 */ 7. main() 8. { 9. int c; 10. int nc, nl, nw; 11. int state; 12. state = OUT; 13. nc = nl = nw = 0; 14. /* 各変数の初期化 */ 15. while ((c = getchar()) != EOF) { 16. ++nc; 17. /* バイト数をカウント */ 18. if (c == '\n') { 19. ++nl; 20. } 21. /* 改行ごとに行数をカウント */ 22. if (c == ' ' || c == '\n' || c == '\t') { 23. state = OUT; 24. } else { 25. if (state == OUT) { 26. state = IN; 27. ++nw; 28. } 29. } 30. /* 1) 空白、改行、タブの場合には、ステートを 0 に変更 */ 31. /* 2) 入力した char がエスケープ文字ではなくて */ 32. /* 直前のステートが 0 の場合には、ステートを 1 に変更して */ 33. /* 単語数をカウント */ 34. } 35. printf("char : %6d bytes\n", nc); 36. printf("line : %6d lines\n", nl); 37. printf("word : %6d words\n", nw); 38. } $./primitive_wc < primitive_wc.c として確認。 なんだかバイト数が1個多いような気が . .*1 (追記) コードの最初の if文のカッコ*2が閉じてなかったので訂正。 *1:'\0' ←これのセイかな ? *2:コレ→ {} 2004-11-18 φ(-_-) ■ [lang]プログラミング言語 C ・関数を定義するとき、() の中にリストされた変数をパラメータといい、関数呼び 出しの際使われる値を引数(←argument)という。 int kansuu(int x, int y); ↑の x と y がパラメータ c = kansuu(n, m); ↑の n と m とが引数 ・関数を前もってプロトタイプとして宣言する場合、パラメータの名は省略するこ とができる。 こういうかんじ↓ int kansuu(int, int); ・しかし、変数名をうまく選択する(←適切なパラメータ名をつける)ことは、コー ドを書く上で (奨励して)よいことなので、なるべく使用するようにしている。 (p32) プログラムを分割して宣言をヘッダーファイルへまとめて書くときは、このほうがいい のかもしれない。 2004-11-21 φ(-_-) ■ [lang] プログラミング言語 C ・文字配列の使い方とそれを操作する関数を示すために、一群の行を読み込んで一 番長い行をプリントするプログラムを書いてみよう。 ・ (全体をいくつかの部分に分けると) 一つのパートで新しい行を得て、もう一つ のパートでそれをテストし、さらにもう一つのパートで (テストの結果を)格納して 、残りのパートで処理を制御する (というふうにプログラムの骨格を考えてみる)。 ・まず最初に各行を漸次入力する関数 get_line() を書く。他のプログラムの関数 としても有効な(←使える)ように、できるだけ柔軟な形に書くことにする。 1. get_line(char str[], int lim) { 2. int c, i; 3. for (i = 0; i < (lim - 1) && (c = getchar()) != EOF && c != '\n'; ++i) { 4. str[i] = c; 5. } 6. if (c == '\n') { 7. str[i] = c; 8. ++i; 9. } 10. str[i] = '\0'; 11. return i; 12. } ・関数 get_line() は一対の引数と返される値とで関数 main() とつながる。 ・これらの get_line() の引数は最初の行で宣言されている。 → get_line(char str[], int lim); ・これは、最初の引数 str は配列であり、次の引数 lim が整数であることの宣言 である。 ・宣言の中で配列の大きさを与えている目的は、記憶場所の確保である。ただし配 列 str の長さは main() のなかで(すでに)決定されているので get_line() では指 定する必要はない。 ・get_line() は char '\0'(←ヌル文字、値は 0)を作成したファイルの最後におく 。 ・ (なぜなら)一般的に有効なプログラムとして設計するには、行の長さを返り値と し、文字列(←ファイル)の終わりでは 0 を返すようにする必要がある(からである) 。 ・たとえば printf() の(書式の)設定では文字列の終わりが '\0' であることを予 定している。 ・また次の関数 copy() でも入力引数が '\0' で終わっているという事実を使って 、文字を出力引数にコピーしている。 ・つぎに - main() で - 前に一番長かった行よりも長い行が見つかったら、それを どこかに格納しておかねばならない。 ・ここで 2番目の関数 copy() が登場する。これはその(現在一番長い)行を安全な 場所にコピーしておくためのものである。 ・関数 copy() はその機能のみが使われ、値を返さない。copy() の戻り値が void になっているのは返される値がないことを明示するためである。 1. void copy(char to[], char from[]) { 2. int i; 3. i = 0; 4. while ((to[i] = from[i]) != '\0') { 5. ++i; 6. } 7. } ・さいごにget_line() と copy() を制御する main() プログラムが必要である。以 上の結果を示すと次のようになる。 1. /* longest_line.c */ 2. #include 3. #define MAX_LINE 1000 4. get_line(char line[], int max_line); 5. void copy(char to[], char from[]); 6. main() 7. { 8. int length; 9. int max; 10. char line[MAX_LINE]; 11. char longest[MAX_LINE]; 12. max = 0; 13. while((length = get_line(line, MAX_LINE)) > 0) { 14. if (length > max) { 15. max = length; 16. copy(longest, line); 17. } 18. } 19. if (max > 0) { 20. printf("Longest line of this file as under:-\n"); 21. printf("%s", longest); 22. } 23. return 0; 24. } 25. get_line(char str[], int lim) { 26. int c, i; 27. for (i = 0; i < (lim - 1) && (c = getchar()) != EOF && c != '\n'; ++i) { 28. str[i] = c; 29. } 30. if ( c == '\n') { 31. str[i] = c; 32. ++i; 33. } 34. str[i] = '\0'; 35. return i; 36. } 37. void copy(char to[], char from[]) { 38. int i; 39. i = 0; 40. while ((to[i] = from[i]) != '\0') { 41. ++i; 42. } 43. } ・ついでに、だいじなことをいっておきたい。(それは)これほど小さなプログラム であっても、設計上のやっかいな問題を提起している、ということである。 ・例えば、制限されている行の長さよりも大きな行を読み込んだときに関数 main() ではどう(処理)すべきなのか ? ・関数 get_line() では、配列がいっぱいになると改行文字の有無を問わずに入力 を打ち切って、安全に作動する。 ・ (なぜなら) get_line() では前もって入力行の長さを知る方法がないので、オー バーフローをチェックする必要がある(からである)。 ・ (それに対してすでに長さがわかっている) 関数 copy() ではエラーチェックを 付け加えることはしていない。 ・ (話を簡単にするためこの問題は無視するが) main() では、返ってきた(行の)長 さと最後の文字を見て行が長すぎたか否かを判断し、それによって (オーバーフロ ーに)対処することが可能である。(p35-38) $./longest_line < longest_line.c として確認。 う〜ん、どこが「話を簡単にするため」なのかよくわからん . . .