第16章練習問題4

| コメント(0)

 GUIの基本として、ボタンをクリックした時の動作をコールバックで実装する方式を勉強。コールバック関数を登録する際のラムダ式は、まだ記述方法がよくわかっていない。真似をしている段階だ。
 練習問題4は、以下の通り。

 円、正方形、正三角形、六角形をそれぞれ作成するアイテムが含まれたメニューを作成する。座標を指定するための入力ボックスを(1つまたは2つ)作成し、メニューアイテムを押すことによって作成された図形をその座標に配置する。ドラッグ&ドロップはサポートしない。

 メニューを作って、それぞれのボタンから図形を描画する関数を呼べばいいだけだ、と思った。確かにその通りなのだが、それが結構やっかいであった。17.2.9の手法を借用した。
 さらに、円、正方形は、CircleおよびRectangleを使えばいいが、正三角形、六角形はライブラリにないので、Closed_polylineでinitializer_listを使えるようにした関数NClosed_polylineを作って、作成するようにした。とりあえず、動いたのが、下記のコードである。

#include "Simple_window.h"
#include "Graph.h"

struct Draw_menu :Window {
    Draw_menu(Point xy, int w, int h, const string& title);
    Menu shape_menu;

private:
    const int size = 50; //Size of Shape
    Shape* p;
    In_box posx;
    In_box posy;
    void circle_pressed();
    void square_pressed();
    void triangle_pressed();
    void hexagon_pressed();
    void quit_pressed();
};

Draw_menu::Draw_menu(Point xy, int w, int h, const string& title)
    :Window{ xy, w, h, title },
    posx{ Point{x_max() - 310,0},50,20,"x:" },
    posy{ Point{x_max() - 210,0},50,20,"y:" },
    shape_menu{ Point{x_max() - 100,0},70,20,Menu::vertical,"shape" }
{
    attach(posx);
    attach(posy);
    shape_menu.attach(new Button{ Point{0,0},0,0,"circle",
        [](Address, Address pw) {reference_to<Draw_menu>(pw).circle_pressed(); } });
    shape_menu.attach(new Button{ Point{ 0,0 },0,0,"square",
        [](Address, Address pw) {reference_to<Draw_menu>(pw).square_pressed(); } });
    shape_menu.attach(new Button{ Point{ 0,0 },0,0,"triangle",
        [](Address, Address pw) {reference_to<Draw_menu>(pw).triangle_pressed(); } });
    shape_menu.attach(new Button{ Point{ 0,0 },0,0,"hexagon",
        [](Address, Address pw) {reference_to<Draw_menu>(pw).hexagon_pressed(); } });
    shape_menu.attach(new Button{ Point{ 0,0 },0,0,"quit",
        [](Address, Address pw) {reference_to<Draw_menu>(pw).quit_pressed(); } });
    attach(shape_menu);
}

void Draw_menu::circle_pressed() {
    int x = posx.get_int();
    int y = posy.get_int();

    p = new Circle{ Point{ x + size/2, y + size/2}, size/2 };
    attach(*p);
    redraw();

}

void Draw_menu::square_pressed() {
    int x = posx.get_int();
    int y = posy.get_int();

    p = new Graph_lib::Rectangle { Point{ x , y }, size, size };
    attach(*p);
    redraw();

}

struct NClosed_polyline :Closed_polyline {
    NClosed_polyline(initializer_list<Point> lst){
        for (auto p:lst) Shape::add(p); }   
};


void Draw_menu::triangle_pressed() {
    int x = posx.get_int();
    int y = posy.get_int();
    const int h = static_cast<int>(sqrt(2.0) / 2.0 * size);

    p = new NClosed_polyline{
        Point{x + size / 2, y + (size - h) / 2 },
        Point{x + size,     y + (size + h) / 2 },
        Point{x,            y + (size + h) / 2 }};

    attach(*p);
    redraw();
}

