C の入出力システムでは、プログラムと実際のデバイスとのあいだに抽象的なレベルを設けている。これをストリームという。ストリームのおかげで、プログラムからは実際のデバイスにかかわらずほとんど同じように扱える。
これに対して、実際のデバイスをファイルと呼ぶ。この文脈ではいわゆる普通のファイルだけでなく、画面、キーボード、ポートなどを含んでいる。
キーボードからの入力というのは、ユーザーが文字列に続いて Enter キーを押すまで。Enter キーが押されると、文字列を読み込んで最後にある改行をヌル文字に置き換えたうえで引数の配列に格納する。関数自体の戻り値は、入力が成功すると配列の先頭へのポインタを、失敗するとヌルポインタを返す。 次のプログラムでは、gets() の戻り値を p に格納して、p がヌルでないことを確認してから出力している。p も str も配列(文字列)の先頭のアドレスを保持しているので、同じ文字列が2回出力されるはず。
#include
int main(void)
{
char *p, str[80];
printf("Input string: ");
p = gets(str);
if (p) { /* if p is not null */
printf("%s %s\n", p, str);
}
return 0;
}
C のプリプロセッサはコンパイルの前にソースコードに対して様々な操作を行う。プリプロセッサへの命令がプリプロセッサディレクティブ。これまでにヘッダファイルを読み込む #include を使ってきた。 ここでもうひとつ、#define というディレクティブが出てきた。大まかに言うと、ソースコード中の特定の文字列を別の文字列に置き換えるものだ。これはマクロ置換(macro substitution) と呼ばれる。#define の一般的な形式は次のとおり。
#define マクロ名 文字列
マクロ名は C で有効な識別子なら何でもいい。大文字も小文字も使える。ただ、大文字で書くのが通常だそう。 マクロ名と文字列は1つ以上の空白文字で区切られていなければいけない。 文字列の方は途中に空白を含んでも構わない。ただし、ディレクティブ自体が1行で完結している必要があるので、行末までが有効な文字列ということになる。
次の例は MAX というマクロを定義している。プリプロセッサがこれを処理すると、for ループの中の MAX という文字列が 100 という文字列に置き換わる。
#incluce
#define MAX 100
int main(void)
{
int i;
for (i = 0; i < MAX; i++) {
printf("%d\n", i);
}
return 0;
}
結果としてこのループは、コンパイラにはこう見える。
for (i = 0; i < 100; i++) {
printf("%d\n", i);
}
いったん定義したマクロはほかのマクロの中でも使える。次の例は正しい使い方だ。
#define SMALL 1
#define MEDIUM SMALL + 1
#define LARGE MEDIUM + 1
C のプログラムでは、コマンドライン引数を main() の2つの引数、argc と argv で受け取る。argc と argv という名前は決まったものではないらしいけど、伝統的のこの名前が使われているらしい。 argc は int 型で、コマンドライン引数の数を受け取る。argv は文字列ポインタの配列で、コマンドライン引数自体を受け取る。注意が必要なのは、コマンドラインに打ち込んだプログラム自体も含まれているということだ。なので、argc は 1 以上の整数になり、argv[0] にはプログラム名が入っている。
これらは次のように使う。
int main(int argc, char *argv[])
{
....
}
*argv[] となっているのは、コマンドライン引数の数が決まっていないから。
ちょっと試してみよう。次のプログラムは、コマンドライン引数の数と、それぞれの引数を出力する。
#include
int main(int argc, char *argv[])
{
int i;
printf("%d\n", argc);
for (i = 0; i < argc; i++) {
printf("%s\n", argv[i]);
}
return 0;
}
実行例:
takatoh@nightschool $ ./sample_7_4a
1
./sample_7_4a
takatoh@nightschool $ ./sample_7_4a foo bar baz
4
./sample_7_4a
foo
bar
baz
さて、コマンドライン引数は文字列として渡されるので、もし数値として使いたいなら、文字列から数値へ変換しなければいけない。よく使われる関数は次のとおり。これらを使うには stdlib.h を include する必要がある。
int atoi(char *str);
double atof(char *str);
long atol(char *str);
long は long int のことね。 試してみよう。
#include
#include
int main(int argc, char *argv[])
{
int i;
double d;
long l;
i = atoi(argv[1]);
d = atof(argv[2]);
l = atol(argv[3]);
printf("%d %f %ld\n", i, d, l);
return 0;
}
int 型や float 型の引数を普通に渡すのが値渡し。このとき引数の値はコピーされて関数に渡されるので、関数の中で変更しても元の(つまり関数の外の)変数の値は変化しない。 これに対して、ポインタで渡すのが参照渡し。引数のポインタの持っているアドレスがコピーされて関数に渡されるけど、このアドレスは関数の呼び出し元のポインタと同じデータを指している。結果として、関数内で引数(ポインタ)の指すデータを変更すると、呼びさし側でもデータが変化することになる。