プログラミング言語 C

C プログラムは 変数あるいは関数の いずれかである 外部オブジェクトの集合で 構成されている。

外部的とは、主に 関数内で 定義される 引数と (自動)変数を 記述する 内部的という 考え方と 対照的に 使用される。

(第1章で 述べたように) 外部変数は すべての関数の外側で (前もって) 定義され、多くの関数で 利用することが 可能となる。

C では 関数内で 別の関数を 定義することは 許されていないので 関数自身は 常に 外部的である(と いえる)。

特に 指定のない限り 外部変数 および (外部)関数は、その同じ名前で 参照すると、別に コンパイルされた 関数からであっても やはり 同じ変数 または 関数を 参照できるという 性質をもっている。この性質を 標準規格では 外部リンクと 呼んでいる。

もし 2つの 関数が データを 共用し そして どちらからも 相手の関数を 呼び出さない場合、引数で (データを) 受渡しするより 外部変数を 使ったほうが ベンリである。(p90)

演算子 + - * / が使える 電卓プログラムを 書いてみよう。

実装を やさしくするため このプログラムでは (普通の) 挿入記法を 使わず、逆ポーランド法を 使用することにする。

逆ポーランド法は ポケット電卓や Forth、Postscript のような言語で 使われている 方式である。

逆ポーランド法では 各演算子は 被演算数の 後に置く。(つまり) 次のような 挿入記法の式、
(1 - 2) * (4 + 5)

1 2 - 4 5 + *
のように 表す。カッコは 不要である。

これの実装は きわめて簡単である。(←そんなことは なかったゾ)

まず 各演算数を スタック上に プッシュする。(次に) 演算子が来ると 要求される個数の 被演算数が ポップされ、それに 演算子が 適用されて その結果が (再び) スタックにプッシュされる。

上の例では 1 と 2 が プッシュされ (- 演算子によって) その差 -1 が 代わって プッシュされる。

次に 4 と 5 が プッシュされ (+ 演算子によって) その和 9 が 入れ替わりに プッシュされる。

さらに -1 と 9 の積 -9 が (* 演算子により 計算され、最終的に) 入れ替わる。

スタック上の この数字が 入力行の 終了時に ポップされて プリントされる。

エラーの検出等を 加えていくと 操作が 長くなるので、プログラム全般にわたって コードを並べていくより、それぞれの操作を 別個の関数に するほうがいい。

また 入力から 次の演算子や 被演算数を 取り出す 別の関数も 必要になる。

このプログラムの 設計上で たいせつなのは、スタックを どこに置くか、である。つまり どのルーティンで それに 直接 アクセスするか である。

