前回予告しました通り、出来上がったC++版をC#(Ver 5)へ移植してみました。C++版との違いなど、ご覧あれ。尚、Calc_Int.csはDLL版としてコンパイルするものです。そのまま実行ファイル(*.exe)にするにはMain関数でCalcインスタンスを作って、TryCalcメソッドを実行してください。
【Calc_Int.cs】
////////////////////////////////////////////////
// Calc Class - Evaluates String Expression and
// retruns end-result (integer value).
////////////////////////////////////////////////
/*
【Calc仕様】
1.四則演算子(+, -, *, /, %-opecode配列に収納)を用いた(整数の)算術計算式の値を取得する。
2.計算は左から右へ行う。
3.優先順位は単項演算子(+-)→()→*、/、%→+、-の順
4.値は10進数、16進数(Int32.ParseとSystem.Globalization.NumberStyles.HexNumberを使用)//https://learn.microsoft.com/ja-jp/dotnet/api/system.globalization.numberstyles?view=net-8.0
5.二項式は"operand0 四則演算子(+, -, *, /, %)operand1"とする。operand0と1は(空白文字を除く)文字列、その値はopevalu0と1整数とする。
6.最初に出てくること有るべき単項式(operand0が空文字""でopeval0が0)は単に"operand0 加減演算子(+, -)operand1"で処理し、*、/、%はエラーとする。
7.値はNumber(10進数)、HexNumber(16進数)対応とし、HexNumberには頭に"0x"か"0X"をつける必要がある。
【参考サイト】
String Class - https://learn.microsoft.com/ja-jp/dotnet/api/system.string?view=net-8.0
Chars[Int32] プロパティ
Length プロパティ
Substring(Int32 pos, Int32 len)
Char Class - https://learn.microsoft.com/ja-jp/dotnet/api/system.char?view=net-8.0
IsWhiteSpaceメソッド
Int32.TryParseメソッド
https://learn.microsoft.com/ja-jp/dotnet/api/system.int32.tryparse?view=net-9.0#system-int32-tryparse(system-string-system-globalization-numberstyles-system-iformatprovider-system-int32@)
*/
using System;
using System.Globalization; //NumberStyle使用の為(オリジナルコードであれば不要)
namespace Calc_Int
{
public class Calc
{
//クラス内定数
const int ERR = -1;
//メンバー変数
public string Formula {get; set;} //演算式記録用
/////////////////////
//コンストラクター
/////////////////////
public Calc(string str = "")
{
if(str != string.Empty)
Formula = str; //strをFormulaにコピー
}
///////////////////////////////////////////////
//演算制御を行う主関数(+または-の処理を行う)
//演算に成功するとtrue、失敗するとfalseを返す。
//参照変数sol(ution)に解が入る。
///////////////////////////////////////////////
public bool TryCalc(ref int sol, string str = "")
{
//ローカル変数
int ptr, start, end; //順に演算式用、切り取り用ポインター
int opeval = 0; //式評価用オペランド値
int opec = 0; //オペコード(+は1、-は2)
bool post_opec = false; //「オペコード後」サイン
string operand; //文字列切り取り用
//初期化
sol = 0; //解(solution)が入る参照変数を初期化
if(str == string.Empty) //strが既定値の空文であれば
str = Formula; //計算式(str)をFormulaとする
else //strに指定があれば
Formula = str; //strをFormulaにコピー
ptr = 0; //ポインターを演算式文字列の起点に初期化
end = start = ERR; //切り取りポインター初期化(値は0~str.Length - 1)
//ループ処理
for(ptr = 0; ptr < str.Length; ptr++) //strの最後まで継続(C#はヌル終端でない為、文字列長で判断)
{
//空白処理
if(Char.IsWhiteSpace(str, ptr))
{
if(start > ERR && end == ERR) //startは設定され、endは設定されていない場合
end = ptr - 1; //endを空白文字直前に設定
}
//ポインターの指す文字により処理を振り分ける
else
{
switch(str[ptr])
{
case '+': //'+'の場合
case '-': //'-'の場合
if(post_opec) //オペレーター後にオペレーターが続けば
return false; //エラー
if(start == ERR) //単項演算(start == ERR)の場合
start = ptr + 1; //startを+-符号(Chars[0])の次に進める
else //二項演算で
{
if(end == ERR) //オペランド未設定(end == ERR)ならば
end = ptr - 1; //'+'、'-'演算子の直前に設定
//文字列切り取り-string.Substring(int startIndex, int length);
operand = str.Substring(start, end - start + 1);
if(!TryParse(ref opeval, operand)) //整数値でない場合
{
if(!TryMulDiv(ref opeval, operand)) //乗除演算が不可能
return false; //エラー
}
}
if(opec == 0) //初期値の場合(単項式の場合
sol = opeval; //第1オペランドであるsolに代入する
else if(opec == 1) //'+'の場合
sol += opeval; //(単項演算子を含む)加算または
else if(opec == 2) //'-'の場合
sol -= opeval; //(単項演算子を含む)減算を行う
if(str[ptr] == '+') //新しいオペコードをopecに設定
opec = 1; //'+'の設定
else if(str[ptr] == '-')
opec = 2; //'-'の設定
end = start = ERR; //切り取りポインターの初期化
post_opec = true; //オペレーター後フラグ
break;
case '*': //'*'の場合
case '/': //'/'の場合
case '%': //'%'の場合
if(start == ERR) //最初に'*'、'/'、'%'があれば
return false; //エラー
if(post_opec) //オペレーター後にオペレーターが続けば
return false; //エラー
end = start; //end == start(defaultでの処理の為)にして
post_opec = true; //オペレーター後フラグ
break;
default: //'+'、'-'、'*'、'/'、'%'以外の非空白文字
if(start == ERR) //startが未設定であれば
start = ptr; //非空白文字の先頭に設定
else if(start == end) //'*'、'/'、'%'があった場合の次の非空白文字
end = ERR; //オペランド未設定(end == nullptr)に戻す
else if(start > ERR && end > ERR) //start、end共に設定済で演算子以外の場合
return false; //構文エラー
if(str[ptr] == '(') //括弧の場合
{
if(!SkipBraces(str, ref ptr))//括弧をスキップし、ポインターは')'を指す
return false; //括弧が閉じられていなければ構文エラー
end = ptr; //endも')'を指す
}
post_opec = false; //オペランド後なので、オペレーター後フラグを戻す
break;
}
}
}
//ポインターを進めて終端になった場合(+-のラップアップ処理)
if(post_opec) //オペレーター後にオペレーターが続けば
return false; //エラー
if(end == ERR) //endが未設定であれば
end = ptr - 1; //終端の直前に設定
//文字列切り取り-string.Substring(int startIndex, int length);
operand = str.Substring(start, end - start + 1);
if(!TryParse(ref opeval, operand)) //整数値でない場合
{
if(!TryMulDiv(ref opeval, operand)) //乗除演算が不可能
return false; //エラー
}
if(opec == 0) //初期値の場合
sol = opeval; //第1オペランドであるsolに代入する
else if(opec == 1) //'+'の場合
sol += opeval; //加算または
else if(opec == 2) //'-'の場合
sol -= opeval; //減算を行う
return true;
}
////////////////
//乗除演算関数
////////////////
private bool TryMulDiv(ref int sol, string str)
{
//ローカル変数
int ptr, start, end; //順に演算式用、切り取り用ポインター
int opeval = 0; //式評価用オペランド値
int opec = 0; //オペコード(*は1、/は2、%は3)
string operand; //文字列切り取り用
//初期化
sol = 0; //解(solution)が入る参照変数を初期化
ptr = 0; //ポインターを演算式文字列の起点に初期化
end = start = ERR; //切り取りポインター初期化(値は0~str.Length - 1)
//ループ処理
for(ptr = 0; ptr < str.Length; ptr++) //strの最後まで継続(C#はヌル終端でない為、文字列長で判断)
{
//空白処理
if(Char.IsWhiteSpace(str, ptr))
{
if(start >= 0 && end < 0) //startは設定され、endは設定されていない場合
end = ptr - 1; //endを空白文字直前に設定
}
//ポインターの指す文字により処理を振り分ける
else
{
switch(str[ptr])
{
case '*': //'*'の場合
case '/': //'/'の場合
case '%': //'%'の場合
if(str[start] == '(') //startが'('であれば
{
start++; //startを'('の次に(endは')'の直前)進める
//文字列切り取り-string.Substring(int startIndex, int length);
operand = str.Substring(start, end - start + 1);
if(!TryCalc(ref opeval, operand)) //再帰処理
return false; //処理不能であればエラー
}
else
{
if(end < 0) //オペランド未設定(end == ERR)ならば
end = ptr - 1; //'*'、'/'、'%'演算子の直前に設定
//文字列切り取り-string.Substring(int startIndex, int length);
operand = str.Substring(start, end - start + 1);
if(!TryParse(ref opeval, operand)) //整数値でない場合
return false; //エラー
}
if(opec == 0) //初期値の場合
sol = opeval; //第1オペランドであるsolに代入する
else if(opec == 1) //'*'の場合
sol *= opeval; //乗算、
else if(opec == 2) //'/'の場合
sol /= opeval; //除算または
else if(opec == 3) //'%'の場合
sol %= opeval; //剰余算を行う
if(str[ptr] == '*') //新しいオペコードを設定
opec = 1; //'*'の設定
else if(str[ptr] == '/')
opec = 2; //'/'の設定
else if(str[ptr] == '%')
opec = 3; //'%'の設定
end = start = ERR; //切り取りポインターの初期化
break;
default: //'*'、'/'、'%'以外の非空白文字
if(start < 0) //startが未設定であれば
start = ptr; //非空白文字の先頭に設定
else if(start >= 0 && end >= 0) //start、end共に設定済で演算子以外の場合
return false; //構文エラー
if(str[ptr] == '(') //括弧の場合
{
if(!SkipBraces(str, ref ptr)) //括弧をスキップし、ポインターは')'を指す
return false; //括弧が閉じられていなければ構文エラー
end = ptr - 1; //endに')'の前のアドレスを記録
}
break;
}
}
}
//ポインターを進めて終端になった場合(ラップアップ処理)
if(str[start] == '(') //startが'('であれば
{
start++; //startを'('の次に(endは')'の直前)進める
//文字列切り取り-string.Substring(int startIndex, int length);
operand = str.Substring(start, end - start + 1);
if(!TryCalc(ref opeval, operand)) //再帰処理
return false; //処理不能であればエラー
}
else
{
if(end < 0) //オペランド未設定(end == ERR)ならば
end = ptr - 1; //'*'、'/'、'%'演算子の直前に設定
//文字列切り取り-string.Substring(int startIndex, int length);
operand = str.Substring(start, end - start + 1);
if(!TryParse(ref opeval, operand)) //整数値でない場合
return false; //エラー
}
if(opec == 0) //初期値の場合
sol = opeval; //第1オペランドであるsolに代入する
else if(opec == 1) //'*'の場合
sol *= opeval; //乗算、
else if(opec == 2) //'/'の場合
sol /= opeval; //除算または
else if(opec == 3) //'%'の場合
sol %= opeval; //剰余算を行う
return true;
}
と、
書いてきたら「6,000字超えたから」と無情にもエラーが...
後は
(2)へ続く。




















