質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

Q&A

解決済

7回答

8231閲覧

strtok等の文字列を分割する関数を用いずに文字を分割するプログラミングについての質問

itumaki

総合スコア12

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

0グッド

0クリップ

投稿2019/07/05 20:23

前提・実現したいこと

c言語についての質問です。 
文字配列 char text[]="It is good to see you. Thank you for coming."; を宣言し、
textの文字列を単語ことに分割して標準出力するというプログラムを作成しています。

ルールとしてスペースやピリオドは出力せず、出力する単語は一行に一単語ずつ '['と']'
で囲んで出力するというプログラムです。

また、単語間は必ず一文字分のスペースで区切られているという前提で作成してよいとする。
strtok等の文字列を分割する関数を用いずに作成する。外部プログラムの呼び出しもNG

という前提です。

発生している問題・エラーメッセージ

str[0] : [It ]
str[1] : [is ]
str[2] : [good ]
str[3] : [to ]
str[4] : [see ]
str[5] : [you. ]
str[6] : [Thank ]
str[7] : [you ]
str[8] : [for ]
str[9] : [coming.]
続行するには何かキーを押してください . . .

これは、プログラムを実行したときの実行結果です。

しかし、先生から スペースやピリオドを除いてください.ということでした。
いろいろサイトを見て試してみたのですがどうしても回答に行きつきません。 どうしたらよいでしょうか? 
初めての質問なので不慣れな点があるかと思いますが、よろしくお願いします

該当のソースコード

c言語
ソースコード#include<stdio.h># define _SPACE 0x20

int main(void)
{
int i;
char text[]="It is good to see you. Thank you for coming.";
char str[10][256+1];
char *s, *d;

s = text; for(i=0; i<10;i++) { d = str[i]; while ((*d++ = *s++) != _SPACE) { } *d = '\0'; } for(i=0; i<10; i++) { printf("str[%d] : [%s]\n",i,str[i]); }

}

試したこと

いろいろなサイトを見て、# define _SPACE 0x20を追加してみたもののうまくいきませんでした。

補足情報(FW/ツールのバージョンなど)

ここにより詳細な情報を記載してください。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答7

0

ベストアンサー

せっかくここまでやったのだから、出来るだけ活かしましょう。
一見してまず、区切りの空白が出力されちゃってるのが問題、かな。

C

1 while ((*d++ = *s++) != _SPACE) 2 { 3 } 4 *d = '\0'; 5}

ここ、一所懸命(そして地道に、端折らずに)考えてみて下さい。例えば冒頭のIt[空白]でどう動くかトレース。
whileのカッコ内で、

dすなわちstr[0][0]にsすなわちtext[0]の'I'をコピー
d++でdはstr[0][1]、s++でsはtext[1]を指すようになります。
で、'I'は_SPACEとは異なるのでループ継続して、

dすなわちstr[0][1]にsすなわちtext[1]の't'をコピー
d++でdはstr[0][2]、s++でsはtext[2]を指すようになります。
で、't'は_SPACEとは異なるのでループ継続して、

dすなわちstr[0][2]にsすなわちtext[2]の' 'をコピー
d++でdはstr[0][3]、s++でsはtext[3]を指すようになります。
で、' 'は_SPACEと等しいのでループを継続しません。

そして、whileループを抜けたところで*dすなわちstr[0][3]に'\0'を代入しています。
さて、ここでstr[0]はどうなっていますか。
'I' 't' ' '
空白を見つけてループを終了はしましたが、コピーしたものが入っています。これが、あなたの出力に空白が含まれてしまう原因。

対応の方法はいくつか考えられますが、例えば

C

1 while ((*d++ = *s++) != _SPACE) 2 { 3 } 4 *--d = '\0'; 5}

としてみるとか。whileを抜けたときにdは' 'の次の文字を指しているので、一つ戻して'\0'を上書きしています。あるいは、

C

1 while ((*d = *s++) != _SPACE) 2 { 3 d++; 4 } 5 *d = '\0'; 6}

とすれば、ループの中でdをインクリメントしますから、ループを抜けたときにはdは' 'を指しているので'\0'を上書きします。

つづいてピリオドも出力しないようにしましょう。
まず、

C

1#define __SPACE ' ' 2#define __PERIOD '.'

としましょうか。_+アルファベット大文字というシンボルは諸般の事情(C++では予約語とされる)で使わないほうがいいと思うのでちょっと変えます。また、空白なら' 'をそのまま記述すべきかと思います。だって、あなたが扱いたいのは' 'でしょ。0x20と書き換える意味はないじゃないですか。(ASCIIコードじゃない文字コードにも対応出来る、とも言えるし...超レアだけど)
続いて、今は空白を識別している場所を拡張します。

