こんにちはゲストさん。会員登録(無料)して質問・回答してみよう!

解決済みの質問

自分が思った通りにコンパイルできません

#include<iostream>
using namespace std;
#define SIZE 10

class stack{
char stck[SIZE];
int tos;
char who;
public:
stack(char c);
void push(char ch);
char pop();
};

stack::stack(char c){
tos=0;
who=c;
cout<<"生成スタック"<<who<<"\n";
}

void stack::push(char ch){
if(tos==SIZE){
cout<<"スタック"<<who<<"は一杯です\n";
return;
}
stck[tos]=ch;
tos++;
}

char stack::pop(){
if(tos==0){
cout<<"スタック"<<who<<"は空です\n";
return 0;
}
tos--;
return stck[tos];
}

int main(){
stack s1('A'),s2('B');
int i;
s1.push('a');
s2.push('x');
s1.push('b');
s2.push('y');
s1.push('c');
s2.push('z');

for(i=0;i<5;i++)cout<<"s1をポップする"<<s1.pop()<<"\n";
for(i=0;i<5;i++)cout<<"s2をポップする"<<s2.pop()<<"\n";

return 0;
}

をコンパイルすると

生成スタックA
生成スタックB
s1をポップするc
s1をポップするb
s1をポップするa
スタックAは空です
s1をポップする
スタックAは空です
s1をポップする
s2をポップするz
s2をポップするy
s2をポップするx
スタックBは空です
s2をポップする
スタックBは空です
s2をポップする

となったのですが、自分は

生成スタックA
生成スタックB
s1をポップするc
s1をポップするb
s1をポップするa
s1をポップする
スタックAは空です
s1をポップする
スタックAは空です
s2をポップするz
s2をポップするy
s2をポップするx
s2をポップする
スタックBは空です
s2をポップする
スタックBは空です

になると思いました。何故こうなるのでしょうか?お願いします

投稿日時 - 2008-12-21 00:37:20

QNo.4571513

すぐに回答ほしいです

質問者が選んだベストアンサー

いろいろ考えてみましたが、これは処理系依存です。つまり、「意図したように動く処理系」と「意図したように動かない処理系」があります。

もっとわかりやすくいえば、この書き方は「やっちゃいけない」ので、「プログラマが意識して避ける」ように期待されています。

「本に書いてなかった」ということですが、ちゃんとした本ならこのことは書いてあります。手元にビヤーンの C++ 第2版(ちょっと古い本です)があるので引用しますが、「式の中で部分式を評価する順序は定義されていない。(中略) 式の評価順序に制限がなければ、より良いコードが生成できる。コンパイラはこうした曖昧さについて警告を出すことができる。残念ながらほとんどのものはやってくれないが」と、いうくだりがあります。

今回のような「複雑な」場合でも警告が出せるコンパイラは皆無でしょう。つまり、プログラマが意識して避ける以外に「必ず正しく動く」ことを保証することはできません。

残念ですが、#1 と #2 の回答は誤りです。
以下の説明は、質問者の方が理解できるレベルを大きく超えていると思いますが、実は同じ勘違いをしている中級者はとても多いので、詳しく書いておきます。今は理解できないかもしれませんが、いずれ理解できれば上級者の域に達したと思ってかまわないでしょう。上級者はほとんど本能的にこの状態を回避してプログラムを作成/解読しています。

ここから説明。

まず最初に stream に用いられる << 演算子の話をしましょう。
これは正式には「演算子の多重定義」と呼ばれる技法で、内部的には次のように定義されています。

ostream& operator<<(int);
ostream& operator<<(const char*);

他にもたくさんオーバーロードされていますが、省略します。また、この書き方はこの後の説明でちょっと面倒な上に暗黙で宣言されている this ポインタが見えないので、C# 風に ope(cout, int) と書くことにします。リターン値は cout ですね。

とすると、質問者が悩んでいる「cout<<"xxx"<<s1.pop()」は、次のように書き換えることができます。
ope(ope(cout, "xxx"), s1.pop());

さて、ここで問題です。第一引数の ope(cout, "xxx") と第二引数の s1.pop() は、「どちらが先に評価される」のでしょうか?

ここが大きく誤解されているところですが、実はこの評価順序は C や C++ の言語の仕様では定まっていません。

「えー、だって C では不定個の引数を可能にするために、後ろからスタックに積むんじゃないの????」

そのとおりです。が、この文章は2箇所で誤り/見落としがあります。「後ろからスタックに積む」ことと、「後ろの引数から評価すること」はイコールではありません。また、C の処理系によっては、定数個の引数を持つ場合の効率を上げるためや Pascal など他の言語のライブラリを呼ぶときのために「前の引数からスタックに積む」場合があります。(今回の話とは直接関係ありませんが、引数を解放するのが呼び出し側なのか呼び出された側か、というのも、言語仕様では規定されていません)

一般的には、この評価の順序が問題になることはありません。
func(a+1, b+2) という呼び出しにおいて、a+1 と b+2 のどちらを先に計算しても「結果は変わらないから」です。このため、中級の C/C++ プログラマは、評価順序を意識せずにプログラムを書いてしまいがちです。

