安卓(java)計算器簡單實現
博主q q 656358805 歡迎線上交流!
以下兩圖是計算器的結果展示:
好了,那麼今天我們來講一下安卓計算器的簡單實現,對於廣大愁於程式設計的初學者大學生來說,自己製作一個計算器的作業會非常難,但是不要害怕,程式設計本來就很難,智商跟不上很正常,這不!博主所幸為您提供一個解決當務之急渠道,那麼就正式開始今天的學習。
這裡我們對於計算器介面的設計就略講了,因為如果讀者用android studio做的話介面佈局是很簡單的,只需用一個表格佈局;而用eclipse做的話,就要採用java的swing技術來做介面,用的就是swing中的GridLayout佈局管理器,不過出於創新,讀者可以試著設計自己的佈局方式,那麼計算器的介面我就扯這麼多了。
下面我要著重講一下計算器的核心,也就是演算法。
使用java呢計算中的大部分基礎計算是直接呼叫java的函式就可以了,比如三角函式,就直接使用java裡的Math.sin(),Math.cos(),Math.tan()就可以實現了,這裡建議把每一個基本計算放入一個類中,或者是把所有的基本計算類放在一個計算類中,便於編寫和閱讀。
現在我們講一下第一個類,也就是計算器的主介面類:MainActivity
在這個類裡,我定義瞭如下的變數。
display:計算器的文字輸入和顯示框,每當使用者輸入表示式或刪除表示式會在這個文字框中顯示。
sign:承裝符號的容器
number:承裝數字的容器
若干button: 是計算器上的按鈕們
private static TextView display; private Button back,clear,plus,sub,mul,div,leftR,rightR,PI,e,point,equal,one,two,three,four,five,six,seven,eight,nine,zero,sin,cos,tan,pow,Log,ln,design,star; static ArrayList<Character> sign=new ArrayList<Character>(); static ArrayList<Double> number=new ArrayList<Double>();
在這裡我們要做的第一步已經完成,就是資料的定義,其次我們要把這些button都加上監聽器,以實現使用者點選按鈕時會出現不同的功能效果,加監聽器非常簡單就不講了。(注意,在加玩監聽器時最好跑一遍程式碼,試試點選事件是否正確處理,若是處理正確那麼就開始我們的表示式處理階段,也就是演算法的核心部分)
在講核心演算法之前我先說一下,可能讀者聽說過用正則表示式或者各種各樣的字串處理方法來處理輸入的字串,筆者並沒有採用這些方式,以下所講的處理方法是筆者自己想出來的,所以就效率來講不一定趕得上某些字元處理方法,但是再怎麼說也是原創! 我還是把這種演算法記錄下來,和讀者相互交流一下。
剛才讀者在加監聽器的時候應該發現了一個問題,就是點選等號的時候應該處理什麼樣的點選事件呢? 那麼問題來了,使用者所輸入的表示式不一定合法,也不一定帶有什麼樣的計算,帶括號,或不帶括號,我們究竟要怎麼做才能知道使用者的輸入形式呢? 不妨先想一下,如果計算器規定了輸入不能帶括號,那麼就只能計算無括號混合運算,也就是說在使用者輸入合法的前提下,我們只需判斷使用者的輸入帶不帶有括號,從而確定基本計算的優先順序是否被改變,那麼也就是說,如果使用者沒有輸入括號,那就意味著乘除先於加減算。
好了,說了一大堆屁話我們終於清楚了一點——就是我們需要判斷表示式中有沒有括號。
那麼現在我們來考慮一件事,一個表示式非常長,我們要用什麼方式得知到底以什麼樣的順序來計算一個表示式呢? 筆者給出的方法,首先將輸入的表示式切分成眾多個數和符號,數字加入到number容器中,符號加入到sign容器中。
舉個例子: 將(3+2)*5-1這個表示式切分,則sign中應該是‘(’,‘+’,‘)’,‘*’,‘-’ 而number中應該是‘3’,‘2’,‘5’,‘1’
處理切分的時候很容易,但是需要注意的兩個問題,當用戶輸入浮點數時,如:3.25 這個時候3.25是一個數,注意字元的處理,不要把3.25分成3和25了;其次另一個問題是輸入一個負數的時候,不能把負號看成減號存入sign,而是連帶數字整體存入number,如:輸入-3.5,則number中加入的是-3.5,而不是3.5。
下面這段程式碼就是輸入表示式的切分和裝入:
boolean isleftR=false; //這個布林值表示上一個被掃描的字元是不是左括號
int leftbound=0,rightbound; //leftbound表示要存入容器資料的左邊界,rightbound則是有邊界,比如3.56左邊界指向3,有邊界指向6,而單個字元則是左右邊界相等
for(int i=0;i<processString.length();i++){ //掃描整個表示式
if(processString.charAt(i)=='('){ //遇到左括號就將其加入sign,設定isleftR為true sign.add(processString.charAt(i)); isleftR=true; } else if(processString.charAt(i)==')'||processString.charAt(i)=='+'||processString.charAt(i)=='*'||processString.charAt(i)=='/'){ //如果是右括號,加,乘,除就直接加入sign,重置isleftR sign.add(processString.charAt(i)); isleftR=false; }else if(processString.charAt(i)=='-'&&!isleftR&&i!=0){R //減號比較特殊,如果減號不是表示式的第一個符號,而且上一個字元不是左括號,則表示這個減號是運算的減號,否則是負號 sign.add(processString.charAt(i)); isleftR=false; }else if (processString.charAt(i)=='-'&&(isleftR||i==0)){ //負號的話我們要加進number的就不只是一個字元了,我們要加進負號連同它後面的數字,所以我們這裡使用預先定義的左右邊界,下面if的條件表示有邊界選定的規則 if(i==0){ leftbound = i+1; for (rightbound = leftbound; rightbound < processString.length(); rightbound++) { if (processString.charAt(rightbound) == '(' || processString.charAt(rightbound) == ')' || processString.charAt(rightbound) == '+' || processString.charAt(rightbound) == '-' || processString.charAt(rightbound) == '*' || processString.charAt(rightbound) == '/') { number.add(Double.parseDouble(processString.substring(leftbound-1, rightbound))); break; } } if (rightbound == processString.length()) { //如果有邊界到了表示式的總長度處還沒有確定,則視為最後一個字元為有邊界 number.add(Double.parseDouble(processString.substring(leftbound-1, rightbound - 1) + processString.charAt(processString.length() - 1))); } isleftR = false; i = rightbound - 1;}else { leftbound = i+1; for (rightbound = leftbound; rightbound < processString.length(); rightbound++) { if (processString.charAt(rightbound) == '(' || processString.charAt(rightbound) == ')' || processString.charAt(rightbound) == '+' || processString.charAt(rightbound) == '-' || processString.charAt(rightbound) == '*' || processString.charAt(rightbound) == '/') { number.add(Double.parseDouble(processString.substring(leftbound-1, rightbound))); break; } } if (rightbound == processString.length()) { number.add(Double.parseDouble(processString.substring(leftbound-1, rightbound - 1) + processString.charAt(processString.length() - 1))); } isleftR = false; i = rightbound - 1;} }else if((processString.charAt(i)>='0')&&(processString.charAt(i)<='9')){ //如果是數字的話,判斷方式同負號一樣 leftbound=i; for(rightbound=leftbound;rightbound<processString.length();rightbound++){ if(processString.charAt(rightbound)=='('||processString.charAt(rightbound)==')'||processString.charAt(rightbound)=='+'||processString.charAt(rightbound)=='-'||processString.charAt(rightbound)=='*'||processString.charAt(rightbound)=='/'){ number.add(Double.parseDouble(processString.substring(leftbound,rightbound))); break; } } if(rightbound==processString.length()){ number.add(Double.parseDouble(processString.substring(leftbound,rightbound-1)+processString.charAt(processString.length()-1))); } isleftR=false; i=rightbound-1; } else{ //輸入其它非法符號則表示式錯誤 Toast.makeText(MainActivity,"計算表示式輸入錯誤!",Toast.LENGTH_LONG).show(); } }
以上是表示式的切分,到現在sign和number容器應該已經裝滿相應的資料了,然後我們就要以一定的順序來計算這些資料了,也就是在sign和number這兩個容器之間尋找一種規則,此處我們考慮先計算不帶括號的表示式,再新增一個定位括號的演算法進而完成帶括號表示式的計算,所以下面介紹一下不帶括號表示式的演算法,例如: 輸入3*5+2-4 , 結果應該是13.
程式碼實現如下:
引數解釋:a,b為sign和number,length是sign長度,start和startN是指標用於操作容器,資料結構如上圖所示
public static double compute_withoutR(ArrayList<Character> a,int start,int length,ArrayList<Double> b,int startN) { int beginvalue=start; int count=0; //count計數已經算過的符號數 for(int i=start;beginvalue<start+length;beginvalue++) //從頭掃描符號容器,因為沒有括號,所以先算乘除後算加減 {if(a.get(i).equals('*')||a.get(i).equals('/')){ //這裡我們只講解乘號,因為後面的判定運算都相似 if(a.get(i).equals('*')) { double temp = mul(b.get(startN+i-start),b.get(startN+i-start+1)); //讀者不妨對應上面的資料結構看一下這規律,如果第i
個符號是乘號,那麼這個乘號所關係的兩個乘數,其中一個必定是number的第startN+i-start個數字,另一個是第 startN+i-start+1個數字。
b.remove(startN+i-start); //每計算完一次,都要把這次計算的兩個數從number中移除,符號也要移除 b.remove(startN+i-start); b.add(startN+i-start,temp);//將本次運算結果加入number a.remove(i);count++; }else{ double temp = div(b.get(startN+i-start),b.get(startN+i-start+1)); b.remove(startN+i-start); b.remove(startN+i-start); b.add(startN+i-start,temp); a.remove(i);count++; } }else {i++; } } beginvalue=start; for(int i=start;beginvalue<start+length-count;beginvalue++){ //計算剩餘符號 if(a.get(i).equals('+')||a.get(i).equals('-')){ if(a.get(i).equals('+')) { double temp = Plus.plus(b.get(startN+i-start),b.get(startN+i-start+1));b.remove(startN+i-start); b.remove(startN+i-start); b.add(startN+i-start,temp); a.remove(i); }else{ double temp =Sub.sub(b.get(startN+i-start),b.get(startN+i-start+1)); b.remove(startN+i-start); b.remove(startN+i-start); b.add(startN+i-start,temp); a.remove(i); } }else{ System.out.println("basic運算錯誤!"); } } return b.get(startN); //所有的符號計算完,number中的startN所指的數就是要返回的結果值 }
目前為止,我們已經可以計算不帶括號的表示式了,下面我要說的就是比較麻煩一點的帶括號運算,其思路就是傳入容器的一部分到上述的無括號運算函式中,這裡一部分是指一個小括號裡的內容,也就是說下面要介紹的是一個容器吞吐的函式,它的作用就是依照括號的順序來計算表示式,先將表示式裡所有的括號從右向左從裡向外地計算完,剩下沒有括號的表示式再做最後一次運算可得最終結果,因此搞明白如何匹配括號是下面這個函式的重點。
程式碼實現:
int leftRCount=0,leftRLastindex=-1,leftNindex=0,rightRindex=0; //leftRCount為左括號數,leftRLastindex指向容器中最後一個左括號,leftRindex指向最後一個左括號,rightRindex指向最後一個右括號。 for(int i=0;i<sign.size();i++){ //計數左括號數 if(sign.get(i).equals('(')){ leftRCount++; } } if(leftRCount==0){ //如果沒有左括號則表示式中無括號計算 display.setText(String.format("%.5f",BasicCompute.compute_withoutR(sign,0,sign.size(),number,0))); sign.clear(); number.clear(); }else { for (int loop = 0; loop < leftRCount; loop++) { //有幾個左括號我們就要計算幾次括號內容 for (int i = 0; i < sign.size(); i++) { if (sign.get(i).equals('(')) { leftRLastindex = i; //確定leftRLastindex } }for (int i = 0; i < sign.size(); i++) { if (i < leftRLastindex && (sign.get(i).equals('+') || sign.get(i).equals('-') || sign.get(i).equals('*') || sign.get(i).equals('/'))) { leftNindex++; //確定leftRindex } if (i > leftRLastindex && sign.get(i).equals(')')) { rightRindex = i; //確定rightRindex break; } } double temp = BasicCompute.compute_withoutR(sign, leftRLastindex + 1, rightRindex - leftRLastindex - 1, number, leftNindex);sign.remove(leftRLastindex); //每計算一次都要把該次的小括號從sign中移除sign.remove(leftRLastindex);leftNindex = 0; } temp=String.format("%.5f", BasicCompute.compute_withoutR(sign, 0, sign.size(), number, 0)); //本次運算是括號都計算後的最後一次無括號運算,是括號運算的最後一步,相當於上面的無括號運算。 display.setText(String.format("%.5f", BasicCompute.compute_withoutR(sign, 0, sign.size(), number, 0))); sign.clear(); number.clear(); } return temp; //temp為最終的計算值
到現在為止計算器的基本內容也是核心內容就講解完畢了,剩下的其它科學運算建議採用隔離法,意思是在輸入表示式的時候就讓特殊的計算進行完畢,讓後其結果值返回至計算器顯示文字中,作為表示式的一部分,而不必所有的運算都要在輸入表示式之後統一處理,另外還有精度、表示式輸入正確檢測、錯誤提示等細節功能需要依據個人風格來完成。
最後我們來總結一下這個演算法的優點與缺點:
優點:函式分工清除方便呼叫,演算法無需遞迴和堆疊以及樹,理解方便,計算簡便,所用資源少,所有的處理都在兩個容器中完成,體現了遊標的實用。
缺點:博主至今沒找到。