いちばん失敗した人決定戦

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

int main(void){
char str[] = "str == NULL ? \"(NULL)\" : str";
char *p, *q;
int ch;
p = str;
for(;;){
for(q=p; !(*q=='?' || *q==':' || *q==0); q++) ;
ch = *q;
*q = 0;
printf("|%s|\n", p);
if(ch==0) break;
p = q+1;
}
}
のプログラマの細部にわたるポインタや文字列、アドレスなどわけがわからなくなってきました。
なぜアドレスを++するのに*q++ではなく、
q++なのか、p = q+1;は文字列の何を+しているのか、混乱しています。
どうか図を使って解説して頂けないでしょうか?

質問者からの補足コメント

  • なるほど、文字のアドレスを探して?:0を見つけたらそこまでを文字列にして、文字列の最後のNULLが入らないようにqを+1してポインタのpのアドレスに代入する。
    それを繰り返しているのですね!

      補足日時:2021/02/01 21:16
  • なるほど、strに入った文字列をポインタpのアドレスの先頭から入れて、forのq++により
    ポインタqのアドレスの先頭に入れた文字のアドレスを一つ一つ探して?:0を見つけたらそこまでを文字列にして、ポインタのpに(文字列の最初のアドレスから文字列の最後のアドレスを)に文字列を入れて保管。
    p=q+1はポインタpに文字列を保管する際に、文字列の最後のNULL('/0'?)が入らないようにqのアドレスを+1するために書いた。

    それを繰り返して、ポインタqの0 ('/0'?)が入っているアドレスになったら、ループを終了する。
    そして、保管されたポインタp(最初の文字列のアドレスから文字列の最後のアドレス)に入った文字列を表示する。

    と理解しました。正しいでしょうか?

      補足日時:2021/02/01 21:49
  • なるほど、strに入った文字列をポインタpのアドレスの先頭から入れて、forのq++により
    ポインタqのアドレスの先頭に入れた文字のアドレスを一つ一つ探して?:0を見つけたらそこまでを文字列にして、ポインタのp(文字列の最初のアドレスから文字列の最後のアドレス)に文字列を入れて保管。

      補足日時:2021/02/01 21:59
  • そして、
    p=q+1はポインタp(文字列の最初のアドレスから文字列の最後のアドレス)文字列を保管する際に、前の文字列の最後の文字のNULL(0)が入らないように、次の文字列の先頭の文字がポインタpに入るようにするためにforにより文字を探しているポインタqのアドレスを+1して、次の文字列の最初の文字がポインタpに入るために書いた。
    それを繰り返し、ポインタqの0 ('/0'?)が入っているアドレスになったら、ループを終了。そして、保管されたポインタp(最初の文字列のアドレスから文字列の最後のアドレス)に入った文字列を表示する。
    と理解しました。正しいでしょうか?

      補足日時:2021/02/01 22:00
  • あの、アドレスに入る。
    文字の'/0'と0は何が違うのですか?
    同じものなのでしょうか?

      補足日時:2021/02/01 22:02
  • 編集します。

    p=strより、ポインタpが持つアドレスが指す
    メモリに文字列が入る。
    forのq++により
    ポインタqが持つアドレスが指すメモリの先頭に入れた文字のアドレスはforにより一つ一つ+され、そのアドレスの対応したメモリを探してメモリに入った文字列の文字を?:0を排除しながら、排除されなかった文字列のポインタのみが
    p=qによりポインタqに持つアドレスが指すメモリに入った文字列がポインタpが持つアドレスが指すメモリ代入する。
    それによりアドレスは上書きされ、ポインタpの持つアドレスの指すメモリに文字列が入り保存される

      補足日時:2021/02/02 04:18
  • そして、
    p=q+1はポインタqが持つアドレスを+1したアドレスをポインタpに入れて、次の文字列がはいるメモリの最初のメモリとしてに書いた。
    それを繰り返し、ポインタqが持つアドレスが指すメモリがNULLになったらループを終了。そして、保管されたポインタpの持つアドレスに入ったメモリの文字列を表示する。
    と理解しました。正しいでしょうか?

      補足日時:2021/02/02 04:18

A 回答 (9件)

> p=strより、ポインタpが持つアドレスが指す


> メモリに文字列が入る。

うーん・・・。これ、書き方なんかなぁ。
・・・ちょっと待てよ。

