C++サンプル集

【第1部】C言語(K&R検証)編

0025.逆ポーランド電卓を実装する

 逆ポーランド電卓とは
(12 + 34) * (56 - 78)
 ならば
12 34 + 56 78 - *
 と記述する電卓です。
 K&Rでは、このサンプルの説明をするのに、複数のソースファイルへの記述が説明されています。
 ここでは、それに習い、複数ファイルに記述します。
 なお、以下のソースは、レポジトリprojects/Sample0025_1にあります。(ファイルの中身はK&Rの説明とは若干違います)
 VS2019で開けばビルドできます。

ソース紹介

 もし0からプロジェクトを作成する場合はC++コンソールアプリケーション-空のプロジェクトを作成して、以下のファイルを追加してビルドしてください。この場合VS2019でなくてもVS2015もしくはVS2017でも実装できます。
CPPファイルに記述
calc.h
#pragma once

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cctype>
using namespace std;

#define NUMBER '0'
void push(double d);
double pop();
int getop(char s[]);
int getch();
void ungetch(int c);
stack.cpp
#include "calc.h"

#define MAXVAL 100
int sp = 0;
double val[MAXVAL];

void push(double d) {
    if (sp < MAXVAL) {
        val[sp++] = d;
    }
    else {
        cout << "push: スタックがいっぱいです" << endl;
    }
}
double pop() {
    if (sp > 0) {
        return val[--sp];
    }
    else {
        cout << "pop: スタックが空です" << endl;
        return 0.0;
    }
}
getop.cpp
#include "calc.h"

int getop(char s[]) {
    int i, c;
    while ((s[0] = c = getch()) == ' ' || c == '\t')
        ;
    s[1] = '\0';
    if (!isdigit(c) && c != '.') {
        return c;
    }
    i = 0;
    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;
}
getch.cpp
#include "calc.h"

#define BUFSIZE 100

char buf[BUFSIZE];
int bufp = 0;

int getch() {
    return (bufp > 0) ? buf[--bufp] : cin.get();
}
void ungetch(int c) {
    if (bufp >= BUFSIZE) {
        cout << "ungetch: これ以上追加できません" << endl;
    }
    else {
        buf[bufp++] = c;
    }
}
main.cpp
#include "calc.h"

#define MAXOP 100

int main()
{
    int type;
    double op2;
    char s[MAXOP];
    while ((type = getop(s)) != EOF) {
        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 {
                cout << "0除算です" << endl;
            }
            break;
        case '\n':
            cout << '\t' << pop() << endl;
            break;
        default:
            cout << "コマンドが不明です" << endl;
            break;
        }
    }
    return 0;
}
出力
12 34 + 56 78 - *
        -1012
12.345 67.89 - 987.65 43.21 + *
        -57259.1
12 34 - 56 78 + /
        -156
^Z
 もし浮動小数点の精度を上げたければ、0024.小数点のある足し算を実装するで説明した方法でcoutの設定を変えてください。

サンプル説明

 このサンプルはK&Rの中でもとくに有名なサンプルです。逆ポーランドというなじみの薄い文法ですが、矛盾なく複雑な計算もできます。
 僕は、このソースで感心するのはgetop()関数の
    if (c != EOF) {
        ungetch(c);
    }
 の部分です。これは入力を読みすぎた時に戻す処理ですが、なかなか思いつくものではないなあ、と思います。(解説になってない)
 まあ、じっくり読んでください。読めば読むほど味のあるソースです。

K&Rでの記述

 元になったのは第4章:関数とプログラム構造に記述されています。外部変数の説明のところです。