C入門[1]

今回はちょっと詳しくやるので長くなります。

 

標準出入力

CUIプログラムは,入力に応じて出力をします。

このときの入力を標準入力といい,出力を標準出力といいます。

コマンドプロンプトで普通に実行したときは,ユーザの入力が標準入力で,プログラムによってコマンドプロンプトに表示されるのが標準出力です。

 

標準出入力を特定のファイルにリダイレクトすることもできます。

コマンドプロンプトからプログラムを呼び出すときに,プログラム名の後に続けて「>~.txt」とファイル名を指定すると,出力先がそのファイルになります。「0<~.txt」とするとそのファイルから読み込みます。

 

Hello, world!

プログラミングを勉強する人が必ず最初に作るプログラムがあります。

その名もHello World。標準出力に「Hello, world!」と出力するプログラムです。

とりあえずそのコードを出して軽く説明します。

#include <stdio.h>
int main(void){
    printf("Hello, world!\n");
    return 0;
}

 これをコンパイルして実行すると標準出力に「Hello, world!」と出力されるはずです。

このプログラムについて説明します。

「main関数」と呼ばれる関数がプログラムの本体になります。

関数とは,①いくつかの引数ひきすう(0個でもよい)を受け取って,②何かをして,③値を返すものです。

int main(void) は「main関数の中身を続く{ }の中に書くよ!」という意味です。「void」は引数が0個である(引数をとらない)ことを表し,「int」は返す値が int 型であることを表します。型についてはまたいつか書きます。

main関数の中身は続く{ }の中に書かれています。ここでは{ }の中に2つの文があります。「printf("Hello, world!\n");」と「return 0;」です。文の終わりには必ずセミコロン「;」を入れます。慣れていないうちは忘れやすいので注意。

printfは関数です。「printf(文字列)」で文字列を渡してprintf関数を呼び出すと,標準出力に文字列が出力されます。ただしそのまま出力されるわけではありません。「\n」は改行として扱われます。つまり,「printf("Hello, world!\n");」で「Hello, world!」と出力して改行するという意味になります。printf関数は出力した文字数(バイト数)を返しますがここでは使いません。

printf の f はフォーマットの f です。これについてはまたいつか書きます。

return は関数の終わりを表します。main関数の中に書かれているこのreturn文はmain関数の終了,すなわちプログラムの終了を意味します。return に続く 0 は,main関数に0という値を返させます。0を返すのはマナーです。別に「return 1;」でも正常に実行されます。

さて,まだ説明していないものが一つ残っています。1行目の「#include <stdio.h>」です。これは,printf関数を使うために書かなければいけないものです。

存在しない関数を呼び出すことはできません。呼び出せる関数は,既に「宣言」と「実装」がなされた関数のみです。しかしprintf関数は上のコードの中では宣言も実装もされていません。実は他のファイルの中で宣言されているのです。それが stdio.h というファイルです。これをヘッダファイルといいます。Cのコンパイラをインストールすると,たくさんのヘッダファイルがついてきます。そのうち,使うヘッダファイルだけを #include して使います。printf関数はまた別のファイルの中で実装されています。これをライブラリといいます。競プロerは自分で作った役に立つ関数をコピペして使うときにその関数群をライブラリと呼びますが,本来の意味ではありません。

 C言語において,「#」で始まる行はプリプロセッサといい,コンパイラが一番最初に処理します。

あと int main(void) が int main(int argc, char *argv[]) になることもありますが,競プロでは出てきません。

 

2倍する

問題:標準入力で整数Nが与えられる。Nの2倍を出力せよ。

制約:-100000 ≦ N ≦ 100000

これを満たすプログラムを作るために知っておかなければいけないことが3つあります。

①変数

②標準入力から読み込む方法

③標準出力に数を出力する方法

①変数とは,メモリ上に値を保存するものです。変数には型があります。今回はNが整数なのでint型を使います。intの大きさは決まっていませんが基本4バイト。4バイト=32ビットなので正負9桁くらいが扱えます。

Cでは宣言されていない変数を使うことはできません。変数を宣言するには,型につづけて変数名を書きます。int型の変数Nを宣言するには,

int N;

 とします。これでメモリ上に4バイトの領域を確保できました。この領域は{ }で囲まれたブロック内で自由に使うことができます。メモリ上の変数の場所をアドレスといい,変数の名前の前に&(アンパサンド)をつけて取得することができます。

②で標準入力から数を読み込んでNに格納して,その後にNの値を2倍するのですが,そこはちょっと飛ばして,先に③を説明します。標準出力にNの値を出力します。

文字列でなく数を出力するときもprintf関数を使います。

printf("%d\n", N);

 これでNの値が出力されます。printf関数の1つめの引数は"%d\n"ですが,%dは10進法でNの値に置き換えられます。これがprintfのfがフォーマットたる所以です。

printf("Nの値は%dです\n", N);

とすると,"Nの値は%dです\n"の%dがNに置き換えられ,「Nの値は~です」とNの値が出力されます。

これをちょっと詳しく説明します。

関数を呼び出すときのプロセスは,

レジスタ/スタックに引数を格納する

②呼び出す

の2段階です。スタックは一時的に変数を格納しておく場所です。printf("%d\n", N)と書くと,スタックにNの値と"%d\n"という文字列が格納され,そのあとにprintf関数が呼び出されます。printf関数は,"%d\n"という文字列を読み込み,その中に%dを発見すると,スタックからNの値を読み込み,10進法に変換して,表示します。ここで重要なのは,スタックに格納されているのはNの「値」だということです。Nそのものではありません。

それを踏まえて,②の説明をします。標準入力から数を読み込むときは,scanf関数を使います。scanf関数はstdio.hで宣言されています。

scanf("%d", &N);

これで標準入力から数を読み込んでNに格納できます。今回はNの前にアンパサンドが付いています。その理由を説明します。scanf関数はprintf関数と形式がちょっと似ていますが,決定的な違いがあります。それは,printf関数と違ってscanf関数はNの値を書き換えるということです。scanf関数がNの値を書き換えるために必要なものはなんでしょうか。

もし,printf関数のときのようにスタックに「15」という値が格納されていても,それを読んだscanf関数はNの値を書き換えることができません。

scanf関数がNの値を書き換えるために必要なものは,Nのアドレスです。

スタックにNのアドレスが格納されていれば,scanf関数はそれを書き換えることができます。それで,scanf関数の第2引数はポインタ型なのです。

 

最後に,Nの値を2倍する方法を言います。

N = N * 2;

「=」は代入を表します。右辺を計算してから左辺に代入します。

N *= 2;

と書くこともできます(糖衣構文)。

 

これら全てを組み合わせ,最終的に出来上がるコードがこちら。

#include <stdio.h>
int main(void){
    int N;
    scanf("%d", &N);
    N *= 2;
    printf("%d", N);
    return 0;
}

3行目で変数Nを宣言し,4行目で標準入力から読み込み,5行目でNの値を2倍し,6行目で出力しています。

 

最後に一つ付け加えておきます。BorlandのCコンパイラ

#include <stdio.h>
int main(void){
    int a = 5;
    printf("%d", a);
    printf("%d");
}

とすると,1回目にprintf関数を呼び出したときにスタックにプッシュした5という値が,2回目にprintf関数を呼び出したときもまだ残っているため,全体で「55」と出力されることがあります。gccやclangなどのしっかりしたコンパイラではこのようなことは起こりません。