2日目 4日目

3日目

目標

課題

課題 3

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

始めに、TeraTerm を用いて PorgE1 に login し、Windows 上から ProgE1 のディスクが 利用できるように準備をする。その後、ProgE1 上に kadai03 というフォルダを作成しておく。


前回の復習

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

main()
{
	int i;

	/* 月始めの「空白」 */
	for (i = 0; i < 3; i = i + 1) {
		printf("   ");
	}

	/* 日付の表示 */
	for (i = 1; i <= 30; i = i + 1) {
		printf(" %2d", i);

		/* 「土曜日」ならば、改行する */
		if ((i % 7) == 4) {
			printf("\n");
		}
	}

	/* 最後には改行 */
	printf("\n");
}

変わる可能性があるところは変数を使う

mycal3.c の欠点はなんだろうか?

答は簡単で、「水曜日から始まる30日の月」以外には対応していない事である。 しかし、何曜日から始まろうが、月の日数が何日であろうが、カレンダの作成方法が変わるわけではない。 そこで、変わる可能性があるところ(つまり「水曜日」、「30日」に対応するところ)を変数として表すことで、 適用できる範囲が大きく広げてみることにする。

いま、考えているカレンダプログラムでは変化する可能性があるところを洗い出すと

の3つである。

始めに、曜日を数字を用いて表すことを考える。例えば、

日 → 0、月 → 1、火 → 2、水 → 3、木 → 4、 金 → 5、土 → 6

と対応づけることにしよう。 そうすると、「最初に出す空白の日数」とは、「その月の最初の日(1日)の曜日」とみなすことができる。(日曜日を 0 としていることに注意。)

日 月 火 水 木 金 土    ← 曜日
 0  1  2  3  4  5  6    ← 曜日を表す数字
---------------------
          1  2  3  4
 5  6  7  8  9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30

さらに、「改行を出力する条件に現れる数」とは、その月の最初の土曜日であったから、 最初の日の曜日が決まれば次の式で求めることができるような気がする。

最初の土曜日 = 1週間の日数(== 7) - 最初の日の曜日

しかし実はこれでは不十分である。1日が日曜日であった場合、最初の土曜日は 7日であるが、 この場合 if文の中の左辺 i%7は 0 となる。 したがって、if文の右辺としては、「最初の土曜日を 7 で割ったあまり」が 正しいことになる。

そこで、その月の日数と、最初の日の曜日、改行条件の数字の3つを変数としてプログラムを 書き直してみると、次のようになる。

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

/*
 * 曜日は次のルールで数字に置き換えた
 * 日曜日 → 0
 * 月曜日 → 1
 * ...
 * 土曜日 → 6
 */

main()
{
	int i;
	int youbi;  /* 1日の曜日 */
	int nissu;  /* その月の日数 */
	int kaigyo; /* 最初の土曜日を7で割った余 == 改行条件*/

	youbi  = 3;
	nissu  = 30;
	kaigyo = (7 - youbi) % 7;

	/* 月始めの「空白」 */
	for (i = 0; i < youbi; i = i + 1) {
		printf("   ");
	}

	/* 日付の表示 */
	for (i = 1; i <= nissu; i = i + 1) {
		printf(" %2d", i);

		/* 「土曜日」ならば、改行する */
		if ((i % 7) == kaigyo) {
			printf("\n");
		}
	}

	/* 最後には改行 */
	printf("\n");
}

何度も使う可能性があれば関数にする

これで、月はじめの曜日と月の日数を与えれば、1月分のカレンダが表示できるようになった。 しかし、たとえば、3ヶ月分のカレンダを表示しようとした際に、同じことを3度も 書かなければならず、非常に面倒である。 このような場合は、カレンダを書く手続きをひとまとまりとして、関数として定義すると便利である。 関数は次のような形式で定義する。

 関数の型  関数名(引数1, 引数2, ...) {
     関数内部でだけ使用する変数の宣言;

     関数で行う処理1;
     関数で行う処理2;
     ...
     return (関数の返り値);
 }

引数はなくても良い。今までに利用してきた printf やおまじないのように 唱えてきた main も実は関数である。 関数の型を省略した場合は int型が仮定されるが、プログラムを作成する際にはできるだけ明示的に宣言すべきである。

また、関数を利用する前には、その関数の型や引数の種類をあらかじめ宣言しておくのが望ましい。 関数の宣言を行うには、次のように行う

  関数の型 関数名(引数1の型, 引数2の型, ...);

printf等の標準的に備わっている関数も本来は宣言を行って使用するべきであるが、 いちいち、書くのは面倒であるため、あらかじめそのような宣言を行っているファイル(ヘッダファイル(header file)と呼ぶ)が用意されている。 その宣言ファイルは stdio.h という名前で /usr/include 直下に存在している。 いままでプログラムの先頭に書いていた#include <stdio.h>は、ここに /usr/include/stdio.hを取り込め(include)という指示である。 あらかじめ用意されている関数が、どのようなヘッダファイルで宣言されているかはマニュアル等で調べれば良い。 例えば、man 3 printf とすると、次のような出力が得られ、printf 関数が stdio.h で宣言されていることがわかる。

PRINTF(3)                 Linux Programmer’s Manual                 PRINTF(3)

名前
       printf,  fprintf,  sprintf,  snprintf,  vprintf,  vfprintf,  vsprintf,
       vsnprintf - 整形された書式へ変換する関数

書式
       #include <stdio.h>

       int printf(const char *format, ...);
       int fprintf(FILE *stream, const char *format, ...);
       int sprintf(char *str, const char *format, ...);
       int snprintf(char *str, size_t size, const char *format, ...);