C

1 while ( (*s != __SPACE) && (*s != __PERIOD) ) 2 { 3 *d=*s; 4 d++; 5 s++; 6 } 7 *d='\0'; 8 s++;//sをインクリメントしておかないといけない。

比較を二度行うので、sのインクリメントをwhileの式中でやってしまうわけにはいきません。((*d = *s) != __SPACE) && (*s++ != __PERIOD) )とすれば二度目の比較のあとでのインクリメントになると考えるかもしれませんが、落とし穴があって...&&は、第一項が偽だと結果が決まってしまうので第二項を評価しないことになっています。そうなると、空白だったときとピリオドだったときとでインクリメントが行われる行われないの違いが出てしまいます。
これでどうなるか...

TEXT

1str[0] : [It] 2str[1] : [is] 3str[2] : [good] 4str[3] : [to] 5str[4] : [see] 6str[5] : [you] 7str[6] : [] 8str[7] : [Thank] 9str[8] : [you] 10str[9] : [for]

ちょっと惜しい感が...str[6]の[]がなんだかなぁ。で、最後のcomingが押し出されちゃった。原文では". "のところ。'.'で単語が終わって、さらに' 'で単語が終わるので、空の単語ができちゃったんですね。
じゃあ、' 'とか'.'がなくなるまで、単語の開始を送っておきましょう。

C

1 //空白と.の間は文字を送る 2 while( (*s==__SPACE) || (*s == __PERIOD)){ 3 s++; 4 } 5 //ここに来た時は空白や.ではない 6 while ( (*s != __SPACE) && (*s != __PERIOD) ) 7 { 8 *d=*s; 9 d++; 10 s++; 11 } 12 *d='\0'; 13 s++;

とりあえずこんなもので。
「表示」だけすればいいのだから、別に各々の単語を個別の文字列に格納する必要はない、とか与える文字列(単語数)が変わったら、とかいい出すと流石に根本から考え直しになるので、ここまで。(そういうのを考えてみるのはとてもいい勉強になりますけれど)

投稿2019/07/06 02:02

thkana

総合スコア7639

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

ビギナは仕方ないかも知れないが、
すべてのことをmain一本でやろうとするのは悪いクセだ。
必要な部品(関数)を考え、それを作って組み上げよ。

C

1#include <stdio.h> 2#include <stdbool.h> 3 4/* str が c を含むなら true を返す */ 5bool contains(const char* str, char c) { 6 while ( *str != '\0' ) { 7 if ( *str == c ) return true; 8 ++str; 9 } 10 return false; 11} 12 13/* str 中、delim に含まれない文字が見つかった位置を返す */ 14char* skip_delim(char* str, const char* delim) { 15 char* ptr; 16 for ( ptr = str; *ptr != '\0'; ++ptr ) { 17 if ( !contains(delim, *ptr) ) break; 18 } 19 return ptr; 20} 21 22/* str 中、delim に含まれる文字が見つかった位置を返す */ 23char* span_delim(char* str, const char* delim) { 24 char* ptr; 25 for ( ptr = str; *ptr != '\0'; ++ptr ) { 26 if ( contains(delim, *ptr) ) break; 27 } 28 return ptr; 29} 30 31/* 32 * 定義した skip_delim, span_delim を用いて単語を切り出す 33 */ 34int main() { 35 char text[] = "It is good to see you. Thank you for coming."; 36 const char* delim = " ."; /* 空白とピリオドが単語の区切り */ 37 38 char* end = text; 39 while ( *end != '\0' ) { 40 char* begin = skip_delim(end, delim); /* 単語はbeginから始まる*/ 41 if ( *begin == '\0' ) break; /* 終端に達したらおしまい */ 42 end = span_delim(begin, delim); /* end:単語の区切りを見つけ */ 43 *end = '\0'; /* そこに'\0'を置くことで文字列を終端すれば */ 44 printf("[%s]\n", begin); /* beginが切り出すべき単語(の先頭)となる */ 45 ++end; /* endの次から繰り返す */ 46 } 47 return 0; 48}

投稿2019/07/05 23:14

episteme

総合スコア16614

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

一文字ずつ...

c

