Java编写简单计算器

本博客链接:
https://blog.csdn.net/weixin_50301255/article/details/110731387

本文用Javaswing来实现一个简单计算器,主要内容为图形用户界面GUI的实现以及运算表达式核心算法的设计编写。

程序运行环境为Windows10 ,编译环境为MyEclipse 。

一、具体功能:

1、:输入,输出

输入:允许输入带有括号的完整计算式(例 8*(4-95)+5÷2*e-pi) 输出:输出Double类型的结果 输出:整个运算表达式并保存于历史记录中

  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

2、:功能

 基本的加,减,乘,除,四则运算 平方运算 开方运算 求余运算
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

最终界面如下图:

除了常规的数字按钮和运算符,还有两个常数e,pi(π),清空键AC,括号运算符(),平方(x^x)和开方(sqrt)运算符,输入显示框以及历史记录文本框,文本框的垂直滚动条和水平滚动条。

二、主要思想:

1:中缀表达式转为后缀表达式

准备:

①后缀表达式队列:postQueue,用于存储逆波兰表达式(其实不用队列排序直接输出也行)
②操作符栈:opStack,对用户输入的操作符进行处理,用于存储运算符

算法思想:

从左向右依次读取算术表达式的元素X,分以下情况进行不同的处理:
(1)如果X是操作数,直接入队
(2)如果X是运算符,再分以下情况:
a)如果栈为空,直接入栈。
b)如果X==”(“,直接入栈。
c)如果X==”)“,则将栈里的元素逐个出栈,并入队到后缀表达式队列中,直到第一个配对的”(”出栈。(注:“(”和“)”都不 入队)
d)如果是其他操作符(+ - * /),则和栈顶元素进行比较优先级。 如果栈顶元素的优先级大于等于X,则出栈并把栈中弹出的元素入队,直到栈顶元素的优先级小于X或者栈为空。弹出完这些元素后,才将遇到的操作符压入到栈中。
(3)最后将栈中剩余的操作符全部入队。

示意图:


2、计算后缀表达式

准备:

需要用到一个结果栈Res_Stack :用于存放计算的中间过程的值和最终结果

算法思想:

1、从左开始向右遍历后缀表达式的元素。
2、如果取到的元素是操作数,直接入栈Res_Stack,如果是运算符,从栈中弹出2个数进行运算,然后把运算结果入栈
3、当遍历完后缀表达式时,计算结果就保存在栈里了。

示意图:


三、结果测试


分析:

1、可实现基本四则运算及平方、开方、求余运算。
2、运算表达式可显示于输入界面并保存于历史记录栏
3、输入界面和历史记录栏皆可实现不断字自动换行功能以及滚动条功能
4、不足之处:进行平方和开方运算时其保存在历史记录中的表达式会出现两个等号及两个结果。

四、完整源代码(每行代码已附有详细注释)

