プログラミング所感 - 目次 次へ97/11/11(月)関係ないが、今日は1が4つ並んでいる。リスト構造の終端をヌルにするか、先頭にするか2通りのやり方がある。 あるプロジェクトの角度のグループ化では、後者の方が圧倒的にわかりやすい。 プログラムも最初そういう風に作ったが、後からSTLを使って書き直した。 結果的にプログラムは一部わかりにくく、一部わかりやすくなったが、コードの量は減った。 保守性から見てもSTLを使った方がよさそうである。
ここから、本題。ライブラリには3つの側面、「便利」、「融通」、「効率」がある。 バグの記録の方はさっぱり進んでいない。バグが出ているのは出ているのだが。 97/11/14(金)comp.lang.c++で見つけたVC++5のバグ#include <string> using namespace std; int main() { string b1,b2; b1 = "1234567890123456789012345678901234567890"; b2 = b1; b1 = "ok"; return 0; }これは、実行エラーになる。困ったものだ。 98/7/24(金)VC++のSTLのバグ#include <iostream> #include <deque> using namespace std; struct A { char s[256]; }; int main() { int i,j; deque<A> d; deque<A>::iterator it; for (i=0;i<100;++i) { d.push_back(A()); it = d.begin(); for (j=0;j<d.size();++j) ++it; if (it != d.end()) cout << d.size() << " not match!\n"; } return 0; }これをみると、サイズが 8+16n のときにおかしくなる。 dequeは使えんってことか? 98/7/27(月)64ビット整数のビット数を求める関数を フィンローダのあっぱれご意見番を参考に作った。int NumOfBits(__int64 i) { i = (i & 0x5555555555555555) + (i >> 1 & 0x5555555555555555); i = (i & 0x3333333333333333) + (i >> 2 & 0x3333333333333333); i = ((i >> 4) + i) & 0x0f0f0f0f0f0f0f0f; long j = (long)(i + (i >> 32)); j += j >> 16; return (int)(j + (j >> 8)) & 0xff; }話はかわるが、CGIでページ毎の読まれる回数を 調べるものを作った。結果、読まれているのは、 「赤ちゃん」と「リンク」だけだった。 リンクはブックマークをちょっと整理したもの がメインだ。(整理しきれてないが。) 「つれづれ」は全く読まれてないようだ。 (まあ、更新を付けているのは「赤ちゃん」だけだし。) 98/7/28(月)昨日の関数の高速化を図っていたのだが、 不思議な現象になった。2つの関数の速度が、inline にすると逆転するのだ。 いろいろ調べてみると、最適化で計算が省かれていたらしい。 res = func();で、変数 res を volatile にしたら予想通りになった。 うーむ、他のやつは、volatile にしてたのになぁ。 結局、インラインの効果はどの関数でも同じになったが、 場合によっては、展開後に最適化されてさらに速くなるケースも あるかもしれない。 高速化は表引きにしたら、昨日のやつの2、3倍速くなった。 表は1バイト分(=256個)持った。
ちょっと気づいたこと。 98/8/5(水)今週は夏休みだけど、なんで書いてるかって?(まあ細かいことは気にしないで) さて、本題。 プログラムである部分の実行速度を知りたいとき、 for (i=0;i<100000000;++i) func();などとする。しかし、コンパイラの最適化で、 func(); for (i=0;i<100000000;++i);となったりする。(あるいは、forそのものもなくなるかも) そこで、 volatile int i,v; for (i=0;i<100000000;++i) v = func();と書いたりする。これでも、 tmp = func(); for (i=0;i<100000000;++i) v = tmp;となるかもしれない。 こうなったら、吐き出されるアセンブラを見るしかない。 実際、 k = a[i]+a[j]+a[k]; (A) k = 1; (B)(A)を(B)に変更してもスピードが全く変わらないことがあった。 アセンブラを見たら、和の計算がforの外に出されていて、 forの中では、レジスタに1つの値を代入しているだけ ということがあった。
ちなみに、アセンブラを吐き出させるには、
コンパイラオプションで指定すればよい。 98/8/6(木)前回(8/5)の補足である。吐き出されるアセンブラは、 デバッグバージョンとリリースバージョンで 異なる。当然、リリースバージョンで見るべきである。 なぜなら、デバッグバージョンでは、行に対応した アセンブラを吐くが、リリースバージョンでは 最適化の結果、ちゃんと行とは対応しないからだ。 (デバッグバージョンの方が、わかりやすいけど) ところで、次の2つはどちらが、実行速度が速いか?
ちなみに、アセンブラを見ると次のようになる。 (コロン以降は私の注釈。edx等はレジスタ。) ケース A
ケース B
ケース B で無駄な代入が見られるが、しょうがないのだろう。 (9/13/99追記:添字とポインタのどちらが速いかは一概に言えないようである。 実際、v[j] = j を int* p=v.begin();(ループ外) *(p+j) = i; に変えると、 スピードは変らない。ちなみに、v[j]の定義は、*(v.begin()+j) であり、 v.begin() は、v._First である。何でスピードが変るかは不明。) 98/8/13(木)C++ の話じゃなくてExcel97 の話である。新規作成で、Excel5.0/95 形式で保存する。 当然、問題はない。次に、シートの左上をクリックして、 全選択して、保存してみよう。怒られるはずだ。 選択状態も保存されるのだが、これが、Excel95とExcel97で 異なるらしい。それにしても訳分からんメッセージである。 98/8/14(金)K&R では、strcpy が次のように紹介されているらしい。 (数理にK&Rなかったっけ?)char* strcpy(char *dst, const char *src) { while (*dst++ = *src++) ; }初心者には、ちょっと間違いやすいかもしれない。 いろいろ、書いてみてアセンブラがどうなっているか比較してみた。 コンパイラはVC++5 である。 (最近、アセンブラに凝ってしまった。) Case 1: while (*dst++ = *src++) ; Case 2: while (*s) *s++ = *t++; *s = 0; Case 3: do *s = *t++; while (*s++); Case 4: while (1) {*s = *t++; if (*s++) break;}で、結果は次の通り。 Case 1: 00401000 mov edx,dword ptr [esp+8] : edx = t 00401004 mov ecx,dword ptr [esp+4] : ecx = s 00401008 inc ecx : ++ecx 00401009 mov al,byte ptr [edx] : al = *edx 0040100B inc edx : ++edx 0040100C mov byte ptr [ecx-1],al : *(ecx-1) = al 0040100F test al,al : ? al 00401011 je 0040101D : if ==0 goto 00401013 mov al,byte ptr [edx] : al = *edx 00401015 mov byte ptr [ecx],al : *ecx = al 00401017 inc ecx : ++ecx 00401018 inc edx : ++edx 00401019 test al,al : ? al 0040101B jne 00401013 : if !=0 goto 0040101D ret Case 2: 00401000 mov eax,dword ptr [esp+4] : eax = s 00401004 cmp byte ptr [eax],0 : ? *eax,0 00401007 je 0040101A : if ==0 goto 00401009 mov ecx,dword ptr [esp+8] : ecx = t 0040100D mov dl,byte ptr [ecx] : dl = *ecx 0040100F mov byte ptr [eax],dl : *eax = dl 00401011 mov dl,byte ptr [eax+1] : dl = *(eax+1) 00401014 inc eax : ++eax 00401015 inc ecx : ++ecx 00401016 test dl,dl : ? dl 00401018 jne 0040100D : if !=0 goto 0040101A mov byte ptr [eax],0 : *eax = 0 0040101D ret Case 3: 及び Case 4: 00401000 mov ecx,dword ptr [esp+8] : ecx = t 00401004 mov eax,dword ptr [esp+4] : eax = s 00401008 mov dl,byte ptr [ecx] : dl = *ecx 0040100A inc ecx : ++ecx 0040100B mov byte ptr [eax],dl : *eax = dl 0040100D inc eax : ++eax 0040100E test dl,dl : ? dl 00401010 jne 00401008 : if !=0 goto 00401012 retこんなものたくさん出されても、わからないって? 簡単に言えば、一番すっきりしているのは、Case 3と4 となっている。 Case 1は、ちょっと余計なものがあるが、効率は Case 3と変わらない。 Case 2は、効率が悪い。という所である。 でも、素直に書いたら、Case 2だよな。Case 1とか3とか4 は、あまり書かないと思う。 98/8/17(月)最近、VC で STL の map を使うので、#pragma warning(disable:4786)と書くことが多い。 (解説:VC とは、Microsoft(R)の発売する、 C,C++用開発ツールの Visual C++ のことである。 ちなみに、VC でまともに STL が使えるように なったのは、Version 5.0 からである。 また、STL とは、標準テンプレートライブラリ (Standard Template Library)のことであり、 1996年ぐらいから、C++ で使えるようになった。 もともとは、HewlettPackard で開発されたが、 C++の標準化委員会で標準ライブラリで採用された。 便利ではあるが、もっと追加して欲しい機能もある。 map とは集合を管理するコンテナの一種で、 キーと値を組で持つ辞書の様なものである。 使い所によっては非常に便利である。 解説が長くなってしまった。)
pragma により汎用的なプログラミングが
可能になっている。
STL のエラーメッセージは判りにくいものが多いが、
最近は慣れつつある。コツをお教えしよう。 (9/13/99追記:VC++6から上記の pragma はヘッダの前に書かないと 効果がないので注意して欲しい。) 98/8/24(月)MS に対するぐちである。別にC++の話じゃないけど、 雑記しているのが、これだけなので、ここに書いている。ZDnet ニュースの MS のソース利用者狩りという記事によると、 MS の開発しているアプリの中には、他のベンダに公開していない API を利用しているそうだ。なんとも人を馬鹿にしている話だ。 いくら大会社とはいえ、信用なんかできるものではない。 (もっとも、そのうち、誰かに全APIを暴露されちゃうかもね。)
もっと腹が立つのは、Javaに対しても独自機能を隠していれようと
していることである。 98/8/31(月)const の話題を1つ。(3連続月曜更新。何故だろう?実を言えば、家でも更新できるようになっているのだが。) さて、 const n = 10; int a[n];というのは、OKだが、 int i = 10; const n = i; int a[n];これは駄目である。但し、怒られるのは int a[n]; の所だ。 要するに、配列のサイズは定数でなければいけないということだ。 それで、const 指定は、定数という意味ではなく、 変更不可という意味でしかない。但し、定数で初期化した const 変数は 定数として扱えるということだろう。 この「但し...」のことは、ちょっと混乱しやすいような気がする。 まあ、使うときはそんなに気にしなくても問題ないのだが。 10/19の話も参照。 98/9/4(金)またまた Excel の話だ。(タイトルを改名しようか。)Excel は MS の製品にしては、評判がいいし、私もそう思ってたけど、 ポロポロ変な所が出てくる。
まず、1つめ。 もう1つは、同じデータをCSVにして保存すると、 そのファイルが開けないというものだ。
と怒られる。 これも、しばらく調べて原因は分かった。 最初のセルに「ID」と入っていたためだ。 これは、簡単に再現できる。Excelで新規作成して、セルA1に「ID4」と 入れてCSVで保存してみよう。開けないはずだ。 原因は、適当なSYLKファイルを作成して、テキストエディタで見てみれば分かる。 最初が、「ID;」で始まっているのだ。つまり、Excelは「ID」で始まっていると SYLKだと思うのだ。お馬鹿な仕様としかいいようがない。 98/9/7(月)「見える/見えない」、「使える/使えない」の話。private/protected/public は、アクセス制御レベルの設定である。 これは、使える/使えないの違いであって、全て見えてはいるのである。 見えているというのは、名前が同じだと困るということである。 例えば、 class A { int a; };このとき、メンバ関数以外でメンバ a にアクセスして、コンパイルしてみよう。 「a がない」ではなく 「a にアクセスできない」と怒られるはずだ。 この区別が必要となるのは、「同じ名前をつけそうになったとき」である。 「staic 関数の場合のファイルスコープ外」のように見えないのであれば、 同じ名前をつけても一向に構わない。
もう1つ混乱しやすいことがある。入れ子クラスでは、アクセス指定をクラス名と
クラス内のメンバ(及びメンバ関数)に設定できる。 class A { struct X { void Func(){} }; X m_x; public: X* GetX(){return &m_x;} };となってたら、 A a; a.GetX()->Func();と呼び出しても何の問題もない。 つまり、ユーザはクラス A::X は使えないのに、A::X の Func は使えるのだ。 これは、クラス設計者の意図したところではないだろう。 98/9/16(水)Netscape4.05 で HTML を保存すると、改行が 「0x0d,0x0d,0x0a」となる。(Windows95の場合)これは、ファイルに保存するときに、バイナリにしていないせいである。 テキストモードで保存しているので、元データが「0x0d,0x0a」(DOSでの改行コード)のように 来たときに、0x0dで0x0dを書き、0x0aで0x0d,0x0aを書いてしまっているのだ。 (NetscapeはUNIXで開発していて、Windows上ではあまりテストしてないということかな。) 98/9/19(土)オブジェクト指向について。 (以下の話をつらつら考えてたのは、土曜日だと思うけど、書いているのは、実は月曜日だったりする。) これは、C++OnePointでも書いたけど、 書き足りなかった分かな。
小さいプログラムでは、オブジェクト指向の方が効率が悪い。何故なら、
オブジェクト指向風に書くとコードが1.5倍から2倍ぐらいに増えるからだ。 デザインパターンは切り分ける場所を示し、その名前を与えるものである。 つまり、プログラミングテクニックではなく、切り分けかたのサンプル集 なのである。(唯一、テクニックに近いものは、Decorator だと思うが、 他にもあるかも。) オブジェクト指向に関する私の助言はこうだ。
98/9/21(月)Pro- について。(C++の話じゃないけど)プログラマというと、なんとなくカッコよく聞こえると思う。 たぶん、響きからプロフェッショナルとかプロフェッサ(CPUじゃないよ)のイメージが あるのかもしれない。もっとも、pro で始まる言葉は多い。 UNIXなら次のように確認できるだろう。 grep "^[P|p]ro" /usr/dict/words | wc全体の1%近くもある。pro- という接頭語が、「前の」とか「賛成の」 という意味があるようだから、結構いい意味のが多いかもしれない。
それはともかく、ソフト業界では、プログラマーといったら詳細設計書を元に
プログラムを書くだけの人のことを指したりする。この作業は、それほど
面白いものでもなく、難しいものでもない。
プログラマには向き・不向きがある。これは、プログラミングが好きか嫌いか
とかなり相関がある。(好きならば上達していくはずだからだ。)
好きなだけでなく、論理的思考ができなければいけない。
(これができないと、プログラマとしてやって行くのはつらいと思う。)
あと必要な素質は、好奇心旺盛なことだと思う。
コンピュータの業界は、次々と新技術が登場する。いつまでも古い技術で
やっていたら、誰にも相手にされなくなる可能性が高い。 98/10/5(月)VC++のヘルプはWebBrowserコントロールを使っている。 また、IE(インターネットエクスプローラ)も 同じコントロールを使っている。しかし、何故に VC++はIEを必要とするのだろうか?(もし、私のプログラムが math.h の sin を使っていて、あなたのプログラムが、 同じく sin を使っているからといって、あなたが私のプログラムを必要になったら?) これは、MS の陰謀としか言えない。私は、このために VC++5 を少なくとも3回は インストールするはめになった。全く、何とかして欲しいね。 98/10/6(火)auto_ptr を誤解していた。 GC(ガベージコレクション)の代わりになるかと思っていたら、 単に、所有権が移るだけらしい。(まあ、それでも使いではあるけど。)GC の代わりにするためには、所有権ではなく参照数を覚えておかないとだめだろう。 (GC の構築方法は他にもあるけど。) そこで、参照数によってメモリ管理する汎用のクラス refer_ptr を作ってみた。 こんな風に使える。 refer_ptr<A> p(new A(123)),q = p; p = refer_ptr<A>(new A(789));簡単に言ってしまえば、auto_ptr のように使えるが、ちゃんとメモリ管理してくれるものである。 但し、これは配列に使えない。メモリの解放が、delete[] ではなく、delete だからだ。 では、delete[] 用にもう1つクラスを作るべきか? いやいや、もっとよい方法がある。 それが、array_ptr というラッパクラスである。次のように使える。 array_ptr<char> p(new char[10]); delete p; // 内部のポインタに対しては、delete[] が呼ばれるこうすると、auto_ptr でも refer_ptr でも配列に対して使えるようになる。例えば、こんな風に auto_ptr<array_ptr<char> > p(array_ptr<char>(new char[10])); ... // p を使用 // ブロックを抜けると自動的に delete[] が呼ばれるこれらのラッパクラスの詳細をチャレンジの解答にのせる。 もっとも、2時間くらいで適当に作ったやつだからバグがあるかもしれない。 98/10/13(火)strtok を誤解していた。 "123,,45" というのを "," で読込んだら、 "123","","45" と取れると思ったら、"123","45" となる。使えん。 結局、CSV データを1つづつ読むやつを自分で作った。char* GetCSV(char** pBuf) { char *p = *pBuf,*q = p; if (!p) return p; if (*p == '"') { *p++ = 0; do ++q; while (*q && *q != '"'); if (*q == '"') *q++ = 0; } if ((*pBuf = strchr(q,',')) != 0) *(*pBuf)++ = 0; return p; }ちゃんと、""にも対応してある。それに C でも動く。 98/10/19(月)VC++ と g++ は同じ C++ と思いきや、結構違う。 おそらく、g++ の方が独自の拡張を入れていることが多いのだろう。例えば、8/31 のconst の話では、VC++ では怒られるコードが g++ では平気だったりする。他にも、 struct A { static i = 1; };これも、VC++ では怒られるが、g++ では通ってしまう。 (怒る方が正しいと思う。) 98/10/23(金)1byte と 2byte が混在しているときの比較の話である。半角スペースと全角スペースのどちらも読み飛ばしたいときに、 次のように書いていた。 while (true) { if (*p == ' ') ++p; else if (!strncmp(p," ",2)) p += 2; else break; }でも、両方とも半角だったら、単純にこう書ける。 while (*p == ' ' || *p == ' ') ++p;そこで、はたと気づいた。こう書けばいいのだ。 while (*p == ' ' || !strncmp(p," ",2) && ++p) ++p;テストしてみると、うまく行っている。しかし、ぱっと見てよく分からないかもしれない。 でも、私は行数が少ない方が好きなのでこれを採用しよう。 次に、半角スペースと全角スペースのどちらかがくるまで読み飛ばすやつを while (*p && *p != ' ' && *p != " "[0]) ++p;と書いていた。正しくは、 while (*p && *p != ' ' && strncmp(p," ",2)) ++p;こう書くべきだろう。(今回も C++ じゃなく C の話だった。)
追記:DOS od を見てて何か変だと思ったら、バイトオーダが UNIX と逆だからだった。 98/10/28(水)C++ のキャストが新しくなった。次の4種類だ。
#include <cstdio> struct A { int i; A(int i0) : i(i0){} }; struct B { int j; B(int j0) : j(j0){}}; struct C : A,B { C(int i0,int j0) : A(i0),B(j0){} }; int main() { B b(-1); C c1(1,2); static_cast<B&>(c1) = b; printf("%d %d\n",c1.i,c1.j); C c2(1,2); reinterpret_cast<B&>(c2) = b; printf("%d %d\n",c2.i,c2.j); return 0; }結果は、static_cast が C の中の B の部分を置き換えるのに対し、 reinterpret_cast は、C の中の A の部分を置き換えてしまう。 これは、C が A,B の順に並んでいるのに、C の先頭を B の先頭と 解釈するからである。 11/9の話も参照。 98/11/5(木)論理値を表現するために bool という型がある。 bool から int に変換すると、 0 または 1 になることが、C++ の規格で定められている。 しかし、VC++ はそのようにはなっていない。 例えば、次のやつを実行すると、#include <cstdio> struct A {bool b;}; int main() { bool a = A().b, b = true; if (a && b) printf("a && b\n"); if (a & b) printf("a & b\n"); return 0; }「a && b」しか出なかったりする。 (実際には、初期化していない値を参照しているので結果は不明である。) 何故、このようになるかといえば、a を int にすると1以外の数(例えば2)に なり、2&1 が 0 になり、false と判断されてしまうからである。 結論を言えば、「bool から int への暗黙の変換を使用すべきではない」 ということである。(ということで、ビット演算子は整数に変換されてから適用されるから、 bool に対してビット演算子は使うべきではない。) 例えば、i = (j > 0) * 2; というのは、i = j > 0 ? 2 : 0; と書くべきである。
ついでに言えば、int から bool への暗黙の変換もない方が言語としては
親切だと思う。(そして、if 文は bool しか受け付けなくするわけだ。) (9/13/99追記:上の例は、未定義値を使用しているので、例として相応しくない。 また、上記の件も再現性がないので、バグかどうかは分からない。) 98/11/6(金)プログラミングの本には、よく「10項目の指針」 というのがついてたりする。 その中に、「コンパイラの警告レベルは最大にしておこう」というのがあった。 はっきり言って、そういう事を言う輩は、本当にプログラムを書いているか 疑問を持ちたくなる。実際の所、適当なサンプルで警告レベルを最大にして コンパイルすると、警告が100以上も出てくる。 標準ライブラリのヘッダーの中とかでだ。 これでは、仕事にならない。98/11/8(日)MatheMagic という数学の先生の作った、WebPage に こんなパズルがあった。1 2 o 2 3 5 3 o o 6 1 4 o o o o o o o o o o o o上の図で、「o の中に数字を入れよ」という問題だ。 どういう規則になっているかは、自分で考えてみよう。 で、規則は分かっても答は分からないので、プログラムを 書いてみた。最初は、STL で next_permutation を使ったのだが、 さすがに、15! 通りもやると終わらなかった。そこで、次のように直した。 #include <cstdio> #include <algorithm> using namespace std; void Check(int n,bool chk[],int res[],int pos,int i,int j) { int k,a,b,c; if (pos == n) { for (k=0;k<n;++k) printf("%d ",res[k]); printf("\n"); return; } if (j == 0) { if (++j > i) ++i,j=0; for (k=1;k<=n;++k) if (chk[k]) { chk[k] = false; res[pos] = k; Check(n,chk,res,pos+1,i,j); chk[k] = true; } } else { a = res[pos-1]; b = res[pos-i-1]; if (++j > i) ++i,j=0; k = a+b; for (c=0;c<2;++c) { if (chk[k] && k >= 1 && k <= n) { chk[k] = false; res[pos] = k; Check(n,chk,res,pos+1,i,j); chk[k] = true; } k = a-b; } } } int main() { const max = 100; int res[max], n = 15; bool chk[max+1]; fill(chk,chk+max+1,true); Check(n,chk,res,0,0,0); return 0; }わざわざ載せるほどのものでもないんだけど、これなら一瞬で答が出る。 でも、「計算仮面」は 1分ちょっとで答をだしちゃうんだよな。簡単に解く方法でもあるのだろうか? ここでの教訓 「組合せをしらみつぶしに探す場合でも、うまくやれば早くできる」 98/11/9(月)10/28のキャストの話の続きだ。static_cast と reinterpret_cast はそれぞれ次のようにも書ける。 *(B*)&c1 = b; // または (B&)c1 = b; *(B*)(void*)&c2 = b;結局、これがそれぞれのキャストの意味を表しているといえる。 1番目と2番目のキャストの振舞いの違いを理解できないとしたら、 それは以前の私と同じだ(安心しよう)。 たぶん、C++ を知らない C プログラマにとっては同じ意味だと思う。 C++ では、継承関係が出てきたので、 「アドレスはそのポインタ型と共に意味を持つ」 ようになったのだ。 別の言い方をすると、 「アドレスを継承関係のある型のポインタ型にキャストするとずれることがある」 ということだ。 98/11/12(木)クラスとして const を実装するには、どうすればよいだろう。 そもそも、const 属性用に別のクラスを用意しなければいけない ケースとはどのようなものであろうか。例えば、iterator と const_iteratorなどが考えられる。また、動的継承でも必要になるかもしれない。 では、const 属性が付いたときの振舞いはどのようになるだろうか。 実験してみよう。 Any admy; const Any cdmy; Any a = cdmy; const Any c = admy; Any aa(cdmy); const Any cc(admy); a = c; c = a; // NG Any* padmy; const Any* pcdmy; Any* pa = pcdmy; // NG const Any* pc = padmy; pa = pc; // NG pc = pa;上で、NG と書いた所がコンパイルエラーになるところだ。 さて、いきなり解答だ。次のようにして、const Any を const_Any に置き換えれば、上と同じ振舞いになる。 (但し、エラーメッセージまでは同じではない。) class Any; class const_Any { void operator=(const Any&); // 宣言のみ public: // const メンバ関数 }; class Any : public const_Any { public: Any(){} Any(const const_Any& c) : const_Any(c) {} // 非const メンバ関数 // 必要なら const メンバ関数を非const でオーバロードする };Any::Any(const const_Any&) の代わりに const_Any::operator Any() でも よい。どちらがよいのだろう。よくわからない。どちらでもいいのかもしれない。 注意すべき所
(9/8/99 追記:VC++6.0 では、constから派生するように直っている。よしよし) 11/16の話も参照。 98/11/13(金)fj.comp.lang.c++ で面白い話を聞いた。for (int i=0;i<n;++i) { ... } // ここで i は使える?for の () の中で宣言した場合、スコープは、for の中だけでなければ いけない。しかし、VC++ を始め、そうなっていないコンパイラも多い。 それを回避するのが、以下のマクロだ。 #define for if (false) ; else forこうするとうまくいくのだ。なんでだろうか。 else は、実行されるかされないか判らないので、 for 以降も有効にならないようにしてある のだろう。 また、if の後に使ってもよいように工夫されている。 (つまり、if (true) for でもいいのだが、問題がある。) 但し、私ならこんなマクロを使うよりは、for の中で 宣言しないようにするだろう。 98/11/16(月)11/12 の記事で勘違いしていることに気づいた。 わざわざ、「オーバロードはオーバライドの誤りではない」と書いていたのに、 実は誤りだった。恥ずかしい。 チェックすればいいものを、するまでもないと思ってしてなかった。でも、何故、オーバロード(多重定義)にならないのだろう。不思議だ。 3/3の話も参照。 98/12/22(火)1ヶ月ぶりの更新。この前、C++の本を買った。 本の名前は「プログラミング言語C++第3版」Bjarne Stroustrup 著、長尾高弘訳、7000円である。 実は、10年前に出た第1版は会社にもある。この第1版と第3版を見比べてみたが、 量的に3倍に増えている。内容も前より面白そうである。なかなかお勧めである。この本を読むと、いろいろ知らないことにであった。例えば、 if (int i = func()) { // ... }こんな例が出ていた。これの利点は本に出ていた理由のほかに、 = が == の間違いでないとはっきりする所もあると思う。VC++5 でコンパイルできるが、 if 文の後で、変数 i が有効になっていないのが「なんだかなあ」である。 もちろん規格通りなのだろうが、for 文とあってない点が気持ち悪い。 この本の章の終わり毎に、練習問題があるのでやってみたい。できたら、 回答にまとめる。 目次 次へ |