ところが、今回の質問者の場合、第二引数の s1.pop() は、内部で cout を呼んでいます。つまり、概念的には
ope(ope(cout, "xxx"), ope(cout, "yyy"));
という状態です。この場合、ope(cout, "xxx") と ope(cout, "yyy") のどちらを先に評価するかはとても大事です。ところが、これは「言語仕様的に定められていません」。ですから、xxx が先に出力される処理系もあれば、yyy が先に出力される処理系もあるのです。

大半の処理系では「後ろの引数を先に評価」しているため、yyy が先に表示されるはずですが、それが偶然であり、将来別の環境で動作させたときに保証されないことは頭のかたすみに入れておいてください。(IO マニピュレータの flush を使っても解決にならないと思います)

さて、今回の質問者は
cout<<"xxx";
cout<<s1.pop();
と二行に分けたらちゃんと動作するようになった、ということですが、まさにこれが正しいプログラムのしかたです。この場合、1行目が先に処理/評価されることが保証されているので、所望の動作となります。

ここまでが説明です。
以下、まだ納得できない人のための補足。

「<< オペレータは左結合だから、説明のような動作にはならないはずだ」

左結合、という部分はあってます。が、評価順序という意味では、左結合か右結合か、というのは、関係ありません。C および C++ で左項が右項よりも先に評価されることを保証している演算子は実は3つしかありません。それ以外の演算子では、どちらの項が先に評価されるかは処理系依存です。簡単な例をあげましょう。
a = (1+2)*(3+4);
利口なコンパイラなら定数項としてコンパイル時に計算してしまいますがそれはないと仮定すると、この場合、1+2 と 3+4 のどちらを先に計算するかは決められていません。もちろん、() のほうが * よりも優先するので、* の「前に」必ず 1+2 と 3+4 の評価は終わっている必要がありますが、どちらを先に計算するかは自由です。
関数の引数の評価順序もこれに準じます。関数の引数を区切る「,」は、演算子ではありません。ですから、
f1(v[i], i++);   危険
f2( (v[i], i++) ); OK
となります。引数の評価時に副作用(side effect)がある場合、その副作用によって処理系依存のプログラムとなってしまいます。これを避けるのはプログラマの責任である、と C/C++ では明記されているため、今回のようなプログラムを書くことは推奨されません。そういう意味では C/C++ は「プロの道具」なのです。

やたら長くなってしまいましたが、おわかりいただけたでしょうか?

投稿日時 - 2008-12-22 01:41:05

お礼

ここまで書いてくれるなんて感謝の気持ちでいっぱいです。
途中わからない言葉がたくさんありましたが、理解しようとがんばりました。
coutを使う時はこのように1つずつ出力していくようにします。
cout<<"...";
cout<<"...";
cout<<"...";

投稿日時 - 2008-12-22 07:12:27

ANo.3

このQ&Aは役に立ちましたか?

1人が「このQ&Aが役に立った」と投票しています

回答(3)

ANo.2

> coutの中にある関数を処理してから、coutが実行されるのですかー。
> 本には書いてなかったOTL

いや、本に書いてないはずがないというか、質問者さん自身、そうなることを想定していないはずがありません。

s1.pop内に画面表示をするコードがあるかどうかを抜きにしても、
> cout<<"s1をポップする"<<s1.pop()<<"\n";
これは「s1.pop() が返した値」を、「cout に << して画面表示」することを期待されていますが、
そうなるためには当然、「cout に <<」する前に「s1.pop() が実行」されなければなりません。
つまり、このコード自体が「coutの中にある関数を処理してから、coutが実行される」ことを想定したものになっています。

投稿日時 - 2008-12-21 11:53:40

お礼

言い方が間違っていました。すみません。

投稿日時 - 2008-12-22 07:07:15

ANo.1

スタックが空ですのメッセージの場所が意図どおりでないということですか?

実行順序がそうだから。としか言いようがないのですが。
pop() が0を返すところを、適当なキャラクタを返すようにしてみてください。

s2をポップする ==>x
スタックBは空です
s2をポップする ==>#
スタックBは空です
s2をポップする ==>#

pop() を呼び出す → pop()内での出力が行われる。
の後で pop() が返した値を使って for ループの本体にある
出力を行っています。

投稿日時 - 2008-12-21 03:07:48

お礼

ありがとうございます。
coutの中にある関数を処理してから、coutが実行されるのですかー。
本には書いてなかったOTL

上のソースをこのようにしたら
for(i=0;i<5;i++){
cout<<"s1をポップする\n";
cout<<s1.pop()<<"\n";
}
for(i=0;i<5;i++){
cout<<"s2をポップする\n";
cout<<s2.pop()<<"\n";
}



生成スタックA
生成スタックB
s1をポップする
c
s1をポップする
b
s1をポップする
a
s1をポップする
スタックAは空です

s1をポップする
スタックAは空です

s2をポップする
z
s2をポップする
y
s2をポップする
x
s2をポップする
スタックBは空です

s2をポップする
スタックBは空です

このように、自分がやりたいようにできました。

投稿日時 - 2008-12-21 04:01:42