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

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


実習の準備

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

$  cd		# ホームディレクトリへ移動
$  mkdir  c-0425	# 本日の作業用ディレクトリを作成
$  cd  c-0425	# 作業用ディレクトリへ移動
各行の末尾では,キー入力 [Enter] or [Return] も必要.

行頭の記号「$」はプロンプト(入力促進)の表示であり, この記号は入力しないこと. また,記号「#」から行末まではコメント(説明文)であり, 入力する必要はない. なお,unix 用語「ディレクトリ」は Windows 用語「フォルダ」とほぼ同じ意味をもつ.

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

要するに,ホームディレクトリの整理整頓のための作業ね.

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

$  ls	# ファイルの確認(どんなファイルがあるか?)
$  pwd	# カレントディレクトリの確認(今,どこを見ているか?)

作業手順のまとめ

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

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

    $  gedit  ソース.c  &	# emacs や leafpad でも OK
    

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

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

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

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

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

  5. プログラムの実行
  6. プログラムファイル名を指定して実行する:

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

    もし,実行結果が期待通りでなければ, さらにデバッグしよう.


「割り算プログラム」の開発

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

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);
}
半角文字のバックスラッシュ「\」と円記号「¥」は, コンピュータ内部では同じものとして取り扱われます. キーボードの種類や端末の設定によって,どちらか一方に決まります.

保存 → コンパイル → 実行:

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

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

プログラムは思い通りには動かない. 書いた通りに動く.

コンピュータは間違わない. プログラマが間違えるんだ.


Step 2. 正しく計算させてみる
#include <stdio.h>

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

正しく計算できた.

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

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


Step 3. 変数を使ってみる

変数(メモリ)を使えば, 変更すべき箇所を少なくできる.

#include <stdio.h>

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. プログラム実行時(実行中)に数値を入力してみる
#include <stdio.h>

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

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

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

...

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

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

これでどんな割り算でも計算できるようになった? しかし,y にゼロを入力するとどうなるか? その後の終了状態は?


Step 5. エラーによる強制終了を回避してみる
#include <stdio.h>

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

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

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

...

色々な数値で実験しよう.

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


Step 6. 計算を自動的に繰り返せるようにしてみる
#include <stdio.h>

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. 繰り返しを止められるようにしてみる
#include <stdio.h>

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

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

		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]
おつかれさまでした.

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


練習問題:「計算ドリル」の開発

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

というわけで,男子小学生向け エデュテイメントソフト「計算ドリル」を開発してみよう. ( 元ネタ

目標とする動作
$  gedit  drill.c  &
$  cc  drill.c  -o  drill
$  ./drill
うんこ計算ドリル
♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪
84 個のうんこをもらいました.
7 人で山分けしましょう.
一人分は何個ですか? > 7
☓ :正解は 12 です.
♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪
78 個のうんこをもらいました.
6 人で山分けしましょう.
一人分は何個ですか? > 13
◯
♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪♪
.
.
.
おしまい.
基本ソースコード
#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);
}

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


コマンドライン引数の利用

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

ここでは 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言語で作られています.


(c) 2018, yanagawa@kushiro-ct.ac.jp