dポイントプレゼントキャンペーン実施中!

C言語初心者です。現在入門的書籍の1冊目を読んで勉強中です。VBAは経験あります。

printf関数について質問です。

引数の指定で通常
 printf("書式文字列", 変数で値)
のようにしていしますよね。

 char a = 'A';
 printf("%c", a); ←ここで変数aの値の「A」を渡している
と理解しています。

上の例で、書式文字列を省略し、
 printf(a);
だとエラーがでます。

しかし、文字列の場合
 char a[] = "ABC";
 printf(a);
はエラーにならず、「ABC」と出力されます。

さらに、ポインタを使用して
 char a[] = "ABC";
 char *b = a;
 printf(b);
もエラーにならず、上と同様に「ABC」と出力されます。

まず、この2つの例で、エラーとならず、書式文字列が省略できているのが不思議です。

これが書式文字列を省略しているわけではないとするならば、
「printf(a)」=「printf(b)」=「printf("ABC")」ということになりますが、
「printf(a)」のaも「printf(b)」のbも"ABC"の先頭のアドレスを示しているんですよね。

ということは、printf関数の引数の指定方法は
 printf(書式文字列 または 文字列の先頭アドレス, 変数)
というように考えられるのですが、認識があっているでしょうか?

そもそも文字列について、VBAでは文字と文字列の区別はなにも意識せずに扱えたのでやや戸惑っているのですが、C言語では文字列の場合は宣言時に
 char a[]
のように宣言し、あとは先頭のアドレスで文字列を使っていくという感じなのでしょうか?

初心者なもので何が理解できていないのかもよくわからない状況で、質問がわかりづらいかもわかりませんが、よろしくお願いします。

A 回答 (6件)

厳密に言うと、Cには「文字列型」って無いんです。



char型は「文字コードが一つ入る大きさを持った整数」です。
'a'(シングルクオート)は「文字aのコードに相当する整数」です。

文字列は「char型へのポインタを先頭にして、以降『0』になるまでの間にあるchar型の数値の連続」を「文字列」として扱う、という決まりになっています。
"ABC"というのは 'A','B','C',0と連続したchar型の列への先頭アドレス、ということになります。

また、Cでは、ポインタと配列は同列に扱われます。

このあたりの振舞いや、配列とポインタについては、それだけで本があるくらい、Cでは重要なことです。しっかり理解するようにしましょう。


VBでは「文字」と「文字列」が区別が無い、とありますが、正確には「文字」は「長さ1の文字列」なので、違いが無いのも当りまえのことです。



さて、それを踏まえると

>  printf(書式文字列 または 文字列の先頭アドレス, 変数)

書式文字列も「文字列」ですから、指定する際にはその先頭アドレスを使用します。なので、printfは単に
printf(文字列の先頭アドレス[, ...])
となります。([, ...]は省略可能な値)

書式文字列では、 %で始まる変換書式以外はそのまま出力されます。よって、今回の場合はprintf("%s",b)とprintf(b)が同じ出力になります。

> char a = 'A';
> printf(a);

がエラーになるのは、aが数値であってポインタでは無いからです。

この回答への補足

丁寧な回答ありがとうございます。

>このあたりの振舞いや、配列とポインタについては、それだけで本があるくらい、Cでは重要なことです。しっかり理解するようにしましょう。

重要ポイントですね。がんばります。


>VBでは「文字」と「文字列」が区別が無い、とありますが、正確には「文字」は「長さ1の文字列」なので、違いが無いのも当りまえのことです。

そういうことなんですね。
C言語を勉強し始めて思ったのですが、VBのことにも理解が深まっていきます。

>> char a = 'A';
>> printf(a);
>
>がエラーになるのは、aが数値であってポインタでは無いからです。

わかりやすいです!

ここで、ちょっと疑問が出てきたのですが、
文字のポインタでprintfに渡すと、
 char a = 'A';
 printf(&a);
→「AフフフフH・」と出力されたのですが、これはどういうことなのでしょうか?

文字列の先頭アドレスがprintfに渡されるので無理矢理
>以降『0』になるまでの間・・・
に続いているアドレスに格納されていた文字が出力されたのでしょうか?