void Draw_menu::hexagon_pressed() {
    int x = posx.get_int();
    int y = posy.get_int();
    const int h = static_cast<int>(sqrt(3.0) / 2.0 * size);

    p = new NClosed_polyline{
        Point{x + size / 4,     y + (size - h) / 2 },
        Point{x + size * 3 / 4, y + (size - h) / 2 },
        Point{x + size,         y + size / 2 },
        Point{x + size * 3 / 4, y + (size + h) / 2 },
        Point{x + size / 4,     y + (size + h) / 2 },
        Point{x,                y + size / 2 } };

    attach(*p);
    redraw();
}

void Draw_menu::quit_pressed() {
    hide();
}


int main()
{
    using namespace Graph_lib;

    try {
        Draw_menu win{ Point{100,100}, 600, 400, "Draw Shape" };
        return gui_main();
    }

    catch (exception& e) {
        cerr << "exception: " << e.what() << endl;
        return 1;
    }

    catch (...) {
        cerr << "Some exception" << endl;
        return 2;
    }
}

第15章練習問題5

| コメント(0)

 この章で、関数をグラフ化する方法とか、ラムダ式の書き方とかを勉強。練習問題5をやることにした。

1-1/3+1/5-1/7+1/9-1/11+・・・をアニメーションにする。これは「ライプニッツの級数」と呼ばれ、π/4に収束する。

 軸の描き方とか、いろいろ面倒な用意をする必要がある。πの値は、#define _USE_MATH_DEFINESを宣言しておくと、M_PIで値が定義されているようである。
 アニメーションといっても、単一の値が収束していくだけである。級数の添え字をnとし、nの値をX軸にして、その時の級数の値をy軸に表示するような構成にしてみた。あまり面白い表示にはなっていないが、一応、私の作った例は下記の通り。


#define _USE_MATH_DEFINES
#include "Simple_window.h"
#include "Graph.h"

double leibniz(int n) {
    double r = 1;
    while (n > 0) {
        if (n % 2 == 0) r += 1.0 / (2.0 * n + 1.0);
        else r -= 1.0 / (2.0 * n + 1.0);
        --n;
    }
    return r;
}

 

int main()
{
    using namespace Graph_lib;

    constexpr int xmax = 600;
    constexpr int ymax = 400;

    constexpr int margin = 40;

    constexpr int xlength = xmax - margin;
    constexpr int ylength = ymax - margin;

    constexpr int x_orig = margin;
    constexpr int y_orig = ymax- margin;
    const Point orig{ x_orig,y_orig };

    constexpr int nmax = 50;

    constexpr double x_scale = xlength / nmax;
    constexpr double y_scale = (ylength-margin) / 5.0 * 4.0; //縦軸を0.25刻みで5個分取る

    Simple_window win{ Point{100,100},xmax,ymax,"Functopn graphing" };

    Axis x{ Axis::x,Point{ margin,y_orig },xlength,nmax,"one notch = 1" };
    Axis y{ Axis::y,Point{ x_orig,ylength },ylength - margin,5,"one notch =0.25" };
    x.set_color(Color::red);
    y.set_color(Color::red);
    win.attach(x);
    win.attach(y);

    Function pi4([](double x) {return M_PI / 4;}, 0, nmax, orig, 200, x_scale, y_scale); // pi/4
    pi4.set_color(Color::blue);
    win.attach(pi4);

    for (int n = 1;n < 50;++n) {
        ostringstream ss;
        ss << "Leibniz; n=" << n;
        win.set_label(ss.str());

        double y;
        int xp,yp;

        xp = x_orig + n*x_scale;

        y = leibniz(n);
        yp = ylength - y*y_scale;

        Circle l{ Point{ xp, yp },2 };

        win.attach(l);

        win.wait_for_button();

        win.detach(l);
    }

}

 プログラムそのものよりも、ボタンを押す都度、画面が小さくなっていくという現象があって、解決に時間を取ってしまった。c++ - Stroustrup's "Simple_window" shrinks when pushing "Next" button - Stack Overflowで、ある環境でのみ生じるFLTKのバグであることがわかった。この記事では、FLTK1.3.3で解決済みとあったが、私の環境では、それでもだめで、1.4.2をダウンロードし、環境を再構築して、やっと動かすことができた。この手の問題は、勉強の本質の部分よりも時間が必要でやっかいだ。

