ブルーノの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が同じことはわかるので許してください。。。

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

 

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

例外について(4/3)

f:id:covory10101101276:20171222135010j:image

みなさんこんにちは🌚

前回勢い余って内容が多くなってしまい、3回で終われなかったブルーノです。今回は本当に最後のまとめとして例外の他の例をまとめます!

 

独自の例外

 (1/3)回目で出たExceptionクラスやApplicationExceptionクラスからオリジナルの例外を派生させることができます(もちろん他の例外クラスからも派生させられますが、シールされているものもあるので注意してください)。

f:id:covory10101101276:20171222145218p:imagef:id:covory10101101276:20171222144835p:imagef:id:covory10101101276:20171222145225p:image

3~12行目のBruno089派生クラスをDivideByZeroExceptionから派生させ、その内容をnewキーワードやoverrideキーワードで上書きして新しい例外クラスにしています。

次のBruno090クラスでは、その例外で書き換えた部分をキャッチブロックで全て表示するようにしています。ちなみにURLはディシディアファイナルファンタジーNTのβ版のサイトです。是非みなさんやりましょう😀😀

 

オーバーフローについて

 プログラム実行時に変数のデータ型の範囲を超える値が代入されてしまうとオーバーフロー(桁あふれ)が起きてしまい、計算途中でこれが起こることで結果が大幅に狂ってしまいます。この時、checkedキーワードを使うことでオーバーフロー時にOverflowException例外を発生させることができます。checkedキーワードはtry-catch文のようにオーバーフローが起きそうな文を { } で囲むことで使うことができます。

f:id:covory10101101276:20171222232909p:plain

f:id:covory10101101276:20171222232917p:plain

checked文の内側ではオーバーフローを起こすように簡単な式が組まれていて、これによりchecked文はキャッチ節でキャッチされます。ちなみにchecked文の逆でunchecked文というものもあり、これはオーバーフローを無視します。

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

 

ついに例外が終了しました。次回からは演算子オーバーロードについてやっていきたいと思います。

今日学校でVBAのゲームを作るよう課題を出されました。C#でもゲームのためにこうして勉強してるのにVBAでそんな高度なことをいきなりやらされるとは.....😓

例外について(3/3)

f:id:covory10101101276:20171220221939p:plain

みなさんこんにちは🌚

最近プログラミング勉強したい欲が高まっているのに学校が忙しくてできないブルーノです😹冬休みに向けて頑張ってスキマ時間を見つけてやっていきます!!というわけで前回の例外処理の様々な他の例をまとめていきます。

 

throw文

 あるtry-catchブロックが含まれているメソッドの中で別のtry-catchブロックが含まれているメソッドを呼び出したい時、内側の例外を外側に投げることができます。これを文字のままスロー(throw)と言います。投げるにはオブジェクト化しなければなりません。ちょっと実感が湧きづらいので例のスクショをあげます

f:id:covory10101101276:20171222112644p:imagef:id:covory10101101276:20171222112644p:image

まず3~19行目の行では配列の境界を越えたり0で割ろうとしたりわざと例外を発生させるように作られたCalcメソッドを設けています。まずここで配列を超えると12行目のIndexOutOfRangeException例外を捕まえるキャッチブロックに捕まります。するとそのメッセージを表示した後にDivideByZeroException例外をオブジェクト化し(14行目)、外側にスローするようになっています(16行目)。そしてこの3~19行目のメソッドを呼び出すのが次の21~34行目のBruno087クラスです。このメソッドは先ほどの一連を呼び出すだけのtryブロックで、キャッチブロックにはDivideByZeroExceptionをキャッチするようになっています。そう、先ほどスローするよう組まれたDivideByZeroExceptionはここでキャッチされ、またその旨のメッセージを表示します。そして最後にこのクラスをメインメソッドから実行しています。つまり、自分で例外を作成し、外側に投げているわけですね。ということはこのプログラムの3~19行目の中で0の除算を行わなくても

f:id:covory10101101276:20171222114216p:imagef:id:covory10101101276:20171222114218p:imagef:id:covory10101101276:20171222114222p:image

このようにDivideByZeroExceptionは投げられ、キャッチされるのです。ちょっとメソッドを別々に組んでいてわかりにくいですね。なので今度はこれをforループの時のようにネストしていまいましょう。

 

try-catch節のネスト