さらに、
 int a = 9;
 printf(&a);
ではいくつかの空白が出力され、
 int a = 47;
 printf(&a);
では「/」が出力されたのですが、

まず「9」については「9」というコードに相当する文字が空白で、移行「0」になるまで空白が格納されていた。

「47」については、「/」のコードが「47」で、すぐ後のアドレスに「0」があった。

ということでしょうか?

補足日時:2011/01/22 23:36
    • good
    • 0

>ということは、printf関数の引数の指定方法は


> printf(書式文字列 または 文字列の先頭アドレス, 変数)
>というように考えられるのですが、認識があっているでしょうか?
もうわかってらっしゃるかもしれませんが、これは違います。


char a[] = "ABC";
printf(a);
は、「ABC」と出力されますが、
char a[] = "A%sC";
printf(a);
だと、実行時エラーになるでしょう。

これが、
char a[] = "A%sC";
printf("%s",a);
だと、「A%sC」と出力されます。

ついでに、
char a[] = "A%sC";
printf(a,"X");
だと、「AXC」ですね。

つまり、第一引数は常に書式文字列ということですね。
    • good
    • 0
この回答へのお礼

titokaniさんありがとうございます。

>つまり、第一引数は常に書式文字列ということですね。

>printf(書式文字列 または 文字列の先頭アドレス, 変数)」
で「または」としているのがおかしいですね。
第一引数は「文字列」の先頭アドレスで、「文字列」の内容は書式文字列として指定する。
ですね。

「文字列」の先頭アドレスでないとコンパイルエラー
「文字列」内に%sなどのフォーマット指定子を指定した場合は、続けて対応する引数を指定しないと、実行時エラーになる。

とまとめてみたのですがいかがでしょう。

お礼日時:2011/01/24 13:10

余談ですが, 文字列リテラルは実際には無名配列になります. つまり


#include <stdio.h>
int main()
{
printf("Hello, world\n");
return 0;
}
と書いた場合, 実質的に
#include <stdio.h>
int main()
{
static const char _[] = "Hello, world\n";
printf(_);
return 0;
}
と等価 (ただし配列名は使えない) です. もちろん
#include <stdio.h>
int main()
{
static const char _[] = { 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '\n', '\0' };
printf(_);
return 0;
}
とも (ほぼ) 等価になります.
    • good
    • 0
この回答へのお礼

Tacosanさんありがとうございます。

なるほどですね。
変数としては使えない状況にあるが、メモリ上では同じように扱われるということですね。
理解が深まります。

お礼日時:2011/01/24 11:50

>  char a = 'A';


>  printf(&a);
> →「AフフフフH・」と出力されたのですが、これはどういうことなのでしょうか?

> 文字列の先頭アドレスがprintfに渡されるので無理矢理
>> 以降『0』になるまでの間・・・
> に続いているアドレスに格納されていた文字が出力されたのでしょうか?

そういうことです。
これもCの特徴なのですが
・明示しない限り、変数やメモリの初期化はしない。したがって、そこに何が書かれているかは保証されない。
今回はたまたまchar aの後にフフフHと読める領域が続いていたのでそう表示された
・配列とかポインタの範囲のチェックはしない
VBではdim a(10)としたら a(20)などではエラーになると思います。しかし、Cではa[10]としてもa[20]はコンパイルエラーになりません。実際に動作させても、たまたまエラーにならないこともあります。


> int a = 9;
> printf(&a);
> ではいくつかの空白が出力され、
> まず「9」については「9」というコードに相当する文字が空白で、移行「0」になるまで空白が格納されていた。

ポインタの「型」は、実体にアクセスするときに、何バイト取りだして、どんな処理をするか、を決めるためのものです。内部的にはただのアドレスであることが多いです。

ここからは、実行環境に依存する話です。
まず、intはsizeof(int)バイト分のメモリを使用します。intが32bitなら4バイトです。
その範囲でどのような順番に並んでいるか、も違います。現在主流のIntel系CPUだと、リトルエンディアンといて、下のバイトが先にくるように並んでいます。つまり、intの 9はメモリ上に
9,0,0,0
と4バイト並んでいることになります。
int型へのポインタの場合、その参照の際には4バイトをセットにして扱いますので、参照した値は「9」になります。
ところが、printfの第一引数では、const char *なので、先頭アドレスは同じでも、各バイト毎に扱うので、 長さ1の文字列で、1文字目のコードが9、と解釈されます。

