四則演算プログラムについて
Re:無題
>力業とはどういうことでしょうか?
ちょっとわかりにくかったですね。
えーと、比喩的な表現でいろいろと意味があるのですが、ここでは
思いつくまま書いていくことを指しています。
今回のケースですと、本来であれば二項演算の「式」はどういう文字の要素で成り立ち、
どういうルールで何の文字が現れるのかを正確に仕様化してプログラムを考えるのですが、
二項演算ほどシンプルなものであれば適当に「数字 演算子 数字」とだけ分かってれば
正負の単項演算や括弧が入ってきても、シンプルであるが故に仕様に基づいた構文解析とか
しなくても適当に対処できなくはないかな、ということです。
ちょっとわかりにくかったですね。
えーと、比喩的な表現でいろいろと意味があるのですが、ここでは
思いつくまま書いていくことを指しています。
今回のケースですと、本来であれば二項演算の「式」はどういう文字の要素で成り立ち、
どういうルールで何の文字が現れるのかを正確に仕様化してプログラムを考えるのですが、
二項演算ほどシンプルなものであれば適当に「数字 演算子 数字」とだけ分かってれば
正負の単項演算や括弧が入ってきても、シンプルであるが故に仕様に基づいた構文解析とか
しなくても適当に対処できなくはないかな、ということです。
Re:無題
>今はopという変数に演算子を格納しているのですが
それはそれでいいと思いますよ。
>そこに括弧を格納し、switchなどで分岐させるのでしょうか?
>はたまた括弧用の変数を用意した方がいいのでしょうか?
単項演算のみで考えた場合括弧は対応状態だけ
チェックして基本は無視でいいと思います。
ちょっとサンプル作ってみました。
括弧付きの正負の単項演算のみに対応したものです。
つまり、"--(+(-(3)))"のようなものを解析します。
parse_test()の中の test_expression_tbl[/url]の中にテスト用の
単項演算の式が複数入っています。
この文字列を1つずつ parse()に引き渡して、正当な単項演算ができれば
1が戻り、失敗すれば 0が戻ります。
この延長線上で考えれば二項演算も楽にできると思います。
それはそれでいいと思いますよ。
>そこに括弧を格納し、switchなどで分岐させるのでしょうか?
>はたまた括弧用の変数を用意した方がいいのでしょうか?
単項演算のみで考えた場合括弧は対応状態だけ
チェックして基本は無視でいいと思います。
ちょっとサンプル作ってみました。
括弧付きの正負の単項演算のみに対応したものです。
つまり、"--(+(-(3)))"のようなものを解析します。
parse_test()の中の test_expression_tbl[/url]の中にテスト用の
単項演算の式が複数入っています。
この文字列を1つずつ parse()に引き渡して、正当な単項演算ができれば
1が戻り、失敗すれば 0が戻ります。
この延長線上で考えれば二項演算も楽にできると思います。
Re:無題
Justyさん。レスありがとうございました。
サンプルプログラム拝見しました。初めに思ったのですが、
main関数の短さに驚きました。あれだけ短いと他の関数で
何の処理をしているのかがつかみ易いですね。それだけでも
大変勉強になりました。
ただ、サンプルいろいろと動かしてみたのですが、まだ実際どのように
すれば解析出来るのか見えてきません。括弧の対応状況を調べるというのは
例えばopに'('が入っていたら')'と対応しているか調べるということですよね?
なお今現在管理人さんのサンプルを下に、opには演算子(0~9以外の文字)
をopに格納するようにしているのですが、その条件に'(' ')'を加えればよいの
でしょうか?もし加えた場合、演算子が上書きされてしまわないでしょうか?
質問ばかりですみません。よろしくお願いします。
サンプルプログラム拝見しました。初めに思ったのですが、
main関数の短さに驚きました。あれだけ短いと他の関数で
何の処理をしているのかがつかみ易いですね。それだけでも
大変勉強になりました。
ただ、サンプルいろいろと動かしてみたのですが、まだ実際どのように
すれば解析出来るのか見えてきません。括弧の対応状況を調べるというのは
例えばopに'('が入っていたら')'と対応しているか調べるということですよね?
なお今現在管理人さんのサンプルを下に、opには演算子(0~9以外の文字)
をopに格納するようにしているのですが、その条件に'(' ')'を加えればよいの
でしょうか?もし加えた場合、演算子が上書きされてしまわないでしょうか?
質問ばかりですみません。よろしくお願いします。
Re:無題
>あれだけ短いと他の関数で 何の処理をしているのかがつかみ易いですね
私の場合、普通仕事で書く場合 main関数は数行しかないです。
他の人を見ても、どんなに大きなプログラムでも概ね多くてmain関数は十数行程度ですね。
>まだ実際どのように すれば解析出来るのか見えてきません
parse関数が二項演算「因子 演算子 因子」でいうところの因子の演算を
行うものであるのはわかりますよね?
そして parse関数の第二引数はどこまで解析が終わったか(正確には未解析の文字列の先頭)を返すことが
できるように設計されています。
この二点から、まず解析したい文字列を parse()にかけて
正常に因子の計算ができたことを確認し、解析が終わった文字列から演算子(+-*/)
を取り出します(opにでも代入しておくといいでしょう)。
で、その次の文字からを再度 parse関数にかけてあげれば2つ目の数値が取り出せます。
(実際にはこのサンプルの仕様上、parse関数は未解析の文字列の先頭が NUL文字であることを
確認しているので、このチェックを外さなければなりませんが)
従って、サンプルの parse関数を使うのであれば、もう解析の為の whileループは不要です。
>その条件に'(' ')'を加えればよいの でしょうか?
いいえ、全て parse関数が処理しています。
main関数が行わなければならないのは parse関数への引数と戻り値の管理と
演算子の管理、そして実際の計算だけとなります。
私の場合、普通仕事で書く場合 main関数は数行しかないです。
他の人を見ても、どんなに大きなプログラムでも概ね多くてmain関数は十数行程度ですね。
>まだ実際どのように すれば解析出来るのか見えてきません
parse関数が二項演算「因子 演算子 因子」でいうところの因子の演算を
行うものであるのはわかりますよね?
そして parse関数の第二引数はどこまで解析が終わったか(正確には未解析の文字列の先頭)を返すことが
できるように設計されています。
この二点から、まず解析したい文字列を parse()にかけて
正常に因子の計算ができたことを確認し、解析が終わった文字列から演算子(+-*/)
を取り出します(opにでも代入しておくといいでしょう)。
で、その次の文字からを再度 parse関数にかけてあげれば2つ目の数値が取り出せます。
(実際にはこのサンプルの仕様上、parse関数は未解析の文字列の先頭が NUL文字であることを
確認しているので、このチェックを外さなければなりませんが)
従って、サンプルの parse関数を使うのであれば、もう解析の為の whileループは不要です。
>その条件に'(' ')'を加えればよいの でしょうか?
いいえ、全て parse関数が処理しています。
main関数が行わなければならないのは parse関数への引数と戻り値の管理と
演算子の管理、そして実際の計算だけとなります。
Re:無題
Justyさん。レスありがとうございます。
>parse関数が二項演算「因子 演算子 因子」でいうところの因子の演算を
>行うものであるのはわかりますよね?
正直な所、因子の演算という意味がよく理解できません。
申し訳ありません。Justyさんのおっしゃられていることが理解出来ません。
大変丁寧な説明だと思うのですが、私の知識では理解出来ませんでした。
parse()にかけてというのは、引数を渡すということなのでしょうか?
parse(const char *expression, const char **end, int *pValue)となっていますが
どのようにすればいいのか分かりません。skipやfactorもどんな方に処理すればよいのかも分かりません。
>もう解析の為の whileループは不要です
この部分でしょうか?
>parse関数が二項演算「因子 演算子 因子」でいうところの因子の演算を
>行うものであるのはわかりますよね?
正直な所、因子の演算という意味がよく理解できません。
申し訳ありません。Justyさんのおっしゃられていることが理解出来ません。
大変丁寧な説明だと思うのですが、私の知識では理解出来ませんでした。
parse()にかけてというのは、引数を渡すということなのでしょうか?
parse(const char *expression, const char **end, int *pValue)となっていますが
どのようにすればいいのか分かりません。skipやfactorもどんな方に処理すればよいのかも分かりません。
>もう解析の為の whileループは不要です
while ( st != '\0' ) { if ( ! ( st >= '0' && st <= '9' ) && i != 0 ) { op = st; st = '\0'; lop = atoi ( st ); rop = atoi ( &st[i+1] ); break; } i++; }
この部分でしょうか?
Re:無題
>正直な所、因子の演算という意味がよく理解できません。
二項演算というのはわかりますか?
二つの数と演算子を元に、新たな数を導く計算のことです。
四則演算なら例えば「1 + 1」とか「5 * 4」のように「数字 演算子 数字」の
ような形になります。
因子というのはここでいう数字の部分に相当します。
数字と書かなかったのは数字だけがここに来るとは限らないので、因子と書きました。
例えば「-(+(-5)) / +++(3) 」であれば2つの因子とはそれぞれ「-(+(-5))」と「+++(3)」を指します。
parse関数はこの 「-(+(-5))」や「+++(3)」の文字列を解析して、
int型のデータに変換する機能を持っています。
>parse()にかけてというのは、引数を渡すということなのでしょうか?
そうです。
>parse(const char *expression, const char **end, int *pValue)となっていますが
>どのようにすればいいのか分かりません。skipやfactorもどんな方に処理すればよいのかも分かりません。
使うのは基本的には parse関数だけでいいのですが、ひょっとしてサンプルが何をしているのも
わからなかったのでしょうか?
parseの第一引数は解析したい「因子」となる文字列を指定します。
第二引数は第一引数の文字列を「因子」としての解析が終了した直後の文字列の位置を
ポインタを経由して取得できます。
第三引数は「因子」としての解析が成功したのなら結果がポインタを経由して取得できます。
例を挙げますと以下のようなプログラムの場合
[color=#d0d0ff" face="aria[/url]-5 # hdfghdfgh[/color]
parse関数は valに -5を、pには 第一引数で指定した文字列の中で因子としての解析が終わった位置を戻しているので、
このように表示されます。
では、こうしたらどうでしょうか。
[color=#d0d0ff" face="aria[/url]-5 , -4[/color]
になるはずです。
1回目の parse解析で、expression内の文字列解析し、-5という値を出しています。
そのとき、pは expression文字列の中の "-4"の部分を指しているポインタになっているので、
その pをそのまま parseにかけると2つ目の値 -4が取得しています。
この説明でなんとなく掴めたでしょうか?
二項演算というのはわかりますか?
二つの数と演算子を元に、新たな数を導く計算のことです。
四則演算なら例えば「1 + 1」とか「5 * 4」のように「数字 演算子 数字」の
ような形になります。
因子というのはここでいう数字の部分に相当します。
数字と書かなかったのは数字だけがここに来るとは限らないので、因子と書きました。
例えば「-(+(-5)) / +++(3) 」であれば2つの因子とはそれぞれ「-(+(-5))」と「+++(3)」を指します。
parse関数はこの 「-(+(-5))」や「+++(3)」の文字列を解析して、
int型のデータに変換する機能を持っています。
>parse()にかけてというのは、引数を渡すということなのでしょうか?
そうです。
>parse(const char *expression, const char **end, int *pValue)となっていますが
>どのようにすればいいのか分かりません。skipやfactorもどんな方に処理すればよいのかも分かりません。
使うのは基本的には parse関数だけでいいのですが、ひょっとしてサンプルが何をしているのも
わからなかったのでしょうか?
parseの第一引数は解析したい「因子」となる文字列を指定します。
第二引数は第一引数の文字列を「因子」としての解析が終了した直後の文字列の位置を
ポインタを経由して取得できます。
第三引数は「因子」としての解析が成功したのなら結果がポインタを経由して取得できます。
例を挙げますと以下のようなプログラムの場合
[color=#d0d0ff" face="monospace] const char *p;
int val;
const char *expression = "-(--( 5 )) -4";
parse(expression, &p, &val);
printf("%d # %s\n", val, p);[/color]
実行するとこのように表示されます。[color=#d0d0ff" face="aria[/url]-5 # hdfghdfgh[/color]
parse関数は valに -5を、pには 第一引数で指定した文字列の中で因子としての解析が終わった位置を戻しているので、
このように表示されます。
では、こうしたらどうでしょうか。
[color=#d0d0ff" face="monospace] const char *p;
int val1, val2;
const char *expression = "-(--( 5 )) -4";
parse(expression, &p, &val1);
parse(p, &p, &val2);
printf("%d , %d\n", val1, val2);[/color]
表示は[color=#d0d0ff" face="aria[/url]-5 , -4[/color]
になるはずです。
1回目の parse解析で、expression内の文字列解析し、-5という値を出しています。
そのとき、pは expression文字列の中の "-4"の部分を指しているポインタになっているので、
その pをそのまま parseにかけると2つ目の値 -4が取得しています。
この説明でなんとなく掴めたでしょうか?
Re:無題
Justyさん。レスありがとうございます。
説明して下さったおかげで因子については理解出来ました。
>基本的には parse関数だけでいいのですが
これはparse関数内で他の関数を呼び出しているから、parse関数を
呼び出せばいいという考え方でよいのでしょうか?
>ひょっとしてサンプルが何をしているのもわからなかったのでしょうか?
はい。すみません。実際はこんな処理をしているのかな~ということしか分かりませんでした。
ポインタが絡んでくるともう駄目になってしまい、本を見ながら解読していたので
すが、意味を理解することが出来ませんでした。
>この説明でなんとなく掴めたでしょうか?
先ほどよりは掴めてきました。ただ、なんとなくですが。。
結局のところ、変数opに括弧と演算子を格納することが出来る
ようになるということでしょうか?結論が今いち掴めないです。
理解力不足ですみません。
説明して下さったおかげで因子については理解出来ました。
>基本的には parse関数だけでいいのですが
これはparse関数内で他の関数を呼び出しているから、parse関数を
呼び出せばいいという考え方でよいのでしょうか?
>ひょっとしてサンプルが何をしているのもわからなかったのでしょうか?
はい。すみません。実際はこんな処理をしているのかな~ということしか分かりませんでした。
ポインタが絡んでくるともう駄目になってしまい、本を見ながら解読していたので
すが、意味を理解することが出来ませんでした。
>この説明でなんとなく掴めたでしょうか?
先ほどよりは掴めてきました。ただ、なんとなくですが。。
結局のところ、変数opに括弧と演算子を格納することが出来る
ようになるということでしょうか?結論が今いち掴めないです。
理解力不足ですみません。
Re:無題
ポインタは決して難しいものではありませんよ。
これは私が初めてポインタを理解できたような気がした時に頭に過ぎった例えです。
参考になるか分かりませんが、良かったら読んでみて下さい。
メモリを本だと考えて下さい。
アドレスをページだと考えて下さい。
その本に書かれている文章が変数や関数だと考えて下さい。
ポインタはその書かれた場所を指し示す栞だと考えて下さい。
栞は単体では意味を成しませんよね?
書物に挟む事で初めて意味を成すものです。
int nVal;
int* pPtr;
pPtr = &nVal;
これは、nValという変数が書かれているページにpPtrという栞を挟んだと考えればどうでしょう?
pPtrは栞でありながら、nValが書かれたページでもある訳です。
*pPtr = 10;
つまり、上記のような代入を行うと、pPtrが挟まれているページの内容を10に書き換えなさいという意味になる訳です。ですから、pPtrという栞を通して、間接的にnValを書き換える事になる訳です。
参考になればよいですが…(^_^;)
これは私が初めてポインタを理解できたような気がした時に頭に過ぎった例えです。
参考になるか分かりませんが、良かったら読んでみて下さい。
メモリを本だと考えて下さい。
アドレスをページだと考えて下さい。
その本に書かれている文章が変数や関数だと考えて下さい。
ポインタはその書かれた場所を指し示す栞だと考えて下さい。
栞は単体では意味を成しませんよね?
書物に挟む事で初めて意味を成すものです。
int nVal;
int* pPtr;
pPtr = &nVal;
これは、nValという変数が書かれているページにpPtrという栞を挟んだと考えればどうでしょう?
pPtrは栞でありながら、nValが書かれたページでもある訳です。
*pPtr = 10;
つまり、上記のような代入を行うと、pPtrが挟まれているページの内容を10に書き換えなさいという意味になる訳です。ですから、pPtrという栞を通して、間接的にnValを書き換える事になる訳です。
参考になればよいですが…(^_^;)
Re:無題
上司の方に説明する必要があるので、中身を理解する必要があるのは
わかるのですが、今は中身の処理より、結果に注目してください。
つまり、parse()の中身は今は無視してください。
>parse関数を呼び出せばいいという考え方でよいのでしょうか?
単項部分に限って言えば、Y
>変数opに括弧と演算子を格納することが出来る ようになるということでしょうか?
N
括弧を格納する必要はありません。
先ほどのコードに手を加えてみました。
[color=#d0d0ff" face="aria[/url]val1 = -5, p = -4[/color]
となります。
さて、ここで注目なのは 1つ目の parseを実行し終わったところで文字列へのポインタ pは
"-4"を指しています。
ということとは p[0]は '-'であり、p[1]='4'である、ということです。
では、これをふまえて、expressionの式を二項演算に代えてみました。
この結果 pは何を指すと思いますか?
わかるのですが、今は中身の処理より、結果に注目してください。
つまり、parse()の中身は今は無視してください。
>parse関数を呼び出せばいいという考え方でよいのでしょうか?
単項部分に限って言えば、Y
>変数opに括弧と演算子を格納することが出来る ようになるということでしょうか?
N
括弧を格納する必要はありません。
先ほどのコードに手を加えてみました。
[color=#d0d0ff" face="monospace] const char *p;
int val;
const char *expression = "-(--( 5 )) -4";
parse(expression, &p, &val);
printf("val = %d, p = %s\n", val, p);[/color]
このコードの実行結果は[color=#d0d0ff" face="aria[/url]val1 = -5, p = -4[/color]
となります。
さて、ここで注目なのは 1つ目の parseを実行し終わったところで文字列へのポインタ pは
"-4"を指しています。
ということとは p[0]は '-'であり、p[1]='4'である、ということです。
では、これをふまえて、expressionの式を二項演算に代えてみました。
[color=#d0d0ff" face="monospace] const char *p;
int val;
const char *expression = "-(--( 5 )) * -4";
parse(expression, &p, &val);
printf("val = %d, p = %s\n", val, p);[/color]
このコードだったらどうでしょう?この結果 pは何を指すと思いますか?
Re:無題
Justyさん。レスありがとうございました。
一応ソースを添付しておきます。
ソース部分で//文字列のチェックという部分を削り、//右オペランドのチェック
の上の部分でparse()関数を呼び出せばいいんですよね?
>終端が NUL文字であることをチェックして
>1か 0か(成功したかどうか)を戻すようになっているので
アドバイスありがとうございました。その部分については削除しました。
一応ソースを添付しておきます。
#pragma warning ( disable : 4996 )
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <math.h>
#include <ctype.h>
#include "calc.h"
/*----------------- 関数プロトタイプ宣言 -----------------*/
// エラーメッセージ
void usage_err ( int, char *[/url] );
void integer_err ();
void int_over_err ();
void operator_err ();
void div_err ();
void over_flow_err ();
// 四則計算
long long add ( long long, long long );
long long sub ( long long, long long );
long long mul ( long long, long long );
long long did ( long long, long long );
long long mod ( long long, long long );
// NUL文字
#define ASCII_NUL ('\0')
// 式文字列のバッファサイズ
#define EXPRESSION_BUFF_SIZE ( 128 )
// 配列サイズ
#define ArraySize(array) (sizeof(array)/sizeof(*(array)))
/*********************************************************************/
// 構文
// factor ::= integer | ('(' factor ')') | ('+' factor) | ('-' factor)
// expression ::= factor
// 空白をスキップ
static const char *skip_parce(const char *p)
{
if(p)
{
while(isspace(*p))
++p;
}
return p;
}
// 要素解析
static int parse_factor(const char **p, int *pValue)
{
int c;
// 空白をスキップ
*p = skip_parce(*p);
c = **p;
if(isdigit(c))
{
char *end;
*pValue = (int)strtol(*p, &end, 10);
*p = end;
return 1;
}
else
if(c == '(')
{
++(*p);
if(parse_factor(p, pValue))
{
*p = skip_parce(*p);
if(**p == ')')
{
++*p;
return 1;
}
}
}
else
if(c == '-')
{
++(*p);
if(parse_factor(p, pValue))
{
*pValue = -(*pValue);
return 1;
}
}
else
if(c == '+')
{
++(*p);
if(parse_factor(p, pValue))
return 1;
}
return 0; // 異常
}
// 解析
static int parse(const char *expression, const char **end, int *pValue)
{
// NULLチェック
if(expression && pValue)
{
// 要素解析
const char *p = expression;
if(parse_factor(&p, pValue))
{
// 解析が終了した文字列の位置を記録
p = skip_parce(p);
if(end) *end = p;
}
}
return 0;
}
/*********************************************************************/
/*-------- 受け取ったコマンドライン引数を配列strまとめる --------*/
void storage ( int argc, char *argv[/url], char st[128] )
{
int i = 0, j = 0, s = 0;
while ( ++i < argc ) {
s = 0;
while ( argv != '\0' ) {
st[j] = ( argv );
s++;
j++;
}
}
st[j]='\0';
}
/*----------------- main関数 -----------------*/
int main ( int argc, char *argv[/url] )
{
long long lop; // 左オペランド
long long rop; // 右オペランド
long long i = 0; // 配列要素
long long n = 0;
long long surplus; // 余り
long long result; // 結果
char op; // 演算子
char st[128]; // コマンドライン引数を格納
char *check; // 変換不能な文字列へのポインタを格納
// パラメータのチェック
if ( argc <= 1 || argc >= 5 ) {
usage_err ( argc, argv );
}
storage ( argc, argv, st );
// 文字列のチェック
while ( st != '\0' ) {
if ( ! ( st >= '0' && st <= '9' ) && i != 0 ) {
op = st;
st = '\0';
lop = atoi ( st );
rop = atoi ( &st[i+1] );
break;
}
i++;
}
// 左オペランドのチェック
strtol ( st, &check, 10 );
if ( errno != ERANGE ) {
if ( *check != '\0' )
integer_err ();
} else {
int_over_err ();
}
// 右オペランドのチェック
strtol ( &st[i+1], &check, 10 );
if ( errno != ERANGE ) {
if ( *check != '\0' )
integer_err ();
} else {
int_over_err ();
}
switch ( op ) {
case '+':
result = add ( lop, rop );
break;
case '-':
result = sub ( lop, rop );
break;
case '*':
result = mul ( lop, rop );
break;
case '/':
if ( rop == 0 )
div_err ();
// 除算のオーバーフローを考慮
if ( ( lop == INT_MIN ) && ( rop == -1 ) )
over_flow_err ();
result = did ( lop, rop );
surplus = mod ( lop, rop );
if ( surplus == 0 ) {
printf ( "%lld %c %lld = %lld\n", lop, op, rop, result );
exit ( 0 );
} else if ( surplus != 0 ) {
printf ( "%lld %c %lld = %lld 余り %lld\n", lop, op, rop, result, surplus );
exit ( 0 );
}
break;
default:
operator_err ();
}
// 加、減、乗算のオーバーフローを考慮
if ( ( result > INT_MAX ) || ( result < INT_MIN ) )
over_flow_err ();
// 左オペランド、演算子、右オペランドの順に出力
printf ( "%lld %c %lld = %lld\n", lop, op, rop, result );
exit ( 0 );
}
ソース部分で//文字列のチェックという部分を削り、//右オペランドのチェック
の上の部分でparse()関数を呼び出せばいいんですよね?
>終端が NUL文字であることをチェックして
>1か 0か(成功したかどうか)を戻すようになっているので
アドバイスありがとうございました。その部分については削除しました。
Re:無題
>main関数内のstrtolの部分を削ると上手く いくのですが、
>削らないと上手くいかないのはparse_factor関数で判定している
もうこのチェック方法ではだめですね。
このチェックが int型変換時の intで扱える範囲を超えた場合に対するチェックであれば
parse_factor()内の strtol()を使っているところでチェックするといいでしょう。
>-5 + 4 = -1となってしまい、括弧が表示されません。
それはそうでしょう。
数値に変換したあとの lop/ropを使って printfしていますから。
普通に文字列 stを表示すればいいのではないですか?
>今テストしていたんですが、かなりバグがありました。
>明らかにおかしいという部分の指摘をお願いします。
parse()の戻り値をチェックしていない点でしょうか。
因子に不正な文字が入っていてもそれを無視してしまっています。
さらに parse()は元々成功したとき 1を返すようにしていましたが、
それがなくなっています。
(NUL文字チェックは削ってもいいのですが、1は戻すようにしてください)
>削らないと上手くいかないのはparse_factor関数で判定している
もうこのチェック方法ではだめですね。
このチェックが int型変換時の intで扱える範囲を超えた場合に対するチェックであれば
parse_factor()内の strtol()を使っているところでチェックするといいでしょう。
>-5 + 4 = -1となってしまい、括弧が表示されません。
それはそうでしょう。
数値に変換したあとの lop/ropを使って printfしていますから。
普通に文字列 stを表示すればいいのではないですか?
>今テストしていたんですが、かなりバグがありました。
>明らかにおかしいという部分の指摘をお願いします。
parse()の戻り値をチェックしていない点でしょうか。
因子に不正な文字が入っていてもそれを無視してしまっています。
さらに parse()は元々成功したとき 1を返すようにしていましたが、
それがなくなっています。
(NUL文字チェックは削ってもいいのですが、1は戻すようにしてください)
Re:無題
Justyさん。レスありがとうございます。
>このチェックが int型変換時の intで扱える範囲を超えた場合に対するチェックであれば
>parse_factor()内の strtol()を使っているところでチェックするといいでしょう。
了解しました。ありがとうございます。
>数値に変換したあとの lop/ropを使って printfしていますから。
>普通に文字列 stを表示すればいいのではないですか?
確かにその通りですね。固定観念はいけないですねw
>parse()の戻り値をチェックしていない点でしょうか。
>因子に不正な文字が入っていてもそれを無視してしまっています。
戻り値のチェックとはどういったことをすればよいのでしょうか?
>このチェックが int型変換時の intで扱える範囲を超えた場合に対するチェックであれば
>parse_factor()内の strtol()を使っているところでチェックするといいでしょう。
了解しました。ありがとうございます。
>数値に変換したあとの lop/ropを使って printfしていますから。
>普通に文字列 stを表示すればいいのではないですか?
確かにその通りですね。固定観念はいけないですねw
>parse()の戻り値をチェックしていない点でしょうか。
>因子に不正な文字が入っていてもそれを無視してしまっています。
戻り値のチェックとはどういったことをすればよいのでしょうか?
Re:無題
>先にも書きましたが、parse関数は成功したら 1を返すよう修正してください
すみませんでした。NULLチェックを省きました。
// 解析
static int parse(const char *expression, const char **end, long long *pValue)
{
// 要素解析
const char *p = expression;
if(parse_factor(&p, pValue))
{
// 解析が終了した文字列の位置を記録
p = skip_parce(p);
if(end) *end = p;
// 解析が終わってもう文字が残っていなければ成功
if(*p == ASCII_NUL)
return 1;
}
return 0;
}
こうでいいんですよね? ただ、減算、乗算、除算がうまくいきません。
すみませんでした。NULLチェックを省きました。
// 解析
static int parse(const char *expression, const char **end, long long *pValue)
{
// 要素解析
const char *p = expression;
if(parse_factor(&p, pValue))
{
// 解析が終了した文字列の位置を記録
p = skip_parce(p);
if(end) *end = p;
// 解析が終わってもう文字が残っていなければ成功
if(*p == ASCII_NUL)
return 1;
}
return 0;
}
こうでいいんですよね? ただ、減算、乗算、除算がうまくいきません。
Re:無題
Justyさん。Compさん。レスありがとうございます。
"Justyさん"
>parseの戻り値をチェックしてください。
>チェックして、問題があれば(0が戻ってきている)エラーとしてください。
本当に申し訳ありません。具体的にどのようにすればよいのか分かりません。
if等で指定すればいいのでしょうか?
"Compさん"
>ちょっとした処理の忘れ物があるだけです。
う~ん。分かりません。ステップさせてみたんですが、ここがこうでという
筋道を立てて理解することが出来ていないので、最終的な結果しか分からないです。
ちなみにその処理とはmain関数内ではないんですよね?
"Justyさん"
>parseの戻り値をチェックしてください。
>チェックして、問題があれば(0が戻ってきている)エラーとしてください。
本当に申し訳ありません。具体的にどのようにすればよいのか分かりません。
if等で指定すればいいのでしょうか?
"Compさん"
>ちょっとした処理の忘れ物があるだけです。
う~ん。分かりません。ステップさせてみたんですが、ここがこうでという
筋道を立てて理解することが出来ていないので、最終的な結果しか分からないです。
ちなみにその処理とはmain関数内ではないんですよね?
Re:無題
>ちなみにまだ戻り値のチェックは出来ていません
>if等で指定すればいいのでしょうか
一昨日あたりに書いたと思ったのですが、ifでチェックして下さい。
>上手く動きません
できれば何がどう旨く動かないのか書いて欲しいところですが。
でもまぁ大体推測はつきます。
[color=#d0d0ff" face="aria[/url]if ( *end != '\0' )[/color]
NUL文字でなければエラーにするのは多分数式の後に余計な文字があるかどうかを
みようとしたのだと思いますが、それでは最初の parseチェックでエラーになってしまいますよ。
2回目の parseが終わった後、チェックするようにすれば式が最後まで解析されたかどうか判ると思います。
>if等で指定すればいいのでしょうか
一昨日あたりに書いたと思ったのですが、ifでチェックして下さい。
>上手く動きません
できれば何がどう旨く動かないのか書いて欲しいところですが。
でもまぁ大体推測はつきます。
[color=#d0d0ff" face="aria[/url]if ( *end != '\0' )[/color]
NUL文字でなければエラーにするのは多分数式の後に余計な文字があるかどうかを
みようとしたのだと思いますが、それでは最初の parseチェックでエラーになってしまいますよ。
2回目の parseが終わった後、チェックするようにすれば式が最後まで解析されたかどうか判ると思います。
Re:無題
Justyさん。お世話になっております。
レスが遅れて申し訳ありませんでした。
戻り値のチェックですが、こんな感じでいいでしょうか?
レスが遅れて申し訳ありませんでした。
戻り値のチェックですが、こんな感じでいいでしょうか?
static int Parse (const char *expression, const char **end, long long *pValue) { // 要素解析 const char *p = expression; if (Parse_factor (&p, pValue)) { // 解析が終了した文字列の位置を記録 p = Skip_parce (p); if (end) *end = p; return 1; } if (Parse == 0) { printf("error : return error\n"); } return 0; }
Re:無題
>戻り値のチェックですが、こんな感じでいいでしょうか
まぁ場所的にはそこでも間違いではないですが、そのチェック位置に
来たときは常にエラーです。
つまりそこでチェックする場合、エラーチェックそのものが不要になります。
ただ、parseの再利用性を考えるならそこでチェックせず、main側で行った方がいいでしょう。
せっかく parse関数は成功すれば 1が、失敗すれば 0が戻ってきているのですから。
>if (Parse == 0)
ちなみにこの if文は常に偽となります。
意味がありません。
まぁ場所的にはそこでも間違いではないですが、そのチェック位置に
来たときは常にエラーです。
つまりそこでチェックする場合、エラーチェックそのものが不要になります。
ただ、parseの再利用性を考えるならそこでチェックせず、main側で行った方がいいでしょう。
せっかく parse関数は成功すれば 1が、失敗すれば 0が戻ってきているのですから。
[color=#d0d0ff" face="monospace]
if(!parse (st, &p, &lop))
printf("error\n");[/color]
>if (Parse == 0)
ちなみにこの if文は常に偽となります。
意味がありません。
Re:無題
Justyさん。keichanさん。レスありがとうございました。
>if (Parse == 0)
>ちなみにこの if文は常に偽となります。
>意味がありません。
掘り下げて論理式を調べてみました。今更ですが、
間違った考え方をしていました。指摘ありがとうございます。
// 文字列stの解析 Parse (st, &p, &lop); // opに演算子を代入 op = p[0]; p++; // 文字列pの解析 Parse (p, &p, &rop); if (!Parse (st, &p, &lop)) { printf("error\n"); }これで問題無いですよね?
>if (Parse == 0)
>ちなみにこの if文は常に偽となります。
>意味がありません。
掘り下げて論理式を調べてみました。今更ですが、
間違った考え方をしていました。指摘ありがとうございます。
Re:無題
>ちょっとおかしいと思うのですが、何処がいけないのでしょうか?
あー、なるほど。
たしかにこのままではその値は扱えませんね。
Parse_factor()を見るとわかると思いますが、符号があった場合
ひとまずスキップして数字文字を見つけようとします。
で、その数字文字を整数化した後、符号に従って符号変換をします。
つまり、-2147483648の場合、符号を無視した数字 2147483648が INT_MAXを超える為、
strtolの結果が INT_MAXである 2147483647となり、その後符号を反転するので
-2147483647になってしまいます。
一番簡単な解決方法としては(機種依存になりますが) strtolの代わりに
_strtoi64を使って解析し、その後、lop / ropが INT_MIN~INT_MAXの範囲内に
あることをチェックするのが一番簡単だと思います。
あー、なるほど。
たしかにこのままではその値は扱えませんね。
Parse_factor()を見るとわかると思いますが、符号があった場合
ひとまずスキップして数字文字を見つけようとします。
で、その数字文字を整数化した後、符号に従って符号変換をします。
つまり、-2147483648の場合、符号を無視した数字 2147483648が INT_MAXを超える為、
strtolの結果が INT_MAXである 2147483647となり、その後符号を反転するので
-2147483647になってしまいます。
一番簡単な解決方法としては(機種依存になりますが) strtolの代わりに
_strtoi64を使って解析し、その後、lop / ropが INT_MIN~INT_MAXの範囲内に
あることをチェックするのが一番簡単だと思います。
Re:無題
>ちなみにエラーメッセージをまとめるには もっといい方法があるんでしょうか?
今は用途別に関数を作っているのですね。
見たところ、exitに渡す終了コードと文字列以外には違いはないようです。
であれば、終了コードとエラーメッセージの文字列を引数に受け取るエラー関数を
用意してみてはどうでしょうか。
[color=#d0d0ff" face="aria[/url]int error_exit(int exit_code, const char *message);[/color]
こんな関数を用意して、エラーを検出したら
[color=#d0d0ff" face="aria[/url]error_exit(-1, "0 division is a prohibition");[/color]
のように使うカンジで。
#余裕があったら
int error_exit(int exit_code, const char *format, ...);
の形式にチャレンジしてもいいかもしれません。
今は用途別に関数を作っているのですね。
見たところ、exitに渡す終了コードと文字列以外には違いはないようです。
であれば、終了コードとエラーメッセージの文字列を引数に受け取るエラー関数を
用意してみてはどうでしょうか。
[color=#d0d0ff" face="aria[/url]int error_exit(int exit_code, const char *message);[/color]
こんな関数を用意して、エラーを検出したら
[color=#d0d0ff" face="aria[/url]error_exit(-1, "0 division is a prohibition");[/color]
のように使うカンジで。
#余裕があったら
int error_exit(int exit_code, const char *format, ...);
の形式にチャレンジしてもいいかもしれません。
Re:無題
>何かありましたらご指摘お願いします
んーと
・ exitの多用
どのルートを経由しても必ず exitで終わらせるのは、どうなんでしょう。
exitしなければならない状態というのはよほどの異常事態です。
できれば exitすることなく、普通に main関数を抜けるようにした方が、
いいのではないでしょうか。
・ エラーコードが直値
定数を直接コードの中に埋め込むのはあまり良い方法とはいえません。
例えばバッファサイズの 128とか、エラーコードとか。
特に後者は exit_codeが 1とか言われてもさっぱり意味がわからないので、
何かしら defineや enumなどで名前を割り振っておくといいでしょう。
・ エラーの場合、式が表示されないことある。
式の中にエラーがあった場合でも、入力された式を表示してから
エラーとした方がいいのではないでしょうか。
・ char st[128]
mainの引数から 128文字以上入力されると Storage()内でバッファをオーバーしてしまい
不正なプログラムになります。
128文字以上にならないようにしましょう。
・ lop / ropの範囲チェック
四則演算の結果が出てからチェックしていますが、する前にした方がいいのではないでしょうか。
・ 式の最後に余計な文字があった場合
"1+1a"のように最後に余計な文字があっても解析が成功しますが、これはいいのでしょうか。
・ テスト
コードを書いてからビルドして、その後テストをするとき、
毎回毎回式をコマンドラインでキーボードから打つか or MSVCのコマンドライン引数の設定で
記入した内容を main関数の引数に渡していると思うのですが、面倒くさくないですか?
そういうときはデバッグ向けに式をプログラムの中に埋め込んで、
自動的にテストできるようにしてはどうでしょうか。
ついでにテスト用の式を配列にしておいて、複数の式を順次解析、結果表示していくように
していくと、様々なケースのテストを一度に行うことができますよ。
参考までに改良したコードを載せておきます。
(添付のコードの mainの中の #if 1を #if 0にするとこのテストモードになります)
んーと
・ exitの多用
どのルートを経由しても必ず exitで終わらせるのは、どうなんでしょう。
exitしなければならない状態というのはよほどの異常事態です。
できれば exitすることなく、普通に main関数を抜けるようにした方が、
いいのではないでしょうか。
・ エラーコードが直値
定数を直接コードの中に埋め込むのはあまり良い方法とはいえません。
例えばバッファサイズの 128とか、エラーコードとか。
特に後者は exit_codeが 1とか言われてもさっぱり意味がわからないので、
何かしら defineや enumなどで名前を割り振っておくといいでしょう。
・ エラーの場合、式が表示されないことある。
式の中にエラーがあった場合でも、入力された式を表示してから
エラーとした方がいいのではないでしょうか。
・ char st[128]
mainの引数から 128文字以上入力されると Storage()内でバッファをオーバーしてしまい
不正なプログラムになります。
128文字以上にならないようにしましょう。
・ lop / ropの範囲チェック
四則演算の結果が出てからチェックしていますが、する前にした方がいいのではないでしょうか。
・ 式の最後に余計な文字があった場合
"1+1a"のように最後に余計な文字があっても解析が成功しますが、これはいいのでしょうか。
・ テスト
コードを書いてからビルドして、その後テストをするとき、
毎回毎回式をコマンドラインでキーボードから打つか or MSVCのコマンドライン引数の設定で
記入した内容を main関数の引数に渡していると思うのですが、面倒くさくないですか?
そういうときはデバッグ向けに式をプログラムの中に埋め込んで、
自動的にテストできるようにしてはどうでしょうか。
ついでにテスト用の式を配列にしておいて、複数の式を順次解析、結果表示していくように
していくと、様々なケースのテストを一度に行うことができますよ。
参考までに改良したコードを載せておきます。
(添付のコードの mainの中の #if 1を #if 0にするとこのテストモードになります)
Re:無題
Justyさん。レスありがとうございます。
ソース拝見しました。さすがJustyさん。
あぁ~やはりプロっぽいコーディングですね。
質問があるのですが、エラーコードの数とエラーメッセージ一覧の個数が
一致していないと思うのですが、それは何故なんでしょうか?
// テーブルの数とエラーコードの数が一致するかどうか検証
// 範囲外なら codeを 0に
というコメント以下はどういうふうに読めばいいのでしょうか?
ソース拝見しました。さすがJustyさん。
あぁ~やはりプロっぽいコーディングですね。
質問があるのですが、エラーコードの数とエラーメッセージ一覧の個数が
一致していないと思うのですが、それは何故なんでしょうか?
// テーブルの数とエラーコードの数が一致するかどうか検証
// 範囲外なら codeを 0に
というコメント以下はどういうふうに読めばいいのでしょうか?
// エラーコード enum { ERROR_CODE_NO_ERROR, ERROR_CODE_INVALID_EXPRESSION, ERROR_CODE_INT_OUT_OF_RANGE, ERROR_CODE_INVALID_OPERATOR, ERROR_CODE_DIVIDED_ZERO, ERROR_CODE_MULTI_OVERFLOW, ERROR_CODE_NUMBER_OF_UNUSUAL_ARGS, ERROR_CODE_STRING_BUFFER_OVER, ERROR_CODE_MAX }; // エラーメッセージ一覧 void ErrorMessage(int code) { static const char * const message_list[/url] = { "unknown", "Please input a correct expression", "The range of the int type is exceeded", "Please input the operator correctly", "0 division is a prohibition", "The over_flow learns by experience", "Please input the argument correctly", "Buffers of character string overflowed.", }; // テーブルの数とエラーコードの数が一致するかどうか検証 StaticAssert(ArraySize(message_list) == ERROR_CODE_MAX, message_list_size_and_ERROR_CODE_MAX_is_not_equal); // 範囲外なら codeを 0に if(code < 1 && code>=ArraySize(message_list)) code = 0; printf("error : %s\n", message_list[code]); }
Re:無題
>質問があるのですが、エラーコードの数とエラーメッセージ一覧の個数が
>一致していないと思うのですが、それは何故なんでしょうか?
一致していないように見えるだけだと思います。
エラーメッセージ message_listの配列要素数は見ての通り8個あります。
で、エラーコードは
計7個ですが、enumにはエラーではないことを示すERROR_CODE_NO_ERRORもあるので
計8個となり、message_listの要素数と同じだけあります。
ERROR_CODE_MAXは、特殊な使い方をするもので、エラーコード
としては使いません。
実はエラーコードの数を表すのに使われます。
>というコメント以下はどういうふうに読めばいいのでしょうか?
asdさんの書かれている通りです。
このErrorMessage関数から見ると codeに何が入ってくるかは予想できないので、
配列に対して不正なアクセスをしないようにしています。
例えば codeが 100とか -100とかの状態で message_list
>一致していないと思うのですが、それは何故なんでしょうか?
一致していないように見えるだけだと思います。
エラーメッセージ message_listの配列要素数は見ての通り8個あります。
で、エラーコードは
[color=#d0d0ff" face="monospace]
ERROR_CODE_NO_ERROR, // 0・・・エラーではない
ERROR_CODE_INVALID_EXPRESSION, // 1・・・何かのエラー
ERROR_CODE_INT_OUT_OF_RANGE, // 2・・・何かのエラー
ERROR_CODE_INVALID_OPERATOR, // 3・・・何かのエラー
ERROR_CODE_DIVIDED_ZERO, // 4・・・何かのエラー
ERROR_CODE_MULTI_OVERFLOW, // 5・・・何かのエラー
ERROR_CODE_NUMBER_OF_UNUSUAL_ARGS, // 6・・・何かのエラー
ERROR_CODE_STRING_BUFFER_OVER, // 7・・・何かのエラー
ERROR_CODE_MAX // 8・・・エラーコードの数[/color]
で、実際にエラーとして使われるのはERROR_CODE_NO_ERROR(1)~ERROR_CODE_STRING_BUFFER_OVER(7)の計7個ですが、enumにはエラーではないことを示すERROR_CODE_NO_ERRORもあるので
計8個となり、message_listの要素数と同じだけあります。
ERROR_CODE_MAXは、特殊な使い方をするもので、エラーコード
としては使いません。
実はエラーコードの数を表すのに使われます。
>というコメント以下はどういうふうに読めばいいのでしょうか?
asdさんの書かれている通りです。
このErrorMessage関数から見ると codeに何が入ってくるかは予想できないので、
配列に対して不正なアクセスをしないようにしています。
例えば codeが 100とか -100とかの状態で message_list
初歩の初歩で・・・・
今、僕は「VisualC++ 2005」を用いて、0「導入」より始めようとしたのですが
何度やっても、「DxLib.sln」をビルドすると
『ビルド エラーが発生しました。続行して、最後に成功したビルドを実行しますか?』
と出て、「はい」とすると・・・
『プログラム'C:\Documents and Settings\****\デスクトップ\DxLib_VC
\サンプルプログラム実行用フォルダ\Debug\DxLib.exe'を開始できません。
指名されたファイルが見つかりません。』
と出てしまい、どうしようもないのです。
ちなみに「DxLib.sln」を開く前には変換ヴィザードが出ました。
「VisualC++」最新だからでしょうか教えてください。
何度やっても、「DxLib.sln」をビルドすると
『ビルド エラーが発生しました。続行して、最後に成功したビルドを実行しますか?』
と出て、「はい」とすると・・・
『プログラム'C:\Documents and Settings\****\デスクトップ\DxLib_VC
\サンプルプログラム実行用フォルダ\Debug\DxLib.exe'を開始できません。
指名されたファイルが見つかりません。』
と出てしまい、どうしようもないのです。
ちなみに「DxLib.sln」を開く前には変換ヴィザードが出ました。
「VisualC++」最新だからでしょうか教えてください。
Re:初歩の初歩で・・・・
お使いのコンパイラは製品版ですか?
それともフリーのExpressEditionですか?
前者でしたら何も設定なしに出来ますが、後者では設定が必要です。
http://homepage2.nifty.com/natupaji/DxLib/dxuse.html
この辺ご参考にどうぞ。
それともフリーのExpressEditionですか?
前者でしたら何も設定なしに出来ますが、後者では設定が必要です。
http://homepage2.nifty.com/natupaji/DxLib/dxuse.html
この辺ご参考にどうぞ。
Re:初歩の初歩で・・・・
突然すいません。
visual studio2005でCtrl+F5と押してコンパイル?をすると、”指定されたファイルは見つかりません。”と出てきます。
おそらくコンパイルは出来ているのだと思うんですが、実行結果が出てきません。
どうしたらいいのか教えてください。
visual studio2005でCtrl+F5と押してコンパイル?をすると、”指定されたファイルは見つかりません。”と出てきます。
おそらくコンパイルは出来ているのだと思うんですが、実行結果が出てきません。
どうしたらいいのか教えてください。