...

さて、カレンダを書くためには、月始めの曜日とその月の日数が必要であった (改行条件は月始めの曜日から計算できたので、不要である)ので、引数は二つとなる。 関数の名前として、calendarを選ぶと、カレンダプログラムは次のように変更できることになる。 ついでに、11月 から 1月までの3ヶ月分表示するようにもしてみた。

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

/* 関数宣言 */
int calendar(int, int);

int main()
{
	calendar(3,30);  /* 2006年11月 */
	printf("\n");
	calendar(5,31);  /* 2006年12月 */
	printf("\n");
	calendar(1,31);  /* 2007年 1月 */
	printf("\n");
}

/*
 * カレンダを表示する。
 * 第1引数は月始めの曜日、第2引数はその月の日数

 * 曜日は次のルールで数字に置き換える
 * 日曜日 → 0
 * 月曜日 → 1
 * ...
 * 土曜日 → 6
 */

int calendar(int youbi, int nissu)
{
	int i;
	int kaigyo; /* 最初の土曜日を7で割った余 == 改行条件*/

	kaigyo = (7 - youbi) % 7;

	/* 月始めの「空白」 */
	for (i = 0; i < youbi; i = i + 1) {
		printf("   ");
	}

	/* 日付の表示 */
	for (i = 1; i <= nissu; i = i + 1) {
		printf(" %2d", i);

		/* 「土曜日」ならば、改行する */
		if ((i % 7) == kaigyo) {
			printf("\n");
		}
	}

	/* 最後には改行 */
	printf("\n");

	return(0);  /* カレンダ表示が成功したことを意味する */

}

あらかじめ月の日数ってわかってるよね(配列)

これで、だいぶ使い回せるような形に近づいてきた。 しかし、月のはじめの曜日や日数をいちいち与えるのはめんどうである。 できれば、年月を指定すると勝手にコンピュータが月のはじめの曜日や日数を考えてくれる方が利用者には便利である。

良ぉく考える(調べる)と、2月の日数が閏年か平年かで変化する以外は、何月かが分かればその月の日数は判る。 このような場合には、月の日数を配列に入れておいて参照すると便利である。

配列とは、同じ型の変数を順に並べておいたものである。 宣言の際に、変数名の後ろに数字をカギ括弧("[" と "]")でくくって宣言すると配列となる。 たとえば int a[5];と宣言すると次のように、a[0], a[1], a[2], a[3], a[4] の5つの変数が用意される。

括弧でくくられた部分は添字(subscript)と呼ばれる。 数学で出てきた数列などを表現する a0, a1 の小さい右下の文字と同じ意味と思って良い。 添字は 0 から始まるので、用意される配列の最後は宣言の時の数字より1小さいことに注意しなければならない。

月の日数を保持する配列として、int mdays[13] を用意し値を代入するプログラムは次のようになる。

   int mdays[13]; /* 用意される最大要素の添字を12にするには要素数は13 */
   mdays[1] = 31;
   mdays[2] = 28;
   mdays[3] = 31;
   mdays[4] = 30;
   mdays[5] = 31;
   mdays[6] = 30;
   mdays[7] = 31;
   mdays[8] = 31;
   mdays[9] = 30;
   mdays[10] = 31;
   mdays[11] = 30;
   mdays[12] = 31;

宣言と同時に初期化を行う場合にのみ、次のようにも書ける。

   /* 用意される最大要素の添字を12にするには要素数は13 */
                  /*     1   2   3   4   5   6   7   8   9  10  11  12 */
   int mdays[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

このようにすると、何月かが変数 mに入っているとすると、 その月の日数はmdays[m]で求められる。 問題は年によって変化する 2月のデータであるが、 次の閏年かどうかの判別ルール

  1. 西暦年数が4で割りきれなければ平年
  2. 西暦年数が400で割りきれると閏年
  3. 西暦年数が400では割りきれず、100で割りきれると平年
  4. 残りは閏年

ifを使うと解決できるはずである。

ヒント
「割りきれる」とは「割ったあまりが 0」の事である。


問題 3-1

年が変数 year に入っているものとして、 2月の日数を求めるような関数 february を考えてみよ。


曜日って計算できる?

閏年の判別方法が判るということは、年と月が判れば、どのような場合でもその月の日数が判ることになる。 ということは、年月日をすべて指定すると、ある基準日からの延べ日数が判るはずである。 さらに、曜日は7で割ったあまりで表現することができたのであるから、結局曜日も計算できることになる。

曜日を計算する方法は様々なものがあるが、ここでは、次の方法を使うことにしよう。 次の方法は西暦1600年以降の年と月が与えられた場合のその月の最初の日の曜日を求めるものである。

  1. 整数変数 y に年を代入する
  2. 整数変数 m に月を代入する
  3. y から 1600を引く
  4. 次のような条件に基づく処理を行う。
  5. 整数変数 d に(365.25*y)を代入する
  6. d から (y/100) を引く
  7. d に (y/400) を加える
  8. さらに d に 30.6*m + 3.5を加える
  9. 得られた d を7で割った余りは、上で決めたルールにしたがった曜日を表す数字である。

問題 3-2

上の手法をプログラムとして表現し、関数calendarの引数を 「年」と「月」に変更して mycal6.c として保存せよ。 できれば、「2006/11」の用にどの月のカレンダかを表示したり、 何曜日か判るようにしてみよう。

問題 3-3

5から8をまとめて行うと、正しい答えを出さない場合がある。なぜだろうか?


2日目 表紙 4日目

tacha@tack.fukui-med.ac.jp
$Id: 03.html,v 1.1 2006/11/08 15:36:46 tacha Exp $