第三章 程序设计基础
3.1 流程图
3.1.1 什么是流程图:
流程图是通过箭头(流程线)相互连接的几何图形来表达程序运行的方法。
流程图是算法的一种图形化描述,比较直观的表达了一件事务的处理过程。相对于代码来讲容易理解。
3.1.2 流程图的组成:
画流程图的方法是:从左到右、从上到下。根据问题的需要,确定解决问题的先后处理顺序以及前提条件,将流程线将各图形符号连接起来,直观的表达自己的逻辑思想或问题的处理方法,最终达到预期的结果。
注意事项:
一个流程图只能有一个开始框,开始框作为处理问题的开始,根据解决问题的需要,其他描述框可以有多个。对于判断框,必须提供一个进口流程线和两个出口流程线。出口线必须在流程线上标明判断结果为真或假(YES或NO);如果只标明一个条件,则另一条流程线则是相反条件。
3.1.3程序控制的三种结构
从程序执行流程可分成下列三种典型结构:
顺序结构:程序是按程序语句或模块在执行流中的先后顺序逐个执行。
选择结构:也叫分支结构,程序是按设定的条件实现程序执行流的多路分支。
循环结构:程序是按给定的条件重复地执行指定的程序段或模块。
结论:理论上已经证明,用三种基本程序结构可以实现任何复杂的算法。
3.1.4 基本流程图的表达示例
3.1.5 例题
3.2 程序注释
3.2.1 程序注释
程序注释主要是为了程序的易读性。对一个程序语句,一个程序段,一个程序它的作用是什么,必要时都应该用注释简要说明。
程序中的注释不是程序的语句部分,它可以放在程序的任何地方,系统在编译时忽略它们。
以“//”开始后跟注释文字。这种注释方式可单独占一行,也可放在程序语句的后边。
例如,在下边的程序片段中使用注释:
单行注释:(//后的一行均为注释)
//下面定义程序中所使用的量
int id ; //定义一整型变量id ,表示识别号码。
String name; //定义一字符串变量name,表示名字。
多行注释:(以“/﹡”开始,以“﹡/”结束)
当需要多行注释时,一般使用“/﹡……﹡/”格式作注释,中间为注释内容。
/﹡
* 本程序是一个示例程序,在程序中定义了如下两个方法:
* setName(String ) ---设置名字方法
* getName () --- 获取名字方法。
﹡/
public void setName(String name)
{
……….
}
public String getName()
{
return name;
}
………
3.2.2 文档注释
程序文档注释是Java特有的注释方式,它规定了一些专门的标记,其目的是用于自动生成独立的程序文档。
程序文档注释通常用于注释类、接口、变量和方法。下面看一个注释类的例子:
/**
* 该类包含了一些操作数据库常用的基本方法,诸如:在库中建立新的数据表、
* 在数据表中插入新记录、删除无用的记录、修改已存在的记录中的数据、查询
* 相关的数据信息等功能。
* @author unascribed
* @version 1.50, 02/02/06
* @since JDK2.0
*/
在上边的程序文档注释中,除了说明文字之外,还有一些@字符开始的专门的标记,说明如下:
@author 用于说明本程序代码的作者;
@version 用于说明程序代码的版本及推出时间;
@since 用于说明开发程序代码的软件环境。
3.3 Java命令提示符下的输入输出
3.3.1 输入
字符界面下的输入输出是由Java的基类System提供的,在前边的示例中,我们已经使用了System.out.println()方法在屏幕上输出信息。下边看一下输入输出方法的一般格式。
1. 输入方法
Scanner是SDK1.5新增的一个类,可是使用该类创建一个对象。
Scanner reader = new Scanner(System.in);
然后reader对象调用下列方法(函数),读取用户在命令行输入的各种数据类型: next.Byte(),nextShort(),nextInt(),nextLong(),nextFloat,nextDouble(),next(),nextLine()。
nextByte()得到输入的字节;
nextShort()得到输入的短整型
nextInt() 得到输入的整型。
nextLong() 得到输入的长整型
nextFloat() 得到输入的单精度浮点
nextDouble() 得到输入的双精度浮点数
next() 得到输入的字符串,必须有字符
nextLine() 得到输入的字符串,包括
上述方法执行时都会造成堵塞,等待用户在命令行输入数据,回车确认。
例如,用户在键盘输入12.34,hasNextFloat()的值是true,而hasNextInt()的值是false。
nextLine()等待用户输入一个文本行并且回车。该方法得到一个String类型的数据。
其它输入方法:
格式: System.in.read();
功能:该方法的功能是从键盘上接受一个字符,按照byte类型的数据处理。若将它转换为字符型,它就是字符本身;若转换为整型,它是扩展字符的ASCII码值(0~255)。
格式: System.in.read(byte []a);
功能:该方法返回一组字节数据,并保存在字节数据a中。
- 输出方法
格式1: System.out.print(表达式);
格式2: System.out.println(表达式);
格式3: System.out.printf(表达式);
功能:在屏幕上输出表达式的值。
Scanner 的使用
/**
* 本程序利用Scanner 让用户输入一些数字,然后计算用户输入数据的和与平均值。
* 源程序名ExampleScanner.java
*/
import java.util.*;
public class ExampleScanner{
public static void main(String args[]){
System.out.println("请输入若干个数,每输入一个数用回车确认");
System.out.println("最后输入一个非数字结束输入操作");
Scanner reader=new Scanner(System.in);
double sum=0;
int m=0;
while(reader.hasNextDouble()){
double x=reader.nextDouble();
m=m+1;
sum=sum+x;
}
System.out.printf("%d个数的和为%f\n",m,sum);
System.out.printf("%d个数的平均值是%f\n",m,sum/m);
}
}
System.in.read
/* 示例3.1 程序名:IoExam3_1.java
* 这是一个字符界面输入输出的简单示例。
* 它主要演示从键盘上输入一个字符,然
* 后以字节方式、字符方式在屏幕上输出。
*/
class IoExam3_1
{
public static void main(String [] args)
{
int num1=0;
try
{
System.out.print("请输入一个字符:");
num1=System.in.read(); //从键盘上输入一个字符并把它赋给num1
}
catch(Exception e1) {
}
System.out.println("以数值方式显示,是输入字符的ASCII值= "+num1);
System.out.println("以字符方式显示,显示的是字符本身 = "+(char)num1);
}
}
3.3.2 输出
/**
* 测试System.out.print&println
*/
import java.util.*;
public class Print {
public static void main(String[] args) {
System.out.println("打印一行,以下只打印一个空行");
System.out.println();
System.out.print("只打印不换行,");
System.out.println("呵呵");
System.out.print("用转义字符换行\n");
}
}
本程序输出
打印一行,以下只打印一个空行
只打印不换行,呵呵
用转义字符换行
如果输出的字符串中使用一些比较特殊的字符,比如换行(Enter)、退格(Backspace)、制表(Tab),可以通过在字符串序列中加入转义字符来完成这类功能。
Java语言支持一些特殊的转义字符序列。
符号 字符含义
\n 换行 (0x0a)
\r 回车 (0x0d)
\f 换页符(0x0c)
\b 退格 (0x08)
\s 空格 (0x20)
\t 制表符
\” 双引号
\’ 单引号
\ 反斜杠
\ddd 八进制字符 (ddd)
\uxxxx 16进制Unicode字符 (xxxx)
对于不确定宽度的输出,Java可以使用格式化字符(占位符)来完成,每个转换符对应一个要输出的变量,结果是输出变量的值。
转换符 说明 示例
%s 字符串类型 “Microsoft”
%c 字符类型 ‘m’
%b 布尔类型 True
%d 整数类型(十进制) 99
%x 整数类型(十六进制) FF
%o 整数类型(八进制) 77
%f 浮点类型 1.23
%a 浮点类型(十六进制) ff.35ae
%e 浮点类型(指数,科学计数法) 1.2e-5
%g 通用浮点(f和e中较短的)
%h 散列码
%% 百分比符号 %
%n 换行
%tx 日期与时间类型(x代表不同的日期与时间转换符)
示例:
public class Print {
public static void main(String[] args) {
int i=1,j=10;
double x=1.134567890,y=3.145;
System.out.printf("格式化输出字符串:\t%d 0x%08X\n",i,j);
System.out.printf("浮点格式化:\t\t%f %.4g %4.2f",x,x,y);// 4.2中的4代表数位宽度占用4个字符,小数点后占2位,其它的类推,无小数点不要使用。
}
}
本程序输出:
格式化输出字符串: 1 0x0000000A
浮点格式化: 1.134568 1.135 3.15
3.3 分支控制语句
Java程序通过一些控制结构的语句来执行程序流,完成一定的任务。程序流是由若干个语句组成的,语句可以是单一的一条语句,如c = a + b; 也可以是用大括号“{}”括起来的一个复合语句即语句块。
Java语句包含一系列的流程控制语句,这些控制语句表达了一定的逻辑关系,所以可选择性的或者是可重复性的执行某些代码行,这些语句与其他编程语言中使用的流程控制语句大体相近,Java的流程控制语句基本上是仿照C/C++中的语句。每一个流程控制语句实际上是个代码块,块的开始和结束都是用大括号来进行表示的,其中“{”表示开始,“}”表示结束。在本节先介绍分支控制语句。
3.3.1 if条件分支语句
若布尔表达式(关系表达式或逻辑表达式)产生true (真)值,则执行该语句,否则跳过或执行下面的语句。
格式1 if
if (布尔表达式) 语句;
示例
import java.util.*;
public class If {
public static void main(String[] args) {
float x = 45.2145f;
// 一条语句也要进行块分割
/*
if(x<0) {
x = -x;
}
*/
if(x<0);// 这种错误真的不要犯呀!!!!!
x = -x;
System.out.println("x=" + x); }
}
格式2 if … else …
if (布尔表达式) 语句1;
else 语句2;
语句3
示例:
import java.util.*;
public class IfElse {
public static void main(String[] args) {
int score = 40;
boolean b = score >= 60;// 布尔型变量b是false
if (b)
System.out.println("你通过了测试");
else
System.out.println("你没有通过测试");
}
}
格式3:if… else if … else…
if (布尔表达式1) 语句1;
else if (布尔表达式2) 语句2;
……
else if (布尔表达式n-1) 语句n-1;
else 语句n;
语句
示例:
/**
* 为考试成绩划定五个级别,当成绩大于或等于90分时,划定为优;
* 当成绩大于或等于80且小于90时,划定为良;
* 当成绩大于或等于70且小于80时,划定为中;
* 当成绩大于或等于60且小于70时,划定为及格;
* 当成绩小于60时,划定为差。
*/
/* 这是一个划定成绩级别的简单程序
* 它主要演示多者择一分支语句的应用。
*/
public class IfElseIf {
public static void main(String[] args) {
int score = 79;// 成绩
if (score >= 90) {
System.out.println("成绩为优=" + score);
}
else if (score >= 80){
System.out.println("成绩为良=" + score);
}
else if (score >= 70){
System.out.println("成绩为中=" + score);
}
else if (score >= 60){
System.out.println("成绩为及格=" + score);
}
else{
System.out.println("成绩为差=" + score);
}
}
}
3.3.2 switch条件语句
格式:
switch(表达式) {
case 常值1: 语句组1; [break;]
case 常值2: 语句组2;[break;]
……………………………
case 常值n-1: 语句组n-1;[break;]
case 常值n: 语句组n;[break;]
default: 语句组n+1; }
其中:
1)表达式是可以生成整数或字符串值的整型(int)表达式或字符型表达式或枚举类型。
2)常量i (i=1~n)是对应于表达式类型的常量值。各常量值必须是唯一的。
3)语句组i (i=1~n+1) 可以是空语句,也可是一个或多个语句——语句块(0,n)
4)break关键字的作用是结束本switch结构语句的执行,跳到该结构外的下一个语句执行,如果没有break,程序就顺序执行。
示例
/**
* 这是一个划定成绩级别的简单程序
* 程序的名字是Switch.java
* 它主要演示多者择一分支语句的应用。
*/
public class Switch{
public static void main(String[] args) {
int score = 75;
int n = score / 10;
switch (n) {
case 10:
case 9:
System.out.println("成绩为优=" + score);
break;
case 8:
System.out.println("成绩为良=" + score);
break;
case 7:
System.out.println("成绩为中=" + score);
break;
case 6:
System.out.println("成绩为及格=" + score);
break;
default:
System.out.println("成绩为差=" + score);
}
}
}
示例
/* 这是一个计算某年某月天数的程序
* 程序的名字是:DayofMonthExam3_5.java
* 程序的目的是演示Switch结构的应用。
*/
public class DayofMonthExam3_5
{
public static void main(String [] args)
{
int year = 1980;
int month = 2;
int day=0;
switch(month)
{
case 2: day=28; //非闰年28天,下边判断是否闰年,闰年29天
if((year % 400==0) || ((year % 4 == 0) && (year%100 != 0))) {
// 能被400整除或者 可以被4整除但不能被100整除
day++;
}
break;
case 4:
case 6:
case 9:
case 11: day=30;
break;
default: day=31;
}
System.out.println(year+"年"+month+"月有"+day+"天");
}
}
switch 的整数模式
int month = 5;
int day = 0;
switch(month) {
default:
day=38;
case 2:
day=28;
break;
case 4:
case 6:
case 9:
case 11:
day=30;
break;
}
System.out.println(day);
// 输出 28
// switch的字符串值模式
public class Input {
public static void main(String[] args) {
String str="*";
switch(str){
case "*":{
break;
}
case "+":{
}
break;
default:
}
}
}
3.4 循环控制语句
在程序中,重复地执行某段程序代码是最常见的,Java也和其他的程序设计语言一样,提供了循环执行代码语句的功能。
3.4.1 for循环语句
for循环语句是最常见的循环语句之一。for循环语句的一般格式如下:
for ( 表达式1; 表达式2; 表达式3){
[语句组1;] //循环体
[break;]终止循环
[continue;]—>到表达式3
[语句组2;] //循环体
}
1)表达式1一般用于设置循环控制变量的初始值,例如:int i=1;
2)表达式2一般是关系表达式或逻辑表达式,用于确定是否继续进行循环体语句的执行。例如:i<100;
3)表达式3一般用于循环控制变量的增减值操作。例如:i++; 或i–;
4)语句组是要被重复执行的语句称之为循环体。语句组可以是空语句(什么也不做)、单个语句或多个语句。
示例
/* 这是一个求和的程序
* 计算sum=1+2+3+4+5+…+100的值。
* 主要是演示for 循环结构的应用。
*/
public class For{
public static void main(String [] args)
{
/*
for ( 1;2;4){
3;
}
*/
int sum = 0;
for(int i=1; i<=100; i++){
sum += i;// sum = sum + i ;
}
System.out.println("sum="+sum) ; }
}
示例
/**
* 例3.7
* 这是一个古典数学问题:
* 一对兔子从它出生后第3个月起,每个月都生一对小兔子,
* 小兔子3个月后又生一对小兔子,假设兔子都不死,求每个月的兔子对数。
* 该数列为:1 1 2 3 5 8 13 21… 即从第3项开始,其该项是前两项之和。
* 求100以内的波那契数列。程序参考代码如下:
* 功能概述: 生成100以内的斐波那契数列 Fibonacci.java文件
*/
public class FibonacciFor {
public static void main(String args[]) {
System.out.println("斐波那契数列:");
/**
* 采用for循环,声明3个变量:
* i--- 当月的兔子数(输出);
* j--- 上月的兔子数;
* m--- 中间变量,用来记录本月的兔子数
*/
for (int i = 1, j = 0, m = 0; i < 100;) {
m = i; // 记录本月的兔子数
System.out.print(" " + i); // 输出本月的兔子数
i = i + j; // 计算下月的兔子数
j = m; // 记录本月的兔子数
}
System.out.println("");
}
}
3.4.2 while和do-while循环语句
while循环
while(布尔表达式)
{
语句组1; //循环体
[break;]// 终止循环
[continue;]到语句组1
语句组2; //循环体
}
其中:
1) 布尔表达式可以是关系表达式或逻辑表达式,它产生一个布尔值。
2) 语句组是循环体,要重复执行的语句序列。
示例
/**
* 目的主要是演示While语句的应用
*/
public class While {
/**
* 通过while循环来解决你女朋友要你对她说一百遍”我喜欢你“
*/
public static void main(String[] args) {
// 你的女朋友要你站在一个地方说十遍"我喜欢你!!!"
int i = 20;// 1.初
while (i <= 10) {// 2.判
System.out.println("我喜欢你");
System.out.println("我已经说了" + i + "遍");
i++;// 3变
}
}
}
示例
/**
* 功能概述:使用while 循环计算100以内的斐波那契数列
* 1 1 2 3 5 8 13 21…...
*/
public class FibonacciForWhile {
public static void main(String args[]) {
int i = 1;
int j = 0;
int m = 0;
System.out.println("斐波那契数列:");
while (i < 100) {
m = i;
System.out.print(" " + i);
i = i + j;
j = m;
}
System.out.println("");
}
}
do-While循环
do {
语句组1; //循环体
[break;]
[continue;]// 到语句组1
}
while(布尔表达式);
示例
/**
* 目的主要是演示?DoWhile语句的应用
*/
public class DoWhile {
/**
* 通过while循环来解决你女朋友要你对她说一百遍”我喜欢你“
*/
public static void main(String[] args) {
// 你的女朋友要你站在一个地方说十遍"我喜欢你!!!"
int i = 10;// 1.初
do {
System.out.println("我喜欢你");
System.out.println("我已经说了" + i + "遍");
i++;// 2变
} while (i <= 20);// 3.判
System.out.println("不吱声就算了");
}
}
从两种循环的格式和处理流程我们可以看出它们之间的差别在于:while循环先判断布尔表达式的值,如果表达式的值为true则执行循环体,否则跳过循环体的执行。因此如果一开始布尔表达式的值就为false,那么循环体一次也不被执行。do…while循环是先执行一遍循环体,然后再判断布尔表达式的值,若为true则再次执行循环体,否则执行后边的程序语句。无论布尔表达式的值如何,do…while循环都至少会执行一遍循环体语句。下边我们看一个测试的例子。
/**
* 功能概述:进行while和do-while循环的测试
*/
public class WhilePKDoWhile {
public static void main(String args[]) {
int i = 0; // 声明一个变量
System.out.println("准备进行while操作");
while (i < 0) {
i++;
System.out.println("进行第" + i + "次while循环操作");
}
System.out.println("准备进行do-while循环");
i = 0;
do {
i++;
System.out.println("进行第" + i + "次do-while循环操作");
} while (i < 0);
}
}
3.5 其他控制语句
3.5.1 break语句
在前边介绍的switch语句结构中,我们已经使用过break语句,它用来结束switch语句的执行。使程序跳到switch语句结构后的第一个语句去执行。
break语句也可用于循环语句的结构中。同样它也用来结束循环,使程序跳到循环结构后边的语句去执行。
如果没有break语句,程序就顺序执行。
break语句有如下两种格式:
1) break;
2) break 标号;
/**
* 这是一个求50~100之间所有素数的程序,
* 目的是演示一下break语句的使用。
*/
class Break {
public static void main(String[] args) {
int n, m, i;
for (n = 50; n < 100; n++) {
for (i = 2; i <= n / 2; i++) {
if (n % i == 0)
break; // 被i除尽,不是素数,跳出本循环
}
if (i > n / 2) // 若i>n/2,说明在上边的循环中没有遇到被除尽的数
{
System.out.print(n + " "); // 输出素数
}
}
}
}
3.5.2 continue语句
也有两种格式:
1) continue;
2) continue 标号;
第一种格式比较常见,它用来结束本轮次循环(即跳过循环体中下面尚未执行的语句),直接进入下一轮次的循环。for循环里直接跳到语法中的语句3处
/**
* 本程序计算10~1000之间既能被3整除也能被7整除的数
* 目的是演示continue语句的用法。
*/
public class Continue {
public static void main(String args[]) {
int k = 1;
System.out.println("在10~1000可被3与7整除的为");
for (int n = 10; n <= 1000; n++) {
if (n % 3 != 0 || n % 7 != 0)
continue;
System.out.print(n + " ");
if (k++ % 10 == 0)
System.out.println("");// k用来控制1行打印10个
}
System.out.println(" ");
}
}
3.5.3 return语句
return语句用于方法中,该语句的功能是结束该方法的执行,返回到该方法的调用者或将方法中的计算值返回给方法的调用者,reutrn 可以使用在任何需要的地方。
return语句有以下两种格式:
1) return;
2) return 表达式;
第一种格式用于无返回值的方法;第二种格式用于需要返回值的方法。
/**
* 判断一个正整数是否是素数,若是计算其阶乘。
* 判断素数和阶乘作为方法定义,并在主方法中调用它们。
* 该程序包含以下两个方法:
* prime()方法判断一个整数是否为素数
* factorial()方法用于求一个整数的阶乘
* 目的主要是演示return语句的应用
*/
public class Return {
/**
* 判断n是否素数方法
* @param n 待检测的整数
* @return 如果是素数返回true 否则返回 false
*/
public static boolean prime(int n) {
for (int i = 2; i < n / 2; i++) {
if (n % i == 0)
return false; // n不是素数
}
return true; // n是素数
} // prime()方法结束
/**
* @param n 要计算的整数
* @return n!
*/
public static int factorial(int n) // 求阶乘方法
{
if (n <= 1)
return 1;
int m = 1;
for (int i = 1; i <= n; i++)
m *= i;
return m;
}// factorial 方法结束
public static void main(String args[]) // 主方法
{
int n = 13;
if(n==13){
System.out.println(n);
return;
}
System.out.println(n + "是素数吗?" + prime(n));
if (prime(n)) {
System.out.println(n + "!=" + factorial(n));
}
/*//
boolean fact=prime(n);
System.out.println(n + "是素数吗?" + fact);
if(fact){
System.out.println(n + "!=" + factorial(n));
}
//*/
} // main()方法结束
}
3.6 数组
数组的引入:一个养鸡场有6只鸡,它们分别的体重是不3 、5、1、3.4、2、50 公斤,请问这6只鸡的总体重是多少?平均体重是多少?怎么办,如果有100只鸡、1000只….
数组(Array)是Java 语言中内置的一种基本数据存储结构,通俗的理解,就是一组数的集合,目的是用来一次存储多个数据。数组是程序中实现很多算法的基础,可以在一定程度上简化代码的书写。
备注:
1. 数组的好处:数组里的每个元素都有编号,编号从0开始,并且依次递增,而且访问时不可超出定义的上限,否则会产生越界错误;
2. 使用Java数组:必须先声明数组,再给该数组分配内存;
3. 数组对应在内存中一段连续空间,数组的长度是固定的。
4. 数组元素必须是相同数据类型,也可以是引用数据类型,但是同一个数组中的元素必须是同一类数据类型。
5. 简单的数据类型数组可以直接赋值。
6. 对象数组在定义后,赋值时需要再次为每个对象分配空间(即:new 类型名)
7. 数组名可以理解为执行数组首地址的引用。
3.6.1 一维数组
一维数组:可以理解为一列多行、类型相同的数据,其中每个数据被称为数组元素;
数据1 0
数据2 1
数据3 2
… …
数据n-1 n-2
数据n n-1
一维数组的声明方式:
数据类型 变量名[]; 或 数据类型[] 变量名;(推荐),这里变量名就是数组的名称。
例如:int age[]; int []age;
java语言声明数组的时候不能指定其长度(元素的个数)如:int a[5]; //非法操作
初始化:
Java中的数组必先初始化才可以使用,所谓初始化就是为数组的数组元素分配内存,并为每个数组元素赋值;
数组初始化的两种方式:
1. 静态初始化:
初始化时由我们自己指定每个数组元素的初始值,由系统决定需要的数组长度;
格式:数组名 = new 数组类型[]{元素1,元素2,元素3…元素n};
简化语法:数组名 = {元素1,元素2,元素3…元素n};
int[] a = { 1, 2, 3 };
a = new int[] { 1, 2, 3, 4, 0 };
- 动态初始化:
初始化时由我们指定数组的长度,由系统为数组元素分配初始值;
格式:数组名 = new 数组类型[数组长度];
int a ;
a= new int[5];
int [] b = new int[6];
注意:不能静态和动态初始化同时使用,也就是说不能同时指定数组的长度和元素;
a = new int[5] {0, 1, 2, 3, 4 };//这是个美丽的错误
示例
/**
* 目的主要是演示数组的定义及初始化
*/
public class Array {
public static void main(String[] args) {
int[] age = new int[10];
// 动态初始化
for (int i = 0; i < 10; i++) {
//age[i] = i;
System.out.print(age[i] + "\t");
}
}
}
输出:0 0 0 0 0 0 0 0 0 0
一维数组的使用:
Java语言的数组索引是从0开始的,也就是说数组里的第一个元素的索引是0,第二个元素的索引是1,依次可以类推。
常见操作:
给数组元素赋值,格式:数组名[索引] = 数组类型的值 ;
访问数组元素,格式:数组类型 变量 = 数组名[索引];或 变量 =数组名[索引];
得到数组的长度,格式:int len = 数组名.length; //length是数组的属性
/**
* 目的主要是演示数组的使用
* 定义一个数组并初始化,通过程序把数组中的最大值找出来
*/
public class Array {
public static void main(String[] args) {
int num[] = new int[] { 12, 26, 3, 60, 55, 6, 48, 4, 98 };
int max = num[0];
for (int i = 0; i < num.length; i++) {
if (max < num[i]) {
max = num[i];
}
}
System.out.println(max);
}
}
数组的内存分配,栈内存和堆内存
如定义一个数组 int[]scores将在栈内存中为scores 分配内存空间,其值是一个不确定的值。
当执行语句scores=new int[5]时,将在堆内存分配连续5个空间,每个空间4个字节,用于存放整型数据,其初始值为0,然后将该段空间首地址,也就是第一个元素的地址,比如0*3000,赋给scores变量。该地址相当于一个指针,指向堆内存中分配的空间。此时堆内存中分配的5个空间可以分别使用scores[0],一直到scores[4]来表示。当执行四个赋值语句时,分别用指定值填充到对应元素位置。如果此时将null值赋给scores时,scores变量将不再指向任何位置,此时堆内存中分配的空间就变成了垃圾,由垃圾回收器在某一时间进行回收。
在方法中定义的变量,包括基本数据类型变量和引用数据类型变量,都将在栈内存中分配空间,当超过变量作用范围后,自动回收。
3.6.2 二维数组
二维数组:(其实是一个一维数组,它的每一个元素又是一个一维数组),
00 01 02 03 04
10 11 12 13 14
20 21 22 23 24
30 31 32 33 34
40 41 42 43 44
可以看做是一张表格。
声明方式:
数据类型[][] 变量名; 或者 数据类型 变量名[][];
int[][] a;
int b[][];
初始化:
1. 动态初始化
int[ ][ ] arr = new int[3][2];// 3行2列
说明:定义了一个二维数组,其中有3个一维数组,每一个一维数组中有2个元素。每个元素的值是0。
int[][] arr = new int[3][];// 3行,列数不定
说明:这种情况发生在二维数组第一行的一维数组不固定时使用。比较难于理解
2. 静态初始化
int[ ][ ] arr = new int[][]{{1,2},{3,4},{5,6}};
int[ ][ ] arr = {{1,2},{3,4},{5,6}};
/**
* 目的主要是演示数组的使用
* 定义一个二维数组并初始化,然后输出其中的数值
*/
public class Array2 {
public static void main(String[] args) {
int age[][] = new int[][] { { 1, 2 }, { 3, 4 }, { 5, 6, 7 } };//二维数组并不是完全意义上的表格
System.out.println(age[0].length);// 2
System.out.println(age[2].length);// 3
for(int i=0;i<age.length;i++){
for(int j=0;j<age[i].length;j++){
System.out.print(age[i][j]+" ");
}
System.out.println();
}
}
}
示例2
/**
* 二维数组也可以分解为两个一维数组的形式然后再分别输出数据,
*/
public class TwoDimensionArray {
public static void main(String[] args) {
int[][] arr0 = new int[2][3];// 动态初始化,2行3列
int[][] arr2 = new int[][] { { 1, 2, 3, 4 }, { 1, 2, 3, 4 } };// 静态初始化
int[][] arr = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8 } };// 静态初始化
int[] foo = arr[0]; // 将arr[0]对象引用赋值给foo
for (int i = 0; i < foo.length; i++) {
System.out.print(foo[i] + " ");// 循环输出
}
System.out.println();
foo = arr[1]; // 将arr[1]对象引用赋值给foo
for (int i = 0; i < foo.length; i++) {
System.out.print(foo[i] + " ");// 循环输出
}
System.out.println();
foo = arr[2]; // 将arr[1]对象引用赋值给foo
for (int i = 0; i < foo.length; i++) {
System.out.print(foo[i] + " ");// 循环输出
}
System.out.println();
System.out.println("arr2.length = " + arr2.length);
int[][] arr1 = new int[3][];// 动态初始化
arr1[0] = arr[0];
arr1[1] = new int[]{3,4,5};// 对二维的某一行,再次定义一个新的一维数组
arr1[2] = arr[2];
// 输出 arr1
for (int i = 0; i < arr1.length; i++) {
for (int j = 0; j < arr1[i].length; j++) {
System.out.print(arr1[i][j] + " ");
}
System.out.println();
}
}
}
3.7 方法
3.7.1 什么是方法
方法是一组为了实现特定功能的代码块的集合,有的书中也叫函数(C语言)(method or function)。
方法在语法上的功能主要有以下两个:
①:结构化代码
将代码按照功能进行组织,使代码的结构比较清晰,容易阅读和修改,也就是程序的可维护性强。
②:减少代码重复
一个固定的功能,可能会在程序中多次使用,在使用时只需要调用写好的方法,而不用重复书写对应的功能代码。
方法在书写时需要注意以下两点:
①:逻辑严谨
方法实现的一个完整的功能,所以在书写时要考虑到各种可能的情况,并对每种情况做出恰当的处理。
②:通用性强(可重复利用)
方法实现的是一种功能,在实际实现时,可以根据需要,编写具备一定的通用性的方法,除非必要,否则不要写专用的方法。
在Java 语言中,恰当的使用方法,将使程序更加优雅,便于阅读和使用。
3.7.2 方法的声明及定义
[修饰符] 返回值类型 方法名( [参数类型 形式参数1,参数类型 形式参数2,……] ){
执行语句;
[return 返回值;]//返回值的类型与声明的返回值类型一致,void类型的方法无返回值。
}
方法的特征值:参数列表(包括参数的类型 ,参数的个数,参数的顺序)
只要上述有一个不一样,那么这个参数列表(方法的特征值)就不一样!对于方法而言,即使同名也不是同一个方法,也就是下面讲的方法签名。
每个工程项目,都有且只有一个入口方法main()。
public static void main(String [] args){
}
3.7.3 方法的属性
访问控制符:访问控制符限定方法的可见范围,或者说是方法被调用的范围。
方法的访问控制符有四种,按可见范围从大到小依次是:public、protected,无访问控制符,private。其中无访问控制符不书写关键字即可。具体的范围在后续有详细介绍。
形式参数:在方法被调用时用于接收外界输入的数据。
实参:调用方法时实际传给方法的数据。
返回值:方法在执行完毕后返还给调用它的环境的数据。
返回值类型:事先约定的返回值的数据类型,如无返回值,必须给出返回类型 void。
方法签名:方法名和方法的参数列表(能区别方法);//最关键的
java语言中调用方法:[对象名.]方法名(实参列表)。
实参的数目、数据类型和次序必须和所调用方法声明的形参列表匹配。
Java中使用return 语句终止方法的运行并指定要返回的数据,返回值定义为 void 类型方法,无返回值。只用return;就可以了。
3.7.4 方法特点
1. 它可以实现独立的功能;
2. 必须定义在类里面;
3. 它只有被调用才会执行;
4. 它可以被重复使用;
5. 方法结束后方法里的对象失去引用;
public class Method {
/**
* player 的值分别是1,2,3或者其他值时,
* 该方法返回“大明”,“二明”,“小明”,“我没有这个孩子”。
*/
public static void main(String[] args) {
int player = 5;
System.out.println("返回的结果是:" + show(player));
}
public static String show(int p) {
switch (p) {
case 1:
return "大明";
case 2:
return "二明";
case 3:
return "小明";
default:
return "我没有这个孩子!";
}
}
}
3.7.5 什么是方法的重载(Overload)
概念:在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型或参数顺序不同即可。
存在的原因:屏蔽了一个对象的同一类方法由于参数不同所造成的差异。
特点:与返回值类型无关,只看参数列表。
举例:
public void a(int a){}
public int a(){}
public void a(int a,String s){}
完整示例:
/**
* 本程序定义了两个同名称的方法,功能是分别找了两个给定数字的最大数
* 目的:学习方法的重载(Overload)
*/
public class OverloadMethod {
public static void main(String[] args) {
// TODO Auto-generated method stub
int a = 0,b=1;
System.out.println(a + "与"+b+"中的最大值是"+max(a,b));
double c=3.0,d=5.0;
System.out.println(a + "与"+b+"中的最大值是"+max(c,d));
}
/**
*
* @param a
* @param b
* @return a,b中的最大值
*/
public static int max(int a, int b) {
return a>b?a:b;
}
/**
*
* @param a
* @param b
* @return a,b中的最大值
*/
public static double max(double a, double b) {
return a>b?a:b;
}
}
3.7.6 方法的递归
递归:简而言之,就是在某些情况下,方法以不同的参数调用自己的逻辑的实现。比如我们编写一个查找目录下文件的方法,如果程序找到了一个文件夹,那么如果要继续查找该文件下有多少个文件(该文件夹里还有文件夹…….),怎么办?本方法就是查找文件夹下有多少个文件,这种情况用循环这种流程控制已经不方便了,还不如自己调用自己。这就是递归的基本使用场景。
递归算法:
是一种直接或者间接地调用自身的算法,不论是直接还是间接,其算法的流程走向必须形成封闭(即本身属于广义上的循环过程),否则递归将不能形成。在计算机编写程序中,递归算法对解决很多类问题是十分有效,它使算法的描述简洁而且易于理解。
递归算法的特点:
递归过程一般通过函数或子过程来实现。
递归算法的实质:
是把问题转化为规模缩小了的同类问题的子问题。然后递归调用函数(或过程)来表示问题的解。
递归算法解决问题的特点:
(1)递归就是在过程或函数里调用自身。
(2)在使用递增归策略时,必须有一个明确的递归结束条件,称为递归出口。
(3)递归算法解题通常显得很简洁,但递归算法解题的运行效率较低,即占用内存很大,有时这种情况用栈来代替解决。所以一般不提倡用递归算法设计程序。
(4)在递归调用的过程当中系统为每一层的返回点、局部量等开辟了栈来存储。递归次数过多容易造成栈溢出等。所以一般不提倡用递归算法设计程序。
递归算法所体现的“重复”一般有三个要求:
一是每次调用在规模上都有所缩小(通常是减半);
二是相邻两次重复之间有紧密的联系,前一次要为后一次做准备(通常前一次的输出就作为后一次的输入);
三是在问题的规模极小时必须用直接给出解答而不再进行递归调用,因而每次递归调用都是有条件的(以规模未达到直接解答的大小为条件),无条件递归调用将会成为死循环而不能正常结束。
递归的缺点:
其余的复杂循环(甚至嵌套)情况建议不用递归,因为递归过程中所用到的变量都在递归结束前的过程中一直占用着内存,如果循环过程又趋于复杂则内存耗用量将显著提升,运行速度也将下降。(通常用循环解决的,就不用递归)
递归方法的语法格式:
权限修饰符 函数返回类型 函数名(参数列表){
if(递归结束条件){
递归结束终值赋值语句;
return 返回值;
} else {累计计算+循环调用函数语句;
return 返回值;
}
}
两个示例程序代码:求n!
package cn.huasheng.example;
public class JieCheng {
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println(factorial2(5));
}
/**
* 使用循环
* @param n 要计算的整数
* @return n!
*/
public static int factorial(int n) // 求阶乘方法
{
if (n <= 1)
return 1;
int m = 1;
for (int i = 1; i <= n; i++)
m *= i;
return m;
}// factorial 方法结束
/**
* 使用递归思想
* @param n 要计算的整数
* @return n!
*/
public static int factorial2(int n) // 求阶乘方法
{
if (n <= 1)
return 1;
else
return n*factorial2(n-1);
}// factorial 方法结束
}
示例:
斐波那契数数列:1 1 2 3 5 8 13 21 34……
此数列的前两项为1 第三项是第一项与第二项的和,依此类推。
输出此数列前20项的示例代码,分别用循环和递归实现
public class Fibonacci{
public static void main(String[] args) {
for (int i = 0; i < 50; i++)
System.out.print(fibonacciXunHuan(i) + " ");
System.out.println("OK");
}
/**
* 使用递归思想实现
* @param n 第几项
* @return
*/
public static int fibonacciDiGui(int n) {
if (n < 2) {
return 1;
} else {
return fibonacciDiGui(n - 1) + fibonacciDiGui(n - 2);
}
}
/**
* 使用循环思想实现
* @param n 第几项
* @return
*/
public static int fibonacciXunHuan(int n) {
int a0 = 1, a1 = 1, a2 = 1;
if (n < 2)
return 1;
for (int i = 2; i <= n; i++) {
a2 = a0 + a1;
a0 = a1;
a1 = a2;
}
return a2;
}
}
汉诺塔:
法国数学家爱德华·卢卡斯曾编写过一个印度的古老传说:在世界中心贝拿勒斯(在印度北部)的圣庙里,一块黄铜板上插着三根宝石针。印度教的主神梵天在创造世界的时候,在其中一根针上从下到上地穿好了由大到小的64片金片,这就是所谓的汉诺塔。不论白天黑夜,总有一个僧侣在按照下面的法则移动这些金片:一次只移动一片,不管在哪根针上,小片必须在大片上面。僧侣们预言,当所有的金片都从梵天穿好的那根针上移到另外一根针上时,世界就将在一声霹雳中消灭,而梵塔、庙宇和众生也都将同归于尽。
不管这个传说的可信度有多大,如果考虑一下把64片金片,由一根针上移到另一根针上,并且始终保持上小下大的顺序。这需要多少次移动呢?这里需要递归的方法。假设有n片,移动次数是f(n).显然f(1)=1,f(2)=3,f(3)=7,且f(k+1)=2*f(k)+1。此后不难证明f(n)=2^n-1。n=64时,
假如每秒钟一次,共需多长时间呢?一个平年365天有31536000 秒,闰年366天有31622400秒,平均每年31556952秒,计算一下:
18446744073709551615秒
这表明移完这些金片需要5845.54亿年以上,而地球存在至今不过45亿年,太阳系的预期寿命据说也就是数百亿年。真的过了5845.54亿年,不说太阳系和银河系,至少地球上的一切生命,连同梵塔、庙宇等,都早已经灰飞烟灭。
**
* 1.有三根杆子A,B,C。A杆上有若干碟子
* 2.每次移动一块碟子,小的只能叠在大的上面
* 3.把所有碟子从A杆全部移到C杆上
*/
public class HanNuoTa {
public static void main(String args[]) throws Exception {
int n = 4;
doTowers(n, 'A', 'B', 'C');
}
// 借助B 把 A 移动到 C
public static void doTowers(int pace, char A, char B, char C) {
if (pace == 1) {// 如果from只有一个盘子了,直接放一to上
System.out.println("Disk 1 由 " + A + " 移动到 " + C);
} else {
// 借用C把A的pace-1 全部移到B
doTowers(pace - 1, A, C, B);
System.out.println("Disk " + pace + " 由 " + A + " 移动到 " + C);
// 然后借用A把B移到 C
doTowers(pace - 1, B, A, C);
}
}
}