最大1万円超分の電子書籍プレゼント♪

#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; } } において、 cahr型は文字や文字列、文字としての数字や数字の列を表す文字コードなどを数値として扱い、 char* pは文字や文字列や文字としての数字や数字の列の先頭のアドレスを数値として扱い、 *qは一つの文字や文字としての数字を文字コードなどを数値として扱う。そのため、整数などの値を扱うint型のchに数値として渡せるため ch = *q;と出来たのでしょうか? もう一つ、疑問があるのですが、なぜforの二周目以降のデバッグでのstr[]の中身はstr == NULLであるのに、一週目のstr[]の中身はstr == NULL ? \"(NULL)\": strであるかについてです。 過去にforの一週目のカッコの中身、すなわち(q = p; !(*q == '?' || *q == ':' || *q == 0); q++)のq++は働かないと聞きました。q++が働かなったためにqには0や値すらも入らないためp = q + 1;が動かず、一週目のデバッグのstr[]の中身はstr == NULL ? \"(NULL)\": strなのかなと考えてみたのですが、正しいでしょうか。

gooドクター

A 回答 (6件)

あー、ちなみに。


こう書き直した方がもっと明解かな。

/* ここから */

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

int main(void) {
 char str[] = "str==NULL?\"(NULL)\":str";
 char* p = str, *q;
 char ch = *p;
 while (ch != '\0') {
  q = p;
  while(!(*q == '?'|| *q == ':' || *q == '\0')) {
   q++;
  }
  ch = *q; /* 一旦ポインタ q が指し示してる値を保存する */
  *q = '\0';
  printf("|%s|\n", p);
  p = q + 1;
 }
 return EXIT_SUCCESS;
}

/* ここまで */

break; ってのは便利な脱出構文なんだけど、個人的な経験から言うと、(switch構文以外で)break; 使ってるプログラムってどっかで余計な事やってんだよな〜。もっとシンプルに書ける筈なのに、どっかで抜けた事やってる「サイン」なんだよ。
break; と continue; を多用しそうになってる、ないしは多用してる場合、一回ロジックを見直してみた方が良い、ってのが個人的な経験則です。
大体どっかでマヌケな事をやってます。
    • good
    • 0
この回答へのお礼

ありがとうございます。

お礼日時:2021/08/06 20:14

んでだな。

長くなったから分割するけど。
もうちょっと分かりやすく書き直すと件のコードは次のようになります。

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

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

んで、ループによってstrがどう変わっていくのか。
結論から言うと、

初期状態: str[] = "str==NULL?\"(NULL)\":str";
ループ1回目: str[] = "str==NULL\0\"(NULL)\":str";
ループ2回目: str[] = "str==NULL\0\"(NULL)\"\0str";

となっていってる。strと言う配列全体の長さは全く変わってないんだけど、?と:がヌル文字("\0")で置き換わっていってるだけ。このプログラムはこれ「だけ」がキモなの。
この置き換えはポインタqは最初ポインタpと同じ場所を指してるんだけど、内側のループでポインタqを進めていってstrの配列内の"?"と":"を探して"\0"で書き換えていってる。それだけ、なんだ。
それはここで行われている。

*q = '\0';(原作だと *q = 0;)

この前に*qの値は問題のchに保存されている。chに'\0'(原作だと0)が代入された時がオリジナルのstrの末尾に到達した、って事なんで、それで脱出してる、ってのが構造。
そして、printfの仕組みなんだけど・・・ホントは仕様書読んで欲しいんだけど、

「文字列が存在しないC言語だと0ないしは"\0"が登場した時、そこを便宜上文字列の末尾と解釈する」って約束事になってんのね。

C言語は数値しか扱えない、って事はいずれにせよ配列も結果数値の塊をいくつか突っ込んだ・・・英語で言えばsetになるんだろうけど、日本語だとどう言えばいいんだ?組とか塊か。まあそういったモンだよな。
数値の場合は中に0があっても問題はないんだけど、「文字列として解釈するなら」そこは終了を意味してる。つまりprintfには0と言う数値が「ここが終了地点だよ」と伝える役目があるわけ。
って事は、例えばループ1回目でstrが

