第二回課題解説

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

典型的な間違い例もあげるので、自分の理解に不備がないかチェックして欲しい。


1:コンストラクタなしの stack

基本的にはよくできていたが、細かな点で誤解している人が多い。以下解説する。

(1) Hello World の表示

必要な部分のみ書くと以下のようになる。

  // スタックを初期化する
  s1.init();
  s2.init();

  s1.push('o');
  s1.push('l');
  s1.push('l');
  s1.push('e');
  s1.push('H');

  s2.push('d');
  s2.push('l');
  s2.push('r');
  s2.push('o');
  s2.push('W');

  for(i=0; i<5; i++) cout << "s1をポップする: " << s1.pop() << "\n";
  for(i=0; i<5; i++) cout << "s2をポップする: " << s2.pop() << "\n";


for 文の回数 (5 回) も変更しなければならないことに注意。 この問題はよくできていた。

(2) 10 回プッシュすると何が起こるか

解答は以下の通り。

「スタックは一杯です」と表示され、それ以上スタックにデータをプッシュできない。

それを実現しているのは、クラス実現部の push 関数の以下の部分である。

  if(tos==SIZE) {
    cout << "スタックは一杯です";
    return;
  }


この問題の後半、コード中の場所を指摘する部分のできが必ずしも良くなかった。間違い例は

#define SIZE 10




  char stck[SIZE]; // スタック領域を確保する


などである。 確かに「#define SIZE 10」や「char stck[SIZE];」によって、スタックを構成する配列 のサイズが 10 と定められているのだが、
「if(tos==SIZE)」によるチェックを行わない限り、配列のサイズを超えて書き込むことができてしまう

このように、「サイズを超えて配列を読み書きする」というのは配列を使ったプログラムで起こる典型的なエラーであるので注意しなければならない。

(3) init() を呼び出す理由は何か

一言で書くと「初期化をするため」なのだが、初期化の具体的な内容まで書いている人は多くなかった。解答は以下の通りである。

初期化によって tos の値を 0 にするため。

初期化しないと tos の値は不定であり、stck[tos] によって、意図しないメモリ領域に読み書きしてしまうことがある。


また、

初期化しないとメモリが確保されない。


と書いている人がいるが、これは間違いである。 init() を呼び出す前のメモリの模式図は



となり、tos や配列 stck[SIZE] は既に確保されている。

「初期化しないと値が不定」の意味は、「整数 tos や char 型の変数 stck[0] ~ stck[9] の値が不定」という意味である。

さらに言えば、stck[0] ~ stck[9] の値が不定であっても、このプログラムでは実害はない。(何が格納されていようと、push 時に上書きされるから)
実害を引き起こすのは、解答にあるように、tos の値が不定であることである。


2:コンストラクタつきの stack

これは

コンストラクタが自動的に呼び出され、その内部で tos=0 と初期化されるから


が解答ある。

できは悪くなかったが、若干気になる表現をしている人がいた。

コンストラクタによって自動的に初期化されるから


がその例である。コンストラクタは自動的に呼び出されるのだが、
初期化の内容、すなわち「tos=0;」 は自分でコンストラクタ内に記述しなければならないことに注意しよう。


3:複素数クラス

複素数は C++ の例題として取り上げることも多いため、正答率が高かった。
解答例は以下の通り。

#include <iostream>
#include <cmath>
using namespace std;

class complex{

  private:
    double real; // 実部
    double imag; // 虚部

  public:
    complex(double r,double i);  // コンストラクタ

    double get_real();
    double get_imaginary();
    double get_norm();

    void show();

};

// コンストラクタ
complex::complex(double r,double i){
  real = r;
  imag = i;
}

double complex::get_real(){
  return real;
}

double complex::get_imaginary(){
  return imag;
}

double complex::get_norm(){
  return sqrt(real*real+imag*imag);
}

void complex::show(){
  cout << real << "+" << imag << "i\n";
}


間違いの例としては以下のようなものがあった。

1.メンバ変数を使ってはいけないところで使う



…
class complex{

  private:
    double a; // 実部
    double b; // 虚部

  public:
    …
    douuble get_norm();

};
…
double complex::get_norm(){
  a = sqrt(a*a+b*b);    //← ()

  return a;
}



() の部分で、複素数の実部 (a) にノルムを代入してしまっている。
つまり、get_norm() を計算した後は実部 (a) がおかしな値になってしまう。
正しくは解答のように「return sqrt(a*a+b*b);」と直接結果をリターンすると良い。
一時的な変数をどうしても使いたければ、

double complex::get_norm(){
  double r = sqrt(a*a+b*b);

  return r;
}



などのようにすべきである。こうすると、double r は「get_norm()」内のみで使える変数となる。

2.関数の戻り値を使えない

return 文を使って関数から戻り値を渡すことができない人が何名かいた。
例えば以下のようにしている。

void complex::get_real(){
  cout << "real part is " <<  real <<  "\n";
}


確かにコンソールには実部の値が表示されるが、これではプログラムの他の部分 (main 関数など) から実部の値を利用することが全くできない
関数で引数と戻り値を用いるのは基本中の基本であるから、ここで理解しておきたい。

3.ちょっとしたミス

上の「関数の戻り値」にやや関連しているが、以下のようなちょっとしたミスをしている人がいた。

double complex::get_norm(){
  double r=real*real+imag*imag;
  sqrt(r);                     // ←()
  return r;
}


一見よさそうに見えるが、これでは 3+4i に対する戻り値が 25 になってしまう。これは () の部分に問題がある。
()のような記述では、「関数 sqrt() に引数は渡されるが、戻り値がどこにも格納されない」。 すなわち、正しくは「r=sqrt(r);」などとすべきだったのである。

4.メンバ変数の型

メンバ変数の型を int 型にしている人がいた。これでは 0.5 などの実数が格納できない。
ちなみに、「int i;」に対して「i=0.5」を実行すると、i には丸められた 0 が格納される。注意しよう。

5.引数つきのコンストラクタ

引数付きのコンストラクタが書けていない人がいた。解答を見て欲しいが、
「complex(double r, double i)」のようなコンストラクタを定義すると、
main 関数での complex 型の変数の宣言は「complex c(3,4);」のように行うようになる。覚えておこう。

なお、余談だが、コンストラクタ「complex(double r, double i)」を定義すると、「complex d;」のような変数の宣言はできなくなる。
「complex c(3,4);」と「complex d;」のどちらの宣言も許すには、「デフォルトコンストラクタ」、「関数のオーバーロード」などの知識が必要になる (いずれ触れるかもしれない)。

6.おまけ「a+bi (b<0)」

上の解答の例では 3-4i のように、虚部が負の場合に show 関数を実行すると、 「3+-4i」と表示される。(この件で減点はしていない)
これを避けるには、show() 関数内で、虚部の正負で if 文による条件分岐を行うとよいだろう。



←第二回課題第三回演習→

第二回演習トップページへ

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