連載C#入門第8回 式と演算子 |
四則演算(「+」、「-」、「*」、「/」)
基本的に、四則演算は、常識のとおりに機能する。「+」は足し算、「-」は引き算、「*」はかけ算、「/」は割り算である。ただし、Visual BASICプログラマはいくつか注意が必要である。これらの演算子は、何もしなければオーバーフローをチェックしない。そのため、値が桁あふれを起こした場合、単純にあふれた桁を捨てた値になる。最小値と最大値は、データ型によって異なり、また、符号有りと符号無しでも異なる。ベテラン・プログラマは、桁あふれが消えることを前提にプログラムを書くこともあるが、100%オーバーフロー時の動作を把握できない場合は、「オーバーフロー、アンダーフローしないようにプログラムを書く」ということを心がけよう。そうしなければ、出力結果の3〜5行目、7〜8行目、10〜11行目のような、一見デタラメな結果を得ることになる。
|
|
桁あふれによって、一見デタラメな値が表示されたもの |
割り算には別の注意がある。整数と整数の割り算は整数になり、端数が出ても小数形式に変換されたりしない。小数の割り算を行う場合は、浮動小数点数と浮動小数点数の割り算として行わねばならない。12〜13行目が整数の割り算で、14行目が浮動小数点数の割り算である。
|
|
整数値の割り算と浮動小数点値の割り算 |
剰余演算(「%」)
「%」は割り算を行ったときの余りを得る演算子である。15行目は6割る3の余りが0であることを示す。16行目は6割る5の余りが1であることを示す。剰余演算子は整数で使うものである。
|
|
剰余演算子の使用例 |
比較演算(「<」、「>」、「<=」、「>=」、「==」、「!=」)
数値の大小と一致を比較する演算子である。演算の結果は、条件が成立すればTrue、成立しなければFalseという値になる。Visual BASICプログラマは、一見似て非なる書式に注意しなければならない。Visual BASICでは一致は「=」だが、C#では「==」と2文字のイコール記号を記述する。不一致は、「<>」ではなく、「!=」と書く。また、「=>」や「=<」はVisual BASICでは問題ないが、C#ではエラーになる。
is演算(「is」)
isは、データの型を判定する演算子である。数値計算を行う他の演算子とは少々性格が異なる。isの手前には任意の値を書き、後ろにはデータ型の名前を書く。もちろん、データ型の代わりにクラス名を指定してもよい。35〜36行目の実行結果を見ると分かるが、整数の1は、int型であるので、「1 is int」は成立する。成立した場合はTrueの値になる。しかし1.0は浮動小数点なので、整数の一種であるint型ではあり得ない。だから、1.0 is intはFalseという値になる。
|
|
is演算の使用例 |
論理演算(「&」、「^」、「|」)
「&」はAND演算を、「^」はXOR演算を、「|」はOR演算を行う。37〜45行目は、TrueとFalseの組み合わせにそれぞれの演算子を適用した場合を示す。AND、 XOR、 ORの演算子の意味を知っていれば、結果は歴然だろう。分かりにくいのは、61〜63行目で数値を相手にこれらの演算子を使った場合だ。これらは数値を構成するビットごとに論理演算を適用することになる。ビット表現を理解しているプログラマにのみ意味がある使い方だろう。
|
|
論理演算子の使用例 |
条件演算(「〜 ? 〜 : 〜」)
この演算子は3つの値を持ち、それらを「?」記号と「:」記号で分離する。「式1?式2:式3」という順番に書き、式1の結果次第で、式2と式3のどちらを解釈するかを決める。プログラム言語風に書けば、「if( 式1 ) 式2 else 式3」という感じだ。そして、この演算子全体が結果として得る値は、式2と式3の中で評価された方の値ということになる。46〜47行目の例がこの条件演算を実際に実行した結果である。ここでは式1として、trueやfalseを書いているが、実際には実行時に値が変化するような式を書く。
|
|
条件演算の使用例 |
シフト演算子(「<<」、「>>」)
シフト演算子はビットシフトを行う。「<<」は左シフト、「>>」は右シフトで、演算子の後ろに書く値がシフトするビット数になる。昔は、2の倍数のかけ算と割り算をシフトで行うというテクニックがあったが、現代ではコンパイラの最適化機能が自動的にその程度の最適化はやってくれるので、意識する必要性はほとんどないだろう。ビット表現を理解するプログラマだけが、シフト演算子を使うべきであり、それ以外のプログラマは手を出さない方がよいだろう。
単項演算子(「+」、「-」、「!」、「~」)
単項演算子は値の手前に付ける演算子である。「+」は値の符号を変えないという機能を持つ(つまり何もしない)。「-」は値の符号を反転させる。「!」はtrueとfalseを逆転させる(not演算)。「~」は「!」と似ているが、ビット単位で数値を反転させる(1の補数を取る)。これらは、2項演算子、つまり前後に値が付く使い方をされた場合と機能が異なることに注意が必要である。たとえば、「x-y」は2項演算子として「-」を使っており、「-x」は単項演算子として「-」を使っている。
インクリメント・デクリメント演算子(「++」、「--」)
変数の前後に「++」あるいは「--」という演算子を付けることができる。「++」は変数の値に1を加え変数に戻す。「--」は変数の値から1を引き変数に戻す。68〜71行目の実行結果を見ると分かるだろう。68行目で0にセットされた変数xの値が、「x++」を表示するごとに増えていく。変数xの値が変わるのは、「++」演算子の効力によるものだ。同じように76〜79行目を見れば、「--」を利用して値が減っていく様子が分かると思う。
|
|
インクリメント演算子、デクリメント演算子の使用例 |
さてここで問題なのは、「++」や「--」を変数の前に書くか後ろに書くか、である。前と後ろでは結果が異なる。68〜71行目と72〜75行目の違いは、「++」をxの手前に書くか後ろに書くかの違いだけだが、結果はまったく違っている。「++」を手前に書くと言うことは、変数xの値を利用する前に1が足されると言うことを意味する。一方、「++」を後に書くと、変数xの値を利用した後で、1が足されることを意味する。つまり、ここでは出力される前か後か、という相違になる訳だ。
「++」と「--」は単独で使われることがある。つまり、「x++」と書いて、その値をまったく利用しないと言うことだ。しかし、変数の中身は書き換えられるので、変数に1を足す(あるいは引く)ときには、「x = x + 1;」よりも「x++;」の方が簡潔なので、よく用いられる、
代入演算子(「=」、「+=」、「-=」 …)
Visual BASICでは、変数への代入は1つの文だが、C#では代入も式である。つまり、代入そのものが値として認識される。「x=y」という式を書くと、それは「y」と書いたのに等しい。そして、同時に、変数xにyの値が代入されるのである。
もう1つ、「+=」のように、演算子の名前にイコール記号(=)を付けた代入演算子がある。これは、ある変数に対して、その演算子を適用した計算を行った結果を再び格納するというものである。例えば、「x += y」と書いたら、「x = x + y」と意味的に等価である。
ただし、意味的に等価でも、機能的に等価ではないことに注意が必要だ。たとえば、「(x++) += y」と、「(x++) =(x++) + y」は結果が異なる。xに1を足す回数が違うからだ。
「+=」は足し算を行うが、同様の表記によって引き算、かけ算、割り算、論理演算、シフト演算なども可能である。
論理条件演算子(「||」、「&&」)
これをいちばん最後にまわしたのは、通常の使い方では論理演算子との違いが分かりにくいためだ。「&」と「&&」、「|」と「||」の違いを理解することは、単なる値の問題として考える限り分からない。「||」と「&&」は、前後に式を書くが手前の式を計算した結果、それ以上計算しなくても結果が決まっている場合、後の式は計算しないという特徴を持つ。そのため、複数の式の条件を複合して処理を変えたい場合などは、条件判断式を「&」や「|」でつなぐよりも、「&&」や「||」でつなぐ方がより高速になる。
それなら、いつでも必ず「&&」や「||」を使えばよいのかというと、そうではない。「&&」や「||」にはビット単位の論理演算という機能がない。それから、式を計算しないということが副作用をもたらす場合がある。98〜100行目を見ていただきたい。
|
|
論理条件演算子の副作用の例 |
98行目で変数xは0にされている。returnFalse()という関数は何もせず、falseを返すだけの関数である。そこで、returnFalse(x++)という式が何を意味するのかというと、この式そのものはfalseという値を持つが、式を計算するときに変数xの値が1個増えるという副作用が付く。では、「returnFalse(x++) & returnFalse(x++)」という式になると副作用として何が起こるのか。当然、変数xは2だけ増える。さらに「&」を「&&」に変えると何が起こるだろうか。「returnFalse(x++) && returnFalse(x++)」は、最初のreturnFalse()関数の戻り値をチェックした時点で、後半の式がどんな値になろうと式全体の値がfalseになるのは必然である。そのため後半の式は計算しないということになる。そこで、後半の式の引数に書かれた「x++」は処理されなくなる。つまり変数xは、1しか増えないのである。
「||」や「&&」は、使いこなせばプログラムの処理効率がアップするので、できるだけ「|」や「&」よりも使うようにしたい。しかし、その場合は副作用だけには注意したい。副作用を起こすような機能を使う場合は、「||」や「&&」とは同じ式に記述しないように心がけると安心だろう。
まとめ
今回は、C/C++プログラマには退屈な内容だったと思う。だが式の持つ副作用や、独特の挙動を示す「||」や「&&」演算子など、C/C++あるいはJavaプログラマなら常識であっても、その他のプログラム言語の経験者には極めて大きなハードルになりかねない恐れがあるものである。これらの機能に親しんでいないプログラマの皆さんは、ここでじっくりとソースコードと計算結果を見比べて、挙動を納得していただきたい。
次回は、C#のさまざまなステートメントを取り上げたいと思う。
INDEX | ||
C#入門 第8回 式と演算子 | ||
1.C#の演算子 | ||
2.四則演算(+、-、*、/) | ||
コラム:Visual Studio.NET ベータ2使用時の注意 | ||
「C#入門」 |
- 第2回 簡潔なコーディングのために (2017/7/26)
ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている - 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう - 第1回 明瞭なコーディングのために (2017/7/19)
C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える - Presentation Translator (2017/7/18)
Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
|
|