課題2 課題4

課題3

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

始めに、TeraTerm を用いて PorgE1 に login し、Windows 上から ProgE1 のディスクが 利用できるように準備をする。その後、O ドライブの examples フォルダ内の kadai03 という フォルダ全体を O ドライブ直下にコピーする。

課題2から続いて課題3に取り掛かる場合は、上記の作業を行った後、 作業場所を TeraTerm 上で次のようにして変更する。

    [user99@proge1]~/kadai02% cd
    [user99@proge1]~/ cd kadai03
    [user99@proge1]~/kadai03%
  

とする(プロンプトを注意深くみていると一度 login した状態に戻っている事が判るはずである)か、 一度に

    [user99@proge1]~/kadai02% cd ~/kadai03
    [user99@proge1]~/kadai03%
  

としてもよい


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

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  = 2;
	nissu  = 30;
	kaigyo = (7 - youbi) % 7;

	/* タイトルの表示 */
	printf("        2008/4\n");
	printf("  S  M Tu  W Th  F  S\n");

	/* 月始めの「空白」 */
	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を選ぶと、カレンダプログラムは次のように変更できることになる。 ついでに、4月 から 6月までの3ヶ月分表示するようにもしてみた。 ただし、タイトルの表示はこの段階では「お預け」とした。

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

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

int main()
{
	calendar(2,30);  /* 2008年 4月 */
	printf("\n");
	calendar(4,31);  /* 2008年 5月 */
	printf("\n");
	calendar(0,30);  /* 2008年 6月 */
	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);  /* カレンダ表示が成功したことを意味する */

}

問題 3

カレンダをつくる場合、考える年が閏年であるかどうかを判断する事は重要である。 次に示す閏年の判別ルールを用いて、与えられた年が閏年かどうかを判別するような 関数 leap_year を作成せよ。

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

なお、与えられた年が閏年である場合は返り値 1、閏年でない場合は返り値 0 とせよ。

ヒント


課題2 日程表 課題4

tacha@tack.fukui-med.ac.jp
$Id: kadai03.html,v 1.2 2008/04/24 18:22:19 tacha Exp $