1#include <stdio.h> 2#define FLAG_ON (-1) 3#define FLAG_OFF (0) 4int main(void) { 5 char text[]="It is good to see you. Thank you for coming."; 6 int count=0, inWord=FLAG_OFF; 7 for(char *p=text; *p!='\0'; p++) { 8 if(*p == ' ' || *p == '.') { 9 if(inWord) { printf("]\n"); inWord=FLAG_OFF; } 10 continue; 11 } 12 if(!inWord) { printf("str[%d] : [", count++); inWord=FLAG_ON; } 13 printf("%c", *p); 14 } 15}

投稿2019/07/06 17:42

jimbe

総合スコア12646

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

kazuma-s

2019/07/07 00:12

この text の場合、最後が "coming." と '.' で終わっているのでいいのですが、 "coming" のように '.' や ' ' で終わっていない場合、"]\n" が表示されません。 forループを抜けた後に、if (inWord) printf("]\n"); を追加しないといけないでしょう。
guest

0

前回回答者です。
>ルールとしてスペースやピリオドは出力せず、出力する単語は一行に一単語ずつ '['と']'
>で囲んで出力するというプログラムです。

「スペースやピリオドは出力せず」のところが、気になりました。
?とか#とか数字なども出力しないのでしょうか。

「1単語は、AZ又はazのみで構成される文字とし、それ以外の文字は、出力しない」ということであれば
以下のようにしてください。

C

1#include<stdio.h> 2# define _SPACE 0x20 3 4int main(void) 5{ 6 int i; 7 char text[]="It is good to see you? Thank you for coming."; 8 char *s, *d; 9 10 s = text; 11 d = s; 12 i = 0; 13 while(1) 14 { 15 if (*s == '\0') break; 16 if ((*s >= 'A' && *s <= 'Z') || (*s >= 'a' && *s <= 'z')) 17 { 18 s++; 19 }else{ 20 *s = '\0'; 21 if ( d != s ) 22 { 23 i++; 24 printf("[%d] : [%s]\n",i,d); 25 } 26 s++; 27 d = s; 28 } 29 } 30 if ( d != s ) 31 { 32 i++; 33 printf("[%d] : [%s]\n",i,d); 34 } 35} 36

投稿2019/07/06 01:29

tatsu99

総合スコア5438

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

raccy

2019/07/06 20:17

ASCIIまたはASCII互換な文字コードであることが前提であれば、A~Zとa~zがそれぞれ連続しているとしても構わないとは思いますが、IBMのサーバーのようにEBCDIC系を採用している環境ではうまく行かないのではないのでしょうか?
rubato6809

2019/07/06 21:59

???どこにいけばEBCDICを体験できますか? ここに質問するような人がEBCDICに遭遇する可能性があるんでしょうか?
raccy

2019/07/06 22:07

就職して配属された部署の仕事が昔のIBMのメインフレームの流れをそのまま引き継いだシステムで、文字コードがEBCDICだったというのはあり得ない話ではないと思いますよ。
rubato6809

2019/07/06 22:19

それがどれだけ可能性あることなのか、です。今の質問者の段階でEBCDICを考慮させることにどれだけ意味あるのか=余計な負荷をかけるだけじゃないのか?99%以上ASCIIで通用するなら、EBCDICなど一旦忘れて構わないんじゃないの?と思うわけです。
raccy

2019/07/06 22:33

それは「intは32bitです」というのと同じぐらい暴論だと思います。質問者はC言語それ自体を学びたいのでしょうか?それとも、ある環境では動くけど、ある環境では動かないかもしれない環境依存のC言語を学びたいのでしょうか? C言語は環境に依存した動作について覚えておかなくてはならないことが多くある言語です。文法は単純なのに、そういった所が他言語よりも難しくしている原因の一つになっていると思っています。初心者だからそれらを学ばなくても良いとするのは同意できません。初心者だからこそ、環境に依存していること、環境に依存していないことを区別して学んでおかないと、後から苦労するのはその人自身です。セキュリティ問題に直結するような知らなかったでは済まされないものも多くあります。 難しいから、負担になるから、しない・・・と言うのであれば、初めからC言語なんて学ぶべきではありません。
rubato6809

2019/07/06 22:59

私は学習し始めから<しばらくは> int = 32 bit 固定で全然構わないと思います。最初から何でもかんでも押しつけることは有害だと思います。
rubato6809

2019/07/06 23:01 編集

int のサイズが32bitだけじゃないことは比較的容易に触れることができますが、EBCDICを使う環境はかなりレアです。
raccy

2019/07/07 00:36

