C言語プログラミングの基礎実験

計算プログラムを例として, Linux(unix)環境でのC言語プログラミングの作業方法を把握しよう.


準備と基礎知識

作業用ディレクトリの作成

まずは,マウス操作によりアプリケーション「端末」を開こう. そして,その端末内でキーボード操作により, 次のようなコマンド(命令)を入力しよう:

$ cd		# ホームディレクトリへ移動
$ mkdir p-1023	# 本日の作業用ディレクトリを作成
$ cd p-1023	# 作業用ディレクトリへ移動
unix 用語「ディレクトリ」は Windows 用語「フォルダ」とほぼ同じ意味をもつ.

ここで,行頭の記号「$」はプロンプト(入力促進)です. キーボードによる unix コマンドの入力が求められています. プロンプト記号を入力する必要はありません.

また,記号「#」から行末まではコメント(説明文)です. これも入力不要です.

これで,今回作成する複数のファイルを ひとつのディレクトリにまとめておくことになる. 次回以降も同様に作業しよう.

ホームディレクトリの整理整頓のためですよ.

ディレクトリやファイルの操作では, 次のコマンドもおぼえておこう:

$ ls		# ファイルの確認(どんなファイルがあるか?)
$ pwd	# カレントディレクトリの確認(今,どこにいるか?)
C言語プログラミングの作業手順

C言語プログラムの作成から実行までの手順は次の通り:

  1. ソースの編集(プログラミング)

    エディタを利用し,ソースコード(プログラムの内容)を記述し, ソースファイル(拡張子「.c」)として保存する:

    $ gedit ソース.c &		# gedit & の代わりに nano とかでも OK
    
    端末とは別のウィンドウが開くようなコマンドの場合, コマンドラインの末尾に記号「&」を付けると, マルチタスクな作業を同時並行に進められる. ただし,端末内だけで完結するコマンドの場合,付けるな危険.

    ソースファイルは人間が読み書きするためだけのものであり, このままではまだ,コンピュータが実行できる状態ではない.

  2. ソースのコンパイル

    コンパイラを利用し,ソースファイルを 実行形式(プログラムファイル)に変換する:

    $ cc ソース.c -o プログラム
    

    これで,実行可能な状態のプログラムファイルが生成される.

    もし,ソースコード内に明らかな間違いがあった場合には, エラーメッセージが表示される. この場合,手順1(編集作業)へ戻り, デバッグ(間違いを修正)しよう.

    「エラーメッセージが出なかったから,正しいプログラムが完成した」とは思い込まないこと!! なお,コンパイラには発見できない種類のバグ(間違い)もある. この場合,プログラムの実行中に強制終了とか異常動作が発生してしまうことになる.
  3. プログラムの実行

    ディレクトリパス名とプログラムファイル名とを指定して実行する:

    $ ./プログラム
    
    先頭の記号列「./」は カレントディレクトリ (現在位置)を表わしている. 自作プログラムを実行するには,通常, このようにディレクトリのパスも指定する必要がある.

    もし,実行結果が期待通りでなければ, また手順1に戻り,デバッグを繰り返そう.


本日の作業

タスク:「割り算プログラム」を開発せよ.

割り算を計算するプログラムを例として, C言語プログラムの開発作業を体験しよう. 単純なプログラムから開始し, 徐々に機能を向上させて行きます.

まあ,今回は初めてのC言語なので, 写経(書き写す)だけの簡単なお仕事です.
Step 0. 最も単純なプログラム(何もしないプログラム)を作ってみる.

ソースファイルを編集:

$ gedit div.c &
int main(void)
{
	return (0);	// 行頭の空白(インデント)は[Tab]キーね
}
// 記号列「//」から行末まではコメントなので入力不要だよ

編集が終わったら「保存」をお忘れなく.

そしてコンパイル:

$ cc div.c -o div		# 拡張子.c の有無に注意!!

エラーが発生したら,編集に戻るんですよ.

エラーがなければ,実行:

$ ./div

...何も起きないのですが?

...はい,何もさせていないので.

このプログラムの実行直後に, 次のコマンドを実行してみよう:

$ echo $?	# 直前のプログラムの終了状態 $? を表示

戻り値(return (N) の数値 N) を色々と変更し,実行結果を確かめてみよう.

Step 1. 割り算の数式を表示させてみる.

ソースを改造:

#include <stdio.h>	// printf() を使う場合に必要

