K&R 演習4-10 解答 (プログラミング言語C 第2版)

スポンサーリンク

鍛錬 843

K&R 演習4-10 解答 (プログラミング言語C 第2版)

もう一つのやり方として, getline を使って,入力行全体を読み込む方法がある。この方法を使うように,電卓プログラムを書き変えよ。

B.W.カーニハン D.M.リッチー 石田晴久 訳 『プログラミング言語C 第2版 ANSI 規格準拠』, (共立出版, 2017), pp.97.

 
スポンサーリンク

プログラム

本書中の逆ポーランド記法を使用した電卓プログラムについて、入力行全体を読み込んだ後で処理を行うように書き変えたプログラム、kr_4_10.c です。

本書中で作成した、入力行全体を読み込む getline() 関数について、関数名 getline() は stdio.h で定義されているため、本プログラムではmy_getline()と関数名を変更しています。

以下の機能は、演習 4-3 , 4-4 , 4-5 , 4-6 で追加済みです。

機能 コマンド
モジュロ(%)(剰余演算)計算機能 %
マイナス(-)演算子である単項が含まれる場合の計算機能 無し
スタックにおける一番上の値をポップせずに印字し、複製する機能 d
スタックにおける上2つの要素を交換する機能 c
指定したラジアンの正弦(sin)を計算する機能
(ライブラリ関数 sin() )
s
ネイピア数eのx乗を計算する機能
(ライブラリ関数 exp() )
e
底と指数を指定し、べき乗を計算する機能
(ライブラリ関数 pow() )
p
変数の値を読み込む機能 r
変数に値を書き込む機能 w
変数の値を0(ゼロ)で初期化する機能 i
直近に印字された値を特定の変数に格納する機能 無し

pp.97 の時点でまだ登場していないライブラリ関数などはできる限り使用しないようにし、本書中で作成した関数を使用するようにしています。
記述方法(「 char *s 」または「 char s[] 」など)についても、できる限り pp.97 時点での記載通りにしています。

// include
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <math.h>

// preprocessor
#define MAXLINE 100		// 入力行の最大長
#define MAXOP   100		// 被演算数,演算子の最大サイズ
#define NUMBER  '0'		// 数字が存在した
#define MAXVAL  100		// スタックの最大深さ
#define BUFSIZE 100		// 入力文字を押し戻す際に利用するバッファのサイズ

// prototype
int getop(char[]);
void push(double);
double pop(void);
int getch(void);
void ungetch(int);
void NoPopPrintAndDup(void);
void ChangeTwoElmOnStack(void);
void CalcSin(void);
void CalcExp(void);
void CalcPow(void);
void ReadVariable(void);
void WriteVariable(void);
void InitZeroVariable(void);
void ungets(char s[]);
int my_getline(char s[], int lim);

// variable
int sp = 0;					// スタックポインタ
double val[MAXVAL];			// スタック
char buf[BUFSIZE];			// ungetch()用のバッファ
int bufp = 0;				// バッファbufにおける次の空き位置
double variable[26] = {0};	// 変数

// main
int main(void)
{
	char line[MAXLINE];
	char s[MAXOP];
	double op1, op2;
	int type;
	int elmt_num;
	
	while (my_getline(line, MAXLINE) > 0) {
		ungets(line);
		while (type != '\n') {
			type = getop(s);
			switch (type) {
				case NUMBER:
					push(atof(s));
					break;
				case '+':
					push(pop() + pop());
					break;
				case '*':
					push(pop() * pop());
					break;
				case '-':
					op2 = pop();
					push(pop() - op2);
					break;
				case '/':
					op2 = pop();
					if (op2 != 0.0) {
						push(pop() / op2);
					}
					else {
						printf("error: zero divisor\n");
					}
					break;
				case '%':
					op2 = pop();
					op1 = pop();
					if (op2 != 0.0) {
						push(op1 - (op2 * (int)(op1 / op2)));
					}
					else {
						printf("error: zero modulo\n");
					}
					break;
				case 'd':
					NoPopPrintAndDup();
					break;
				case 'c':
					ChangeTwoElmOnStack();
					break;
				case 's':
					CalcSin();
					break;
				case 'e':
					CalcExp();
					break;
				case 'p':
					CalcPow();
					break;
				case 'r':
					ReadVariable();
					break;
				case 'w':
					WriteVariable();
					break;
				case 'i':
					InitZeroVariable();
					break;
				case '\n':
					break;
				default:
					printf("error: unknown command %s\n", s);
					break;
			}
		}
		elmt_num = 'P' - 65;
		variable[elmt_num] = pop();
		printf("\t%.8g\n", variable[elmt_num]);
		type = 0;
	}
	
	return 0;
}

