次の例で最も単純なCプログラムの構造について説明する.
#include <stdio.h> int main(void) { printf("Hello."); return 0; }
#include <stdio.h>
は,
printf
を使用するときに必要となる [19.4.4].
stdio.h
というファイルの中に
printf
関数を使用するための設定が記述されている.
#include
という命令により, この命令がある位置に stdio.h
というファイルの内容が取り込まれる. これにより printf
関数を
使用できるようになる.
int
main(void)
はプログラムが何も引数を受け取らないで(void
) 整数 (int
) を返すことを表す.
int
main(void)
{ ... }
の内部がプログラムが実行される内容を表す [19.4, 19.4.5].
{
と}
で囲まれた部分が1つのブロックを形成し,
複数の文(関数)を記述することができる. ブロック内の文は順に実行される.
printf
により, それに続く()内の内容をが画面に出力する.
書式(format)に従って出力を行うので,
print
に f
が付いいた関数名になっている.
全ての文の終わりはセミコロン「;」で明示する必要がある.
return 0;
はここではプログラムの終了を表している [19.4.5].
「0」は正常終了を意味する.
;
で終了する.
これにより, 1行に複数の文を記述することも可能である.
改行しただけでは 文が終了したことにはならないので注意する必要がある.
「/*
」 と 「*/
」 で囲まれた部分は 「コメント」となる.
また, C99では「//
」 からその行の終わりまでもコメントになる.
「コメント」は, プログラムに全く影響を与えず, 自由に何でも書ける. 関数の使い方, 変数の説明, 何をやろうとしているのかの説明 等を書いておくと, 後でそのプログラムを読み返したときに理解しやすくなる.
例えば
/* コメントは 複数行にわたって書ける. */ // 1行だけのコメントは, この表記法の方が書き易い. /* printf を使うのに必要. */ #include <stdio.h> /* プログラムの本体 */ int main(void) { /* 画面に文字を出力する */ printf("Hello."); return 0; // プログラムの終了 }ここでは 説明の為に日本語でコメントを書いたが, C プログラム中での日本語を記述する場合にはコンパイラーが対応している文字コードを用いる必要がある [23.6.5]. 日本語のコメントは正しく処理されない場合があるので, アルファベットだけで書いた方がいいかも知れない.
ここでは, C 言語で利用できる基本的な数や文字の表現方法, 演算の仕方を述べる.
同時に, れらの値を printf
関数を用いて出力してみる.
値の表現方法 [20.3.2], 演算 [20.4.1],
printf
を用いた出力 [20.8.1] の各セクションに詳細な記述がある.
1 度で誤りのないプログラムを書けることはほとんどない. 適当な時期に誤りを見つける方法[23.6.3]を身につけておくことが望ましい.
printf
を用いることで,
標準出力(特に指定しない限りは画面) に出力形式(フォーマット)を指定して出力を行なうことができる.
単純な文字の出力.
printf("aaa bbb ccc");実際に使うには 1.4 を参照のこと.
\
とそれに続く文字により表現される.
このような文字は, 通常の文字の出力から一旦エスケープして特別な命令を実行するので,
エスケープ文字[20.3.2] と呼ばれる.
printf("aaa\nbbb\nccc\n");
pirntf("aaa\tbbb\tccc");
ここでは, 整数や整数同士の演算に慣れておこう.
整数同士の足し算や引き算には それぞれ 「+
」や「-
」 を用いる.
掛け算や割り算には, それぞれ 「*
」や「/
」 を用いる.
これらや数学での表現とは異なるので注意が必要である.
整数同士の割り算を行なうとその結果は整数では表現できない場合があるので,
想像していたのとは異なる結果になるかも知れない.
演算の順序を指定するために (
と)
を用いることができる.
{
, }
, [
, ]
は用いることができないので注意する必要がある.
演算の順序を指定するために括弧を使いたいときには, 常に (
と)
を用いる.
計算の結果を表示するには, 表示したい位置と型を指定する.
符合付き整数を表示するには, %d を用いる. % は単純な文字ではなく,
その位置に値を表示することを printf
に指示する為の特殊な文字として解釈される.
#include <stdio.h> int main(void) { printf("answer = %d\n", 3); printf("answer = %d\n", 3+4); printf("answer = %d\n", 3-4); printf("answer = %d\n", 3*4); printf("answer = %d\n", 12/3); printf("answer = %d\n", 12/5); printf("answer = %d\n", (3+4)*4); printf("answer = %d\n", ((3+4)*4 +1)*5 ); return 0; }
このような複数の文を記述する仕方については 19.1.2 を参照のこと.
実数 (浮動小数点数) は, 整数と区別するために小数点を含めて表記する.
例えば「3
」は整数で,「3.0
」は実数である.
や
のように桁が大きな実数や小さな実数は,
それぞれ「
3.0e15
」,「3.0e-15
」と表記する.
#include <stdio.h> int main(void) { printf("answer = %g\n", 3.0); printf("answer = %g\n", 3.4+4.7); printf("answer = %g\n", 3.4-4.7); printf("answer = %g\n", 3.4*4.7); printf("answer = %g\n", 3.4*(-4.7)); printf("answer = %g\n", 12.0/3.0); printf("answer = %g\n", -12.0/5.0); printf("answer = %g\n", (-3.0+8.5)*4.0); printf("answer = %g\n", 3.0e15*4.0e15); printf("answer = %g\n", 3.0e15/4.0e50); return 0; }
printf("first:%d, second:%d, third:%d\n", 3, 4, 5); printf("first:%d\n second:%d\n third:%d\n", 3, 4, 5);
最初の数 3 は 最初の % で指示される出力位置 (すなわち first: の直後)に, 次の 4 は 次の % で指示される位置 (即ち second: の直後)に というように, 順番に % で指示される位置に値が出力されていく.
printf("a:%d, b:%g, c:%g\n", 3, 4.5, 5.5/2.4);
文字を出力するには %c, 文字列を出力するには, %s を用いる.
'a'
のように シングルクォーテーョン 「'
」で
囲んで表現する.
"aaa"
のように ダブルクォーテーション「"
」
で囲んで表現する.
printf("1:%c, 2:%s\n", 'a', "bbb"); printf("1:%s, 2:%s, 3:%s\n", "aaa", "bbb", "ccc"); printf("1:%s,\t2:%s,\t3:%s\n", "aaa", "bbb", "ccc"); printf("%d:%s,\t%d:%s,\t%d:%s\n", 100, "aaa", 200, "bbb", 300, "ccc");
ここでは, 変数を使うことを学ぶ. 変数は使う前に型宣言を行なう必要がある. 初めは面倒であるが, プログラムの誤りを実行する前に検出できる点で有用である.
整数は int
というキーワードを用いて型宣言する.
例えば という名前の整数を使用するには,
int main(void) { int a; .... }
のように使用する前にその変数の型を宣言してから, それ以降の部分で使用する. 例えば,
#include <stdio.h> int main(void) { int a; a = 3; printf("%d\n", a); return 0; }
変数名として使うことができる名前には一定の制限がある [20.2].
複数の変数を使うには, 型宣言を複数記述する.
#include <stdio.h> int main(void) { int a; int b; a = 3; b = -3; printf("%d + %d = %d\n", a, b, a+b); return 0; }
``{'' と ``}'' で囲まれた文の集まりはブロックと呼ばれるが, 型宣言はブロックの先頭で行うことになっていた. C99では, その変数を使用する以前であれば, ブロックの中でのどこで型宣言を行っても構わない. 例えば
int main(void) { int a; int b; a = 3; b = 4; return 0; }としてもよいし,
// C99 のプログラム int main(void) { int a; a = 3; int b; b = 4; return 0; }としてもよい.
同じ型の変数はまとめて宣言することもできる.
#include <stdio.h> int main(void) { int a, b; a = 3; b = -4; printf("%d + %d = %d\n", a, b, a+b); return 0; }
#include <stdio.h> int main(void) { double a, b; a = 3.5e7; b = -8.2e6; printf("%g + %g = %g\n", a, b, a+b); return 0; }
代入は = で表す. = は等号ではなく, 代入を表す.
#include <stdio.h> int main(void) { double a, b, c; a = 3.5e7; b = -8.2e6; c = a + b; printf("%g + %g = %g\n", a, b, c); return 0; }
次の例では の結果を
に代入していることに注意.
#include <stdio.h> int main(void) { double a, b; a = 3.5e7; b = -8.2e6; a = a + b; printf("%g\n", a); return 0; }
整数 | 浮動小数点 | |
説明 | ||
表記法 | ||
型宣言 | int a; |
double a; |
printf |
%d | %g |
サイズ(記憶域) 精度
比較検討
integer の方が速い -> 例
integer では,
のような浮動小数点を表現できない.
double x = 3.2; int i = x; /* 3 */
double x = 0.2; int i = x; /* 0 */
2/3 = 0;
演算精度 と サイズに応じた精度
a = a + 1;
や a = a - 1;
のように 変数に1だけ加えたり, 1
だけ減らしたりするときは, a++;
, a--;
と表すことができる.
こちらの方が演算が高速になることがある. これらはそれぞれインクリメント演算子,
ディクリメント演算子と呼ばれる.
#include <stdio.h> int main(void) { int i; i = 0; printf("%d\n", i); i++; printf("%d\n", i); i++; printf("%d\n", i); i--; printf("%d\n", i); i--; printf("%d\n", i); return 0; }
i++
や i--
は値を評価した後に値が変更される.
逆に, 値を変更した後にその値を使いたい場合は, 20.4.1 を
参照のこと.
#include <stdio.h> int main(void) { int i; i = 0; printf("%d\n", i++); printf("%d\n", i++); printf("%d\n", i--); printf("%d\n", i--); return 0; }
関数は, 同じような処理を何度もする場合に, 一ヶ所にまとめることができる機能で, 全体が簡潔に見通しよく記述できる. さらに, 関数内部で宣言した変数は外部から遮蔽されるので, 関数を呼び出す場合は, 入力と出力だけを考えればよい. これは大きなプログラムを構築する上で重要な「構造化」の機能を提供する.
数学関数20.8.9を使うには, 予め用意されているコンパイル済のプログラムを実行ファイルに結合して, 実行形式のファイルを作成する.
次のプログラムでは の時の
の値を計算している. sin
関数の返す値は倍精度実数なので, 結果を代入するための変数としては, 倍精度実
数の変数を用意する. 又, sin 関数などの数学関数を用いるために,
新たに math.h を読み込んでいる点に注意すること.
#include <stdio.h> #include <math.h> int main(void) { double x, y; x = 1.0; y = sin(x); printf("sin(%g) = %g\n", x, y); return 0; }
gccでコンパイルする際に, sin 等の関数を使うには -lm オプションを付けてコンパイルする必要がある. 上のプログラムのファイル名を aaa.c とすると次のようになる.
$ cc aaa.c -lm
sin x 等の数学関数を計算するための関数は, 予めコンパイルをしてlibmというライブラリーファイルの中に集められている. 一般に, ライブラリーファイルは libxyz のように lib で始まる名前が付けられている. この中の関数を使うには xyz でこのライブラリーを指定し -l オプションで -lxyz のように C コンパイラーに伝える.
$ cc aaa.c -lxyz
ここで, aaa.c と -lxyz の順番が重要なので注意すること[23.6.7].
他の数学関数の一覧を [20.8.9] にまとめてある. これらを使って計算を行なってみよう.
#include <stdio.h> #include <math.h> int main(void) { double x, y1, y2, pi; /* π の値を求める. */ pi = 4.0 * atan(1.0); x = pi / 2.0; y1 = sin(x); y2 = cos(x); printf("sin(pi/2) = %g, cos(pi/2) = %g\n", y1, y2); return 0; }
#include <stdio.h> int add(int a, int b) { int c; c = a + b; return c; } int main(void) { int x; x = add(3, 4); printf("answer = %d", x); return 0; }
int add(int a, int b)
で始まり, それに続く {
と 対応する
}
に囲まれた部分が 新たに定義した という関数である. 関
数名として使うことができる名前には 一定の制限がある
[20.2].
関数名 add
の直前の int
が この関数がどのような値を返す
かを表している. 関数名add
に続く(
と )
の中にこの
関数がどのような入力を受け付けるかを記述する. ここでは2つの整数型の値
を引数としている. 1つめの値は 関数の内部では という変数で, 2つ目
は
という変数で参照される.
return
文に渡される引数が戻り値
となる. ここには式を書いても構わない. この例では, c
の値が返さ
れる. その値を呼び出した側の main
関数の中では x
に代入
している. return
文は関数中の任意の位置に書くことができ, その時
点で関数内での実行を終了し呼び出し側に戻る.
引数の型や個数には何ら制限がない. 例えば,
#include <stdio.h> double calc(double x1, double x2, double x3) { double y; y = (x1 + x2) * x3; return y; } int main(void) { double x; x = calc(3.0, 4.0, 5.0); printf("answer = %g", x); return 0; }では 3つの倍精度実数を受け取り それらの演算を行なった結果を倍精度実数 で返す関数
このような型宣言は, 初めは煩雑であると感じるが プログラムの誤りが 実行 以前に明らかとなるので 非常に有用である.
次のように変数を関数に渡しても構わない. ただし関数呼び出しを行なう時点 でその変数が値を代入されていなければ, 結果は保証されない.
#include <stdio.h> int add(int a, int b) { int c; c = a + b; return c; } int main(void) { int x, y, z; x = 3; y = 4; z = add(x, y); printf("answer = %d", z); return 0; }
次のように関数が返す値をを関数に渡しても構わない. 下の例では まず
関数が実行されそれが返す値が,
関数を呼び出すときの引数
として使われる.
#include <stdio.h> int add(int a, int b) { int c; c = a + b; return c; } int main(void) { printf("answer = %d", add(3, 4)); return 0; }
時として値を返さない関数も必要となる. その時は キーワード void
を用いる. 例えば 1つの倍精度実数を受け取って 値を返さない関数
は 次のようになる. 関数から戻るときは
return;
のように
return
文の引数には何も与えない.
void report(double x) { printf("x =%g \n", x); return; } int main(void) { double a; a = -1.2e-5; report(a); return 0; }
逆に値を受け取る必要がない関数もあるが 同様にキーワード void
を
用いる. 次の例では, 何も受け取らずにメッセージを表示して戻る関数
を定義している. 呼び出す側では,
hello();
のように 関数
呼び出しであることを示すために 中に何も含まない 「()」 を付ける必要が
ある.
void hello(void) { printf("Hello.\n"); return ; } int main(void) { hello(); hello(); return 0; }
2つの整数を引数として取る関数 について考えてみる.
#include <stdio.h> int add(int a, int b) { return a + b; } int main(void) { int x; x = add(3.0e5, 4.0); printf("answer = %d", x); return 0; }この例では main の中で誤って 3.0e5 と 4.0 という実数を 関数
このような誤りがある場合, よくできたコンパイラーはコンパイル時に警告を
出してくれる [23.6.1 -Wall オプション] ので, それを参考にしなが
ら修正することができる. そのために int add(int a, int b)
とい
うような面倒な関数の型宣言を予め行なっておくのである.
次の例のように 関数の呼び出しが関数の定義よりも前にあるとどうなるだろ うか ?
#include <stdio.h> int main(void) { int x; x = add(2, 3); printf("answer = %d", x); return 0; } int add(int a, int b) { return a + b; }
この場合 が呼び出される時点では
どのような引数を取るかは不
明である. コンパイラーは前から順番に1度だけしか関数の型の調べないから
である.
この問題を解決するのに, これまでやってきたように関数の実体を呼び 出しが行なわれるより先に記述するという方法がある. このことから main 関 数は通常一番最後に実体が置かれることが多い.
もう一つのやり方は, どのような型の引数と戻り値を取るかという宣言だけを
呼び出しが行なわれるより前に行なっておくことである. 例えば 今問題とし
ている関数 の型は
int add(int a, int b)
なのでそれを呼び
出しが行なわれるより前に宣言するのである. このような型宣言は 前の方に
集められることが多い.
#include <stdio.h> int add(int a, int b); int main(void) { int x; x = add(2, 3); printf("answer = %d", x); return 0; } int add(int a, int b) { return a + b; }型宣言を行なっている所には 最後に文の終了を表す
;
が付いている
ことに注意する必要がある.
これまでに printf や sin 等の予め用意されて関数を使用してきた. これら を使用する場合にもやはり予め型宣言が必要である.
関数を使うときに stdio.h というファイルをインクルードするのは
このためである. stdio.h の中で
関数の型宣言が行なわれている.
そこで, それを呼び出すより前に stdio.h というファイルを取り込んで型を
調べておくのである. このようなことから
#include <stdio.h>
はプ
ログラムのほぼ先頭の位置に記述されることが多く, ヘッダーファイルと呼ば
れ, .hで終わるファイル名が付けられている. 同様に 関数のような数
学関数は math.h の中で型宣言が行なわれているので
#include
<math.h>
が先頭付近で必要となる. #include
という命令はこ
のような ファイルの取り込みを行なう命令である [23.6.2 参照].
同様に プログラムが大きくなってくるとプログラムを幾つかの部分に分割し たファイルに用意しておいた方が扱いやすくなってくる. このようなときにも 別のファイルに実体が記述された関数を利用する必要がでてくる. そこで, ヘッ ダーファイルに型宣言だけを記述してファイルの先頭で取り込む. これにより 多くの関数の型宣言を行なう手間が省け, 一元的に管理できるようになる.
main関数は, コマンドラインの引数を受け取り, 終了コードを返すことで, 実行する環境とデータをやりとりする. コマンドラインからファイル名やオプションを与えて実行するプログラムは多くあるが, これらのコマンドラインから与えられた文字列の組がmain 関数に引数として渡される. 逆に main 関数の中でのreturn は main関数を終了し それを実行した環境にプログラム終了コードを返す. 詳細は後述[20.7] する.
変数や関数は計算機上のメモリー上のある場所 (アドレス) にその実体がある. C 言語ではそのアドレスを得るための仕組みが用意されている. また, そのア ドレスを保持するための変数 (ポインター) も用意されている.
変数のアドレスは &
を用いることにより知ることができる. 例えば
a
という変数のアドレスは &a
である. 例えば, 次のように用
いる. アドレスと値は異なるものであるということを理解しておく必要がある.
#include <stdio.h> int main(void) { int a; a = 5; printf("value:%d, address:%p\n", a, &a); return 0; }ここで
%p
はアドレスを出力する
ことを意味する.
上のようなアドレスを保持するための変数がポインターで, 変数の前に
*
をつけて宣言する. 例えば int 型の変数のアドレスを保持するポイ
ンター ptr
は int *ptr;
と宣言する. この時 *ptr
がそのアドレスで指し示される変数の値を表す.
#include <stdio.h> int main(void) { int a; int *ptr; /* ptr が a のアドレスを指すようにする. a のアドレスは実行時に int a; の宣言が行なわれているところで 確定 している. */ ptr = &a; /* a への代入は その値が実際に使われる前であれば いつ行なっても構 わない.*/ a = 5; printf("[a] value:%d, address:%p\n", a, &a); printf("[ptr] value:%d, address:%p\n", *ptr, ptr); return 0; }
普通の変数とポインター変数の表記法をまとめると次のようになる.
普通の変数 | ポインター変数 | |
型宣言 | int a; |
int *p; |
値 | a |
*p |
アドレス | &a |
p |
ポインターを使うときには, 予め実体を確保する必要がある. 従って 次の例 の様な場合 動作が保証されていない.
#include <stdio.h> int main(void){ /* ポインター ptr が指す実体は 用意されていない. この時点で ptr が指すアドレスは不明である. */ int *ptr; /* どのに実体があるか確定させていない状態で, そこに値を代入してし まっている. */ *ptr = 3; return 0; }
正しくは 次のように実体を確保した後に代入を行なう.
#include <stdio.h> int main(void){ /* 実体としての変数 a を用意する. */ int a; int *ptr; /* ptr に a が存在する場所代入する. */ ptr = &a; /* この場合 a に代入が行なわれる. */ *ptr = 3; return 0; }
練習のためにもう少しポインターを使ってみる.
#include <stdio.h> int main(void) { double a, b; double *ptr1, *ptr2; a = 10.0; b = 20.0; /* ptr1 は a がある場所を指している. */ ptr1 = &a; ptr2 = &b; /* ポインターが指す実体の演算もできる. *ptr1 は a がある場所の実体, 即ち a の値を意味する. */ printf("a = %g, *ptr1 = %g, *ptr2 = %g\n", a, *ptr1, *ptr2); /* *ptr1 + *ptr2 の結果が *ptr1 即ち a に代入される. このとき ptr1 が指すアドレス上の値が変更されるので a の値が変更されている. */ *ptr1 = *ptr1 + *ptr2; printf("a = %g, *ptr1 = %g, *ptr2 = %g\n", a, *ptr1, *ptr2); /* このように 変数 a を直接操作しなくてもそれを指す ポインターを導入す ることで その変数 a が保持する値を変更できる. */ return 0; }
下のを関数化すればそれなりに汎用性が高くなるかもしれない.
#include <stdio.h> int main(void) { double a, b, c; double *ptr; a = 10.0; b = 20.0; c = 30.0; ptr = &a; printf("%g %p", *ptr, ptr); ptr = &b; printf("%g %p", *ptr, ptr); ptr = &c; printf("%g %p", *ptr, ptr); return 0; }
[19.4] では, 関数は高々1つの戻り値を返すことができること を説明した. 複数の戻り値を返したい時にはどうするのであろうか ?
例えば,
#include <stdio.h> void addoffset(int a, int b) { a = a + 10; b = b + 20; return; } int main(void) { int x, y; x = 1; y = 2; addoffset(x, y); printf("x=%d, y=%d", x, y); return 0; }のように呼び出された関数の中で
#include <stdio.h> /* a, b は呼び出し側の変数のアドレスを受け取る. */ void addoffset(int *a, int *b) { *a = *a + 10; *b = *b + 20; return; } int main(void) { int x, y; x = 1; y = 2; addoffset(&x, &y); printf("x=%d, y=%d", x, y); return 0; }
このように渡した変数の値を関数内部で変更したいときはアドレスを渡してポイン ターで受け取る (参照渡し, Call by Reference と呼ばれる). そうでない時は値の変更は行なわれない. 関数を呼び出すときに, 別に変数が確保さそこに代入された後に関数内部のプログラムが実行される (値渡し, Call by Value) からである.
標準入力からの入力は 関数により行なうことができる.
関
数は,
int scanf(char *format, ...);という書式を持ちformat に従って標準入力から読み込みを行なう. 「...」 の部分は可変の個数の引数を表す. 読み込んだ値は, この引数で与えられた変 数に代入される. このように,
#include <stdio.h> int main(void) { int a; scanf("%d", &a); printf("a = %d\n", a); return 0; }プログラムを実行後, 入力待ちになるので整数を入力して「Enter」を押すと その数値が
double 型の場合は 入力フォーマットは "%lf"
となる.
#include <stdio.h> int main(void) { double a; scanf("%lf", &a); printf("a = %g\n", a); return 0; }
複数の値を読み込むときは
#include <stdio.h> int main(void) { double a, b; scanf("%lf%lf", &a, &b); printf("a=%g, b=%g\n", a, b); return 0; }実行時に数値を2つ入力するには, 二つの数値をスペース, タブ, 改行のいず れかで区切って入力する.
配列は 変数名の後に []
を付けて表現する.
#include <stdio.h> int main(void) { /* 配列は 変数名の後ろに [ ] をつけることで表現される. 初めに宣言 を行ない領域を確保する. 次のは 3こからなる double 型の変数を宣言し ている. */ double x[3]; /* 1番目の配列の要素は x[1] ではなく, x[0] で表される. [] 内の数 字は 何番目かではなく, 一番前からどれだけ離れているかというオフセッ トを表していると捕らえた方が分かりやすい. */ x[0] = 10.0; x[1] = 20.0; x[2] = 30.0; /* 全部で3個用意したので x[3] は存在しない. 不用意に x[3] に代入を行なわないように注意する必要がある. */ printf("%g\n", x[0]); printf("%g\n", x[1]); printf("%g\n", x[2]); return 0; }ここでは, double 型の配列を用意したが, 同様に int 型や char 型等の任意 の型の配列を用いることができる. C 言語では 厳密にいうと ``文字列'' と いうものはなくて char 型の配列で代用している.
#include <stdio.h> int main(void) { /* ptr はアドレスを保持するための変数で, それ自身で数を保持するこ とはできない. */ double *ptr; /* 配列の初期化は 次のようにして行なうこともできる. */ double x[3] = {10.0, 20.0, 30.0}; int index; /* 通常の変数と同じように & をつけることで アドレスを得ることがで きる. */ ptr = &x[0]; printf("x[0]:%g(%p), ptr:%g(%p)\n", x[0], &x[0], *ptr, ptr); /* x[1] は ptr + 1 により指される. */ printf("x[1]: %g(%p)\n", *(ptr+1), ptr+1); /* 同様にして x[2] は ptr + 2 により指される. */ printf("x[2]: %g(%p)\n", *(ptr+2), ptr+2); /* 一般的な書き方をするならば x[0] は ptr + 0 により指される. */ printf("x[0]: %g(%p)\n", *(ptr+0), ptr+0); /* 変数 index を用いて, x[index] を ptr + index で指すこともできる. */ index = 2; printf("x[%d]: %g(%p)\n", index, *(ptr+index), ptr+index); /* ptr に加える値は, そのポインターが指す要素から幾つ離れた要素で あるかを表す. 次の例では, ptr + 1 は x[1] から1つ離れた要素 即ち x[2] を指す. */ ptr = &x[1]; printf("ptr+1:%g(%p)\n", *(ptr+1), ptr+1); return 0; }
C99では, 配列の大きさはプログラム作成時に指定せずに, 実行時に決定できる. すなわち大きさが実行時に変えられる 可変長配列を使用することができる. 次の例では, scanf により必要な配列の大きさnを入力し, その次の行で大きさnの配列xを使用できるよう宣言している.
// 以下は C99 のプログラムです. #include <stdio.h> #include <stdlib.h> int main(void) { int n, i; scanf("%d", &n); double x[n]; // 配列のサイズを, プログラム実行時に与えられる. for(i=0; i<n; i++) x[i] = (double)i; for(i=0; i<n; i++) printf("%g\n", x[i]); return EXIT_SUCCESS; }
可変長配列の詳細については, [20.3.5] を参照のこと.
前の例では 配列 double x[3]
のアドレスを &x[0]
により得
た. C 言語では 配列名 x
そのものが その配列の最初の要素のアドレ
ス &x[0]
になっている.
#include <stdio.h> int main(void) { double array[5] = {10.0, 20.0, 30.0, 40.0, 50.0}; int index; /* *array は array[0] と同じ. array が array[0] のアドレスだからで ある. */ printf("array:%p &array[0]:%p\n", array, &array[0]); printf("*array: %g\n", *array); /* *(array+2) と array[2] は同じ. */ printf("*(array+2):%g (%p), array[2]:%g (%p)", *(array+2), array + 2, array[2], &array[2]); /* *(array + index) と array[index] は同じ. */ index = 2; printf("*(array+index):%g (%p), array[index]:%g (%p)", *(array+index), array + index, array[index], &array[index]); return 0; }このように, 配列名そのものはポインターと全く等価である. 唯一の違いは
array = ptr
のように 代入を行なうことはできない点である.
同じように C 言語では関数名そのものが その関数の実体が保管されているア ドレスを指す [20.6.4]. これを用いて関数名を変数に 代入したり関数に渡したりすることが可能となっている.
逆に ポインター double *ptr;
に対して []
を用いて
ptr[i]
のように書くことで ptr から i だけ離れた要素の値を得るこ
とができる.
#include <stdio.h> int main(void) { /* 初期化するときには [] 内の要素数を省略することもできる. その時 は {} 内に列記された要素の数が配列の大きさとなる. */ double real_array[] = {1.0, 2.0, 3.0}; double *ptr; ptr = real_array; printf("0:%g, 1:%g, 2:%g\n", ptr[0], ptr[1], ptr[2]); return 0; }
配列を関数に渡すには, 配列名がポインターと等価であることから次のように なる.
#include <stdio.h> /* 先頭から index だけ離れた配列要素の値を得る関数. */ double get_value(double *a, int index) { double val; /* 次のは val = a[index]; ともできる. */ val = *(a + index); return val; } /* 先頭から index だけ離れた配列要素に 値 valを代入する関数. */ void set_value(double *a, int index, double val) { /* 下のは a[index] = val; でもいい.*/ *(a + index) = val; } /* 配列の全ての要素の値を出力する関数. */ void report(double *a) { /* a[0], a[1], a[2], ... は *a, *(a+1), *(a+2), ... でもいい. */ printf("0:%g, 1:%g, 2:%g, 3:%g, 4:%g\n", a[0], a[1], a[2], a[3], a[4]); } int main(void) { double array[5] = {1.0, 2.0, 3.0, 4.0, 5.0}; report(array); set_value(array, 3, 9.0); report(array); printf("a[2] = %g\n", get_value(array, 2)); return 0; }
これまでに説明してきた ポインター及び配列名の使い方をまとめると下のよ うになる. 下の表の観点からは両者は全く同じである. 両者の違いは, 配列に は値を保持する領域が確保される点, 及び配列名はそれが指すアドレスを変更 することができない点にある.
double array[10]; |
double *ptr; |
|
index だけ離れた要素の値 | array[index] |
ptr[index] |
index だけ離れた要素の値 | *(array + index) |
*(ptr + index) |
最初の要素のアドレス | array |
ptr |
index だけ離れた要素のアドレス | array + index |
ptr + index |
index だけ離れた要素のアドレス | &array[index] |
&ptr[index] |
ポインター変数は 変数なので指すアドレスをいくらでも変更することができ る.
#include <stdio.h> int main(void) { double x[5] = {1.0, 2.0, 3.0, 4.0, 5.0}; double *ptr; ptr = x; printf("x[0]: %g(%p), %g(%p)\n", x[0], &x[0], *ptr, ptr); ptr = x + 3; printf("x[3]: %g(%p), %g(%p)\n", x[3], &x[3], *ptr, ptr); return 0; }
ポインター変数も通常の変数と同じように ++
や --
により1
つだけ増やしたり, 減らしたりすることができる.
#include <stdio.h> int main(void) { double x[5] = {1.0, 2.0, 3.0, 4.0, 5.0}; double *ptr; ptr = x; printf("x[0]: %g(%p), %g(%p)\n", x[0], &x[0], *ptr, ptr); ptr++; /* ptr = ptr + 1; の意味. */ printf("x[1]: %g(%p), %g(%p)\n", x[1], &x[1], *ptr, ptr); ptr++; /* 更に ptr = ptr + 1; の意味. */ printf("x[2]: %g(%p), %g(%p)\n", x[2], &x[2], *ptr, ptr); return 0; }
double *
型のみでなく 任意の型の変数に対して ++
や --
が使
える.
#include <stdio.h> int main(void) { int x[5] = {1, 2, 3, 4, 5}; int *ptr; ptr = x; printf("x[0]: %d(%p), %d(%p)\n", x[0], &x[0], *ptr, ptr); ptr++; /* ptr = ptr + 1; の意味. */ printf("x[1]: %d(%p), %d(%p)\n", x[1], &x[1], *ptr, ptr); ptr++; /* 更に ptr = ptr + 1; の意味. */ printf("x[2]: %d(%p), %d(%p)\n", x[2], &x[2], *ptr, ptr); return 0; }実際のアドレスの増加分に注目する必要がある. int 型と double 型では, 1 つの値を保持するのに必要なバイト数 (sizeof(double) や sizeof(int)) は 異なる. その結果, 同じ ptr = ptr + 1; とポインターの移動を行なっても, 対象となる変数の型に応じてバイト数の増加分は異なる. このようなポイン ター変数の演算では, 次の変数のアドレスを指すようになっていて, 各変数の 1 つあたりの領域のバイト数の差異は自動的に吸収してくれる. そのためにど のような型の変数へのポインターであるのかを ポインターを宣言するときに 指定するのである.
C 言語では文字列は, 文字の配列により実現される. 文字列の最後は
'\0'
(数値で表現して「0」[20.3.2] が入れておく必要が
ある.
次の例は, 正に文字の配列により文字列が表現されることを示すためのプ ルグラムで, 実際にはもっとスマートは操作法が用意されている.
#include <stdio.h> int main(void) { char str[10]; str[0] = 'a'; str[1] = 'b'; str[2] = ' '; str[3] = 'c'; str[4] = 'd'; str[5] = '\0'; /* ここで, str は str[10] の先頭のアドレスであることに注意. */ printf("str=%s\n", str); return 0; }
#include <stdio.h> int main(void) { char str[10] ={'a', 'b', ' ', 'c', 'd', '\0'}; printf("str=%s\n", str); return 0; }
もっと簡単に次のようにもできる.
#include <stdio.h> int main(void) { char str[10] = "ab cd"; printf("str=%s\n", str); return 0; }
ポインターは次のようにして使える.
#include <stdio.h> int main(void) { char str[10] = "ab cd"; char *ptr; /* これにより, ptr は str[10] の先頭アドレスを保持する. */ ptr = str; printf("str=%s\n", ptr); return 0; }
"ab cd"
のような文字列は, 配列の初期化を行なっている文脈以外で
は, それへのポインターを返している. 次の例では, ptr
というポイ
ンターを渡していたところに "ab cd"
を渡している. このような表現
を用いた場合, "ab cd"
という文字列が入るだけの配列が用意され,
そこにこの内容が書き込まれている. 配列名が未定になっているだけで, 一つ
上の例と実際に行なわれていることは変わりない.
#include <stdio.h> int main(void) { printf("str=%s\n", "ab cd"); return 0; }
判定は if
を用い, 次のような書式を取る.
if ( 式 ) 文1 else 文2
{ }
で囲んでブロックにする
[20.5.1].
例:
#include <stdio.h> int main(void) { int a; a = 1; if( a == 1 ) printf("a = 1\n"); else printf("a != 1\n"); return 0; }
が
と等しいことを調べるには,
==
を用いる. =
では
ないことに注意する必要がある. このような 判定に使える式の一覧を
[20.5.2] に載せる. 式 a == 1
は, が
と等し
くないときに
を返し, 等しいときに
でない数を返す.
if
は
その数が か
でないかを調べプログラムの実行を制御する.
次のように else
以降は無くても構わない.
#include <stdio.h> int main(void) { int a; a = 1; if( a == 1 ) printf("a = 1\n"); return 0; }
複数の文は { }
で囲む.
#include <stdio.h> int main(void) { int a; a = 1; if( a >= 0 ){ printf("a = %d\n", a); printf("a is positive or zero.\n"); } else { printf("a = %d\n", a); printf("a is negative.\n"); } return 0; }
else
の文の所でもう一度 if () ...; else ...;
を用いても
いい.
#include <stdio.h> int main(void) { int a; a = 3; if( a >= 10 ) { printf("a >= 10\n"); } else if ( a > 0) { printf("a > 0\n"); } else if (a == 0) { printf("a = 0\n"); } else { printf("a < 0\n"); } return 0; }
for は次のような構文を持ち, 繰り返しを同じ文を実行する.
for ( 式1; 式2; 式3 ) 文「文」は単一の文でもいいし, 複数の文を
{ }
で囲んだブロックでも
いい [20.5.1].
上の for
の構文は, 分かりやすい言葉で書き直すと
for ( 初期設定; 次のループに入るかどうかの判定; 次のループにはいる前の設定変更 ) { 実行文1; 実行文2; .... 実行文n; }となる.
例えば 次のように使える.
#include <stdio.h> int main(void) { int i; for(i=0; i<10; i=i+1){ printf("i = %d\n", i); } return 0; }
for
の部分の実行は 次の順に行なわれる. 下の図も参照のこと.
場所 | i |
|
IN | 不定 | |
式1 | 0 | 第一の部分 i=0; の代入が最初に一度だけ行なわれる. |
式2 | 0 | 第二の部分 i<10 が ``真'' ならループに入る. |
文 | 0 | ループ内が実行される.
この場合は { printf(...);} が実行される. |
式3 | 1 | 再度 ループに入る前に
第三の部分 i=i+1 が実行される. |
式2 | 1 | 再び第二の部分 i<10 を評価し
``真'' ならループに入る. |
... | ||
式3 | 10 | 再度 ループに入る前に
第三の部分 i=i+1 が実行される. |
式2 | 10 | 再び第二の部分 i<10 を評価し,
``偽'' の時ループに入らない. |
OUT | 10 |
式3 |--------| +----| i=i+1 |<-----------------------<--+ | |--------| | | 文 | 式1 | 式2 |------------------| | IN |--------| V |--------|Yes | { | | ---->| i=0 |---+--->| i<10 ? |--->| printf(...); |-->+ |--------| |--------| | } | | No |------------------| | | OUT +---------------------------------->
i の値は 0 から始まって 10 より小さい値 即ち 9 まで1つずつ 増加する. これにより 10 回ループを回ることになる.
10 回ループを回るのなら for(i=1; i<=10; i=i+1)
と書いた方が, 人
間には分かりやすいが, 計算機にとっては効率の悪い計算になることが多
い. そこで 普通 C 言語では for(i=0; i<10; i=i+1)
とする. C 言
語は計算機寄りに設計されている代わりに, 効率よく計算を行うことができる.
また配列の添字もi=0
から始まるので, 同様な使い方ができる.
#include <stdio.h> int main(void) { int i; int x[10]; for(i=0; i<10; i++) x[i] = i; for(i=0; i<10; i++) printf("x[%d] = %d\n", i, x[i]); return 0; }更にここでは,
i++
なる表現を用いた. これは i=i+1
と同じ結
果をもたらすが, 計算が早くなる可能性がある [19.3.3].
次の例では, 1から100までの数の和を求める.
#include <stdio.h> int main(void) { int i, sum; sum = 0; for(i=1; i<101; i++) sum = sum + i; printf("sum = %d\n", sum); return 0; }
次のように書くこともできる.
#include <stdio.h> int main(void) { int i, sum; for(sum=0, i=1; i<101; i++) sum += i; printf("sum = %d\n", sum); return 0; }
もう一つ例を別のところ[21.7]に挙げておく. アスキーコー ドの表を得られる.
while は次のような構文を持ち, 繰り返しを同じ文を実行する.
while ( 式 ) 文「文」は単一の文でもいいし, 複数の文を
{ }
で囲んだブロックでも
いい [20.5.1].
上の while
の構文は, 分かりやすい言葉で書き直すと
while ( 次のループに入るかどうかの判定 ) { 実行文1; 実行文2; .... 実行文n; }となる.
例えば 次のように使える. x[]
の合計を求めるプログラムである.
ここでは, がデータの終了を意味することにしておく. このようにして
おけばデータの個数は不明でも構わない.
while
によりデータの終了
を表す が来るまで足している.
#include <stdio.h> int main(void) { int i, sum; int x[] = {1, 2, 3, -1}; i=0; sum = 0; while(x[i] != -1){ sum = sum + x[i]; i++; } return 0; }
while
の部分の実行は 次の順に行なわれる.
x[i] != -1
が真か偽かを調べる. 最初は,
x[0]=1
なので真.
{ sum = sum+x[i]; i++}
を実行する.
x[i] != -1
が真か偽かを調べる.
次も x[0]=1
なので真 なのでループに入る.
i=3
になった時 x[i]=-1 となり, 式x[i] != -1
は
``偽'' となる. この時, 再びループに入ることはなく, while
の部
分の実行を終了する.
do-while は次のような構文を持ち, 繰り返しを同じ文を実行する.
do 文 while ( 式 );まず 「文」 が実行され, 次に 「式」 が評価される. 「式」 が ``真'' であれば 再び 「文」 が実行され, ``偽'' であれば終了する.
「文」は単一の文でもいいし, 複数の文を { }
で囲んだブロックでも
いい [20.5.1]. while(式)
の後ろに ;
を付け
る必要がある.
上の do while
の構文は, 分かりやすい言葉で書き直すと
do { 実行文1; 実行文2; .... 実行文n; }while ( 次のループに入るかどうかの判定 );となる.
while
とは, 判定をループ内を実行した後に行なっている点
が異なる. この結果, ループ内に少なくとも1度は入る.
次の例では, 与えられた整数 i
の桁数と同じだけ 文字「+」 を表示
する.
#include <stdio.h> int main(void) { int i; i=50; printf("%d\n", i); do { printf("+"); i = i/10; } while( i > 0 ); printf("\n"); return 0; }
while
の部分の実行は 次の順に行なわれる.
{ printf("+"); i=i/10; }
が実行さ
れる. この時点で i=5 である.
i>0
の真偽がテストされる. 最
初は ``真'' なので, 再度ループに入る.
i>0
は ``偽'' なので もうループには入らない.
switch は次のような構文を持ち, 複数の選択肢のうちの1つに分岐する.
switch (式) { case 定数式1: 文1 case 定数式2: 文1 ... case 定数式n: 文n default: 文d }分岐は, 式が定数式i に一致する時その部分に分岐する. 一致する定数式がな い時は, defulat: ラベルがあるときにはそこへ分岐する. 無いときには何も しない. 下の図に示すように switch では, 文1, 文2,
{ }
で囲むこと無く複数の文を記述でき
る.
|IN switch によるプログラムの流れ V | | +--->[式 == 定数式1 ? ]--->[ 文1 ] | | | v +--->[式 == 定数式2 ? ]--->[ 文2 ] | | : ..... : | | | v +--->[式 == 定数式n ? ]--->[ 文n ] | | | v +--->[ default ]--->[ 文d ] | | | | +--->-------------------------+ | | OUT V
次の例は, 生まれた年から満年齢を計算するプログラムである. 次の値を設 定すると年齢が出力される.
#include <stdio.h> int main(void) { int age, birthyear, thisyear; char nengo; /* 初期設定 */ nengo ='T'; birthyear = 40; thisyear = 11; /* 計算 */ age = 0; switch (nengo) { case 'M': age = age + 44; case 'T': age = age + 14; case 'S': age = age + 63; case 'H': age = age + thisyear; } age = age - birthyear; /* 出力 */ printf("%c %d -> age = %d\n", nengo, birthyear, age); return 0; }この例では,
case 'T'
のラベルの所から入って, その続きの
age = age + 14;
, age = age + 63;
,
age = age +thisyear;
が実行され, switch の { }
を終了す
る.
ジャンプした先から続く全ての文を実行したい場合もあるが, 途中で脱出した
いときもある. swich の{ }
の中から途中で抜け出すには,
break
を用いる.
次の例は, 何月かを month に設定するとその月が何日あるかを表示するプロ グラムである.
#include <stdio.h> int main(void) { int month = 6; switch(month){ case 2: printf("28 - 29 days.\n"); break; case 4: case 6: case 9: case 11: printf("30 days.\n"); break; default: printf("31 days.\n"); break; } return 0; }この例では,
case 6:
のラベルの所から入って,
printf("short month\n");
を実行し, その直後の break;
に
よって switch の { }
の外に抜け出している.
分岐を行なったら, 次の図のように, その分岐に専用の作業をして それ以外 のことは行なわないことが多い.
|IN V | +--->[式 == 定数式1 ? ]--->[ 文1 ]---break-->+ | | | | +--->[式 == 定数式2 ? ]--->[ 文2 ]---break-->+ | | : ..... : | | +--->[式 == 定数式n ? ]--->[ 文n ]---break-->| | | | | +--->[ default ]--->[ 文d ]---break-->+ | | | | +--->--------------------------------------->+ | |OUT V例えば次のようなプログラムがこれにあたる.
#include <stdio.h> int main(void) { int number; number = 3; switch(number){ case 1: printf("one\n"); break; case 2: printf("two\n"); break; case 3: printf("three\n"); break; default: printf("other\n"); break; } return 0; }この例では, どの分岐から入っても各分岐で実行する文が終了した後に
break;
により swithch の { }
から抜け出している.
break;
を付け忘れることは多々あるので注意が必要である.
これまでに 端末からの入出力関数として 関数と
関数につ
いて説明してきた. ファイルへの入出力を行なうには これらに
がついた
関数である
関数,
関数を用いる. これらは, 入出力の対
象となるファイルを指定する以外は
関数や
関数と同じで
ある. 対応関係 [20.8.7] をまとめると次のようになる.
標準入出力 (端末) | ファイル | |
出力 | printf | fprintf |
入力 | scanf | fscanf |
ファイルの入出力を行なうには, 予め対象となるファイルを 関数で
オープンし, 使用後に
関数で閉じる必要がある.
ファイルへの出力を行なうには, 関数の出力先を 標準出力からファ
イルに変更した
関数を用いる. 出力する前に予めファイル名等を指
定してファイルをオープンし(fopen), 出力し終わったらファイルを閉じる
(fclose) 必要がある.
/* ここで必要な FILE や関数を利用するためには予め stdio.h を読み込んで おく必要がある. */ #include <stdio.h> int main(void) { /* ファイルを指定したり操作するための情報を保持する領域 FILE を用 意する必要がある. 実体は fopen が用意してくれるのでポインターだけ 用意する. */ FILE *fp; /* ファイルのオープンを fopen で行なう. fopen は FILE *fopen (const char *path, const char *mode); という関数になっている. path は オープンするファイル名, mode には, 読み込みの時には "r", 書き込みの時には "w" を指定する. */ fp = fopen("aaa.dat", "w"); /* オープンに失敗すると NULL が返される. ここでは, 任意の時点でプ ログラムを終了する関数 exit を用いて終了する. 異常終了の場合は 0 でない値を返すのがいい. */ if (fp==NULL) exit(1); /* printf と同じように fprintf を用いてファイルに出力する. fprintf 関数は, 出力先のファイルを指定するところが printf 関数とは異なる. fprintf 関数は, int fprintf(FILE *stream, const char *format, ...); という書式を持ち, format で指定された書式に従って, 「...」 で示さ れる可変個の引数の値を, stream で指定されるファイルに出力する. */ fprintf(fp, "%d %d\n", 10, 15); fprintf(fp, "%s\n", "aaa bbb ccc"); /* 使い終わったらできるだけ早く閉じる. fclose 関数は int fclose( FILE *stream); という書式を持ち stream でどのファイルを閉じるかを指定する. */ fclose(fp); return 0; }
複数のファイルを同時を読み書きすることを考えると, FILE *fp を用意する 必然性が分かる. fp でファイルを指定して そこに出力を行なったり, そこか ら入力を行なったりする. 次の例では, 2つの別のファイルに書き込みを行な う.
#include <stdio.h> int main(void) { FILE *fp1, *fp2; fp1 = fopen("aaa.dat", "w"); if (fp1==NULL) exit(1); fp2 = fopen("bbb.dat", "w"); if (fp2==NULL){ /* fp1 の方は開いているので閉じる. */ fclose(fp1); exit(1); } fprintf(fp1, "%d %d\n", 10, 15); fprintf(fp2, "%s\n", "aaa bbb ccc"); /* 使い終わった方から閉じる. ここでは, 2つの順序は関係ない. */ fclose(fp1); fclose(fp2); return 0; }
ファイルからの入力には, 関数を用いることができる.
関
数は, 入力元のファイルを指定する以外は
関数と同じである. ファ
イルのオープンやクローズは
関数と同様に,
関数及び
関数を用いる.
次のような内容を持つファイルを読み込むプログラムを作ってみる.
---data.dat の中身--- 12 13 20 ----------------------このデータファイルはエディターで編集して作ることもできるし,
#include <stdio.h> int main(void) { FILE *fp; double x; /* ファイルのオープンを fopen で行なう. fopen は FILE *fopen (const char *path, const char *mode); という関数になっている. 読み込みの時には mode は "r" を指定する. */ fp = fopen("data.dat", "w"); if (fp==NULL) exit(1); /* printf と同じように fprintf を用いてファイルに出力する. */ fscanf(fp, "%lf", &x); printf("%g\n", x); fscanf(fp, "%lf", &x); printf("%g\n", x); fscanf(fp, "%lf", &x); printf("%g\n", x); /* 使い終わったらできるだけ早く閉じる. */ fclose(fp); return 0; }
データの個数が不定の場合, 1 行に 座標
座標のデータが含まれてい
る場合, 誤ったデータが含まれている場合 等のより現実的な使い方について
は[20.8.4, 20.8.3] を参照のこと.
fscanf
関数は scanf
と同様に実用には余り向かない.
(c)1999-2013 Tetsuya Makimura