第14章:ドリル1~4

| コメント(0)

 グラフィックスというよりは、C++の機能の解説の章である。なかなか読みごたえがあった。練習問題は、あまり面白そうな問題はないので、まじめにドリルをやることにした。
 まず、ドリル1~3の問題は下記の通り。

1.仮想関数vfと非仮想関数fを持つクラスB1を定義する。これらのクラスをB1クラスの内部で定義し、それぞれの名前(”B1::vf()”など)を出力するように実装する。これらの関数はpblicで宣言する。B1オブジェクトを作成し、これらの関数を呼び出す。
2.B1の派生クラスとしてD1を作成し、vf関数をオーバーライドする。D1オブジェクトを作成し、そこでvf関数とf関数とを呼び出す。
3.B1への参照(B!&)を定義し、それを作ほど定義したD1オブジェクトとして初期化する。その参照を使ってvf関数とf関数を呼び出す。

 問題文だけ読むと何が書いてあるのか、よくわからない。14.3.3の例題を参考に、作ったのが、以下の通り。

#include "..\..\std_lib_facilities.h"

struct B1 {
    virtual void vf() const { cout << "B1::vf()" << endl; }
    void f() const { cout << "B1::f()" << endl;; }
};


struct D1:B1 {
    void vf() const override { cout << "D1::vf()" << endl; }

};


int main() {
    B1 b1;
    D1 d1;

    b1.vf();
    b1.f();

    d1.vf();
    d1.f();

    B1& b{ d1 };
    b.vf();
    b.f();
}

 この時の出力結果は、

image

 

 ドリル4の問題は、以下の通り。

4.D1にfという関数を定義し、問題1~3を繰り返し、その結果を説明する。

 ほとんど、問題1~3と同様のコードになる。

#include "..\..\std_lib_facilities.h"

struct B1 {
    virtual void vf() const { cout << "B1::vf()" << endl; }
    void f() const { cout << "B1::f()" << endl;; }
};


struct D1 :B1 {
    void vf() const override { cout << "D1::vf()" << endl; }
    void f() const { cout << "D1::f()" << endl;; }
};


int main() {
    B1 b1;
    D1 d1;

    b1.vf();
    b1.f();

    d1.vf();
    d1.f();

    B1& b{ d1 };
    b.vf();
    b.f();
}

 出力は、

image

 

 ドリル1~3では、クラスD1にはf()を定義していないので、d1.f();では、B1で定義されたf()を実行する。一方、ドリル4では、D1でf()を定義したので、d1.f();ではD1で定義したf()が実行されている。

第13章練習問題2

| コメント(0)

 第12章の練習問題はやらず。グラフィックス関係は解答例もない。第13章は、勉強になりそうな、練習問題2をやる。練習問題は、下記の通り。

 角丸四角形を描画する。4つの直線と4つの弧で構成されるBOXクラスを定義する。


 仕様としては、Rectangleに、rrという引数を追加することにした。このrrは、弧の半径だ。Rectangleの実装を真似て作ったのが、下記の解答例。P.436のdraw_line()関数は、基底クラスのShapeで仮想関数として定義されている関数なので、派生クラスごとに、同じ名前の関数を用意する必要がある。この部分が、最初わからず、少し苦労した。解答例は、下記の通り。

#include "Simple_window.h"
#include "Graph.h"

struct Box : Shape {
    Box(Point xy, int ww, int hh, int rr);
    Box(Point x, Point y, int rr);
    void draw_lines() const;

    int height() const { return h; }
    int width() const { return w; }
private:
    int h;
    int w;
    int r;
    Point p;
};