package software;import java.awt.*;import java.awt.event.ActionEvent;import java.awt.event.ActionListener;import javax.swing.*;//Calculator类,继承JFrame框架,实现事件监听器接口public class Calculator extends JFrame implements ActionListener { private String[] KEYS = { '7', '8', '9', 'AC', '4', '5', '6', '-', '1', '2', '3', '+', '0', 'e', 'pi', '/', 'sqrt', '%', 'x*x', '*', '(', ')', '.', '=' }; private JButton keys[] = new JButton[KEYS.length]; private JTextArea resultText = new JTextArea('0.0');// 文本域组件TextArea可容纳多行文本;文本框内容初始值设为0.0 private JTextArea History = new JTextArea();// 历史记录文本框初始值设为空 private JPanel jp1=new JPanel(); private JPanel jp2=new JPanel(); private JScrollPane gdt1=new JScrollPane(resultText);//给输入显示屏文本域新建一个垂直滚动滑条 private JScrollPane gdt2=new JScrollPane(History);//给历史记录文本域新建一个垂直滚动滑条 // private JScrollPane gdt3=new JScrollPane(History);//给历史记录文本域新建一个水平滚动滑条 private JLabel label = new JLabel('历史记录'); private String b = '';// 构造方法 public Calculator() {super('Caculator');//“超”关键字,表示调用父类的构造函数,resultText.setBounds(20, 18, 255, 115);// 设置文本框大小resultText.setAlignmentX(RIGHT_ALIGNMENT);// 文本框内容右对齐resultText.setEditable(false);// 文本框不允许修改结果History.setBounds(290, 40, 250,370);// 设置文本框大小History.setAlignmentX(LEFT_ALIGNMENT);// 文本框内容右对齐History.setEditable(false);// 文本框不允许修改结果label.setBounds(300, 15, 100, 20);//设置标签位置及大小jp2.setBounds(290,40,250,370);//设置面板窗口位置及大小jp2.setLayout(new GridLayout());jp1.setBounds(20,18,255,115);//设置面板窗口位置及大小jp1.setLayout(new GridLayout());resultText.setLineWrap(true);// 激活自动换行功能resultText.setWrapStyleWord(true);// 激活断行不断字功能resultText.setSelectedTextColor(Color.RED);History.setLineWrap(true);//自动换行History.setWrapStyleWord(true);History.setSelectedTextColor(Color.blue);gdt1.setViewportView(resultText);//使滚动条显示出来gdt2.setViewportView(History);gdt1.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);//设置让垂直滚动条一直显示gdt2.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);//设置让垂直滚动条一直显示gdt2.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);//设置让水平滚动条一直显示jp1.add(gdt1);//将滚动条添加入面板窗口中jp2.add(gdt2);this.add(jp1);//将面板添加到总窗体中this.add(jp2);//将面板添加到总窗体中this.setLayout(null);this.add(label);// 新建“历史记录”标签//this.add(resultText);// 新建文本框,该语句会添加进去一个新的JTextArea导致带有滚动条的文本无法显示或者发生覆盖//this.add(History);// 新建历史记录文本框,该语句会添加进去一个新的JTextArea导致带有滚动条的文本无法显示// 放置按钮int x = 20, y = 150;for (int i = 0; i < KEYS.length; i++){ keys[i] = new JButton(); keys[i].setText(KEYS[i]); keys[i].setBounds(x, y, 60, 40); if (x < 215) {x += 65; } else {x = 20;y += 45; } this.add(keys[i]);}for (int i = 0; i < KEYS.length; i++)// 每个按钮都注册事件监听器{ keys[i].addActionListener(this);}this.setResizable(false);this.setBounds(500, 200, 567, 480);this.setDefaultCloseOperation(EXIT_ON_CLOSE);this.setVisible(true); }// 事件处理 public void actionPerformed(ActionEvent e) {//History.setText(b);//使输入的表达式显示在历史记录文本框中String label=e.getActionCommand();//获得事件源的标签if(label=='=')//{ resultText.setText(this.b); History.setText(History.getText()+resultText.getText()); if(label=='=')//调用计算方法,得出最终结果 {String s[]=houzhui(this.b);String result=Result(s);this.b=result+'';//更新文本框,当前结果在字符串b中,并未删除,下一次输入接着此结果以实现连续运算resultText.setText(this.b);History.setText(History.getText()+'='+resultText.getText()+'\n'); }} else if(label=='AC')//清空按钮,消除显示屏文本框前面所有的输入和结果 {this.b=''; resultText.setText('0');//更新文本域的显示,显示初始值; }else if(label=='sqrt'){ String n=kfys(this.b); resultText.setText('sqrt'+'('+this.b+')'+'='+n);//使运算表达式显示在输入界面 History.setText(History.getText()+'sqrt'+'('+this.b+')'+'=');//获取输入界面的运算表达式并使其显示在历史记录文本框 this.b=n;}else if(label=='x*x'){ String m=pfys(this.b); resultText.setText(this.b+'^2'+'='+m);//使运算表达式显示在输入界面 History.setText(History.getText()+this.b+'^2'+'=');//获取输入界面的运算表达式并使其显示在历史记录文本框 this.b=m;}else if(label=='e'||label=='pi'){ if(label=='e') {String m=String.valueOf(2.71828);//将e的值以字符串的形式传给m this.b=this.b+m;//保留显示m之前输入的运算符或数字字符继续下一步运算 resultText.setText(this.b);// History.setText(History.getText()+this.b); } if(label=='pi') {String m=String.valueOf(3.14159265); this.b=this.b+m; resultText.setText(this.b);// History.setText(History.getText()+this.b); }}else{ this.b=this.b+label; resultText.setText(this.b); // History.setText(History.getText()+this.b); }//History.setText(History.getText()+this.b);//使输入的表达式显示在历史记录文本框中 }//将中缀表达式转换为后缀表达式 private String[] houzhui(String str) {String s = '';// 用于承接多位数的字符串char opStack[] = new char[100];// 静态栈,对用户输入的操作符进行处理,用于存储运算符String postQueue[] = new String[100];// 后缀表达式字符串数组,为了将多位数存储为独立的字符串int top = -1, j = 0;// 静态指针top,控制变量jfor (int i = 0; i < str.length(); i++)// 遍历中缀表达式// indexof函数,返回字串首次出现的位置;charAt函数返回index位置处的字符;{ if ('0123456789.'.indexOf(str.charAt(i)) >= 0) // 遇到数字字符的情况 {s = '';// 作为承接字符,每次开始时都要清空for (; i < str.length() && '0123456789.'.indexOf(str.charAt(i)) >= 0; i++) { s = s + str.charAt(i);}i--;postQueue[j] = s;// 数字字符直接加入后缀表达式j++; } else if ('('.indexOf(str.charAt(i)) >= 0) {// 遇到左括号top++;opStack[top] = str.charAt(i);// 左括号入栈 } else if (')'.indexOf(str.charAt(i)) >= 0) {// 遇到右括号for (;;)// 栈顶元素循环出栈,直到遇到左括号为止{ if (opStack[top] != '(') {// 栈顶元素不是左括号postQueue[j] = opStack[top] + '';// 栈顶元素出栈j++;top--; } else { // 找到栈顶元素是左括号top--;// 删除栈顶左括号break;// 循环结束 }} } if ('*%/'.indexOf(str.charAt(i)) >= 0)// 遇到高优先级运算符 {if (top == -1) {// 若栈为空则直接入栈 top++; opStack[top] = str.charAt(i);} else {// 栈不为空,把栈中弹出的元素入队,直到栈顶元素优先级小于x或者栈为空 if ('*%/'.indexOf(opStack[top]) >= 0) {// 栈顶元素也为高优先级运算符postQueue[j] = opStack[top] + '';// 栈顶元素出栈进入后缀表达式j++;opStack[top] = str.charAt(i);// 当前运算符入栈 } else if ('('.indexOf(opStack[top]) >= 0) {// 栈顶元素为左括号,当前运算符入栈top++;opStack[top] = str.charAt(i); } else if ('+-'.indexOf(str.charAt(i)) >= 0) {// 遇到低优先级运算符postQueue[j] = opStack[top] + '';// 栈顶元素出栈进入后最表达式j++;opStack[top] = str.charAt(i);// 当前元素入栈 }} } else if ('+-'.indexOf(str.charAt(i)) >= 0) {if (top == -1) { top++; opStack[top] = str.charAt(i);} else { if ('*%/'.indexOf(opStack[top]) >= 0) {// 栈顶元素也为高优先级运算符postQueue[j] = opStack[top] + '';// 栈顶元素出栈进入后缀表达式j++;opStack[top] = str.charAt(i);// 当前运算符入栈 } else if ('('.indexOf(opStack[top]) >= 0) {// 栈顶元素为左括号,当前运算符入栈top++;opStack[top] = str.charAt(i); } else if ('+-'.indexOf(str.charAt(i)) >= 0) {// 遇到低优先级运算符postQueue[j] = opStack[top] + '';// 栈顶元素出栈进入后最表达式j++;opStack[top] = str.charAt(i);// 当前元素入栈 }} }}for (; top != -1;) {// 遍历结束后将栈中剩余元素依次出栈进入后缀表达式 postQueue[j] = opStack[top] + ''; j++; top--;}return postQueue; }//开方运算方法 public String kfys(String str) {String result = '';double a = Double.parseDouble(str), b = 0;b = Math.sqrt(a);result = String.valueOf(b);//将运算结果转换为string类型并赋给string类型的变量resultreturn result; }//平方运算方法 public String pfys(String str) {String result = '';double a = Double.parseDouble(str), b = 0;b = Math.pow(a, 2);result = String.valueOf(b);return result; } // 计算后缀表达式,并返回最终结果 public String Result(String str[]) {String Result[] = new String[100];// 顺序存储的栈,数据类型为字符串int Top = -1;// 静态指针Topfor (int i = 0; str[i] != null; i++) { if ('+-*%/'.indexOf(str[i]) < 0) {Top++;Result[Top] = str[i]; } if ('+-*%/'.indexOf(str[i]) >= 0)// 遇到运算符字符,将栈顶两个元素出栈计算并将结果返回栈顶 {double x, y, n;x = Double.parseDouble(Result[Top]);// 顺序出栈两个数字字符串,并转换为double类型Top--;y = Double.parseDouble(Result[Top]);Top--;if ('-'.indexOf(str[i]) >= 0) { n = y - x; Top++; Result[Top] = String.valueOf(n);// 将运算结果重新入栈}if ('+'.indexOf(str[i]) >= 0) { n = y + x; Top++; Result[Top] = String.valueOf(n);// 将运算结果重新入栈}if ('*'.indexOf(str[i]) >= 0) { n = y * x; Top++; Result[Top] = String.valueOf(n);// 将运算结果重新入栈}if ('/'.indexOf(str[i]) >= 0){ if (x == 0)// 被除数不允许为0 {String s = 'error!';return s; } else {n = y / x;Top++;Result[Top] = String.valueOf(n);// 将运算结果重新入栈 }}if ('%'.indexOf(str[i]) >= 0) { if (x == 0)// 被除数不允许为0 {String s = 'error!';return s; } else {n = y % x;Top++;Result[Top] = String.valueOf(n);// 将运算结果重新入栈 }} }}return Result[Top];// 返回最终结果 } // 主函数 public static void main(String[] args) {Calculator a = new Calculator(); }}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316