// ========================================
// @brief  sに入力行を格納し,その長さを返す
// @param  lim [in],入力行の最大長
// @param  s   [out],入力された文字列
// @return i -> 入力された文字列の文字列長
// @note   無し
// ========================================
int my_getline(char s[], int lim)
{
	int c, i;
	
	i = 0;
	while (--lim > 0 && (c = getchar()) != EOF && c != '\n') {
		s[i++] = c;
	}
	if (c == '\n') {
		s[i++] = c;
	}
	s[i] = '\0';
	
	return i;
}

// ==================================================
// @brief  文字列全体を入力に戻す(バッファに格納する)
// @param  s [in],入力に戻す文字列
// @return 無し
// @note   無し
// ==================================================
void ungets(char s[])
{
	int len;
	int i;
	
	len = strlen(s);
	if (len >= (BUFSIZE - bufp)) {
		printf("ungets: too many characters\n");
	}
	else {
		i = len - 1;
		while (i >= 0) {
			ungetch(s[i]);
			i--;
		}
	}
}

// ===================================
// @brief  変数に0を格納して初期化する
// @param  無し
// @return 無し
// @note   無し
// ===================================
void InitZeroVariable(void)
{
	char s[MAXOP];
	char var;
	int elmt_num;
	
	getop(s);
	if (s[0] < 'A' || s[0] > 'Z') {
		printf("error: usage [i variable]\n");
		printf("                ^^^^^^^^\n");
		printf("       Variable is valid between A and Z.\n");
		printf("       (Do not use variable P)\n");
		return;
	}
	var = s[0];
	
	elmt_num = var - 'A';
	variable[elmt_num] = 0.0;
	push(variable[elmt_num]);
}

// ==========================
// @brief  変数に値を書き込む
// @param  無し
// @return 無し
// @note   無し
// ==========================
void WriteVariable(void)
{
	char s[MAXOP];
	char var;
	int type;
	int elmt_num;
	
	getop(s);
	if (s[0] < 'A' || s[0] > 'Z') {
		printf("error: usage [w variable number]\n");
		printf("                ^^^^^^^^\n");
		printf("       Variable is valid between A and Z.\n");
		printf("       (Do not use variable P)\n");
		return;
	}
	if (s[0] == 'P') {
		printf("error: usage [w variable number]\n");
		printf("                ^^^^^^^^\n");
		printf("       Do not use variable P.\n");
		return;
	}
	var = s[0];
	
	type = getop(s);
	if (type != NUMBER) {
		printf("error: usage [w variable number]\n");
		printf("                         ^^^^^^\n");
		return;
	}
	elmt_num = var - 'A';
	variable[elmt_num] = atof(s);
	push(variable[elmt_num]);
}

// ========================================
// @brief  変数の値を読み込んでプッシュする
// @param  無し
// @return 無し
// @note   無し
// ========================================
void ReadVariable(void)
{
	char s[MAXOP];
	char var;
	int elmt_num;
	
	getop(s);
	if (s[0] < 'A' || s[0] > 'Z') {
		printf("error: usage [r variable]\n");
		printf("                ^^^^^^^^\n");
		printf("       Variable is valid between A and Z.\n");
		printf("       (Do not use variable P)\n");
		return;
	}
	var = s[0];
	
	elmt_num = var - 'A';
	push(variable[elmt_num]);
}

// =========================================
// @brief  底と指数を指定し,べき乗を計算する
// @param  無し
// @return 無し
// @note   無し
// =========================================
void CalcPow(void)
{
	double base, exponent;
	double result;
	
	exponent = pop();
	base = pop();
	result = pow(base, exponent);
	push(result);
}

// ==================================
// @brief  ネイピア数eのx乗を計算する
// @param  無し
// @return 無し
// @note   無し
// ==================================
void CalcExp(void)
{
	double x;
	double result;
	
	x = pop();
	result = exp(x);
	push(result);
}

// ========================================
// @brief  指定したラジアンの正弦を計算する
// @param  無し
// @return 無し
// @note   無し
// ========================================
void CalcSin(void)
{
	double y;
	double rad;
	
	rad = pop();
	y = sin(rad);
	push(y);
}

