複数の文字列を1つの配列に格納するプログラム

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
まさ1941

複数の文字列を1つの配列に格納するプログラム

#1

投稿記事 by まさ1941 » 13年前

[1]
任意のテキストファイルの内容を1つの配列に保存するプログラムが作りたいです。
実際に作っているプログラムから、問題の部分を抜き出して質問させていただきます。
テキストファイルの内容は、半角英数字のみで、以下に示します("test_r.txt")。

abc
defg
hijkl
mnopqr
stuvwxyz
[EOF]

自分は、fgetsを使い1行ずつ配列に読み込ませようとしています。
ソースコード :

コード:

#include<stdio.h>

int main(void){
    
    FILE *fp1;
    char *fname1 = "test_r.txt";                       // 読み取りファイル名
    char buf[10];                                           // 読み取り時の一時保存用
    char *str[10];                                          // 読み取った文字列の保存用
    
    int i=0;                       // 繰り返し用
    
    //-----  処理開始  -----//
    fp1 = fopen((const char*)fname1, "r");
    
    if(fp1 == NULL){
        printf("'%s' を開けない\n", fname1);
        return(0);
    }
    
    
    while(fgets(buf, 85, fp1) != NULL){                 // 1行ずつ str に格納
        str[i] = buf;
        i++;
    }
    
    
    for(i = 0; i <= 4; i++){                       // str に格納された文字列を表示
        printf("%s", str[i]);
    }
    
    
    fclose(fp1);
    return(0);
}
上のプログラムをコンパイルしたところ、エラーメッセージはでませんでした。
コンソール画面で実行して、ファイルの内容を表示させたいのですが、
実行結果は、「stuvwxyz」が5行並ぶものとなりました。
修正箇所やそもそもやり方が適していない等、教えていただきたい。


[2] 環境  
OS : Windows 7 64bit,
コンパイラ名 : VC++ 2008EE (コマンドプロンプト)

[3]
現在、電気系の大学院生で、C言語は学部の授業で基礎を習った程度です。
ポインタを使って、行列計算のプログラムを作ったことはあるのですが、
いまいち、理解はできていません。

jay
記事: 314
登録日時: 14年前
住所: 大阪市
連絡を取る:

Re: 複数の文字列を1つの配列に格納するプログラム

#2

投稿記事 by jay » 13年前

う~ん、恐らくですが

str = buf;
この部分が問題かと思われます。

今回の場合bufという数値はchar型の配列bufの先頭のアドレスになっています
つまりstr[0]にもstr[1]にもstr[2]にもbufのアドレスが入ってる訳ですね(多分)

最後に

コード:

for(i = 0; i <= 4; i++){                       // str に格納された文字列を表示
        printf("%s", str[i]);
    }
とやっているので結果としてbufに残っている文字列(今回の場合stuvwxyz)が5回表示されているのでしょう


解決策としては、文字列を操作するときは文字列操作関数を使ってください
ヒントは文字列のアドレスを渡すのではなく文字をそのまま渡す。です
文字列操作関数についてはこちらを参照してくださいね
http://tkmakwins15.tuzikaze.com/contents/string1.htm
♪僕たちは まだ森の中 抜け出そう 陽のあたる場所へ

アバター
バグ
記事: 130
登録日時: 15年前
住所: 愛媛県
連絡を取る:

Re: 複数の文字列を1つの配列に格納するプログラム

#3

投稿記事 by バグ » 13年前

jayさんの書き込みに更にプラスということになりますが・・・
fgets関数は改行文字まで読み込んでしまいますので、そのまま連続して表示しようとしても改行されて表示されてしまいます。
ですので、fgetcとかで1文字ずつ読み込んでいき、改行文字だったら何もしない・・・みたいな方法のほうが楽なのではないかなぁ・・・と思うのですがいかがでしょうか?

コード:

#include<stdio.h>
#include <string.h>

