4日目 6日目

5日目

目標


課題 4

カレンダプログラムを自分で作る(3)


前回の課題3の最後の問題までできると、 main()関数で、「年」「月」を指定するだけで、その月のカレンダを 表示できるようになったはずである。 しかし、いちいちプログラムをコンパイルするのは汎用性に欠けるし、 良く知らない人に使ってもらえるとは期待できない。

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

そのために、今まで出してこなかった文字や文字列について考えてみることにする


文字とは

はじめに、次の簡単なプログラムをコンパイルして実行してみよう。

/* char_test.c */
/* 「文字」とは ? */

#include <stdio.h>

int main() 
{
        printf("100 を %%d で書くと %d\n", 100);
        printf("'Q' を %%c で書くと %c\n", 'Q');

        printf("100 を %%c で書くと %c\n", 100);
        printf("'Q' を %%d で書くと %d\n", 'Q');        
}

コンパイルのあと、実行してみると次のようになるはずである。

cc char_test.c -o char_test
./char_test 
100 を %d で書くと 100
'Q' を %c で書くと Q
100 を %c で書くと d
'Q' を %d で書くと 81

printfの書式の中の %%%文字そのものを表している。 第2日目に学習したように、%dは引数を 10進数で表示するのであった。 2行目を見ると %cは、引数を文字として表示するのであることは想像できると思う。 3行目、4行目は少し混乱をまねくかもしれない。 3行目では「数値」を与えているのにもかかわらず、「文字」が出力されており、 4行目では「文字」を与えているのにもかかわらず、「数値」が出力されている。

じつは、コンピュータ内部ではすべての「文字」について、番号が割り振られている。 この番号の事を文字コードと呼ぶ。 一般に良く使われる文字コードとして 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種類しかない。

変数のところで、整数型の中にchar型があった。 もともと char は character(文字)と言う意味から来ており、 それは ASCII が仮定されているため、128種類表現できれば十分であることに由来する。

ASCII 表を見てみると、確かに 100(16進数では 0x64)に対応する文字は、'd' であり、 Q には 0x51、つまり 81が対応していることが確認できる。


文字列について

文字をプログラム中で使用するには、「'」(シングルクォート)で括る。 この時に括ることができるのは、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]の中身は何もないように見えるが、次のように実行すると 何かが入っていることは確認できる。

./string_test | cat -v
表示される ^@ は NULL 文字を意味している。

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

では、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("%d番目の引数は<%s>\n", i, argv[i]);
        }
}

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

cc arg_test.c -o arg_test
./arg_test
./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>

問題 4

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

./mycal7 2005 11

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


4日目 表紙 6日目

tacha@tack.fukui-med.ac.jp
$Id: 05.html,v 1.1 2005/12/02 04:34:17 tacha Exp $