プログラミング言語 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 として確認。
う〜ん、どこが「話を簡単にするため」なのか よくわからん . . .


(追記) コメントをつけると繁雑になりそうなので、注意が必要(?)なところだけ コードの色を変えてみました。