まずね。

char str[] = "str == NULL ? \"(NULL)\" : str";

この時点でstrと言う配列は確保されてんのよ?文字配列をズバっと保有するだけのメモリは確保されてる。
貴方のPCがどれだけメモリ積んでるか知らんし、Cのコンパイラが・・・今のPCだと16GBくらい標準で積んでるのかな?いずれにせよ、その16GBと言う大きさのメモリの「どっか」(具体的な場所はコンパイラ任せなんで知らん)に

"str == NULL ? \"(NULL)\" : str"

と言う文字列を置けるだけの連続した記憶領域は確保してるわけです。
んでもう一回言うけど、

> p = str

ってのはポインタpに「strと言う連続した記憶領域の先頭のアドレス」を代入してる、のね。先頭よ。
いやらしく言うと「先っぽだけ」よ。先っぽだけ入れさせてもらってんのよ。
だからpで確保したメモリに文字列を入れた、わけじゃないよ?お分かりですか?

> ポインタqが持つアドレスが指すメモリの先頭に入れた文字のアドレスは

もはや日本語が大変な事になってるなぁ(笑)。いや、理解しようって頑張ってるのは分かるんだけど。
ただ、コード的には取り敢えずポインタqにはポインタpを代入してる、と。だから初期状態ではqは文字配列strの先頭のアドレスを指しています。そこだけ分かってたらOK。

> そのアドレスの対応したメモリを探してメモリに入った文字列の文字を?:0を排除しながら

排除・・・うーん、排除はしてないんじゃない?
もう一回確認しておきますが、単なる「中身の書き換え」です。
?も:も終端記号(NULL文字)でさえNULL文字で書き換えられています。

> 排除されなかった文字列のポインタのみが

この辺が怪しいんだよなぁ・・・・。
連続してるのはメモリ、記憶領域、だけどポインタが連続してるわけじゃない。
そもそも「文字列」って実体自体はポインタ(指し示すモノ)じゃなくって、むしろポインティ(指し示される側?)ですよ(そんな英語があるか知らんが・笑)。要するにメモリに「入ってる(実体は)数値」ですよね。

> p=qによりポインタqに持つアドレスが指すメモリに入った文字列がポインタpが持つアドレスが指すメモリ代入する。
> それによりアドレスは上書きされ、ポインタpの持つアドレスの指すメモリに文字列が入り保存される

だからこの辺が怪しいんだよなぁ・・・・・・。
うーむ・・・・・・。

> p=q+1はポインタqが持つアドレスを+1したアドレスをポインタpに入れて、次の文字列がはいるメモリの最初のメモリとしてに書いた。

恒常的にそうだ、って事じゃないですよ?
qが指してる場所に入ってる文字が'?'や':'だったトコ限定ですからね。
一般論じゃないです。

> それを繰り返し、ポインタqが持つアドレスが指すメモリがNULLになったらループを終了。

はい、ここはOK。

> そして、保管されたポインタpの持つアドレスに入ったメモリの文字列を表示する。

うーん、ここは・・・・どう言えばいいんだろ。
もう一回言うけど、ポインタpに結果文字列は入りませんよ?「保管された」がどこに正確にかかるかピンと来ませんが。

まぁなぁ、ポインタの動作を「日本語で説明しようとすると苦労する」のは分かるんだけど、正確に書こうとすればするほど日本語がメチャクチャになってくでしょ(笑)?
めんどくせぇんだよなぁ。

もう一回。
念の為に#4のコードを読んで、動かして下さい。

そしてもう一回言うけど

「文字配列を対象にしてる」

ってこたぁ、理解の仕方としては

「配列の添字(index-number)」

を対象として操作する、ってのと同じなんですよ。
言い換えると「ポインタは表記の問題」であって、やってるこたぁ事実上「配列相手の操作」と同じなんです。
分かります?そういう意味ではポインタ演算は別に「特殊じゃない」。

んで、このお題の構造ってのは、例えば

int hoge[] = {0, 1, 2, 3, 4, 5};

って配列があって、

「整数の配列hoge、の4番目の要素を9で置き換えよ」

ってのと全く同じなんです。
それをポインタで表現しろ、ってのとあまり変わりません。
本質は、所詮、配列に対する操作、なのです。
    • good
    • 0