// =======================================================
// @brief  スタックにおける一番上の値をポップせずに印字し,
// @brief  複製する.
// @brief  印字した値を変数Pに格納する.
// @param  無し
// @return 無し
// @note   無し
// =======================================================
void NoPopPrintAndDup(void)
{
	int c;
	int elmt_num;
	
	if (sp > 0) {
		printf("\t%.8g\n", val[sp - 1]);
		
		elmt_num = 'P' - 65;
		variable[elmt_num] = val[sp - 1];
	}
	else {
		printf("error: stack empty\n");
	}
	
	while ((c = getch()) != '\n' || c == EOF) {
		;
	}
}

// =============================================
// @brief  スタックにおける上2つの要素を交換する
// @param  無し
// @return 無し
// @note   無し
// =============================================
void ChangeTwoElmOnStack(void)
{
	double d_tmp1, d_tmp2;
	
	if (sp >= 2) {
		d_tmp2 = pop();
		d_tmp1 = pop();
		push(d_tmp2);
		push(d_tmp1);
	}
	else {
		printf("error: stack empty\n");
	}
}

// =====================================
// @brief  fの値をスタックにプッシュする
// @param  f [in],プッシュする値
// @return 無し
// @note   無し
// =====================================
void push(double f)
{
	if (sp < MAXVAL) {
		val[sp++] = f;
	}
	else {
		printf("error: stack full, can't push %g\n", f);
	}
}

// ==============================================
// @brief  スタックから一番上の値をポップして返す
// @param  無し
// @return val[--sp] -> スタックから取得した値
// @note   エラーの場合は return で 0.0 を返す
// ==============================================
double pop(void)
{
	if (sp > 0) {
		return val[--sp];
	}
	else {
		printf("error: stack empty\n");
		return 0.0;
	}
}

// ==================================================
// @brief  次の演算子あるいは数値の被演算数を取得する
// @param  s [out],取得した文字列
// @return c -> 演算子や改行コード
// @return NUMBER -> フラグ(数値を取得した)
// @note   無し
// ==================================================
int getop(char s[])
{
	int i, c;
	
	while ((s[0] = c = getch()) == ' ' || c == '\t') {
		;
	}
	s[1] = '\0';
	if (!isdigit(c) && c != '.' && c != '-') {
		return c;
	}
	
	i = 0;
	if (c == '-') {
		while (isdigit(s[++i] = c = getch())) {
			;
		}
		if (i == 1) {
			if (c != EOF) {
				ungetch(c);
			}
			c = '-';
			return c;
		}
	}
	else if (isdigit(c)) {
		while (isdigit(s[++i] = c = getch())) {
			;
		}
	}
	if (c == '.') {
		while (isdigit(s[++i] = c = getch())) {
			;
		}
	}
	s[i] = '\0';
	if (c != EOF) {
		ungetch(c);
	}
	
	return NUMBER;
}

// =================================================
// @brief  1文字を取得する
// @param  無し
// @return buf[--bufp] -> バッファに押し戻された文字
// @return getchar() -> 入力された1文字
// @note   無し
// =================================================
int getch(void)
{
	return (bufp > 0) ? buf[--bufp] : getchar();
}

// ============================================
// @brief  文字を入力に戻す(バッファに格納する)
// @param  c [in],入力に戻す文字
// @return 無し
// @note   無し
// ============================================
void ungetch(int c)
{
	if (bufp >= BUFSIZE) {
		printf("ungetch: too many characters\n");
	}
	else if (c == EOF) {
		printf("ungetch: EOF can't be buffered\n");
	}
	else {
		buf[bufp++] = c;
	}
}

実行結果

以下は、プログラム kr_4_10.c を実行しています。

***@ubuntu:~/***/test/c$ 
***@ubuntu:~/***/test/c$ gcc -Wall -Wextra kr_4_10.c -lm -o kr_4_10
***@ubuntu:~/***/test/c$ ./kr_4_10
2 3 +
	5
5.213 2 %
	1.213
-100 50 -
	-150
1 2 + d
	3
10 *
	30
-1 5 -
	-6
-1 5 c -
	6
0.52359877 s
	0.5
3 e
	20.085537
2 3 p
	8
w Y 100
	100
r Y
	100
23 r Y +
	123
i Y
	0
r Y
	0
111 222 +
	333
r P
	333
444 r P +
	777