实验项目一 支持算数表达式求解的计算器


一.实验要求

        1.能通过设计的按钮控件输入并实现算术表达式,表达式在文本框中显示,运算结果输出显示;保存和浏览历史运算记录;

        2.能够检验算术表达式的合法性;

        3.能够实现混合运算的求解,算术表达式中包含加、减、乘、除、括号等运算符;

        4.要求交互界面友好,程序健壮。

二.基本流程:

三.实验思路:

         1.利用java语言编写程序实现算术表达式求解。

                提示:①参与运算的数据(操作数)可以为整数或者实数,操作符为+、-、*、/等。

                           ②运算顺序是先乘除、后加减、括号内运算优先。

                思路

                方法一:单栈

                (1)利用一个字符可变序列StringBuilder和操作符栈(opStack)将键盘输入的中缀表达式转换为后缀表达式。其中,StringBuilder存放后缀表达式的结果,操作符栈(opStack)用于存储运算符。

  • 算法思想:

        把中缀表达式当作一个字符串,表达式从左到右一个个字符判断,规则如下:

        ① 如果是数字,直接存进StringBuilder。

        ② 如果字符是±* /( , 加减乘除和左括号:

                A.栈为空,直接将操作符入栈;
                B.假设现在的操作符是c,栈顶的操作符是s
                     
  a. 若c的优先级大于s,直接入栈;
                        b.若c的优先级小于s,将栈顶操作符弹出到StringBuilder,知道栈顶元素s优先级低于c或栈为空。弹出完这些元素后,再将c压入到栈中;
                        c.若栈顶元素是(,则直接入栈;

        ③如果字符是) 右括号,则依次将栈顶元素添加进StringBuilder,直到遇到左括号,左括号不加进StringBuilder,直接丢弃。
        ④字符判断完了之后,如果栈不为空,则依次输出栈的元素到StringBuilder。

                 (2)利用栈来进行后缀表达式值的求解,此栈用于存放计算的中间过程和最终结果。

  • 算法思想:

        把后缀表达式看作是一个字符串,然后从左到右一个一个字符进行判断,规则如下:

        ①如果是数字,直接进栈。

        ②如果是+或*,则弹出栈顶两个元素,进行相加或相乘,将计算结果压入栈中。

        ③ 如果是-或/,则先弹出一个数字为x,再弹出一个数字为y,然后y-x或y/x,最后将计算结果压入栈中。

                 (3)输出计算结果。

                方法二:双栈运算符优先级法

               双栈法运算符优先级法为了实现表达式求值需要设置两个栈:

                ①运算符栈,用于寄存运算符OP

                ②操作数栈,用于寄存运算数和运算结果OPND

                求值的处理过程是自左至右扫描表达式的每一个字符:

                ①当扫描到的是运算数,则将其压入栈OPND;

                ②当扫描到的是运算符:

                        如果这个运算符比OP栈顶运算符优先级高,则入栈;

                        如果这个运算符比OP栈顶运算符优先级低,则从OPND栈中弹出两个运算数,从栈OP中弹出栈顶运算符进行运算,并将运算结果压入栈OPND;

                ③继续处理当前字符,直到遇到结束符为止;

        2.学习GUI图形界面的设计,设计计算器的界面

                第一部分放数字与操作符组件(0-9,+ - * / . =),4*4网格布局;第二部分放文本框,置于顶部;第三部分放 '(  ' 与 ‘ )’以及清空按钮和撤销按钮;第四部分为历史记录按钮。

                所谓GUI,全称为(Graphical User Interface),又称图形用户接口,或者是图形用户界面其实就是指采用图形方式显示的计算机操作用户界面。知道了GUI,然后我们就自然联想到了各种按钮,各种窗口图形,而这些东西,全部都包含在了java自带的Swing中,Swing 使用纯粹的 Java 代码来模拟各种控件(使用 Java 自带的作图函数绘制出各种控件),没有使用本地操作系统的内在方法,所以 Swing 是跨平台的。也正是因为 Swing 的这种特性,人们通常把 Swing 控件称为轻量级控件。

        JFrame :它是屏幕上window的对象,能够最大化、最小化、关闭

        其常用构造方法如下所示:

  • JFrame():构造一个初始时不可见的新窗体。
  • JFrame(String title):创建一个具有 title 指定标题的不可见新窗体。

        JPanel :是一种轻量级容器,可以加入到JFrame窗体中

        其常用的构造方法如下。

  • JPanel():使用默认的布局管理器创建新面板,默认的布局管理器为 FlowLayout。
  • JPanel(LayoutManagerLayout layout):创建指定布局管理器的 JPanel 对象。

        JTextField:单行文本框组件

        其常用构造方法如下。

  • JTextField():创建一个默认的文本框。
  • JTextField(String text):创建一个指定初始化文本信息的文本框。
  • JTextField(int columns):创建一个指定列数的文本框。
  • JTextField(String text,int columns):创建一个既指定初始化文本信息,又指定列数的文本框。

        JTextArea :是一个文本框,可以显示文本

        其常用构造方法如下。

  • JTextArea():创建一个默认的文本域。
  • JTextArea(int rows,int columns):创建一个具有指定行数和列数的文本域。
  • JTextArea(String text):创建一个包含指定文本的文本域。
  • JTextArea(String text,int rows,int columns):创建一个既包含指定文本,又包含指定行数和列数的多行文本域。

        JButton : 是Java中的按钮,可以实现一些功能。

        常用构造方法如下。

  • JButton():创建一个无标签文本、无图标的按钮。
  • JButton(Icon icon):创建一个无标签文本、有图标的按钮。
  • JButton(String text):创建一个有标签文本、无图标的按钮。
  • JButton(String text,Icon icon):创建一个有标签文本、有图标的按钮。

        3.功控件响应事件处理           

                ① 首先需要为按钮添加监控器,这样才能响应按键事件,另外我定义了两个字符串exp和outcome(初值都为空串),分别用来存储运算式和运算结果。
                ② 对“C”键的响应

                 “C”键的功能是清空算式内容,因此只需要存储算式的字符串exp设置为“0”即可。

                ③对“D”键的响应
                由于按下此键的功能是删除一个数字或算符等,因此我使用字符串提取子串函数substring来提起从字符串开始到字符串倒数第二个字符为止的子串,然后将其显示在JTextField控件中即可,但需要注意的是,需要考虑到空串的情况,当存储算式的字符串exp为空串时,将该字符串设置为“0”然后在JTextField控件上显示0。

                ④对运算符(+、-、*、/)键的响应
                由于运算符必须要在数字的后面,因此需要判别当前存储算式的字符串是否为空串,只有不为空串且串的最后一个字符不为运算符和“.”的情况下才能将当前按下的运算符加入到存储算式的字符串exp中去。
                ⑤对“=”键的响应