f:id:covory10101101276:20171222133053p:imagef:id:covory10101101276:20171222133056p:image

今度は発生させる例外をOutOfRangeExceptionのみにし、DivideByZeroExceptionとOutOfRangeExceptionの両方のキャッチブロックを張りました。最初のDivideByZeroExceptionのキャッチブロックには当然ですが引っかからず、するとそれが外側にあるOutOfRangeExceptionのキャッチブロックにキャッチされます。このようにtry-catch節をネストさせることもできます。

ただし、同時に例外が複数起こるようCalcメソッド内に設けても最初の例外でキャッチ節に移動してしまうのでそれ以降は実行されないようです。

今回はここまでにします。

 

全3回にして分けるつもりが今回ちょっと長くなってしまいました。あと少しだけまとめることが残っているのでちょっと次回も例外についてまとめます。3回で終わらなかったけどゴメンネ!

例外について(2/3)

f:id:covory10101101276:20171206192551p:plain

 みなさんこんにちは🌚

 ちょっとゼルダの伝説時のオカリナに夢中になっていて気づけば最後の記事から2週間が経ってしまっていました。。。ゲームの面白さは一概にグラフィックや派手な展開だけではないということを痛感しました😄

さて今回は前回に引き続き例外の処理についてまとめていきます。

 

Finally節

前回からtry節、catch節と続いてまた新しい節であるFinally節を今回は紹介します。try節で例外が起こった時catch節に行くのは前回やりましたが、この時catch節でこのメソッドが終了させられたり、別の場所に飛ばされたりする場合、それ以降の記述は本来適用されません。しかし中にはcatch節の内容を実行してもしなくてもどうしても実行したい文などが出て来る場合もあり、その時にこのFinally節を設けておくと、try節の後に絶対に実行してくれます。調べたところアプリケーション自体を終了しない限りは実行されるようです。では実際に例を見てみましょう。

 

f:id:covory10101101276:20171219201558p:plain

f:id:covory10101101276:20171219201605p:plain

 各例外をキャッチした後に必ず続けるかどうかをwhileループの中で聞いています。このFinally節にのみループを抜ける手段が設けられるので絶対に来て欲しいですよね。今回はとりあえずここまでにします。

 

気づけば断捨離で長袖をほとんど捨ててしまっていたためにとても寒い思いをしています🤮図書館に避難してこのブログの方にまた注力していきたいです!

学校も今週までだ!冬休みは勉強するぞー

 

例外について(1/3)

f:id:covory10101101276:20171206170757p:plain

みなさんこんにちは🌚

お昼ご飯をついつい買い込んでしまうブルーノです。ポケ森をやって空腹を忘れさせています😑

さて今回からは例外についてまとめていきます。

 

例外について

コンパイルが正常にできたプログラムなどにおいてもエラーは度々発生します。例えば境界の範囲以上に配列にアクセスしようとしたり、Parseメソッドなどで文字列型を数値に変換しようとしてしまった場合などがそうです。こういった予期せぬエラーを例外(Exeption)と言います。例外が発生するとその例外を表す例外オブジェクトが自動的に生成され、プログラムに送られてきます。これを「例外がスローされる」と言います。このスローは英語でthrowのことで「投げる」という意味ですね。文字通り例外が投げられるということです。まずは簡単な例を作ってみましょう

f:id:covory10101101276:20171206172357p:plain

f:id:covory10101101276:20171206172401p:plain

見ればわかるように、ユーザーから二つの数を受け取り、引き算をしてその結果を表示する簡単なプログラムです。問題はこの9行目と13行目で、ここでもしstr1,2に文字列が入っていてdouble型に変換できなかったらどうなるでしょうか

f:id:covory10101101276:20171206172653p:plain

f:id:covory10101101276:20171206172657p:plain

私の場合はこのようにスローされましたと表示されました。windowsアプリケーションの場合は強制終了させられるかもしれません。どこかでうっかりミスをしただけでいちいちプログラムを強制終了されたんじゃたまったものじゃないですね。C#ではこれに対応できるように処置が用意されています。

まず、例外が起こりそうな文をtryブロックの中に記述します。そしてそのtryボックスの中の記述通りのことが起こった場合に実行されるcatchブロックを記述します。具体的には以下の通りです。

f:id:covory10101101276:20171206174143p:plain