int main(void)
{
	printf("12/3 = 5\n");	// 数式を表示.計算間違いはそのままに...
				// \n は改行記号
	return (0);
}
半角文字のバックスラッシュ「\」と円記号「¥」は, コンピュータ内部では同じものとして取り扱われる. キーボード上には,どちらか片方の文字しか無いよね? ソースコード上の表示とは違っても,そいつを押せば OK. ただし,半角ね.全角にしてしまうと NG.

コンパイル → 実行:

$ cc ...
$ ./div
12/3 = 5

何か表示されたが,12/3 しか計算できない? しかも,計算結果が間違っている? プログラムがおバカ,というか,プログラマが...

プログラムは思い通りには動かない. 書いた通りに動く.
コンピュータは間違わない. プログラマが間違えるんだ.
Step 2. 正しく計算させてみる.

プログラマが働く(そして間違える...)のではなく, コンピュータを働かせよう.

...
int main(void)
{
	printf("12/3 = %d\n", 12/3);	// %dは12/3の計算結果に置き換わる
	return (0);
}
$ cc ...
$ ./div
12/3 = 4

正しく計算できた.

数式内の数値を色々と変更し, 正しく計算できることを確認しよう.

でも,変更しなければならない部分が2箇所もあり面倒だよね...


Step 3. 変数を使ってみる.

プログラムを効率良く(手間なく)書こう.

変数(メモリ)を使えば, 変更すべき箇所を少なくできる. そして,プログラマの間違いも少なくできる.

...
int main(void)
{
	int	x = 12, y = 3;	// 数値を色々と変更してみよう
	int	z;		// 計算結果を入れるための変数.プログラム作成時には数値は未定.

	z = x/y;
	printf("%d/%d = %d\n", x, y, z);
	return (0);
}

...

はい,数値の変更は1箇所だけにできた.

しかし,別の数式を計算するために,いちいちソースを変更し, コンパイルもやり直す必要がある. これはまだまだ面倒くさい...

Step 4. プログラム実行時(実行中)に数値を入力してみる.

プログラムに柔軟性を与えよう. 何度も作り直さなくても,様々な計算に利用できるように.

...
int main(void)
{
	int	x, y;	// 作成時には数値は未定
	int	z;

	printf("2個の整数値 > ");	// プロンプトを表示する
	scanf("%d %d", &x, &y);		// 実行時に数値を入力する

	z = x/y;
	printf(...);
	return (0);
}

...

$ ./div
2個の整数値 > 12 3	# x と y の値を空白で区切って入力
12/3 = 4

$ ./div
2個の整数値 > 20 4
20/4 = 5
...

これでどんな割り算でも計算できるようになった?

本当に? 例えば,y にゼロを入力するとどうなるか? その後の終了状態 $? の値は?

ゼロ以外の数をゼロで割ると数学的には「∞(無限大)」だけど, コンピュータには計算できない. 割り算を実行した段階でプログラムが強制終了してしまい, return (0) は実行されていないことがわかるハズ.
Step 5. エラーによる強制終了を回避してみる.

動いていたシステムが突然動かなくなってしまうと,ユーザはとても困ります...

空港・銀行など,場面によっては,生命・資産を失いかねません. 危険な事故が現実に発生してしまっています.

エラー発生を事前に察知して,ディザスタ(災害・惨事)を防ごう.

...
int main(void)
{
	int	x, y;
	int	z;

	printf("2個の整数値 > ");
	scanf("%d %d", &x, &y);	

	if (y == 0) {		// y がゼロの場合...(y = 0 ではなく y == 0 ね)
		printf("ゼロでは割れません.\n");	// 割らない
	} else {
		z = x/y;	// ゼロ以外なら割る
		printf("%d/%d = %d\n", x, y, z);
	}
	return (0);
}

...

色々な数値を入力して,実験を繰り返そう.

あーでも,プログラムを何度も実行しなおすのも面倒くさいぞ...

Step 6. 計算を自動的に繰り返せるようにしてみる.

反復(繰り返し)

...
int main(void)
{
	int	x, y;
	int	z;

	while (1) {		// 反復.ここから...
		printf("2個の整数値 > ");
		scanf("%d %d", &x, &y);	

		if (y == 0) {
			printf("ゼロでは割れません.\n");
		} else {
			z = x/y;
			printf("%d/%d = %d\n", x, y, z);
		}
	}			// ...ここまで
	printf("\nおつかれさまでした.\n");	// 終了メッセージを表示
	return (0);
}