Box::Box(Point xy, int ww, int hh, int rr)
    : w{ ww }, h{ hh }, r{ rr }, p{ xy }
{
    if (h<=0 || w<=0 || r<=0) error("Bad Box: non-positive side");
    if (h<=r*2 || w<=r*2) error("Bad Box: arc is too big");
}

Box::Box(Point x, Point y, int rr)
    : w{ y.x - x.x }, h{ y.y - x.y }, r{ rr }, p{ x }
{
    if (h <= 0 || w <= 0 || r <= 0) error("Bad Box: non-positive side");
    if (h <= r * 2 || w <= r * 2) error("Bad Box: arc is too big");
}

void Box::draw_lines() const
{

    fl_line(p.x + r, p.y , p.x + w - r, p.y );
    fl_line(p.x + r, p.y + h , p.x + w - r, p.y + h );
    fl_line(p.x , p.y + r, p.x , p.y + h - r);
    fl_line(p.x + w , p.y + r, p.x + w , p.y + h - r);
    fl_arc(p.x, p.y, r+r, r+r, 90, 180);
    fl_arc(p.x + w -r-r, p.y, r + r, r + r, 0, 90);
    fl_arc(p.x, p.y + h - r - r, r + r, r + r, 180, 270);
    fl_arc(p.x + w - r - r, p.y + h - r - r, r + r, r + r, 270, 360);
}

int main()
{
    using namespace Graph_lib;
    Point tl(100, 100);
    Simple_window win(tl, 600, 400, "Canvas");


    Box b{ Point{100,100},100,100,10};

    win.attach(b);
    win.wait_for_button();
}

 第12章から16章までは、グラフィック関係である。当然、何らかのライブラリーが必要になる。この本では、FLTKを使う。そのインストール方法は、付録に書いてある。FLTKのインストールうまくいき、FLTKを使ったテストコードも動く。ところが、次のステップのhttp://www.stroustrup.com/Programming/PPP2code/からソースコードを持ってきてP.424の構成にしてもも、うまくコンパイルできない。少しくらいなら、なんとかなるとおもっったっが、次々と問題があって、あきらめた。
 ここまできて、以前、この第1版を勉強したときに 「ストラウストラップのプログラミング入門」マテリアルのお世話になったことを思い出した。でも、今度は、ここに書いてある通りにやっても、うまくいかない。あの時となんかが変わったのだ。
 最終的に、P.424の構成のまま、この環境に、上記のおすなばセットのソースコードをそのままもってきて、うまく動くようになった。この環境構築に2日ほどっかかってしまった。

第11章練習問題10

| コメント(0)

 第11章は、標準入出力をカスタマイズする方法である。ホワイトスペースを追加するというカスタマイズの手法は、C++言語らしい方法で非常に参考になった。
 練習問題10は、以下の通り。

 vector<string> split(const string& s)関数を記述する。この関数は、引数sをホワイトスペースに基づいて部分文字列に分割し、それらを格納したvectorを返す。

 文字列ストリームを使う問題だろう、ということで、私の回答は、以下の通り。

#include "..\..\std_lib_facilities.h"

vector<string> split(const string& s) {
    istringstream is{ s };
    vector<string> svector;
    string str;

    while (true) {
        is >> str;
        if (is.eof()) return svector;
        svector.push_back(str);
    }
}

int main() {
    vector<string> result;
    string test = "This is test. ";

    result = split(test);
    for (int i = 0; i < result.size(); i++) cout << i << ":" << result[i] << endl;

    return 0;
}

 模範解答は、http://www.stroustrup.com/Programming/Solutions/Ch11/e11-9.cpp。私の回答は、eofを検出した時に、無限ループをreturnで抜けるという方法になっている。一夫、模範解答は、while (is>>buf)でループを作っている。明らかにこちらの方がいい。どうも、このあたりの私の悪い癖を直す必要がありそうだ。

第10章練習問題11