当按下等号按键时,在算式后加上“#”,然后利用中缀式求值来计算结果,并将double类型的结果转化为字符串并被outcome引用,在获取到outcome后,需要将算式字符串exp末尾的“#”去掉,然后将算式显示在第一个JTextField控件中,并将结果显示在第二个JTextField控件之中,最后为了下次计算,需要将exp串置为空串。

                ⑥对“.”键的响应
小数点必须要在数字的后面,且两个小数点间不能都是数字,即不能出现2.33.3这种情况,因此,我先在算式中寻找最后一个小数点的位置pos:当算式中存在小数点时,在算式最后一个字符为数字且算式从pos+1开始到末尾的子串不都是数字的情况下,才能将小数点加入到算式中去。
当算式中不存在小数点时,只要算式最后一个字符为数字时即可将小数点加入算式。
                ⑦对数字按键的响应
对于数字按键,一般是直接将当前按下的数字加入到算式中即可,但需要注意不能在算式为“0”的情况下直接将按下的数字直接加入到算式中去,即不能出现0123这种类似的情况,因此当exp为“0”时先将其置为空串,然后将按下的数字加入。

四.具体功能实现:

        1.基础控件与模块的添加初始化及为各按键添加响应

        包括20个4×5分布的功能按钮(0-9,+,-,*,/,=,C,DEL,(,),.),1个历史记录按钮(HIS)以及三行文本框区域                

        代码实现:

public class Calculator implements ActionListener {
    private JFrame f;
    private JPanel p;
    //定义两个文本框用来显示算式和结果
    private JTextField show, showexp;
    //定义数字按钮
    private JButton zero, one, two, three, four, five, six, seven, eight, nine;
    //定义控制按钮
    private JButton H, C, DEL, point, LK, RK;
    //定义运算符号按钮
    private JButton div, mul, sum, minus, eql;
    //定义算式的字符串表示
    private String input, output;

         控件及变量初始化代码:

 Calculator() {
        f = new JFrame("Calculator By Lny");
        Image image = new ImageIcon("C:\\Users\\李宁宇\\Pictures\\Saved Pictures\\1.jpg").getImage(); //添加背景图片
        p = new BackgroundPanel(image);
        show = new JTextField("0");//创建单行文本控件
        showexp = new JTextField();
        zero = new JButton("0");//创建按钮
        one = new JButton("1");
        two = new JButton("2");
        three = new JButton("3");
        four = new JButton("4");
        five = new JButton("5");
        six = new JButton("6");
        seven = new JButton("7");
        eight = new JButton("8");
        nine = new JButton("9");
        H = new JButton("HIS");
        C = new JButton("C");
        DEL = new JButton("DEL");
        sum = new JButton("+");
        minus = new JButton("-");
        mul = new JButton("*");
        div = new JButton("/");
        point = new JButton(".");
        LK = new JButton("(");
        RK = new JButton(")");
        eql = new JButton("=");
        input = output = "";//初始设置存储算式和结果的字符串为空串
    }