...

繰り返しが止まらない... とりあえず,キー操作 [Ctrl]+[C] でプログラムを強制終了しよう.

それに,終了メッセージが表示されないぞ? どこが変?

Step 7. 繰り返しを止められるようにしてみる.
...
int main(void)
{
	int	x, y;
	int	z;

	while (1) {
		printf("2個の整数値 > ");
		if (scanf("%d %d", &x, &y) == EOF) break;
			// キー操作 [Ctrl]+[D] で入力を終了し,break で繰り返しを終了

		if (y == 0) {
			printf("ゼロでは割れません.\n");
		} else {
			z = x/y;
			printf("%d/%d = %d\n", x, y, z);
		}
	}
	printf("\nおつかれさまでした.\n");
	return (0);
}

...

$ ./div
2個の整数値 > 20 4
20/4 = 5

2個の整数値 > [Ctrl]+[D]
おつかれさまでした.

これで,何とか実用的なプログラムとしての形が整った?

練習問題:「計算ドリル」を開発せよ.

割り算プログラム div.c の逆バージョンを考えよう. 人間が出題しコンピュータに計算させるのではなく, コンピュータが出題し人間が計算する... クイズゲームのような計算練習ソフトになりそうです.

目標とする動作

次の実行例のような,男子小学生向け エデュテイメントソフト「💩計算ドリル」を開発してみよう. ( 元ネタ

$ gedit drill.c &
$ cc drill.c -o drill
$ ./drill
💩計算ドリル
♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪
84 個の💩をもらいました.
7 人で山分けしましょう.
一人分は何個ですか? > 7
☓ :正解は 12 です.
♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪
78 個の💩をもらいました.
6 人で山分けしましょう.
一人分は何個ですか? > 13
◯
♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪
.
.
.
おしまい.
基本ソースコード drill.c
#include <stdio.h>
#include <stdlib.h>	// rand() を使うために必要

int main(void)
{
	int	x, y, z;	// 問題の数値 z = x/y;
	int	a;		// ユーザが入力する答案の数値

	printf("💩計算ドリル\n");	// タイトルを表示

	while (1) {
		x = rand()%100 + 1;	// 1〜100の乱数を設定
		y = rand()%10 + 1;	// 1〜10の乱数を設定
		printf("♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪\n");
		printf("%d 個の💩をもらいました.\n", x);
		printf("%d 人で山分けしましょう.\n", y);
		printf("一人分は何個ですか? > ");

// ====== 適切なコードを追加し,プログラムを完成しなさい ======
		...		// 答案を入力
		...		// 正解を計算
		...		// 正/誤を判定
// ====== ここまで ======

	}
	printf("\nおしまい.\n");
	return (0);
}

上級者は,機能を追加してみよう. たとえば,成績(正答数,誤答数,正答率,など)の表示機能とか?

発展学習:「割り算コマンド」を実現せよ.

もし余裕があれば,取り組んでみよう.

割り算プログラム div.c を改造し, コマンドライン上で数値を指定できるようにしてみよう. つまり,プログラムの実行途中ではなく, 実行開始時に数値を指定できるようにする.

ここでは Step 3 のソースコードを元にして改造しよう:

#include <stdio.h>
#include <stdlib.h>	// atoi() に必要

int main(int argc, char *argv[])
{
	int	x, y;
	int	z;

	if (argc != 3) return (1);	// 引数不足等の場合,終了
	x = atoi(argv[1]);	// 引数1 の文字列を整数化して x に代入
	y = atoi(argv[2]);	// 引数2 の文字列を整数化して y に代入
	if (y == 0) return (1);	// ゼロ割りを回避
	z = x/y;
	printf("%d/%d = %d\n", x, y, z);
	return (0);
}

コマンドラインに指定されたデータは, 変数 argv[番号] にセットされる. ただし,そのデータは文字列(数字列)なので, 関数 atoi( ) によって数値に変換した.

実行例:

$ ./div 24 8
24/8 = 3

$ ./div 32 16
32/16 = 2

これって結局,unix コマンドを自分で作った,ということだよね? そだねー. 実際,unix コマンドのほとんどはC言語で作られています.


本日の課題

レポートを提出せよ.

質問 Q1〜Q2 に回答し,電子メールで提出せよ.