- 締切済み
文字列の扱い方
初歩的な質問ですみません… str文字列からcという文字を見つけたら添字を返すという関数を作ったのですが、 iにこの関数を代入して、if文の制御式にiを使って比較するまでは正常なのですが、 真文にiを使うと何故か偽文(という言い方でいいのでしょうか…この場合("そんな値はありません。"というところです)が実行されてしまいます。 よろしければご教授お願い致します。 #include <stdio.h> int str_char(const char str[],int c) { int len = strlen(str); int i; for (i = 0;i < len;i++) { if (str[i] == c) return i; } return -1; } int main() { char str[64] = "Fucking Brutal Death Metal"; int ch,i; printf("どの文字を調べますか?"); scanf("%c",&ch); i = str_char(str,ch); if (i >= 0) printf("その文字は%d番目にあります。",str_char(str,ch) + 1); //何故かiだと動かない else printf("そんな値はありません。"); return 0; }
- みんなの回答 (8)
- 専門家の回答
みんなの回答
- kmee
- ベストアンサー率55% (1857/3366)
例として、sizeof(int)=4 とします。 int ch とすることで、メモリのどこかにsizeof(int)を確保して、そこを使うことにします。 それを100番地とすると 100番地 ここから変数cの領域 101番地 ここは変数cの領域 102番地 ここは変数cの領域 103番地 ここは変数cの領域 例えるなら、 新人chが入ってきた。ロッカーを4つ分使いたいということなので、No.100~103を使うように指示した という感じ。 ここで、他の言語では「ロッカー」が綺麗に掃除されていたりするのですが、C言語では「空きロッカーの割り当て」をするだけです。 そのため、中に前に使った人の「ゴミ」が残っていることがあります。たまたま綺麗に掃除されていることもあります。 ch=0x41とすると、1バイト毎に分割されて 100番地 0x41 101番地 0 102番地 0 103番地 0 となります。 ※ ここでは、一旦そうしておきます。(後述) scanfでは、何を入れるか("%c")、どこに入れるか(&ch)を指示しています。 ここで、&ch でscanfに渡されるのは、100番地という最初のアドレスだけです。「そこから4つを使っている」という情報は渡されていません。 そこで、scanfは「%c」 から判断して「ロッカー一つに入れる」と解釈します。 入力した文字を100番に入れます。101から103は手付かずです。 scanfが文字コード0x41を読み込んだとすると 100番地 0x41 101番地 ごみ 102番地 ごみ 103番地 ごみ となってしまいます。 たまたま ごみが0だったら、 ch==0x41となりますが、そうでなければch!=0x41です。 str_charの中で、 文字と比較しようにも、chが「文字」を示していないので、「見つかりません」となります。 ch=0を入れると「正常」に動作する理由は、ごみを0にする効果があるからです。 printfによる直接的な違いはありません。 ただし、コンパイルによって生成される機械語は、大きく異なっている可能性があります。 その影響で、「ごみ」が0になりやすかったり、 ch に「ごみの無い領域」が割り当てられたりして、「正常」になっている可能性が考えられます。 さて、先程は、 ch=0x41とすると... と書きましたが、ここに「嘘」があります。 分割したときに、下のバイトから順番に並べるのを「リトルエンディアン」と呼びます。 おそらく、あなたが使っているであろう、IntelのCPU(やその互換CPU)を使ったWindowsでは、リトルエンディアンを採用しているので、上記のような動作をします。 しかし、逆に上のバイトから並べる「ビッグエンディアン」を採用している環境があります。 この場合 ch=0x41 は、メモリ上では 100番地 0 101番地 0 102番地 0 103番地 0x41 となります。 一方、scanf("%c",&ch)では、100番地に代入、ということに変化はありません。ch=0の後に実行しても 100番地 0x41 101番地 0 102番地 0 103番地 0 となり、 ch == 0x41000000 であって 0x41ではありません。 上で「正常」と書いてあるのは、このように「たまたま動作する」からであって、正しい手法では無いからです。
- 麻野 なぎ(@asano_nagi)
- ベストアンサー率35% (42/120)
すでに回答にあるように、ch をchar にするとか、getchar() を使うとか言うことは、確かに正しいことです。 ただし、気をつけなければならないのは、これは、 > printf("その文字は%d番目にあります。",i + 1); という話とは、(私自身が何か見落としてなければ)無関係だと言うことです。 int ch; で、 scanf("%c",&ch); という呼び方をした場合、scanf() は、2番目の引数は、char 型の変数をポイントするポインタだと考えます。 一般的な関数だと、「テンプレート」を使って、ある程度引数の不整合を検出できますが、printf() scanf() のような、「可変引数」の関数は、2番目以降の引数の型チェックができません。 なので、"%c" に対応するのが、&ch なら、これは、char 型のポインタだと考えます。 scanf() は、ch の位置にある、char の大きさの領域だけを変更します。 多くの場合、char より、int のほうがサイズが大きいので、intとしてみた場合、ch の中にscanf() で変更されない部分が残ります。 この状態で、 int str_char(const char str[],int c); の二番目の引数に、ch が渡されますが、いずれも、int 型なので、(scanf で変更されない部分を含めて)そのままコピーされて渡されます。 このあと、 if (str[i] == c) という比較が行われますが、これは、char と int の比較なので、str[i] は、いったん、int のサイズにされて比較されます。 このときに、(いわゆる「半角英数字」の範囲だと)、char と int の、サイズが一致しない部分は、0 で埋められます。 一方、scanf() で入力された ch は、scanf では変更されない「なにか」が入っています。 この部分が、たまたま、0なら、比較は成功しますし、それ以外のものが入っていたら、比較が失敗します。 ということで、本質的に、「運がよければうごく」というものではあるわけです。 だから、 > printf("その文字は%d番目にあります。",i + 1); としたかどうかには、関係ない気はします。 ただ、その他の事象(たとえば、str[] に十分な数を確保していなかったとか、変数の宣言の順序とか)で微妙な影響を受けることがあって、本当に、printf() の中身が違うだけかどうかは、疑問でした。 あと、デバッグモードだと、ご丁寧に、変数をゼロで初期化してくれるとかあった気もしますが。 一般的に、プログラムが動かないから、訳もわからずいじってみたら、動いてしまったというのは、本質を外していることが多いです。 「こうしたら動いた」というには、「他のところをいじってない」とか、「そうしたら必ず成功する(失敗する)」というチェックが必要です。 さて、おまけです。 なぜ、ch を char で宣言するれば動いたからと言えば、実は、scanf() の挙動だけが原因というわけではないです。 > int str_char(const char str[],int c); にたいして、 > i = str_char(str,ch); という呼び出しかたがされています。 こちらの方は、テンプレートが使えますので、ch が char だと、ここで、char → int の型変換が発生するので、足りないところが無事にゼロで埋められます(今回の場合) ch が int だと、ゴミも含めて、丸ごと代入されてしまうのです。 ですから、ch が int で、 int str_char(const char str[],char c); でも、この場合は大丈夫です。 (もちろん、scanf() "%c" に対応するのは、 char ですというのは、その通りです) あと、 ch = 0 で初期化するというは、前述した「scanf() で変更されない部分」を、あらかじめゼロにしておくと言うことですね。 最後に、 ch = getchar() の場合、getchar() の返値は int なので、int 型の変数に代入しても問題ありません。
- SONICLA
- ベストアンサー率100% (2/2)
他の方の言うように、手っ取り早いのは ch を 0 で初期化、もしくはchar型で宣言するのが良いでしょう。 scanfの特徴とも言えるかもしれませんが、%cではcharサイズ分の値更新になり、int型の領域全部を更新してはくれないためだと思います。 例えば、scanfではなく、ch = 'i'; などで値を入れてあげれば、期待通りになるはずです。 scanf関数は他にも妙な特性があるので、対話式プログラムを作成する際は、fgets(または1文字入力で十分ならgetchar)などを使用することをお勧めします。
- Tacosan
- ベストアンサー率23% (3656/15482)
scanf を使うなら「ch を char 型にする」のが唯一幸せになる方法なんですけどね. 0 クリアしても処理系によってはダメなので. あるいは scanf をやめて別の関数を使うとか. いずれにしても質問文は意味がわからないが.
お礼
自分で要点がよく分からないまま質問してしまった部分があります。ごめんなさい。 文章下手くそですよね。読み返して意味わからなくなりました。 下のお礼で言ってますが、main関数のif文内のprintfが printf("その文字は%d番目にあります。",i + 1); だと動かないのです。 i = str_char(str,ch);の次の行に printf("%d\n",i); を入力して実行すると、 どの文字を調べますか?B -1 そんな値はありません。 となります。 chをchar型にすると どの文字を調べますか?B 8 その文字は9番目にあります。 となり、正常に動きます。 この違いは一体何でしょうか。 処理系によってだめということは、ソフトが関係しているということですか?
- Picosoft
- ベストアンサー率70% (274/391)
問題の現象の本質からはズレているかもしれませんが、 気になったので。 とある処理系での実行例(デバッグ情報付き) http://ideone.com/Wpi3bQ chをchar型にするか、0で初期化しておくと幸せになれるかもしれません。
お礼
chを0で初期化したら、scanfでもきちんと実行できました。 ありがとうございます。 iの値も変わっていません。
- 麻野 なぎ(@asano_nagi)
- ベストアンサー率35% (42/120)
やはり、「具体的に動かないプログラム」が必要ですね。 とりえず、 printf("その文字は%d番目にあります。",str_char(str,ch) + 1); を printf("その文字は%d番目にあります。",i); で置き換えても、こちらでは、正常に動いているように見えますが。
お礼
printf("その文字は%d番目にあります。",i); で置き換えましたが、iの値がおかしいです。 下のお礼で詳しく書きましたが、i = str_char(str,ch); の次の行に printf("%d\n"i); と入力し、実行すると正常に実行できる場合とできない場合でiの値が変わります。 変えたのは回答者様も実行して頂いた printf("その文字は%d番目にあります。",/* この部分です。 */ + 1); printf("その文字は%d番目にあります。",i); で実行した場合、実行結果は どの文字を調べますか?B -1 そんな値はありません。 となりました。 ソフトのせいなのでしょうか。(使ってるのはVS2010です。)
- Tacosan
- ベストアンサー率23% (3656/15482)
これでは何をどうしたのかがさっぱりわからない. 具体的に「上手く動かない」場合のプログラムを書いてください.
お礼
main関数内のiにstr_char関数の返り値を入れ、main関数内のif関数の条件式にiを使うのは大丈夫なのですが、if文内のprintfを printf("その文字は%d番目にあります。",i + 1); とすると動かなくなります。 if内のprintfにiではなく printf("その文字は%d番目にあります。",str_char(str,ch) + 1); とする、質問に書いてあるとおりのプログラムだと動きます。 違いは一つだけなのですが、それだけで動かなくなる理由がよくわかりません。 試しにi = str_char(str,ch); のあとに printf("%d\n",i); を入れ、動くプログラムと正常に動かないプログラムを比較すると 動くプログラム(printf("その文字は%d番目にあります。",str_char(str,ch) + 1);とした時): どの文字を調べますか?B 8 その文字は9番目にあります。 正常に動かないプログラム(printf("その文字は%d番目にあります。",i + 1);とした時): どの文字を調べますか?B -1 そんな値はありません。 printfの中にiを使うかどうかだけで何故iの値が変わってしまうのでしょうか。
- hashioogi
- ベストアンサー率25% (102/404)
質問内容がよくわかりませんが、 私の翻訳環境(VS2008)では、 (1) #include <string.h>を必要とします。 (2) scanf("%c",&ch);で入力していますから、 int ch,i ; ではなく char ch ; int i; でないとまずいと思います。
お礼
main関数の中で、int型のiを定義して、その中にstr_char関数の返り値を入れるのですが、 main関数内のifの条件式にiを使って比較しても問題はないのにif文の中のprintfにiを使うとプログラムが動作しなくなります。 str_char関数は引数の中の文字列の長さを測る関数です。 説明ヘタクソで申し訳ないです。 なるほど、scanfではint型のchは読み込めないのですね。 ありがとうございます。
お礼
int ch;でも、ch = getcharにしたら正常に実行できました。 ありがとうございます。