#define BUFFER_SIZE	4096						// ファイルの最大文字数

int main(void)
{
	FILE* fp;									// ファイルポインタ
	const char* name = "test_r.txt";			// 読み取りファイル名
	int i = 0;									// 繰り返し用
	int tmp = 0;								// fgetcの戻り値格納用バッファ
	char str[BUFFER_SIZE];						// 読み取った文字列の保存用

	// バッファの初期化
	memset(str, '\0', sizeof(char) * BUFFER_SIZE);

	// 処理開始
	fp = fopen(name, "r");
	if (fp == NULL)
	{
		printf("'%s' を開けない\n", name);
		return 0;
	}

	// 読み取り
	while (1)
	{
		// 1文字ずつ読み込み
		tmp = fgetc(fp);

		// ファイル終端だったらループ終了
		if (tmp == EOF)
		{
			break;
		}

		// 改行文字だったら何もしない
		if (tmp == '\n')
		{
			continue;
		}

		// 文字列バッファへ値を格納
		str[i++] = (char)(tmp);
	}

	// 文字列表示
	printf("%s\n", str);

	// ファイルクローズ
	fclose(fp);

	return 0;
}

box
記事: 2002
登録日時: 14年前

Re: 複数の文字列を1つの配列に格納するプログラム

#4

投稿記事 by box » 13年前

まさ1941 さんが書きました:

コード:

    char buf[10];                                           // 読み取り時の一時保存用
この大きさがなぜ10で、
まさ1941 さんが書きました:

コード:

    while(fgets(buf, 85, fp1) != NULL){                 // 1行ずつ str に格納
この大きさがなぜ85なのか、論理的に説明してください。
バグのないプログラムはない。
プログラムは思ったとおりには動かない。書いたとおりに動く。

jay
記事: 314
登録日時: 14年前
住所: 大阪市
連絡を取る:

Re: 複数の文字列を1つの配列に格納するプログラム

#5

投稿記事 by jay » 13年前

バグ さんが書きました:fgetcとかで1文字ずつ読み込んでいき、改行文字だったら何もしない・・・みたいな方法のほうが楽なのではないかなぁ・・・と思うのですがいかがでしょうか?
僕もそのやり方の方がいいと思います(というか僕ならそうします)
改造する時も融通が効きそうですしね

でも、そのやり方は初心者には少々難しいと思います。
C言語の基礎と言っても、ファイル操作関数についてそこまで詳しく習うとは思えませんし(現にウチの学校でもそうでした)
まぁ、授業でどこまで教えるかなんてその学校によってまちまちでしょうし
理解できるなら少しでも利便性のある仕様にした方がいいのかも、ですね。
♪僕たちは まだ森の中 抜け出そう 陽のあたる場所へ

まさ1941

Re: 複数の文字列を1つの配列に格納するプログラム

#6

投稿記事 by まさ1941 » 13年前

皆様、ご回答ありがとうございます。

>>jay 様
strcpy 関数を利用してみます。

>>バグ 様
現在行いたい処理では、ファイルの内容をそのまま表示しようとしたので fgets 関数を使用しましたが、
読み込んだ文字列を操作する際には、fgetc 関数の方が便利かなと思いました。
貴重なご意見ありがとうございます。
ソースコードも参考にさせていただきます。

>>box 様
実は、このプログラムは元々作りたかったものの一部で、この部分でつまづいたので、
ひとまずこの処理ができるプログラムを作ろうと思いました。

変数 buf の「10」は、今回読み込みに使用したファイルの内容が全6行と分かっていたので、余裕をもたせて「10」にしました。
fgets 関数の引数は、元々作っていたプログラムで、対象となるテキストファイルの1行の文字数が最高で82字であり、
これも少し余裕をもたせて、「85」にしました。
以上の説明でよろしいでしょうか?

まさ1941

Re: 複数の文字列を1つの配列に格納するプログラム

#7

投稿記事 by まさ1941 » 13年前

No: 6 の書き込みの一部を訂正します。
fgets 関数の引数は、元々作っていたプログラムで、対象となるテキストファイルの1行の文字数が最高で82字であり、
これも少し余裕をもたせて、「85」にしました。
fgets 関数の引数の「85」は、元々作っていたプログラムで、対象となるテキストファイルの1行の文字数が最高で82字であり、
これも少し余裕をもたせて「85」にし、ソースコードをそのままコピーしたため、この数字になりました。

naohiro19
記事: 256
登録日時: 15年前
住所: 愛知県

Re: 複数の文字列を1つの配列に格納するプログラム

#8

投稿記事 by naohiro19 » 13年前

Wikipediaより。
fgets は gets や scanf と異なり改行文字も 1文字として数える。
このため scanf や gets を fgets に置き換える場合に改行文字を除去する処理を加えておかなければ元のコードと同一の動作となる保証は無い
実際に入力データをfopenやsystem等の関数のパラメータとして渡す場合では文字列は末尾に改行文字があるとエラーとなる。
最後に編集したユーザー naohiro19 on 2011年10月19日(水) 12:27 [ 編集 1 回目 ]

アバター
バグ
記事: 130
登録日時: 15年前
住所: 愛媛県
連絡を取る:

Re: 複数の文字列を1つの配列に格納するプログラム

#9

投稿記事 by バグ » 13年前

改行文字を無視する必要がないのであれば、fgetsで読み取った文字列をstrcatを使用して連結していけばいいと思いますよ。

コード:

#include <stdio.h>
#include <string.h>

#define STR_LEN		85							// 1行の最大文字数
#define BUF_SIZE	4096						// ファイル全体の最大文字数

int main(void)
{
	FILE* fp;									// ファイルポインタ
	const char* name = "test_r.txt";			// 読み取りファイル名
	int i = 0;									// 繰り返し用
	char line[STR_LEN] = {0, };					// fgetsで読み取った文字列が格納されるバッファ
	char str[BUF_SIZE] = {0, };					// ファイルの全ての文字列を格納するバッファ

	// 処理開始
	fp = fopen(name, "r");
	if (fp == NULL)
	{
		printf("'%s' を開けない\n", name);
		return 0;
	}

	// 読み取り
	while (fgets(line, STR_LEN, fp) != NULL)
	{
		// 文字列の連結
		strcat(str, line);
	}

	// 文字列表示
	printf("%s", str);

	// ファイルクローズ
	fclose(fp);
	return 0;
}




もし、どうしても、1行ずつchar*型の配列におさめる必要があるのならば、mallocで動的確保するくらいしか思いつかないなぁ・・・

コード:

#include <stdio.h>
#include <malloc.h>
#include <string.h>

#define STRING_SIZE		85						// 1行の最大文字数
#define FILE_SIZE		128						// ファイルの最大行数

int main(void)
{
	FILE* fp;									// ファイルポインタ
	const char* name = "test_r.txt";			// 読み取りファイル名
	int i = 0;									// 繰り返し用
	char tmp[STRING_SIZE];						// fgetsで読み取った文字列が格納されるバッファ
	char* str[FILE_SIZE];						// ファイルの全ての文字列を格納するバッファ

	// 処理開始
	fp = fopen(name, "r");
	if (fp == NULL)
	{
		printf("'%s' を開けない\n", name);
		return 0;
	}

	// バッファ初期化
	memset(str, NULL, sizeof(char*) * FILE_SIZE);

	// 読み取り
	for (i = 0; fgets(tmp, STRING_SIZE, fp) != NULL; ++i)
	{
		// 文字列格納用領域を動的確保
		str[i] = (char*)(malloc(sizeof(char) * strlen(tmp) + 1));

		// 文字列のコピー
		strcpy(str[i], tmp);
	}

	// 文字列表示
	for (i = 0; str[i] != NULL; ++i)
	{
		printf("%s", str[i]);
	}

	// ファイルクローズ
	fclose(fp);

	// 後始末
	for (i = 0; str[i] != NULL; ++i)
	{
		// 動的確保した領域の解放
		free(str[i]);
	}
	
	return 0;
}
最後に編集したユーザー バグ on 2011年10月19日(水) 09:37 [ 編集 1 回目 ]

non
記事: 1097
登録日時: 14年前

Re: 複数の文字列を1つの配列に格納するプログラム

#10

投稿記事 by non » 13年前

まさ1941 さんが書きました: 変数 buf の「10」は、今回読み込みに使用したファイルの内容が全6行と分かっていたので、余裕をもたせて「10」にしました。
fgets 関数の引数は、元々作っていたプログラムで、対象となるテキストファイルの1行の文字数が最高で82字であり、
これも少し余裕をもたせて、「85」にしました。
以上の説明でよろしいでしょうか?
それなら、間違ってます。
non

まさ1941

Re: 複数の文字列を1つの配列に格納するプログラム

#11

投稿記事 by まさ1941 » 13年前

ご回答ありがとうございます。
皆様のおかげで、ファイル内容をそのまま表示することはできました。

コード:

#include <stdio.h>
#include <string.h>

int main(void){
    
    FILE *fp1;
    char *fname1 = "test_r.txt";                        // 読み取りファイル名
    char buf[11] = {""};                                // 読み取り時の一時保存用
    char str[110] = {""};                               // 読み取った文字列の保存用
    
    int i=0;
    
    //-----  処理開始  -----//
    fp1 = fopen((const char*)fname1, "r");
    
    if(fp1 == NULL){
        printf("'%s' を開けない\n", fname1);
        return(0);
    }
    
    
    while(fgets(buf, 10, fp1) != NULL){                 // 1 行ずつ str に結合していく
        strcat(str, (const char*)buf);
    }
    
    printf("%s", str);                                  // str に格納された文字列を表示
    
    fclose(fp1);
    return(0);
}
一応、当初の要求は満たしたのですが、取り込んだ文字列を操作する際は使いにくいと思いますので、
まだまだ改良する必要があると思います。

>> バグ様
自分は、char 型の初期値を {""} としました。
No: 9 の1つ目のプラグラムの方で、char 型の変数の初期値を {0, } とされていますが、
こうすべきなのか、こうした方がいいのか、また、自分のものでは問題があるか、教えていただきたいです。

>> non 様
このプログラムでは、対象となるファイルの1行は 10 文字('\n' 含む)が最大とし、文字列の末尾の '\0' を入れて、変数 buf の大きさは「11」としました。
また、テキストの行は最大 10 行 とし、変数 str の大きさは「110」としました。

アバター
bitter_fox
記事: 607
登録日時: 14年前
住所: 大阪府

Re: 複数の文字列を1つの配列に格納するプログラム

#12

投稿記事 by bitter_fox » 13年前

まさ1941 さんが書きました:

コード:

    char buf[11] = {""};                                // 読み取り時の一時保存用
    char str[110] = {""};                               // 読み取った文字列の保存用
>> バグ様
自分は、char 型の初期値を {""} としました。
No: 9 の1つ目のプラグラムの方で、char 型の変数の初期値を {0, } とされていますが、
こうすべきなのか、こうした方がいいのか、また、自分のものでは問題があるか、教えていただきたいです。
問題はなく結果は同じですが若干意味が違います。
まずバグさんの{0, }は,以降の要素がないので{0}と解釈されchar型配列の先頭の要素を0、それ以降を初期値(0)で初期化します。
ですので、{5, 3}と書けば[5, 3, 0, ...]の様になります。また、{0}の{}を外して0とするとエラーになります。

コード:

char buf[11] = {0};    // OK
char buf[11] = {0, };  // OK
 
char buf[11] = {5, 3}; // OK
 
char buf[11] = 0;      // Error
一方{""}はchar型配列の先頭の要素を""で初期化するのではなく、{}が取れて解釈されchar buf[11] = "";と同じ意味になります。
また、{""}はバグさんのような書き方をするとエラーになります。(""を先頭の要素として配列を初期化するという意味にはならないので)

コード:

char buf[11] = {""};    // OK
char buf[11] = {"", };  // Error

char buf[11] = {"", 3}; // Error

char buf[11] = "";      // OK
また、'\0'(ヌル文字)を用いて次のように書くことも出来ます。

コード:

char buf[11] = {'\0'};
まさ1941 さんが書きました:

コード:

    char buf[11] = {""};                                // 読み取り時の一時保存用

    while(fgets(buf, 10, fp1) != NULL){                 // 1 行ずつ str に結合していく
        strcat(str, (const char*)buf);
    }
>> non 様
このプログラムでは、対象となるファイルの1行は 10 文字('\n' 含む)が最大とし、文字列の末尾の '\0' を入れて、変数 buf の大きさは「11」としました。
また、テキストの行は最大 10 行 とし、変数 str の大きさは「110」としました。
fgetsは(第二引数-1)バイト分読み込みますので10を指定した場合は9バイト読み込みます。('\0'除く)
ですので、ここはfgets(buf, 11, fp1)と書いても構いません。
http://ja.wikipedia.org/wiki/Fgets
http://ohmoriws1.ms.kagu.tus.ac.jp/1997 ... i/c04.html
オフトピック
連結すると'\0'は末尾だけに付与されるのでstrの大きさは101でも問題ないですが・・・
ちなみに、僕ならこんな感じにします。

コード:


#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define MAX_LINE 100 // 最大100行

int main()
{
	char **str_lines;
	char *file_name = "test.txt";
	FILE *fp = fopen(file_name, "r");
	int file_length;
	int line_count = 0;
	int len;
	int i;

	if (fp == NULL)
	{
		printf("file cannot open: %s\n", file_name);

		return -1;
	}

	// ファイルの総バイト数を調べる
	fseek(fp, 0, SEEK_END);
	file_length = ftell(fp);
	fseek(fp, 0, SEEK_SET);

	// ファイルが空ならエラー
	if (file_length == 0)
	{
		printf("file is empty: %s\n", file_name);

		return -2;
	}

	// MAX_LINE × file_lengthで確保する
	str_lines = (char**)calloc(MAX_LINE, sizeof(char*));
	for (i = 0; i < MAX_LINE; i++)
	{
		str_lines[i] = (char*)calloc(file_length + 1, sizeof(char)); // ヌル文字を考慮して+1
	}

	// 最大MAX_LINE行読み込む
	while (line_count < MAX_LINE && fgets(str_lines[line_count], file_length + 1, fp) != NULL)
	{
		len = strlen(str_lines[line_count]);
		if (str_lines[line_count][len - 1] == '\n') // 末尾の'\n'を除去
		{
			str_lines[line_count][len - 1] = '\0';
		}

		line_count++;
	}

	// 読み込んだ分を出力してみる
	for (i = 0; i < line_count; i++)
	{
		printf("%d : %s\n", i + 1, str_lines[i]);
	}

	// ちゃんと解放
	for (i = 0; i < MAX_LINE; i++)
	{
		free(str_lines[i]);
	}
	free(str_lines);

	fclose(fp);

	return 0;
}

main関数縛りのようなのでそれに準じましたがここまで来ると関数化したくなりますねw

まさ1941

Re: 複数の文字列を1つの配列に格納するプログラム

#13

投稿記事 by まさ1941 » 13年前

>> bitter_fox 様
[tab=30]ご丁寧に解説して戴いてありがとうございます。


当初の問題も解決し、その他の改善案も教えていただいたので、
質問を締め切らせていただきます。
皆様のご協力に感謝します。

閉鎖

“C言語何でも質問掲示板” へ戻る