人によってはintが32bit環境ではないものに触れることの方がレアになる場合があると思います。少なくとも私の手元の環境ではintが32bitではない環境はありません(現役のPCはすべて64bitなのでWindows 16bitアプリ等を動かす方法がない)。結局基準なんてないし、自分が持っていないから大丈夫なんて私は言えません。 私は何でもかんでもEBCDICに対応しろと言いたいわけではありません。コードはそのままで、ただ一言「ASCII等のa-zやA-Zが連続する文字コードが前提です。」を付け加えるだけで十分だと思います。一番怖いのは、そうではない環境が存在することを知る機会を失い、どこかで重大なバグを引き起こすかも知れないということです。 もしかしたら、質問者さんが将来、とても有用なCライブラリの作成者になって、そのライブラリがa-zが連続する事を前提にしていた作りになっていて、EBCDIC環境で重大なバグを引き起こして文句を言われる、そんな未来が来ないと誰が保証できますか?
tatsu99

2019/07/07 00:54

このコードを書いた本人です。raccyさんが言われるのが、確かに正論なのですが、私は心情的には、rubato6809さんの立場をとります。 例えば、 int i; for ( i = 0; i < 100000; i++){ 何かの処理 } のようなコードを提示する時、intが16bitなら誤ったコードになりますが、「intは32bit以上であることが前提です」とその都度書く必要はない。 というのが、私の心情です。 高い意識の質問に対しては、高い意識で回答すべきですが、低い意識の質問に対しては、それに沿った回答で良いと考えています。 只、このたぐいの議論は宗教論争になるので、どちらが正しいときめる必要もないかと思います。 racyyさんが回答される場合は、raccyさんの判断で回答すれば良いですし、rubato6809さんが回答される場合は、rubato6809さんの判断で回答すれば良いかと。
rubato6809

2019/07/07 00:59

C言語の主戦場の一つは組込分野です。組込で使うCコンパイラにはintが32bitでないものがかなりの割合で存在します。当然私自身も経験しています。 逆に、ASCIIコードが通用しない環境が今どれだけあるのか知りたい程です。はっきり言えばIBMのメインフレーム以外にあるのか、です。EBCDICは特殊な環境です。そこに携わる頃までに知れば良いことです。EBCDICに拘ったり急ぐ必要性は、全く感じられません。
raccy

2019/07/07 01:53

> tetsu99さん tetsu99さんがわかっていながらわざと書かないという信念であれば、私からこれ以上何も言うことはありません。 > rubato6809さん rubato6809さんにとっては主戦場で、当たり前のように思っているかも知れませんが、他の人にとってもそうとはかぎりません。少なくとも私にとっては、組み込み系は、たぶん一生触れることがない、遙か遠い世界の出来事です。 私には、自分が知っている環境、自分が持っている環境だけを基準にすることはできません。だから、私は、C言語で仕様上確定していない全てのことについて注意を払うようにしています。 ただ、初心者にそのようなものをわざと教えないという信念を取るのであれば、あとは私からこれ以上言うことはありません。 --- ただ、私は、C言語に限らず、回答が環境等に依存する確定していない事柄を暗黙の前提としている場合、これからもコメントで指摘し続けるだけです。
rubato6809

2019/07/07 02:31

初心者にそのようなものをわざと教えない・・・それは曲解です。初心者であろうと、例えば「A..Zが連続することを前提にしてはいけないのは何故か?」と問われれば私だってEBCDICを例に挙げて答えます、そこが疑問のポイントだし、EBCDICしか実例が無いようだから。 質問者の疑問、あるいは学習の段階にそぐわないことにわざわざ言及しなくても良いと思います。一定のレベル以上になったら「全てのことについて注意を払うように」すべきでしょうが、初心者にそれを要求したら身動きが取れなくなります。親切が仇になりうるという事。
guest

0

char str[10][256+1];
としていますが、単語が10個であることを前提にしています。
もし、どうしても使いたいならchar str[1000][256+1];のように十分大きくとるべきです。
それなら単語1000個まで対応可能です。
又、for(i=0; i<10;i++)も、単語が10個であることを前提に作っています。
よって、strを使用しない方法にしました。
以下のようになります。

C

1#include<stdio.h> 2# define _SPACE 0x20 3 4int main(void) 5{ 6 int i; 7 char text[]="It is good to see you. Thank you for coming."; 8 char *s, *d; 9 10 s = text; 11 d = s; 12 i = 0; 13 while(1) 14 { 15 if (*s == '\0') break; 16 if (*s == _SPACE || *s == '.' ) 17 { 18 *s = '\0'; 19 if ( d != s ) 20 { 21 i++; 22 printf("[%d] : [%s]\n",i,d); 23 } 24 s++; 25 d = s; 26 }else{ 27 s++; 28 } 29 } 30 if ( d != s ) 31 { 32 i++; 33 printf("[%d] : [%s]\n",i,d); 34 } 35} 36