'/0' と '\0' は違うものだし, C では '\0' と 0 は「見掛け以外全く同じ」だよね>#7.



C++ なら '\0' は char型なんだけど....
    • good
    • 0

> 文字の'/0'と0は何が違うのですか?


> 同じものなのでしょうか?

うーん、メモリ上は同じ、っつっていいかなぁ。
詳しくはASCIIコード表を見て欲しいんですが。

ASCIIコード表:
https://www.k-cube.co.jp/wakaba/server/ascii_cod …

見た通り、10進数で0が、'\0'、つまりNULL文字です。

ただね、もう一回原点に戻ると、int型とchar型ってのは「コンパイラが用意してくれるメモリの量」が違うわけですよ。メモリは塊で用意してくれるわけで、そういう意味で言うとchar型である'\0'とint型で用意されてる0とでは「器の大きさが違う」事がありえますよね。
また、よくある2byte文字なんかを使う為に制定されたwchar_t型とか、最近のchar16_t型とかchar32_t型でも原則的には「器が違う」と言う事ですねぇ。
だから無理矢理int型に0を代入は「可能」だけど、見た目で考えても、それと「意図としても」、どっちみちあまり明解じゃない事は書くべきじゃないですね。
「意図した事を意図したように」表現する方がbetterだと思いますよ。
    • good
    • 0

☓ p=q+1はポインタpに文字列を保管する際に



まあ、言い方なのかもしれませんが、一応。
「ポインタは文字列を保管」出来ません。あくまで配列の先頭のアドレスを記録しています。

ぶっちゃけた話、C言語は結構その辺「手抜きの実装」なんですよねぇ。
「気の利いた言語」なら「配列の大きさ」の情報をどっかに持ってる。
ただ、C言語の場合、単にむき出しのメモリがそこに転がっていて、「どっかのアドレス」しか基本ないんですよ。そしてそのアドレスを「先頭として解釈する」。
で、原則「配列の末尾」っつーのがどこなのかはサッパリ分かりません。何故なら厳密に言うと「配列」は存在せず、単にむき出しのメモリがそこに転がってる。だから「配列の大きさ」なんつーものが定義出来ない。「どこが配列のケツ」なのかは原則プログラマが管理せんとならない。だからループで「プログラマが把握してる筈の」範疇超えたりするとコア吐いたりするわけですね。
そういう貧弱な配列の中で、唯一どこがケツなのか分かるのが「文字配列」です。ケツは'\0'で判断される。

いずれにせよ、ポインタに記録されるのはこの場合、「配列の先頭」です。それ以外に記憶出来るような「素敵な能力」はC言語には存在しません。

> ポインタqの0 ('/0'?)が入っているアドレスになったら、ループを終了する。

ロジック上は元々のstrのケツに到達したら、って事ですけどね。
ただ、その辺の判定がメンド臭いんで、色んな小手先のテクニックが導入されてるわけです。
    • good
    • 0

もう一回書くけど、



> 文字のアドレスを探して?:0を見つけたらそこまでを文字列にして

正確に言うと

> 文字(?:0)を探して?:0を見つけたらそこを'\0'に書き換えてそこまでを文字列として

で、

> 文字列の最後のNULLが入らないようにqを+1してポインタのpのアドレスに代入する。

と言うより(strに挿入された)NULL文字の次の文字(q+1が指す実値)が次の文字列の先頭になるから、です。
    • good
    • 0

> forのq++により次の文字のアドレスを探して、p=q+1のpはポインタですが、


> なぜさらにqに+1して文字のアドレスを探さずているのでしょうか?

いや、その後、ループするでしょ?
また

for(q=p; !(*q=='?' || *q==':' || *q==0); q++) ;

が始まるから貴方が考えたような動作になります。

つまり、二回目のループの初期値は、(このプログラム上では)str内の'?'の次の文字、三回目のループの初期値は、同様にstr内の':'の次の文字、になります。

大体ね、「ロジックだけに」着目して書けば別にポインタがどーの、ってのは関係ないんですよ?
例えば、文字配列をフツーの配列のように考えて(もっとも、考えるもクソもその通りだから)、添字で中弄って言っても良い。

例:

#include <stdio.h>