| コメント(0)

 第10章は、入力ストリームと出力ストリームである。実務上は、エラー処理に関わる多くの部分がこの範疇に属するので重要な章なのだが、退屈な章であることも確かである。最後のところに、「ところで、実際にコードと説明をすべて読んだだろうか。それとも、ぼんやり眺めて読み飛ばしただろうか。」という問いかけがあって、笑ってしまった。確かに、きっちりと読みには、つらい章である。
 練習問題11は、次の通り。

 テキストファイルに含まれている整数をすべて合計するプログラムを作成する。これらの整数はホワイトスペースで区切られた情愛で含まれている。

 基本的には、1文字読んで、読み戻し、読んだ1文字が数字なら数字として、読んだ1文字が数字以外なら文字列として読むという構造にした。読んだ1文字が'-'なら、マイナスとして取り扱うようにしている。私の回答は、下記の通り。

#include "..\..\std_lib_facilities.h"

int main(){

try {
    cout << "ファイル名を入力してください:";
    string iname;
    cin >> iname;
    ifstream ist{ iname };
    if (!ist) error("指定されたファイルは開けませんでした", iname);

    int sum = 0;
   
    while (true) {
        char ch;

        ist >> ch;

        if (ist.bad()) error("入力にエラーが発生しました");
        if (ist.eof()) break;


        if ((ch >= '0') && (ch <= '9')) {
            ist.unget();
            int n;
            ist >> n;
            sum += n;
        } else if (ch=='-') {
            ist >> ch;
            if ((ch >= '0') && (ch <= '9')) {
                ist.unget();
                int n;
                ist >> n;
                sum -= n;
            }
        } else {
            ist.unget();
            string s;
            ist >> s;
        }
    }

    cout << "合計は:" << sum << endl;
    return 0;
}
catch (exception& e) {
    cerr << "exception: " << e.what() << endl;
    char c;
    while (cin >> c&& c != ';');
    return 1;
}
catch (...) {
    cerr << "exception\n";
    char c;
    while (cin >> c && c != ';');
    return 2;
}

}

 

 模範解答http://www.stroustrup.com/Programming/Solutions/Ch10/e10-11.cppは、少し方針が異なる。文字のチェックなどを関数にしていて、確かにその方が見通しがいいと思う。’-‘の処理は方針が違うようだ。

第9章練習問題13

| コメント(0)

 第9章は、クラスに関する。この部分は、C言語とC++言語との大きな違いでる。練習問題は、いかにもクラスの問題らしい練習問題13を選んだ。問題文は、以下の通り。

 有理数を表すクラスRationalを設計し、実装する。代入演算子、加算演算子、減算演算子、乗算演算子、除算演算子、等価演算子を提供する。また、doubule型への変換をサポートする。

 私の回答は、まずヘッダファイルRational.hは、下記の通り。問題文にはなかった、分母取り出し関数と分子取り出し関数、有理数を出力するための関数を追加した。

#include "..\..\std_lib_facilities.h"

class Rational {
public:
    class Invalid {};

    Rational(int denominator, int numerator);
    Rational();

    double real() const { return double(b) / double(a); }
    int den() const { return a; }
    int num() const { return b; }

private:
    int a, b;
};


Rational operator+(const Rational& x, Rational& y);
Rational operator-(const Rational& x, Rational& y);
Rational operator*(const Rational& x, Rational& y);
Rational operator/(const Rational& x, Rational& y);
bool operator==(const Rational& x, Rational& y);
ostream& operator<<(ostream& os, const Rational& x);

 これの実装の関数Rational.cppは下記の通り。正規化するための最大公約数関数gcm、正規化関数normalizeは、この実装で使うための関数である。

#include "Rational.h"


int gcm(int x, int y) {
    int r;

    x = abs(x);
    y = abs(y);

    if (x < y) swap(x, y);

    /* ユークリッドの互除法 */
    r = x % y;
    while (r != 0) {
        x = y;
        y = r;
        r = x % y;
    }

    return y;
}

void normalization(int& x, int& y) {

    int g{ 1 };

    if ((x!=0) && (y!=0))g = gcm(x, y);

    x = x / g;
    y = y / g;

    if (y == 0) x = 1;

    return;
}


