現場での経験もあるPGなのですが、C言語の基礎を復習していたら疑問に思うことが出てきてしまったので、質問させて下さい。
printf関数などに使われる変換指定%sについてですが、
char word[] = "test";
char *pointer;
pointer = word;
とした場合、pointerの値は文字列wordの先頭アドレスになるので、
printf ("%x", pointer);
とすれば、そのアドレス値が表示されるのはとてもよくわかるのですが、
printf ("%s", pointer);
とした場合に、"test"と表示されるのがイマイチ納得できないんです。
printf ("%s", *pointer);
なら、まだわかるんですけど・・・
変換指定の%sというものは、
「アドレスを受け取って、受け取ったアドレスにある文字列を\0がくるまで表示する」
というものなのでしょうか?
int型のポインタで同じように
printf ("%d", pointer);
とすると、pointerの値であるアドレスが10進数表示されて、pointerが指している変数の値を表示するには、
printf ("%d", *pointer);
としなければならないわけで、そういうことをいろいろ考えていたら、収拾がつかなくなってしまって(^_^;)
「とにかく%sはそういうものなの!!」と丸暗記すれば困るようなことはないのですが、どうにもモヤモヤしっぱなしなので、%sの動きについて詳しくお分かりの方がいらっしゃいましたら、ご教授下さい。
よろしくお願いしますm(__)m
No.4ベストアンサー
- 回答日時:
C言語の規格上では。
%につづく変換指定子sは空白類でない文字の並びに相当し、
長さ修飾が存在しない場合、対応する実引数は文字型配列の
先頭要素へのポインタで無ければならない
配列内の終端ナル文字の直前まで書き込み精度が指定された場合、
精度を超える書き込みは行わない。精度が指定されない場合、
または精度が配列より大きい場合その配列はナル文字を含まなければ
ならないとされています。
つまり配列より小さい精度が指定されなければ受け取ったポインタを
文字配列の先頭要素とし\0が出てくるまで表示します。
No.9
- 回答日時:
> かなり特殊な変換指定だったんですね。
私の説明が分かりづらかったかもしれません。
「%sは、なんら特殊ではない」という結論に行き着いて欲しかったのですが・・・
char* という型は、正確には文字列を指し示すものではなく、文字を指し示すものです。
ですので、*pointer と記述してしまうと、「文字列」ではなくて「文字」になってしまうんですね。
つまり "test" の先頭文字である 't' です。
't' とだけ言われても、printf は "test" と表示することはもちろん出来ません。
> puts関数の自作はかなり難しそうな気がするんですけど、とりあえず頑張ってみます(^_^;)
まずは、「文字」「文字列」「配列」「ポインタ」について復習してみてください。
基礎的な関数ですので、自作できることよりも、仕組みを理解することが重要だと思います。
再度のご返答ありがとうございます。
No.8さんへのご返答にも書いたのですが、%s以外の変換指定がその値そのもののビットの並びをint型などに変換するのに対して、%sはその値のアドレスが指しているビットの並びを変換しているという部分が、少し疑問に感じてしまったんです。
いろいろとご教授くださってありがとうございました。
基礎的な部分にはなるのですが、これからも勉強してみます。
No.8
- 回答日時:
C言語で『文字列』はどう定義しますか?
・ここが理解できれば
『char word[] = "test";』
と
『char *pointer = word;』
が同じ文字列として扱えることが分かりますけど。
・『word』は char 型の配列ですが、配列名は実はアドレスを意味しています。
よって『pointer』が char 型のポインタ(アドレス)を持っていれば char 型の配列と
同じような扱いが出来るということです。
だから配列とポインタは別物でも似たような使い方が出来るのです。
例えば:
char word[] = "test";
char *pointer = word;
// 配列の場合
word[0]⇒'t'
word[1]⇒'e'
word[2]⇒'s'
word[3]⇒'t'
// ポインタの場合
pointer[0]⇒'t'
pointer[1]⇒'e'
pointer[2]⇒'s'
pointer[3]⇒'t'
上記の2つは同じようにアクセスできるのです。
なぜ、ポインタなのに [ ] 演算子でも配列と同じようにアクセスできるか分かりますか?
ここがポイントですね。これを理解するには文字列の表現と配列とポインタを十分に理解
しないと駄目でしょうな。
ちなみにポインタには
*(pointer + 0)⇒'t'
*(pointer + 1)⇒'e'
*(pointer + 2)⇒'s'
*(pointer + 3)⇒'t'
としてもアクセス出来ます。
これは記述こそ違いますが [ ] 演算子と同じことですから。
余談:
printf( "test[0] = '%c'\n", "test"[0] );
printf( "test[1] = '%c'\n", "test"[1] );
printf( "test[2] = '%c'\n", "test"[2] );
printf( "test[3] = '%c'\n", "test"[3] );
↑
この実行結果を簡単に予想できますか?
もし正しく予想できたらこの問題を質問しなくても良かったはずです。
最後に:
・『%s』は char 型配列を NULL 文字で終わる文字列だと仮定して処理します。
よって配列を受け取る
↓
アドレス(ポインタ)を受け取る
↓
文字列のポインタでも受け取れる(正しく)
↓
ちゃんと文字列として表示も出来る
となります。
決して『%s』が特別だったわけではありません。
重要なのは『配列名はアドレス(ポインタ)を意味する』を理解していなかっただけです。
・以上。
ご返答ありがとうございます。
配列とポインタについては理解していたつもりだったんですけど(^_^;)
ただ、他の変換指定、例えば%dなどは、その値(ビットの並び)をint型に変換するものなのに対して、%sはその値をアドレスだと認識して、そのアドレスにある値を\0が出現するまで文字列として変換するという、他の変換指定とは違う動きをしている部分が疑問に感じてしまって。
それが、Cの規格としてどのように定義されているのかを知りたかったんです。
わかりにくい質問ですみませんでしたm(__)m
No.7
- 回答日時:
>そもそも C に「文字列」なんてものは存在しない
文字列も一応規格に用語が以下のように定義されています。
最初のナル文字で終わり、かつそれを含む連続した文字の並びを「文字列」という。
No.6
- 回答日時:
って~か, そもそも C に「文字列」なんてものは存在しない (いや, 規格には「文字列リテラル」ってあるけど...). あれは「'\0' で終わる文字の配列」だ.
で, 「配列名」は一部の場面を除いて全て「その配列の先頭要素をさすポインタ」に変換される.
だから, pointer = word; を実行すると pointer の値は「配列 word の先頭要素のアドレス」になる.
で, printf("%s\n", word); の printf の第2引数は配列名なので「その配列の先頭要素をさすポインタ」に変換される. つまり「配列 word の先頭要素のアドレス」である.
一方, printf("%s\n", pointer); における printf の第2引数は pointer であるが, その値は (上の代入により) 「配列 word の先頭要素のアドレス」である.
だから, この 2つは同じ結果でなければならない.
ちなみにポインタの値を %p 以外で変換するのは未定義動作なので, 「突然鼻歌を歌いだす」「どこかの原発をメルトダウンさせる」ということがあったとしても規格は一切関知しない.
配列名がその配列の先頭アドレスを指すことまではわかっていたんですけど、「"%s"がそのアドレスの指す値から\0が出てくるまでを文字列にして表示する」なんて処理をやってくださっているというのが、はっきりわかっていなくて。
今まで何気なく使っていた%sさんに感謝することにします(^^ゞ
ご返答ありがとうございました。
No.5
- 回答日時:
#ANo.4です。
自分の書き込みで何が言いたいか分かりにくかったので補足。。。
char str[]= "test";
char* pointer;
pointer = str;
printf ("%s", *pointer);
・上記の場合の「*pointer」は既にC規格からみて
「文字型配列の先頭要素へのポインタ」にはならない。
・精度を指定すれば、\0まで表示されない事もある。
(精度とはprintf( "%10s", str)などの10のこと)
などなど。
規格として、「%sはアドレスを受け取るもの」だったんですね。
今まで何となく使っていたんですけど、こうしてはっきりわかると安心しました。
ご返答、ありがとうございました。
No.3
- 回答日時:
そういうものです。
ためしにご自分で puts 関数を作ってみればよくお分かり頂けると思います。
内部で使っていい関数は putchar だけとし、stdout に対して出力してみてください。
(*pointer) の型は char 型なので、引数は char 型にして見ましょう。
その場合、どのように二文字目以降を表示すればよいのでしょうか。
実現できない事がお分かり頂けると思います。
やっぱりそうですよね。
%sは参考書などでかなり最初のほうから出てきますし、特に説明もないのであまり深く考えていなかったんですけど、かなり特殊な変換指定だったんですね。
これからは意識して使ってみることにします。
puts関数の自作はかなり難しそうな気がするんですけど、とりあえず頑張ってみます(^_^;)
ご返答ありがとうございました。
お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!
似たような質問が見つかりました
- C言語・C++・C# C言語初心者 構造体 課題について 2 2023/03/10 19:48
- C言語・C++・C# 宣言する関数の形が決まっている状態で、 str1とstr2の文字列をこの順に引っ付けてstrに保存し 2 2022/05/30 18:21
- C言語・C++・C# Cのdoubleの浮動小数点表示について 3 2023/04/17 13:14
- C言語・C++・C# 至急教えてください。プログラミングの問題です。 最初に正の整数nの入力を受け付け、次に分数の分子と分 1 2022/07/19 17:03
- C言語・C++・C# 至急教えてください。プログラミングの問題です。 malloc関数を使ってください!お願いします! 最 1 2022/07/21 09:28
- C言語・C++・C# このプログラミング誰か教えてくれませんか 1 2022/06/02 15:27
- C言語・C++・C# 至急お願いします。プログラミングの問題です。 最初に正の整数nの入力を受け付け、次に分数の分子と分母 3 2022/07/19 17:09
- C言語・C++・C# C言語のエラーについて 2 2022/07/11 13:56
- C言語・C++・C# c言語 プログラムのエラー 1 2023/02/11 20:31
- C言語・C++・C# C言語 3 2022/10/04 15:07
このQ&Aを見た人はこんなQ&Aも見ています
関連するカテゴリからQ&Aを探す
おすすめ情報
このQ&Aを見た人がよく見るQ&A
デイリーランキングこのカテゴリの人気デイリーQ&Aランキング
-
配列を使わずに、変数名を動的...
-
C言語 配列の長さの上限
-
配列で格納したものをmsgboxで...
-
パスカルの三角形
-
VBAで配列からbmp画像を出力す...
-
配列の参照渡しで型が一致しま...
-
【C言語】配列の中に配列を入れ...
-
テキストファイルから文字列を...
-
配列をEraseしてもメモリが開放...
-
ExcelVBAで質問です。離れた二...
-
現在、C/C++ で作成したプログ...
-
C言語で特定列だけを抽出して配...
-
【速いブラインドタッチ】手を...
-
動的に作成した構造体配列の中...
-
配列を含む構造体の初期値について
-
構造体配列を引数とするDLL作成...
-
c言語での文字列の渡し方
-
プログラムが書けません。
-
C# Listを使わずに2次元配列の...
-
C# 配列の変数宣言について。
マンスリーランキングこのカテゴリの人気マンスリーQ&Aランキング
-
C言語 配列の長さの上限
-
配列を使わずに、変数名を動的...
-
先頭アドレスとは何ですか?
-
配列で格納したものをmsgboxで...
-
C# Listを使わずに2次元配列の...
-
C# 配列の変数宣言について。
-
テキストファイルから文字列を...
-
配列の参照渡しで型が一致しま...
-
ExcelVBAで質問です。離れた二...
-
unsigned char配列への入力の仕方
-
VBで構造体の配列を関数に渡す...
-
C++ vectorに配列をプッシュしたい
-
C言語で特定列だけを抽出して配...
-
Excel、VBAのユーザーフォーム...
-
複数の選択範囲の行番号を個別...
-
配列をEraseしてもメモリが開放...
-
配列を含む構造体の初期値について
-
【速いブラインドタッチ】手を...
-
C言語 配列の再初期化
-
Redimした動的配列はEraseする...
おすすめ情報