おそらく、次のプログラムはCのプログラムとして有名なものの一つです。
hello.c
- “hello,world.”と出力する
#include <stdio.h>
main()
{
printf("hello,world.\n");
}
このプログラムを実行すると、“hello,world.”と出力されます。 (実行させるための具体的な段取りについてはここでは触れません。 )
Cでメッセージを出力するときにはprintfがよく使われます。 ここで「出力する」と述べましたが、 いったいどこに出力されるのでしょうか? その回答は「これだけの条件ではわからない」です。
多くの環境では端末の画面に出力されることでしょう。 しかし、ものによってはプリンタに出力されるかも知れません。 あるいはファイルに出力されたり、 エディタの編集画面に現れることもあります。
┌── Note - printfは画面に出力する関数/手続きか?Cについて解説している書籍の多くは 「printfは画面に文字を出力する関数である。」とあるかもしれませんが、 厳密にはそれは誤りです。 正しくは「printfは標準出力に文字を出力する関数」です。 標準出力の出力先が画面であれば、 結果として画面に文字が出力されるだけです。
"hello,world\n"の部分をいろいろ変更すると、 他の任意なメッセージを出力することができます。 しかし、無条件にどのようなメッセージをも記述できるわけではありません。 次に示すように、いくつか制限があります。
Cで作成する動作するプログラムには、 動作単位ごとに必ずmainが含まれます。 そしてmainには「プログラムの動作はここから始まる」という意味があります。
プログラムの具体的な動作内容は“{”と“}”で囲まれた中に記述します。 別途解説しますが、Cのプログラムは区別できる範囲で 自由に余白(スペース; space)を開けてもよいことになっています。 改行も余白文字に含まれます。 その区別できる範囲には厳密な定義がありますが、 通常は単語の途中やダブル クォート(")によって囲まれている 領域(文字列中)に余分な空白を空けさえしなければ、大丈夫です。
言うまでもありませんが、 printfのメッセージに含まれる余白には意味があります。 その余白はそのまま空白として出力されことになります。 このメッセージ部分は、(")によって囲まれているので、文字列です。
Cの文法では、自由に空白を空けたり改行したりできると述べましたが、
#include <stdio.h>
のようなナンバー記号(#)にキーワードが続く行は
これに当てはまりません。
独立した行として記述しなくてはなりません。
これはCではなく、Cのプリプロセッサ(C pre-processor)に対する指示だからです。
なお、#includeはファイルを取り込むという働きがあり、 プログラムの拡張に利用されています。 #include <stdio.h> の記述によって システムの提供する“stdio.h”というファイルがそこに取り込まれて、 printfという関数(手続き)が利用できるようになります。 この手のファイルはヘッダ ファイルと呼ばれます。 詳しい仕組みは別途解説します。
┌── Note - Cにおける関数と手続き - ファンクションCにおいては、関数と手続きには厳密な区別がありません。 原文ではこれらはファンクション(function)という用語で表現されています。 ファンクションには日本語でいうところの機能という意味もあります。 著者は日本語でファンクションにうまく対応する一つの用語はないと 考えています。そこで原文のままファンクションという用語を使うことの方がよいと 思っていますが、現在出版されている多くのCの日本語の文献ではみなこれを 関数と訳しているようなので、以下本稿でも全て関数と表現することにします。
add2.c
- 二つの数字の和を求める
#include <stdio.h>
main()
{
int a = 32, b = 15;
printf("%d + %d = %d\n",a,b,a + b);
}
sum100.c
- n * (n + 1)/2の式を利用して、1から100までの和を求める
#include <stdio.h>
main()
{
int n = 100;
printf("sum 1 to %d = %d\n",n, n * (n + 1)/2);
}
circle.c
- 円の半径から円周と面積を求める
#include <stdio.h>
main()
{
double pi = 3.1415926535, r = 13;
printf("2 * pi * %f = %f\n",r, 2 * pi * r);
printf("pi * %f^2 = %f\n",r, pi * r * r);
}
Cでは整数と浮動小数点数の演算ができます。 intが整数(integer)を扱うことを、 doubleが浮動小数点数を扱うことを意味しています。
Cでは整数は通常、10進数で記述します。 数字0〜9で構成されることになります。
int a = 32, b = 15;ではa,bに整数値が格納できることを意味しており、 それぞれに32, 15という整数値が設定されることになります。 この記述は
int a, b; a = 32, b = 15;というように分けて記述することもできます。 この場合、言語の文法上の意味は少し変りますが、 実質的に同じ効果があります。
浮動小数点数も同じく10進数で記述します。 数字0〜9とピリオド(.)で構成されます。 ピリオドがついていることが小数点数であることを意味しています。
$3.141592 \times 10^3$ (TeX Style)に相当する指数表記も可能です。 その場合は 3.141592e3 というように“e”あるいは“E”を挟んで表記します。
先の整数値の場合と同様、
double pi = 3.1415926535, r = 13;ではpi,rのそれぞれが浮動小数点数を格納できることを意味しており、 それぞれの値でもって初期化されます。
┌── Note - 実数と浮動小数点数浮動小数点数は コンピュータの内部で小数点を用いる数字を表現するときの形式の一つです。 書籍によっては実数という用語でこれを解説しているものもありますが、 浮動小数点数は数学で言うところの実数とは性質がまるで異なるものです。 (私はその表現はあまりお勧めできません。)
浮動小数点数は少ない記憶容量で広い範囲の数値を扱うことができるという 特徴を備えていますが、時として電卓よりも精度が劣るような演算結果が 得られることがあります。実際には状況によってさまざまな理由が考えられます。 一つ例を挙げると、10進数の0.1は浮動小数点数ではうまく表現できません。 というのは0.099999...を0.1として扱っていたりするからです。 この理由は(たいがいの処理系では)浮動小数点数を10進数ではなく 2進数で表現しているためです。
プログラムの表記は10進数で行ないますが、 実際の処理は2進数で行なっており、その変換の際に誤差が現れます。 2進数による演算そのものには誤差はないとしても、 最初の変換による誤差がある値でもって処理を続けると、 最終的にその誤差が影響してくるということがあります。 0.1を10回加えても、1を加えたのと同値にはならないという話 を気いたことはありませんか?
これは欠陥ではなく浮動小数点数の性質です。 プログラミングを行なう人がこの生ずる誤差を認識した上で、 誤差があったとしても問題にならないように注意しながら 利用しなくてはなりません。
大学では数値解析という科目等で この浮動小数点数について学習する機会があるでしょう。
Cでは四則演算(加減乗除)を行うために、 それぞれ “+”, “-”, “*”, “/”といった記号を用います。 演算結果は、整数同士を計算すれば整数に、 浮動小数点数同士であれば浮動小数点数になります。 これらを混ぜた場合は、その演算を行う前に 整数が浮動小数点数に変換された上で処理されます。 整数同士の割り算の結果は、 少数点以下が切り捨てられた結果の整数値となります†。
┌── Note - 乗除算と演算子日本ではあまりなじみはないようですが、 国によっては“*”, “/”を実際に掛け算や割り算の演算のための 記号に用いているところがあります。 日本では通常“×”と“÷”を使っていますね。
printfのメッセージ部分に“%d”を含めると、 対応する引数から整数が読み込まれて、数字として出力されます。 これによって例えば add2.cにおける
printf("%d + %d = %d\n",a,b,a + b);という記述によって、
32 + 15 = 47という出力を得ることができます。
同様にprintfのメッセージ部分に“%f”を含めると、 対応する引数から浮動小数点数が読み込まれて、数字として出力されます。 circle.cでは、例えば
2 * pi * 13.000000 = 81.681409というような結果が出力されます。
pi * 13.000000^2 = 530.929158
-----
†厳密には処理系により異なります。 処理系のマニュアルを参照して下さい。
先の例では、計算できる値はプログラム記述時に決まってしまっていて、 あまり有用性はありません。計算する値をプログラム利用時に 決めることができるように改良します。
add2a.c
二つの数字を入力し、その和を求める。
#include <stdio.h> #include <stdlib.h> main() { int f,a,b; if((f = scanf("%d%d",&a,&b)) == EOF) { fprintf(stderr,"no inputs.\n"); exit(1); } if(f < 2) { fprintf(stderr,"invalid inputs.(%d)\n",f); exit(1); } printf("%d + %d = %d\n",a,b,a + b); return 0; }
circle2.c
円の半径を入力し、円周と面積を求める
#include <stdio.h> #include <stdlib.h> main() { double pi = 3.1415926535, r; int f; if((f = scanf("%lf",&r)) == EOF) { fprintf(stderr,"no inputs.\n"); return 1; } if(f < 1) { fprintf(stderr,"invalid input.(%d)\n",f); return 1; } printf("2 * pi * %f = %f\n",r, 2 * pi * r); printf("pi * %f^2 = %f\n",r, pi * r * r); return 0; }
scanfが数値の入力部分です。 書式の指定部分に含まれる“%d”が整数の読み込みを、 “%lf”が浮動小数点数の読み込みを意味しています。 書式指定に続いて、読み込んだ値を格納する変数を
scanf("%d%d",&a,&b)のようにアンパッサンド(&)を添えてカンマで区切って並べます。 この記述は10進数で表記された整数値を2個読み込んで、 a, bのそれぞれに順に値を格納することを表わしています。
入力するデータは、それが複数個あるときは、空白で区切って入力します。 このとき空白は無視されて、数値だけ読み込まれることになります。
┌── Note - scanfの書式指定における空白多くのCに関する書籍では2つの整数をscanfで読み込む時には
のように“%d”と“%d”の間に空白があります。 これに対して本稿の例では空白はありません。 結論から先に述べると、この空白は必要ありません。 scanfの書式で指示する空白は、 空白文字(タブや改行を含む)を読み飛ばすという指示になります。 しかし%dの指示自身も、まず空白を読み飛ばした後に 数値を読み取る構造になっているのです。 読みやすさを考慮して空白を空けるべきだという主張もありますが、 プログラムを記述する人の趣味で自由に決めてよいことです。
scanf("%d %d",&a,&b)
scanfは入力できたデータの個数を値として返します。 入力するデータが無ければEOFが返ります。
f = scanf(...)における“=”は代入を行っています。 右辺の演算結果を左辺の変数に設定させる指示となります。 この記述でscanfの返り値がfに設定されることになります。
ifは、もし〜ならばという意味で、 括弧の条件が成立したときにそれに続く命令文を実行します。 文が波括弧“{”と“}”で囲まれていれば、 それら全体が条件が成立したときに実行される対象となります。
上の例では“==”や“<”を条件として用いていますが、 他にも “>”, “<=”, “>=”, “!=” 等が使えます。 それぞれの条件の意味は次のようになります。
a < b | aがbより小さい |
a > b | aがbより大きい |
a == b | aとbが等しい |
a <= b | aはb以下 |
a >= b | aはb以上 |
a != b | aとbは異なる |
従って、上のプログラムにおける
という記述は「もしデータの入力が無かったら」という意味になります。 また、add2a.cにおけるif((f = scanf(...)) == EOF) { ... }
という記述は、それだけに注目すると、 「もしfの値が2より小さかったら」という意味になります。 しかしfにはその上のscanfの値が格納されているので、 「もし入力されたデータの個数が2個に満たなかったら」 と解釈することになります。if(f < 2) { ... }
fprintf(stderr, ...)はエラー メッセージを出力するときに利用します。 ...の部分の利用の仕方はprintfと同じです。 if文でデータの入力がないか、データが必要な個数だけ入力されたか検査し、 条件が満たない場合にそれをエラーとして出力するために利用しています。
exitはプログラムを停止させる働きがあります。 これには適当な整数値を一つだけ与えます。 数値は通常、終了ステータスとして プログラムを動作させた環境に返されます。 その数値にどのような意味があるかは環境によって異なります。 一般には0が正常終了を意味するので、 ここではそれ以外の値ということで1を与えています。 しかし必ずしも1でなくてはならないというものではありません。
mainにおけるreturn 0;
は
exit(0);
と同じ意味になります。
┌── Note - scanfの返り値の検査Cの解説書の多くはscanfの返り値を検査していません。 これは安全な実用的なプログラムを記述するという意味から見ると、 誤った記述であると言えます。書籍によっては、 Cの関数の機能の本質がぼやけないようにという考えがあってとか、 紙面のスペースの関係で(仕方なく)略している場合とかがあります (そんなことを考えていない場合もありますが...)。 しかし実際のプログラムではこれをまねて 返り値の検査を略すようなことをしてはいけません。
scanfを初めとする入力処理の関数を利用するプログラムは、 入力は正しく行われるものだと想定した上で次の処理に移行するのが普通です。 では常に正常にデータを入力できるかというと、 それは「はい」とは言いきれないでしょう。 もし数値の入力を期待しているときに、 誤って数字以外の文字を入力してしまったときにはどうなるでしょう。 人がデータを入力するとすれば、そのような事態もありうるはずです。 適当な値をでっち上げて計算を続けられるよりは、 入力に誤りがありましたと反応するプログラムの方が ずっとよいプログラムであるといえるでしょう。
浮動少数点数のprintfによる出力は“%f”ですが、 scanfによる入力は“%lf”なので注意してください。 整数を10進数で扱う場合はどちらも“%d”です。 printfとscanfの初期指定は似ている部分もありますが、 同じではない部分もあるので、 その違いをきちんと把握するまでは十分注意する必要があります、
printf, scanf, fprintfを利用するために必要なヘッダ ファイルは <stdio.h>です。exitを利用するために必要なヘッダ ファイルは <stdlib.h>です。どの関数/手続き利用するときに どのヘッダ ファイルが必要になるかというのは、 通常、標準関数のリファレンス マニュアルに記述されています。
add2b.c
- 二つの数字を入力しその和を求めることを繰り返す。
#include <stdio.h>
main()
{
int f,a,b;
int ct = 0;
while((f = scanf("%d%d",&a,&b)) != EOF) {
if(f < 2) {
fprintf(stderr,"invalid inputs #%d, %d.(ignored)\n",ct,f);
scanf("%*s"); /* 単語一つ(次の空白文字まで)読み飛ばす */
continue;
}
printf("%d + %d = %d\n",a,b,a + b);
ct++;
}
return 0;
}
whileが繰り返しを記述する文です。 whileは〜の間という意味で、 それに続く括弧の条件が続く間、さらにそれに続く文を実行します。 ifの時のように続く文が波括弧“{”と“}”で囲まれていれば、 それら全体が繰り返される対象となります。
whileの条件に記述する内容はifのそれと同じものが利用できます。
while((f = scanf("%d%d",&a,&b)) != EOF)
という記述は、入力されるデータが無くならない間
繰り返すという意味になります。
言い方を変えて入力データがある間繰り返すと解釈した方が自然かもしれません。
演算子“++”(プラス プラス)は一つ値を増やすことを意味します。 ct++ は ct の値が一つ増えることになります。 add2b.cではctは演算回数を保持することになります。
この演算子は値を変更して格納するという性質を持つため、 変数のように値を格納出来る対象に対してしか利用できません。 例えば ct + 3++; という記述は誤りです。
また上のプログラムのように単独で存在する場合は、 ++ct のように ++ を先に記述しても構いません。 どちらの記述スタイルを選ぶかは好みによります。
“--”(マイナス マイナス)という、値を一つ減らす演算子もあります。
入力エラー処理部分に記述されている
scanf("%*s")
は入力から単語一つ分を無視する(読み捨てる)ことを意味します。
ここでいう単語とは、空白(文字)で区切られた文字列を意味します。
上のプログラムadd2b.cでは、 この記述を、2つの整数値が入力できなかったとき、 数値として読み取れない単語が入力されたと仮定して、 読み捨てて次の入力に備えることにしています。
まだ実際のプログラムでは利用していませんが、 scanfにおける“%s”書式は単語を読み取る働きがあります。 そして“%d”や“%lf”についても“%*d”、“%*lf”のように “%”に続けてアスタリスク(*)を指示すると、上と同様に 整数値や浮動小数点数を読み飛ばすことが出来ます。
continueは繰り返し文の中で用いる特殊な分岐命令です。 この場合はwhileを判定のところからやり直すことを意味します。 add2b.cではデータの入力に失敗したときに、 scanfで読めない入力データを捨てて、 continueによってもう一度入力の部分から繰り返そうとしています。
プログラム中で“/*”と“*/”で 囲まれた部分は注釈として扱われます。 プログラムに対する解説をプログラム中に埋め込みたい場合に この記述を利用します。
sum.c
- 数値を読み込み、その合計と平均を求める。
#include <stdio.h>
main()
{
int f,a;
int sum = 0;
int ct = 0;
/* 整数値データがある間繰り返す*/
while((f = scanf("%d",&a)) != EOF) {
if(f < 1) {
/* 数値が読めなかったら、単語を読み飛ばして再開 */
fprintf(stderr,"invalid inputs, #%d.(ignored)\n",ct);
scanf("%*s");
continue;
}
sum += a;
ct++;
}
printf("sum of %d:%d\n",ct,sum);
/* 0で割算しないようにする */
if(ct > 0)
printf("avr:%d\n",sum/ct);
return 0;
}
“+=”を利用した演算は左辺の項目に右辺の値を加える働きがあります。
sum += a;という記述によって、sumにaの値が加えられます。 この処理はwhileによって繰り返されるため、 結果として入力された値の合計がsumに求まることになります。
Cには似たような意味での記述として “-=”, “*=”, “/=”等が利用できます。 それぞれ、値を引く、値を掛ける、値で割るという意味になります。
繰り返す回数があらかじめわかっている場合、 どのようにプログラムを記述すればよいでしょうか。 カウンタ変数を用意して、その値を繰り返し条件に利用するのが基本です。
例えば、次のように記述できるでしょう。
このプログラムは0から9までの10個の数値を出力します。 10回の繰り返し処理が行われたわけです。 Cにはこのような決められた回数だけ繰り返し処理を行うのに 都合のよい記述方法があります。 それはfor文を利用することです。 先の例はforを使うと次のようになります。int i = 0; while(i < 10) { printf("%d ",i); i++; }
int i; for(i = 0; i < 10; i++) printf("%d ",i);
このforによる繰り返し処理と、 その前のwhileによる繰り返し処理は全く同等です。
sum.cは 任意の個数のデータの合計を求めるようになっていましたが、 これをforを使って10個のデータの合計を求めるように変更してみると 次のように記述できます。
sum10.c
- 10個の数値を読み込み、その合計と平均を求める。
#include <stdio.h>
main()
{
int i,f,a;
int sum = 0;
int ct = 0;
for(i = 0; i < 10; i++) {
if((f = scanf("%d",&a)) == EOF)
break;
if(f < 1) {
fprintf(stderr,"invalid input #%d.(ignored)\n",ct);
scanf("%*s");
continue;
}
sum += a;
ct++;
}
printf("sum of %d:%d\n",ct,sum);
if(ct > 0)
printf("avr:%d\n",sum/ct);
return 0;
}
forにおいてiがカウンタとして利用されています。 通常このfor文によって、10回、 数値の読み込み処理が繰り返されることになります。
通常と断わったのは、まず
if((f = scanf("%d",&a)) == EOF)
break;
の記述によって、入力データが無い場合は
繰り返し処理から抜け出るからです。
breakはforやwhile等の繰り返し処理から抜け出る働きがあります。
このプログラムは入力データが10個に満たない場合は そこで繰り返し処理をやめるように記述されています。
もう一つ10個の数値を扱えない場合あります。 それは入力されたデータが整数値でないときに発生します。 入力エラーが発生すると、fprintf(stderr, ...)によって報告されたのち、 continueによってループ処理の再開を図ろうとします。 しかしこの場合のforにおけるcontinueは、 i++によりカウンタ値が進めらて i < 10の条件判断が行われることになります。
つまり常に繰り返し回数は10回になるのですが、 正しい入力がなされなければ、 10個の数値を扱えないプログラムであるわけです。
数値を入力して、その数値を入力したものの 逆順で数値を出力するプログラムを考えてみます。
rev.c
- 入力の逆順で数値を出力する
#include <stdio.h>
main()
{
int i,f,a[100];
int ct = 0;
while((f = scanf("%d",&a[ct])) != EOF) {
if(f < 1) {
fprintf(stderr,"invalid input #%d.(ignored)\n",ct);
scanf("%*s");
continue;
}
if(++ct == 100)
break;
}
for(i = ct - 1; i >= 0; i--)
printf("%d ",a[i]);
putchar('\n');
return 0;
}
入力した値を逆に出力するためには、入力された値を 出力するまで記憶しておく必要があります。 上のプログラムでは変数aがその役割をしています。
int a[100];のように角括弧で数値を囲む形で変数を宣言すると、 その変数は添字指定による配列を扱うことが出来るようになります。 この場合、指示できる添字は 0 から 99までの100個分となります。 つまり、配列の宣言においては要素数を添字として与えることになります。
上のプログラムでは利用していませんが、 角括弧の中に記述する添字は -- 定数や単純な変数だけではなく -- 結果が整数値になる演算式を記述することもできます。 ただし演算式結果が添字として有効な範囲に収まるように しなくてはなりません。
Cでは(規格では検査をするように義務づけられていないこともあって) 添字の有効範囲を検査しない処理系の方が圧倒的に多いです。 添字を越えた場合どのように振舞うかは処理系によります。 多くの処理系は暴走やプログラムの強制停止ということになります。
if(++ct == 100) break;という記述は、まずctの値を一だけ増やし、その後 値が100になったか検討し、そうであればwhileループを抜け出る という意味になります。
これは配列の添字が99までしか有効でないために取った処置です。
これを
if(ct++ == 99) break;にすると、実質的効果は同じですが、 動作上の意味が変わるの注意が必要です。 この記述の場合まず値が99であるか検討し、 そうであればwhileループを抜け出るのですが、 ループを抜ける抜けないにかかわらず、ctの値を一だけ増やす という意味になります。
この説明を読むとなにやら複雑な印象を受けますが、 実は非常に単純なルールに基づいて動作しています。 それは、演算子“++”や“--”が用いられる場合、
この性質を利用すると、配列の内容を逆に出力する部分を 以下のように簡潔に記述することができます。
while(ct-- >= 0) printf("%d ",a[ct]);
Cにおいては文字も数値で表現されています。 (実際には別にCに限らず、他のプログラミング言語でもそうです。)
次に示すプログラムは、そのCで利用されている 文字に対応した数値を確認するためのものです。
#include <stdio.h> main() { int i; char s[] = "abcdefg"; for(i = 0; i < sizeof s; i++) printf("%d ",s[i]); putchar('\n'); }ここでは参考として“abcdefg”のそれぞれの文字について 文字コードを確認しています。
Cにおける一般的な文字の表現のための型はchar (characterの略と思われる)です。
char s[] = "abcdefg";という記述は、文字の配列、すなわち文字列を(変数 sを介して) 扱うことを意味しています。
この目的においては、配列の要素数を1, 2, 3 ..と数えて
char s[8] = "abcdefg";と記述してもよかったのですが、 初期化を伴う配列の宣言においては要素数を略せるので (自動的に必要数を数えてくれる) 上のプログラムではそのようにしています。
forループの中で用いられている
sizeof sという記述は配列 sの大きさを得るためのものです。 sizeofによって得る値の単位は「char」です。 char何個分の大きさかが分かります。
実行させるとわかりますが、 プログラムで記述した文字列の最後の要素には 文字コード0(ゼロ)が必ず入っています。
Cで文字列を扱う時には必ずしも最後に0が必要という 訳ではありませんが、プログラム中で記述した文字列には そういう性質があるのも事実で、 それを利用したプログラム技法がよく使われるのも確かです。 実際、標準ライブラリにもそれを想定した作りになっているものが いくつかあります。