程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> 關於C# >> 仿查詢分析器的C#計算器——2.記號對象

仿查詢分析器的C#計算器——2.記號對象

編輯:關於C#

上一篇中提到了用樹形結構來分析表達式並計算求值的思路。但對程序來說,輸入的表達式只是一個字符串而已。要將表達式表示成樹型結 構,首先必須可以將表達式分解成一個個節點,然後才可以由節點組成樹。這裡將樹上的每一個節點稱之為記號對象TokenRecord。

根據上面的分析得出,記號對象要求有一個存儲自身值的變量,有自己特定的計算方法,還要能知道其下級的值。由此可以得出 TokenRecord的基本信息(略去非關鍵信息):

屬性

Index:在表達式中的列號,int類型,出錯時用於指示錯誤所在,從1開始。

Priority:優先級,int類型,在分析表達式的時候需要。

TokenValue:記號值,object類型

ChildList:下級列表,List對象,用來存儲下級元素,實現樹結構。

方法

Execute:執行該元素的操作,abstract方法。

SetChildCount:設置下級數量,虛方法,用於檢查下級數量合法性,在構造函數中調用。對應有一個m_ChildCount字段,用於存儲下級數 量。因為檢查下級屬於元素內部的任務,所以將m_ChildCount設置為protected,也沒有對應的ChildCount屬性。

SetPriority:設置優先級,虛方法,在構造函數中調用。

CheckChildCount:檢查下級數量,在Execute中調用,保證表達式合法。

其中TokenValue屬性使用object類型是因為這裡支持字符串、數值和邏輯值的運算。在最開始設計的時候,曾經采用過兩個字段string和 double類型來存儲字符串和數值,邏輯值也用數值表示,後來改成object更簡單了。

TokenRecord類的代碼如下:

/// <summary>
    /// 記號對象
    /// </summary>
    /// <remarks>Author:Alex Leo;</remarks>
    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 int m_Length;
        /// <summary>
        /// 操作符長度
        /// </summary>
        public int Length
        {
            get { return m_Length; }
        }

        private Type m_TokenValueType;
        /// <summary>
        /// 記號值類型
        /// </summary>
        public Type TokenValueType
        {
            get { return m_TokenValueType; }
            set { m_TokenValueType = value; }
        }

        private object m_TokenValue;
        /// <summary>
        /// 記號值
        /// </summary>
        public object TokenValue
        {
            get { return m_TokenValue; }
            set { m_TokenValue = value; }
        }


        private List<TokenRecord> m_ChildList = new List<TokenRecord>();
        /// <summary>
        /// 下級列表
        /// </summary>
        public List<TokenRecord> ChildList
        {
            get { return m_ChildList; }
        }

        #endregion

        /// <summary>
        /// 構造函數
        /// </summary>
        /// <param name="Index">序號</param>
        /// <param name="Length">自身長度</param>
        public TokenRecord(int Index, int Length)
        {
            this.m_Index = Index;
            this.m_Length = Length;
            this.SetPriority();
            this.SetChildCount();
        }


        #region 方法

        /// <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.TokenValue.ToString();
        }

        /// <summary>
        /// 檢查下級數量,必要時可以重寫,因為有些Token的下級數量可以是一個區間
        /// </summary>
        /// <param name="ErrorInformation">下級數量不符時顯示的錯誤信息</param>
        internal void CheckChildCount(string ErrorInformation)
        {
            if (this.m_ChildList.Count != this.m_ChildCount)
                throw new SyntaxException(this.m_Index, this.m_Length, ErrorInformation);
        }

        #region 必須重寫的方法

        /// <summary>
        /// 執行代碼
        /// </summary>
        public abstract void Execute();

        /// <summary>
        /// 設置下級數量
        /// </summary>
        protected abstract void SetChildCount();

        /// <summary>
        /// 設置優先級
        /// </summary>
        protected abstract void SetPriority();

        #endregion


        #endregion


        #region 轉換記號值類型

        /// <summary>
        /// 將記號值轉換為字符串類型
        /// </summary>
        internal string ChangeTokenToString()
        {
            string strValue;
            strValue = (string)(this.TokenValue = this.TokenValue.ToString());
            this.TokenValueType = typeof(string);
            return strValue;
        }

        /// <summary>
        /// 將記號值轉換為數字類型
        /// </summary>
        /// <param name="ErrorInformation">無法轉換成數字時顯示的錯誤信息</param>
        internal double ChangeTokenToDouble(string ErrorInformation)
        {
            double dblValue;
            if (this.TokenValueType != typeof(double))
            {
                if (double.TryParse(this.TokenValue.ToString(), out dblValue))
                    this.TokenValueType = typeof(double);
                else
                    throw new SyntaxException(this.m_Index, this.m_Length, ErrorInformation);
            }
            else
            {
                dblValue = (double)this.TokenValue;
            }
            return dblValue;
        }

        /// <summary>
        /// 將記號值轉換為邏輯值
        /// </summary>
        internal bool ChangeTokenToBoolean()
        {
            bool blnValue = false;
            if (this.TokenValueType == typeof(string))
            {
                switch (this.TokenValue.ToString().Trim().ToLower())
                {
                    case "true":
                        blnValue = (bool)(this.TokenValue = true);
                        break;
                    case "false":
                    case "":
                    default:
                        blnValue = (bool)(this.TokenValue = false);
                        break;
                }
                this.TokenValueType = typeof(bool);
            }
            else if (this.TokenValueType == typeof(double))
            {
                blnValue = (bool)((Convert.ToInt32(this.TokenValue) != 0) ? (this.TokenValue = true) : (this.TokenValue = false));
                //檢查上一行代碼是否錯誤
                this.TokenValueType = typeof(bool);
            }
            else if (this.TokenValueType == typeof(bool))
            {
                blnValue = (bool)this.TokenValue;
            }
            else
            {
            }

            return blnValue;
        }

        #endregion

    }//class TokenRecord