Rational::Rational(int denominator, int numerator)
{
    if (denominator == 0) throw Invalid();

    normalization(denominator, numerator);

    a = denominator;
    b = numerator;

}

Rational::Rational()
    : a{ 1 }, b{ 1 }
{

}

Rational operator+(const Rational& x, Rational& y)
{
    int a, b;
    a = x.den() * y.den();
    b = x.num() * y.den() + y.num()*x.den();
    return { a,b };
}

Rational operator-(const Rational& x, Rational& y)
{
    int a, b;
    a = x.den() * y.den();
    b = x.num()*y.den() - y.num()*x.den();
    return{ a,b };
}

Rational operator*(const Rational& x, Rational& y)
{
    int a, b;
    a = x.den() * y.den();
    b = x.num() * y.num();
    return{ a,b };
}
Rational operator/(const Rational& x, Rational& y)
{
    int a, b;
    if (y.num() == 0) error("divided by zero ");
    a = x.den() * y.num();
    b = x.num() * y.den();
    return{ a,b };
}

bool operator==(const Rational& x, Rational& y)
{
    if (x.den() != y.den()) return false;
    if (x.num() != y.num()) return false;
    return true;
}

ostream& operator<<(ostream& os, const Rational& x)
{
    return os << x.num() << "/" << x.den() << endl;
}


 

最後にテストのメイン関数

#include "Rational.h"
int main() {
    try {
        Rational x, y;

        cout << x;   //デフォルトは1/1

        x = Rational{ 5,4 }; // 4/5
        cout << x;
        x = Rational{ 10,8 };  //これも4/5 正規化ができているか?
        cout << x; 

        y = Rational{ 5,4 }; // 4/5
        cout << x + y;
        cout << x - y;
        cout << x * y;
        cout << x / y;
        cout << (x == y) << endl;
        cout << x.real() << endl;


        return 0;
    }

    catch (Rational::Invalid) {
        cerr << "Denominator = 0" << endl;
        return 1;
    }
    catch (exception& e) {
        cerr << "exception: " << e.what() << endl;
        char c;
        while (cin >> c&& c != ';');
        return 2;
    }
}

 

 この問題は、結構勉強になった。模範解答http://www.stroustrup.com/Programming/Solutions/Ch9/e9-13.cppでは、有理数がマイナスの時に、分母ではなく分子をマイナスにするようにしている。確かにこちらの方が良さそうだ。
 私の回答では、符号を反転させるーとか、等価演算の!=とかを忘れているのも問題だ。一方、除算の時には、ゼロの除算でエラーを出すようにしているのは、私の回答例の方がいいかもしれない。
 分母と分子を、私の回答では隠蔽するようになっている。これは、第9章のDateクラスに倣ったからだ。でも、解答例のように、あっさりPublicに置く方がいいかもしれない。これは、どうなんだろう?有理数の使い方にもよるのだろうけど、1組で数値を表すということを考えると、やっぱり隠蔽して、分母、分子を直接さわれない方がいいように思う。

第8章練習問題2~3

| コメント(0)

 第8章は、C++言語の記法である。関数の参照渡しのconstとか、参考になる。
 練習問題2~3を一度に解く。練習問題は、下記の通り。

 int型のvectorをcoutに出力する関数printを記述する。この関数は引数として、出力に「ラベルを付ける」ための文字列とvectorの2つを受け取る。
 フィビナッチ数列のvectorを作成し、printで出力する。vectorを作成するための関数としてfibonacci(x,y,v,n)を記述する。この場合、整数xとyはint型、vは空のvector<int>、nはvに設定する要素の個数である。

 printは、本文で説明していたvectorの参照渡しで内容を変更しないconstで渡す。逆にfibonaccは、vを参照渡しとして、中身を変更するということだろう。ということで、私の回答は、下記の通り。

#include "..\..\std_lib_facilities.h"

void print(string label,const vector<int>& v)
{
    for (int i = 0; i < v.size(); i++) {
        cout << label << "[" << i << "]=" << v[i] << "\n";
    }
    return;
}

