C言語の初学者、とりわけ日本人の場合には、プログラムにスペースを空けずに詰めて書く人が多いようです。しかし、スペースや改行を効果的に使うと、読みやすくなりますし、規則的なプログラムを書くのにも有用ですので、早めにスペースを入れる習慣を身につけましょう。
英文をタイプする上では、コンマやセミコロンの後ろにスペースを空ける習慣というか、厳格なルールがあります。このため欧米人は、C言語でも自然とスペースを入れたくなるのだろうと推測します。
次の2つは同じ意味ですが、どちらが読みやすいでしょうか。
for(i=0;i<10;i++){...}
for ( i=0; i<10; i++ ) {...}
改行も、C言語ソース上ではスペースと同じ扱いですので、適宜改行しましょう。同じ意味のものを2組挙げます。
dist2 = (x1-x2) * (x1-x2) + (y1-y2) * (y1-y2);
dist2 = (x1-x2) * (x1-x2) + (y1-y2) * (y1-y2);
printf("year=%d month=%d day=%d mday=%d\n", year, month, day, mday);
printf("year=%d month=%d day=%d mday=%d\n", year, month, day, mday);
2行の文字の位置が揃って見やすいと思うのは当然のことに思えますが、これには等幅フォントを使っているという前提が必要です。欧米人の中にはプロポーショナルフォントに慣れていて、「読みにくい等幅フォントを使うと開発効率が下がる」という人もいるそうです。そういう人には、2行を揃えるという概念が通用しないことになります。世の中、わからないものです。
関数のブロックの中で、使う変数の宣言と、実際の処理の間には、空行を1行入れるのが当たり前とされています。
int diff_abs(int a, int b) { int diff; ←ここに空行 diff = a - b; if ( diff >= 0 ) return diff; else return -diff; }
新しいC言語規格(C99)では、Java や C++ と同様に、変数の宣言をブロックの先頭に書く必要がなくなったので、C99 が広まればこの空行の意味合いは薄れていくかもしれません。もっとも Java や C++ で、この空行の習慣がないわけではありませんので、急に消えることもないでしょう。
複数行の文字列を表示する場合、printf() を連発しなくてもよい方法があります。次の2つは同じ意味です。
printf(" a i u e o\n"); printf("ka ki ku ke ko\n"); printf("sa si su se so\n");
printf(" a i u e o\n" "ka ki ku ke ko\n" "sa si su se so\n");
というのも、C言語のソースでは、隣り合う文字列は連結されるという約束があるからです。そしてスペースと改行は同じ扱いになるからです。小さな例を見てみると理解しやすいでしょう。次の4つは同じ意味です。
"123456"
"123" "456"
"1" "2" "3" "4" "5" "6"
"123" "456"
ところで、次のソースは、ちゃんと動いているようで、実は問題をかかえています。
printf(" a i u e o ←見えないけれどここで改行 ka ki ku ke ko ←見えないけれどここで改行 sa si su se so\n");
本物の改行を含めて "" で囲って文字列にしています。これがどんな問題を抱えているかというと、移植性の話になります。改行コードは Win, Mac, UNIX で3通りあります。ソースの改行コードが実行環境と一致していれば、ちゃんと動いてしまうかもしれませんが、食い違った場合には何が起こるかわかりません。C言語の '\n' という文字は、3通りのバリエーションを吸収するためにあるものですから、素直に '\n' を使いましょう。
他の言語でも、長い文字列を扱う方法が用意されています。Java では、文字列の連結は "123" + "456" という具合に、足し算で実現できます。多くのスクリプト言語(Perl, Ruby, sh など)には、ヒアドキュメント (here document) という機能が用意されています。
printf() と scanf() は一見すると対称な動作をするように思えます。同じようなフォーマット文字列(例えば "%d")が、出力と入力のどちらにも使えると思っている人も多いでしょう。
しかしながら、細かく見るとフォーマット文字列にも違いがあります。 double を扱うのに printf() では "%f" と l(エル)が例外的に付きません。これは間違いにくいですが、つられて scanf() まで "%f" として、必要なエルを付け忘れることが多々あります。 更に scanf() で値を格納する変数には & をつけねばなりませんが、ここにも例外があって、"%s" だけは & が不要です。
この程度の問題は、gcc なら警告レベルを上げるだけで検出できるので、取るに足らないとも言えます。→Cygwinでデバッグ/不可解な動作
変数の型 | 整数 | 浮動小数点 | 文字(列) | |||||
---|---|---|---|---|---|---|---|---|
short | int | long | float | double | long double | char | char* | |
printf() | %hd | %d | %ld | %f | %f | %Lf | %c | %s |
scanf() | %hd | %d | %ld | %f | %lf | %Lf | %c | %s |
そして何より大きな違いは、scanf() は、期待したような入力でなかった場合のエラー処理がほとんどできないという意味で、入力を受け付ける関数として失格だということです。
scanf() は、文字列を受け付ける時にはバッファをあふれさせるセキュリティ上の欠陥を抱えています。数値を受け付けている時には、数値でない文字が入力されると、それっきり処理が前に進まなくなります。また、改行文字とスペースの区別がつけられないので、CSV のような改行区切りのデータを扱うのに無理があります。
scanf() は scan formatted の名前が表すように、書式のちゃんとしたテキストを読み込むために設計されていて、キーボードから人間の入力するようないいかげんなものを受け付けるようには作られてないのです。
では、通常はどうするかというと、fgets() でバッファに1行取り込みを行ってから atoi(), strtol(), sscanf() などで解析します。
int i, a[10]; for (i=0; i<10; i++) { scanf("%d", &a[i]); }
char buff[BUFSIZ]; int i, a[10]; for (i=0; i<10; i++) { fgets(buff, BUFSIZ, stdin); a[i] = atoi(buff); }
初心者向けのC言語の書籍では、必ずといってよいほど scanf() が使われています。確かに、似たような動作をさせるのに、上の scanf 版のように短く(あるいは知識が少なくて)書けるので好都合なのでしょう。 その代償として、ローカル変数を他の関数で書き換えるためにポインタ渡しをする、という高等テクニックを「お約束」だとして & を付けさせることになります。
このため、C言語の初学者は、早い段階から「ローカル変数の値が、代入もしていないのに変化する」という体験をしてしまいます。この体験が、後の学習に悪影響を及ぼしていないか、よく検証してみる必要を感じます。
せめて、文字列を学習したあたりで fgets() を「お約束」として教え直し、「実践では scanf() は使わない」ということを叩き込んでもらいたいところですが、残念ながらそのような書籍は見たことがありません。
結論ですが、printf() と scanf() は対称どころではなく、「printf() はよく使うが scanf() は避けるべき」ということです。