l VisualC 演算子のオーバーロード '97.00.00
l.お知らせ 内容 LINK FILE   HTML Win PC Unix MS-DOS C C++ Mfc Java 
目次.C++言語_ 1 クラス 2 派生クラス 5 演算子のオーバーロード クラス(1)
 準備 3 オブジェクト 6 テンプレート 8 etc クラス(2)
 CC++ 4 フレンド・多重継承 7 メモリ確保・例外処理 未使用
演算子のオーバーロード   引数がふたつの場合   二項演算子 +   演算子=で文字列を代入   フレンド関数で演算子をオーバーロード   後置演算子

T 演算子のオーバーロード
引数が異なれば、関数はオーバーロードできます。
ところで、+や−などの演算子は、呼出し形式は異なりますが、実体は関数です。
+の演算子で整数も小数も演算できるのは、+演算子で呼出される関数が整数用も小数用もオーバーロードされているからです。

演算子で呼出す関数は自由に変更でき、ある演算子に新しく関数呼出しの機能を設定することは、演算子のオーバーロードと言います。

関数を演算子で呼出す方法
代入演算子 = を例に考えます。

まず、メンバデータ a に引数の値 i を代入するメンバ関数を考えてみます。
class C1
{ public : int a;
      void func(int i) { a=i; }
};
void main()
{ C1 OB; // オブジェクト OB の宣言
 OB.func(5); // OB.a に 5 を代入
}
 
← 全角スペースが含まれています。
 コピー注意

関数名は、次のように演算子で表すことができるようになっています。
void    func (int i) { a=i; }
void operator = (int i) { a=i; }
普通に記述
演算子で呼出せるように記述

関数名を演算子で表わした場合は、次のように呼出します。
OB.func(5);
OB = 5;
OB.operator=(5);
普通の関数の呼出し
演算子で呼出し
(このようにも呼び出せます。 「operator=」 が関数名。)

次の例は、オブジェクト名を変数名らしく X と Y に変えています。
#include <iostream.h>           /* 演算子のオーバーロード */
class C1
{ public : int a;
//         void       func (int i) { a=i; } ...普通の代入用メンバ関数
           void operator = (int i) { a=i; } // 演算子のオーバーロード 
};
void main()
{ C1 X;     // オブジェクトの宣言 (C1 型の変数の宣言)
  C1 Y;
//X.func(3); ...普通のメンバ関数で代入するとき
  X = 3;    // 代入
  Y = 5;
  cout << X.a << " " << Y.a << endl; // 代入されているか確認 3 5
  int xx; cin >> xx;
}
ソースコードを詳しく見れば X と Y はオブジェクトですが、クラスを定義してしまった後は、変数と同じに使えます。

演算子をオーバーロードすると、その演算子のスコープでは(その演算子が使える範囲内では)、その演算子の元々の機能はすべて無効になります。

T 変数を代入する場合
=演算子には、変数に定数を代入する機能だけではなく、変数の値を別な変数に代入するの機能もあります。
定数を代入する演算子=は、次のようにオーバーロードしました。
void    func (int i) { a=i; }
void operator = (int i) { a=i; }
普通の関数の記述
演算子で呼出せるように記述

この関数の仮引数をメンバではなく、オブジェクトに換えてみます。
すると、次のような関数を作ることができます。
void    func (C1 ob) { a=ob.a; }
void operator = (C1 ob) { a=ob.a; }
普通の関数の記述
演算子で呼出せるように記述
(引数 ob で与えられたオブジェクトのメンバの値 ob.a を、
この関数を含んでいるオブジェクトのメンバの値 a に代入します。)

これは次のように使用できます。
void main() /* 普通の関数を呼び出す場合 */
{ C1 X, Y; // オブジェクト X と Y を宣言
 Y.func(X) // オブジェクト Y のメンバ関数を、オブジェクト X を引数に与えて呼出し
       // Y.a に X.a が代入されます。
}
// この場合の func() は Y のメンバだから、a は Y.a と同じです。
// 従って、 func(C1 ob) { a=ob.a; } は → func(C1 X) { Y.=X.a; } のように動作します。

void main() /* オーバーロードされた演算子を呼び出す場合 */
{ C1 X, Y; // オブジェクト X と Y を宣言
 Y = X;  // Y.a に X.a が代入されます。
}

実際の例は次のようになります。
をひとつでもオーバーロードすると、そのスコープでは最初からあるの機能が全て無効になるので、定数を代入する演算子の=もオーバーロードしています。
#include <iostream.h>           /*  二項演算子 =  */
class C1
{ public : int a;
//         void       func (int i) { a=i; }
           void operator = (int i) { a=i; }    // 定数を代入する演算子
//         void       func (C1 ob) { a=ob.a; }
           void operator = (C1 ob) { a=ob.a; } // 変数を代入する演算子
};
void main()
{ C1 X;     // オブジェクトの宣言 (変数の宣言)
  C1 Y;
//X.func(5);
  X = 5;    // 定数を代入
//Y.func(X);
  Y = X;    // 変数を代入
  cout << X.a << " " << Y.a << endl; // 代入されているか確認 
  int xx; cin >> xx;
}

