檜山正幸のキマイラ飼育記の[雑記/備忘]「常識」というよりは「理解の基盤」と「説明の方法」より。
str1, str2が文字列だとして、if (str1 == str2)と書いてしまうのはよく見かけますよね(ある意味、自然だと言える)。Cならstrcmp、Javaならequalsを使え、って教えるでしょうが、その理由とか事情とかを心底納得できるように説明するにはどうしますか。文字列がインターンされていれば、==でもよかったりするし。
このネタは、ちょっと前に日経ソフトウェアで連載中のC入門で書いた気が……。
ちょっと引用してみます。
文字列の比較に“==”を使用できない理由 §
“==”は両辺が整数の場合に「同じ」であることを判定できます。しかし,文字列が「同じ」であることを調べるためには使用できません。ところが,使用できないにもかかわらず,文字列に対して直接“==”を使用しても,エラーになりません。
例えば,以下のようなソースコードはコンパイルしても正常に終了します。
char buffer1[1000];
char buffer2[1000];
…
if( buffer1 == buffer2 ) …
実は,この場合の“==”は間違いではないのです。ただし,ここで判定されているのは,文字列が同じかどうかの判定ではなく,文字列を格納している場所が同じかどうか,なのです。つまり,全く同じ文字列が格納されている場合でも,それが別個の変数の場合は,格納場所が違うという理由で条件は不成立と判定されます。
このような理由により,誤って文字列の比較に“==”を使用してもエラーになりません。文字列を比較する場合には注意を払ってください。
でも本当は…… §
実は、上記の文章は檜山さんの問い掛けの答えにはなっていません。
檜山さんの問い掛けそのものに問題がある、というのが私の考えです。
以下、それを説明します。
まず以下の文章に問題があります。
仮にCを使っていると仮定するなら、厳密に言うと文字列型というデータ型は存在しません。文字列は、文字型の配列や、その他の方法で確保されたメモリ上に展開された'\0'で終端されるコードの列によって間接的に表現されるものです。
従って、上記のstr1, str2というのは、文字列を参照するポインタ値か、あるいは文字型の配列ということになります。この2つをまとめれば、「文字列に対する参照を示す式」と抽象的に言っても良いと思います。
このように解釈すれば、if (str1 == str2)は参照の等価性を判定するものであって、文字列の等価性を判定しないことは良く分かります。
より分かりやすく表現するなら、str1, str2というシンボルも不適切で、あくまで参照を示すものだと言うことを示すためにref1, ref2というような名前にする方がベターだと思います。
ただ、実際のソースコード上の話をすれば、それがポインタ型ならptr1, ptr2、配列ならary1, ary2のように表記すると分かりやすいかもしれません。
以上、一応実装は一切引き合いに出さない説明のつもりです。
Javaの場合は…… §
Javaの場合は、以下の部分の「文字列」は「文字列オブジェクト」と表記しなければ不十分だと思います。
そして、オブジェクトに対して作用する"=="演算子は、オブジェクトの参照の等価性を判定するという機能性についての知識があれば、if (str1 == str2)はオブジェクト参照の等価性を判定するものであって、文字列の等価性を判定しないことは良く分かります。
以上、これも実装は一切引き合いに出さない説明のつもりです。
このような解釈が直感に反する……、ということは良く分かりますが、それはJavaの言語仕様を考えた人が、たとえ直感に反しようとも言語仕様を単純化しようとしたためだろうと思います。そういうストイックな美学に異議を差し挟みたいのなら、Javaを使う資格はないと思います。
というか、そいう研ぎ澄まされた達人の境地にある言語を、初心者向けだなどと言ってはいけない……と今は思います。言語仕様が小さいことは初心者に優しいことを何ら意味しません。それどころか、過剰にコンパクトにまとまりすぎた言語は、初心者に厳しいことの方が多いと思います。
実利主義的余談 §
ちなみに、実利主義的立場に立てば、「文字列の参照」の等価性の判定と「文字列」の等価性の判定は、圧倒的に後者の方が出現頻度が高いので、後者をより短い構文で書ける言語の方が便利です。つまり、==で参照を判定し、Equalsメソッドで文字列を判定するよりも、==で文字列を判定して、Object.ReferenceEqualsで参照を判定する言語の方がラクチンです。
残りの3つへの感想 §
残りの3つの問い掛けへの感想を余談として付けておきます。
ローカル変数としてchar buffer[BUFF_SIZE];って宣言して、そのままreturn buffer;として何故いけない? これ、割と問題なしにいったりしますよ(いや、問題ありだけど)。
記憶リソースの使用権の問題ですね。使用権を失った記憶リソースへの参照はいかなる保証もされない、というような表現なら実装に言及しない説明と言えるか?
というか、本来ならそういう仮想的な記憶リソースを記述するレイヤーあってしかるべきという気が……。
代入文の右と左で使える式の形を構文ルールとして理解すべきでしょうか。けっこうバラエティありますよ。n = 3*x; a[1] = n + 2; *(p + 1) = a[i - n];とか。
左辺値と右辺値という概念抜きで説明したいということでしょうか?
メソッド内で使える「this って何?」と聞かれたら…。
私が最もオーソドックスだと勝手に思っているオブジェクトのモデルは、フィールドとメソッドが入った「入れ物」です。
thisは、自分が入っている「入れ物」を指し示す参照です。
別にややこしい話ではないような気が……。
ああ、そうか。クラスという概念が入ってくるからthisの扱いがややこしくなるのか。
また余談 §
『自分が入っている「入れ物」を指し示す参照』っていう表現は、どことなく自己言及的で良いなぁ。
イメージ的にはクラインの壺?