我的计算器——2 记号对象
来源:互联网 发布:中国为何封锁外国网络 编辑:程序博客网 时间:2024/05/22 05:23
/// <summary> /// 记号值类型 /// </summary> public enum TokenValueTypeEnum { String, Number, } /// <summary> /// 记号记录 /// </summary> public abstract class TokenRecord { #region 属性和字段 //下级个数 protected int m_ChildCount; private int m_Index; /// <summary> /// 列序号 /// </summary> public int Index { get { return m_Index; } } /// <summary> /// 优先级,必须赋值 /// </summary> protected int m_Priority; /// <summary> /// 优先级 /// </summary> /// <returns></returns> public int Priority { get { return m_Priority; } } private TokenValueTypeEnum m_TokenValueType; /// <summary> /// 记号值类型,字符串/数值 /// </summary> public TokenValueTypeEnum TokenValueType { get { return m_TokenValueType; } set { m_TokenValueType = value; } } private string m_TokenString; /// <summary> /// 记号字符串 /// </summary> public string TokenString { get { return m_TokenString; } set { m_TokenString = value; } } private double m_TokenNumber = 0; /// <summary> /// 记号数值 /// </summary> public double TokenNumber { get { return m_TokenNumber; } set { m_TokenNumber = value; } } private List<TokenRecord> m_ChildList = new List<TokenRecord>(); /// <summary> /// 下级列表 /// </summary> public List<TokenRecord> ChildList { get { return m_ChildList; } set { m_ChildList = value; } } #endregion /// <summary> /// 构造函数 /// </summary> /// <param name="Index">序号</param> public TokenRecord(int Index) { this.m_Index = Index; this.SetPriority(); this.SetChildCount(); } /// <summary> /// 重写ToString方法 /// </summary> /// <returns></returns> public override string ToString() { return this.GetType().Name + "_" + GetValueString() + "_" + TokenValueType.ToString(); } /// <summary> /// 获取值的字符串表示 /// </summary> /// <returns></returns> public string GetValueString() { return this.TokenValueType == TokenValueTypeEnum.String ? this.TokenString : this.TokenNumber.ToString(); } /// <summary> /// 执行代码,必须重写 /// </summary> public abstract void Execute(); /// <summary> /// 设置下级数量 /// </summary> protected abstract void SetChildCount(); /// <summary> /// 设置优先级 /// </summary> protected abstract void SetPriority(); /// <summary> /// 检查下级数量,必要时可以重写,因为有些Token的下级数量可以是一个区间 /// </summary> /// <param name="ErrorInformation">下级数量不符时显示的错误信息</param> protected void CheckChildCount(string ErrorInformation) { if (this.m_ChildList.Count != this.m_ChildCount) { throw new ArgumentException(string.Format("语法错误,列{0},{1}。", this.m_Index.ToString(), ErrorInformation)); } } #region 转换记号值类型 /// <summary> /// 将记号值转换为字符串类型 /// </summary> internal void ChangeTokenToString() { if (this.TokenValueType == TokenValueTypeEnum.Number) { this.TokenValueType = TokenValueTypeEnum.String; this.TokenString = this.TokenNumber.ToString(); } } /// <summary> /// 将记号值转换为数字类型 /// </summary> /// <param name="ErrorInformation">无法转换成数字时显示的错误信息</param> internal void ChangeTokenToDouble(string ErrorInformation) { if (this.TokenValueType == TokenValueTypeEnum.String) { this.TokenValueType = TokenValueTypeEnum.Number; double dblValue; if (double.TryParse(this.TokenString, out dblValue)) { this.TokenNumber = dblValue; } else { throw new ArithmeticException(string.Format("语法错误,列{0},{1}。" ,this.m_Index, ErrorInformation));//throw exception } } } /// <summary> /// 将记号值转换为逻辑值 /// </summary> internal void ChangeTokenToBoolean() { if (this.TokenValueType == TokenValueTypeEnum.String) { //Error or if string is not empty then value is true,else value is false this.TokenValueType = TokenValueTypeEnum.Number; switch (this.TokenString.Trim().ToLower()) { case "true": this.TokenNumber = 1; break; case "false": case "": this.TokenNumber = 0; break; default: this.TokenNumber = 1; break; } this.TokenNumber = this.TokenString.Trim().Equals(string.Empty) ? 0 : 1; } else { this.TokenNumber = (this.TokenNumber == 0 ? 0 : 1); } } #endregion }
设计出基类TokenRecord后,其他记号类都可以从它继承,然后实现自己的特定计算方法即可。那么实际的表达式中有哪些类型的记号呢?经过分析得出,表达式中所包含的的操作元素无非这几种:关键字,运算符,字符串,数字。
关键字:if,sin,cos,true,false等以英文字母开头的词,其中可以包含数字或者其他允许的符号
运算符:+,-,*,/,/,>,<,>=,<=,&&,||,!等,纯符号操作元素,可以由好几个字符组成
字符串:"hello",'good',"where's my book"等,由字符串标识符单引号或者双引号标识的字符串。字符串中允许包含当前字符串标识符之外另一种字符串标识符,即双引号字符串中可以包含单引号,单引号字符串中可以包含双引号,这一点和JavaScript类似。如果在字符串中包含当前标识符,则必须用连续两个标识符进行转义。
数值:12,856,42.123,-62.45,允许包含小数点和负数。
进一步分析发现,字符串和数值有一定的相似性,就是它们并不需要计算,只需要作为一个存储单元即可。那么就可以将它们合并,称为值记号对象TokenValue。其他记号对象在Execute中实现自己的特定算法,而值记号对象的Execute方法中不做任何操作。
按照上面对操作元素的分类,从TokenRecord类衍生出一下几个类:TokenKeyword(对应关键字,抽象类), TokenSymbol(对应运算符,抽象类), TokenValue(对应字符串和数值)。类图如下:
然后再从这些类中衍生出更多的具体的类,比如TokenIf, TokenSin, TokenCos, TokenPlus, TokenMinus。
以这个表达式为例,23.5+(54/3-9)*2,分析出来的TokenList记号对象列表就是
记号对象 对应表达式 TokenValue 23.5 TokenPlus TokenLeftBracket TokenValue TokenDivide TokenValue TokenMinus TokenValue TokenRightBracket TokenMultiply TokenValue
这样就把传入的表达式字符串表示成程序可以识别的对象了,然后再对这些记号对象进行处理,最终得到计算结果。这一节只是介绍记号对象,至于怎么将表达式转换成记号对象,下一篇再详细介绍。
接下来是如何实现具体的TokenRecord类。以乘法记号类TokenMultiply为例,TokenMultiply继承自TokenArithmetic,TokenArithmetic继承自TokenSymbol,TokenSymbol继承自TokenRecord。类图如下:
TokenSymbol类也是一个抽象类,只是将部分重复的设置实现了,减少其子类中重复的代码,TokenSymbol类的代码如下:
public abstract class TokenSymbol : TokenRecord { public TokenSymbol(int Index) : base(Index) { this.TokenValueType = TokenValueTypeEnum.Number;//部分操作符必须根据实际修改 } protected override void SetChildCount() { this.m_ChildCount = 2;//Not必须根据实际修改 } public abstract override void Execute(); }
TokenArithmetic类则是个完全的抽象类,只是为了进行分类而已,任何功能都没实现,其代码如下:
public abstract class TokenArithmetic : TokenSymbol { public TokenArithmetic(int Index) : base(Index) { } public abstract override void Execute(); }
最后是TokenMultiply类,它实现具体的乘法操作,代码如下:
public class TokenMultiply : TokenArithmetic { public TokenMultiply(int Index) : base(Index) { } protected override void SetPriority() { this.m_Priority = 3;//设置优先级,这里必须根据实际情况设置,保证优先级的正确性 } public override void Execute() { this.CheckChildCount("乘法的运算元素数量不合法");//检查下级数量,不合法则抛出错误 //经过上面的检查后,可以保证其包含两个下级 TokenRecord TokenFirst = this.ChildList[0]; TokenRecord TokenSecond = this.ChildList[1]; //对下级求值,比如5*sin(45),就必须先对sin求值 TokenFirst.Execute(); TokenSecond.Execute(); //保证乘法下级是数值,否则无法进行计算,如果转换失败则抛出错误 TokenFirst.ChangeTokenToDouble("乘法的运算元素不是数值"); TokenSecond.ChangeTokenToDouble("乘法的运算元素不是数值"); //执行实际的运算并保存计算得到的值 this.TokenNumber = TokenFirst.TokenNumber * TokenSecond.TokenNumber; } }
TokenRecord中的三个虚方法SetChildCount, SetPriority, Execute都得到了实现。这里每一个具体的记号都只负责自己内部的计算,降低了类和外部的联系,即高内聚低耦合。如果需要添加新的计算方法,只需要从TokenRecord继承,然后实现这三个方法即可,可以很容易的进行扩展。例如本程序中实现了三角函数sin和cos,但未实现tan。如果需要实现tan,只需要根据sin或者cos的实现方法去做就可以了。甚至可以再提炼出一个抽象类,让所有的三角函数计算都从它继承。
今天就到此,下一篇将介绍如何将表达式字符串转换成程序可以处理的TokenRecord对象列表。
- 我的计算器——2 记号对象
- 我的计算器——4 语法分析
- 我的Android案例—计算器
- Qt—学习之我的计算器
- 情人节 记号!很重要的记号
- 我的计算器——1 具有词法分析功能的计算器
- 我的计算器——3 词法分析
- 我的计算器——5 计算求值
- 我的计算器——6 用依赖注入改进
- 我的QT入门——QT版简单计算器
- Android——我的第一个APP,乘法计算器
- 我的第一个Android Application——计算器
- 我的计算器
- 我的swt计算器
- 我的swt计算器
- 我的swt计算器
- 我的swt计算器
- 我的计算器01
- 爱尔兰---一个禁止离婚的国家(转)
- 传“谷歌金山词霸”将于近日正式推出
- 通过优化内存存取提高代码性能
- Java设置系统时间
- 源码网站供参考
- 我的计算器——2 记号对象
- 七星购物巨亏3.8亿港元 拟转型空中沃尔玛
- 2007中国电子信息产业收入5.6万亿
- 中国服务外包从优势到胜势
- 从session中取出持久化对象
- 土豆网因用户上传"疯狂的石头"被判侵权
- 换位思考,与人为善
- 程序中如何保留反審的各個不同版本的記錄,
- 博客首篇