プログラミング言語C 第2版(K&R)の内容について

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

プログラミング言語C 第2版(K&R)の内容について

#1

投稿記事 by cookierinon » 10年前

C言語初心者です。プログラミング言語C 第2版(K&R)の内容について質問させていただきます。
P38演習1-18 各入力行から、行の後のブランクやタブを取り除き、かつ空白行は全て削除するようなプログラムを書け。において、
(1)デバッグ時に\n(文末)がフと表示されてしまいます。
This is a pen.[空白orタブ][エンター(改行)]
と入力すると、
This is a pen.フ
修正箇所を教えていただけないでしょうか。

(2)『文字列配列の末尾に\0を挿入する』ためのif文内の条件が考え付かず、35行目に
if(newline == '\n')
と妥協案として書いてるのですが適切な条件を教えていただけないでしょうか。

(3)line[]内の空白タブを削除されたものをnewline[]に後ろから格納する際、
line[10]に空白があるとしてnewline[10]には何も格納されないわけですが、
newline[10]には『何も格納されていない』のか『\0で埋まる』のかどちらでしょうか。
環境はvisual studio 2010です。

コード:

/* 全ての行から末尾のブランクやタブを削除、空白行を削除 */
#include <stdio.h>
#define MAXLINE 1000
#define IN 1
#define OUT 0
#define START 2	/* statusをIN or OUTで初期化すると最初の\nの処理に失敗するので */

main(){
	int i, c;
	int status;	/* INで文の中、OUTで文の外 */
	char line[MAXLINE];
	char newline[MAXLINE];	/* 空白など削除後にline[]からこの配列に移し、こいつを出力 */

	printf("文章を入力してください。\n");
	printf("全ての行から末尾のブランクやタブを削除され、空白行も削除されます。\n");

	for(i=0; (c = getchar()) != EOF; i++)
		line[i] = c;

	status = START;
	for(i=MAXLINE; i >= 0; --i){
		
		if(line[i] == '\n' && status != OUT){	/* statusが IN or START なら\nを格納 */
			status = OUT;
			newline[i] = '\n';
		}
		else if(line[i] != '\n' && line[i] != ' ' && line[i] != '\t')
			status = IN;
		
		if(status == IN)
			newline[i] = line[i];
	}

	for(i=MAXLINE; i >= 0; --i){	/* 配列の末尾に\0を挿入 */
		if(newline[i] == '\n'){
			newline[i+1] = '\0';
			break;
		}
	}
	
	printf("%s\n", newline);
	return 0;
}

アバター
みけCAT
記事: 6734
登録日時: 15年前
住所: 千葉県
連絡を取る:

Re: プログラミング言語C 第2版(K&R)の内容について

#2

投稿記事 by みけCAT » 10年前

まず23行目、27行目、35行目などでline[MAXLINE]やnewline[MAXLINE]にアクセスしていますが、ここは確保されていないのでアクセスしてはいけません。
19行目でiに入力の長さが格納されているので、この情報を活用するといいでしょう。
また、main関数はmain()ではなくint main(void)と書いた方がいいでしょう。
cookierinon さんが書きました:P38演習1-18 各入力行から、行の後のブランクやタブを取り除き、かつ空白行は全て削除するようなプログラムを書け。において、
(1)デバッグ時に\n(文末)がフと表示されてしまいます。
This is a pen.[空白orタブ][エンター(改行)]
と入力すると、
This is a pen.フ
修正箇所を教えていただけないでしょうか。
例えば、newline全体を'\0'で初期化し、「行の後のブランクやタブを取り除き、かつ空白行は全て削除する」処理の後に途中の'\0'を詰める処理をするといいでしょう。
すなわち、処理後newlineを最初から見ていき、'\0'以外のデータを別の配列またはnewlineの先頭から格納していくといいでしょう。
文字列の長さは別途管理するといいでしょう。
途中の'\0'を詰める処理の例:

コード:

処理前         ↓これは途中の'\0'だから詰める ↓これは文字列終端として必要な'\0'
p    e    n    '\0' '\n' c    a    t    '\n' '\0'
処理後
p    e    n    '\n' c    a    t    '\n' '\0'
cookierinon さんが書きました:(2)『文字列配列の末尾に\0を挿入する』ためのif文内の条件が考え付かず、35行目に
if(newline == '\n')
と妥協案として書いてるのですが適切な条件を教えていただけないでしょうか。

添字が「途中の'\0'を詰めた後の文字列の長さ」の所に'\0'を格納するといいでしょう。

cookierinon さんが書きました:(3)line[]内の空白タブを削除されたものをnewline[]に後ろから格納する際、
line[10]に空白があるとしてnewline[10]には何も格納されないわけですが、
newline[10]には『何も格納されていない』のか『\0で埋まる』のかどちらでしょうか。

C言語では不定です。
cookierinonさんの環境では'フ'が格納されているようですね。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

cookierinon

Re: プログラミング言語C 第2版(K&R)の内容について

#3

投稿記事 by cookierinon » 10年前

ご回答ありがとうございます。質問以外の誤りの訂正にも感謝です。
みけCAT さんが書きました:まず23行目、27行目、35行目などでline[MAXLINE]やnewline[MAXLINE]にアクセスしていますが、ここは確保されていないのでアクセスしてはいけません。
『確保』というのは
for(i=0; i<MAXLINE; i++)
line = '\0';
のように『初期化』するという意味でしょうか?

また、
みけCAT さんが書きました: 19行目でiに入力の長さが格納されているので、この情報を活用するといいでしょう。

というのは、
文字数を格納する変数lengthを用意してgetchar()で入力するfor文から抜けた後にlengthに変数iを代入する、という解釈で合っていますか?

修正後のコードです。誤りはないでしょうか?

コード:

/* 全ての行から末尾のブランクやタブを削除、空白行を削除 */
#include <stdio.h>
#define MAXLINE 1000
#define IN 1
#define OUT 0
#define START 2	/* statusをIN or OUTで初期化すると最初の\nの処理に失敗するので */

int main(void){
	int i, j, c;
	int status;	/* INで文の中、OUTで文の外 */
	int length;	/* 入力した文章の文字数 */
	char line0[MAXLINE];	/* 入力した文章を格納 */
	char line1[MAXLINE];	/* 空白など削除後にline0[]からこの配列へ */
	char line2[MAXLINE];	/* line1[]から\0を取り除き、左詰めにしてこの配列へ */

	printf("文章を入力してください。\n");
	printf("全ての行から末尾のブランクやタブを削除され、空白行も削除されます。\n");

	for(i=0; (c = getchar()) != EOF; i++)
		line0[i] = c;
	length = i;	/* 入力した文章の文字数を格納 */

	for(i=0; i < length; i++)	/* 文字数分\0で初期化 */
		line1[i] = '\0';

	status = START;
	for(i=length; i >= 0; --i){
		
		if(line0[i] == '\n' && status != OUT){	/* statusが IN or START なら\nを格納 */
			status = OUT;
			line1[i] = '\n';
		}
		else if(line0[i] != '\n' && line0[i] != ' ' && line0[i] != '\t')
			status = IN;
		
		if(status == IN)
			line1[i] = line0[i];
	}

	j = 0;
	for(i=0; i < length; i++){
		if(line1[i] == '\0')
			;
		else{
			line2[j] = line1[i];
			++j;
		}
	}
	line2[j] = '\0';
			
	printf("%s\n", line2);
	return 0;
}

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

Re: プログラミング言語C 第2版(K&R)の内容について

#4

投稿記事 by box » 10年前

cookierinon さんが書きました: 『確保』というのは
for(i=0; i<MAXLINE; i++)
line = '\0';
のように『初期化』するという意味でしょうか?

そういうことではなさそうです。

コード:

    char line[MAXLINE];
と定義したとき、正しくアクセスできるのはline[0]~line[MAXLINE - 1]までのMAXLINE個です。
line[MAXLINE]という要素は、配列の定義範囲外です。「範囲外」であることを別の言い方で
「line[MAXLINE]という場所は確保していない」と表現されたのでしょう。

なお、その配列を本当に初期化したければ、そのfor文でもいいですが、定義時に

コード:

    char line[MAXLINE] = { '\0' };
と書く方が簡単であると思います。
バグのないプログラムはない。
プログラムは思ったとおりには動かない。書いたとおりに動く。

アバター
みけCAT
記事: 6734
登録日時: 15年前
住所: 千葉県
連絡を取る:

Re: プログラミング言語C 第2版(K&R)の内容について

#5

投稿記事 by みけCAT » 10年前

cookierinon さんが書きました:
みけCAT さんが書きました:まず23行目、27行目、35行目などでline[MAXLINE]やnewline[MAXLINE]にアクセスしていますが、ここは確保されていないのでアクセスしてはいけません。
『確保』というのは
for(i=0; i<MAXLINE; i++)
line = '\0';
のように『初期化』するという意味でしょうか?