以下、実行結果です。
[1] : [It]
[2] : [is]
[3] : [good]
[4] : [to]
[5] : [see]
[6] : [you]
[7] : [Thank]
[8] : [you]
[9] : [for]
[10] : [coming]

投稿2019/07/06 00:01

編集2019/07/06 00:40
tatsu99

総合スコア5438

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

sscanf は使っていいのでしょうか?

C

1#include <stdio.h> 2 3int main(void) 4{ 5 char text[] = "It is good to see you. Thank you for coming."; 6 char str[256]; 7 int i = 0; 8 while (1) { 9 int n = 0; 10 sscanf(text + i, "%255[^a-zA-Z]%n", str, &n); 11 i += n; 12 if (sscanf(text + i, "%255[a-zA-Z]%n", str, &n) != 1) break; 13 i += n; 14 printf("[%s]\n", str); 15 } 16}

別解

やっぱり、sscanf は文字列を分割する関数になるかもしれないので、
別のやり方にします。

C

1#include <stdio.h> 2#include <ctype.h> 3 4int main(void) 5{ 6 char text[] = "It is good to see you. Thank you for coming"; 7 unsigned char c; 8 int n = 0, i = 0, j = 0; 9 for (; c = text[i]; i++) 10 if (isalpha(c)) !n && (n = 1, j = i); 11 else if (n) n = !printf("[%.*s]\n", i - j, text + j); 12 if (n) printf("[%.*s]\n", i - j, text + j); 13}

isalpha(c) の c は EOF 以外の負の値であってはならないので
unsigned char c; にしています。
n は、単語を表示しているので後で改行が必要ですというフラグです。

追記

出力する単語は一行に一単語ずつ '['と']' で囲んで出力する

この条件を満たしていなかったのでコードを修正しました。

投稿2019/07/05 21:38

編集2019/07/06 01:25
kazuma-s

総合スコア8224

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

時間がないのでヒントだけ。
単語を判定するには、ctype.hをincludeしてisalnumで判断しましょう。
ctype.h
「追記」・・・遅くなりましたがw

c

1#include <stdio.h> 2#include <ctype.h> 3 4int main(void) 5{ 6 const char text[] = "It is good to see you. Thank you for coming."; 7 // 8 const char *cptr = text; 9 // 10 char buf[128][32]; // 数字は適当 11 int index = 0; 12 while (*cptr) { 13 // 英字以外をスキップ 14 while (!isalpha(*cptr) && *cptr) { 15 cptr++; 16 } 17 // 終端の時は終了 18 if (*cptr == '\0') { 19 break; 20 } 21 // 英字だけをコピー 22 char *dptr = buf[index]; 23 while (isalpha(*cptr)) { 24 *dptr++ = *cptr++; 25 } 26 *dptr = '\0'; // 行末 27 index++; 28 } 29 // 30 for (int i = 0; i < index; i++) { 31 printf("str[%d] : [%s]\n", i, buf[i]); 32 } 33 // 34 return 0; 35}

実行結果

text

1usr ~/Project/test % ./a.out 2str[0] : [It] 3str[1] : [is] 4str[2] : [good] 5str[3] : [to] 6str[4] : [see] 7str[5] : [you] 8str[6] : [Thank] 9str[7] : [you] 10str[8] : [for] 11str[9] : [coming] 12usr ~/Project/test %

投稿2019/07/05 21:05

編集2019/07/06 19:45
cateye

総合スコア6851

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

cateye

2019/07/05 21:07

英字だけならisalpha()でもOK
cateye

2019/07/06 13:56

題意に沿うようにソース修正しました。
kazuma-s

2019/07/06 15:14

そのプログラムでは "coming" を buf に入れた後、 *cptr == '.' で、while (*cptr) { のループを継続しますが、 '.' をスキップした後、'\0' もスキップしてどんどん先に行きます。 cateyeさんの環境ではたまたま英字あり、その直後が '\0' だったので それを取り込み index が一つ多くなっているようです。 私の環境では、index が 12 や 15 になりました。
cateye

2019/07/06 20:43 編集

あちゃ・・・失礼しました・・・終端の判断が抜けていました。 ソース修正しました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問