ブルーノのC#プログラミング & unity勉強日記

プログラミング素人、ブルーノの自主勉強ノートです。他のプログラミングを勉強したい方の助けになれば幸いです。その他趣味の雑記もしていきたいです

演算子のオーバーロード

f:id:covory10101101276:20171224183114p:plain

みなさんこんにちは🌚

久々に東京に出向き、FF7ACのウォールスクロールを衝動買いしてしまったブルーノです。クラウドのウォールスクロールなのですが思ったよりでかくて家の中で存在感を放っています。

さて今回は演算子オーバーロードというものについてやっていきます。一回にまとめたら多くなってしまいました。

 

演算子オーバーロードとは?

演算子については以前最初の方でさらっとだけ紹介しました。しかし当時は記号を見てわかる通りの算数でも習うような基本的な機能しか使わなかったので本当に一瞬しか触れませんでした。実はその演算子の定義をクラスの時のようにオーバーロード(複数定義)させることができるのです。私自身も全てを理解したわけではありませんが、どうやら演算子の機能の拡張のために作られたもののようです。

例えば、 + の演算子はこれまで数値や文字列を合わせるためだけに使用してきましたが、その定義をオーバーロードして変えることで、違う型同士を足したり、オブジェクト同士の足し算の中身を自由にカスタマイズしてスムーズに処理できます。しかしC#の文法上のルールを変えることはできないのでこの変更はできるだけ元の演算子の振る舞いに似たものにする必要があります。というよりあまり演算子オーバーロード自体しない方が良いらしいです。

演算子オーバーロードを行う際にはまず、そのオーバーロードした演算子を使いたいクラス内部で定義する必要があります。そして

public static 戻り値の型 operator 定義する演算子 (引数リスト)

とメソッドのように定義します。この戻り値の型というのは一般的に定義を変更するクラス名です。実際に例を書き起こしてみます。

下の画像は複素数の計算の様子をプログラムに表したものです。複素数はざっくり説明すると虚数を含んだ数のことで、虚数とは存在しない数のことです。虚数複素数は高校で習う単語なのでそれが分からない場合は存在しない数とだけ今回は覚えてください。この存在しない数、存在がないため他の数とごっちゃにして計算してはまずいので、それを含む数の中でも常に「 i (imaginary)」という文字で孤立しています。つまりX という複素数の中でもa + biというように中身が別れており、このaが実部(real)、biが虚部(imaginary)と呼ばれています。複素数Xと、別の複素数を計算する際、いちいち分解することもできますが、+演算子一つにこの実部同士、虚部同士の計算をするように定義すると計算が楽ですよね。これが機能の拡張というもので、こういう場合にオーバーロードを使用します。さて、長くなってしまいましたがコードの説明に移ります。

単項演算子の計算について

f:id:covory10101101276:20171224202737p:plain

f:id:covory10101101276:20171224202742p:plain

f:id:covory10101101276:20171224202747p:plain

f:id:covory10101101276:20171224202753p:plain

まず単項演算子についてですが、60,61行目にあるように(-A,-Bのこと)、項が一つだけの計算の時の演算子のことです。1枚目が実部と虚部の準備、2枚目が肝心のオーバーロードの中身について、3枚目がその計算の様子を表したものです。注目すべきは2枚目で、Bruno092から継承したBruno093クラスにオーバーロードを実装しています。複素数にマイナス( - )がかけられた場合、実部、虚部にそれぞれマイナスをかけるようになっています。どちらか一つだけにマイナスをかけると計算結果が変わってしまいますよね。

さて、そうして3枚目でこれを使用したBruno094クラスは、びっくりするほど見やすいスッキリしたものになっていますね。60,61行目でマイナスをかけた時の計算を綺麗に処理しています。これが単項演算子オーバーロードです。

2項演算子オーバーロードについて

先ほどは単項演算子オーバーロードだったのでいわゆる「++」とか「-」のみの項が一つの計算で使うものでしたがやはり2項を伴う演算子の方が一般的ですよね。こちらも書き方の基本形は単項の時とあまり変わりません。

public static 戻り値の型 operator 演算子(データ型 オペランド1, データ型, オペランド2)

引数が2個に増え、これにより内部で扱える数が2つに増えました。後は先ほどとあまり変わりません。

f:id:covory10101101276:20171224220048p:plain

f:id:covory10101101276:20171224220057p:plain

一つだけ違うのは、オーバーロードするメソッド内でTostringメソッドについても定義していることです。これをオーバーライドすることで改めてどうやって文字列で表すかを指定することができます。やはりBruno097クラスはスッキリしていますね。

等価演算子について

最後に注意するのは等価演算子についてです。等価演算子とは、「==」や「!=」などのtrue, falseを返す演算子ですね。なぜ注意しなければならないのかというと、先ほどの2項演算子の時にTostringメソッドを定義しておく必要があったように、等価演算子の場合は演算子の定義以外にも定義しなければならないことが3つもあるからです。下の項目がその3つになります。

-Equalsメソッド

このメソッドは自身のオブジェクトと引数のオブジェクトが等しい際にtrueを返し、そうでない場合はfalseを返すメソッドです。

-GetHushCodeメソッド

これが厄介なもので、オブジェクトに対してあるint型の値を割り振るメソッドです。同じオブジェクトは同じハッシュ値を、異なるオブジェクトの場合は異なるハッシュ値を持つようにしなければなりません。これがなんの役にたつかはわかりにくいですが、このint型の一つの数によって、正否や優劣などのオブジェクト同士の整数での比較ができるのです。

-不等価演算子

これはいわゆる等価演算子の逆で、オブジェクト同士が同じ場合はfalse,異なる場合はtrueを返します。つまり先ほどまでの逆を定義すれば良いわけですね。

f:id:covory10101101276:20171224230709p:plain

f:id:covory10101101276:20171224230714p:plain

f:id:covory10101101276:20171224230717p:plain

一枚目のEqualsメソッドで、GetTypeメソッドで取得した型を比較し、型とその中身が等しい場合はtrue,異なる場合はfalseを返します。GetHushCodeメソッドの中身についてですが、内側のGetHushCodeメソッドでハッシュ値を作り、それを複雑にして返している、とだけ覚えておきます。これで結果を見ればわかるように複素数の比較ができていますね。あ、Cの値を表示し忘れていました。コードを見ればBとCが同じことはわかるので許してください。。。

今日はここまでにしておきます。

 

いやあ今回の内容を読み解くのにまた数日を要してしまいました。やはりわからなくてもまずは実際にやってみることが大事ですね。この冬休み中が追い込みなのでがんばって勉強していきたいです😤😡