[←1つ前] [→1つ後] [↑質問一覧] [↑記事一覧] [ホームページ]
スタイルなどという言葉はC言語の世界では未定義(^^)なのですが、そのため、 逆に非常によく議論の対象になることでもあります。特に、仕様上はどちらでも 構わないという場合には、個人の好みに左右されるため、意見が分かれます。し かし、C言語も結構長い寿命の言語であり、いろいろな議論を通り抜けてきた慣 習はそれなりの経験に裏付けられた意味のあるものだと考えるべきでしょう。
printf("hello world!¥n"); // お決まりの文句
ただ、一部の処理系には、このような書き方をコメントとみなすように勝手に
拡張されているものがあります。このため、他の処理系では使えないことをうっ
かり忘れて使ってしまう人がいるようです。「私の処理系では
a >>< 1
と書けば右に1ビットローテイトするから、このように書けば他の人も解釈して
くれるだろう」のように考え始めると、結局どんなことを書いてもC言語になって
しまいます。
誰でも理解できる共通概念として標準的なC言語があるのですから、多くの人と の対話が必要なら、標準的な書き方から逸脱しない方が自分のためにもなります。
C++の場合は「//」をコメントとして解釈する規格になっています。
/* i = 1; /* i = foo(i); */
/*
」が現れた場合、次に「
*/
」が現れるまでの全てをコメントとして解釈します。例のコードは、最初、
"i = foo(i);"
だけをコメントにした状態だったが、その後
"i = 1;"
もコメントにしたいとプログラマーが考えたのかもしれません。
コンパイラの種類によっては、ネストしたコメントを書けるようにするオプシ ョンが用意されていますが、後で問題になることを避けるためには、コメントを ネストさせない方がよいでしょう。
main
の型を
void
で定義したら先輩に怒られた。
main
関数の型は
int
と決められています。
void
とするのは間違いです。
にも関らず、ある種の参考書には
main
の型を
void
として説明しているようです。また、初心者に限らず熟練したプログラマーがこ
のように書くことが珍しくありません。このため、特に初心者のプログラマーは
何も考えずに(あるいは特に決まった値を戻す必要がないと思って)
main
の型を
void
にしてしまうことがありますが、ネットではこれは非難の対象になりがちです。
main
の型を
void
とした方が、むしろ自然ではないのか。
void
と定義すべきではないか、という発想があります。考え方としては、これは正し
いと思います。ただ、
main
関数の場合には、それが意味のある値を戻さないということ自体に、むしろ問題
があると考えられます。特別な値を戻す必要がないなら、0でもいいし、
EXIT_SUCCESS
を戻してもいいはずです。何か値を
return
しておく方が無難です。
main
の型が
int
でないといけない根拠は何か。
int
と決められているから、という答えが理解しやすいでしょう。
main
関数が値を指定しないでリターンした場合にホスト環境に戻される終了状態は、
未定義とされています。コンパイラによっては、
int
の関数と
void
の関数とでは、呼び出しの手順が異なるかもしれません。その場合
int
の関数である
main
を想定して書かれたスタートアップルーチンは、
void
と定義された
main
を呼び出すか、あるいはそこから戻ってくる時に、とんでもない結果になるかも
しれません。
規格には、
int main (void) {/*...*/}として定義することも、二つの仮引数をもつ関数
int main (int argc, char *argv[]) {/*...*/}として定義することもできる、と書かれています。これ以外の書き方は規格に はありません。いずれかの書き方をしなければいけません。
int
であるということを述べているのではないのでは。もしその表現を厳密に解釈す
るのなら、
main
関数の実際の処理の内容は、
"..."
という文字列が書かれたコメント一個でなければ仕様に反するのではないか。
main
関数の中身は
{/*...*/}
というものでなければならない、という主張なのですが(これはcomp.std.cで実際
に議論になりました)、それはいくら何でも考え過ぎです。ここは常識で考えれば
、実際はこの箇所にいろんなコードが入るよ、という暗黙の合意があると考える
べきです。
main
の最後に
return 0;
という文があった。
return EXIT_SUCCESS;
とするべきではないか。
EXIT_SUCCESS
という値が成功終了を意味することになっています。従って、0でも全く問題あ
りません。呼び出した処理系に複数の状態を戻したいには、プログラマーが0以外
の特別な値を意図的に使うこともあります。
1996-03-12訂正
static int foo(void) { ... }
しかし、良いことばかりではありません。関数名が分かっている時に、例えば
grep foo *.cを実行すれば、上のように定義されたコードから
foo(void)
という行を抜き出してくれます。これだけでは関数
foo
の型が分かりません。
static int foo(void) { ... }このように書いてあれば、grepは
static int foo(void)
という行を抜き出して
くれるので、この関数が
static int
と定義されていることが分かります。
一つのプログラムソースの中で無意味に混在していなければ、どちらの書き方 でも殆ど問題ないでしょう。
char* p;
という書き方と、
char *p;
という書き方は、どちらがよいのか。
発想としては、
char* p;
というのは「
p
が
(char *)
という型である」という考え方であり、
char *p;
というのは「
*p
が
(char)
という型である」ということになります。特に前者を意識している人なら、
char* p;
と書きたくなると思われます。
私の場合、基本的に後者のように発想するので、
char *p;
と書いた方が分かりやすいのですが。
よく指摘されることとして、複数の変数を宣言する時に、
char* p;
のような書き方をるすと失敗しやすい、というものがあります。
char* p, q;という宣言は、一見、
(char *)
という型の変数
p
と
q
を意味しているようですが、
実際は
(char *)
の変数
p
と、
(char)
の変数
q
を宣言することになります。
char *p, q;このように書けば、先程のような失敗は少なくなりそうです。
このような失敗をするのは、一行で複数の変数を宣言するから悪いのだ、とい
う人もいるようですが、他の人もすべてそのように考えるくれるとは限らないこ
とも頭に入れておくべきです。また、
typedef
を使うことも、失敗を避ける効果があります。
typedef char *charp; charp p;このように定義しておけば、分かりやすいし、失敗も少なくなります。
空白は、その両側にあるモノを分かれて見せる働きがあります。適切に空白を 使うと、プログラムをぱっと見た時に、随分見易くなるものです。
K&R 2nd.では、4文字分のインデントが使われています。これが一つの目安 になるでしょう。
タブは使ってもいいのですが、空白と混在させると話がややこしくなります。 使うのなら、タブ一つをインデントの階層に対応させるとよいでしょう。
while ((c = getchar()) != EOF) { putchar(c); }
while ((c = getchar()) != EOF) { putchar(c); }
while ((c = getchar()) != EOF) { putchar(c); }
while ((c = getchar()) != EOF) { putchar(c); }
case
はどのような位置に書くべきだろうか。次のような書き方がよくあるが、どのよ
うな規則なのか分からない。
switch (c) { case 'a': /* ... */ break; default: break; }
switch
文の書式は、
switch (式) 文です。これは
if
や
for
と同じように考えれば、ブロックにする場合、基本的には次のよう位置に文を書くことになります。
switch (式) { 文 文 ... }さて、問題は
case
です。ラベルを書く位置については、次の原則を覚えておきましょう。
「ラベルは本来のインデントの位置よりも一段浅くインデントする」
このような例外を設けるのは、ラベルは本来の処理の流れとは違った所から飛 んでくる目印ですから、他の文の外にはみ出すように書くことによって、より目 立つようにした方が分かりやすくなるからです。
この原則は
goto
の飛び先のラベルの場合だけでなく、
case
、
default
ラベルにも適用します。その結果、質問の例にあるような位置に
case
を書くことになります。
この他の書き方として、ラベルを本来の位置より半分だけ浅くインデントする 場合もあります。
switch (c) { case 'a': /* ... */ break; default: break; }また、
switch
の中に限って二段深くインデントして、ラベルはそれより一段浅くする人もいま
す。この場合、
switch
がネストすると、すぐにインデントが深くなりすぎて見にくくなるという欠点が
あります。
switch (c) { case 'a': /* ... */ break; default: break; }
goto
は使ってはいけないと言われた。なぜか。
goto
を使うと処理の流れが分かりにくくなることが多いからです。
しかし、
goto
はC言語の仕様に定められている正しいキーワードです。使ってもC言語として問
題が生じることはありません。また、
goto
を使わない方がむしろ分かりにくくなる場合もあります。典型的な例としては、
多重のループの中から一番外への脱出があります。
for (i = 0; i < 10; i++) { for (j = 0; j < 10; j++) { if (a[i][j] == 0) goto found; } } printf("not found¥n"); return; found: /* 見つかった時の処理 */この例では
for
のループが二重になっているため、
break
だけでは抜け出ることができません。
このような処理も、
goto
を避けることは不可能ではありません。大きく分けて、二つのやりかたがありま
す。まず、
goto
を書きたい箇所に
return
が書けるように、関数を工夫して使うという方法です。
void foo(void) { for (i = 0; i < 10; i++) { for (j = 0; j < 10; j++) { if (a[i][j] == 0) { /* 見つかった時の処理 */ return; } } } printf("not found¥n"); return; }このように書くためには、新たに関数を用意しなければならないかもしれませ ん。その結果、処理のオーバーヘッドが増えるかもしれません。しかし、多くの 場合、それは些細なことにすぎないでしょう。
もう一つの典型的な方法は、フラグを使うというものです。
for (not_found = 1, i = 0; not_found && i < 10; i++) { for (j = 0; not_found && j < 10; j++) { if (a[i][j] == 0) { not_found = 0; } } }これも、ループの判断の度にフラグの検査をしなければならず、ループの回数 によってはオーバーヘッドが問題になるかもしれません。また、プログラムその ものが繁雑になってしまいます。このようなフラグを導入するよりは、gotoを書 いた方がましだ、と思う人は多いでしょう。
#define begin { #define end }
その他大勢のCプログラマにとって、このような定義は邪魔物以外の何物でもあ
りません。記号を使うことによって、他のキーワードや名前を目立たせるという、
C言語の持っている特徴を放棄することにもなります。
{}
の意味が分からないとい
うなら別ですが、そうでなければ
{}
を使うべきです。
もしかすると、あなたの使っている端末は特殊で、
{}
を入力することが大変困難なのかもしれません。それならやむを得ないという考
え方もありますが、むしろ端末をリプレースした方がよいという考え方もありま
す。
a = 1, b = 2, c = 3;なぜ次のように書かないのか。
a = 1; b = 2; c = 3;
一つの文を一行に書くという原則は、全く改行しないで複数の文を一行に書く よりも、ずっと見やすいコードを作る点で有意義です。昔、BASICという言語では、 実行速度とメモリの節約のために、複数の文をできるだけ一行で書くという技法 が好まれたことがあります。C言語をコンパイラで使う場合は、そのように詰めて 書いてもメモリの節約になるわけではありません。
カンマ演算子は、左から右へ一つずつ式を実行していきます。殆どの場合、セ ミコロンを使った場合と同じ振る舞いになるはずです。次のような場合は注意が 必要です。
if (reset) a = 1, b = 2, c = 3;このカンマをセミコロンに置き換えると、
if
で条件判断された結果は
a = 1;
という文のみに掛かり、それ以外の文は
reset
の値にかかわらず実行されてしまいます。
=
と比較の
==
を間違う、という伝説が根拠になっています。変数には代入することができます
が、定数には他の値は代入することはできません。比較の左辺に定数を書くよう
にすれば、間違って=と書いてしまった時に、コンパイラがエラーにしてくれるの
で、変なバグが入ったプログラムを実行して致命的な結果になることを避けられ
ます。
しかし、このような癖を身に付けても、比較の両辺が変数である場合には何の
役にもたちません。また、多くの人が、プログラムを左から右に読んでいきます。
ある変数の値が何であるか調べる、という発想があるのなら、左に変数を書いて、
「もし
fp
が
NULL
なら」と考える方が自然です。「もし
NULL
が
fp
なら」と考える人は滅多にいないでしょう。日常の言語(日本語や英語)では、
主語が先に現れますから、普段、そのような発想をしているはずです。変な順序
で物事を考えると、かえってミスを招きかねません。個人的には、このような理
由により、
if (fp == NULL)と書く方が望ましい、と考えます。
CheckMode
と書くべきか、それとも
check_mode
と書くべきか。
check_mode
の方が、checkとmodeの間に隙間があるため見やすいと思います。しかし、文字数
が増えるのは嫌だ、という考え方もあるでしょう。
return (0);
のようなコードを見た。なぜ括弧が必要なのか。
return
の後に括弧が必要な処理系が大昔にあったという伝説があります。実際、K&R
の初版では、
return
の戻す値には必ず括弧が付いていました。しかし、いまや
return
の後には括弧はありません。
括弧がある方が見やすいと主張する人もいますが、たいした根拠はありません。
括弧がない場合、うっかり
retrun (0);
と書いてしまった時に、全てのコンパイル
が終了してリンクし終わる寸前に「
retrun
という関数が定義されていない」というエラーが発生するまで気付かないかもし
れません。たまたま
retrun
という名前の関数があったら、もっと面白いことになります。