        计算器界面及按键监听设置:

//计算器界面设置
    public void display() {
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//设置关闭就退出程序

        //设置显示结果的单行文本框相关属性
        show.setFont(new Font(Font.SERIF, Font.BOLD, 40));//设置字体样式
        show.setBorder(BorderFactory.createEmptyBorder());//设置单行文本控件无边框
        show.setHorizontalAlignment(SwingConstants.RIGHT);//设置文本靠右显示
        show.setEnabled(false);//设置单行文本框不能点击

        showexp.setFont(new Font(Font.SERIF, Font.BOLD, 16));//设置字体样式
        showexp.setBorder(BorderFactory.createEmptyBorder());//设置单行文本控件无边框
        showexp.setHorizontalAlignment(SwingConstants.RIGHT);//设置文本靠右显示
        showexp.setEnabled(false);//设置文本框不能点击

        H.setForeground(Color.red);//设置按钮字体颜色为蓝色
        H.setFont(new Font(Font.SERIF, Font.BOLD, 16));//设置按钮字体样式
        H.setContentAreaFilled(false);//设置按钮为透明效果

        C.setForeground(Color.red);
        C.setFont(new Font(Font.SERIF, Font.BOLD, 16));
        C.setContentAreaFilled(false);

        DEL.setForeground(Color.red);
        DEL.setFont(new Font(Font.SERIF, Font.BOLD, 16));
        DEL.setContentAreaFilled(false);

        LK.setForeground(Color.red);//设置按钮字体颜色为蓝色
        LK.setFont(new Font(Font.SERIF, Font.BOLD, 16));//设置按钮字体样式
        LK.setContentAreaFilled(false);//设置按钮为透明效果

        RK.setForeground(Color.red);//设置按钮字体颜色为蓝色
        RK.setFont(new Font(Font.SERIF, Font.BOLD, 16));//设置按钮字体样式
        RK.setContentAreaFilled(false);//设置按钮为透明效果

        sum.setForeground(Color.BLUE);
        sum.setFont(new Font(Font.SERIF, Font.BOLD, 16));
        sum.setContentAreaFilled(false);

        minus.setForeground(Color.BLUE);
        minus.setFont(new Font(Font.SERIF, Font.BOLD, 16));
        minus.setContentAreaFilled(false);

        mul.setFont(new Font(Font.SERIF, Font.BOLD, 16));
        mul.setForeground(Color.BLUE);
        mul.setContentAreaFilled(false);

        div.setForeground(Color.BLUE);
        div.setFont(new Font(Font.SERIF, Font.BOLD, 16));
        div.setContentAreaFilled(false);

        point.setForeground(Color.BLUE);
        point.setFont(new Font(Font.SERIF, Font.BOLD, 16));
        point.setContentAreaFilled(false);

        eql.setForeground(Color.red);
        eql.setFont(new Font(Font.SERIF, Font.BOLD, 16));
        eql.setContentAreaFilled(false);

        zero.setForeground(Color.BLUE);
        zero.setFont(new Font(Font.SERIF, Font.BOLD, 16));
        zero.setContentAreaFilled(false);

        one.setForeground(Color.BLUE);
        one.setFont(new Font(Font.SERIF, Font.BOLD, 16));
        one.setContentAreaFilled(false);

        two.setForeground(Color.BLUE);
        two.setFont(new Font(Font.SERIF, Font.BOLD, 16));
        two.setContentAreaFilled(false);

        three.setForeground(Color.BLUE);
        three.setFont(new Font(Font.SERIF, Font.BOLD, 16));
        three.setContentAreaFilled(false);

        four.setForeground(Color.BLUE);
        four.setFont(new Font(Font.SERIF, Font.BOLD, 16));
        four.setContentAreaFilled(false);

        five.setForeground(Color.BLUE);
        five.setFont(new Font(Font.SERIF, Font.BOLD, 16));
        five.setContentAreaFilled(false);

        six.setForeground(Color.BLUE);
        six.setFont(new Font(Font.SERIF, Font.BOLD, 16));
        six.setContentAreaFilled(false);

        seven.setForeground(Color.BLUE);
        seven.setFont(new Font(Font.SERIF, Font.BOLD, 16));
        seven.setContentAreaFilled(false);

        eight.setForeground(Color.BLUE);
        eight.setFont(new Font(Font.SERIF, Font.BOLD, 16));
        eight.setContentAreaFilled(false);

        nine.setForeground(Color.BLUE);
        nine.setFont(new Font(Font.SERIF, Font.BOLD, 16));
        nine.setContentAreaFilled(false);


        //为按钮添加监听事件
        H.addActionListener(this);
        C.addActionListener(this);
        DEL.addActionListener(this);
        sum.addActionListener(this);
        minus.addActionListener(this);
        mul.addActionListener(this);
        div.addActionListener(this);
        point.addActionListener(this);
        eql.addActionListener(this);
        zero.addActionListener(this);
        one.addActionListener(this);
        two.addActionListener(this);
        three.addActionListener(this);
        four.addActionListener(this);
        five.addActionListener(this);
        six.addActionListener(this);
        seven.addActionListener(this);
        eight.addActionListener(this);
        nine.addActionListener(this);
        LK.addActionListener(this);
        RK.addActionListener(this);

        //GridBagLayout(网格包布局管理器)
        GridBagLayout gblayout = new GridBagLayout();
        //创建GridBagLayout布局管理器

        p.setLayout(gblayout);
        //使用GridBagLayout布局管理器

        GridBagConstraints g = new GridBagConstraints();

        g.fill = GridBagConstraints.BOTH;//设置当某个单元格未填满时填满整个空间
        //fill
        //指定组件填充网格的方式,可以是如下值:GridBagConstraints.NONE(默认值)、
        // GridBagConstraints.HORIZONTAL(组件横向充满显示区域,但是不改变组件高度)、
        // GridBagConstraints.VERTICAL(组件纵向充满显示区域,但是不改变组件宽度)
        // GridBagConstraints.BOTH(组件横向、纵向充满其显示区域)。

        g.weightx = 1.0;//设置窗口变大时缩放比例
        g.weighty = 1.0;
        //weightx 和 weighty
        //用来指定在容器大小改变时,增加或减少的空间如何在组件间分配,默认值为 0,
        // 即所有的组件将聚拢在容器的中心,多余的空间将放在容器边缘与网格单元之间。
        // weightx 和 weighty 的取值一般在 0.0 与 1.0 之间,数值大表明组件所在的行或者列将获得更多的空间。

        g.gridx = 0;//定位在第一行第一列
        g.gridy = 0;
        //gridx 和 gridy
        //用来指定组件左上角在网格中的行和列。容器中最左边列的 gridx 为 0,最上边行的 gridy 为 0。
        // 这两个变量的默认值是 GridBagConstraints.RELATIVE,表示对应的组件将放在前一个组件的右边或下面。

        g.gridwidth = GridBagConstraints.REMAINDER;//填满整行
        g.gridheight = 1;//占一行网格
        gridwidth 和 gridheight
        //用来指定组件显示区域所占的列数和行数,以网格单元而不是像素为单位,默认值为 1

        g.insets = new Insets(5, 5, 0, 5);
        //设置该组件与其它组件的距离
        // insets指定组件显示区域的外部填充,即组件与其显示区域边缘之间的空间,默认组件没有外部填充。

        gblayout.setConstraints(showexp, g);//设置文本控件位置
        g.gridx = 0;
        g.gridy = 1;//设置在第二行第一列
        g.gridheight = 2;//占两行网格
        g.insets = new Insets(0, 5, 5, 5);

        gblayout.setConstraints(show, g);
        g.gridwidth = 1;
        g.gridheight = 1;//占一行网格且填满整行
        g.insets = new Insets(5, 5, 5, 5);

        g.gridy = 4;//第五行
        g.gridx = 0;
        gblayout.setConstraints(C, g);
        g.gridx = 1;
        gblayout.setConstraints(DEL, g);
        g.gridx = 2;
        gblayout.setConstraints(LK, g);
        g.gridx = 3;
        gblayout.setConstraints(RK, g);

        g.gridy = 5;
        g.gridx = 0;
        gblayout.setConstraints(seven, g);
        g.gridx = 1;
        gblayout.setConstraints(eight, g);
        g.gridx = 2;
        gblayout.setConstraints(nine, g);
        g.gridx = 3;
        gblayout.setConstraints(div, g);

        g.gridy = 6;
        g.gridx = 0;
        gblayout.setConstraints(four, g);
        g.gridx = 1;
        gblayout.setConstraints(five, g);
        g.gridx = 2;
        gblayout.setConstraints(six, g);
        g.gridx = 3;
        gblayout.setConstraints(mul, g);

        g.gridy = 7;
        g.gridx = 0;
        gblayout.setConstraints(one, g);
        g.gridx = 1;
        gblayout.setConstraints(two, g);
        g.gridx = 2;
        gblayout.setConstraints(three, g);
        g.gridx = 3;
        gblayout.setConstraints(sum, g);

        g.gridy = 8;
        g.gridx = 0;
        gblayout.setConstraints(point, g);
        g.gridx = 1;
        gblayout.setConstraints(zero, g);
        g.gridx = 2;
        gblayout.setConstraints(eql, g);
        g.gridx = 3;
        gblayout.setConstraints(minus, g);

        p.add(showexp);
        p.add(show);
        p.add(H);
        p.add(C);
        p.add(DEL);
        p.add(div);
        p.add(seven);
        p.add(eight);
        p.add(nine);
        p.add(mul);
        p.add(four);
        p.add(five);
        p.add(six);
        p.add(minus);
        p.add(one);
        p.add(two);
        p.add(three);
        p.add(sum);
        p.add(zero);
        p.add(point);
        p.add(eql);
        p.add(LK);
        p.add(RK);


        f.setContentPane(p);
        f.setSize(450, 550); //设置窗口尺寸
        f.setLocationRelativeTo(null);
        f.setVisible(true);   //设置窗口是否可见

    }
@Override
    public void actionPerformed(ActionEvent e) {
        output = "";
        if ((e.getSource()) == C) {//清空算式
            input = "0";
            show.setText(input); //显示文本框内容
        } else if ((e.getSource()) == DEL) {
            //提取字符串开始到倒数第二个字符
            input = input.substring(0, input.length() - 1);
            if (input.length() == 0) input = "0";
            show.setText(input);
        } else if ((e.getSource()) == sum || (e.getSource()) == minus || (e.getSource()) == mul || (e.getSource()) == div) {
            if (input.length() != 0 && (!isOperator(input.charAt(input.length() - 1))))//确认必须有数字才能输入运算符
                input += e.getActionCommand(); //getActionCommand():返回于此动作相关的命令字符串
            show.setText(input);
        } else if ((e.getSource()) == point) {
            int pos = input.lastIndexOf('.');//找到最后一个小数点的位置

            if (pos >= 0)//前后两个小数点间不能都是数字,即不能2.33时又添加一个小数点变为2.33.
            {
                if (isDigit(input.charAt(input.length() - 1)) && !isDigitSring(input.substring(pos + 1)))
                    input += e.getActionCommand();
            } else {//小数点前一个必须是数字
                if (isDigit(input.charAt(input.length() - 1)))
                    input += e.getActionCommand();
            }
            show.setText(input);
        } else if ((e.getSource()) == eql) {
            input += '#';//算式末尾添加’#’
            //从算式中拆分出数字
            String[] nums = input.split("[^.0-9]"); //将字符串分割为子字符串,然后结果以字符串数组返回
            //任何字符除了.、0-9
            List<Double> numList = new ArrayList<>();
            for (int i = 0; i < nums.length; i++) {//将每个数字串转化为Double类型
                if (!"".equals(nums[i]))
                    numList.add(Double.parseDouble(nums[i]));
            }
            double out = getValueOfMid(input, numList);//利用中缀式求值
            output = "" + out;//将求得的结果转为字符串


            input = input.substring(0, input.length() - 1);//去除算式后的’#’
            showexp.setText(input);//第一个单行文本框展示算式
            show.setText(output);//第二个单行文本框显示结果
            input = "";//存储算式的字符串清空
        } else {
            if (input == "0") input = "";
            input += e.getActionCommand();
            show.setText(input);
        }

    }