int main(void) {
 char str[] = "str ==NULL ? \"(NULL)\" : str";
 int p, q;
 char ch;
 p = 0;
 while (1) {
  for (q = p; !(str[q] == '?' || str[q] == ':' || str[q] == '\0'); q++);
  ch = str[q];
  str[q] = '\0';
  printf("|");
  while (str[p] != '\0') {
   printf("%c", str[p]);
   p++;
  }
  printf("|\n");
  if (ch == '\0') break;
  p = q + 1;
 }
}

/* ここまで */

貴方がどの程度Cの実力があるかは知らんのですが、「ロジックだけに着目すれば」別にフツーに添字使って配列の中身を書き換えても良いのです。分かりやすい方を行えば良い。
(原則、「ポインタ演算の方がスピードが・・・」とか言う話が流布されてるが、それは「コンパイラによる」ってのが正しく、理論的に言うと、この例のように「必ずしもポインタを使わねばならない」と言う話は存在しない。分かりやすい方を選んで、結果その方がメインテナンス性はむしろ上がるだろう。)
まあ、出力がポインタ使った時よりも書く量増えてますが、そもそも、あんま出力は重要じゃないネタなんじゃないんですか?よく知らんけど。
(そもそも出力「だけ」考えるならこんなクソメンド臭いコード書かんで済むし)。
    • good
    • 1

あ、ごめん、タイポだわ。



> 8行目: ポインタchに文字配列strの先頭のアドレスを代入

ポインタpだわ。メンゴ。

いずれにせよ、さっき説明したのは希望に合わせて冗長にしたけど、概要をまとめると、

1. 文字'?'と':'を含むテキトーな文字配列strを準備する。
2. フラグchを作る。
3. ポインタpとポインタqを作る。
4. まずはポインタpをstrの先頭とする。ポインタq = ポインタpとする。従って、ポインタpもqもstrの先頭になってる。
4.5 ポインタp=ポインタqで結果、strの位置の「どこかを指してる」だよ?O.K.?
5. 文字配列str内の'?'ないしは':'の位置をシークする。具体的にはポインタqを使ってその位置を発見する。
6. フラグchにqの「実値」を代入する(この「qの実値」と言うのがC言語では「*q」と言うクソ表現になる)。chに'\0'が代入された時に計算を終了する。
7. この時点でのqが指す値、つまり文字配列上の'?'や':'を'\0'に書き換えていく、ってのがポイント。
8. メモリ上に配置された文字情報は、「'\0'」が入ってる時点で、そこまでを(フツーの、もっとマトモな言語での)「文字列」と判断する。従って、sで書式指定されたprintfは「'\0'」までを文字列として扱い出力する。
9. 現時点、qは'?'あるいは':'「だった」場所を指している。q+1は結局「その次の文字」を表してるので、pにその場所を指させる。4.5に戻る。

つまり、実はポイントとしては、

1. 連続したメモリがあって、中身がどうあろうと、ある地点で'\0'(ヌル文字)があった場合、C言語では、(あるアドレスから)そこまでをいわゆる「文字列」として扱う。
2. と言う事は、元々あった何らかの文字配列が一つあったとしても、一部の文字をヌル文字に書き換える事で、2つの文字配列や3つの文字配列と「任意の個数に分割する事が出来る」。

であって、C言語のポインタのクッソ分かりづらさはともかくとして、「配列の書き換え」が主眼で、あんまポインタそのものは関係ないんですよ。
クッソ分かりづらいコードを「わざと」書いてるのか、でどの道これ書いた人は死んだ方が良い、たぁ思ってますけどね。
こういう「読んだ人が理解しづらい」スタイルは、真似せん方が、どっちにせよ良いでしょう。
悪趣味です。
    • good
    • 1
この回答へのお礼

ありがとうございます。
forのq++により次の文字のアドレスを探して、p=q+1のpはポインタですが、
なぜさらにqに+1して文字のアドレスを探さずているのでしょうか?

お礼日時:2021/02/01 21:03

> プログラムの動きを一つ一つ細かく解説して頂けないでしょうか?



そんなの必要なのかなぁ。
っつーか、そもそもこのプログラム、わざわざ「分かりづらくするように」書いてない?
言い換えると、これ書いた人って、このまま「使う」のを前提にすると、「書くのがヘタなだけ」だと思うんですけどね。あるいはアタマがおかしいか。
教科書?だとしたらロクな本じゃないですよ。
その教科書は、貴方が「これを手本として」分かりづらいプログラムを書け、って事を言いたいのかしらん?
結果ロクな事にならん。むしろマネせん方がいい。

