第3章 结构化程序设计———设计 图书ISBN 校验器 3.1 图书ISBN 校验器 国际标准书号(InternationalStandardBookNumber,ISBN)是专门为识别图书等文 献而设 计 的 国 际 编 号。存 在 两 种 形 式 的 ISBN 编 码:10位 ISBN 编 码 与 13位 ISBN 编码。 10位ISBN 编码的 组 成 方 式:组 号-出 版 者 号-书 序 号-校 验 码,例 如 7-302-08599-4。 组号是国家、地区、语言的代号;出版者号是出版社代号,由其隶属的国家或地区的ISBN 中心分配;书序号由出版社定义其发行图书的编号;最末位的校验码能够校验出ISBN 号 是否正确。10位ISBN 编码的校验码满足如下公式: C10 =11-MOD( Σ9 i=1 (11-i)×Ci,11) C1~C10分别表示1位ISBN 编码,其中,C1~C9 取值为0~9的数字,C10可以取值字母 X 及数字0~9,如果校验码 C1=10,则用 X表示。 13位ISBN 编码的组成方式:前缀码-组号-出版者号-书序号-校验码,例如978-7302- 08599-7。在10位编码的基础上增加了前缀码:978或979。校验码满足如下公式: C13 =10-MOD . è ... Σi≤12 且i为 奇 数 Ci+3× Σi≤12 且i为 偶 数 Ci,10 . . ÷÷÷ 如果计算得到 C13=10,则令 C13=0,以保证最末位的校验码取值为0~9的数字。 3.2 程序设计思路 依然采用IPO 程序设计模式。 (1)程序的输入(I):以字符串的形式输入待检测的ISBN 编码。 (2)程序处理过程(P):这部分包括程序的核心算法,需要依次完成以下操作: ① 需要获取输入的ISBN 编码的长度,进而根据长度选择不同的公式计算校验码; ② 获取输入编码的第i位数字,即 Ci; ③ 根据公式计算累加和及余数。 (3)程序的输出(O):给出判断结果,即是否为合法的ISBN 校验码。 24 3.3 关键技术 为了使程序具有良好的结构,使程序易于设计、易于理解,通常高级语言都会提供 3 种基本的流程控制结构:顺序结构、分支结构和循环结构。顺序结构按照语句的书写顺 序执行程序;分支结构根据条件的取值选择性地执行代码分支;循环结构根据循环条件重 复执行代码段。 3.3.1 顺序结构 顺序结构指计算机按照语句的编写次序执行,没有分支和跳转语句。常见的顺序结 构包括:表达式语句、复合语句及空语句。 1.表达式语句 在表达式的末尾加上分号即可构成表达式语句,例如:count=1。 2.复合语句 使用大括号把若干语句和声明组合到一起即可构成复合语句。 3.空语句 仅由分号构成,不执行任何操作。 3.3.2 分支结构 Java语言中常用的分支结构包括if语句及switch语句。 1.if语句 if语句根据逻辑表达式的取值决定程序的执行分支。if语句有如下3种格式。 格式1:单分支语句。 if(逻辑表达式){ 语句序列; } 流程图如图3-1所示。 格式2:双分支语句。 if(逻辑表达式){ 语句序列 1; } else { 25 语句序列 2; } 流程图如图3-2所示。 图3-1 if语句单分支流程图 图3-2 if语句双分支流程图 格式3:多分支语句。 if (逻辑表达式 1){ 语句序列 1; } else if(逻辑表达式 2){ 语句序列 2; } … else { 语句序列 n+1 } 流程图如图3-3所示。 3种格式是相通的,如果格式2省略else子句,它就变成了格式1;同理,格式3中省 略elseif子句就变会成格式2。一般而言,如果语句序列中仅包含一行语句,则可以省略 花括号;但是为了增强程序的可读性,通常不建议省略花括号。 分析下面的例3-1,根据输入的成绩判断所属的成绩等级。 例3-1 import java.util.Scanner; public class ScoreGroup { public static void main(String[]args) { Scanner sc = new Scanner(System.in); System.out.println("请输入成绩:"); intscore = sc.nextInt(); if(score>=0 &&score<=59) { 26 System.out.println("不及格"); }else if(score<=69) { ① System.out.println("及格"); }else if(score<=89) { System.out.println("良"); }else if(score<=100) { System.out.println("优秀"); }else { System.out.println("输入信息有误"); } sc.close(); } } 图3-3 if语句多分支流程图 表面上看起来,上面的程序没有问题,分别输入成绩50、65、79、94、120时,可以得到 预期的输出结果。但是当输入成绩为-4时,运行上面的程序,会得到及格的成绩等级, 显然程序出现了问 题。经 过 分 析 发 现,主 要 原 因 在 于 else子 句 虽 然 后 面 仅 包 含 一 个 条 件,但是实际上else子句还隐含了一个条件:对前面条件取反。如例3-1的代码行①,条 件是score<=69,实际上这里隐含的条件是:!(score>=0&&score<=59)&&score 27 <=69,等价于score<0||(score>59&&score<=69)。显然,当输入成绩为-4时, 程序出现了问题。 为了得到正确的程序逻辑,把例3-1修改为如下形式。 import java.util.Scanner; public class ScoreGroup { public static void main(String[]args) { Scanner sc = new Scanner(System.in); System.out.println("请输入成绩:"); int score = sc.nextInt(); if(score<0 || score>100) { System.out.println("输入信息有误"); }else if(score>=90) { System.out.println("优秀"); }else if(score>=70) { System.out.println("良"); }else if(score>=60) { System.out.println("及格"); }else { System.out.println("不及格"); } sc.close(); } } 2.switch语句 语法格式如下: switch (表达式){ case 值 1:语句序列 1;break; case 值 2:语句序列 2;break; … case 值 n:语句序列 n;break; default: 语句序列 n+1; } 流程表示如图3-4所示。 switch中的表达 式 只 能 是 byte、short、char、int及 枚 举 类 型。Java7之 后,增 加 了 String类型的表达式。 switch语句的执行过程:计算表达式将表达式的值按从上至下的顺序依次与case子 句的常量进行比较。当表达式的值与case子句的常量相等时,执行其后的语句序列,直 到遇到 break或者switch语句执行结束。若没有与表达式的值相同的case常量,则执行 default子句对应的语句序列,若程序省略default子句,则结束switch语句。 28 图3-4 switch语句多分支流程图 3.3.3 循环结构 Java语言常用的3种循环结构为 while循环、do-while循环及for循环。 1.while循环 语法格式如下: 初始化; while(逻辑表达式){ 语句序列; } 流程表示如图3-5所示。 while循环的执行过程如下: ① 执行初始化操作; ② 计算逻辑表达式的值,若值为true,则转到③,否则转到④; ③ 执行语句序列,转到②; ④ 循环出口,while语句结束。 使用 while循环时,一定要保证作为循环条件的逻辑表达式可以变成false,否则这个 循环就会成为死循环,程序无法结束。 2.do-while循环 语法格式如下: 初始化; do { 29 语句序列; } while(逻辑表达式); 流程表示如图3-6所示。 图3-5 while语句流程图 图3-6 do-while语句流程图 do-while循环与 while循环相比主要的区别在于:while循环是先判断逻辑表达式后 再执行循环语句序列;而do-while循环是先执行循环语句序列,然后判断逻辑表达式,如 果逻辑表达式取值为true,则继续执行语句序列,否则中止循环。 初学者使用do-while循环时容易出现的错误如下: ① 忽略 while(逻辑表达式)后的分号,造成编译错误; ② 在循环内部,声明循环变量,例如如下代码段。 do { System.out.println("请输入一个 1~10 之间的整数"); int guess = sc.nextInt(); if(guesstruth) System.out.println("太大了"); }while(guess!=truth); 循环内部声明的 局 部 变 量 guess的 作 用 域 仅 限 于 大 括 号 定 界 的 语 句 序 列,不 包 括 while子句 后 面 的 逻 辑 表 达 式,所 以 上 述 程 序 会 提 示 “cannotberesolvedtoavariable” 错误。 30 图3-7 for语句流程图 3.for循环 语法格式如下: for(初始化;逻辑表达式;迭代表达式){ 语句序列; } 流程表示如图3-7所示。 for语句的执行过程如下: ① 执行初始化操作; ② 计算并判断 逻 辑 表 达 式,若 取 值 为true,则 转 到 ③, 否则转到⑤; ③ 执行语句序列; ④ 计算迭代表达式; ⑤ 结束for语句。 注意:在for循环结构中,初始化部分定义的局部变量 的作用范围仅局限于for语句,即在for语句体之外,该局部 变量将不可以使用。可以通过把初始化部分的代码放置在 for语句之前扩大局部变量的作用范围。 3.3.4 循环控制结构 常用的循环控制结构包括 break语句、continue语句及return语句。 1.break语句 break语句用于结束循环。以for循环结构为例,语法定义如下: for(初始化;逻辑表达式 1;迭代表达式){ 语句序列 1; if(逻辑表达式 2)break; 语句序列 2; } 流程控制如图3-8所示。 注意:对于循环嵌套结构,break仅结束其所在的循环结构。 2.continue语句 continue语句的作用是忽略本次循环剩余的语句,继续 执 行 下 一 次 循 环。以for循 环结构为例,语法定义如下: for(初始化;逻辑表达式 1;迭代表达式){ 31 语句序列 1; if(逻辑表达式 2)continue; 语句序列 2; } 流程控制如图3-9所示。 图3-8 break语句流程图 图3-9 continue语句流程图 注意:当逻辑表达式2取值为true时,流程控制将短路语句序列2,会执行迭代表达 式,继续下一次循环。 3.return语句 结束当前方法的调用,返回主调方法。 3.4 图书ISBN 校验器设计步骤 1.ISBN 编码的输入 利用 Scanner类提供的 nextLine()方法可以实现字符串的输入。 3.4 32 2.digitAt()方法 digitAt()方法负责解析ISBN 编码的每一位字符,并将其转换为对应的数字。首先 利用字符串 String类提供的charcharAt(intindex)方法得到字符串的第index个字符, 然后将其转换为数字。 public static int digitAt(String isbn, int index) { char digit = isbn.charAt(index); if(digit == 'X') return 10; else return digit - '0'; } 3.isISBN()方法 isISBN()方法判断读入的编码是否是合法的ISBN 编码。 public static boolean isISBN(String isbn) { boolean result = false; int checkSum = 0; if (isbn.length() == 10) { for (int i= 1; i< 10; i++ ) checkSum + = (11 - i) * digitAt(isbn, i - 1); checkSum = 11 - checkSum % 11; if (checkSum == digitAt(isbn, 9)) result = true; } else if (isbn.length() == 13) { for (int i = 1; i < 13; i++ ) { if (i % 2 == 0) checkSum += 3 * digitAt(isbn, i - 1); else checkSum += digitAt(isbn, i - 1); } int remainder = checkSum % 10; if(remainder == 0) checkSum = 0; else checkSum = 10 - remainder; if (checkSum == digitAt(isbn, 12)) result = true; } return result; } 33 4.定义主方法 定义主方法,调用上述方法,构建图书ISBN 校验器。 public static void main(String[]args) { System.out.print("请输入图书的 ISBN 编码"); Scanner sc = new Scanner(System.in); String isbn = sc.nextLine(); if (isISBN(isbn)) System.out.println("ISBN 校验码正确"); else System.out.println("ISBN 校验码不正确"); sc.close(); } 3.5 练一练 1.按照如下个人所得税税率表及公式编写个人所得税计算器。 全月应纳税所得额=扣除四金之后的应发工资-5000 提示:个人所得税的计算可以采用以下两种方法。 (1)超额累积税率计算法如表3-1所示。 表3-1 超额累积税率计算法 级数 全月应纳税所得额 税率/% 1 不超过3000元 3 2 超过3000元至12000元的部分 10 3 超过12000元至25000元的部分 20 4 超过25000元至35000元的部分 25 5 超过35000元至55000元的部分 30 6 超过55000元至80000元的部分 35 7 超过80000元的部分 45 (2)速算扣除数法计算法如表3-2所示。 工资个税的计算公式为 应纳税额=全月应纳税所得额×适用税率-速算扣除数 表3-2 速算扣除数法计算法 级数 全月应纳税所得额 税率/% 速算扣除数 1 不超过3000元 3 0 2 3000元至12000元 10 2520 34 续表 级数 全月应纳税所得额 税率/% 速算扣除数 3 12000元至25000元 20 16920 4 25000元至35000元 25 31920 5 35000元至55000元 30 52920 6 55000元至80000元 35 85920 7 超过80000元 45 181920 2.通过键盘输入任意多个数字并计算其平均值。 提示:文本扫描器类 Scanner的 hasNextDouble()可以判断是否存在下一个 double 类型的输入数据。 3.用Π 4≈1-13 +15 -17 +…求 Π 的 近 似 值,直 到 发 现 某 一 项 的 绝 对 值 小 于 10-6 为止。提 示:对于“/”运算符,当两个操作数都为int、long、short时,计算结果只保留整数部 分,舍弃小数部分。