いいえ。
box さんが書きました:

コード:

    char line[MAXLINE];
と定義したとき、正しくアクセスできるのはline[0]~line[MAXLINE - 1]までのMAXLINE個です。
line[MAXLINE]という要素は、配列の定義範囲外です。「範囲外」であることを別の言い方で
「line[MAXLINE]という場所は確保していない」と表現されたのでしょう。
ということです。
cookierinon さんが書きました:また、
みけCAT さんが書きました: 19行目でiに入力の長さが格納されているので、この情報を活用するといいでしょう。
というのは、
文字数を格納する変数lengthを用意してgetchar()で入力するfor文から抜けた後にlengthに変数iを代入する、という解釈で合っていますか?
そういう実装でいいと思います。
cookierinon さんが書きました:修正後のコードです。誤りはないでしょうか?
惜しいです。
・27行目で、lengthではなくlength-1から始めたほうがいいでしょう。
・19行目で、入力の長さがMAXLINE以上になっていないという条件も入れたほうがより安全です。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

cookierinon

Re: プログラミング言語C 第2版(K&R)の内容について

#6

投稿記事 by cookierinon » 10年前

みけcatさん、boxさん、回答ありがとうございます。
理解、解決いたしました。

cookierinon

Re: プログラミング言語C 第2版(K&R)の内容について

#7

投稿記事 by cookierinon » 10年前

訂正
みけCATさん、boxさん、回答ありがとうございます。
理解、解決いたしました。

かずま

Re: プログラミング言語C 第2版(K&R)の内容について

#8

投稿記事 by かずま » 10年前

別解です。分かりますか?

コード:

#include <stdio.h>

#define MAXBUF 1000

int main(void)
{
    char buf[MAXBUF];  /* buffer for spaces */
    int n = 0;         /* number of spaces in buf */
    int state = 0;     /* 0: no char in line, 1: char in line */
    int c;

    while ((c = getchar()) != EOF)
        if (c == '\n') {
            if (state != 0)
                putchar('\n');
            state = n = 0;
        }
        else if (c == ' ' || c == '\t') {
            if (n >= MAXBUF) {
                fprintf(stderr, "too many spaces\n");
                return 1;
            }
            buf[n++] = c;
        }
        else {
            int i;
            for (i = 0; i < n; i++)
                putchar(buf[i]);
            putchar(c);
            state = 1;
            n = 0;
        }
    return 0;
}

cookierinon

Re: プログラミング言語C 第2版(K&R)の内容について

#9

投稿記事 by cookierinon » 10年前

かずまさん、別解ありがとうございます。変数名のつけ方なども参考にさせていただきます。

要するに、
最後に配列をprintfで出力するため配列を3つも用意しなければならなかった
のを、
putchar関数とbuf[]を使ってスマートになったのがわかります。

しかし
a\n\ta
と入力するとしてトレースすると、行の後の\tが削除されず出力されてしまうと思うのですが27行目のfor文の前にstateで判断するif文が必要にならないでしょうか?

アバター
みけCAT
記事: 6734
登録日時: 15年前
住所: 千葉県
連絡を取る:

Re: プログラミング言語C 第2版(K&R)の内容について

#10

投稿記事 by みけCAT » 10年前

cookierinon さんが書きました:しかし
a\n\ta
と入力するとしてトレースすると、行の後の\tが削除されず出力されてしまうと思うのですが27行目のfor文の前にstateで判断するif文が必要にならないでしょうか?
この入力(ただし、\nを改行文字に、\tをタブ文字に変換した)に対し、cookierinonさんのコード(No: 3)とかずまさんのコード(No: 8)で同じ出力が得られました。
問題の仕様では行の前のブランクやタブについては言及されていないですし、
この入力には「行の後の\t」が含まれていないので、行の後の\tが削除されず出力されるのは当たり前だと思います。
何が問題なのでしょうか?

【訂正】
×同じ出力が得られました
○cookierinonさんのコードの出力の3行目以降とかずまさんのコードの出力の1行目以降が一致しました
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

cookierinon

Re: プログラミング言語C 第2版(K&R)の内容について

#11

投稿記事 by cookierinon » 10年前

自分の中で行の前と行の後がごっちゃになってしまっていました。
コードに誤りはありません、申し訳ありません。

みけCATさん、
ご指摘のおかげで気づけました、ありがとうございます。

閉鎖

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