そして、こんなの日本語で解説すると冗長ですよ?

1行目: 標準入出力ヘッダ導入
2行目: 文字列ヘッダ導入(しかもこれは全く使ってない)
3行目:
4行目: メイン関数定義開始
5行目: 文字配列str定義。内容は'?'と':'を含む文字配列なだけで意味なし。
6行目: char型のポインタ、pとqを定義
7行目: 整数型でchを定義。これがまたロクでもない原因。
8行目: ポインタchに文字配列strの先頭のアドレスを代入
9行目: 事実上while(1) { と同じ。こんな書き方するのがまたもやロクでもない。
10行目: ポインタqにポインタpを代入してからループ開始。つまり、初期値はstrの先頭アドレスである。そして基本的にはポインタの値を増やしてるだけ。そしてポインタが指してる値(*q)、この場合、文字が'?'だったり':'だったり0だった場合ループ脱出。そしてこんな書き方がまず「ロクでもない」のだ。書いたヤツは死んだ方がいい。急に出てくる数値0は裏を返せばNULL文字の事である。文脈的には'\0'って書きゃエエのにそうしてない。やっぱり死んだ方が良い。
いずれにせよ「本体が無い」ループである。
11行目: chにポインタqが指す値を代入する。ここがまたロクでもない。整数値chにポインタqが指すもの、つまり文字を代入してるが、それはぶっちゃけ、ここで使用してるのがアスキー文字コードだ(数値になる)、と言う前提でやっている。「文字列がない」C言語だから出来るデタラメな方策だし、真似せんでエエ。
12行目: ポインタqが指す値に0を代入。これもクソ。ポインタqは文字データを受け付ける筈なのに数値0を代入してる。これは10行目にあったように、実はNULL文字'\0'になる。こういうコードの書き方は明解じゃないので避けるべきで、代入するのは表現としては'\0'にすべきだろう。もう一回書くけど、これ書いたヤツは死んだ方がいい。
いずれにせよ、ここで文字的には'?'ないしは'*'が終端文字であるヌル文字で「書き換えられ」、ここまでが「文字列」として判定される。要するに大元のstrが書き換えられてるのだ。
ポインタqが指す値、と言うのは要するに大元のstrのどっか、を直接指してるのだ。
もしだったら、プログラムのケツ、つまり全ての実行が終了した後の文字配列strの中身がどうなってるのか、出力してみて中身を確かめてみよう。
13行目: pは元々strを指してたが、12行目のせいで一部終端文字に書き換えられている。従って出力関数printfの書式指定sで認識される「文字列」は終端文字'\0'まで、なのだ。従って、元々のstrの一部だけ、が表示される。
14行目: chに終端文字が代入されてたらbreak。要するに9行目が作ったループから脱出する。
15行目: ポインタqは文字配列strに元々あった'?'か':'の位置を指している。ポインタpにそのqの次のアドレスを代入する。
16行目: ループ(10行目)終了。
17行目: メイン関数終了。
    • good
    • 1

> なぜアドレスを++するのに*q++ではなく、


> q++なの

「なぜ」じゃないです。
ポインタは、単に宣言する時は「*」を使いますが、
「宣言場面じゃない時」は、何も付かないのがポインタです。
そして、「ポインタが指してる値」を指す時に「*」を使う。
要するに、「宣言」時と「宣言してない時」では用法が真逆なのです。

C言語は設計上、どう考えてもポインタ周りはおかしな決定をしています。
だからC言語のポインタは分かりづらいのです
これは、イコール「ポインタが分かりづらい」って事ではありません。
単に「C言語の設計が狂ってるから、C言語に於いてのポインタが分かりづらい」だけです。
要するにC言語はバカな設計をしてるのです。

> どうか図を使って解説して頂けないでしょうか?

だから図を使っても意味ないですよ。
問題は貴方の理解力にあるのではなく、もう一度言いますが、根本的にCのポインタ周りの設計が狂ってるから分かりづらいだけ、です。
    • good
    • 1
この回答へのお礼

ありがとうございます。ちなみに、
プログラムの動きを一つ一つ細かく解説して頂けないでしょうか?
どうかよろしくお願いします。

お礼日時:2021/02/01 17:12

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!