メンバ a が、その関数が含まれるオブジェクトであることを明確にするには、次のように this ポインタを使います。
また、関数の仮引数にオブジェクトではなく、次のようにオブジェクトの参照を使うようにすれば効率的です。
void    func (C1 &ob) { this->a=ob.a; }
void operator = (C1 &ob) { this->a=ob.a; }
普通の関数の記述
演算子で呼出せるように記述

オブジェクトに代入する演算子は用意されていません
上記のようにオブジェクトを指定して代入する演算子は、最初は用意されていません。
普通は次のようにメンバに代入します。
OB.a=5;

オブジェクトを変数のように扱うために他の演算子をオーバーロードするときは、代入演算子も必ずオーバーロードする必要があると思います。

T 二項演算子 +
+演算子は、代入ではなく加算をして、その結果を返り値として返せばよいわけですから次のようになります。
void    func (int i) { this->a=i; }
void operator = (int i) { this->a=i; }
void    func (C1 &ob) { this->a=ob.a; }
void operator = (C1 &ob) { this->a=ob.a; }
 
int     func (C1 &ob) { return this->a+ob.a; }
int operator + (C1 &ob) { return this->a+ob.a; }
 
定数を代入する演算子=
 
変数の値を代入する演算子=
 
普通の関数の記述
引数がふたつの演算子+

T 演算子=で文字列を代入
文字列の代入演算子は最初は用意されていないので、演算子での代入はできません。
しかし文字列の代入関数で演算子をオーバーロードすることができます。

文字列は for などのループを使って \n までをコピーしてもよいのですが、文字列の代入関数 strcoy() を呼出すことにします。
文字列代入関数  char *strcpy(char *t, char *d);
d でポイントされる文字列を t にコピーします。返り値は t
 
文字列を代入するメンバ関数を作ってしまえば、それを演算子で呼出すのは、 次の例のように前述の整数の代入の場合と同じです。
#include <iostream.h> // cout を使うために必要           /* 文字列の代入 */
#include <string.h>   // strcpy() を使うために必要
class C1
{ public :
   char s[50];   // データメンバ (ここに文字列を格納)
// void       func (char *p) { strcpy(s,p); }          // *p を s[50] にコピー
   void operator = (char *p) { strcpy(s,p); }          // 定数を代入
// void       func (C1 ob) { strcpy(s,(char *)ob.s); } // ob.s を s[50] にコピー
   void operator = (C1 ob) { strcpy(this->s,ob.s); }   // 変数を代入
};
void main()
{ C1 X, Y;       // オブジェクト X と Y の宣言 (変数の宣言)
//X.func("ABC"); // ABC を、X.s[50] にコピー
  X="ABC";
//Y.func(X);     // Y.s[50] に、X.s[50] をコピー
  Y=X;
  cout << X.s << " " << Y.s << endl; // 代入されているか確認 ABC ABC 
  int xx; cin >> xx;
}
strcpy()でコピーするだけでなく、strlen()で文字長を調べ、配列に納まるかどうか調べるようにすれば安全です。

T フレンド関数で演算子をオーバーロードする
フレンド関数はクラスの中に書かれたグローバル関数です。
従って、クラスの中に書かれてはいますが個々のオブジェクトには所属しません。
そのため、OB.func() のように、オブジェクトを指定した呼び出しはできません。

OB.func() のような、オブジェクトを指定した呼び出しができないので、フレンド関数で演算子をオーバーロードするには制約があります。
Y=X のように、ふたつのオブジェクトを直接使用する演算子のオーバーロードはできません。

フレンド関数で演算子のオーバーロードが可能な場合の例を示します。
引数の値をあらかじめ +2 して、その値を返します。
( +1 にすれば、前置インクリメント演算子 ++ の機能になります。)
#include <iostream.h>          /*  Y=++X  */
class C1
{ public :
   int x;      // データメンバ 
   void operator =(int i){ x=i;}      // 定数の代入
   void operator =(C1 ob){ x=ob.x;}   // 変数の代入
//        int       func (){ x=x+2; return x; } // 普通のメンバ関数
//        int operator ++(){ x=x+2; return x; } // 演算子で呼び出せるように変更。
   friend int operator ++(C1 &ob)     // フレンド関数
   { ob.x=ob.x+2;    // x を +2 する
     return ob.x;    // x を返す
   }
};
void main()
{ C1 X, Y;     // オブジェクト X と Y の宣言 (変数の宣言)
  X=10;        // X.x に 10 を代入
  Y=++X;       // X.x を +2 し、Y.x に代入
  cout << X.x << ' ' << Y.x << endl; // 確認 X.x=12, Y.x=12
  int q; cin >> q;
}
仮引数にオブジェクトをコピーしても代入はできませんから、オブジェクトの参照(C1 &ob)を受け取るようにしています。

前置演算子 : 変数などの前に置かれた演算子。
プレインクリメント : 使う前に +1 する。 プレデクリメント : 使う前に -1 する。
後置演算子 : 変数などの前に置かれた演算子。
ポストインクリメント : 使った後で +1 する。 ポストデクリメント : 使った後で -1 する。