f:id:covory10101101276:20171206174150p:plain

まず先ほどと違う点として、例外が投げられそうな記述をtryブロックの中に書き込んでいます。そしてそこで例外が発生した時にcatchブロックの中の処理を行なっています。double d1, d2には例外が投げられた時に何も入っていない状態になってしまうために0で初期化しています。つまり

例外が発生しそうな記述をtryブロックで囲む

起こった場合の処理をすぐ下のcatchブロックに記述する

そのまま下へ実行されていく

という流れになります。

さて、例外が起こったらそれへの対応をすることには成功しましたがこのプログラムでは例外の種類には触れていませんでした。 その内容に応じて対応を変えられればより便利そうですね

例えば...

  • 「年齢を入力して下さい」→「二十」

→「数字で入力して下さい」

  • 「年齢を入力して下さい」→「20」

→「半角で入力して下さい」

など 

まあこの場合は開発者が予想できているので変換してやれよという気もしますが笑

さて、実は例外が投げられると先ほどは言いましたが、これは実はSystem名前空間で定義されているExceptionクラスとそこから派生したクラスからなる「例外オブジェクト」というものが生成されプログラムに送られてくるという行程のことを言っていたのです。この投げられた例外オブジェクトは種類によってその中身(メンバ)が様々に派生しています。このメンバによって対応を変えるようcatchブロックの中に色々なケースの対応を書いておけば良いわけです。

f:id:covory10101101276:20171206181540p:plain

f:id:covory10101101276:20171206181544p:plain

3つのcacthブロックの中にそれぞれ例外クラスについての対応を書きました。それぞれの引数の中の記述の説明は以下の通りです。

  • IndexOutOfRangeExceptionクラス:Exceptionクラスの派生クラス。配列の境界を超えてアクセスしようとすると投げられる例外
  • DivideByZeroExceptionクラス:Exceptionクラスの派生クラス。0で割ろうとした時に投げられる例外
  • Exceptionクラス:全ての例外クラスの基本クラス

結果を見ればわかるようにメインメソッド内で0で割ろうとしたために真ん中のDivideByZeroExceptionクラスがキャッチされました。そして注目すべきは3つ目のExceptionクラス。全ての例外クラスを包括している基本クラスなのですが、これはキャッチされていません。これは、手前で先に別のキャッチ節でキャッチされているのでもうこの一連のキャッチ節では捕まらないからです。これは重要なことですね。ではDivideByZeroExceptionとExceptionのキャッチブロックを逆にするとどうなるかというと実はコンパイルエラーになります。全ての例外はExceptionにキャッチされるのは明らかなのでその後のcatch説が意味を持たなくなってしまうためです。

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

 

