Qiitaの新着エントリを眺めてると、論理演算子の特殊性について、これまでに2度ほど「初めて知った」と言ってる人を見かけたので、試しにまとめてみる。
知ってる人には当たり前だけど、だからこそ知らない人もいるのではないだろうか。
#短絡評価
trueまたはfalseを返す論理演算子(&&, ||)は、最後まで評価しなくても、途中で返すべき値が分かる場合がある。
これを眺めてほしい。
true && true → true
true && false → false
false && true → false
false && false → false
こういう挙動になっていることに気づく。
- &&の左側がtrueの場合、&&の右側がtrueだとtrueを返し、falseだとfalseを返す
- &&の左側がfalseの場合、&&の右側が何であってもfalseを返す
同様に||の場合は、
- ||の左側がtrueの場合、||の右側が何であってもtrueを返す
- ||の左側がfalseの場合、||の右側がtrueだとtrueを返し、falseだとfalseを返す
つまり、&&の左側がfalseの場合や、||の左側がtrueの場合は、右側の値を見る(評価する)必要はない。言語として、この場合は右側の値を評価しない、という仕様にすれば、多少は無駄が省けるかもしれない。そのような仕様を「短絡評価」と呼ぶ。
##C/C++における、評価順序
実は、C/C++では、式中の値の評価順序は処理系に依存する。もちろん演算結果は演算子の優先度通りとなるが、内部的にどういう順序で評価が行われるかは未規定である。
だが、&&と||については、左側から評価すると規定されている。
#短絡評価と副作用
プログラミングにおける副作用とは、値を返す以外の挙動のことを指す。例えば、値の代入や、文字列のコンソールやファイルへの出力などがある。
&&や||が返す値がtrueかfalseかは、短絡評価が行われても変わらないが、短絡された部分に副作用が含まれている場合は短絡評価の有無によって挙動が変わりうる。
#include <stdio.h>
int main(void){
1 && puts("1"); // 1と表示される。
0 && puts("0"); // 何も表示されない。(puts("0")の呼び出しが短絡されたため)
}
この挙動は、エラーチェックなどに使える。
a || error(); // if(!a){ error(); } のような挙動
a || (a = 1); // if(!a){ a = 1; } のような挙動(代入が式である言語で有効)
a && doSomething(); // if(a){ doSomething(); } のような挙動
##ゼロ除算チェックや配列の境界チェックと、短絡評価
短絡評価について特に意識したことがなかった人も、こういうコードを書いたことがある人は多いのでは?
if(b>0 && a/b > c){ doSomething(); }
if(i < size && array[i] == a){ doSomething(); }
もし短絡評価がなければ、こんなコードは書いてはいけないはずだ。&&の左側がfalseのとき、&&の右側は評価せずに飛ばしてくれる短絡評価のおかげで、このコードは許されている。
ちなみに、短絡評価を使い倒すなら
b>0 && a/b > c && doSomething();
i < size && array[i] == a && doSomething();
となる。
#最後に評価した値を返す、過激な言語たち
bool型への暗黙の型変換が行われる言語では、&&や||の左側、右側にくる値はtrueかfalseとは限らない(1や2や3かもしれないし、"aaa"かもしれない)し、また、それらが返す値もtrueやfalseである必要はない。
Cでは、&&や||は、1か0しか返さないが、Perl, Ruby, Python, JavaScriptなどでは、最後に評価した値(つまり、短絡が行われたら演算子の左側の値を、行われなかったら右側の値)を返す。
失敗する可能性のある処理、指定されていない可能性のある引数の評価を行い、
失敗あるいは指定がない場合にデフォルト値を入れる、などの処理が簡単に書ける。
foo = trySomething() || default_value
##三項演算子もどき
Cなどには condition ? true_value : false_value
として、conditionが真のときはtrue_valueを、偽のときはfalse_valueを返す三項演算子がある。これと似たものを短絡評価によって作ることができる。
condition && true_value || false_value
ただし、true_valueが、bool値として評価するとfalseとなる場合は、これではできない。
配列が簡単に作れて、また、空でない配列がtrueとなる言語では、こういう風にできる。
(condition && [true_value] || [false_value])[0]
読みづらいので正直おすすめしないが。
#その他こまごま
##Visual Basicの場合
VB6, VBAは短絡評価をしないので、コードを書く際は注意すること。
VB.NETでは、And, Orは短絡評価しないが、短絡評価する論理演算子AndAlso, OrElseが付け加えられている。
##演算子のオーバーロード
C++では、演算子をオーバーロードできるが。そうした場合、もはやただの関数である。短絡評価も左からの実行もまったく保証されなくなってしまうので注意すること。
デモ: http://codepad.org/WX8myJuK
他にも演算子のオーバーロードが可能な言語があるかもしれないが、その場合も同様に注意すること。