与えられたファイルに含まれる各行を検査し、もっとも長い行に関する情報を 表示するプログラムを作成せよ。 表示する情報としては、行番号、長さおよび、その行そのものの3つとする。 ただし、一行の長さに関する制限はないものとする。
まず、必要なデータをまとめた構造体を定義する。 一行の長さに関する制限がないので、行そのもののデータを保持する領域は動的に確保する必要がある。
/* もっとも長い行に関する情報を保持する構造体 */
struct line_info
{
int line_no; /* 行番号 */
int length; /* 長さ */
char *line_data; /* 行そのもの */
};
行の長さに上限があるのであれば、たとえば次のようなプログラムを作成すれば良い。
/* ans13-1.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUFLEN (80)
/* もっとも長い行に関する情報を保持する構造体 */
struct line_info
{
int line_no; /* 行番号 */
int length; /* 長さ */
char *line_data; /* 行そのもの */
};
/* 引数に与えられたファイルを検査する */
int read_file (char *);
int main(int argc, char *argv[])
{
argc--;
while(argc) {
++argv;
read_file(*argv);
argc--;
}
return 0;
}
/* ファイル検査の本体 */
int read_file(char *filename)
{
FILE *fp;
int length;
char buf[BUFLEN];
int line_no = 0;
struct line_info longest_line_info;
/* 構造体の初期化 */
longest_line_info.line_no = 0;
longest_line_info.length = 0;
longest_line_info.line_data = NULL;
if (NULL == (fp = fopen(filename, "r"))) {
fprintf(stderr, "Cannot open file %s\n", filename);
return -1;
}
while (fgets(buf, sizeof(buf), fp)) {
line_no++; /* 今読み込んでいる行番号 */
length = strlen(buf); /* 読み込めた文字列の長さを求める */
if (longest_line_info.length < length) {
/* もっと長い行が現れた */
longest_line_info.line_no = line_no;
longest_line_info.length = length;
/* いままでにもっとも長かった行のデータは
* 不要となった
*/
free(longest_line_info.line_data);
/* 現在行の情報を保持する */
longest_line_info.line_data = strdup(buf);
}
}
/* 最長行に関する情報の表示 */
printf("ファイル %s の中に含まれるもっとも長い行は", filename);
printf("%d 行目にあり、長さは %d で、次のようなものでした。\n",
longest_line_info.line_no, longest_line_info.length);
printf("%s", longest_line_info.line_data);
return 0;
}
前回学習した malloc を使って1行読み込んだ処理を書き直してみると
...
while (fgets(buf, sizeof(buf), fp)) {
line_no++; /* 今読み込んでいる行番号 */
length = strlen(buf); /* 読み込めた文字列の長さを求める */
/* 読み込んだデータを保持するのに
* 必要なだけのメモリを確保する。
* 終端文字のために +1 が必要
*/
keep_line = malloc(length + 1);
if (NULL == keep_line) {
/* 必要なだけのメモリが確保できなかった */
fprintf(stderr, "Cannot allocate memory\n");
exit(1);
}
strcpy(keep_line, buf); /* 確保したメモリへコピー */
/* keep_line には、現在処理中の行全体が含まれている */
length = strlen(keep_line);
if (longest_line_info.length < length) {
/* もっと長い行が現れた */
longest_line_info.line_no = line_no;
longest_line_info.length = length;
/* いままでにもっとも長かった行のデータは
* 不要となった
*/
free(longest_line_info.line_data);
/* 現在行の情報を保持する */
longest_line_info.line_data = keep_line;
} else {
/* この行に関するデータは不要と判明したので
* 解放する
*/
free(keep_line);
}
}
...
と直せる。
行長に制限がなくなった場合は、fgets() で、行全体が読み込めない場合があることになる。
そのために、fgets で読み込んだ後、読み込んだバッファに改行文字があるかどうかを検査することになる。
存在すれば、行が読み込めた事になるが、存在しない場合は、さらに fgetsを用いてデータを読み込むという手順を繰り返す。
フローチャートで示すと、次のようになることになる。
必要なデータを待避することや、malloc して不要になった領域を free することを 忘れないように気をつけること。
上の図をみると、内側の loop は do〜while 文を用いた方が、すっきり書けることに気付くだろうか?
| 12日目 | 表紙 | 14日目 |