C言語の研究・演習編LAST
いよいよC言語の研究・演習編も終わりとなった。
最後に情報処理技術者試験の問題を2問見て終わりにしよう。
いずれも簡潔にして、よく考え練られたいい問題だ。
2つの情報処理技術者試験問題
第1問 日付に関する問題
生年月日と今日の日付を西暦年、月、日とスペースで区切ってキーボードから入力して、生年月日から今日までの日数を表示するプログラムである。
@入力できる年月日は1800年1月1日以降とする。年月日が正しい範囲内にないときは、入力エラーとしてメッセージを出力して終了する。
A生年月日から今日の日付までを次の式により計算する。
(生年月日から今日の日付までの日数)=(西暦1年1月1日から今日までの日数)
-(西暦1年1月1日から生年月日までの日数)
B日数の計算および入力データの誤りの検出には、うるう年を考慮する。うるう年は次のように判定する。
4で割りきれかつ100で割りきれない年、および400で割りきれる年はうるう年である。
それではすばらしいプログラムを見てみよう。
#include <stdio.h>
#define leap(y) (y % 4 == 0 && y % 100 != 0 || y % 400 == 0)
typedef struct{
int year;
int month;
int day;
}TDATE;
long calcd(TDATE *);
int check(TDATE *);
int table[24] = {31,28,31,30,31,30,31,31,30,31,30,31,
31,29,31,30,31,30,31,31,30,31,30,31};
void main()
{
long total;
TDATE today, birthday;
printf("**
生年月日をスペースで区切って入力してください **\n");
scanf("%d %d %d",&birthday.year, &birthday.month,
&birthday.day);
printf("**
今日の日付をスペースで区切って入力してください **\n");
scanf("%d %d %d",&today.year, &today.month,
&today.day);
printf("-----Result-----\n");
if(check(&birthday) == 0 &&
check(&today) == 0){
if((total =
calcd(&today)-calcd(&birthday)) >= 0)
printf("%d年%d月%d日から今日までの日数は\n"
"%ld日です\n",birthday.year, birthday.month, birthday.day, total);
else
printf("***生年月日が今日の日付より後になっています***\n");
}
else
printf("***入力した年月日が正しい範囲にありません***\n");
}
/*西暦1年1月1日からの日数を計算する*/
long calcd(TDATE *dayp)
{
int i;
int offset;
long years = dayp->year - 1;
long days = dayp->day;
days += years * 365;
days += years/4 - years/100 + years/400; /*前年までのうるう年の合計を加算*/
if(leap(dayp->year))
offset = 12;
else
offset = 0;
for(i = 0 ; i < dayp->month - 1;
i++)
days += table[i + offset];
return days;
}
/*入力データの誤りを検出する*/
int check(TDATE *dayp)
{
int offset;
int dlimit;
if(leap(dayp->year))
offset = 12;
else
offset = 0;
dlimit = table[dayp->month + offset -1];
if(dayp->year < 1800 || dayp->month < 1 || dayp->month
> 12 ||
dayp->day < 1 || dayp->day >
dlimit)
return 1; /*年月日が正しい範囲にないとき、1を返す*/
else
return 0;
}
いかがだろうか、簡潔にしてよく出来たプログラムである。例によって赤色部分が穴埋め式になっている。
最初の赤字の部分で#define leap(y)の値を、次の()内の条件が成立するかどうかによって真か偽を判断する方法については勉強しなかったので、ここで覚えておいてほしい。
情報処理技術者試験は昔からこの手のうるう年が絡む問題が多く、かなり熟成された問題となっている。
しかし、頭を抱えるほどの難度はないだろう。
これくらいのプログラムを作れるようになりたいものである。
第2問 出力フォーマットに関する問題
次のテキストファイル(text.txt)を読み込んで、一定の行幅、ここでは30字にあわせて折り返し(改行)を行って出力するプログラムである。
なお、テキストファイルには空白文字や改行文字も含まれている。
またテキストファイルから改行文字を読み込んだときは、1行の出力文字数が30字の行幅に満たなくても折り返しを行う。
I use hands to hold my fellowmen I use hands to help with what I can, But
when a man slaughters fellowmen then I will change my hands into fists of fury.
From Fists of Fury
私の場合は、このテキストファイルのtext.txtを例によって、C:\Cというディレクトリに置いていることを前提としている。
それでは見てみよう。
#include <stdio.h>
#define WIDTH 30
void main()
{
FILE *fp;
int ch, pos = 0;
fp = fopen("C:\\C\\text.txt", "r");
while( (ch = fgetc(fp)) != EOF){
if( (pos >=
WIDTH) && (ch != '\n') ){
putchar('\n');
pos = 0;
}
putchar(ch);
pos = (ch == '\n') ? 0 : pos +1;
}
putchar('\n');
fclose(fp);
}
というプログラムで、赤字の部分が穴埋め式だ。そして問題は続く。
ところが、このプログラムでは空白が2個以上続いている場合でも、そのまま空白を続けて出力してしまうので、空白文字は1文字だけを出力するようにしたい。
そのためにはどうするかということで、次のプログラムを見よう。
#include <stdio.h>
#define WIDTH 30
void main()
{
FILE *fp;
int ch, pos = 0;
int bch = '\0';
fp = fopen("C:\\C\\text.txt", "r");
while( (ch = fgetc(fp)) != EOF){
if( (pos >= WIDTH) && (ch != '\n')
){
putchar('\n');
pos = 0;
}
if((ch != ' ') || (bch !=
' ')){
putchar(ch);
pos = (ch == '\n') ? 0
: pos +1;
}
bch = ch;
}
putchar('\n');
fclose(fp);
}
このように空白文字出力が1字になるように追加した赤字の部分が穴埋め式だ。
なぜか情報処理技術者試験の問題というやつは、いつも難しい言いまわしをしている。
私個人としては、そういった点もちょっと苦手である。
これは、私にC言語の実力と同様、国語力もないことを如実に物語っている。
さあ、本当にお疲れさまでした。以上をもって演習編も終了しました。もはや、ANSI C言語の実力は相当のものであると思われます。
演習編の最後に当たって、今から20年以上前、巷ではブルース・リーのカンフー映画が一世を風靡していた。そのときに私が京都市内の四条通りを歩いていると、四条烏丸付近に現在でもある「十字屋」さんというレコード屋からカッコイイ曲が流れていた。
思わず衝動買いしてしまったが、このレコードを私は今でも大切に持っている。
それは、ブルース・スリーが歌、ブルース・ワンが作詞、ブルース・ツーが作曲による
というすばらしいカッコイイ歌だった。それでは早速聞いてみよう。