4.1初试数组
- 如何写一个程序计算用户输入的数字的平均数?
- 如何写一个程序计算用户输入的数字的平均数,并且输出所有大于平均数的数?
- 必须先记录每一个输入的数字,计算平均数之后,再检查记录下来的每一个数字,与平均数比较,决定是否输出。
算平均数代码:
package Array;
import java.util.Scanner;
public class PrintGreaterThanAverage {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
System.out.print("请输入数字:");
int count = 0;
int sum = 0;
int x =in.nextInt();
while (x != -1)
{
sum = sum + x;
count++;
x = in.nextInt(); //这个不能缺失
}
if (count > 0)
{
System.out.println(sum/count);
}
}
}
需求变更:输出大于平均数的数字:
修改后的代码如下:
package Array;
import java.util.Scanner;
public class PrintGreaterThanAverage {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
System.out.print("请输入数字:");
int count = 0;
int sum = 0;
int[] numbers = new int[100];
int x =in.nextInt();
while (x != -1)
{
//numbers[0] 赋值为x,x写到数组的每一个位置上去
numbers[count] = x;
sum = sum + x;
count++;
x = in.nextInt();
}
if (count > 0)
{
//定义一个average变量,值为sum/count;对数组中的每一个元素和average比较
double average = sum/count;
//遍历数组
for (int i = 0; i < count;i++)
{
if (numbers[i] > average )
{
System.out.println("大于平均数的值如下:" + numbers[i]);
}
}
System.out.println("平均数是:" + sum/count);
}
}
}
这个程序是有安全隐患的你能看出来么?
4.2创建数组
程序的安全隐患是数组的大小一旦被创建就无法被改变。
why?
- 这个程序是危险的,因为输入的数据可能超过100个
数组:
int[] numbers = new int[4]; //新建数组后,我们就可以去使用数组了。
numbers[0] = 101;
numbers[1] = 102;
numbers[2] = 103;
numbers[3] = 0;
定义数组变量:
- <类型>[] <名字> = new <类型>[元素个数]
- int[] grades = new int[100];
- double[] averages = new double[20];
注意:
- 元素个数必须是整数
- 元素个数必须给出
- 元素个数可以是变量
int[] a = new int[10]
- 创建了一个int型的数组
- 10个元素:a[0]、a[1]、a[2]、a[3]、a[4]、a[5]、a[6]、a[7]、a[8]、a[9]
- 每个元素都是一个int的变量
- 可以读和写
- eg:a[2] = a[1] + 6;
4.3数组的元素
数组的有效下标:
-
最小的下标是0,最大的下标是数组的元素个数-1
-
可是编译器不会检查你是否用了有效的下标
-
但是如果运行的时候出现了无效的下标,可能会导致程序终止
int[] numbers = new int[100]; numbers[101] = 100; 会报错:数组下标越界: Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 101
因为数组的元素个数可以是变量,所以我们不指定数组的长度,让它是一个变量。
修改代码如下:
package Array;
import java.util.Scanner;
public class PrintGreaterThanAverage {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int sum = 0;
int count = 0;
// int[] numbers = new int[count];
System.out.println("请输入数组的长度:");
count =in.nextInt();
if (count > 0)
{
int[] numbers = new int[count];
for (int i=0;i<count;i++)
{
numbers[i] = in.nextInt();
sum += numbers[i];
}
double average = sum/count;
System.out.println("平均值为:" + average);
for (int i=0; i<count;i++) //此处可以进行调优,i < numbers.length;好处就是这个for循环有可扩展性,count
{
if (numbers[i] > average)
{
System.out.println("大于平均数的数如下" + numbers[i]);
}
}
}
}
}
Length:
-
每个数组有一个内部成员length,会告诉你它的元素的数量
for( i = 0; i < 100 ; ++i)
{
sum += grade[i];
}
最好是:
for ( i = 0; i < grade.length; ++i)
{
sum += grade[i];
}修改代码:
for (int i=0;i<count;i++) ==>
for (int i=0;i<numbers.length;i++)
这个for循环就是遍历的数组,是具有可扩展性的
4.4投票统计
- 写一个程序,输入数量不确定的[0,9]范围内的整数,统计每一种数字出现的次数,输入-1表示结束。
分析:有哪些数据要处理?使用的是什么算法??
需要有每次读进来的数字
代码如下:
package Array;
import java.util.Scanner;
public class VoteCount {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int x;
int[] numbers = new int[5];
System.out.print("请输入数字:");
x = in.nextInt();
while (x != -1)
{
if (x >= 0 && x <=9)
{
numbers[x] ++;
}
x = in.nextInt();
}
for (int i =0;i < numbers.length;i++)
{
System.out.println(i + ":" + numbers[i]);
}
}
}
输入进行测试:
请输入数字:1 4 4 4 66 66 15 15 8 8
-1
0个:0
1个:1
2个:0
3个:0
4个:3
5个:0
6个:0
7个:0
8个:2
9个:0
4.2数组变量和运算
4.2.1数组变量
直接初始化数组:
- new创建的数组会得到默认的0值
- int[] scores = {87,98,95,41,45,24};
- 直接用大括号给出数组的所有元素的初始值
- 不需要给出数组的大小,编译器替你数数
- 如何得知数组的大小?length!
int [] scores = {87,98,95,41,45,24};
System.out.println(scores.length);
for (int i = 0; i < scores.lemgth; i++)
{
System.out.println(scores[i]);
}
数组变量赋值:
int[] a = new int[10];
a[0] = 5;
int[] b = a;
b[0] = 16;
System.out.println(a[0]);
代码:
package Array;
public class ArrayTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] a = new int[10];
a[0] = 5;
int[] b = a;
System.out.println(a[0]);
b[0] = 16;
System.out.println(b[0]);
System.out.println(a[0]);
}
}
输出:
5
16
16
int i = 5;普通变量i是所有者 int j = i; j=5; ==> j=6,j就变成了6
int[] a = new int[10]; //在某个别处制造一个东西给你用
数组变量:a是这个数组的管理者;
int[] b = a ==> 让b去管理a所管理的数组,当b[0] = 16的时候,从a[0]去读到的值也是一样的.
Java数组变量的核心问题,它是管理者,不是所有者.
为了便于理解,一段代码如下:
int[] a1 = {1,2,3,4,5};
int[] a2 = a1;
for (int i = 0; i < a2.length; ++i) //对a2进行一个遍历,对a2中每一个元素加1
{
a2[i] ++;
}
for ( int i = 0; i < a1.length; ++i)
{
System.out.println(a1[i]);
}
- 数组a1 [1,2,3,4,5]
- 邀请a2来管理这个数组[1,2,3,4,5],a2对这个数组进行加1操作,就变成了[2,3,4,5,6]
- 再通过a1来访问这个数组就是[2,3,4,5,6]
总结:
- 数组变量是数组的管理者而非数组本身
- 数组必须创建出来然后叫诶数组变量来管理
- 数组变量之间的赋值是管理权限的赋予
- 数组变量之间的比较是判断是否管理同一个数组
比较两个数组变量是否相同,比较的是a b是否管理着的一个数组,是内容相同,输出依然是false。
package Array;
public class ArrayTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] a = {1,2,3,4,6};
int[] b = a;
System.out.println(a == b);
int[] a1 = {1,2,3,4,5};
int[] b1 = {1,2,3,4,5};
System.out.println(a1 == b1);
}
}
复制数组:
- 必须遍历源数组将每个元素逐一拷贝给目的数组
package Array;
public class ArrayTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] a = {1,2,3,4,5};
int[] b = new int[a.length];
for(int i =0; i < b.length;i++)
{
b[i] = a[i];
}
for(int i =0; i < b.length;i++)
{
System.out.println(b[i]);
}
}
}
需求:判断a、b数组中每个元素是否相等
package Array;
public class ArrayTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] a = {1,2,3,4,5};
int[] b = new int[a.length];
for(int i =0; i < b.length;i++)
{
b[i] = a[i];
}
boolean isEqual = true;
for(int i =0; i < b.length;i++)
{
if (a[i] != b[i])
{
isEqual = false;
break;
}
}
System.out.println(isEqual);
}
}
输出:true
4.2.2遍历数组
- 在一组给定的数据中,如何找出某个数据是否存在?
线性搜索程序:
缺点:效率不高
package Array;
import java.util.Scanner;
public class ThroughArray {
public static void main(String[] args) {
// TODO Auto-generated method stub
//遍历数组
Scanner in = new Scanner(System.in);
int[] data = {3,2,5,7,11,14,34,12,28};
int x = in.nextInt();
//需要找出位置,因为-1不可能有效值
int location = -1;
for (int i = 0; i < data.length; i++)
{
if (x == data[i])
{
location = i;
break;
}
}
if (location > -1)
{
System.out.println(x + "是第" + (location+1)+"个");
}
else
{
System.out.println("不在其中");
}
}
}
- 通常都是使用for循环,让循环变量i从0到 < 数组的length,这样循环体内最大的i正好是数组最大的有效下标.
- 常见错误是:
- 循环结束条件是<=数组长度,或:
- 离开循环后,继续使用i的值来做数组元素的下标!
又进行修改:
只需要判断输入的值是否在给定的数组中,不需要输出在第几个位置。
for-each循环
概念:
for ( <类型> <变量>:<数组>)
{
…
}
适用于遍历一个数组的场合,不可能用这个给数组赋值,k只是和数组中值相等的关系,而不是存储对应的关系
package Array;
import java.util.Scanner;
public class FindFather {
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner in = new Scanner(System.in);
int[] data = {3,12,34,54,23,11,14,28};
System.out.print("请输入一个数:");
int x = in.nextInt();
int loc = -1;
boolean found = false;
for (int i=0;i<data.length;i++)
{
if(x == data[i])
{
loc = i;
break;
}
}
//for-each循环,对于data中的每一个元素,循环每做一轮,都是会变的
for(int k: data)
{
if (k == x)
{
found = true;
}
}
if (loc > -1)
{
System.out.println(x + "是第" + (loc+1) + "个");
}
else
{
System.out.println("不在其中");
}
}
}
4.2.3素数计算
从2到x-1测试是否可以整除
- 对于n要循环(n-1)遍
- 当n很大时就可以看作跑n遍
改进方案1:
去掉偶数后,从3到x-1,每次加2
- 如果x是偶数,立刻
- 否则循环(n-3)/2+1遍
- 当n很大时就是n/2遍
改进方案2:
无须到x-1,到sqrt(x)就够了
- 只需要循环到sqrt(x)遍
- 从n --> n/2 --> sqrt(n)
for (int i = 3; i < Math.sqrt(x); i+=2 )
{
if ( x % i == 0)
{
isPrime = false;
break;
}
}
改进方案3:
判断是否能被已知的且<x的素数整除
构造素数表:
- 令x为2
- 将2x、3x、4x直至ax < n 的数标记为非素数
- 令x为下一个没有被标记为非素数的数,重复2;直到所有的数都已经尝试完毕
How to Inplement?
- 要构造n以内的素数表
- 1、创建prime为boolean[n],初始化其所有元素为true,prime[x]为true表示x的素数
- 2、令x = 2
- 3、如果x是素数,则对于(i=2;xi<n;i++)令prime[ix] = false
- 4、令x++,如果x < n,重复3,否则结束
代码如下:
package Array;
import java.util.Scanner;
public class Top50PrimeNumber {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
boolean[] isPrime = new boolean[100];
for (int i = 0; i < isPrime.length;i++)
{
isPrime[i] = true;
}
for (int i = 2; i < isPrime.length; i++)
{
if (isPrime[i])
{
for (int k = 2; i*k < isPrime.length;k++)
{
isPrime[i*k] = false;
}
}
}
for ( int i = 2; i< isPrime.length;i++)
{
if (isPrime[i])
{
System.out.println(i + " ");
}
}
}
}
4.3二维数组
- int[] [] a = new int[3][5]
- 通常理解为一个3行5列的数组
a[0][0] | a[0][1] | a[0][2] | a[0][3] | a[0][4] |
---|---|---|---|---|
a[1][0] | a[1][1] | a[1][2] | a[1][3] | a[1][4] |
a[2][0] | a[2][1] | a[2][2] | a[2][3] | a[2][4] |
二维数组的遍历:
for ( i = 0; i < 3; i++){
for ( j = 0; j < 5; j++){
a[i][j] = i*j;
}
}
- a[i][j]是一个int
- 表示第i行第j列上的单元
- a[i,j]并不存在
二维数组的初始化:
int[][] a = {
{1,2,3,4},
{1,2,3},
};
- 编译器来数数
- 每行一个{},逗号分隔
- 最后的逗号可以存在,又古老的传统
- 如果省略,表示补0
tic-tac-toe游戏:
- 读入一个3X3的矩阵,矩阵中的数字为1表示该位置上有一个X,为0表示为O
- 程序判断这个矩阵中是否有获胜的一方,输出表示获胜一方的字符X或O,或输出无人获胜
获胜的条件:某一行、某一列、对角线上出现了3个连续的X或O
5.1.1定义函数
对象的操作:
- String s = “hello”;
- int i = s.length();
- System.out.println(s + “bye”);
- 这些都是对象在执行函数
定义函数和调用函数:
package Array;
import java.util.Scanner;
public class test {
//定义isPrime函数,根据我们给的i来返回一个true或者false
public static boolean isPrime(int i)
{
boolean isPrime = true;
for (int k = 2; k < i; k++)
{
if (i % k ==0 )
{
isPrime = false;
break;
}
}
return isPrime;
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
System.out.print("请输入m:");
int m = in.nextInt();
System.out.print("请输入n:");
int n = in.nextInt();
if (m ==1) m=2;
int count = 0;
int sum = 0;
for (int i = m; i <= n; i++)
{
if (isPrime(i))
{
count++;
sum +=i;
}
}
System.out.println("在"+m+"和"+n+"之间有" + count +"个素数,总和为"+sum);
}
}
需求:求和
- 求出1到10、20到30和35到45的三个和
最笨的方法写出的一个分段函数:
在一个代码中,出现了很多的重复代码块,叫做代码复制,是程序不良的表现
package Function;
public class SubsectionSummation {
public static void main(String[] args) {
// TODO Auto-generated method stub
int i;
int sum;
//0到10求和
sum = 0;
for (i=1;i<=10;i++)
{
sum += i;
}
System.out.println(1+"到"+10+"的和是:"+sum);
//20到30求和
sum = 0;
for (i=20;i<=30;i++)
{
sum +=i;
}
System.out.println(20+"到"+30+"的和是:"+sum);
//35到45求和
sum = 0;
for (i=35;i<=45;i++)
{
sum +=i;
}
System.out.println(35+"到"+45+"的和是:"+sum);
}
}
将它转换为函数的定义和调用时:
package Function;
public class SubsectionSummation {
public static void sum(int a,int b)
{
int i;
int sum;
sum = 0;
for (i = a; i<= b;i++)
{
sum += i;
}
System.out.println(a+"到"+b+"的和是" + sum);
}
public static void main(String[] args) {
// TODO Auto-generated method stub
sum(1,10);
sum(10,20);
sum(30,40);
}
}
我们已经使用函数写了如下两个语句:
那什么是函数?
- 函数是一块代码,接收零个或多个参数,做一件事情,并返回零个活一个值
- 可以想像成数学中的函数:
- y=f(x)
函数的定义:
public static void sum(int a, int b)
{
int i;
int sum = 0;
for (i = a; i<=b;i++)
{
sum += i;
}
System.out.println(a + "到" +b+ "的和是" + sum);
}
5.1.2调用函数
-
函数名(参数值)
-
()起到了表示函数调用的重要作用
-
即使没有参数也需要()
-
如果有参数,则需要给出正确的数量和顺序
-
这些值会被按照顺序一次用来初始化函数中的参数
public static void f() { System.out.println("f(x)"); } 调用:f()
在eclipse中进行debug看它的执行过程;从结果我们能看到,函数直到每一次是哪里调用它,函数结束时会返回到正确的地方。
第一次sum ==> 第二次sum ==> 第三次sum
从函数中返回值:
- return停止函数的执行,并送回一个值
- return有两种形式:直接return;不带返回值
- return 表达式; 带着值的return
package Function;
public class SubsectionSummation {
public static int max(int a, int b)
{
int ret;
if(a > b)
{
ret =a;
}
else
{
ret = b;
}
return ret; //函数定义的时候有了int,所以一定要有返回值
}
public static void main(String[] args) {
int a = 6;
int b = 8;
int c;
c = max(10,14); //可以给两个常数
c = max(a,b); //可以给两个变量
c = max(a,23); //可以给一个变量一个常数
c = max(c,23);
c = max(max(c,a),max(5,b)); //max的第一个参数时max的一次调用
System.out.println(max(a,b));
max(12,14);
}
}
对函数定义段,return a; return b;这不是一个好的编程习惯,不能让程序有多个出口
没有返回值的函数:
- void函数名
- 不能使用带值的return
- 可以没有return
- 调用的时候不能做有返回值的赋值
5.2.1函数参数的传递
值传递的时候类型一定要匹配,如果一定要值传递,需要强制类型转换
类型不匹配?
- 当函数期望的参数类型比调用函数时给的值的类型宽的时候,编译器能悄悄替你把类型转换好
- char --> int --> double
- 当函数期望的参数类型比调用函数时给的值的类型窄的时候,需要你写强制类型转换
- (int)5.0
- 当函数期望的参数类型与调用函数时给的值的类型之间无法转换的时候 -->不行
值传递测试:
package Function;
public class SubsectionSummation {
public static void swap(int a, int b)
{
int t;
t = a;
a = b;
b = t;
}
public static void main(String[] args) {
int a = 5;
int b = 6;
swap(a,b); //这个仅仅只是做了值传递
System.out.println(a +""+ b);
}
}
输出:a = 5 ; b = 6. 并没有发生变化。
Java语言在调用函数时,永远只能传值给函数。
传值:
- 每个函数有自己的变量空间,参数也位于这个独立的空间中,和其它函数没有关系
- 对于函数参数表中的参数,叫做“形式参数”,调用函数时给的值,叫做实际参数
形式参数:public static void swap(int a, int b) ⇒ 形式参数
调用函数时叫:
swap(a,b) ==>实际参数(老) ==> 值(新)
- 由于会让初学者误会实际参数就是实际在函数进行计算时的参数,误会调用函数的时候把变量而不是值传进去了,所以不建议用这种古老的方式称呼
- 认为它们是参数和值的关系。
当参数是数组或者字符串的时候??
5.2.2函数的本地变量
本地变量:
- 函数的每次运行,就产生了一个独立的变量空间,在这个空间中国的变量,是函数的这次运行所独有的,称作本地变量
- 定义在函数内部的变量就是本地变量
- 参数也是本地变量
public static void main(String[] args){
sum(1,10);
sum(20,30);
sum(40,50);
}
每次调用都会分配一个新的空间。从debug中可以看出来;新的一个sum和旧的没有一点关系
变量的生存期和作用域:
- 生存期:什么时候这个变量开始出现了,到什么时候他消亡了
- 作用域:在代码的什么范围内可以访问这个变量(这个比那辆可以起作用)
- 对于本地变量,这两个问题的答案是统一的:大括号内–块