第四回-04 : 演算子のオーバーロード ([] と + )

本ページでは「演算子のオーバーロード」について解説する。
前のページでは代入演算子 (=) のオーバーロードを行ったが、ここでは [] 演算子と + 演算子である。


array クラスの記述 ([] 演算子のオーバーロード例)

ここでは array クラスについて解説し、その過程で [] 演算子のオーバーロードについて学ぶ。
まず、array クラスの意義から解説してゆこう。以下のコード (の断片) を見てみよう。

int x[10];

x[100] = 1;


見ての通り、要素数 10 の配列 x を定義し、その配列の 100 番目 (配列は 0 から使うので正確には 101 番目) に 1 という値を書きこんでいる。
あきらかに問題のあるプログラムだが、C/C++ では配列の境界チェックを行わないのでこのプログラムは文法上は何の問題もない。

とはいえ、(当然だが) このように境界を超えて配列の読み書きをすることは時として深刻なエラーを引き起こす。
よって、要素数を超えては読み書きできない配列があると便利である。

また、次の例を見てみよう。

int x[3] = {1,2,3};
int y[3];

// y = x // ←これでは配列のコピーはできない!

for(int i=0 ; i<3 ; i++){
  y[i] = x[i];
}


この例では、配列 x[3] の内容を y[3] にコピーしようとしているのだが、そのためには for 文を使って要素ごとにコピーしなければならない。
やはり、「y=x;」のような簡単な方法で配列のコピーができると便利である。

上記のような要求を満たすように作られるのが array クラスである。以下はそのコード例である。
array クラスの定義は授業資料ではいくつかのバージョンがあるが、ここではそれらの「良いとこ取り」をし、
さらに演習としての内容の一貫性を保つために若干の修整を行った。

#include <iostream>
#include <cstdlib>  // for exit(1)
using namespace std;

class array{   // クラス宣言
    int *p;   // 配列の先頭を指すポインタ
    int size;	
  public:
    array();                // デフォルトコンストラクタ
    array(int sz);          // 要素数を指定するコンストラクタ
    array(const array &a);  // コピーコンストラクタ
    array &operator=(const array &a); // 代入演算子

    ~array(){ delete[] p; cout << "デストラクタ\n"; }  // デストラクタ

    int &operator[](int i); // [] 演算子の多重定義

    int getsize(){ return size; }  // 配列のサイズ取得
};

array::array(){  // デフォルトコンストラクタ
      p = new int[10];
      if(!p) exit(1);
      size = 10;

      cout << "デフォルトコンストラクタ\n";
}

array::array(int sz){  // 要素数を指定するコンストラクタ
      p = new int[sz];
      if(!p) exit(1);
      size = sz;

      cout << "要素数を指定するコンストラクタ\n";
}

array::array(const array &a){   // コピーコンストラクタ
  size = a.size;

  p = new int[size];

  if(!p) exit(1);

  for(int i=0 ; i<a.size ; i++) p[i] = a.p[i];  // 内容をコピー

  cout << "コピーコンストラクタ\n";
}

array &array::operator=(const array &a){    // 代入演算子の多重定義

  delete[] p;

  size = a.size;

  p = new int[size];

  if(!p) exit(1);

  for(int i=0 ; i<a.size ; i++) p[i] = a.p[i];  // 内容をコピー

  cout << "代入演算子\n";

  return(*this);
}

int &array::operator[](int i){    // [] 演算子の多重定義
  if(i<0){
    cout << "配列の範囲から外れています!";
    return p[0];
  }else if(i>=size){
    cout << "配列の範囲から外れています!";
    return p[size-1];
  }else{
    return p[i];
  }
} 

int main(){ //  main 関数

  array num(10);

  for(int i=0 ; i<10 ; i++) num[i] = i;

  for(int i=0 ; i<10 ; i++) cout << num[i];

  cout << "\n";

  array x = num;

// array x;    // 代入演算子の動作を確認するには、
// x = num;    // こちらの2行を試してみよ。


  for(int i=0 ; i<10 ; i++) cout << x[i];

  cout << "\n";

  return 0;
}