str[] = "str==NULL\0\"(NULL)\":str";

ってなったとき、printfは末尾まで読み込まないんだな。途中の"\0"を見つけた時点で「そこまでを」文字列として解釈するわけ。残りの\"(NULL)\":str";は「そこに存在してても」無視するの。
2回目のループもそうだね。

p = q + 1;

でqが指してる位置(それまで"?"があった場所)の次の位置を指し直して以下同文、って事になる。そうすればポインタqが

str[] = "str==NULL\0\"(NULL)\":str";
q               ↑
p         ↑

と":"を指した時点で、そこを"\0"に書き換える。

str[] = "str==NULL\0\"(NULL)\"\0str";
q               ↑
p         ↑

そうすると、printfにポインタpを渡すと、今度は末尾として認識されるのはqが指してる"\0"の位置になる。よって文字列としてはpとqに挟まれた

\"(NULL)\"

しか認識されない、と。
だからどっちかっつーとprintfの仕組みがそのトリッキーさを支えているわけ。

分かりました?
    • good
    • 0

あとね。

いくつか追加しておく。

まず

1. Cが扱えるのは極論、数値だけ、である。

これ、前にも書いたんだけど、サッパリ分かってないようだからもう一回書いておきます。
C言語では基礎部分では極論数値しか扱えません。
以下、取り敢えず整数だけに話を限定しておきますが。
char、int、long、そしてポインタ型と言う「型」は極論、全部整数です。
取り敢えずポインタは外しておいても、char、int、long、と言うのは「扱うバイト数に差がある」だけで他は全部同じです。
ザックリ言うと

char -> 1バイトを上限とする整数
int -> 4バイトを上限とする整数
long -> 8バイトを上限をする整数

でどれも本体は整数であって、もう一回言うけど、C言語の言う基本的な「型」と言うのは、何バイトを纏めて扱うか、と言う事だけでそれ以上にお洒落な意味での「型」は存在しないのです。
じゃ、charは何なのか、と言うと出力側で「文字として解釈」してるだけ、です。本体はいずれにせよ数値の塊でしかありません。

2. C言語には文字列が無い。

文字が無い以上文字列なんかも存在しない、ってのは自明。
ぶっちゃけ、C系の言語で「文字列」が登場したのはJavaが最初なんじゃなかろうか。
気の利いたCの入門書で、「文字列」と言う表現の代わりに「文字配列」と言う表現を使用してるのはこれが理由。文字もねぇけどな!
どっちにしても、他の言語で言うトコのカッコイイ「文字列」はC言語には存在しない。

3. 結果、条件さえ許せば互いに代入や計算等が可能な場合が生じ、これを(狙ってやったかどうか知らんが)「弱い型付け」と呼ぶ。

「型」はある意味プログラマに取ってプログラミングをある程度ラクにしてくれる機能ではありますが(逆にややこしくなる方が多かったりする・笑)、意図してか意図せずか、お互いの型同士代入が出来たり、あるいは計算が出来たりする場合があります。
結果、必ずしも互いに排他的になる、とは限りません。こういうのを「弱い型け」と呼びます。意図してる場合もあるし設計にミスがある場合もある。
C言語の場合は、どっちかっつーと全体的に杜撰な設計になっていて(笑)、原則型のチェックなんかはプログラマ任せで何も行いません。気の利いたコンパイラが警告を出す、って程度ですね。
大きな理由は元々、C言語自体がアセンブリ言語の代替が出来れば良い、って程度の目的で最初実装されたので、数値丸出しでそれを弄り倒すのが前提で、型自体がプログラミング上、「何バイト用意せよ」って長い命令を書くのがメンド臭かったから用意されたに過ぎません(実はこれ以前に、全く型のないプログラミング言語を試しに作って使ってみたらしいんですが、これが使ってみると意外と大変だった模様)。

