Sibainu Relax Room

柴犬と過ごす

VC98 とストラウストラップのプログラミング入門2(cin の理解)

3日前の散歩で Pocky ちゃんとお互い気が合い活発に遊びました。

概要

「ストラウストラップのプログラミング入門」の P180「プログラム構造」まで読み進みました。

しかし、文法以降の6章の後半は難しいです。ここ3日ほどここで停滞してました。

文法に関しては、これまでのプログラミングでは考えることがなかったレベルで、当初全く理解ができませんでした。数回考えてやっと糸口が見つかったかという感覚で、コードを見るまで理解ができていません。

P169の完成したコードを見てやっと理解することができました。

しかし、このように考えることで出来るとは思いもよらず新しい思考回路の発見になりました。

私には、cin の理解がポイントになりました。

この本は普通の入門書ではないです。

class Token

P151のクラス Tokun のコントラスタの記述が次のようになっています。

class Tokun {
public:
  char kind;
  double value;
  Tokun(char ch) 
    :kind(ch), value(0){}
  Tokun(char ch, double val)
    :kind(ch), value(val){}
};

この形は初めて見ます。これまでの私の感覚からすると次のように思い描いていましたが、こんな書き方もあるのだと新たに知りました。

class Tokun {
public:
  char kind;
  double value;
  Tokun(char); 
  Tokun(char, double);
};

Tokun::Tokun(char ch) {
  kind = ch;
  value = 0;
}

Tokun::Tokun(char ch, double val) {
  kind = ch;
  value = val;
}

次のコードで確かめてみました。

copy

include "std_lib_facilities.h"

class Tokun {
public:
  char kind;
  double value;
  Tokun(char ch) 
    :kind(ch), value(55){}
  Tokun(char ch, double val)
    :kind(ch), value(val){}
};

int main() {
  Tokun tokun('a');
  cout << tokun.value << "\n";
  cout << tokun.kind << "\n";
  cout << "Hello World!!\n";
  keep_window_open();
  return 0;
}

ビルドができました。

問題なく実行できました。

calculator00.cpp

P169でバージョン1「calculator00.cpp」をダウンロードして試してみるとあるので、自分なりの解釈して試してみました。

copy

#include "std_lib_facilities.h"

class Token {
public:
  char kind;                            // トークンの種類
  double value;                         // 数値 
  Token(char ch)                        // 演算文字からトークンを作成
    :kind(ch), value(0) { }
  Token(char ch, double val)            // オペランド表現と数値からトークンを作成
    :kind(ch), value(val) { }
};

//------------------------------------------------------------------------------

Token get_token();                      // primary()関数のプロトタイプです

//------------------------------------------------------------------------------

double expression();                    // expression()関数のプロトタイプです

//------------------------------------------------------------------------------

double term();                          // term()関数のプロトタイプです

//------------------------------------------------------------------------------

double primary();                       // primary()関数のプロトタイプです

//------------------------------------------------------------------------------

int main() {
  try {
    while (cin)
      cout << expression() << '\n';
    keep_window_open();
  }
  catch (exception& e) {
    cerr << e.what() << endl;
    keep_window_open();
    return 1;
  }
  catch (...) {
    cerr << "exception \n";
    keep_window_open();
    return 2;
  }
  return 0;
}

//------------------------------------------------------------------------------

double expression()
{
  double left = term();                 // term() → primary() → get_token()
    Token t = get_token();              // 入力から次のトークン
    while (true) {
      switch (t.kind) {
      case '+':
        left += term();                 // '+' 後の入力を Trem 評価して加算
        t = get_token();
        break;
      case '-':
        left -= term();                 // '-' 後の入力を Trem 評価して減算
        t = get_token();
        break;
      default:
        return left;                    // '+' '-' が入力されなかったら計算結果を返す
    }
  }
}

//------------------------------------------------------------------------------

double term() {
  double left = primary();              // primary() → get_token()
  Token t = get_token();                // 入力から次のトークン

  while (true) {
    switch (t.kind) {
    case '*':
      left *= primary();
      t = get_token();
      break;
    case '/':
      {
        double d = primary();
        if (d == 0) error("divide by zero");
        left /= d;
        t = get_token();
        break;
      }
    default:
      return left;
    }
  }
}

//------------------------------------------------------------------------------

double primary() {
  Token t = get_token();
  switch (t.kind) {
  case '(':                             // 文法 '(' expression ')'の処理
    {
      double d = expression();
      t = get_token();
      if (t.kind != ')') error("')' expected");
      return d;
    }
  case '8':                             // '8' はオペランドを表現します
        return t.value;                 // トークンの value の値を返します
  default:
        error("primary expected");
  }
}

//------------------------------------------------------------------------------

Token get_token() {
  char ch;
  cin >> ch;                            // ホワイトスペースを除くことに注意

  switch (ch) {
  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.putback(ch);                  // ストリームから抽出された最後の文字を再び入力操作で抽出できるようにします
      double val;
      cin >> val;                       // floating-point number を読み込みます
      return Token('8', val);           // '8' はオペランドを表現します
    }
  default:
    error("Bad token");
  }
}

警告は出るもののビルドできました。

cin の確認

理解が進まなかった原因として、cin の理解不足があるようです。いろいろ cin について試してみました。

copy

#include <string>
#include "std_lib_facilities.h"

int main() {
  /* 12345689 と入力してみる*/
  char ch;
  /* 1文字取得*/
  cin >> ch;
  /* ch を表示*/
  cout << "取得した文字 : " << ch << endl;

  string str;
  /* 残りを取得*/
  cin >> str;
  /* 残りを表示*/
  cout << "残りの文字 : " << str << endl;

  /* バッファを取得*/
  cin >> str;
  cout << "バッファ : " << str << endl;

  /* 12.345689 と入力してみる*/
  cin >> ch;
  /* ch を表示*/
  cout << "取得した文字 : " << ch << endl;
  double value;
  cin >> value;
  cout << "取得した数値 : " << value << endl;

  /* 12.345689 と入力してみる*/
  cin >> ch;
  /* ch を表示*/
  cout << "取得した文字 : " << ch << endl;
  /* 取得した文字を戻します*/
  cin.putback(ch);
  cin >> value;
  cout << "戻した後の数値 : " << value << endl;

  keep_window_open();

  return 0;
}

ビルドして実行してみました。char で cin を受けると、頭の1文字を取得して残りの部分バッファとして残っているようです。

string で受けると全部受け取りバッファとしては空になるようです。

この動きを理解するとこれまでのことは大分すっきりとしました。

cin にはこんな機能があったとは初めて知りました。

今回はここまでとします。