まず、[] 演算子以外の部分を見てみよう。実はこのクラスは strtype クラスを理解していれば問題なく理解できるはずである。
「array num(10);」によって、要素数 10 の int 型の領域が new によって割り当てられ、ポインタ p がそこを指すようになる (下図)。



その p の指す先を配列として利用しようというのである。

では、問題の [] 演算子である。
まず、p の指す先のメモリ領域に値を読み書きすることを考えよう。
今までの知識では、以下のように put/get 関数を作るしかなかった。

class array{
…
  void put(int i, int j){ if(i>=0 && i<size) p[i] = j; } 
  int get(int i){ return p[i]; } 
…
};


配列の境界チェック (配列のインデックス i が配列のサイズを超えていないか) も行われていることにも注意しよう。

この場合、上の num オブジェクトに対して

num.put(i,i);   // 模式的には num.p[i] = i
num.get(i);     // 模式的には num.p[i] の取り出し


のように p の指すメモリ領域にアクセスすることになる。

もちろんこれはこれで十分機能する。 (演算子のオーバーロード機能がない Java ではこのような記法が多くなるだろう)

しかし、上の記述よりも

num[i]=i;   // 模式的には num.p[i] = i
num[i];     // 模式的には num.p[i] の取り出し


と書けた方がスマートで良いと思う人もいるかもしれない。

それを実現するのが [] 演算子のオーバーロードなのである。

基本的には非常にシンプルで、引数 i (配列のインデックス) に対して、p[i] の参照 (アドレスのようなもの。コード中では「int &」) を返す (return p[i];) だけで良い。
コードでは、配列の境界チェックも行っている。 負の i の対しては p[0] を、大きすぎる i に対しては p[size-1] を返すようにした。


coord クラスの記述 (+ 演算子のオーバーロード例)

+ 演算子や - 演算子のオーバーロード例として登場するクラスは coord (座標) クラスである。
座標であるから、x 座標と y 座標をメンバとして持つ。座標の + や - はもちろんベクトルとしての + や - に対応する。

operator+ や operator- を定義することで 「ob1 + ob2」や「ob1 - ob2」 のようなオブジェクトの加減算が、
また、operator= が *this を返すことで「 ob3 = ob2 = ob1;」 なる記述が可能になる。

operator+ や operator- を記述しないと、「ob1 + ob2」や「ob1 - ob2」やといった記述は許されないことに注意しよう。

#include <iostream>
using namespace std;

class coord{  // coord (座標) クラス宣言
    int x,y;

  public:

    coord(){ x=0; y=0; }
    coord(int i, int j){ x=i; y=j; }

    void get_xy(int &i, int &j){ i=x; j=y;}

    coord operator+(coord ob2);
    coord operator-(coord ob2);
    coord operator=(coord ob2);

   void show(){ cout << x << "," << y << "\n";}
};

  // + 演算子
coord coord::operator+(coord ob2){
  coord temp;
  temp.x = x + ob2.x;
  temp.y = y + ob2.y;
  return temp;
}

  // - 演算子
coord coord::operator-(coord ob2){
  coord temp;
  temp.x = x - ob2.x;
  temp.y = y - ob2.y;
  return temp;
}

  // = 演算子
coord coord::operator=(coord ob2){
  x = ob2.x;
  y = ob2.y;
  return *this;
}

 // main 関数
int main(){

  coord ob1(1,1);
  coord ob2(-1,1);
  coord ob3;

  ob3 = ob1 + ob2;

  ob3.show();

  ob3 = ob1 - ob2;

  ob3.show();

  ob3 = ob1;

  ob3.show();

  return 0;
}






←第四回-03 : コピーコンストラクタ・代入演算子のオーバーロード第四回課題→

第四回トップページへ

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