例外は記述する量が膨大になりそうですがきちんとケアをすることで異常が起きないプログラムが組めそうです。いざとなればExceptionクラスで指定すればいいからヘーキヘーキ(棒

デリゲート/イベントの復習・応用

f:id:covory10101101276:20171205185835p:plain

みなさんこんにちは🌚

上の画像、マリーナ・ベイというシンガポールの高級ホテルらしいんですがなんか屋上に船が刺さってるみたいですね。FF7のゴールドソーサーを思い出しました(笑)

さて今回はデリゲートの総まとめということでちょっとした一桁限定の加算プログラムを組んで見たいと思います。キーボードから数字が入力されるとその値を足していき、cキーを押すと合計をクリア、xキーでプログラムを終了する、といったものです。早速まずはプログラムを書き、紐解いていくことにしましょう

f:id:covory10101101276:20171205193229p:plain

f:id:covory10101101276:20171205193243p:plain

f:id:covory10101101276:20171205193250p:plain

3つのクラスがこのプログラムにはあります。

Bruno079 →イベントを発生させるクラス

Bruno080 →イベント発生時に呼び出される、つまり起こるプログラム

Bruno081 →これらを準備し、実際に行うプログラム(入力されたキーが終了かどうかはここで判別する)

 

こう書くと単純に見えますね。Bruno081クラスには見慣れないキーワードがいくつもあるので少し混乱してしまいますが。ではメインメソッドの中身に沿って説明していきます。

  • 43行目:ユーザーへの説明
  • 44行目:ConsoleKeyInfo構造体は押されたキーを記述(情報を取得と同義?)します。これを定義。
  • 46,47行目:イベントを実行するクラスとイベントの内容をインスタンス化。
  • 49行目:イベントフィールドにイベントハンドラを設定。

ここまでが準備ですね。そしてwhileループの中身です。

  • 53行目:if(Console.KeyAvailable)というのはキーが押されている場合という意味です。押されていないとき(KeyAvailableがfalseの時)は何も起こりません。
  • 55行目:押されたキーの情報をReadKeyメソッドで取得します。この引数のtrueというのは入力された情報を表示しないという意味です。そして取得した情報はcki構造体に送られます。
  • 56~60行目:cki構造体に送られた文字がxの場合プログラムを終了します。
  • 62行目:ようやくここまで通過したキーの情報をConsoleKeyInfo構造体のKeyCharプロパティにより一桁のunicode文字を49行目のHandlerデリゲートを通してイベントフィールドに返します。

と、こうなります。めっちゃ長かったですね〜😩😂でもおかげでメインメソッドはとてもスッキリしましたね!!デリゲートをもし使わなかったらこのクラスの内容がややこしく膨大なものになっていたでしょう。今回はここまでにしておきます。

 

このブログを書いていると時間があっという間に経ってしまいますがとても楽しいです。今回やったのなんてまさにゲーム開発の基礎っぽいですよね。早く開発して見たいなあ

 

イベントについて

f:id:covory10101101276:20171205170218p:plain

みなさんこんにちは🌚

ポケ森でもぐもぐの元不足に悩まされているブルーノです。ツバクロをキャンプから追い出してお礼をせしめないと...

さて今回はGUIベースのアプリケーションには必須なイベントというものについてやっていきたいと思います。

 

イベント

今までスクリーンショットで貼ってきたものはコンソールアプリケーション(CUI)と呼ばれるもので、ユーザーからの入力に反応する場合(例えばConsole.ReadLineなど)、キーボードからの入力についてのみ反応するようにプログラムを組んでいました。もちろんこれだけでも文字の型を適切なものにしたり時には弾いたりなど、あらゆる入力に対応できるよう工夫が必要でしたが、これが一般的にみるグラフィカルユーザーインターフェースベースのアプリの場合ユーザーはどこをクリック(タップ)したり、ドラッグしたり、時にはキーを打ち込んだり、まあやることが多すぎてわかりません。そこで今まではプロパティなどで「入力されたものが何々(以外)だったら」という形でしたが、今度は「何々(以外)が入力されたら」→「何々する」という形にしていきたいと思います。この「何かが起きた」ことをイベントと言い、このイベントが起きてからそれに対する一連の動作の流れをイベントハンドラと言います。

では画像を見ながら少しづつ紐解いていきたいと思います。

f:id:covory10101101276:20171205174343p:plain

f:id:covory10101101276:20171205174347p:plain

まず注目するのが5~14行目のBruno075クラスで、これはイベントを発生させるクラスになります。7行目のeventキーワードを使ったものをイベントフィールドと言い、これをメインメソッドで実行させます。しかし7行目時点ではこのイベントフィールドには何も入っていないので実行させてはいけません。この何もない状態をnull(ヌル)と言います。そこで9行目~13行目のOnEventnameメソッドでeventnameがnullでない(何か入っている)時にこれを呼び出せるスイッチを設けています。実際のメインメソッド(43行目)ではこのスイッチを実行していますね。このスイッチの役割を果たすメソッドの名前は一般的に「On~~」という名前にします(もちろんしなくても構いません)。Bruno076, 077クラスはただ2種類のshowを表示するだけのメソッドです。これをBruno078クラスのメインメソッドでまず36,37行目にあるようにインスタンス化します。そしてイベントを発生させるクラスもインスタンス化し、40,41行目にあるようにその中のイベントフィールドにメソッドを追加していきます。(この時ソフトに「冗長なデリゲート宣言」とか言われたんですが無駄に長いってことですかね😅もっと短くできるのかな)

そして最後に格納したメソッドを43行目のOnEventnameメソッドで実行しています。

イベントフィールドにメソッドを追加するときは「=」ではなく「+=」で行うよう注意しましょう

今回はここまでにして起きます。

 

だんだん説明が長くなってきちゃいました。でもこれら全部大事なことなので、ノートに書き込む感覚でしっかりメモして行こうと思います。

ポケ森楽しい〜〜。もぐもぐのもとは相変わらず足りない😭