- ベストアンサー
VC++の電卓です
初めての投稿になります。 VC++2005で演算子の優先度を識別する電卓を作成しようと試みているのですが、( )の処理がどうにもうまく行きません。 改善点など、ご意見を頂ければ幸いに思います。 詳細に関しては以下の通りです。 仕様 開発環境 VC++2005 開発対象 +-×÷( )の優先度を識別して計算を行う電卓。 備考 BackやCEの機能は考えず、まずは正しい計算結果を求めることが出来れば良い。 問題があると思われるコード //グローバル変数 int num[100];//数値を格納する配列 int op[100];//演算子を格納する配列 int num_count = 0;//数値配列の位置を格納する変数 int op_count = 0;//演算子配列の位置を格納する変数 int ans;//解答を表示 //計算処理 int calculat() { int num1 = num[num_count++];//最初の数値を格納 int num2 = 0;//一つ目の値と二つ目の値を格納しておく変数 int ope = op[op_count++];//最初の演算子を格納 while(ope > 0)//演算子が0(=)にならない限り計算を続ける { switch(ope) { //演算子が+か-の場合 case 1: case 2: num2 = calculat();//再帰的に calculat を呼び出す break; //演算子が×か÷の場合 case 3: case 4: //更に次の演算子が(の場合 if(op[op_count] == 5) { num2 =calculat(); break; } else { num2 = num[num_count++];//二つ目の数値にnum1の次の数値を格納 break; } //演算子が)の場合 case 6: return num1; break; } switch(ope)//格納されている演算子によって処理が異なる { //+の場合 case 1: num1 = num1 + num2; break; //-の場合 case 2: num1 = num1 - num2; break; //×の場合 case 3: num1 = num1 * num2; break; //÷の場合 case 4: num1 = num1 / num2; break; } //次の演算子を格納 ope = op[op_count++]; } return num1; } 計算処理を行う関数 calculat() を再帰的に呼び出すことで優先順位を付けた計算を行っている。 現在のコードで、四則演算までは問題なくこなすことが可能。 ( )を含めた計算も、ループに関しては問題ないが、正しい計算結果が返ってこない。 考えられる原因は計算処理の三行目 int num1 = num[num_count++]; 『演算子が(の場合』で再帰的に関数 calculat() を呼び出した際に限り、 num1に 0 が格納されてしまう。 = num[num_count++]; の処理が行われていない模様。 以上です。 非常に見づらいコードで申し訳ありませんが、よろしくお願いします。
- みんなの回答 (8)
- 専門家の回答
質問者が選んだベストアンサー
電卓処理なら「逆ポーランド記法」と「逆ポーランド 変換」で探してみてください。
その他の回答 (7)
- prophetok
- ベストアンサー率44% (13/29)
#3補足のソースをみて やはり配列データで数式は表現できないんじゃない。 -3 とかの処理は? //演算子のボタンが押された際の処理 void OnOperation(int operation) { num_count++;//数値の配列カウントを次に進める これだと 3+(-2) のように演算子が連続すると数字の配列がとばされて、0が入るよね。 括弧の処理がうまくいかない一つの原因はこれだと思う。 それ以外にも、問題はいっぱいありそう。(加減算と乗除算の優先順位が考慮されていないとか、関数の再帰呼び出しがちゃんと理解できていないとか) あくまで、個人的な意見ですが、もし、部下がこれを持ってきて相談にきたら、yaccの使い方を教えて、全面的に書き直させます。
お礼
現状のコードでは、四則演算も正の数値の計算しか実行できないことにようやく気づきました察しが悪く申し訳ありません。 指摘、ありがとうございます。
補足
確かにその通りです。 これまで自分は、( )を使用する必要があるのは乗除算の場合のみと考えていましたが、認識が甘すぎたようです。 ( )を利用する場合のアルゴリズムを、根本から見直してみようかと思います。 また、電卓を作成するにはyaccが有効であるとのことですが、今回はVC++に慣れることが目的である為、yaccに関しては保留とし、現状のコードを、( )に対応できるように改良するという形を取りたいと思います。 問題があるようでしたら、ご指摘ください。
- titokani
- ベストアンサー率19% (341/1726)
1+2 だと、 数値、演算子、数値 の順だけど、 (1+2)*(3+4) だと、 演算子、数値、演算子、数値、演算子、演算子、演算子、数値、演算子、数値、演算子 になりますよね。 となってくると、 >int num[100];//数値を格納する配列 >int op[100];//演算子を格納する配列 >int num_count = 0;//数値配列の位置を格納する変数 >int op_count = 0;//演算子配列の位置を格納する変数 このデータ構造では表現しきれないんじゃないかと。 特に、式の最初が演算子か数値かという区別をどうやってつけるのか。
補足
現状の自分の考えでは (2*3)*4 の場合(掛け算・割り算) ありえない(意味がない)入力なので対応していない。解として6が出力されてしまうと思われる。 (1+2)*3= の場合(足し算・引き算) 数値配列 1, 2, 3 演算子配列 5, 1, 6, 3, 0 数値配列から num1 に 1 が格納されnum_countが次に進む。 演算子配列から ope に最初の演算子を表す5(※(のこと)が格納されop_countが次に進む。 ループに入る。 ope = 5 の場合に行う処理は何処にもないのでそのまま次の演算子1 が格納されop_countが次に進む。(ope = 5は無視される) 演算子が1である為 num2 = calculat() が実行されcalculat()が呼び出される。 数値配列から num1 に 2 が格納されnum_countが次に進む。 演算子配列から ope に 6 が格納されop_countが次に進む。 ループに入る。 opeが6の為、return num1 が実行される。 num2 = 2 が返されたので、そのまま計算を行う。 num1 = num1 + num2 3 = 1 + 2 次の演算子3 が格納されop_countが次に進む。 演算子が3の為 num2 = num[num_count++] で num2 に 3 が格納されnum_countが次に進む。 そのまま計算を行う。 num1 = num1 * num2 9 = 3 * 3 次の演算子0 が格納されop_countが次に進む。 while(ope > 0)の為ループ終了。 return num1 が返され、解として9が出力される。 ……すごく無理やりなアルゴリズムですみません。( )の識別に関しては、他の方法をとるべきではないだろうかと自分でも思いました。
- Tacosan
- ベストアンサー率23% (3656/15482)
yacc でも再帰下降解析でも演算子順位文法でもいいけど, いずれにしてもスタックを使うのが素直だろうなぁ.
- prophetok
- ベストアンサー率44% (13/29)
電卓を作るのに参考になると思われるキーワードを列挙しておきます。 yacc shift reduce 質問者のように、グローバル変数にデータを持って、処理してもできなくはないだろうけど、普通はスタックをうまく使った方が、スマートでわかりやすいコードになるはず。 yaccとか初心者にはかなり敷居が高いけど、使えるようになって損はないと思うよ。
- prophetok
- ベストアンサー率44% (13/29)
#2です。 >そもそもこの配列データで数式を表現できる? >(1+2+3)*(4+5) >と >(1+2)*(3+4+5) >は順番からいったら同じ配列データになる?? ならないよね。 勘違い。 でもなんか違和感あるんだよな。
補足
肝心なことを伝え忘れました、電卓はWebアプリケーションで作成しています。 基本的に、内容は全てForm1.hに書き込んでおり、自分が手を加えた部分は以下の通りです。 #pragma endregion //各ボタンが押された際の処理 //数値 private: System::Void input_1_Click(System::Object^ sender, System::EventArgs^ e) { OnNumber(1); } private: System::Void input_2_Click(System::Object^ sender, System::EventArgs^ e) { OnNumber(2); } private: System::Void input_3_Click(System::Object^ sender, System::EventArgs^ e) { OnNumber(3); } private: System::Void input_4_Click(System::Object^ sender, System::EventArgs^ e) { OnNumber(4); } private: System::Void input_5_Click(System::Object^ sender, System::EventArgs^ e) { OnNumber(5); } private: System::Void input_6_Click(System::Object^ sender, System::EventArgs^ e) { OnNumber(6); } private: System::Void input_7_Click(System::Object^ sender, System::EventArgs^ e) { OnNumber(7); } private: System::Void input_8_Click(System::Object^ sender, System::EventArgs^ e) { OnNumber(8); } private: System::Void input_9_Click(System::Object^ sender, System::EventArgs^ e) { OnNumber(9); } private: System::Void input_0_Click(System::Object^ sender, System::EventArgs^ e) { OnNumber(0); } //演算子 private: System::Void input_puls_Click(System::Object^ sender, System::EventArgs^ e) { OnOperation(1); text->Text+= "+"; } private: System::Void input_minus_Click(System::Object^ sender, System::EventArgs^ e) { OnOperation(2); text->Text+= "-"; } private: System::Void input_multipl_Click(System::Object^ sender, System::EventArgs^ e) { OnOperation(3); text->Text+= "*"; } private: System::Void input_divid_Click(System::Object^ sender, System::EventArgs^ e) { OnOperation(4); text->Text+= "/"; } private: System::Void Parent1_Click(System::Object^ sender, System::EventArgs^ e) { OnOperation(5); text->Text+= "("; } private: System::Void Parent2_Click(System::Object^ sender, System::EventArgs^ e) { OnOperation(6); text->Text+= ")"; } //イコール private: System::Void input_equal_Click(System::Object^ sender, System::EventArgs^ e) { OnOperation(0); text->Text+= "="; num_count = op_count = 0; ans = calculat(num[num_count++]); text->Text+= ans.ToString(); } //計算処理 (ここに上記の関数) //数値のボタンが押された際の処理 void OnNumber(int number) { num[num_count] = num[num_count] * 10;//格納されていた数値の桁を一つ上げている num[num_count] = num[num_count] + number;//入力された数値を一桁目に格納している text->Text+= number.ToString(); } //演算子のボタンが押された際の処理 void OnOperation(int operation) { num_count++;//数値の配列カウントを次に進める op[op_count++] = operation; } }; } 文字制限で弾かれたため、計算処理は記載できませんでした。
- prophetok
- ベストアンサー率44% (13/29)
まず、素朴な疑問 引数がない関数を再帰的にコールできるのものなのか? そもそもこの配列データで数式を表現できる? (1+2+3)*(4+5) と (1+2)*(3+4+5) は順番からいったら同じ配列データになる?? 計算結果は違うけど。
- asuncion
- ベストアンサー率33% (2127/6289)
main関数など、実行するために必要な部分を こちらが勝手に補ってもいいのですか? 都合が悪ければ、main関数などを含む、 手持ちのコード全体を提示してください。
補足
返答が遅れて申し訳ありません。 そちらにお任せします。
補足
アドヴァイスに従い、逆ポーランド記法について少し調べてみました。 優先順位を付けた電卓を作成する際にはよく使われる手法のようですね。 プログラム言語の世界に飛び込んで早一年、世界の広さに呆然とする毎日です。(笑) とにかく、電卓作成の為のアプローチの仕方が根本的に間違っていたと理解しました。 逆ポーランド記法について学習し、リベンジを果たそうと心に誓いました。 と、方向転換を行うことが決定した為、この回答は締め切ろうと思います。 素人同然の私の問いに、親身になって回答してくださった皆様には、深く感謝致します。 逆ポーランド記法での電卓作成法についてまた質問を投稿すると思いますので、その際にはまたよろしくお願いします。