なお、よく、プログラミングを覚えるには「理論的である必要がある」とか言いますが、大嘘です。こんなん理論でも何でもない。
自然科学の場合、何か起きた時、現象を「理論的に解析する」頭脳が必要ですが、プログラミングの場合、必要なのは単に「他人の褌で相撲を取る」能力であって、そい言うのは論理性とは呼びません。
プログラミング言語は自然発生的に出てきた自然現象でも何でもなく、単に「人が作ったモノ」です。人が作ったモノである以上、そこに設計意図があるだけ。当然設計に間違いがある場合もある。
よってC言語で「弱い型付け」になってる部分に対して「何故?」とか考えるのは無意味なんですよ(笑)。それは単に「設計者が単にそう設計したから」ってだけです。これは大方のプログラミング言語におけて大体正しいのです。「何故?」の理由は「設計者がそう設計しただけだ」ってのが答えな場合が殆ど、ですね。
これに「おかしい」とか噛み付くのは・・・大方無意味ですね。逆に言うと、当然「強い型付け」のプログラミング言語も存在するんで、Cの「方式」が合わなかったら別の言語を選べば良いだけ、なのです。

余談ですが、C言語なら例えば1 + 2.0なんて計算はすぐやってくれます。・・・弱い型付けですしね。intとfloatを足して構わん、と。
ところが、例えば世の中にはOCamlと言う名前の言語があって・・・コイツはホントに「厳密な型検査」が好きで、上のような計算をしようとするとエラーを返しやがります。

(* This is an OCaml editor.
 Enter your program here and send it to the toplevel using the "Eval code"
 button. *)

1 + 2.0; ;;

Line 5, characters 4-7:
Error: This expression has type float but an expression was expected of type int

int型とfloat型なので足せません、と来たモンだ(笑)。
いや、プログラミング言語設計としては「正しい」かもしれんけど、さすがにこれだとメンド臭過ぎないか?とか思ったモンです。
まぁ、この様に、素のままだと全く型の違う数値同士だと「足す」とか「引く」事も出来ないプログラミング言語もこの世には存在する、のです(必ずしも「強い型付け」だから、ってワケじゃないですが)。
いずれにせよ、このように、「プログラミング言語設計者」が「何が正しく」「何が間違ってるのか」をプログラミング言語上で設定してるんで、あんま考え込んでもどの道意味がないんですよ(笑)。「この言語ではこれを是としてるのね」くらいに軽く考えるのが吉、です。
そして「C言語を学んだらハードウェアの動作が分かる」なんつー都市伝説を信用しないように。そんなこたぁ「あり得ません」。分かるのはC言語設計者の意図するトコロまで、です。

最後に大事な事を書いておきます。

4. C言語を苦手としている、ないしはC言語を学んでる最中なら、分かりづらいコードを使うな。読みやすいコードを読んで勉強しろ。

これは大原則です。生憎C言語は汚く書こうとしたらおっそろしく汚く書く事が可能な言語なんで、「分かりづらい」コードはいくらでも書く事が出来ます。国際邪悪なCコードコンテスト、なんつー大会があるくらいなんで。

国際邪悪なCコードコンテスト(IOCCC):
https://www.ioccc.org/

C言語の場合、可読性を重視する人より「俺って短く書けるんだぜ?へへへ」とか言う承認欲求の塊であるユーザーが一定数いるんですよね。んで大体短く書く利点より読みづらくなる欠点の方が大きかったりする。
んで、こういう人の場合「こう書いた方が実効性能が上がって・・・」とか言う言い訳をしたりするんですが、実際問題、「仕様書がそういう保証をしていない限り」そういう事を行うべきではないのです。大体、特定のコンパイラの実装がそうだった、ってだけの話で、別のコンパイラに持っていけばその「利点」は失われてしまう、とかそんなバッドノウハウばっかです。そのテの話は信用しちゃあいけません。
少なくとも、「早すぎる最適化より可読性を重視せよ」と言うのが現代的なセオリーです。最適化は一旦まずはプログラムを書き上げてからプロファイラを使ってボトルネックとなる場所を探した方が良い(クヌース「大体、ボトルネックは貴方が想定してなかった場所に存在する」)。
従って、貴方が読んで「分かりづらいな」と思ったコードは、別に貴方に理解力が無い為「分からない」とは限らんのです。逆に「分からんように」ワザと書いてる可能性がある。C言語ってそういうのが多いんですよ。悪いのはコードの方の可能性が高い。

