第8章 式と演算子:連載 改訂版 C#入門(3/3 ページ)
演算子を用いた式による計算はプログラミングの基礎であり、C#にも多種多様な演算子が用意されている。今回はこれらを一括して解説する。
8-11 代入演算子(=、+=、-=など)
Visual Basicでは、変数への代入は1つの文だが、C#では代入も式である。つまり、代入そのものが値として認識される。「x=y」という式を書くと、それは「y」と書いたのに等しい。そして、同時に、変数xにyの値が代入されるのである。
84: set x=0
85: x=1 = 1
86: x is now 1
87: x=1; x+=2; value of x is 3
88: x=1; x-=2; value of x is -1
89: x=2; x*=3; value of x is 6
90: x=6; x/=2; value of x is 3
91: x=6; x%=2; value of x is 0
92: x=6; x<<=2; value of x is 24
93: x=6; x>>=2; value of x is 1
94: x=1; x&=3; value of x is 1
95: x=1; x^=3; value of x is 2
96: x=1; x|=3; value of x is 3
もう1つ、+=のように、演算子の名前にイコール記号(=)を付けた代入演算子がある。これは、ある変数に対して、その演算子を適用した計算を行った結果を再び格納するというものである。例えば、「x += y」と書いたら、「x = x + y」と意味的に等価である。
ただし、意味的には等価でも、機能的には等価ではないことに注意が必要だ。例えば、「(x++) += y」と「(x++) = (x++) + y」では結果が異なる。xに1を足す回数が違うからだ。
「+=」では足し算を行うが、同様に引き算、掛け算、割り算、論理演算、シフト演算などを記述することも可能である。
8-12 論理条件演算子(||、&&)
この説明を一番最後にまわしたのは、通常の使い方では論理演算子との違いが分かりにくいためだ。「&」と「&&」、「|」と「||」の違いは、単なる値の問題として考えているかぎり分からない。「||」と「&&」演算子では前後に式を書くが、手前の式を計算した結果、それ以上計算しなくても結果が定まる場合には後の式は計算しないという特徴を持つ。そのため、複数の式の条件を複合して処理を変えたい場合などは、条件判断式を「&」や「|」でつなぐよりも、「&&」や「||」でつなぐほうがより高速になる。
それなら、いつでも「&&」や「||」を使えばよいのかというと、そうではない。「&&」や「||」にはビット単位の論理演算という機能がない。さらには、計算しなくても結果の決まっている式を計算しないということが副作用をもたらす場合がある。出力結果の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しか増えないのである。
98: set x=0
99: returnFalse(x++) && returnFalse(x++) = False
100: x is now 1
101: set x=0
102: returnTrue(x++) && returnTrue(x++) = True
103: x is now 2
104: set x=0
105: returnFalse(x++) || returnFalse(x++) = False
106: x is now 2
107: set x=0
108: returnTrue(x++) || returnTrue(x++) = True
109: x is now 1
「||」や「&&」は、使いこなせばプログラムの処理効率がアップするので、できるだけ、「|」や「&」よりも使うようにしたい。しかし、その場合は副作用だけには注意したい。副作用を起こすような機能を使う場合は、「||」や「&&」とは同じ式に記述しないようにしたほうが安心である。
Column - as演算子 -
データ型を変換するas演算子というものが、C#には存在する。機能としては、キャストによく似た役割を持つ。キャストと異なるのは、変換不能の事態に直面したとき、例外を投げるのではなくnullを返すことと、ユーザー定義の変換を扱わないことである。以下はas演算子をキャストと比較しながら使用した例である。
1: using System;
2:
3: namespace Sample002
4: {
5: class Class2
6: {
7: }
8: class Class3 : Class2
9: {
10: }
11: class Class1
12: {
13: [STAThread]
14: static void Main(string[] args)
15: {
16: object instance1 = new Class3();
17: Console.WriteLine(instance1);
18: Class2 instance2 = instance1 as Class2;
19: Console.WriteLine(instance2);
20: Class2 instance3 = (Class2)instance1;
21: Console.WriteLine(instance3);
22: Class1 instance4 = instance1 as Class1;
23: Console.WriteLine(instance4);
24: Class1 instance5 = (Class1)instance1;
25: Console.WriteLine(instance5);
26: }
27: }
28: }
これを実行すると以下のようになる。
実行結果の画面を見ると、23行目の出力結果が出ていないように思うかもしれないが、表示する内容がnullのため、空行が表示されているのである。24行目のキャストでは同じ変換を行っているが、これは例外を投げている。変換成功の場合は、キャストもas演算子も同じように機能しているが、変換できない場合の挙動が同じではないことが見て取れると思う。
Column - 演算子を定義する -
C#では、C++などと同様に、加算の「+」などの演算子の機能を自分で定義することができる。例えば、複素数や行列など、単純な実数型では表現しきれないが一種の数値として演算されるクラスを作成した場合に利用すると、ソースが分かりやすくなる。演算子の機能を定義した例として以下を挙げておく。
1: using System;
2:
3: namespace Sample003
4: {
5: class Class2
6: {
7: public string str;
8: public Class2( string s ) { str = s; }
9: public static Class2 operator -(Class2 x)
10: {
11: return new Class2( "-" + x.str );
12: }
13: public static Class2 operator +(Class2 x, Class2 y)
14: {
15: return new Class2( x.str + "+" + y.str );
16: }
17: }
18: class Class1
19: {
20: [STAThread]
21: static void Main(string[] args)
22: {
23: Class2 a = new Class2("Hello");
24: Class2 b = new Class2("World");
25: Class2 c = a + b;
26: Console.WriteLine(c.str);
27: Class2 d = -c;
28: Console.WriteLine(d.str);
29: }
30: }
31: }
これを実行すると以下のようになる。
この例では、メンバ変数のstrの先頭に「-」記号を付け加える-演算子(9〜12行目)と、2つのインスタンスの持つ文字列を「+」記号を介して連結する+演算子(13〜16行目)を定義している。演算子の定義はメソッドの定義と似ているが、メソッド名の代わりにoperatorキーワードを用いて、「operator -」のように演算子の名前を指定する点が異なる。引数は単項演算子なら1個、2項演算子なら2個を用意する。戻り値の型としては、その演算の結果として得られる型を記述する。また、演算子はインスタンスから独立した存在なので、staticキーワードを付けておく。
例えば、13行目は、Class2型の2つの値を得てClass2型の値を返すことを宣言している。実際に実行される内容は15行目に記述されているが、見てのとおり、普通のメソッドと何ら変わることはない。しかし、これを実際に呼び出す側のソースはメソッド呼び出しとはまったく違って見える。それが25行目だ。まるで整数を2つ足し合わせているよう簡単に記述されているが、これが、13〜16行目のコードを呼び出しているのである。同様に、27行目のコードが、9〜12行目のコードを呼び出している。
演算子の定義を使うと劇的にソースが読みやすくなる事例があるのは事実である。例えば、.NET Frameworkのクラス・ライブラリに含まれる、座標を保持するSystem.Drawing.Point構造体には、「+」や「-」の演算子が存在する。座標値というのはもともと足したり引いたりするものなので、これらの演算子を使いこなせばソースが分かりやすくなるだろう。しかし、もともと計算になじまない役割を与えられたクラスに+や-の演算子を付けることはソースを分かりにくくする。この機能は積極的に使えばよいというわけではない点に注意しよう。
『新プログラミング環境 C#がわかる+使える』
本記事は、(株)技術評論社が発行する書籍『新プログラミング環境 C#がわかる+使える』から許可を得て一部分を転載したものです。
【本連載と書籍の関係について 】
この書籍は、本フォーラムで連載した「C#入門」を大幅に加筆修正し、発行されたものです。連載時はベータ版のVS.NETをベースとしていましたが、書籍ではVS.NET製品版を使ってプログラムの検証などが実施されています。技術評論社、および著者である川俣晶氏のご好意により、書籍の内容を本フォーラムの連載記事として掲載させていただけることになりました。
→技術評論社の解説ページ
ご注文はこちらから
Copyright© Digital Advantage Corp. All Rights Reserved.