普通の演算子は =演算子と共に使用されることが多いので、=演算子でオブジェクトのポインタを保存し、それを使うことができます。
次の例は this ポインタが必要な例として、引数をそのまま返した後、引数の値を +2 します。
代入演算子の呼び出しでオブジェクトのポインタを保存し、そのポインタをフレンド関数が使うようにしています。
( 後置演算子にするため、仮引数に int を付けています。)
#include <iostream.h>          /*  Y=X++  */
class C1
{ public :
   int x, h; C1 *p;   // データメンバ 
   void operator =(int i){ x=i;}       // 定数の代入 ( X=10; で呼び出されます)
   void operator =(C1 ob)              // 変数の代入 ( Y=X++; で呼び出されます)
                  { x=ob.x;
                    p=this; // オブジェクト Y のポインタを p に保存
                  }
   friend int operator ++(C1 &ob, int) // フレンド関数 後置演算子++
   { ob.p->h = ob.x;      // x を h に保存
     ob.x = ob.x+2;       // x を +2 する
     return ob.p->h;      // 保存しておいた h を返す
   }
};
void main()
{ C1 X, Y;       // オブジェクト X と Y の宣言 (変数の宣言)
  X=10; 
  Y=X++;   // X を +2 し、Y に代入
  // Y= では、オブジェクトのポインタがに保存されます。 X++ の返り値を待って、10 が代入されます。
  // X++ で X=12 になります。この返り値は加算前の 10
  cout << X.x << ' ' << Y.x << endl; // 確認 X.x=12, Y.x=10 
  int q; cin >> q;
}

次の例は、5+X の加算を行います。
(クラスのメンバ関数では、+の前の定数は受け取れないようなので、フレンド関数を使っています。)
#include <iostream.h>          /* Y=10+X */
class C1
{ public :
   int x; C1 *p;   // データメンバ 
   void operator =(int i){ x=i;}        // 定数の代入 ( X=5; で呼び出されます)
   void operator =(C1 ob){ x=ob.x; }    // 変数の代入 ( Y=10+X; で呼び出されます)
   friend int operator +(int i, C1 &ob) // フレンド関数 定数加算演算子 +
   { return ob.p->x+i; }
};
void main()
{ C1 X, Y;       // オブジェクト X と Y の宣言 (変数の宣言)
  X=5;           // X.x に 5 を代入
  Y=10+X;        // Y.x に 10+X を代入
  // Y= では、10+X の返り値を待って、15 が代入されます。
  // 10+X の返り値 p->x+i は 15 です。X.x の値は 5 のままです。
  cout << X.x << ' ' << Y.x << endl; // 確認 X.x=5, Y.x=15 
  int q; cin >> q;
}

T 後置演算子にするには
後置演算子にするには上例のように、前置演算子の関数の仮引数に、int を付けます。

 関数の宣言 関数の呼び出し
int operator ++ () { /**/ }
int operator ++ (int) { /**/ }
friendint operator ++(C1 &ob) { /**/ }
friendint operator ++(C1 &ob,int){ /**/ }
Y= ++X; //(前置演算子)
Y= X++; //(後置演算子)
Y= ++X; //(前置演算子)
Y= X++; //(後置演算子)
仮引数に int を追加しただけでは、演算の結果は変りません。
仮引数に int を記述して後置演算子にすると、関数の呼び出し方だけが変ります

引数を受け取る順番は
仮引数の順番と同じ順番で受け取ります。
 関数の宣言 関数の呼び出し
friend int operator + (C1 &ob, int x) { /**/ }
friend int operator + (int x, C1 &ob) { /**/ }
Y = X + 5;
Y = 5 + X;

T グローバル関数で演算子をオーバーロードする
上記のフレンド関数は元々グローバル関数ですから、friend を取り除いて{クラス}の外に書けばグローバル関数になります。

備考 (まとめ)
演算子の引数はひとつだけ
演算子は次のように使用されます。
Y=X

この例では、演算子はひとつの引数 X しか受け取れません。
もし引数二つにするなら、演算子=の使い方を変えてしまわなければなりません。

もうひとつの引数 Y を受け取るためには、this ポインタを使います。
this ポインタは、クラスのメンバ関数では普通は記述を省略します。
グローバル関数やフレンド関数はクラスのメンバ関数ではないので、直接this ポインタを使うことはできません。

グローバル関数では、引数がひとつ多く必要です
演算子をオーバーロードする場合は、クラスのメンバデータを処理の対象にします。
グローバル関数やフレンド関数は this ポインタを使えないので、オブジェクトを引数にします。
(メンバ関数と同じ処理をする場合でも、引数の数がひとつ多く必要です。)

演算子をオーバーロードすると
演算子をオーバーロードすると、その演算子の 元の機能はすべて使えなくなります。
例えば−演算子の元の機能は、次のように複数あります。
Y=-5; Y=X-5; Y=5-X; Z=Y-X;
 

T

C++言語 4 フレンド・多重継承 5 演算子のオーバーロード 6 テンプレート
 
  mtoga@sannet.ne.jp   登録日 '96. 6.15
URL : http://www.page.sannet.ne.jp/mtoga/index.html