       3.对输入的算式求解

这里使用的是双栈运算符优先级法:

//对输入的算式(中缀式)进行求值
    public static double getValueOfMid(String input, List<Double> numList) {
        Stack<Character> OPTR = new Stack<>();//定义算符栈
        Stack<Double> OPND = new Stack<>();//定义操作数栈
        double output = 0;//最终结果
        double a, b;//定义两个操作数
        char sym;//定义运算符
        OPTR.push('#');
        int i = 0, j = 0;
        while (input.charAt(i) != '#' || OPTR.peek() != '#') //peek():返回栈顶元素但不移除它
        {

            if (isDigit(input.charAt(i)))//数字直接入OPND栈
            {//此字符是数字
                while (isDigit(input.charAt(i)) || input.charAt(i) == '.')//此字符是数字或者此字符是·
                {
                    i++;//跳过此字符
                    if (i == input.length())
                        break;
                }
                i--;
                OPND.push(numList.get(j)); //操作数入栈
                j++;
            } else {
                //扫描到是操作符时,如这个运算符比OPTR栈顶运算符的优先级高,则入栈;
                //            如这个运算符比OPTR栈顶运算符优先级低,则从OPND栈中弹出两个运算符,从栈OPTR中弹出栈顶运算符进行运算,并将运算结果压入栈OPND。
                sym = OPTR.peek();
                int m = isp(sym); //m为栈内运算符
                int n = icp(input.charAt(i));//n为栈外元素

                if (m < n || n == '(') OPTR.push(input.charAt(i));//栈内算符优先级小于栈外,则栈外运算符压栈,左括号直接入栈

                else if (m > n)//栈内运算符优先级大于栈外运算符优先级
                {
                    sym = OPTR.peek();
                    OPTR.pop();//取出栈内的运算符

                    if (sym != '(' && m == n || m > n) {
                        b = OPND.peek();//从操作数栈取出一个操作数
                        OPND.pop();
                        if (!OPND.empty() && OPTR.peek() != '(')//当操作数栈不为空且不为’(’时继续取出栈中的数进行运算
                        {
                            a = OPND.peek();
                            OPND.pop();
                            OPND.push(compute(a, sym, b));
                            continue;
                        } else if (sym == ')') {
                            while (sym != '(') {
                                b = OPND.peek();
                                OPND.pop();
                                a = OPND.peek();
                                OPND.pop();
                                OPND.push(compute(a, sym, b));
                            }
                        } else//处理负数
                        {
                            switch (sym)//实现一元运算符的运算
                            {
                                case '+':
                                    OPND.push(b);
                                    break;
                                case '-':
                                    OPND.push(-b);
                                    break;
                            }
                            continue;
                        }

                    }

                    if (sym == '(') OPTR.pop();
                }
            }
            i++;
        }
        output = OPND.peek();
        return output;
    }
}

参考资料:Java Swing JFrame和JPanel:窗口容器和面板容器 (biancheng.net)http://c.biancheng.net/view/1209.html

        

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值