ここで、やはり現在主流のASCIIコード互換の文字コードを考えると、 0~31は画面には文字を表示せずに特別な意味を持つコントロール文字と呼ばれるものになっています。
9はタブ、または水平タブと呼ばれるもので、現在のカーソル位置から「タブストップ」と呼ばれる位置(通常8文字単位)までカーソルを移動させます。画面上は空白がいくつか並んでいるように見えます。

> 「47」については、「/」のコードが「47」で、すぐ後のアドレスに「0」があった。

同様に
47,0,0,0
とメモリ上にあるので「長さ1の文字列で1文字目が「/」」と解釈されています。
    • good
    • 0
この回答へのお礼

すっきりしました!
実に明快な回答をありがとうございます!

>これもCの特徴なのですが
>・明示しない限り、変数やメモリの初期化はしない。したがって、そこに何が書かれているかは保証されない。
>今回はたまたまchar aの後にフフフHと読める領域が続いていたのでそう表示された
>・配列とかポインタの範囲のチェックはしない
>VBではdim a(10)としたら a(20)などではエラーになると思います。しかし、Cではa[10]としてもa[20]は
>コンパイルエラーになりません。実際に動作させても、たまたまエラーにならないこともあります。

この辺がCが難しくもあるが単純明快でもあるという理由なのかな?という感じがしました。

Cの魅力を垣間見れた気がします。
勉強を進めていきます!

お礼日時:2011/01/23 14:34

>printf(書式文字列 または 文字列の先頭アドレス, 変数)…認識があっているでしょうか?


合っていると言えば、合っています。
が、もっと正確に言えば、printf()で最初の引数は「文字列の先頭アドレス」であって、書式文字列も、それは文字列の先頭アドレスの1種に過ぎないのです。
(書式文字列の実体は別に領域がとられ、その書式文字列の先頭アドレスが引き渡される)

また、C言語では「文字列」であるかどうかが問題であって、そこに書式が定義されているかは問題ではありません。
つまり、
printf("%s");
などとすれば、コンパイルエラーとはなりませんが、実行時に大抵は異常終了します。

この回答への補足

みなさん早くに回答恐縮です。
ありがとうございます。

この問題はprintfの引数がどうたらというより、C言語における文字列の問題ですね・・・

>が、もっと正確に言えば、printf()で最初の引数は「文字列の先頭アドレス」であって、書式文字列も、それは文字列の先頭アドレスの1種に過ぎないのです。
>また、C言語では「文字列」であるかどうかが問題であって、そこに書式が定義されているかは問題ではありません。
わかってきたような気がします。

書式文字列 または 文字列の先頭アドレス・・・はどちらも同じで、文字列の先頭アドレスなんですね。

>つまり、
>printf("%s");
>などとすれば、コンパイルエラーとはなりませんが、実行時に大抵は異常終了します。
これは一応文字列が指定された形にはなっているのでコンパイル時にエラーとはならないが、
実行時に、実際何を%sにするの?何もないじゃん。ということでエラーとなり異常終了する。
ということでしょうか?

補足日時:2011/01/22 23:10
    • good
    • 0

> 「printf(a)」=「printf(b)」=「printf("ABC")」ということになりますが、



そうです。
つまりコンパイラには "ABC" という「文字列」がその先頭アドレスのように見えている、ということです。
printf() のプロトタイプ宣言を確認しましょう。
    • good
    • 0
この回答へのお礼

こんなに早く回答が頂けるとは!
ありがとうございます!

>> 「printf(a)」=「printf(b)」=「printf("ABC")」ということになりますが、
>
>そうです。
>つまりコンパイラには "ABC" という「文字列」がその先頭アドレスのように見えている、ということで。す。
なるほど、コンパイラは文字列を先頭アドレスで扱うんですね?

>printf() のプロトタイプ宣言を確認しましょう。
まだヘルプとかの見方が慣れなくて・・・勉強します。

お礼日時:2011/01/22 23:00

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