欢迎评论,指正!

(0)

相关推荐

  • JavaScript连载23-String对象及其常用方法

    一.String对象 1.字符串的所有方法,都不会修改字符串本身(字符串是不可变的,操作完成之后会返回一个新的字符串) 注意点:拼接大量的字符串会有性能问题,我们经常使用服务器渲染和模板引擎来解决这个 ...

  • 前端教程:JavascriptString.charAt()方法

    这个方法返回从指定索引的字符.字符串中的字符进行索引从左向右.第一个字符的索引是0,并且在一个叫stringName字符串的最后一个字符的索引是stringName.length-1. 语法 stri ...

  • LeetCode之Keyboard Row

    LeetCode之Keyboard Row

  • Java实现简单计算器(四则运算,括号,浮点数)

    用堆栈把输入的中缀表达式转化为后缀表达式,然后计算.用的是数据结构里的代码:表达式转换(中缀表达式转为后缀表达式) import java.awt.Container;import java.awt. ...

  • 从0到1实现一个简单计算器

    前言 学习编程语言最重要的就是实践.很多小伙伴在学习完编程语言后,一直停留在基础阶段,只懂一大堆理论知识,而不懂得实践.那么,今天我们一起来动手做一个小计算器,回顾下学习过的知识,同时这也是很多大学计 ...

  • C语言编写简单实现淘宝购物功能 答辩课题实用

      C语言编写简单实现淘宝购物功能,并能够实现 登陆账号验证 添加购物车 l付密码 错误上限 支付成功 等.. #include <stdio.h> #include <string ...

  • 用 python 编写简单的证书域名到期报警脚本

    将脚本放在服务器的计划任务内,定时检测,证书到期不足60天发送报警邮件及钉钉提醒. 在服务器上需要提前安装好 requests 库. pip install requests 钉钉机器人 https: ...

  • java 编写游戏联网

    作者:炭烧生蚝 cnblogs.com/tanshaoshenghao/p/10708586.html 介绍 通过本项目能够更直观地理解应用层和运输层网络协议, 以及继承封装多态的运用. 网络部分是本 ...

  • 2021最新整理Java教程:Java 智能卡迷你计算器

    介绍 本文是关于编写基于Java智能卡的应用程序.本教程将帮助初学者理解Java智能卡和主机应用程序之间的概念和通信.我已经看到Java智能卡技术的初学者提出了一些简单的问题,所以我决定为他们提供一个 ...

  • 发那科机器人编写简单的程序教程

    Robot 为自动化设备,但在自动化运转之前,必须先告诉Robot 要自动完成哪些动作,透过「撰写Robot 程序」可达到此目的. Robot 程序主要由「动作指令」构成,只要熟悉手动操作Robot ...

  • java之简单的Callback使用总结

    java之简单的Callback使用总结

  • 【R分享|实战】利用rvest包编写简单的静态网页爬虫

    把梦想藏在秋风中,闯过灯火阑珊."R实战"专题·第21篇编辑 | 明允  5160字 |13分钟阅读本期推送内容一次有趣的尝试:在学习<基于R语言的自动数据收集>过程中 ...