リテラルなどのトークンを複数組み合わせて、何らかの意味のある処理を行う 1 つの実行単位が文です。 その中でも、コードの多くを占めると考えられる文が式文です。
式文は、計算を行うための演算子や、関数を呼び出すなどして、何らかの副作用や結果を生み出します。 計算を行う方法は、C 言語と同様に実際の数学で利用される + や - 、* や / 等を用いて行うことができます。 計算順序も、数学と同様で ( ) 内の式が最優先され、次に除算や乗算、最後に加減さんが行われます。
10 * ( 3 + 5 ) / 2;
この式文は、整数リテラルだけを用いて計算を行う簡単な計算式を表しています。 式文の末尾は、必ずセミコロン ; で終了しなければならないという決まりも C 言語と同じです。 その間には、トークンが適切であればタブ文字や改行文字があっても問題はありません。
ただし、この式文はコンピュータにとって意味のあるものではありません。 コンピュータを使って計算を行う以上、その結果はなんらの副作用を起こさなければなりません。 例えば、関数に計算結果を渡して処理を行ったり、メモリにデータを記憶するといった方法です。 メモリにデータを保存するには、やはり変数を使いますが、変数については後述します。 この場は、演算結果を関数に渡して画面に表示してみましょう。
stdout . writeLine(toString(32 * 4 - 64 / 2));
例えば、このように式文の結果を toString() 関数に渡すことができます。 toString() 関数には、式文の最終的な計算結果が渡されます。 D 言語の基本的な計算用の算術演算子は次のようなものが定義されています。
演算子 | 解説 |
---|---|
+ | 加算 |
- | 減算 |
* | 乗算 |
/ | 除算 |
% | 剰余 |
演算子は、その前後に計算対象となるトークンが必要となります。 このときのトークンをオペランドと呼びます。 例えば 1 + 2 という式文では、+ 記号が演算子で 1 と 2 がオペランドとなります。 これらの計算用の演算子は、前後に 2 つのオペランドを必要とすることから 2 項演算子に分類されます。
数値の計算などではなく、比較などを行うには関係演算子を利用します。 関係演算子は、算術演算子と同様に前後にオペランドを取る 2 項演算子ですが、結果は常にオペランドの比較結果です。
C 言語では、比較結果などの是非を問う値を 1 か 0 かで区別していました。 D 言語ではこれを予約語として定めていて、真である場合は true、偽である場合は false で示します。 ただし、true は 1、false は 0 に等しいと考えることができます。 10 と 10 が等しいかどうかを調べた場合、10 と 10 は同じ値なので true、50 が 100 よりも大きいかどうかを調べた場合、50 は 100 よりも大きくはないので false というように表現します。
関係演算子 | 解説 |
---|---|
== | 等しい |
!= | 等しくない |
> | より大きい |
>= | 同じか、より大きい |
< | より小さい |
<= | 同じか、より小さい |
!<>= | 比較不能 |
<> | より小さい、またはより大きい |
<>= | 比較不能ではない |
!<= | 比較不能、またはより大きい |
!< | 比較不能、または同じか、より大きい |
!>= | 比較不能、またはより小さい |
!> | 比較不能、または同じか、より小さい |
!<> | 比較不能、または等しい |
多くのプログラマは、途中までは使い慣れた関係演算子なので難しくないでしょう。 ところが、よく見ると !<>= など、従来の C 言語では見られない演算子が存在します。 比較不能かどうかを調べるこれらの演算子は、オペランドに非数 NaN が存在するかどうかを調べます。
浮動小数点数演算では、現代のほとんどの CPU が ANSI/IEEE std 754-1985,IEEE Standard for Binary Floating-Point Arithmetic、通称 IEEE 754 を採用しています。 IEEE 754 では非数 Not a Number を NaN として定めていて、NaN に対する演算は、以降すべてが NaN となります。 無限大との計算や 0 / 0 などの計算を行うと、その結果は数学上、数ではなくなるので非数となります。 D 言語は、NaN との比較を言語レベルで規程した珍しい言語なのです。
import std.stream; int main() { stdout.printf("10 == 10 : %d\n" , 10 == 10); stdout.printf("50 > 100 : %d\n" , 50 == 100); stdout.printf("40 < 40 : %d , 40 <= 40 : %d\n" , 40 < 40 , 40 <= 40); stdout.printf("(0.0 / 0) !<>= 0 : %d\n" , (0.0 / 0) !<>= 0); stdout.printf("true : %d , false : %d\n" , true , false); return 0; }
このプログラムの実行結果を見れば、それぞれの関係演算子の効果を確認することができるでしょう。 関係演算子が比較する条件を満たしていれば true、そうでなければ false が返されます。 最後に、true が 1 であり false が 0 であることを証明するために、それぞれをそのまま printf() 関数で表示しています。
因みに、true や false、あるいはリテラルなど、他の関係する演算子やオペランドが存在しない 1 つのトークンで決定している式を原始式と呼びます。
複数の条件式を組み合わせるには OrOr 式と AndAnd 式を使います。 OrOr 式とは || 演算子 を用いた式です。 この演算子は左右のオペランドのうち一方が true であれば true を返します。 AndAnd 式とは && 演算子 を用いた式で、左右のオペランドの一方が false であれば false を返します。 || 演算子は「どちらか一方が…であれば」という条件に用いることができ、&& 演算子は「双方が…であれば」という条件を実現させるときに利用します。
C 言語の経験者は承知だと思いますが、これらの演算子は左オペランドから評価し、左オペランドで結果が確定すれば右オペランドを省略します。 例えば、|| 演算子は左オペランドの結果が true であれば、結果は確実に true であることがわかります。 && 演算子は、左オペランドが false であれば、結果は確実に false です。
import std.stream; int main() { stdout.printf("false || false : %d\n" , false || false); stdout.printf("true || false : %d\n" , true || false); stdout.printf("false || true : %d\n" , false || true); stdout.printf("true || true : %d\n" , true || true); stdout.printf("false && false : %d\n" , false && false); stdout.printf("true && false : %d\n" , true && false); stdout.printf("false && true : %d\n" , false && true); stdout.printf("true && true : %d\n" , true && true); return 0; }
このプログラムの結果を見れば、|| 演算子と && 演算子の効果を視覚的に確認することができます。 || 演算子の場合はどちらが一方でも true であれば true になりますし、&& 演算子はどちらか一方でも false であれば false を返してます。
整数をビットレベルで演算する論理演算子やビットシフトも、基本的には C 言語と同じです。 一方のビットが 1 ならば 1 を出力する論理和は | 演算子、双方のビットが 1 ならば 1 を出力する論理積は & 演算子、片方のビットが 1、もう一方が 0 のときに 1 を出力する排他的論理和は ^ 演算子 を用います。
整数のビット列をそのまま左右に移動させるには、ビットシフト演算子を利用します。 ビット列を左に移動させるには << 演算子を使います。 D 言語では右シフト演算は機能が拡張され、算術シフトと論理シフトを明確に行えるように定義されています。
負数を表現できる符号付整数において、符号を表す最上位ビットを保存したまま右にシフトする算術シフトは >> 演算子で、最上位ビットも含めてビット列をそのまま右にシフトする論理シフトは >>> 演算子で行うことができます。 ビットシフト演算子は、左オペランドにシフト対象の数を、右オペランドにシフトする数を指定しなければなりません。
対象の値 << シフト数
例えば、左シフトする場合はことのようになるでしょう。 値を 2 ビット左にシフトする場合はシフト数に 2 を指定します。 このとき、ビットをシフトしたことによって最上位、または最下位ビットの空白は 0 で埋められます。 1 バイトのビット列 0101 1101 を左に 2 シフトした場合は 0111 0100 となります。
import std.stream; int main() { stdout.printf("-8 >> 1 = %d\n" , -8 >> 1); stdout.printf("-8 >>> 1 = %d\n" , -8 >>> 1); return 0; }
このプログラムを実行すれば、右シフトにおいて、D 言語は算術シフトと論理シフトを分けて、明確に指示することができるということを確認できるでしょう。 算術シフトは、符号を保存するため、負数の値をシフトしても負数を保ちます。 しかし、論理シフトは最上位ビットも含めて丸ごと移動させるため、符号が消滅してしまいます。