設計出基類TokenRecord後,其他記號類都可以從它繼承,然後實現自己的特定計算方法即可。那麼實際的表達式中有哪些類型的記號呢? 經過分析得出,表達式中所包含的的操作元素無非這幾種:關鍵字,運算符,字符串,數字。

關鍵字:if,sin,cos,true,false,pi等以英文字母開頭的詞,其中可以包含數字或者其他允許的符號

運算符:+,-,*,/,\,>,<,>=,<=,&&,||,!等,純符號操作元素,可以由好幾個字符組成

字符串:"hello",'good',"where's my book"等,由字符串標識符單引號或者雙引號標識的字符串。字符串中允許 包含當前字符串標識符之外另一種字符串標識符,即雙引號字符串中可以包含單引號,單引號字符串中可以包含雙引號,這一點和JavaScript 類似。如果在字符串中包含當前標識符,則必須用連續兩個標識符進行轉義。

數值:12,856,42.123,-62.45,允許包含小數點和負數。(本程序中對負數的處理在某些情況下會有錯誤,有興趣的朋友可以完善一下 )

進一步分析發現,字符串和數值有一定的相似性,就是它們並不需要計算,只需要作為一個存儲單元即可。那麼就可以將它們合並,稱為值 記號對象TokenValue。其他記號對象在Execute中實現自己的特定算法,而值記號對象的Execute方法中不做任何操作。

按照上面對操作元素的分類,從TokenRecord類衍生出一下幾個類:TokenValue(對應字符串和數值),TokenMethod(方法類,抽象類,對 應部分關鍵字而非全部,因為有些關鍵字是常量,可以用TokenValue表示),  TokenSymbol(對應運算符,抽象類)。類圖如下:

然後再從這些類中衍生出更多的具體的類,比如TokenIf, TokenSin, TokenCos, TokenPlus, TokenMinus。

以這個表達式為例,23.5+(54/3-9)*2,分析出來的TokenList記號對象列表如下: 

記號對象 對應表達式 TokenValue 23.5 TokenPlus + TokenLeftBracket ( TokenValue 54 TokenDivide / TokenValue 3 TokenMinus - TokenValue 9 TokenRightBracket ) TokenMultiply * TokenValue 2

這樣就把傳入的表達式字符串表示成程序可以識別的對象了,然後再對這些記號對象進行處理,最終得到計算結果。這一節只是介紹記號對 象,至於怎麼將表達式轉換成記號對象,下一篇再詳細介紹。

接下來是如何實現具體的TokenRecord類。以乘法記號類TokenMultiply為例,TokenMultiply繼承自TokenArithmetic,TokenArithmetic繼 承自TokenSymbol,TokenSymbol繼承自TokenRecord。

TokenSymbol類也是一個抽象類,只是將部分重復的設置實現了,減少其子類中重復的代碼,TokenSymbol類的代碼如下:

public abstract class TokenSymbol : TokenRecord
    {
        public TokenSymbol(int Index, int Length)
            : base(Index, Length)
        {
            this.TokenValueType = typeof(double);//部分操作符必須根據實際修改
        }

        protected override void SetChildCount()
        {
            this.m_ChildCount = 2;//必須根據實際修改
        }

        public abstract override void Execute();
    }

TokenArithmetic類則是個完全的抽象類,只是為了進行分類而已,任何功能都沒實現,其代碼如下:

public abstract class TokenArithmeticSymbol : TokenSymbol
    {
        public TokenArithmeticSymbol(int Index, int Length)
            : base(Index, Length)
        {
        }

        public abstract override void Execute();
    }

最後是TokenMultiply類,它實現具體的乘法操作,代碼如下:

public class TokenMultiply : TokenArithmeticSymbol
    {
        public TokenMultiply(int Index, int Length)
            : base(Index, Length)
        {
        }

        protected override void SetPriority()
        {
            this.m_Priority = 3;
        }

        public override void Execute()
        {
            this.CheckChildCount("乘法的運算元素數量不合法");

            TokenRecord TokenFirst = this.ChildList[0];
            TokenRecord TokenSecond = this.ChildList[1];
            TokenFirst.Execute();
            TokenSecond.Execute();

            this.TokenValue = TokenFirst.ChangeTokenToDouble("乘法的運算元素不是數值") * TokenSecond.ChangeTokenToDouble("乘法的運算元素不是數值");
        }
    }

TokenRecord中的三個虛方法SetChildCount, SetPriority, Execute都得到了實現。這裡每一個具體的記號都只負責自己內部的計算,降 低了類和外部的聯系,即高內聚低耦合。如果需要添加新的計算方法,只需要從TokenRecord繼承,然後實現這三個方法即可,可以很容易的進 行擴展。例如本程序中實現了三角函數sin和cos,但未實現tan。如果需要實現tan,只需要根據sin或者cos的實現方法去做就可以了。甚至可 以再提煉出一個抽象類,讓所有的三角函數計算都從它繼承。

本篇就到此,下一篇將介紹如何將表達式字符串轉換成程序可以處理的TokenRecord對象列表。

本文配套源碼

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved