第五回課題解答

第五回課題の解説を行う。

今回は、「記述量は少ないが内容は深い」という問題が多かった。 解答例をもとに復習しておいて欲しい。


1:stack のテンプレート化

機械的な置き換えであるので、ほとんどの人が良くできていた。

#include <iostream>
using namespace std;

// クラス宣言部

#define SIZE 10

// 文字を保存するstackクラスを宣言する
template <class T> class stack {
  T stck[SIZE]; // スタック領域を確保する
  int tos; // スタック先頭の索引
public:
  stack();  // コンストラクタ (※1)
  ~stack(); // デストラクタ (※2)

  void init(); 
  void push(T ch); // スタックに文字をプッシュする
  T pop(); // スタックから文字をポップする
};

// クラス実現部

// コンストラクタ
template <class T> stack<T>::stack()
{
  cout << "スタックを生成する\n";
  tos = 0;
}
// デストラクタ
template <class T> stack<T>::~stack()
{
  cout << "スタックを破棄する\n";
}

// スタックを初期化する
template <class T> void stack<T>::init()
{
  tos = 0;
}

// 文字をプッシュする
template <class T> void stack<T>::push(T ch)
{
  if(tos==SIZE) {
    cout << "スタックは一杯です";
    return;
  }
  stck[tos] = ch;
  tos++;
}
// 文字をポップする
template <class T> T stack<T>::pop()
{
  if(tos==0) {
    cout << "スタックは空です";
    return 0; // スタックが空の場合はヌルを返す
  }
  tos--;
  return stck[tos];
}



2:リストクラスを用いたキュークラスの記述

全てのソースコードを以下に挙げる。変更点に () をつける。

なお、余談だが、第五回-03 における node クラスの定義で メンバ left、right、data を public で宣言したのはこちらのミスで、
本来は (以下のように) private で宣言すべきであった。
その場合、node クラス内で list クラスを friend 宣言しなければならない。

#include <iostream>
using namespace std;

class node{

    node* left;
    node* right;

    int data;

  public:

    node(){ left=right=0; } // デフォルトコンストラクタ
    node (node* l, node* r, int d){ left=l; right=r; data=d;};  // 引数つき

    void printData(){ cout << data << "\n"; };

    friend class list;
};

class list{  
    node* root;
    node* last;
  public:
    list();  // デフォルトコンストラクタ
    ~list(); // デストラクタ

    void insertRoot(int d);
    void insertLast(int d);

    int getLastValue();

    void printAll();

    void deleteLast();   //()
};

// デフォルトコンストラクタ
list::list(){

  root = 0;
  last = 0;  // ヌルポインタで初期化

}

// デストラクタ
list::~list(){

  node *nl;
  node *nr;

  nl=root;
  while(nl!=0){
    nr = nl->right;
    delete nl;
    nl = nr;
  }

}

void list::insertRoot(int d){

  if(last==0){  // root に node がない (同時に last にも node がない)

    root = new node(0,0,d);
    last = root;

  }else{

    root->left = new node(0,root,d);  // 新たな node を root の左側に
    root = root->left;                // root ポインタのつけ変え

  }
}

void list::insertLast(int d){

  if(last==0){  // last に node がない (同時に root にも node がない)

    last = new node(0,0,d);
    root = last;

  }else{

    last->right = new node(last,0,d);  // 新たな node を last の右側に
    last = last->right;                // last ポインタのつけ変え

  }
}

int list::getLastValue(){

  if(last != 0){ // ヌルポインタでなかったら
    return(last->data);
  }else{
    return(0);
  }

}

void list::printAll(){

  node *n;
  n=root;
  while(n!=0){
    n->printData();
    n=n->right;
  }

}

void list::deleteLast(){      //()

  if(last !=0){               //()
    node *n;                  //()
    n = last->left;           //()
    delete last;              //()

    if(n!=0){                 //()
      n->right = 0;           //()
    }                         //()

    last = n;                 //()
  }                           //()
}                             //()

class queue{

  list l;

  public:

    void push(int i);
    int pop();

};

void queue::push(int i){

  l.insertRoot(i);            //()

}

int queue::pop(){

  int val;                    //()
  val = l.getLastValue();     //()

  l.deleteLast();             //()

  return(val);                //()
}


int main(){

  queue q;

  for(int i=0 ; i<10 ; i++){
    q.push(i);
  }

  for(int i=0 ; i<10 ; i++){
    cout << q.pop() << "\n";
  }

  return(0);
}


root 側からデータを push し、last 側からデータを pop するようにした。
回答の中には last 側からデータを push し、root 側からデータを pop するようにしている ものがいくつかあったが、もちろんそれでも構わない。
その場合、解答がどのように変化するか各自で考えてみよ。
(基本的には last を root にし、left と right を入れ換えれば良い)

データを pop した後、不要な node を削除する必要があるが、この処理を行わない人が多かった。
削除のために list クラスにメンバ関数として deleteLast() を追加した。

deleteLast() には、 の二重のチェックが必要である。片方しかない人がいたので注意して欲しい。
さらに、last ポインタを last->left に付けかえた後、 last->right にヌルポインタを代入する必要がある。この処理を忘れた人もいた。

また、list クラスの getLastValue() の部分に を書いている人もいた。 もちろん、それが正しく動けばこの問題としては正解であるが、
上記のような記述をすると今度は 「last ポインタの付け替えを伴わないデータの取得」 ができなくなってしまう。

一般的に「関数名とは関係ない処理」を関数に行わせると、トラブルの元となることが多い。
(ここでは getLastValue (最後の値の取得) にポインタの付け替えをさせること)

例えば、研究などでプログラミングを行っている際、上記のように「ポインタの付け替え処理を伴う getLastValue()」を書き、
その後しばらくそのプログラムから離れることになった状況を考えよう。
そしてその数ヵ月後に再びプログラムを見る必要が出てきた、あるいはそのプログラムを後輩が引き継ぐことになったとしよう。
その時、そのプログラムを書いた本人 (あるいは後輩) は getLastValue という関数がポインタの付け替えを伴うことを忘れて (あるいは知らずに)、
プログラムの誤動作にしばらく悩むことになるかもしれない。


3:二分木クラスの完成

leftNode の記述を真似すれば rightNode の記述も簡単である点に気がついた人が多く、良くできていた。

//一つ目の

  }else{             // k >= key なら

    bool result;     // 挿入されたかどうかの結果を格納する変数

    if(rightNode != 0){       // 右の枝に BNode が存在すれば
      result = rightNode->insert(k,d);
    }else{                   // 右の枝に BNode が存在しなければ
      rightNode = new BNode(k,d);     //新たな BNode を追加
      rightNode->parentNode = this;   //自分が新たな BNode の上位に
      result = true;                 // 挿入されたので true
    }

    return result;   // 挿入されたかどうかを return

  }

//二つ目の

     }else{             // この Node より右の Node が求める Node なら
       return(rightNode->find(k));
     }






←第五回課題第五回-付録1 : STL におけるリスト

第五回トップページへ

クラスから入る C++ へ戻る