前書き
FizzBuzz問題をif文を使わないで解くコードをC言語で考えています。
コードは三項演算子を使う事にして、出力の形を
数値 (Fizz or Buzz or FizzBuzz or 空白)
にしたいと考えて、最初に以下のコードの考えました。
ver1
1 void FB(){ 2 for( int a = 1; a<50; a++){ 3 printf(a % 15 == 0 ? "%d Fizzbuzz\n",a : a % 3 == 0 ? "%d Fizz\n",a : a % 5 == 0 ? "%d Buzz\n",a : "%d\n",a); 4 } 5}
しかし実行すると以下のエラーが出ました。
error
1 warning: pointer/integer type mismatch in conditional expression
そこで三項演算子について調べていると以下のコードと似た記述をしているコードを見つけて書き直した所、実行できました。
ver2
1 printf(a % 15 == 0 ? "%d Fizzbuzz\n" : a % 3 == 0 ? "%d Fizz\n" : a % 5 == 0 ? "%d Buzz\n" : "%d\n",a);
本題
実行は出来たのですが最初のerrorについて理解しきれていません。
私は三項演算子の処理(条件)ごとに ,a を付ける必要があると考えていました。
条件 ? "%d Fizzbuzz\n",a : 処理(条件)
ですが実際は、一番最後の処理の後に ,a を付けるだけで他の処理にも反映が出来ました。
エラーが条件文側の問題である事までは理解したのですが、文法記述として何が問題だったのか理解できていないので、その部分を教えてもらえると嬉しいです。
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答6件
0
エラーerrorと警告warningは厳密に区別して下さい。エラーは文法に則っていないもの、警告は文法的に解釈は可能なんだけど正しくない可能性があるもの、です。
まず。
三項演算子ですが、
式1 ? 式2 : 式3
で、式1の値が0以外なら式2の「値」を、式1の値が0なら式3の「値」を返すものです。決して、ソースの文字上の処理などではなく、式を評価(計算)した結果を返すことに注意して下さい。これが勘違いその1。
次、カンマ演算子に絡んで、
"%d Buzz\n", a
であれば、,はカンマ演算子なので"%d Buzz"を評価して捨て、aを評価してそれを返します。では、
式1 ? 式2 : 式3 , 式4
はどのように評価されるでしょうか。式1 ? 式2 : 式3は三項演算子によって結びつきますので、これを式Aとすると
式A, 式4
という解釈になり、まず式Aを評価してその結果は捨てられ、式4の値が返ることになります。
プログラム中のa % 5 == 0 ? "%d Buzz\n", a : "%d\n", a
の部分に適用すると式Aの部分は
a % 5 == 0 ? "%d Buzz\n", a : "%d\n"
a%5が0であれば、"%d Buzz\n",a
でint型のaを返し、
a%5が0以外であれば、"%d\n"つまりcharへのポインタを返します。式の結果が、あるときは整数型、あるときはポインタを返すのは(Cではポインタの扱いに鷹揚で、暗黙に整数型に置き換えたりするのでエラーにはならないとしても)なんだか異常と考えられるでしょう。
これが「警告」の内容です。
投稿2020/03/11 15:06
編集2020/03/11 15:36総合スコア7703
0
演算子による演算の結果は式です。
「関数の引数2つ」というのは、式じゃないです。個々の引数が式です。
コードで言うと、
C
1x = "%d\n", a; 2printf(x);
で、
C
1printf("%d\n", a);
と同じ意味にはならないのは分かりますよね?(そもそもx
の型は??)
ということで、演算結果として2つの値を持つのは無理です。
お書きのコードは、"~~~",a
のカンマがカンマ演算子と見做されて、左側の値("~~~"
)が無視され、
C
1printf(a % 15 == 0 ? a : a % 3 == 0 ? a : a % 5 == 0 ? a : "%d\n", a);
と同じになります。で、a
と"%d\n"
の型が不一致なので警告が出ています。いずれにせよ、期待通りの結果にはなりません。
投稿2020/03/11 14:59
総合スコア85901
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
三項「演算子」は、あくまで「値」を計算するものです。printf関数は複数の値を引数として呼び出すことができますが、「値」とは、printf(A, B, ...)とあるうちの、AやBのことです。例えば、このAを、X ? Y : Zで置き換えると、printf(X ? Y : Z, B, ...)となります。もっと具体的にすると、printf(a % 2 == 0 ? "Even! %d" : "%d is Odd", B)とすれば、aが偶数ならprintf("Even! %d", B)と、奇数ならprintf("%d is Odd", B)と同じになります。三項演算子で、「表示する書式を」切り換えているわけです。「表示する書式と表示する値の組み合わせ」を切り換えるわけではないので、質問にあった、「,a」の記述が「三項演算子内では」不要なのです(三項演算子の終了後は必要)。
投稿2020/03/11 14:51
編集2020/03/11 14:56総合スコア1722
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
ベストアンサー
まだ解決していないので私も説明してみます。
一見なんでもないコンマ「,」が曲者です。関数の引数並びを区切るコンマ(printf("%d\n", num); のような)は見慣れていると思いますが、文法的にコンマ演算子と呼ぶコンマは馴染みが少ないと思います。
コンマ演算子と、その働き、どれがコンマ演算子なのか。この機会にコンマ演算子に慣れるために、まずは文字列反転コードで典型的な使い方を示します。
C
1#include <stdio.h> 2#include <string.h> 3int main(void) 4{ 5 char str[] = "1234567"; // この文字列を反転する 6 int a, b; char t; 7 printf("1: %s\n", str); // 反転する前 8 for (a = 0, b = strlen(str) - 1; a < b; ++a, --b) 9 t = str[a], str[a] = str[b], str[b] = t; // 交換 10 printf("2: %s\n", str); // 反転した後
for ( 初期化 ; 条件 ; 増分処理 ) 文 という構文で、
- a = 0, b = strlen(str) - 1 の2つの処理をまとめて「初期化」にする
- ++a, --b の2つの処理をまとめてひとつの「増分処理」にする
- t = str[a], str[a] = str[b], str[b] = t; をひとつの文にまとめる
これら4つのコンマはどれもコンマ演算子です。
上のコードに次の三行を加えます。
C
1 printf( "hello %s", "world(3)\n" ); // 引数を区切るカンマ 2 printf(("hello %s", "world(4)\n")); // カンマ演算子 3 printf( "ans = %d\n", (b = 2, b + 3)); // ans = 5
警告(warning)には目を瞑ってください。実行モジュールが作られ、実行できますから。実行すると表示結果は次の通りです。
plain
1hello world(3) ← 引数を区切るコンマ 2world(4) ← コンマ演算子 3ans = 5 ← ans = 5
「引数を区切るコンマ」行の表示は問題ないでしょう。「コンマ演算子」行の前に「ans = 5」行の方を考察します。
(b = 2, b + 3) というカッコの中に式が二つあり、順に実行した結果、カッコ全体の値は 5(= b + 3 = 2 + 3)となったのです。
即ち、printf("ans = %d\n", (b = 2, b + 3)); は printf("ans = %d\n", 5);
となったのですが、このことは**「式1, 式2」の値は「式2」**だということを意味します。
そのことから、カッコで囲んだ ("hello %s", "world(4)\n")
の値は、コンマの右側の "world(4)\n" となるので、
printf(("hello %s", "world(4)\n")) は printf("world(4)\n") と評価されたことが表示結果から読み取れます。
次に条件演算子(三項演算子、?:)。分かりやすくひとつだけ使ったコードを追加して試します。
C
1 for (a = 4; a <= 7; ++a) 2 printf(a == 6 ? "hello %s", "world(6)\n" : "%d\n", a);
実行すれば表示結果は次のようになるはずです。
plain
14 25 3world(6) 47
この表示結果から、上の printf() は
- a != 6 の場合、printf("%d\n", a); を実行した
- a == 6 の場合、printf("world(6)\n"); を実行した
ことがわかります。もっと踏み込んで言えば、a == 6 の時、
- printf( "hello %s", "world(6)\n" ); と評価したのではなく
- printf(("hello %s", "world(6)\n")); と評価したようだ
と推測できる表示結果です。つまり、そのコンマは、引数を区切るコンマではなく、コンマ演算子だったということ。
また、このことは条件演算子の「条件 ? 式A : 式B」という構造からも言えます。
「条件 ? 式A1 , 式A2 : 式B」と、コンマ演算子がある場合、
「式A1, 式A2」の部分はまとめてひとつの式とみなされるので、
「条件 ? ( 式A1 , 式A2 ) : 式B」のように扱われる、と考えられます。
念の為:同じ「,」でも「引数を区切るコンマ」と「コンマ演算子」は文法的に違う。区別できるようになりましょう。
まとめると、
- printf(a == 6 ? "hello %s", "world(6)\n" : "%d\n", a); は
- a == 6 の時、printf(("hello %s", "world(6)\n")); と評価され、
- 結果として printf("world(6)\n"); を実行した。
もうひとつ確認しておくと、
C
1 printf(a == 6 ? "hello %s", "world(6)\n": "%d\n", a);
で条件演算子とみなされたのは、
C
1 a == 6 ? "hello %s", "world(6)\n" : "%d\n"
の範囲です。
さらに、( 式A1 , 式A2 ) は結果として「式A2」の値が評価されるのだから、上のループは次と同じです。
C
1 for (a = 4; a <= 7; ++a) 2 printf(a == 6 ? "world(6)\n" : "%d\n", a);
ここまでくれば
三項演算子の処理(条件)ごとに ,a を付ける必要があると考えていました
は正しくなく、とてもマズイことを説明できます。次のコードを追加します。
C
1 printf(a > 0 ? "true %d\n", a : "false %d\n", a);
は、上の考察を応用すると、次のコードと同じです。
C
1 printf(a > 0 ? a : "false %d\n", a);
- a > 0 が偽なら printf("false %d\n", a); を実行する(これは問題ない)
- a > 0 が真なら printf(a, a); を実行する(大問題)
printf() の一番目の引数は書式文字列(のアドレス)なのに整数 a が渡されるので、コンパイラは
warning: pointer/integer type mismatch in conditional expression
という警告を出したのです。実行すればセグメンテーション・フォールトで異常終了します。
エラーが条件文側の問題である事までは理解した
まず、エラー(error)ではなく警告(warning)である、とツッコんでおきます。
警告は「条件式にポインタ/整数という型の不一致がある」と言ってますが、条件式それ自体の問題というよりも条件式の周辺に問題があると受け取れば良いと思います。その証拠に、同じ条件式でも次のコードに警告はでません。
C
1 printf(a > 0 ? "true %d\n" : "false %d\n", a);
たとえば、うっかりセミコロン「;」を書き忘れた時、コンパイラはその行ではなく、次の行にエラーがある、と表示することがあります。それと似たようなことではないかと。一般に、コンパイラが的確なエラー表示を出すことは難しいと聞きました。
投稿2020/03/13 10:05
総合スコア1382
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
退会済みユーザー
2020/03/13 14:44
0
3項演算子の ? と : の間の「"%d Fizzbuzz\n",a」は式であり
「,」は、2項演算子のコンマ演算子で、演算結果は右オペランドの a だけです。
ver1 の printf は次のコードと同じです。
printf(a % 15 == 0 ? a : a % 3 == 0 ? a : a % 5 == 0 ? a : "%d\n", a);
最後の「,」関数の引数の区切りです。
3項演算子の結果は、a または "%d\n" です。
"%d\n" は文字列で「charの配列」ですが、「charへのポインタ」に変換されます。
a の型は int。
だから、そのような型の不一致という警告(warning) が出るのです。
投稿2020/03/11 15:00
総合スコア8224
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/03/11 15:42 編集
2020/03/11 15:48