void fibonacci(int x, int y, vector<int>& v, int n)
{
    if ((v.size()<n)||(n < 2)) return;

    v[0] = x;
    v[1] = y;

    for (int i = 2; i < n; i++) {
        v[i] = v[i - 2] + v[i - 1];
    }

    return;
}

int main() {
    const int N{ 10 }; /* Nはフィボナッチ数列の数を指定する定数 */
    vector<int> v(N);
    fibonacci(1, 2, v, N);
    print("fibonacci", v);
    return 0;
}

 

 回答例http://www.stroustrup.com/Programming/Solutions/Ch8/e8-2.cppは、pushbackを使って値を入れていくということにしている。問題文のvは空のvector<int>という指定に従うなら、確かにそうすべきだった。

第7章練習問題1~3

| コメント(0)

 前回、デバッグしたつもりのソースコードを使って練習問題を解くことにする。回答例http://www.stroustrup.com/Programming/Solutions/Ch7/e7-1.cppの方が、練習問題1~3を一度に解いていたので、これにならうことにする。練習問題1~3は、下記の通り。

1.電卓プログラムの変数名でアンダースコアを使用できるようにする。
2.代入演算子=を追加し、letを使って変数を宣言した後に、その値を変更できるようにする。これがなぜ便利なのか、そしてどのような場合に問題の原因になるかについて話し合う。
3.実際に値を変更できない名前付き定数を追加する。

 練習問題1は簡単だ。 Token_stream::get()で、2文字目以降をアルファベットか数字か、となっている部分に'_'を追加すればいい。
 練習問題2は、変数名が初めてletされたときは登録し、それ以外のときは値をセットする。declaration()を少し手を加えた。
 練習問題3は、少し複雑である。問題文にヒントがあって、それを引用する。

定数と変数を区別するメンバーをVariableに追加し、それをset_value関数でチェックする必要がある。単にpiとeを定数として定義するのではなく、ユーザーが定数を定義できるようにしたい場合は、ユーザーがそれを表現するためのconst pi = 3.14;のような表記を追加する必要がある。

 定数ならtrueとなるメンバーを追加し、定数と変数とは別の関数を呼び出すように変えてみた。

#include "..\..\std_lib_facilities.h"

const char let = 'L';
const char quit = 'Q';
const char print = ';';
const char number = '8';
const char name = 'a';
const char CONSTANT = 'c'; /*練習問題3*/

const string prompt = "> ";
const string result = "= ";

class Token {
public:
    char kind;
    double value;
    string name;

    Token() :kind(0) { }
    Token(char ch) :kind{ ch } { }
    Token(char ch, double val) :kind{ ch }, value{ val } { }
    Token(char ch, string n) :kind{ ch }, name{ n } { }
};

class Token_stream {
public:
    Token_stream() :full{ 0 }, buffer{ 0 } { }

    Token get();
    void unget(Token t) { buffer = t; full = true; };
    void ignore(char c);

private:
    bool full;
    Token buffer;
};

 

Token Token_stream::get()
{
    if (full) { full = false; return buffer; }
    char ch;
    cin >> ch;
    switch (ch) {
    case '(':
    case ')':
    case '+':
    case '-':
    case '*':
    case '/':
    case '%':
    case ';':
    case '=':
        return Token(ch);
    case '.':
    case '0':
    case '1':
    case '2':
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
    case '8':
    case '9':
    {    cin.unget();
    double val;
    cin >> val;
    return Token(number, val);
    }
    default:
        if (isalpha(ch)) {
            string s;
            s += ch;
            while (cin.get(ch) && (isalpha(ch) || isdigit(ch)) || ch=='_') s += ch; /*練習問題1*/
            cin.unget();
            if (s == "let") return Token(let);
            if (s == "quit") return Token(quit);
            if (s == "const") return Token(CONSTANT); /*練習問題3*/
            return Token(name, s);
        }
        error("Bad token");
    }
}


