#author("2020-11-09T12:36:36+09:00","default:tutimura","tutimura") #author("2024-05-23T15:10:19+09:00","ldap:bbm85148","土村 展之") [[Cygwinでデバッグ]] #contents ** 数学の表記との違い [#t1b0b7a8] *** 等号と代入、そして lvalue [#s4ba0d4d] 数学で考えると、次の2つの等式は同じ意味です。 (1) a = 2 (2) 2 = a さて、C言語のプログラムで考えると、少し事情が変わります。もしこれらが、if や while の条件式の中に現れて、左右の項を比較しているのだとすればどうでしょう。C言語での比較は == と2文字で表記することに注意します。 (1') if (a == 2) { ... } (2') if (2 == a) { ... } このように書けば、(1')(2') とも同じ意味になります。 しかし、もし代入を意味していれば、状況が異なります。代入の向きは右から左と決まっているので、以下の (1' ') は a に 2 を代入します。ところが (2' ') は文法エラーになります。2 に a の値を代入することはできないからです。 (1'') a = 2; // OK (2'') 2 = a; // NG これは、代入される(できる)ものは特別なものである、ということでもあります。= の左側の式は、英語では "lvalue" という、普通の辞書には載っていない単語で表されます。left value の省略されたものですので、日本語では「左辺値」との訳語があてられます。 左辺値は変数であればよいのかというと、そうでもありません。変数単独であればよいのですが、変数を用いた計算式には代入ができません。 a = 2; // OK a+1 = 3; // NG 同じように左辺値として x は大丈夫でも、x*x はエラーになります。もし x*x をある値にしたいのであれば、その逆関数である平方根を用いて x に代入するように式変形します。 (a) x*x = 4; // NG (b) x = sqrt(4); // OK (c) x = -sqrt(4); // OK(?) > (b)(c) のように、逆関数が1通りでない場合にどうすればよいのかは、プログラマが判断せねばなりません。 *** 不等号 [#j099ac58] 数学では「0≦x<10」のような表記をしますが、C言語の条件文としては、この表記では目的が達せられません。 (1) if ( 0 <= x < 10 ) { ... } // NG (2) if ( 0 <= x && x < 10 ) { ... } // OK (3) if ( x >= 0 && x < 10 ) { ... } // OK (1) は、エラーにはなりませんが違う意味になってしまいます。正しくは(2)や(3)のように、2つの条件を && で結びます。一般に、2つの条件が同時に成りたって欲しければ &&、どちらか片方が成り立てばよいのであれば || をはさんで書き並べます。 > (1) の表現には、gcc だと警告レベルを上げると検出してくれます(→[[Cygwinでデバッグ/不可解な動作]])。Java ではエラーになります。 > ちなみに (1) の式は ((0<=x) < 10) と評価されます。「(0<=x)」の比較結果は(違和感があると思いますが)0 か 1 の数値で表現されます(→[[比較演算>#f925bdd3]])。それが 10 より小さいかが評価されるので、最終的には「常に成り立つ」ということになります。 (2) と (3) はどちらも同じ意味ですが、数直線を思い描く人はにとっては不等号の向きが揃った (2) の表現のほうが理解しやすいでしょう。変数が左にあるほうが落ち着く人には (3) が好まれるでしょう。 さて、上の条件の否定をどう書くのか考えてみます。いろいろなバリエーションが考えられます。 if ( 0 <= x && x < 10 ) { ... } // 否定する前 (a) if (!(0 <= x && x < 10) ) { ... } // 単純に ! をつけて否定にした (b) if (!(0 <= x) ||!(x < 10) ) { ... } // ! を分配して && と || を入れ替え (c) if ( 0 > x || x >= 10 ) { ... } // ! をやめて不等号を入れ替え (d) if ( x < 0 || x >= 10 ) { ... } // 左に変数が来るように (e) if ( x < 0 || 10 <= x ) { ... } // 不等号の向きを揃えた どれも同じ意味ですが、よく使うのは (a), (d), (e) あたりでしょうか。 余談ですが、"<=" と "=<" のどちらか迷う人が多いですが、正しいのは片方だけです。覚え方は「+= も *= も <= も >= も、''すべて = が後ろ''」です。 > C言語の2文字の演算子は = がいつでも後ろですが、Perl では残念なこと(?)に "=~" という演算子があります。 *** 比較演算 [#f925bdd3] 数学で考えれば a+b と a<b は計算の質が異なります。a+b はいつでも計算できます。反面 a<b は計算するものではなく、条件として扱うものです。 ところが、C言語をはじめとする多くのコンピュータ言語では、a+b と a<b のどちらも計算式であって、計算結果としての値を持ちます。C言語でも数学でも、a+b は当然のようにaとbの和です。そしてC言語では、a<b は成り立てば int 型の 1、成り立たなければ int 型の 0 の値になります。変数の a, b の型によらず、比較結果は必ず int 型になります。 > 1 とか 0 とかの値は、言語によって若干のバリエーションがあります。太古の BASIC インタプリタでは整数型の -1 と 0 でした。Java では boolean 型の true と false という値になります。 つまり、c = a+b とすれば c に a+b の値を代入できるように、c = a<b のような代入も可能です。従って、次のような書き換えもできてしまいます(が、あまり使いません)。 if ( a < b ) { ... } // 短縮(?)版 int cond = (a < b); // 分割版 if ( cond ) { ... } 次はよく使う例です。無限ループを while ( 0 == 0 ) { ... } と書くことがありますが、0==0 の演算結果は、成り立つので 1 となります。ですから、 while ( 1 ) { ... } と表記するのと同じ意味になります。 > 条件式は 0 以外は成立とみなされるので、while (2) {...} でも無限ループです。 x が 0 かどうかを判定する関数 is_zero(x) は次のような2通りの書き方が可能です。 int is_zero(int x) { if (x == 0) return 1; else return 0; } int is_zero(int x) { // 短縮版 return (x == 0); } このような関数を呼び出す上では注意が必要です。常套句があって、次のように書きます。 if ( is_zero(a) ) { ... } if の条件式の中で比較を行っていないことに注目して下さい。比較は関数(短縮版)の中ですませているので、if の条件式では 1 と比較することすらしないのが流儀です。この流儀を用いるのはどういう場面かというと、関数名・変数名が is で始まっている時です。プログラムが英語の文章のように読めるでしょう、if a is zero, then ... のように。 否定の条件は次のように書きます。 if ( !is_zero(a) ) { ... } これは if a is not zero, then ... のように理解します。 > C言語の標準関数に isalpha(c) のようなものがあります。不成立なら 0 を返しますが、成立した場合に 1 ではなく、64 のような値を返すことがよくあります。ですから 1 と比較すると間違ったプログラムになってしまいます。 > C 言語ではこのような関数・変数に int 型を用いるため、バラツキに対する注意が必要です。(と言っても、比較しないという流儀を守ればよいだけです。)C++ や最新の C99 には bool 型があるのですが、やはり内部的な値にバラツキがあるので、同じ注意が必要です。安心なのは Java の boolean 型で、true と false の2通りの値に制限されています。(が、やはり比較しないのが流儀です。) > C 言語ではこのような関数・変数に int 型を用いるため、バラツキに対する注意が必要です。(と言っても、比較しないという流儀を守ればよいだけです。)C99 の bool 型や、Java の boolean 型では、true と false の2通りの値に制限されています。(が、やはり比較しないのが流儀です。)