例えば貴方が提示したコード。

int ch;

これが嫌ならchar ch;に書き換えなさい。むしろそっちの方が分かりやすい。
Cでcharは内部的には数値ですが、殊更文字を出力させるのが最終目的ならわざわざintを使う必要がないでしょう。これも「混乱させるのが目的」にしか思えない。

for(;;)

これもクソだと思う。出来なくはない。
でも可読性を大事にしてる気の利いた人なら、少なくともJISでの現行仕様上なら#include <stdbool.h>しておいて

while (true) [

で書きはじめる筈。同様の理由で、

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

なんてクソみたいな事もやらんでしょ。「出来る」のと可読性は必ずしも一致しない。

q = p;
while (!(*q == '?' || *q == ':' || *q == 0)) {
 q++;
}

って書いた方が見た目から意味はスッキリする筈。

とにかく、「読んでみて意味が分からん」コードは単に「意味が分かりづらいようにワザと書いてる」可能性が高い。
そんなの読んで勉強しよう、なんて考えないように。
圧倒的にバッドノウハウをワザと使って書いてる可能性が高いので、理解できたトコで百害あって一利なし、の可能性が高いですね。
    • good
    • 1
この回答へのお礼

ありがとうございます

お礼日時:2021/08/06 18:18

> forループの一週目だとはわかりませんか?



それを言うなら一"周"目だろ。
漢字が違う。

だから言ったでしょ?
まずは落ち着けって。
落ち着いて「自分が書いたモノをもう一回読み返してみろ」って。
それをやらんから、またそういうミスをするの。

人間ミスはするし、別にそれは責めないけど、質問文もそういうミスだらけだわ、また同じミスするわ、って言うと「慌ててる以上に」落ち着きがない、としか思えませんよ。
もう一度言う。
「まずは自分の書いた文章をもう一回読み返してみて、推敲してから」投稿しなさい。
じゃないと何言ってるんだかサッパリです。
    • good
    • 4

なんかどっかで見たことある質問だなあと思ったら、ベストアンサーもらってた。

※1か所だけ質問文が変わってますが。
https://oshiete.goo.ne.jp/qa/12485649.html

https://paiza.io/projects/YpNukdGtIpBc6c9zonKOdw
↑上記コードが質問文のと一緒なら、第一の疑問「ch = *q;」はCの言語仕様として出来ます。それが質問文で書いてあることと一緒かどうかは判断つかなかったです。

2番目の疑問は、paizaでの実行結果を見る感じ、質問文に書いてあるのは「正しい」とは思えませんでした。

cametan_42さんのアドバイスに従ってみては。
》もう一回、推敲して「何が言いたいんだか」自分で整理しなおして投稿した方が良いですよ。
    • good
    • 5

まずよ。



> #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; } } において

この状態を読んでくれ、ってのは間違ってない?

> cahr型

て何?
別にタイポは責めないんだけど、まずは落ち着け、っての。

> char* pは文字や文字列や文字としての数字や数字の列の先頭のアドレスを数値として扱い

アドレスは単に数値でしょ。

> *qは一つの文字や文字としての数字を文字コードなどを数値として扱う

宣言に使われてる*とその他で使われてる*は意味が違いますよ。
どっちの事言ってんの?

> 一週目

って何言ってんだろ。週刊少年ジャンプか何かの話?
いや、これもタイポだって知ってるけどさ。あまりに酷いだろ(笑)。
っつーか打ってる最中、慌てずに自分が何を書いてるのかもうちょっと良く読んでご覧よ。
こんな状態なら「何を言ってんだか分かんない」って事になるでしょ。

> デバッグ

一体いつデバッグの話になったんだろ?

もう一回、推敲して「何が言いたいんだか」自分で整理しなおして投稿した方が良いですよ。
じゃないとこりゃ酷い、としか言えません。
    • good
    • 4
この回答へのお礼

forループの一週目だとはわかりませんか?

お礼日時:2021/08/06 08:56

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

このQ&Aを見た人はこんなQ&Aも見ています

gooドクター

このQ&Aを見た人がよく見るQ&A

人気Q&Aランキング