博主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为最终的计算
到现在为止计算器的基本内容也是核心内容就讲解完毕了,剩下的其它科学运算建议采用隔离法,意思是在输入表达式的时候就让特殊的计算进行完毕,让后其结果值返回至计算器显示文本中,作为表达式的一部分,而不必所有的运算都要在输入表达式之后统一处理,另外还有精度、表达式输入正确检测、错误提示等细节功能需要依据个人风格来完成。
最后我们来总结一下这个算法的优点与缺点:

优点:函数分工清除方便调用,算法无需递归和堆栈以及树,理解方便,计算简便,所用资源少,所有的处理都在两个容器中完成,体现了游标的实用。
缺点:博主至今没找到。







Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