4日目 6日目

5日目

目標

課題 5

カレンダプログラムを作ってみよう(4)


前回までで終わっていること

課題4の最後の問題までできていれば、main() 関数の中で calendar()関数を呼び出す際の引数を変更するだけで、 好きな「年」「月」のカレンダを表示できるようになっているはずである。 しかし、いちいちプログラムをコンパイルするのは汎用性に欠けるし、 C 言語やコンパイル等について良く知らない人に使ってもらえるとは期待できない。

仕上げとして、コンパイルし直すことなく、プログラムの振舞いを変える方法として、 コマンドラインの引数を使うことを考える。

そのため、今まで避けてきた、文字や文字列について考えることとする。


文字とは

C 言語では、「文字」を「'」(シングルクォート)でくくって表現する。

/* print_char.c */
#include <stdio.h>

int main()
{
        char a;

        a = 'A';
        printf("a = \"%c\"\n", a);
}

変数の型のところで、文字という意味の character から名前がついたと説明した 整数型の変数である、char型が使用されている。 なぜ、「文字」なのに整数なのだろうか? そこで、関数printf()を使って様子を探ってみることにする。

関数printf()で書式指定子を使い分けると、同じ値から(見た目が)異なる出力を得られることを学習した。 例えば、次のプログラムは、(10進数で)48から63までの数字を、それぞれ、10進数、8進数、16進数で出力するものである。

/* test_char.c */
#include <stdio.h>

int main()
{
	int i;
	printf(" %%3d  %%4o  %%2x\n");
	for (i = 48; i < 64; i = i + 1) {
		printf("%3d  %4o   %2x\n", i, i, i);
	}
}

書式指定子として 「…対応する文字に変換する」と printf 関数のマニュアルに記述されている "%c" を追加してみると何が表示されるであろうか?

#include <stdio.h>

int main()
{
	int i;
	printf(" %%3d  %%4o  %%2x  %%c\n");
	for (i = 48; i < 64; i = i + 1) {
		printf("%3d  %4o   %2x   %c\n", i, i, i, i);
	}
}
[user99@proge1]~/kadai05% ./test_char
 %3d  %4o  %2x  %c
 48    60   30  0
 49    61   31  1
 50    62   32  2
...
 63    77   3f  ?

48 から 57の「数値」が、'0' から '9' の「数字」に対応している事がわかるだろうか?

じつは、コンピュータ内部ではすべての「文字」について、番号が割り振られている。 この番号の事を文字コードと呼ぶ。 一般に良く使われる文字コードとして ASCIIと呼ばれるものがある。 これは American Standard Code for Information Interchange (情報交換用米国標準コード)の略であり、 アメリカで定められた「文字」と「番号」の対応を定めたものであり、次のようになっている。

ASCIIによる文字コードの表
  0x00 0x10 0x20 0x30 0x40 0x50 0x60 0x70
0     空白 0 @ P ` p
1     ! 1 A Q a q
2     " 2 B R b r
3     # 3 C S c s
4     $ 4 D T d t
5     % 5 E U e u
6     & 6 F V f v
7     ' 7 G W g w
8     ( 8 H X h x
9     ) 9 I Y i y
a     * : J Z j z
b     + ; K [ k {
c     , < L \ l |
d     - = M ] m }
e     . > N ^ n ~
f     / ? O _ o DEL

ASCII では、英語のアルファベットの大文字小文字、数字、記号等だけが 定義されている。 上の表の中で空欄になっている部分には「コントロールコード」と呼ばれる 制御用コード(改行や、タブ等も含まれる)が定義されている。 表に現れるのはコントロールコードを含めても128種類しかない。

変数のところで、整数型の一番小さなもの(256種類の表現が可能な型)にchar型があったのは、 「文字」として ASCII が仮定されているため、128種類表現できれば十分であることに由来する。

逆に、文字の文字コードは、最初に述べた 'A' のような表現を "%d" などの整数に変換するような書式指定子を用いて出力すれば良いことになる。

問 5-1

print_char.c を修正し、 変数 aの内容を文字として出力するだけでなく、10進数、16進数としても出力するようなプログラムを作成せよ。 また、結果が ASCII 表と一致することも確認せよ。


文字列について

上で述べたように、文字をプログラム中で使用するには、「'」(シングルクォート)で括る。 この時に括ることができるのは、1文字だけである。 2文字以上くくると、GCC では警告が表示される。

複数の文字からなる「文字列」を表現するためには、「"」(ダブルクォート)でくくる必要がある。 BASIC 等の言語と異なり、C には、文字列型という型は存在しない。 文字列は「文字」が複数連なっているのであるから、変数としては char型の配列を用いる。

また、C では、文字列の終端を \0 というコードで示すことになっている。 この \0は、NULL 文字(ナル文字と読む)と呼ばれるコントロールシーケンスの一つで、 ASCII では 0 という値を持つ。 そのため、文字列を変数として扱う際には、最低限入力したい「文字列の長さ + 1」個の要素を持つ配列を用意しなければならない。 NULL がないと、「文字列」を扱う関数は正しく動かなくなる上に、 確保した領域が少ないにも関わらず NULL を代入すると、 プログラムのほかの部分のデータが破壊されることになるため慎重に考えること。

「Hello」という文字列を char 型の配列 aに代入した場合の様子は次のようになる。

C string

また、次のようなプログラムで確認することもできる。

/* string_test.c */
/* 文字列とは ? */

#include <stdio.h>

int main()
{
	char a[6] = "Hello";
	int i;

	printf("a = %s\n", a);

	for (i = 0; i < 6; ++i) {
		printf("a[%d] = %c\n", i, a[i]);
	}

	a[0] = 'h';
	printf("a = %s\n", a);

}

新しく出てきた printfの変換指定子 %sは char 型の配列(本当はポインタ)を引数に取り、中身を文字列として表示する。 a[5]の中身は何もないように見えるが、次のように実行すると 何かが入っていることは確認できる。

[user99@proge1]~/kadai05% ./string_test | cat -v
表示される ^@ は NULL 文字を意味している。

また、最後から2行目では、a[0] に 'h' という文字を代入しているので、最後の printf では 「hello」と表示されることになる。

問 5-2

プログラムの最後の部分で、a[2] に \0 を 代入した後、printf("a = %s\n", a);で何が表示されるだろうか?


インクリメント/デクリメント

先週の最後に簡単に触れたインクリメント(increment)演算子 ++について少し詳しくまとめておく。

インクリメント演算子は、変数の値を「1単位」増加させるために利用する。 したがって、

i++

i = i + 1

は、ほとんど同じ意味になる。

逆に変数の値を「1単位」減らす為の演算子として、デクリメント(decrement)演算子 -- が用意されている。

・前置と後置

インクリメント/デクリメント演算子は、

i++

という形式のほかに、

++i

という形式でも利用できる。 区別する場合には、それぞれ変数の後ろ(前)にあるので、「後置(前置)インクリメント演算子」と呼ぶ。 どちらも 変数 i の中身は最終的に 1単位増えるのであるが、式として考えた場合には結果が異なるので注意が必要である。 次のプログラムを実行して、どう違うかを考えてみよ。

/* pre_post_test.c */
/* インクリメント演算子の前置と後置の違い */

#include <stdio.h>

int main()
{
        int i, j;


        i = 5;
        j = i++;
        printf("i=%d,  j=%d\n", i, j);

        i = 5;
        j = ++i;
        printf("i=%d,  j=%d\n", i, j);

}

後置演算子の場合には、まず式の値を評価してから、インクリメントが行われるのに対し、 前置演算子の場合には、インクリメントしてから式の評価が行われる。 for文での利用のように、式の値を使っていないのであれば、どちらを使っても結果は変わらないが、 式の値を使うような場合には、どちらを使うべきか良く考えなければならない。


コマンドラインの引数を利用する

最初はあまり内容について突っ込まずに、次のプログラムを実行してみよう。

/* arg_test.c */
/* コマンドライン引数について */

#include <stdio.h>

int main(int argc, char *argv[])
{
	int i;

	for(i = 0; i < argc; ++i) {
		printf("argv[%d] = <%s>\n", i, argv[i]);
	}
}

出来上がったプログラムを実行する際に、 プログラム名だけではなく様々な余分な情報を空白で区切ってつけてみよう。

[user99@proge1]~/kadai05% ./arg_test
[user99@proge1]~/kadai05% ./arg_test test1 arg2 foo bar hoge

今までのプログラムと大きく異なるのは、main関数に 引数がついたところである。 main の引数にはプログラムが実行された際の情報がセットされる。 最初の引数は、パラメタの数(+1)の情報であり、int 型である。 2番目の引数は、パラメタの内容であり、char 型のポインタの配列である。 ポインタについては、今日は触れないので、あまり気にせず、「配列のようなもの」と思っていれば良い。

パラメタの最初には、実行したプログラム自身の名前が設定されていることに注意。 これにより、なんという名前で実行されたかという情報を得ることができる。 また、その結果、argcの値は、パラメタの数より1多いことになる。

文字列を整数に変換する

コマンドライン引数は、その変数の型や printfでの変換指定子からも判るように文字列である。 与えられた文字列が表現する10進数数値を int型として得るためには atoi関数を利用する。 なお、与えられた引数の中に10進数として解釈できないものが現れた場合、その先は評価されない。

atoi関数は、stdlib.h で宣言されているので利用するには 次のように stdlib.h も include する必要がある。

#include <stdio.h>
#include <stdlib.h>

問題 5

問題 4-1 で作成した mycal6.c を変更して、mycal7.c を作成せよ。 ただし、atoi関数を使って、コマンドライン引数で、 表示するカレンダの年月を指定できるようにせよ。 たとえば、

./mycal7 2007 11

と実行すると2007年11月のカレンダを表示できるようになればよい。 余裕があれば、引数が2個以外の場合のエラー処理を付け加えよ。


4日目 表紙 6日目

tacha@tack.fukui-med.ac.jp
$Id$