オペレータ(演算子)のオーバーロード(Overloading Operators)
C++には45のオペレータが用意されている.いくつかあげると
:: スコープ | . のメンバ | << ビットシフト | = 代入 | && 論理積 | || 論理和 |
などがある.これらのオペレータをオーバーロードする方法を学ぶ
説明を分かりやすくするために,ここでは分数クラスを例にとってコンストラクタとオペレータのオーバーロードを行なう.そこで,まずは分数クラスの定義が必要となる.
解答
Ratio |
num
den |
display() |
#include <iostream>
using namespace std; class Ratio { private: int num, den; public: Ratio(int n, int d); void display(); }; Ratio::Ratio(int n, int d) { num = n; den = d; } void Ratio::display() { cout << num << "/" << den; } int main() { Ratio x(22,7); cout << "x = "; x.display(); } |
実行結果
クラスのコンストラクタは,作成するオブジェクト用に格納場所を用意し,初期化する.また,
Ratio (int n, int d);
Ratio::Ratio(int n, int d) { num = n; den = d; } |
Ratio (int n, int d) {num = n, den = d}
|
Ratio (int n, int d) : num(n), den(d) { }
|
コンストラクタのオーバーロード
クラスのコンストラクタ関数は,コピーコンストラクタを作成するときにオーバーロードされた.他にも,柔軟性を得るためや,配列をサポートするためにコンストラクタ関数のオーバーロードは用いられる.例えば,
Ratio() : num(0), den(1) { }
Ratio(int n) : num(n), den(1) { } Ratio(int n, int d) : num(n), den(d) { } と3つのコンストラクタを用意する代わりに Ratio(int n=0, int d= 1) : num(n), den(d) { } |
練習問題 11..1 分数クラスRatioを1つのコンストラクタで書き直し,Ratio x(22,7)とRation y(3)を求めよ.
|
代入オペレータのオーバーロード(Overloading The Assignment Operator)
代入オペレータ = は,オペレータの中で最もよく用いられるオペレータである.その目的は,オブジェクトを別のオブジェクトにコピーすることである.ディフォルトのコンストラクタのように,代入オペレータは自動的に作成されるが,明示的に作成することもできる.
Ratioクラスに代入オペレータを加える.
#include <iostream>
using namespace std; class Ratio { private: int num, den; public: Ratio(int n=0, int d=1):num(n), den(d) {} Ratio(const Ratio&); // コピーコンストラクタ void operator=(const Ratio&); // 代入オペレータ }; |
operator=() |
次に,多重定義された代入オペレータの実装部は
void Ratio::operator=(const Ratio& r)
{ num = r.num; den = r.den; } |
This ポインタ(this Pointer)
C++には,thisという名前の特別なポインタがある.thisは全てのメンバ変数の呼び出し時に自動的に渡されるポインタで,その呼び出しを行なったオブジェクトを指す.例えば,
obj.f();
と書くと,f()関数にはobjのポインタが自動的に渡される.ここで,objは関数を呼び出したオブジェクト.このポインタはthisという名前で参照できる.
thisポインタが渡されるのは,メンバ関数だけである.
C++では次のように代入を書くことができる.
x = y = z = 3.14;
|
Ratio& operator=(const Ratio& ); |
解答 代入オペレータがクラスメンバ関数としてオーバーロードされたとき,オブジェクトと同じ型の参照が戻されるが,これには名前がない.そこで,これをthisとよぶ.これより,正しい代入オペレータのオーバーロード法は次のようになる.
#include <iostream>
using namespace std; class Ratio { public: Ratio(int n=0, int d=1) : num(n),den(d) {} Ratio(const Ratio& r) : num(r.num), den(r.den) {} Ratio& operator=(const Ratio&); void display(); private: int num, den; }; Ratio& Ratio::operator=(const Ratio& r) { num = r.num; den = r.den; return *this; } void Ratio::display() { cout << num << "/" << den; } int main() { Ratio x(22,7); Ratio y(x); Ratio z; cout << "x = " ; x.display(); cout << "\ny = "; y.display(); z = x; cout << "\nz = "; z.display(); } |
実行結果
これにより,Ratioクラスにコピーコンストラクタと代入オペレータをオーバーロードすることができた. 代入は初期化と同じ記号を用いるが意味は異なる.
Ratio x(22,7); | // これは初期化 |
Ratio y(x); | // これは初期化 |
Ratio z = x; | // これは初期化 |
Ratio w; | |
w = x; | // これは代入 |
初期化はコピーコンストラクタをよび,代入は代入オペレータをよぶ.
これより,クラスTにおける代入オペレータの正しい多重定義は
T& T::operaot=(const T& t)
{
// オブジェクトtのメンバ変数にオーナーのメンバ変数を代入する
return *this;
}
となる.
算術オペレータのオーバーロード(Overloading Arithmetic Operator)
すべてのプログラミング言語は標準の四則演算のオペレータ+, - , *, / を用意している.よって,これらをRatioクラスでも定義するのは自然なことである.古いコンピュータ言語,例えばCなどでは,次のような関数を定義することでこれを行ってきた.
Ratio product(Ratio x, Ratio y)
{ nbsp; Ratio z(x.num*y.num, x.den*y.den); nbsp; return z; } |
これでもうまくいくが,関数は次のようにしてよばなくてはいけない.
z = product(x,y);
C++ではこのような関数を,標準の算術オペレータ記号を用いて定義することができる.つまり,
z = x*y;
と定義することができる.乗算オペレータ*は他のC++のオペレータと同様,予約語operatorを用いてoperator*と表す.これをproductの変わりに用いると
Ratio operator*(Ratio x, Ratio y)
{ nbsp; Ratio z(x.num*y.num, x.den*y.den); nbsp; return z; } |
フレンド関数(friend function)
フレンド関数 |
フレンド関数は,メンバではない通常の関数として定義する.ただし,フレンドとするクラス宣言の内部に,キーワードfriendを先頭につけて関数のプロトタイプを含める.
解答
#include <iostream>
using namespace std; class Ratio { friend Ratio operator*(const Ratio&, const Ratio&); public: Ratio(int n=0, int d=1) : num(n),den(d) {} Ratio(const Ratio& r) : num(r.num), den(r.den) {} Ratio& operator=(const Ratio&); void display(); private: int num, den; }; Ratio& Ratio::operator=(const Ratio& r) { num = r.num; den = r.den; return *this; } Ratio operator*(const Ratio& x, const Ratio& y) { Ratio z(x.num*y.num, x.den*y.den); return z; } void Ratio::display() { cout << num << "/" << den; } int main() { Ratio x(22,7); Ratio y(-3,8); Ratio z; z = x; z.display(); cout << endl; x = y*z; x.display(); cout << endl; } |
実行結果
キーワードfriendは関数の実装部には用いられない.また,Ratio::も用いられていない.なぜなら,operator*はメンバ関数ではない. friend関数はメンバ関数ではなく,全てのクラスのメンバからアクセスできる関数である.
練習問題 11..2 friend関数を用いてoperator+をRatioクラスに多重定義し,
を求めよ.
|
算術代入オペレータのオーバーロード(Overloading the Arithmetic Assignment Operators)
C++では,四則演算と代入オペレータを組み合わせて複合代入演算子を作成することができることを学んだ.例えば,x = x*yと書く代わりに,x *= yと書くことができる.+=,*=などの複合代入演算子は,+や*よりも簡単に定義できることが多い.なぜなら,+処理には3つのオブジェクト(2つの被演算子と計算結果)が関わるのに,+=の処理には2つのオブジェクトしか関わらない.
解答 複合演算子a *= b;はa.operator*=(b);となるので,クラスのメンバとして定義できる.
#include <iostream>
using namespace std; class Ratio { friend Ratio operator*(const Ratio&, const Ratio&); public: Ratio(int n=0, int d=1) : num(n), den(d) {} Ratio(const Ratio& r) : num(r.num), den(r.den) {} Ratio& operator=(const Ratio&); Ratio& operator*=(const Ratio&); void print(); private: int num, den; //データメンバ }; Ratio& Ratio::operator=(const Ratio& r) { num = r.num; den = r.den; return *this; } Ratio& Ratio::operator*=(const Ratio& r) { num = num * r.num; den = den * r.den; return *this; } Ratio operator*(const Ratio x, const Ratio y) { Ratio z(x.num*y.num, x.den*y.den); return z; } int main() { Ratio x(22,7), y(-3,8), z; z = x; z.display(); cout << endl; x = y*z; // 乗算オペレータ x.display(); cout << endl; x *= y; // x = x*y x.display(); cout << endl; } |
解答
実行結果
練習問題 11..3 複合演算子+=をRatioクラスに多重定義し,
を求めよ.
|
関係演算子のオーバーロード(Overloading the Relation Operators)
6個の関係演算子 , , , , == , !=も算術演算子と同じ方法(friend)で,オーバーロードすることができる.
#include <iostream>
using namespace std; class Ratio { friend Ratio operator*(const Ratio&, const Ratio&); friend bool operator==(cosnt Ratio&, const Ratio&); public: Ratio(int n=0, int d=1) : num(n), den(d) {} Ratio(const Ratio& r) : num(r.num), den(r.den) {} Ratio& operator=(const Ratio&); Ratio& operator*=(const Ratio&); void print(); private: int num, den; //データメンバ }; Ratio& Ratio::operator=(const Ratio& r) { num = r.num; den = r.den; return *this; } Ratio& Ratio::operator*=(const Ratio& r) { num = num * r.num; den = den * r.den; return *this; } Ratio operator*(const Ratio x, const Ratio y) { Ratio z(x.num*y.num, x.den*y.den); return z; } bool operator==(const Ratio x, const Ratio y) { return (x.num * y.num == y.num * x.den); } int main() { Ratio x(22,7), y(-3,8), z, w(9,-24); z = x; z.display(); cout << endl; x = y*z; // 乗算オペレータ x.display(); cout << endl; x *= y; // x = x*y x.display(); cout << endl; if (y == w) { y.display(); cout << "="; w.display(); } } |
解答
実行結果
練習問題 11..4 関係演算子!=をRatioクラスに多重定義し,
と
が等しいか調べよ.
|
入出力演算子のオーバーロード(Overloading the Stream Operators)
C++は入力演算子 や出力演算子 をオーバーロードすることができる.
クラスがTでデータメンバがdのとき,出力演算子の書き方は
friend ostream& operator<<(ostream& ostr, const T& t)
{ return ostr << t.d; } |
#include <iostream>
using namespace std; class Ratio { friend Ratio operator*(const Ratio&, const Ratio&); friend bool operator==(cosnt Ratio&, const Ratio&); friend ostream& operator<<(ostream&, const Ratio&); public: Ratio(int n=0, int d=1) : num(n), den(d) {} Ratio(const Ratio& r) : num(r.num), den(r.den) {} Ratio& operator=(const Ratio&); Ratio& operator*=(const Ratio&); void print(); private: int num, den; //データメンバ }; Ratio& Ratio&::operator=(const Ratio& r) { num = r.num; den = r.den; return *this; } Ratio& Ratio&::operator*=(const Ratio& r) { num = num * r.num; den = den * r.den; return *this; } Ratio operator*(const Ratio& x, const Ratio& y) { Ratio z(x.num*y.num, x.den*y.den); return z; } bool operator==(const Ratio& x, const Ratio& y) { return (x.num * y.num == y.num * x.den); } ostream& operator<< (ostream& ostr, const Ratio& r) { return ostr << r.num << "/" << r.den; } int main() { Ratio x(22,7), y(-3,8), z, w(9,-24); z = x; z.display(); cout << endl; x = y*z; // 乗算オペレータ x.display(); cout << endl; x *= y; // x = x*y x.display(); cout << endl; if (y == w){ y.display(); cout << "="; w.display(); cout << endl; } cout << "x = " << x << ", y = " << y << endl; } |
解答
入力演算子のオーバーロード(Overloading the Input and Output Operators)
#include <iostream>
using namespace std; class Ratio { friend istream& operator>>(istream&, Ratio&); friend ostream& operator<<(ostream&, const Ratio&); public: Ratio(int n=0, int d=1) : num(n),den(d) {} void swap() {int temp = num; num = den; den = temp;} void display(); private: int num, den; }; ostream& operator<<(ostream& ostr, const Ratio& r) { return ostr << r.num << "/" << r.den; } istream& operator>>(istream& istr, Ratio& r) { cout << "\t Numerator: "; istr >> r.num; cout << "\t Denominator: "; istr >> r.den; return istr; } void Ratio::display() { cout << num << "/" << den; } int main() { Ratio x, y; cin >> x >> y; cout << "x = " << x << ", y = " << y << endl; } |
解答
アクセス関数(Access Functions)
クラスのメンバ変数はprivate宣言し非公開としているが,クラスにはこのメンバ変数を読むことができるpublicなメンバ関数を用意することが普通である.このようなメンバ関数をアクセス関数(access function)という.
非公開メンバ関数
クラスのメンバ変数は一般に,非公開で,メンバ関数は公開で宣言される.しかし,時にはメンバ関数を非公開で宣言することも必要である.このようなメンバ関数はローカルユーティリティ関数とよばれる.
例えば,分数の計算のときに,分数を既約分数に直しておくほうが扱いやすい.そこで,
class Ratio
{
public:
Ratio(int n=0, int d=1) : num(n), den(d) { reduce();}
void display() {cout num '/' den endl;}
private:
int num, den;
void reduce();
};
にように,reduce()関数を宣言する.この関数は,このクラスの中で用いるだけの関数なので,privateとする.
さて,既約分数は,分母の分子の最大公約数で割ることにより求まるので,reduce()関数には,最大公約数を求めるgcd()関数が必要である.これより,reduce()関数は,
int gcd(int, int);
void Ratio::reduce()
{
if (num == 0 den == 0){
num = 0;
den = 1;
return;
}
if (den 0) {
den *= -1;
num *= -1;
}
if (den == 1) return;
int sgn = (num < 0 ? -1:1);
int g = gcd(sgn*num, den);
num /= g;
den /= g;
}
練習問題 11..5 int gcd(int, int)を完成させて,入力した分数を分子と分母の最大公約数で割って,分子と分母が互いに素である分数を出力するプログラムを作成せよ.
|
型変換演算子のオーバーロード(Overloading the Conversion Operators)
型を変換するのに用いられるオペレータを型変換演算子という.もしtypeが変換後の型とすると,型変換演算子は次のように宣言される.
operator type( );
例えば,floatに変換する場合は
operator float( );
となる.また,piなどの定数を変換するときは
operator double( ) const;
となる.
#include <iostream>
using namespace std; class Ratio { friend ostream& operator<<(ostream&, const Ratio&); public: Ratio(int n=0, int d=1) : num(n),den(d) {} Operator double() const; private: int num, den; }; ostream& operator<<(ostream& ostr, const Ratio& r) { return ostr << r.num << "/" << r.den; } Ratio::operator double() const { return double(num)/den; } int main() { Ratio x(-5,8); cout << "x = " << x << ", double(x) = " << double(x) << endl; const Ratio P(22,7); const double PI = double(P); cout << "P = " << P << ", PI = " << PI << endl; } |
解答
1. 予約語operatorはどのように用いられるか.
2. *thisは何を参照しているか
3. メンバ関数以外ではなぜthisポインタを使うことができないのか
4. 次の2つの違いは何か
Ratio y(x);
Ratio y = x;
5. 次の2つの違いは何か
Ratio y = x;
Ratio y; y = x;
6. なぜ,指数演算子**はオーバーロードできないのか.
7. なぜ,出力演算子と入力演算子はfriend関数としてオーバーロードされなければならないのか.
1. Ratioクラスでは,分数の約分も行なえるようにしたい.そこで,次のプログラムを完成せよ.