void Token_stream::ignore(char c)
{
    if (full && c == buffer.kind) {
        full = false;
        return;
    }
    full = false;

    char ch;
    while (cin >> ch)
        if (ch == c) return;
}

class Variable {
public:
    string name;
    double value;
    bool constant; /*練習問題3*/
    Variable(string n, double v, bool f) :name{ n }, value{ v }, constant{ f } { }
};

vector<Variable> names;

double get_value(string s)
{
    for (int i = 0; i<names.size(); ++i)
        if (names[i].name == s) return names[i].value;
    error("get: undefined name ", s);
}

void set_value(string s, double d)
{
    for (int i = 0; i <= names.size(); ++i)
        if (names[i].name == s) {
            if (names[i].constant) error("定数には代入できません");  /*練習問題3*/
            names[i].value = d;
            return;
        }
    error("set: undefined name ", s);
}

bool is_declared(string s)
{
    for (int i = 0; i<names.size(); ++i)
        if (names[i].name == s) return true;
    return false;
}

Token_stream ts;

double expression();

double primary()
{
    Token t = ts.get();
    switch (t.kind) {
    case '(':
    {    double d = expression();
    t = ts.get();
    if (t.kind != ')') error("'(' expected");
    return d;
    }
    case '-':
        return -primary();
    case '+':
        return primary();
    case number:
        return t.value;
    case name:
        return get_value(t.name);
    default:
        error("primary expected");
    }
}

double term()
{
    double left = primary();
    while (true) {
        Token t = ts.get();
        switch (t.kind) {
        case '*':
            left *= primary();
            break;
        case '/':
        {    double d = primary();
        if (d == 0) error("divide by zero");
        left /= d;
        break;
        }
        case '%':
        {    double d = primary();
        if (d == 0) error("divide by zero");
        left = fmod(left, d);
        break;
        }
        default:
            ts.unget(t);
            return left;
        }
    }
}

double expression()
{
    double left = term();
    while (true) {
        Token t = ts.get();
        switch (t.kind) {
        case '+':
            left += term();
            break;
        case '-':
            left -= term();
            break;
        default:
            ts.unget(t);
            return left;
        }
    }
}

double declaration()
{
    Token t = ts.get();
    if (t.kind != name) error("name expected in declaration");
    string name = t.name;
    bool not_declared = !is_declared(name); /*練習問題2*/
    Token t2 = ts.get();
    if (t2.kind != '=') error("= missing in declaration of ", name);
    double d = expression();
    if (not_declared) names.push_back(Variable(name, d, false)); /*練習問題2*/
    else set_value(name, d); /*練習問題2*/
    return d;
}

double const_set() /*練習問題3*/
{
    Token t = ts.get();
    if (t.kind != name) error("name expected in declaration");
    string name = t.name;
    if (is_declared(name)) error("定数が既に宣言されています");
    Token t2 = ts.get();
    if (t2.kind != '=') error("= missing in declaration of ", name);
    double d = expression();
    names.push_back(Variable(name, d, true));
    return d;
}

double statement()
{
    Token t = ts.get();
    switch (t.kind) {
    case let:
        return declaration();
    case CONSTANT:  /*練習問題3*/
        return const_set();
    default:
        ts.unget(t);
        return expression();
    }
}

void clean_up_mess()
{
    ts.ignore(print);
}

 

void calculate()
{
    while (true) try {
        cout << prompt;
        Token t = ts.get();
        while (t.kind == print) t = ts.get();
        if (t.kind == quit) return;
        ts.unget(t);
        cout << result << statement() << endl;
    }
    catch (runtime_error& e) {
        cerr << e.what() << endl;
        clean_up_mess();
    }
}

int main()

try {
    calculate();
    return 0;
}
catch (exception& e) {
    cerr << "exception: " << e.what() << endl;
    char c;
    while (cin >> c&& c != ';');
    return 1;
}
catch (...) {
    cerr << "exception\n";
    char c;
    while (cin >> c && c != ';');
    return 2;
}