java-BLOG3
作者:互联网
前言
第三次也是最后博客文章主要是关于java课程第三阶段关于PTA题目集、超星作业以及在实验平台上布置的一些题型。相较于第一、二阶段的作业总结而言此次作业更加针对于总结在面向对象过程中的三大技术特性,即封装性、继承性和多态性,类图的设计更加普遍。在不断深入学习Java的过程又延伸到了Javafx,在这个阶段也挺有趣的,不仅能把图片放在窗口还可以通过Java代码的撰写做出很多好玩的小程序。通过学习类属性的访问权限,了解到了四种访问权限:public,private,protected,default;以及不同类的类型:实体类,业务类,接口类;对于类设计上,学习的多,代表思路与编程思想变得更为开阔且严谨,开始真正考虑到如何将一个题目设计到恰如其分,不同的设计思路带来的不同效果。
设计与分析
第三阶段的作业次数与第一、二阶段是一样的,一共有三次,但相比较于第二个,本阶段作业难度有所提升,涉及到的也大多是继承和多态。本阶段的学习让我们还是主要了解了以下三个方面的内容:
1.正则表达式的简单使用。
2.面向对象的三大特性:封装性、继承性、多态性。
3.同时也了解了一些面向对象编程的原则:单一职责原则、开闭原则等。
同时通过可查阅的资料了解到类的继承与多态的特征与属性:关于继承与多态:面向对象的三大特征:封装性、继承性、多态性,继承是多态的前提,如果没有继承,就没有多态。
特点:
1、子类可以拥有父类的“内容”
2、子类还可以拥有自己专有的新内容
3、单继承:一个类的直接父类只能有唯一一个
4、可以多级继承,有多个子类。在继承关系当中,“子类就是一个父类”。也就是说,子类可以被当作父类看待
定义:
1 String m="\\d+"; 2 Pattern p=Pattern.compile(m); 3 Matcher s=p.matcher(arr);
题目一:7-1 电信计费系列1-座机计费
难度迭代:作业1 > 作业 2 > 作业 3
知识迭代:作业 1 ——> 作业2——> 作业3。第七次作业相比之前的图形大作业,难度下降较多,个人来说后面后面布置的几次大作业更能培养java面向对象编程的意识和能力。第八次作业由于实现功能简单,难度较小。第九次作业相比座机收费难度稍大,因为要考虑座机手机互打,手机之间互打的情况。
实现一个简单的电信计费程序:
假设南昌市电信分公司针对市内座机用户采用的计费方式:
月租20元,接电话免费,市内拨打电话0.1元/分钟,省内长途0.3元/分钟,国内长途拨打0.6元/分钟。不足一分钟按一分钟计。
南昌市的区号:0791,江西省内各地市区号包括:0790~0799以及0701。
输入格式:
输入信息包括两种类型
1、逐行输入南昌市用户开户的信息,每行一个用户,
格式:u-号码 计费类型 (计费类型包括:0-座机 1-手机实时计费 2-手机A套餐)
例如:u-079186300001 0
座机号码除区号外由是7-8位数字组成。
本题只考虑计费类型0-座机计费,电信系列2、3题会逐步增加计费类型。
2、逐行输入本月某些用户的通讯信息,通讯信息格式:
座机呼叫座机:t-主叫号码 接听号码 起始时间 结束时间
t-079186330022 058686330022 2022.1.3 10:00:25 2022.1.3 10:05:11
以上四项内容之间以一个英文空格分隔,
时间必须符合"yyyy.MM.dd HH:mm:ss"格式。提示:使用SimpleDateFormat类。
以上两类信息,先输入所有开户信息,再输入所有通讯信息,最后一行以“end”结束。
注意:
本题非法输入只做格式非法的判断,不做内容是否合理的判断(时间除外,否则无法计算),比如:
1、输入的所有通讯信息均认为是同一个月的通讯信息,不做日期是否在同一个月还是多个月的判定,直接将通讯费用累加,因此月租只计算一次。
2、记录中如果同一电话号码的多条通话记录时间出现重合,这种情况也不做判断,直接 计算每条记录的费用并累加。
3、用户区号不为南昌市的区号也作为正常用户处理。类图如下:
:
这是此类作业的开端,一定要把基础写好。
类图比较麻烦,有许多类要创建,仔细看,慢慢看,先把他们串联起来再动手。
我有个小细节与题目提供的类图不同:
我的计算价格的抽象方法写在了rule的最父的类里
需要注意的点,
1.判断输入格式是否正确,里面有许多小细节,一定要注意,
比如,区号可能是三位,
2.价钱计算。
计算的时候记得把时间转成整型,避免除以60后后面有小数,影响结果。
最后余数计算,不到一分钟按一分钟计
源码:
1 import java.util.ArrayList; 2 import java.util.Scanner; 3 public class Main{ 4 5 public static void main(String[] args) { 6 // TODO Auto-generated method stub 7 Scanner in = new Scanner(System.in); 8 ArrayList<String> str = new ArrayList<String>(); 9 int balance = 80; 10 String s = ""; 11 while(!s.equals("end")) { 12 s = in.nextLine(); 13 str.add(s); 14 } 15 String[] s1 = str.get(0).split("u-| "); 16 String[] s2 = str.get(1).split("t-| "); 17 String[] s3 = s2[4].split(":"); 18 String[] s4 = s2[6].split(":"); 19 int[] n = new int[3]; 20 int[] n1 = new int[3]; 21 for(int i = 0; i < n.length; i++) { 22 n[i] = Integer.parseInt(s3[i]); 23 n1[i] = Integer.parseInt(s4[i]); 24 } 25 int t; 26 if(n1[2] - n[2] > 0) { 27 t = 60*(n1[0] - n[0]) + n1[1] -n[1] + 1; 28 } 29 else { 30 t = 60*(n1[0] - n[0]) + n1[1] -n[1]; 31 } 32 33 if(s2[2].matches("0791\\d{8,9}")) { 34 System.out.println(s1[1] + " " + String.format("%.1f",0.1*t) + " " + (80 - 0.1*t)); 35 // System.out.println(s2[2] + " " + "0.0" + " " + "80.0"); 36 } 37 else if(s2[2].matches("079[234567890]\\d{8,9}")) 38 { 39 System.out.println(s1[1] + " " + String.format("%.1f",0.3*t) + " " + (80 - 0.3*t)); 40 } 41 else 42 { 43 System.out.println(s1[1] + " " + String.format("%.1f",0.6*t) + " " + (80 - 0.6*t)); 44 45 } 46 } 47 48 } 49 class User{ 50 double balance = 100; 51 String number; 52 } 53 54 class ChargeMode{ 55 56 } 57 58 class UserRecord{ 59 60 }
题目二:7-1 电信计费系列2-手机+座机计费
实现南昌市电信分公司的计费程序,假设该公司针对手机和座机用户分别采取了两种计费方案,分别如下:
1、针对市内座机用户采用的计费方式(与电信计费系列1内容相同):
月租20元,接电话免费,市内拨打电话0.1元/分钟,省内长途0.3元/分钟,国内长途拨打0.6元/分钟。不足一分钟按一分钟计。
假设本市的区号:0791,江西省内各地市区号包括:0790~0799以及0701。
2、针对手机用户采用实时计费方式:
月租15元,市内省内接电话均免费,市内拨打市内电话0.1元/分钟,市内拨打省内电话0.2元/分钟,市内拨打省外电话0.3元/分钟,省内漫游打电话0.3元/分钟,省外漫游接听0.3元/分钟,省外漫游拨打0.6元/分钟;
注:被叫电话属于市内、省内还是国内由被叫电话的接听地点区号决定,比如以下案例中,南昌市手机用户13307912264在区号为020的广州接听了电话,主叫号码应被计算为拨打了一个省外长途,同时,手机用户13307912264也要被计算省外接听漫游费:
u-13307912264 1
t-079186330022 13307912264 020 2022.1.3 10:00:25 2022.1.3 10:05:11
对于新添加的功能,我们只需要添加一些新的类;来实现上面的功能来对相关的数据进行存储和计算,这道题目使得对象的可能变多了,座机座机,座机手机,手机手机,手机座机,此外就是手机端考虑省内省外.根据不同内容分别判定.
由于代码与题目三大多数相同就不放出 都包含于下面代码源码
题目三:7-1 电信计费系列3-短信计费
实现一个简单的电信计费程序,针对手机的短信采用如下计费方式:
1、接收短信免费,发送短信0.1元/条,超过3条0.2元/条,超过5条0.3元/条。
2、如果一次发送短信的字符数量超过10个,按每10个字符一条短信进行计算。
输入:
输入信息包括两种类型
1、逐行输入南昌市手机用户开户的信息,每行一个用户。
格式:u-号码 计费类型 (计费类型包括:0-座机 1-手机实时计费 2-手机A套餐 3-手机短信计费)
例如:u-13305862264 3
座机号码由区号和电话号码拼接而成,电话号码包含7-8位数字,区号最高位是0。
手机号码由11位数字构成,最高位是1。
本题只针对类型3-手机短信计费。
2、逐行输入本月某些用户的短信信息,短信的格式:
m-主叫号码,接收号码,短信内容 (短信内容只能由数字、字母、空格、英文逗号、英文句号组成)
m-18907910010 13305862264 welcome to jiangxi.
m-13305862264 18907910010 thank you.
源码:
1 2 3 import java.util.ArrayList; 4 import java.util.Comparator; 5 import java.util.Scanner; 6 import java.util.regex.Matcher; 7 import java.util.regex.Pattern; 8 import java.math.BigDecimal; 9 import java.text.SimpleDateFormat; 10 import java.util.Date; 11 import java.util.Locale; 12 import java.text.ParseException; 13 14 public class Main { 15 16 public static void main(String[] args) { 17 18 Outputtool outputtool = new Outputtool(); 19 20 Inputdeal inputdeal = new Inputdeal(); 21 22 ArrayList<User> users = new ArrayList<>(); 23 24 Scanner in = new Scanner(System.in); 25 26 String input = in.nextLine(); 27 28 while (!input.equals("end")) { 29 if (1 == inputdeal.check(input)) { 30 inputdeal.writeUser(users, input); 31 } else if (2 == inputdeal.check(input)) { 32 inputdeal.writeRecord(users, input); 33 } 34 input = in.nextLine(); 35 } 36 37 users.sort(new Comparator<User>() { 38 39 @Override 40 public int compare(User u1, User u2) { 41 if (Double.parseDouble(u1.getNumber()) > Double.parseDouble(u2.getNumber())) { 42 return 1; 43 } else { 44 return -1; 45 } 46 } 47 }); 48 49 for (User u : users) { 50 System.out.print(u.getNumber() + " "); 51 outputtool.output(u.calCost()); 52 System.out.print(" "); 53 outputtool.output(u.calBalance()); 54 System.out.println(); 55 56 } 57 58 } 59 60 } 61 62 abstract class ChargeMode { 63 protected ArrayList<ChargeRule> chargeRules = new ArrayList<>(); 64 65 public abstract double calCost(UserRecords userRecords); 66 67 public abstract double getMonthlyRent(); 68 69 public ArrayList<ChargeRule> getChargeRules() { 70 return chargeRules; 71 } 72 73 public void setChargeRules(ArrayList<ChargeRule> chargeRules) { 74 this.chargeRules = chargeRules; 75 } 76 } 77 78 class UserRecords { 79 80 private ArrayList<CallRecord> callingInCityRecords = new ArrayList<CallRecord>(); 81 private ArrayList<CallRecord> callingInProvinceRecords = new ArrayList<CallRecord>(); 82 private ArrayList<CallRecord> callingInLandRecords = new ArrayList<CallRecord>(); 83 private ArrayList<CallRecord> answerInCityRecords = new ArrayList<CallRecord>(); 84 private ArrayList<CallRecord> answerInProvinceRecords = new ArrayList<CallRecord>(); 85 private ArrayList<CallRecord> answerInLandRecords = new ArrayList<CallRecord>(); 86 private ArrayList<MessageRecord> sendMessageRecords = new ArrayList<MessageRecord>(); 87 private ArrayList<MessageRecord> receiveMessageRecords = new ArrayList<MessageRecord>(); 88 89 public void addCallingInCityRecords(CallRecord callRecord) { 90 callingInCityRecords.add(callRecord); 91 } 92 93 public void addCallingInProvinceRecords(CallRecord callRecord) { 94 callingInProvinceRecords.add(callRecord); 95 } 96 97 public void addCallingInLandRecords(CallRecord callRecord) { 98 callingInLandRecords.add(callRecord); 99 } 100 101 public void addAnswerInCityRecords(CallRecord callRecord) { 102 answerInCityRecords.add(callRecord); 103 } 104 105 public void aaddAnswerInProvinceRecords(CallRecord callRecord) { 106 answerInProvinceRecords.add(callRecord); 107 } 108 109 public void addAnswerInLandRecords(CallRecord callRecord) { 110 answerInLandRecords.add(callRecord); 111 } 112 113 public void addSendMessageRecords(MessageRecord callRecord) { 114 sendMessageRecords.add(callRecord); 115 } 116 117 public void addReceiveMessageRecords(MessageRecord callRecord) { 118 receiveMessageRecords.add(callRecord); 119 } 120 121 public ArrayList<CallRecord> getCallingInCityRecords() { 122 return callingInCityRecords; 123 } 124 125 public void setCallingInCityRecords(ArrayList<CallRecord> callingInCityRecords) { 126 this.callingInCityRecords = callingInCityRecords; 127 } 128 129 public ArrayList<CallRecord> getCallingInProvinceRecords() { 130 return callingInProvinceRecords; 131 } 132 133 public void setCallingInProvinceRecords(ArrayList<CallRecord> callingInProvinceRecords) { 134 this.callingInProvinceRecords = callingInProvinceRecords; 135 } 136 137 public ArrayList<CallRecord> getCallingInLandRecords() { 138 return callingInLandRecords; 139 } 140 141 public void setCallingInLandRecords(ArrayList<CallRecord> callingInLandRecords) { 142 this.callingInLandRecords = callingInLandRecords; 143 } 144 145 public ArrayList<CallRecord> getAnswerInCityRecords() { 146 return answerInCityRecords; 147 } 148 149 public void setAnswerInCityRecords(ArrayList<CallRecord> answerInCityRecords) { 150 this.answerInCityRecords = answerInCityRecords; 151 } 152 153 public ArrayList<CallRecord> getAnswerInProvinceRecords() { 154 return answerInProvinceRecords; 155 } 156 157 public void setAnswerInProvinceRecords(ArrayList<CallRecord> answerInProvinceRecords) { 158 this.answerInProvinceRecords = answerInProvinceRecords; 159 } 160 161 public ArrayList<CallRecord> getAnswerInLandRecords() { 162 return answerInLandRecords; 163 } 164 165 public void setAnswerInLandRecords(ArrayList<CallRecord> answerInLandRecords) { 166 this.answerInLandRecords = answerInLandRecords; 167 } 168 169 public ArrayList<MessageRecord> getSendMessageRecords() { 170 return sendMessageRecords; 171 } 172 173 public void setSendMessageRecords(ArrayList<MessageRecord> sendMessageRecords) { 174 this.sendMessageRecords = sendMessageRecords; 175 } 176 177 public ArrayList<MessageRecord> getReceiveMessageRecords() { 178 return receiveMessageRecords; 179 } 180 181 public void setReceiveMessageRecords(ArrayList<MessageRecord> receiveMessageRecords) { 182 this.receiveMessageRecords = receiveMessageRecords; 183 } 184 185 } 186 187 class LandlinePhoneCharging extends ChargeMode { 188 189 private double monthlyRent = 20; 190 191 public LandlinePhoneCharging() { 192 super(); 193 chargeRules.add(new LandPhoneInCityRule()); 194 chargeRules.add(new LandPhoneInProvinceRule()); 195 chargeRules.add(new LandPhoneInlandRule()); 196 } 197 198 @Override 199 public double calCost(UserRecords userRecords) { 200 double sumCost = 0; 201 for (ChargeRule rule : chargeRules) { 202 sumCost += rule.calCost(userRecords); 203 } 204 return sumCost; 205 } 206 207 @Override 208 public double getMonthlyRent() { 209 return monthlyRent; 210 } 211 212 } 213 214 class Inputdeal { 215 216 public int check(String input) { 217 String[] inputs = input.split(" "); 218 if (inputs.length == 2) { 219 if (inputs[0].matches("^u-[0-9]{11,13}$")) { 220 if (Integer.parseInt(inputs[1]) >= 0) { 221 if (Integer.parseInt(inputs[1]) <= 2) { 222 return 1; 223 } 224 } 225 } 226 } else if (inputs.length == 6) { 227 // if(input.matches("[t]-0791[0-9]{7,8}\\s" + "0[0-9]{9,11}\\s" + 228 // "((([0-9]{3}[1-9]|[0-9]{2}[1-9][0-9]|[0-9][1-9][0-9]{2}|[1-9][0-9]{3})\\.(((0?[13578]|1[02])\\.(0?" + 229 // "[1-9]|[12][0-9]|3[01]))|(([469]|11)\\.([1-9]|[12][0-9]|30))|(2\\.([1-9]|[1][0-9]|2[0-8]))))|(((" + 230 // "[0-9]{2})([48]|[2468][048]|[13579][26])|(([48]|[2468][048]|[3579][26])00))\\.2\\.29))" + 231 // "\\s([0-1]?[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])\\s" + 232 // "((([0-9]{3}[1-9]|[0-9]{2}[1-9][0-9]|[0-9][1-9][0-9]{2}|[1-9][0-9]{3})\\.((([13578]|1[02])\\.(" + 233 // "[1-9]|[12][0-9]|3[01]))|(([469]|11)\\.([1-9]|[12][0-9]|30))|(2\\.([1-9]|[1][0-9]|2[0-8]))))|(((" + 234 // "[0-9]{2})([48]|[2468][048]|[13579][26])|(([48]|[2468][048]|[3579][26])00))\\.2\\.29))" + 235 // "\\s([0-1]?[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])")) { 236 // return 2; 237 // } 238 if (validate(inputs[2])) 239 if (validate(inputs[4])) 240 if (validatet(inputs[3])) 241 if (validatet(inputs[5])) 242 // if (inputs[0].matches("^t\\-[0-9]{10,12}$")) { 243 if (inputs[0].matches("[t]-0791[0-9]{7,8}")) { 244 if (inputs[1].matches(".[0-9]{9,11}")) 245 return 2; 246 } 247 } 248 return 0; 249 } 250 251 private boolean validatet(String string) { 252 String[] split = string.split(":"); 253 if (!string.matches("^([0-1]?[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])$")) { 254 return false; 255 } 256 return true; 257 } 258 259 public static boolean validate(String dateString) { 260 // 使用正则表达式 测试 字符 符合 dddd.dd.dd 的格式(d表示数字) 261 Pattern p = Pattern.compile("\\d{4}+[\\.]\\d{1,2}+[\\.]\\d{1,2}+"); 262 Matcher m = p.matcher(dateString); 263 if (!m.matches()) { 264 return false; 265 } 266 267 // 得到年月日 268 String[] array = dateString.split("\\."); 269 int year = Integer.valueOf(array[0]); 270 int month = Integer.valueOf(array[1]); 271 int day = Integer.valueOf(array[2]); 272 273 if (month < 1 || month > 12) { 274 return false; 275 } 276 int[] monthLengths = new int[] { 0, 31, -1, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; 277 if (isLeapYear(year)) { 278 monthLengths[2] = 29; 279 } else { 280 monthLengths[2] = 28; 281 } 282 int monthLength = monthLengths[month]; 283 if (day < 1 || day > monthLength) { 284 return false; 285 } 286 return true; 287 } 288 289 /** 是否是闰年 */ 290 private static boolean isLeapYear(int year) { 291 return ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0); 292 } 293 294 public boolean judge(String input) { 295 296 return false; 297 } 298 299 public void writeUser(ArrayList<User> users, String input) { 300 User usernew = new User(); 301 String[] inputs = input.split(" "); 302 String num = inputs[0].substring(2); 303 for (User i : users) { 304 if (i.getNumber().equals(num)) { 305 return; 306 } 307 } 308 usernew.setNumber(num); 309 int mode = Integer.parseInt(inputs[1]); 310 if (mode == 0) { 311 usernew.setChargeMode(new LandlinePhoneCharging()); 312 } 313 users.add(usernew); 314 } 315 316 public void writeRecord(ArrayList<User> users, String input) { 317 String[] inputs = input.split(" "); 318 inputs[0] = inputs[0].replace("t-", ""); 319 320 User callu = null, answeru = null; 321 CallRecord callrecord = new CallRecord(inputs); 322 323 for (User i : users) { 324 if (i.getNumber().equals(inputs[0])) { 325 callu = i; 326 } 327 if (i.getNumber().equals(inputs[1])) { 328 answeru = i; 329 } 330 if (callu != null && answeru != null) { 331 break; 332 } 333 } 334 335 if (callu != null) { 336 if (callrecord.getCallType() == 1) { 337 callu.getUserRecords().addCallingInCityRecords(callrecord); 338 } else if (callrecord.getCallType() == 2) { 339 callu.getUserRecords().addCallingInProvinceRecords(callrecord); 340 } else { 341 callu.getUserRecords().addCallingInLandRecords(callrecord); 342 } 343 } 344 345 if (answeru != null) { 346 if (callrecord.getCallType() == 1) { 347 answeru.getUserRecords().addAnswerInCityRecords(callrecord); 348 } else if (callrecord.getCallType() == 2) { 349 350 answeru.getUserRecords().aaddAnswerInProvinceRecords(callrecord); 351 } else { 352 answeru.getUserRecords().addAnswerInLandRecords(callrecord); 353 } 354 } 355 356 } 357 358 } 359 360 abstract class CommunicationRecord { 361 protected String callingNumber; 362 protected String answerNumbe; 363 364 public String getCallingNumber() { 365 return callingNumber; 366 } 367 368 public void setCallingNumber(String callingNumber) { 369 this.callingNumber = callingNumber; 370 } 371 372 public String getAnswerNumbe() { 373 return answerNumbe; 374 } 375 376 public void setAnswerNumbe(String answerNumbe) { 377 this.answerNumbe = answerNumbe; 378 } 379 380 } 381 382 abstract class ChargeRule { 383 384 abstract public double calCost(UserRecords userRecords); 385 386 } 387 388 class CallRecord extends CommunicationRecord { 389 private Date startTime; 390 private Date endTime; 391 private String callingAddressAreaCode; 392 private String answerAddressAreaCode; 393 394 public int getCallType() { 395 if (callingAddressAreaCode.equals(answerAddressAreaCode)) { 396 return 1; 397 } 398 if (callingAddressAreaCode.matches("^079[0-9]$") || callingAddressAreaCode.equals("0701")) { 399 if (answerAddressAreaCode.matches("^079[0-9]$") || answerAddressAreaCode.equals("0701")) { 400 return 2; 401 } 402 } 403 return 3; 404 } 405 406 public CallRecord(String[] inputs) { 407 super(); 408 if (inputs[0].length() == 10) { 409 callingAddressAreaCode = inputs[0].substring(0, 3); 410 } else { 411 callingAddressAreaCode = inputs[0].substring(0, 4); 412 } 413 if (inputs[1].length() == 10) { 414 answerAddressAreaCode = inputs[1].substring(0, 3); 415 } else { 416 answerAddressAreaCode = inputs[1].substring(0, 4); 417 } 418 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss", Locale.getDefault()); 419 try { 420 startTime = simpleDateFormat.parse(inputs[2] + " " + inputs[3]); 421 endTime = simpleDateFormat.parse(inputs[4] + " " + inputs[5]); 422 } catch (ParseException e) { 423 } 424 425 } 426 427 public CallRecord(Date startTime, Date endTime, String callingAddressAreaCode, String answerAddressAreaCode) { 428 super(); 429 this.startTime = startTime; 430 this.endTime = endTime; 431 this.callingAddressAreaCode = callingAddressAreaCode; 432 this.answerAddressAreaCode = answerAddressAreaCode; 433 } 434 435 public Date getStartTime() { 436 return startTime; 437 } 438 439 public void setStartTime(Date startTime) { 440 this.startTime = startTime; 441 } 442 443 public Date getEndTime() { 444 return endTime; 445 } 446 447 public void setEndTime(Date endTime) { 448 this.endTime = endTime; 449 } 450 451 public String getCallingAddressAreaCode() { 452 return callingAddressAreaCode; 453 } 454 455 public void setCallingAddressAreaCode(String callingAddressAreaCode) { 456 this.callingAddressAreaCode = callingAddressAreaCode; 457 } 458 459 public String getAnswerAddressAreaCode() { 460 return answerAddressAreaCode; 461 } 462 463 public void setAnswerAddressAreaCode(String answerAddressAreaCode) { 464 this.answerAddressAreaCode = answerAddressAreaCode; 465 } 466 } 467 468 abstract class CallChargeRule extends ChargeRule { 469 470 } 471 472 class LandPhoneInCityRule extends CallChargeRule { 473 474 @Override 475 public double calCost(UserRecords userRecords) { 476 double sumCost = 0; 477 for (CallRecord call : userRecords.getCallingInCityRecords()) { 478 double distanceS = (-call.getStartTime().getTime() + call.getEndTime().getTime()) / 1000; 479 if (distanceS < 0) { 480 continue; 481 } 482 double distanceM = (int) distanceS / 60; 483 if (distanceS % 60 != 0) { 484 distanceM += 1; 485 } 486 sumCost += distanceM * 0.1; 487 } 488 return sumCost; 489 } 490 491 } 492 493 class LandPhoneInlandRule extends CallChargeRule { 494 495 @Override 496 public double calCost(UserRecords userRecords) { 497 double sumCost = 0; 498 for (CallRecord call : userRecords.getCallingInLandRecords()) { 499 double distanceS = (-call.getStartTime().getTime() + call.getEndTime().getTime()) / 1000; 500 if (distanceS < 0) { 501 continue; 502 } 503 double distanceM = (int) distanceS / 60; 504 if (distanceS % 60 != 0) { 505 distanceM += 1; 506 } 507 sumCost += distanceM * 0.6; 508 } 509 return sumCost; 510 } 511 512 } 513 514 class LandPhoneInProvinceRule extends CallChargeRule { 515 516 @Override 517 public double calCost(UserRecords userRecords) { 518 double sumCost = 0; 519 for (CallRecord call : userRecords.getCallingInProvinceRecords()) { 520 double distanceS = (-call.getStartTime().getTime() + call.getEndTime().getTime()) / 1000; 521 if (distanceS < 0) { 522 continue; 523 } 524 double distanceM = (int) distanceS / 60; 525 if (distanceS % 60 != 0) { 526 distanceM += 1; 527 } 528 sumCost += distanceM * 0.3; 529 } 530 return sumCost; 531 } 532 533 } 534 535 class MessageRecord extends CommunicationRecord { 536 537 private String message; 538 539 public String getMessage() { 540 return message; 541 } 542 543 public void setMessage(String message) { 544 this.message = message; 545 } 546 } 547 548 class User { 549 550 private UserRecords userRecords = new UserRecords(); 551 private double balance = 100; 552 private ChargeMode chargeMode; 553 private String number; 554 555 public double calCost() { 556 return chargeMode.calCost(userRecords); 557 } 558 559 public double calBalance() { 560 return balance - chargeMode.getMonthlyRent() - chargeMode.calCost(userRecords); 561 } 562 563 public UserRecords getUserRecords() { 564 return userRecords; 565 } 566 567 public void setUserRecords(UserRecords userRecords) { 568 this.userRecords = userRecords; 569 } 570 571 public ChargeMode getChargeMode() { 572 return chargeMode; 573 } 574 575 public void setChargeMode(ChargeMode chargeMode) { 576 this.chargeMode = chargeMode; 577 } 578 579 public String getNumber() { 580 return number; 581 } 582 583 public void setNumber(String number) { 584 this.number = number; 585 } 586 587 } 588 589 class Outputtool { 590 591 public void output(double out) { 592 // java.text.DecimalFormat df=new java.text.DecimalFormat("#.##"); 593 // String a=df.format(out); 594 // System.out.print(a); 595 BigDecimal numb = new BigDecimal(out); 596 out = numb.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue(); 597 System.out.print(out); 598 } 599 }
题目四:实验五(JavaFx实现农夫过河游戏)
请修改实验四农夫过河游戏的代码,将用户界面改为图形界面,界面效果自行设计。
设计方法:主要改动的一个是Main方法,把按钮的控制等等代码都加到这里面,其次则是class MaterialObject,class Game,主要改动也是因为添加了按钮,剩下的都与实验四的代码差不多,举个例子如下所示:
和
2、 请画出修改后的类图,并说明在界面中使用了哪些技巧以达到较好地界面效果。
1 import javafx.application.Application; 2 3 import javafx.event.ActionEvent; 4 5 import javafx.event.EventHandler; 6 7 import javafx.geometry.Insets; 8 9 import javafx.geometry.Pos; 10 11 import javafx.scene.Scene; 12 13 import javafx.scene.control.Button; 14 15 import javafx.scene.image.Image; 16 17 import javafx.scene.image.ImageView; 18 19 import javafx.scene.layout.GridPane; 20 21 import javafx.stage.Stage; 22 23 24 25 import java.util.ArrayList; 26 27 import java.util.HashSet; 28 29 30 31 public class Main extends Application { 32 33 GridPane pane = new GridPane(); 34 35 Game user = new Game(); 36 37 @Override 38 39 public void start(Stage stage) { 40 41 pane.setAlignment(Pos.CENTER); 42 43 pane.setPadding(new Insets(5,2,2,2)); 44 45 pane.setHgap(3.5); 46 47 pane.setVgap(3.5); 48 49 50 51 //加入图片效果 52 53 Image image = new Image("file:F:\\图片\\河.jpg"); 54 55 ImageView imageView = new ImageView(); 56 57 imageView.setImage(image); 58 59 imageView.setFitHeight(410.5); 60 61 imageView.setFitWidth(110); 62 63 pane.add(imageView,1,0,1,4); 64 65 66 67 //加入按键 68 69 Button bt = new Button("农夫独自过河"); 70 71 bt.setMinSize(110,30); 72 73 ButtonEvents handle = new ButtonEvents(1); 74 75 bt.setOnAction(handle); 76 77 pane.add(bt,0,4); 78 79 bt = new Button("农夫带狼过河"); 80 81 bt.setMinSize(110,30); 82 83 handle = new ButtonEvents(2); 84 85 bt.setOnAction(handle); 86 87 pane.add(bt,1,4); 88 89 bt = new Button("农夫带羊过河"); 90 91 bt.setMinSize(110,30); 92 93 handle = new ButtonEvents(3); 94 95 bt.setOnAction(handle); 96 97 pane.add(bt,0,5); 98 99 bt = new Button("农夫带菜过河"); 100 101 bt.setMinSize(110,30); 102 103 handle = new ButtonEvents(4); 104 105 bt.setOnAction(handle); 106 107 pane.add(bt,1,5); 108 109 /*bt = new Button("退出游戏"); 110 111 bt.setMinSize(110,63.5); 112 113 handle = new ButtonEvents(0); 114 115 bt.setOnAction(handle); 116 117 pane.add(bt,2,4,1,2);*/ 118 119 120 121 pane=user.play(pane,-1); 122 123 124 125 Scene scene = new Scene(pane); 126 127 stage.setTitle("农夫过河"); 128 129 stage.setScene(scene); 130 131 stage.show(); 132 133 } 134 135 136 137 public static void main(String[] args) { 138 139 Application.launch(args); 140 141 } 142 143 class ButtonEvents implements EventHandler<ActionEvent> { 144 145 int flag; 146 147 ButtonEvents(int i) 148 149 { 150 151 this.flag=i; 152 153 } 154 155 @Override 156 157 public void handle(ActionEvent event) { 158 159 if(flag==0) 160 161 System.exit(0); 162 163 else 164 165 pane=user.play(pane,flag); 166 167 } 168 169 } 170 171 } 172 173 174 175 abstract class MaterialObject 176 177 { 178 179 String type; 180 181 String place; 182 183 boolean isExit; 184 185 MaterialObject() 186 187 { 188 189 this.place = "Left"; 190 191 isExit = true; 192 193 } 194 195 void diedOut() 196 197 { 198 199 this.isExit=false; 200 201 } 202 203 boolean isExit() 204 205 { 206 207 return this.isExit; 208 209 } 210 211 void showStatus(GridPane pane) 212 213 { 214 215 Image image; 216 217 ImageView imageView; 218 219 switch(this.type) 220 221 { 222 223 case "Farmer": 224 225 if(this.place.equals("Right")) 226 227 { 228 229 image = new Image("file:F:\\空.jpg"); 230 231 imageView = new ImageView(); 232 233 setSize(imageView,image); 234 235 pane.add(imageView,0,0); 236 237 image = new Image("file:F:\\图片\\农夫.jpg"); 238 239 imageView = new ImageView(); 240 241 setSize(imageView,image); 242 243 pane.add(imageView,2,0); 244 245 } 246 247 else 248 249 { 250 251 image = new Image("file:F:\\空.jpg"); 252 253 imageView = new ImageView(); 254 255 setSize(imageView,image); 256 257 pane.add(imageView,2,0); 258 259 image = new Image("file:F:\\图片\\农夫.jpg"); 260 261 imageView = new ImageView(); 262 263 setSize(imageView,image); 264 265 pane.add(imageView,0,0); 266 267 } 268 269 break; 270 271 case "Wolf": 272 273 if(this.place.equals("Right")) 274 275 { 276 277 image = new Image("file:F:\\空.jpg"); 278 279 imageView = new ImageView(); 280 281 setSize(imageView,image); 282 283 pane.add(imageView,0,2); 284 285 image = new Image("file:F:\\图片\\狼.jpg"); 286 287 imageView = new ImageView(); 288 289 setSize(imageView,image); 290 291 pane.add(imageView,2,2); 292 293 } 294 295 else 296 297 { 298 299 image = new Image("file:F:\\空.jpg"); 300 301 imageView = new ImageView(); 302 303 setSize(imageView,image); 304 305 pane.add(imageView,2,2); 306 307 image = new Image("file:F:\\图片\\狼.jpg"); 308 309 imageView = new ImageView(); 310 311 setSize(imageView,image); 312 313 pane.add(imageView,0,2); 314 315 } 316 317 break; 318 319 case "Sheep": 320 321 if(this.place.equals("Right")) 322 323 { 324 325 image = new Image("file:F:\\空.jpg"); 326 327 imageView = new ImageView(); 328 329 setSize(imageView,image); 330 331 pane.add(imageView,0,1); 332 333 image = new Image("file:F:\\图片\\羊.jpg"); 334 335 imageView = new ImageView(); 336 337 setSize(imageView,image); 338 339 pane.add(imageView,2,1); 340 341 } 342 343 else 344 345 { 346 347 image = new Image("file:F:\\空.jpg"); 348 349 imageView = new ImageView(); 350 351 setSize(imageView,image); 352 353 pane.add(imageView,2,1); 354 355 image = new Image("file:F:\\图片\\羊.jpg"); 356 357 imageView = new ImageView(); 358 359 setSize(imageView,image); 360 361 pane.add(imageView,0,1); 362 363 } 364 365 break; 366 367 case "Cabbage": 368 369 if(this.place.equals("Right")) 370 371 { 372 373 image = new Image("file:F:\\空.jpg"); 374 375 imageView = new ImageView(); 376 377 setSize(imageView,image); 378 379 pane.add(imageView,0,3); 380 381 image = new Image("file:F:\\图片\\白菜.jpg"); 382 383 imageView = new ImageView(); 384 385 setSize(imageView,image); 386 387 pane.add(imageView,2,3); 388 389 } 390 391 else 392 393 { 394 395 image = new Image("file:F:\\空.jpg"); 396 397 imageView = new ImageView(); 398 399 setSize(imageView,image); 400 401 pane.add(imageView,2,3); 402 403 image = new Image("file:F:\\图片\\白菜.jpg"); 404 405 imageView = new ImageView(); 406 407 setSize(imageView,image); 408 409 pane.add(imageView,0,3); 410 411 } 412 413 break; 414 415 } 416 417 } 418 419 void setSize(ImageView imageView,Image image) 420 421 { 422 423 imageView.setImage(image); 424 425 imageView.setFitHeight(100); 426 427 imageView.setFitWidth(110); 428 429 } 430 431 } 432 433 434 435 class Animal extends MaterialObject 436 437 { 438 439 HashSet<String> recipe = new HashSet<String>(); 440 441 boolean eat(MaterialObject object,MaterialObject farmer) 442 443 { 444 445 if(canBeEat(object,farmer)) 446 447 { 448 449 object.diedOut(); 450 451 return true; 452 453 } 454 455 else 456 457 return false; 458 459 } 460 461 void addedToRecipe(MaterialObject object) 462 463 { 464 465 this.recipe.add(object.type); 466 467 } 468 469 boolean isFood(MaterialObject object) 470 471 { 472 473 if(recipe.contains(object.type)) 474 475 return true; 476 477 else 478 479 return false; 480 481 } 482 483 boolean canBeEat(MaterialObject object,MaterialObject farmer) 484 485 { 486 487 if(isFood(object)&&object.place.equals(this.place)&&!farmer.place.equals(this.place)) 488 489 return true; 490 491 else 492 493 return false; 494 495 } 496 497 } 498 499 500 501 class Person extends MaterialObject 502 503 { 504 505 506 507 } 508 509 510 511 class Plant extends MaterialObject 512 513 { 514 515 516 517 } 518 519 520 521 abstract class AbstractTransport 522 523 { 524 525 String place; 526 527 int capacity; 528 529 AbstractTransport() 530 531 { 532 533 this.place="Left"; 534 535 this.capacity=2; 536 537 } 538 539 ArrayList<MaterialObject> goodses = new ArrayList<MaterialObject>(); 540 541 void moveTo() 542 543 { 544 545 if(this.place.equals("Left")) 546 547 { 548 549 this.place=""; 550 551 this.place="Right"; 552 553 } 554 555 else 556 557 { 558 559 this.place=""; 560 561 this.place="Left"; 562 563 } 564 565 } 566 567 } 568 569 570 571 class Boat extends AbstractTransport 572 573 { 574 575 void crossRiver() 576 577 { 578 579 int i; 580 581 for(i=0;i<goodses.size();i++) 582 583 { 584 585 if(goodses.get(i).place.equals("Left")) 586 587 { 588 589 goodses.get(i).place=""; 590 591 goodses.get(i).place="Right"; 592 593 } 594 595 else 596 597 { 598 599 goodses.get(i).place=""; 600 601 goodses.get(i).place="Left"; 602 603 } 604 605 } 606 607 moveTo(); 608 609 } 610 611 void board(MaterialObject object) 612 613 { 614 615 goodses.add(object); 616 617 capacity--; 618 619 } 620 621 void disembark() 622 623 { 624 625 goodses.clear();; 626 627 capacity=2; 628 629 } 630 631 } 632 633 634 635 abstract class AbstractGame 636 637 { 638 639 GameOverRule gameOverRule=new GameOverRule(); 640 641 GameSuccessRule gameSuccessRule=new GameSuccessRule(); 642 643 GameData gameData=new GameData(); 644 645 abstract GridPane play(GridPane pane,int choice); 646 647 } 648 649 650 651 class Game extends AbstractGame 652 653 { 654 655 GameUI UI = new GameUI(); 656 657 CrossRiverRule rule = new CrossRiverRule(); 658 659 int over=0; 660 661 GridPane play(GridPane pane,int choice) 662 663 { 664 665 switch(choice) 666 667 { 668 669 case 1: 670 671 gameData.boat.board(gameData.farmer); 672 673 gameData.boat.crossRiver(); 674 675 gameData.boat.disembark(); 676 677 break; 678 679 case 2: 680 681 if(rule.hasCross(gameData.wolf, gameData.boat)) 682 683 { 684 685 gameData.boat.board(gameData.farmer); 686 687 gameData.boat.board(gameData.wolf); 688 689 gameData.boat.crossRiver(); 690 691 gameData.boat.disembark(); 692 693 } 694 695 break; 696 697 case 3: 698 699 if(rule.hasCross(gameData.sheep, gameData.boat)) 700 701 { 702 703 gameData.boat.board(gameData.farmer); 704 705 gameData.boat.board(gameData.sheep); 706 707 gameData.boat.crossRiver(); 708 709 gameData.boat.disembark(); 710 711 } 712 713 break; 714 715 case 4: 716 717 if(rule.hasCross(gameData.cabbage, gameData.boat)) 718 719 { 720 721 gameData.boat.board(gameData.farmer); 722 723 gameData.boat.board(gameData.cabbage); 724 725 gameData.boat.crossRiver(); 726 727 gameData.boat.disembark(); 728 729 } 730 731 break; 732 733 case 0: 734 735 over=1; 736 737 break; 738 739 } 740 741 pane=UI.showStatus(gameData,pane); 742 743 Image image; 744 745 ImageView imageView; 746 747 if(over==1) 748 749 { 750 751 image = new Image("file:F:\\图片\\输.jpg"); 752 753 imageView = new ImageView(); 754 755 imageView.setImage(image); 756 757 imageView.setFitHeight(410.5); 758 759 imageView.setFitWidth(337); 760 761 pane.add(imageView,0,0,3,4); 762 763 } 764 765 if(gameOverRule.judge(gameData)) 766 767 { 768 769 pane.getChildren().clear(); 770 771 if(gameSuccessRule.judge(gameData)) 772 773 image = new Image("file:F:\\图片\\胜利.jpg"); 774 775 else 776 777 image = new Image("file:F:\\图片\\输.jpg"); 778 779 imageView = new ImageView(); 780 781 imageView.setImage(image); 782 783 imageView.setFitHeight(417.5); 784 785 imageView.setFitWidth(337); 786 787 pane.add(imageView,0,0,3,4); 788 789 790 791 } 792 793 return pane; 794 795 } 796 797 } 798 799 800 801 class GameUI 802 803 { 804 805 GridPane showStatus(GameData gameData,GridPane pane) 806 807 { 808 809 gameData.farmer.showStatus(pane); 810 811 gameData.wolf.showStatus(pane); 812 813 gameData.sheep.showStatus(pane); 814 815 gameData.cabbage.showStatus(pane); 816 817 return pane; 818 819 } 820 821 } 822 823 824 825 class GameData 826 827 { 828 829 MaterialObject farmer = new Person(); 830 831 Animal wolf = new Animal(); 832 833 Animal sheep = new Animal(); 834 835 MaterialObject cabbage = new Plant(); 836 837 Boat boat = new Boat(); 838 839 public GameData() 840 841 { 842 843 farmer.type="Farmer"; 844 845 wolf.type="Wolf"; 846 847 sheep.type="Sheep"; 848 849 cabbage.type="Cabbage"; 850 851 wolf.addedToRecipe(sheep); 852 853 sheep.addedToRecipe(cabbage); 854 855 } 856 857 } 858 859 abstract class AbstractRule 860 861 { 862 863 abstract boolean judge(GameData gameData); 864 865 } 866 867 868 869 class CrossRiverRule extends AbstractRule 870 871 { 872 873 boolean hasCross(MaterialObject object,Boat boat) 874 875 { 876 877 if(!object.place.equals(boat.place)) 878 879 return false; 880 881 else 882 883 return true; 884 885 } 886 887 @Override 888 889 boolean judge(GameData gameData) { 890 891 if(gameData.farmer.place.equals("Right")&&gameData.wolf.place.equals("Right")&&gameData.sheep.place.equals("Right")&&gameData.cabbage.place.equals("Right")) 892 893 return true; 894 895 else 896 897 return false; 898 899 } 900 901 } 902 903 904 905 class ObjectExitRule extends AbstractRule 906 907 { 908 909 910 911 @Override 912 913 boolean judge(GameData gameData) { 914 915 if(gameData.wolf.eat(gameData.sheep, gameData.farmer)||gameData.sheep.eat(gameData.cabbage, gameData.farmer)) 916 917 return false; 918 919 else 920 921 return true; 922 923 } 924 925 926 927 } 928 929 930 931 class GameOverRule extends AbstractRule 932 933 { 934 935 GameSuccessRule win = new GameSuccessRule(); 936 937 ObjectExitRule live = new ObjectExitRule(); 938 939 @Override 940 941 boolean judge(GameData gameData) { 942 943 if(win.judge(gameData)||!live.judge(gameData)) 944 945 return true; 946 947 else 948 949 return false; 950 951 } 952 953 954 955 } 956 957 958 959 class GameSuccessRule extends AbstractRule { 960 961 CrossRiverRule cross = new CrossRiverRule(); 962 963 ObjectExitRule live = new ObjectExitRule(); 964 965 966 967 @Override 968 969 boolean judge(GameData gameData) { 970 971 if (cross.judge(gameData) && live.judge(gameData)) 972 973 return true; 974 975 else 976 977 return false; 978 979 } 980 981 982 983 }
采坑心得
在设计座机拨打电话时未能实现打电话地区和接电话考虑情况分离,即对于拨打电话用户只需要对拨打地区进行分类,无需同时考虑接收电话区域,导致程序功能不完整,代码冗杂。
在短信计费时计算费用时,一开始对计费规则理解错误,后来修改计费计算方法时少打了1个“+”,导致计算结果问题,经过单步调试得以发现。
对象的创建分析
类与对象的关系
类是一种抽象的数据类型,它是对某一事物的整体描述/定义,但是并不能代表一个具体的事物
动物、植物、手机、电脑…
Person类、Pet类、Car类,这些类都是用来描述/定义某一类具体的事物应该具备的特点和行为
对象是抽象概念具体的实例
张三就是一个人的具体实例,张三家里的旺财就是狗的一个具体实例
能够体现出特点,展现出功能的是具体的实例,而不是一个抽象的概念
创建与初始化对象
使用new关键字创建对象
使用new关键字创建的时候,除了分配内存空间之外还会给创建好的对象默认初始化以及对类中构造器的调用
类中的构造器也称为构造方法,是在进行对象创建的时候必须要调用的,并且构造器有以下两个特点:
必须和类的名字相同
必须没有返回类型,也不能使用void
静态代码块、构造代码块、局部代码块
静态代码块
随着类的加载而加载,并且只被加载一次,一般用于项目的初始化
1 static{...}
概述
1、 静态代码块:在类加载时就加载,并且只被加载一次,一般用于项目的初始化
2、 构造代码块:在调用构造方法前会自动调用,每次创建对象都会被调用
3、 局部代码块:方法里的代码块,方法被调用时才会执行
4、 静态代码块:static{ },位置:在类里方法外
5、 TODO创建测试类,类中写好静态代码块,构造代码块,构造方法,普通方法里嵌套局部代码块。测试他们的执行顺序。
6、 静态 - 构造代码块 - 构造方法 - 局部
比较值相等最好用equal别用==
1 public class equalTest { 2 public static void main(String[] args) { 3 Integer a = 12; 4 Integer b = 12; 5 Integer c = 133; 6 Integer d = 133; 7 Integer e = 24; 8 int f = 133; 9 10 System.out.println(a == b); //true 11 System.out.println(c == d); //false 12 System.out.println(f == c); //true 13 System.out.println(a + b == e); //true 14 }
通过这次学会了Javafx的使用,实话说,感觉这里蛮难的,里面好多方法的使用都要去学习,去琢磨,通过查阅资料,浏览他人优质的代码去学习,去改进,整个实验进行的很曲折,功能也有些不完善,但是总体而言还是收获很大的,javafx里面的按钮事件驱动机制、lambda表达式等等,还有很多值得去学习
改进建议
Java编程中因首先针对抽象类及接口对项目搭建宏观整体框架。在通过对父类继承或对接口实现来填充程序内容已完善细节。对于整体框架的建立至关重要,关系到之后对细节实现的难易程度以及整体的可复用性。良好的框架使得代码的可复用性提高,同时对代码增加细节也将方便进行。因此在编写程序之前,因首先分析整体的执行顺序并对其中的复杂动作进行相应化简使得代码更易书写。
总结
在学习Java的过程中我得出这样的结论:
在学习Java的面向对象的编程语言的特性。比如继承,构造器,抽象类,接口,方法的多态,重载,覆盖,Java的异常处理机制。对于一个没有面向对象语言背景的人来说,我觉得这个过程需要花很长很长时间,因为学习Java之前没有C++的经验,只有C语言的经验,花了很长时间,才彻底把这些概念都搞清楚,把书上面的例子反复的揣摩,修改,尝试,把那几章内容反复的看过来,看过去,看了很多遍,才彻底领悟了。学习中,要养成良好的习惯(写括号时要成对,字母大小写要区分,单词拼写要准确)。在学习的过程中,最好不是仅仅停留在java表层,不是抄书上的例子运行出结果就可以。要注意,即便对一个简单的例子也要有耐心去琢磨、调试、改动。在学习的过程中一定要动手做、试着写代码,而不是抱一本书看看就行。很多东西和体会必须自己动手才能真正属于自己。 在 Java 的学习过程中,可能会遇到形形色色的问题不容易解决,应多去专业论坛了解相关的知识,书本上的知识有限。要会从网上搜索有用的信息 加以整理,促进学习的深入和知识水平的提高。
标签:java,String,BLOG3,return,ArrayList,imageView,new,public 来源: https://www.cnblogs.com/fzq2513610927/p/16389631.html