(以上を 考慮に入れて) スタックと それに関連する情報は 外部変数とし、main() 関数ではなく push() 関数と pop() 関数で アクセスできるようにする。(p90-100)

  1. /* main.c */
  2. #include
  3. #include
  4. /* 関数 atof() に必要、atof() は 文字列を数字に変換 */
  5. #include "calc.h"
  6. #define MAX_OP 100
  7. /* 逆ポーランド電卓プログラム */
  8. main()
  9. {
  10. int type;
  11. double tmp;
  12. /* 2項の 左右の数を 区別するために 導入する 仮変数 */
  13. char str[MAX_OP];
  14. while ((type = get_op(str)) != EOF) {
  15. switch(type) {
  16. case NUMBER:
  17. /* type が 数字であれば */
  18. push(atof(str));
  19. break;
  20. /* 数字を 積んでいく */
  21. case '+':
  22. push(pop() + pop());
  23. break;
  24. case '-':
  25. tmp = pop();
  26. push(pop() - tmp);
  27. break;
  28. case '*':
  29. push(pop() * pop());
  30. break;
  31. case '/';
  32. tmp = pop();
  33. if (tmp != 0.0)
  34. push(pop() / tmp);
  35. else
  36. printf("[警告 !] 0 で 割っちゃダメ\n");
  37. break;
  38. case '\n':
  39. printf("ans : %.8g\n", pop());
  40. /* 引数 g は 有効桁数 */
  41. break;
  42. default:
  43. printf("[警告 !] %s ってゆー演算子、知りませんヨ\n", str);
  44. break;
  45. }
  46. }
  47. return 0;
  48. }
  1. /* stack.c */
  2. #include
  3. #include "calc.h"
  4. #define MAX_VAL 100
  5. int stk_p = 0; /* スタックポインタの初期化 */
  6. double value[MAX_VAL];
  7. void push(double fig)
  8. {
  9. if (stk_p < MAX_VAL)
  10. value[stk_p++] = fig;
  11. else
  12. printf("[警告 !] スタックが いっぱいで %g が push できません\n", fig);
  13. }
  14. double pop(void)
  15. {
  16. if (stk_p > 0)
  17. return value[--stk_p];
  18. else {
  19. printf("[警告 !] スタックは 空ですネ\n");
  20. return 0.0
  21. }
  22. }
  1. /* get_op */
  2. #include
  3. #include
  4. /* 関数 isdigit() に必要、isdigit() は 10進法かどうかを 判別 */
  5. #include "calc.h"
  6. int get_op(char str[])
  7. {
  8. int i, c;
  9. while ((str[0] = c = get_ch()) == ' ' || c == '\t')
  10. ;
  11. /* 空白と タブを スキップ */
  12. str[1] = '\0';
  13. if (!isdigit(c) && c != ".")
  14. return c;
  15. /* c が 数字でなければ それを返す */
  16. i = 0;
  17. if(isdigit(c))
  18. while (isdigit(str[++i] = c = get_ch()))
  19. ;
  20. /* 整数部分の 数字を集める */
  21. if (c == '.')
  22. while (isdigit(str[++i] = c = get_ch()))
  23. ;
  24. /* 小数点以下の 数字を集める */
  25. str[i] = '\0';
  26. if (c != EOF)
  27. unget_ch();
  28. /* この部分は 関数 get_ch() の コメントを参照 */
  29. return NUMBER;
  30. /* input したのが 数字であれば 返り値は '0' になる */
  31. /* main() の switch文は この結果を 判断材料とする */
  32. }
  1. /* get_ch.c */
  2. #include
  3. #define BUF_SIZE 100
  4. char buf[BUF_SIZE];
  5. int buf_pt = 0; /* インデックス変数 */
  6. int get_ch(void)
  7. {
  8. return (buf_pt > 0 ? buf[--buf_pt] : getchar();
  9. }
  10. void unget_ch(int c)
  11. {
  12. if (buf_pt >= BUF_SIZE)
  13. printf("[警告 !] バッファから あふれてますヨ\n");
  14. else
  15. buf[buf_pt++] = c;
  16. }
  17. /*
  18. * プログラムでは 数でない文字を 1つ余分に 読み込む必要がある
  19. * そのため unget_ch() で その文字を 記憶しておき、get_ch() が
  20. * 呼び出される毎に 1文字分を 戻しておく
  21. */
  1. /* calc.h */
  2. #define NUMBER '0'
  3. /* '0' は NUMBER が 数であることの 表記 */
  4. void push(double);
  5. double pop(void);
  6. int get_op(char[]);
  7. int get_ch(void);
  8. void unget_ch(int);

コンパイルしてみます。まず オブジェクトファイルの作成。
$cc -c main.c stack.c get_op.c get_ch.c
コンパイルエラーがあれば ここで訂正。
次に 実行ファイルを つくる。
$cc -o rpn_calc main.o stack.o get_op.o get_ch.o
最後に 確認。
$./rpn_calc
改行して 待ち受け画面になるので
1 2 - 4 5 + * ← Enter
コードが まちがってなければ
ans : -9
と プリントされる . . はず。
(プログラムの終了は Ctrl + d)