算法描述的是:如果要解决一个问题,所需要执行的动作以及这些动作的执行顺序。算法可以帮助程序员在使用程序设计语言编写程序之前进行规划。算法可以用自然语言或者伪代码(即自然语言和程序设计代码混在一起使用)描述。
- 编写程序涉及如何设计解决问题的策略,以及如何应用编程语言实现这个策略。
- 在编写代码之前,以算法的形式来勾勒你的程序(或者潜在的问题),是一个很好的做法。
- 每个 Java 程序都是一个类的声明开始
- 在程序中需要声明一个称作变量的符号。变量代表存储在计算机内存中的一个值
- 变量名应该尽量选择描述性的名字(descriptive name)
- 为了让编译器知道radius 和area是什么,需要指明它们的数据类型(Data Type),即存储在变量中的数据的类型是整数、实数或者其他。这称之为声明变量
程序清单 2-1 ComputeArea.java
public class ComputeArea {
public static void main(String[] args) {
double radius, area;//Declare variable 声明变量
//Assign a radius 分配一个半径值
radius = 20;
//Compute area 计算面积
area = radius * radius * 3.14159;
//Display results 显示结果
System.out.println("The area for the circle of radius " + radius
+ " is " + area);
}
}
加号(+)的用途:
- 是做加法
- 称为字符串连接符,将两个字符串合并为一个
在源代码中,字符串常量不能跨行
从控制台读取输入
- 使用Scanner类从控制台输入
- Java使用System.out来表示标准输出设备,用System.in来表示标准输入设备
Scanner input = new Scanner(System.in)
语法new Scanner(System.in) 表明创建了一个 Scanner 类型的对象。语法 Scanner input 声明 input 是一个Scanner 类型的变量。整行的 Scanner input = new Scanner(System.in) 表明创建了一个Scanner 对象,并且将它的引用值赋给变量 input 。对象可以调用其方法。调用对象的方法就是让这个对象执行某个任务。可以调用nextDouble()方法来读取一个double值,如下所示:
double radius = input.nextDouble();
程序清单 2-2 ComputeArea.java
import java.util.Scanner;
public class ComputeAreaWithConsoleInput {
public static void main(String[] args) {
//Create a Scanner object 创建Scanner类对象
Scanner input = new Scanner(System.in);
//Prompt the user to enter a radius 提示用户输入
System.out.print("Enter a number for radius: ");
double radius = input.nextDouble();
//Compute area 计算面积
double area = radius * radius * 3.14159;
//Display results 显示结果
System.out.println("The area for the circle of radius "
+ radius + " is " + area);
}
}
Scanner类位于java.util包中,在第1行导入。第6行创建了一个Scanner对象。注意,如果第6行用java.util.Scanner 代替Scanner 的话,import 语句可以省略。
第9行的语句在控制台显示字符串“Enter a number for radius: ”这称之为提示,因为它指导用户键入输入。程序应该在希望得到键盘输入的时候,告诉用户输入什么
println会将光标移到下一行,而print不会将光标移到下一行。
第6行创建了一个Scanner对象。第10行的语句从键盘读入一个输入。
double radius = input.nextDouble( );
在用户键入一个数值然后按回车键
之后,该数值就被读入并赋值给radius。
Scanner类在包 java.util 里。它在第1行被导入。由两种类型的 import 语句:明确导入(specific import )和通配符导入(wildcard import)。明确导入是在import语句中指定单个类。例如,下面的语句就是从包 java.util 中导入Scanner。
通配符导入是指通过使用*
号作为通配符,导入一个包中所有的类。例如,下面的语句导入包java.util 中所有的类。
import java.util.*;
程序清单 2-3 ComputeAverage.java
import java.util.Scanner;
public class ComputeAverage {
public static void main(String[] args) {
//Create a Scanner object 创建Scanner类对象
Scanner input = new Scanner(System.in);
//Prompt the user to enter three numbers 提示用户输入三个数
System.out.print("Enter three numbers: ");
double number1 = input.nextDouble();
double number2 = input.nextDouble();
double number3 = input.nextDouble();
//Compute average 计算平均值
double average = (number1 + number2 + number3);
//Display results 显示结果
System.out.println("The average of " + number1 + " " + number2
+ " " + number3 + " is " + average);
}
}
- 本书前面章节中的大多数程序分三个步骤执行,即输入、处理和输出,这被称为IPO。输入是从用户获得输入,处理是使用输入产生结果,而输出是显示结果。
- 如果使用诸如Eclipse 或者NetBean之类的 IDE ,会提示你关闭输入以防止可能的资源泄漏。现在先忽略警告,因为程序结束时输入将自动关闭。这种情况下不会有资源泄漏
标识符(Identifier)的命名规则
- 标识符由数字、字母、下划线和美元符号$构成的字符序列
- 标识符必须以字母、下划线或美元符号$开头,不能以数字开头
- 标识符不能是保留字
- 标识符不能是true、false或null
- 标识符可以为任意长度
注意:由于 Java 是区分大小写的,所以area、Area、AREA是不同的标识符
- 提示:标识符用于命名程序中的变量、方法、类和其他项。具有描述性的标识符可提高程序的可读性。避免采用缩写作为标识符,使用完整的词汇会更具有描述性。比如,numberOfStudents比numStuds、numOfStuds或者numOfStudents要好。本教材中我们在完整的程序采用描述性的命名。然而,为了简明起见,我们也会偶尔在一些代码片段中采用诸如 i,j,k,x和y之类的变量名。这样的命名在代码片段中也是具有一定普遍性的做法。
- 不要用字符 $ 命名标识符。习惯上,字符 $ 只用在机器自动产生的源代码中。(内部类)
变量
- 变量用于表示在程序中可能被改变的值
- 变量声明通知编译器根据数据类型为变量分配合适的内存空间
- 在赋值给变量之前,必须声明变量。方法中声明的变量在使用之前必须被赋值。尽量一步完成变量的声明和赋初值。这会使得程序易读,同时避免程序设计错误
- 一个变量在可以使用前,必须被声明和初始化
赋值语句和赋值表达式
- 变量在声明之后,可以使用赋值语句(assignment statement)给它赋一个值。在 Java 中,将等号(=)作为赋值操作符(assignment operator )
variable = expression; (变量 = 表达式;)
表达式(expression)表示包含值、变量和操作符的一次计算,它们组合在一起得出一个新值。
变量名必须在赋值操作符的左边。
- 在数学运算中,x = 2 _ x + 1表示一个等式。但是,在 Java 中, x = 2 _x +1 是一个赋值语句,它计算表达式 2 *x +1的值,并且将结果赋给 x。
在 Java 中,赋值语句本质上就是一个表达式,该表达式的值是赋给赋值操作符左边变量的值。由于这个原因,赋值语句也称为赋值表达式(assignment expression )
- 在赋值语句中,左边变量的数据类型必须与右边值的数据类型兼容。例如,int x = 1.0 是非法的,因为x 的数据类型是整型int。在不使用类型转换的情况下,是不能把double 值(1.0)赋给int 变量的。
命名常量
- 命名常量(named constant)是一个代表不变值的标识符
final datatype CONSTANTNAME = value;
程序清单 2-4 ComputeAreaWithCOnstant.java
import java.util.Scanner;
public class ComputeAreaWithConstant {
public static void main(String[] args) {
final double PI = 3.14159;//Declare a constant 声明常量
//Create a Scanner object 创建Scanner类对象
Scanner input = new Scanner(System.in);
//Prompt the user to enter a radius 提示用户键入半径值
System.out.print("Enter a number for radius: ");
double radius = input.nextDouble();
//Compute area 计算面积
double area = radius * radius * PI;
//Display result 显示结果
System.out.println("The area for the circle of radius "
+ radius + " is " + area);
}
}
命名习惯
- 严格遵循 Java 的命名习惯可以让你的程序易于理解,并且能避免错误
- 使用小写字母命名变量和方法,例如,变量radius和area以及方法print。如果一个命名包含多个单词,就将它们连在一起,第一个单词的字母小写,而后面每个单词的首字母大写。例如,变量numberOfStudents。这种命名风格称为驼峰命名法,因为名字中的大写字符类似于骆驼的驼峰
- 类名中的每个单词的首字母大写,例如,类名ComputeArea和System
- 常量中的所有字母大写,两个单词间用下划线连接,例如,常量PI和常量MAX_VALUE
- 命名类时不要选择Java库中已经使用的名称。例如,因为Java已定义了System类,就不要用System来命名自己的类
数值数据类型和操作
- Java针对整数和浮点数由六种数值类型,以及+、-、*、/和%等操作符 | 类型名 | 范围 | 存储空间 | | —- | —- | —- | | byte | -27 (-128) ~ 27-1 (127) | 8位带符号数 | | short | -215 (-32768) ~ 215-1 (32767) | 16位带符号数 | | int | -231 (-2 147 483 648) ~ 231-1 (2 147 483 647) | 32位带符号数 | | long | -263 (-9 223 372 036 854 775 808) ~ 263-1 (9 223 372 036 854 775 807) | 64位带符号数 | | float | 负数范围:-3.4028235E+38 ~ -1.4E-45 正数范围:1.4E-45 ~ 3.4028235E+38 | 32位,标准IEEE 754 | | double | 负数范围:-1.7976931348623157E+308 ~ -4.9E-324 正数范围:4.9E-324 ~ 1.7976931348623157E+308 | 64位,标准IEEE 754 |
- IEEE 754 是美国电气电子工程师协会通过的标准,用于在计算机上表示浮点数。该标准已被广泛采用。Java采用32位IEEE 754 表示float型。64位IEEE 754 表示double型。IEEE 754 标准还定义了一些特殊浮点值。
- double又称为双精度(double precision),而float称为单精度(single precision) | 方法 | 描述 | 方法 | 描述 | | —- | —- | —- | —- | | nextByte() | 读取一个byte类型的整数 | nextLong() | 读取一个long型的整数 | | nextShort() | 读取一个short类型的整数 | nextFloat() | 读取一个float类型的数 | | nextInt() | 读取一个int类型的整数 | nextDouble() | 读取一个double类型的数 |
运算符 | 名字 | 示例 | 运算结果 | 运算符 | 名字 | 示例 | 运算结果 |
---|---|---|---|---|---|---|---|
+ | 加 | 34 + 1 | 35 | / | 除 | 1.0 / 2.0 | 0.5 |
- | 减 | 34.0 -0.1 | 33.9 | % | 求余 | 20 % 3 | 2 |
* | 乘 | 300 * 30 | 9000 |
- 操作符%被称为求余或者取模操作符,可以求得除法的余数
- 操作符%通常用在正整数上,实际上,它也可用于负整数和浮点值。只有当被除数是负数时,负数才是负的
程序清单 2-5 DisplayTime.java
import java.util.Scanner;
public class DisplayTime {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
//Prompt the user for input 提示用户输入
System.out.print("Enter an integer for seconds: ");
int seconds = input.nextInt();
int minutes = seconds / 60;//Find minutes in seconds 以秒计算分
int remainingSeconds = seconds % 60;//Seconds remaining 剩余的秒数
System.out.println(seconds + " seconds is " + minutes +
" minutes and " + remainingSeconds + " seconds");
}
}
数值型字面值
- 字面值(literal)是程序中直接出现的常量值
整型字面值
- 默认情况下,整形字面值是一个十进制整数。要表示一个二进制整数字面值,在数字前使用0b 或者 0B(零B);要表示一个八进制整数字面值,在数字前使用0(零);而要表示一个十六进制整数字面值,在数字前使用0x或0X(零x).
- 为了提高可读性,Java允许在一个数值型字面值的两个数字间使用下划线。
科学计数法
- float型和double型都是用来表示带有小数点的数。为什么把它们称为浮点数呢?因为这些数在计算机内部都是以科学计数法的形式进行存储的。当一个像50.534的数被转换成科学记数法的形式时,它就是5.0534E+1,它的小数点移到(即浮动到)一个新的位置。
表达式求值以及操作符优先级
- Java表达式的求值和数学表达式求值是一样的
- 圆括号可以嵌套,嵌套时先计算内层括号
- 操作符的优先级规则
- 首先计算乘法、除法和求余运算。如果表达式中包含若干个乘法、除法和求余操作符,可按照从左到右的顺序执行
- 最后执行加法和减法运算。如果表达式中包含若干个加法和减法操作符,则按照从左到右的顺序执行
程序清单 2-6 FahrenheitToCelsius.java
import java.util.Scanner;
public class FahrenheitToCelsius {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.print("Enter a degree in Fahrenheit: ");
double fahrenheit = input.nextDouble();
//Convert Fahrenheit to Celsius
double celsius = (5.0 / 9) * (fahrenheit - 32);
System.out.println("Fahrenheit " + fahrenheit + " is " +
celsius + " in Celsius");
}
}
显示当前时间
- 可以通过调用System.currentTimeMillis()返回当前时间
- GMT(格林尼治标准时间)1970年1月1日00:00:00开始到当前时刻的毫秒数
程序清单 2-7 ShowCurrentTime.java
public class ShowCurrentTime {
public static void main(String[] args) {
//Obtain the total milliseconds since midnight, Jan 1, 1970
//获取自1970年1月1日午夜以来的总毫秒数(格林尼治时间)
long totalMillliseconds = System.currentTimeMillis();
//Obtain the total seconds since midnight, Jan 1, 1970
//获取自1970年1月1日午夜以来的总秒数
long totalSeconds = totalMillliseconds / 1000;
//Compute the current second in the minute in the hour
//计算-小时中-分钟中-的当前秒数
long currentSecond = totalSeconds % 60;
//Obtain the total minutes 获取总分钟数
long totalMinutes = totalSeconds / 60;
//Compute the current minute in the hour
//计算小时中的当前分钟
long currentMinute = totalMinutes % 60;
//Obtain the total hours 获取总小时数
long totalHours = totalMinutes / 60;
//Compute the current hour 计算当前小时
long currentHour = (totalHours % 24) + 8;
//中国是东八区,加8表示中国当地时间,% 做取整处理。这里不显示具体的年月日
//Display results 显示结果
System.out.println("Current time is " + currentHour + ":"
+ currentMinute + ":" + currentSecond + " GMT");
}
}
增强赋值操作符
- 操作符+、-、、/、%可以结合赋值操作符形成增强操作符 | 操作符 | 名称 | 示例 | 等价于 | | —- | —- | —- | —- | | += | 加法赋值操作符 | i+=8 | i = i + 8 | | -= | 减法赋值操作符 | i-=8 | i = i - 8 | | = | 乘法赋值操作符 | i=8 | i = i 8 | | /= | 除法赋值操作符 | i/=8 | i = i / 8 | | %= | 求余赋值操作符 | i%=8 | i = i % 8 |
- 在增强操作符中是没有空格的
- 就像赋值操作符(=)一样,操作符(+=、-=、*=、/=、%=)既可以构成赋值语句也可以构成赋值表达式。例如,在下面的代码中,第1行的x += 2是一条语句,而在第2行中它就是一个表达式
自增和自减操作符
- 自增操作符(++)和自减操作符(—)对变量进行加1和减1的操作 | 操作符 | 名称 | 描述 | 示例(假设i=1) | | —- | —- | —- | —- | | ++var | 前置自增 | 将var加1,在语句中使用新的var值 | int j = ++i;//j is 2,i is 2 | | var++ | 后置自增 | 将var加1,但是在语句中使用原来的var值 | int j = i++;//j is 1,i is 2 | | —var | 前置自减 | 将var减1,在语句中使用新的var值 | int j = —i;//j is 0,i is 0 | | var— | 后置自减 | 将var减1,但是在语句中使用原来的var值 | int j = i—;//j is 1,i is 0 |
- 使用自增操作符和自减操作符可以使表达式更加简短,但也会使它们比较复杂且难以读懂。应该避免在同一个表达式中使用这些操作符修改多个变量或多次修改同一个变量,如int k = ++i + i*3。
数值类型转换
- 通过显示转换,浮点数可以被转换为整数
- 总是可以将一个数值赋给支持更大数值范围的类型的变量,例如,可以将long类型的值赋给float型变量。但是,如果不进行类型转换,就不能将一个值赋给范围较小的类型的变量。类型转换是将一种数据类型的值转换成另一种数据类型的值的操作。将范围较小的类型转换为范围较大的类型称为扩展类型(widening a type)。Java将自动扩展一个类型,但是缩小类型必须显示完成
- 如果要将一个值赋给一个范围较小的类型的变量,例如将double型的值赋给int型变量,就必须进行类型转换。如果在这种情况下没有使用类型转换,就会出现编译错误。使用类型转换时必须小心,丢失的信息也许会导致不精确的结果
- 类型转换不改变被转换的变量。例如,下面代码中的d在类型转换之后值不变:
double d = 4.5
int i = (int)d
- Java中,x1 op = x2 形式的增强赋值表达式实现为x1 = (T)(x1 op x2),这里T是x1的类型。因此,下面的代码是正确的。
- 将一个int型变量赋值给short型或byte型变量,必须显式地使用类型转换。例如,下述语句就会产生编译错误:
int i= 1;
byte b = i;//Error because explicit casting is required
- 然而,只要整型字面值是在目标变量允许的范围内,那么将整型字面值赋给short型或byte型变量时,就不需要显式的类型转换
程序清单 2-8 SalesTax.java
import java.util.Scanner;
public class SalesTax {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.print("Enter purchase amount: ");
double purchaseAmount = input.nextDouble();
double tax = purchaseAmount * 0.06;
System.out.println("Sales tax is $"+(int)(tax * 100) / 100.0);
//这里的int强制转换的作用是向下取整,并保留小数点后两位。
//向上取整用(int)(x * 100+0.5),通过乘以10的N次方并除以10的N次方来保留N位小数
//当purchase为10的N次方时,仅保留一位
/**
* 实例:Enter purchase amount: 10000000
* Sales tax is $600000.0
*/
}
}
软件开发过程
- 软件开发生命周期是一个多阶段的过程,包括需求规范、分析、设计、实现、测试、部署和维护
- 需求规范
- 需求规范是一个规范化的过程,旨在理解软件要处理的问题,以及将软件系统的功能详细记录到文档中
- 系统分析
- 系统分析旨在分析数据流,并且确定系统的输入和输出。当进行分析的时候,首先确定输出,然后弄清楚需要什么样的输入来产生结果
- 系统设计
- 系统设计是设计一个从输入获得输出的过程。这个阶段涉及使用多层的抽象,将问题分解为可管理的组成部分,并且设计实现每个组成部分的策略。可以将每个组成部分看作一个执行系统特定功能的子系统。系统分析和设计的本质是输入、处理和输出(IPO
- 实现
- 实现是将系统设计翻译成程序。为每个组成部分编写独立的程序,然后集成在一起工作。这个过程需要使用一门编程语言,比如Java,实现包括编码、自我测试以及调试(在代码中查找错误称为调试)
- 测试
- 测试确保代码符合需求规范,并且排除错误。通常由一个没有参与产品设计和实现的独立软件工程团队完成这样的测试
- 部署
- 部署使得软件可以被使用。按照类型的不同,软件可能被安装到每个用户的机器上,或者安装在一个Internet 可访问的服务器上
- 维护
- 维护是对软件产品进行更新和改进。软件产品必须在一直演化的环境中连续运行和改进。这需要进行产品的周期性改进,以修正新发现的错误,并且整合改进部分
程序清单 2-9 ComputeLoan.java
import java.util.Scanner;
public class ComputeLoan {
public static void main(String[] args) {
//Create a Scanner 创建Scanner类
Scanner input = new Scanner(System.in);
//eEnter annual interest rate in percentage,.g.,7.25 键入年利率
System.out.print("Enter annual interest rate,e.g.,7.25: ");
double annualInterestRate = input.nextDouble();
//Obtain monthly interest rate 获取月利率
double monthlyInterestRate = annualInterestRate / 1200;
//Enter number of years 键入贷款年数
System.out.print("Enter number of years as an interger,e.g.,5: ");
int numberOfYears = input.nextInt();
//Enter loan amount 键入贷款金额
System.out.print("Enter loan amount,e.g.,120000.95: ");
double loanAmount = input.nextDouble();
//Calculate payment 计算支付金额
double monthlyPayment = loanAmount * monthlyInterestRate /
(1 - 1 / Math.pow(1 + monthlyInterestRate, numberOfYears * 12));
double totalPayment = monthlyPayment * numberOfYears * 12;
//Display results 显示结果
System.out.println("The monthly payment is $" +
(int) (monthlyPayment * 100) / 100.0);
System.out.println("The total payment is $" +
(int) (totalPayment * 100) / 100.0);
}
}
程序清单 2-10 ComputeChange.java
import java.util.Scanner;
public class ComputeChange {
public static void main(String[] args) {
//Create a Scanner
Scanner input = new Scanner(System.in);
//Receive the amount
System.out.print("Enter an amount in double, for example 11.56: ");
double amount = input.nextDouble();
int remainingAmount = (int) (amount * 100);
//Find the number of one dollars
int numberOfOneDollars = remainingAmount / 100;
remainingAmount = remainingAmount % 100;
//Find the number of quarters in the remaining amount
int numberOfQuarters = remainingAmount / 25;
remainingAmount = remainingAmount % 25;
//Find the number of dimes in the remaining amount
int numberOfDimes = remainingAmount / 10;
remainingAmount = remainingAmount % 10;
//Find the number pf nickels in the remaining amount
int numberOfNickels = remainingAmount / 5;
remainingAmount = remainingAmount % 5;
//Find the number of pennies in the remaining amount
int numberOfPennies = remainingAmount;
//Display results
System.out.println("Your amount " + amount + " consists of+" +
" " + numberOfOneDollars + " dollars" + " " + numberOfQuarters
+ " quarters " + " " + numberOfDimes + " dimes" + " "
+ numberOfNickels + " nickels" + " " + numberOfPennies + " pennies");
}
}
常见错误和陷阱
- 常见的基础编程错误经常设计未声明变量、未初始化变量、整数溢出、非预期的整数除法,以及数值取整错误