7. 数组
求职,能敲电脑就行。
内容导视:
- 一维数组
- 多维数组
7.1 一维数组
内容导视:
- 数组介绍
- 一维数组的声明
- 一维数组的遍历
- 数组元素默认值
- 数组赋值机制
7.1.1 数组介绍
提取片段:
数组是什么
- 存储同一类型元素的容器,引用类型
数组的优缺点
- 根据下标查询元素效率极高
- 随机增删元素效率低
为什么数组下标从 0 开始
前提:
- 数组的每个元素的内存地址是连续的
- 每个元素占用空间一样
知道首元素内存地址,可以算出第 i 个下标的元素的内存地址。设元素是 int 类型:(首地址即第一个元素的内存地址)
- 如果下标从 1 开始,arr[i] 的地址 = 首地址 +(i – 1)* 4
- 如果下标从 0 开始,arr[i] 的地址 = 首地址 + i * 4
很明显从 1 开始多算了一次减法,消耗的 CPU 资源更多。
为什么数组长度不可变
- 数组是连续不断的
- 如果数组可以延长,如果旁边恰好存了一个对象怎么办?
- 如果数组可以缩减,那么突然空出来了一小块空间,别人如何利用?
- 解决
- 固定数组长度,如果数组满了;
- 另寻一个更大的空间创建更大的数组,把原有数据填入新数组。旧数组无引用指向被当作垃圾回收,释放空间。
正文
数组是一种数据结构,当成存储数据的一类容器吧,数组只能存放同一类型的元素。如 int[] 只能存放 int 类型的元素。(包括能自动转换成 int 的元素)
数组一旦创建,长度不可变。如:
int[] arr = new int[5];
- 此数组实例的长度被确定为 5 了,不可改变。
- 数组中的元素的内存地址是连续的,数组拿 首元素的内存地址 作为整个数组的内存地址。
- 数组对象都有 length 属性,可以获取数组的长度。 int length = arr.length;
- 每个元素都有下标,从 0 开始,以 1 递增,如最后一个元素的下标是 length – 1,首元素的下标为 0。
为什么数组长度不可变
// 在堆空间开辟一块空间存储数组的元素
int[] arr = new int[3];
arr = new int[6];
/*
这并不是改变了原数组的长度,而是重新在堆中创建了一个新的数组实例
将地址重新赋给引用,原来的数组实例由于没有引用指向被回收
*/
堆是一块内存空间,用来存放实例(我有时候也称对象)。引用是指保存了实例的内存地址的变量,可以通过引用操纵实例,如同遥控器控制电视一样。
语言设计者对需要执行的任务分配给不同的结构,数组具有固定长度,因为它旨在成为开发人员可以构建的低级别、简单的实现。
计算机内存有限,如果可以延长数组,但旁边的内存已经存了另一个对象怎么办?数组各个元素是彼此相邻存储,延长不了。
固定长度后,如果数组没有足够的空间存放元素,那么可以找到更大的空白空间创建一个新的更大的数组存放原有数组的数据,同时也可以腾出原数组的空间。
如果不确定数组长度多大,可以使用 List 集合存放数据。
集合底层已经实现好了,当数组快装满时,会在更大的空白处创建一个长度更大的数组,将原有数据复制到新数组,不需要我们手动实现。
为什么数组下标从 0 开始
呵呵我猜的。
- 历史原因
- 之前 C 语言数组下标也是从 0 开始,没有必要出一种语言就改一次下标,增加额外的学习和理解成本。
- 减少 CPU 指令运算
- 1)下标从 0 开始,计算第 i 个元素的地址,arr[i] = 首地址 + i * 单个元素所占字节。
- 2)下标从 1 开始,计算第 i 个元素的地址,arr[i] = 首地址 +(i – 1)* 单个元素所占字节。
- 每次寻找地址时,多了一次 i – 1 即减法的指令运算,更加消耗 CPU 资源。(把大脑想象成 CPU,每次寻址时多计算一次二进制的减法)
使用数组存放元素的优点
根据下标查询元素效率极高。
- 每个元素的内存地址在空间上是连续的。
- 数组中每个元素类型相同,占用空间大小一样。
- 知道首元素的内存地址、每个元素的占用空间大小,通过下标可以算出元素的内存地址,直接通过内存地址定位元素。
缺点
- 由于保证数组中每个元素的内存地址连续 ,随机增删元素时,会涉及到后面元素统一向后或向前位移的操作,效率较低。
- 数组不能存储大数据量,因为很难在内存中找一块特别的大的连续的空间。
7.1.2 一维数组的声明
静态初始化
在声明时,同时确定了元素的值。
数组的元素类型[] 变量名 = {元素1, 元素2, ...};
例:
int[] array = {1, 2, 92, 64, 90};
/*
也可以这么写:
int[] array = new int[]{1, 2, 92, 64, 90};
有时候传参,或者先声明了 array,需要用到这样的方式赋值
*/
创建了一个元素类型为 int、长度为 5 的一维数组,引用名为 array,存放了 5 个 int 类型的元素,分别为 1、2、92、64、90。
上面图的元素,下标从左到右,依次为 0、1、2、3、4。
通过引用访问下标对应的元素:
// 访问第 1 个元素
int a1 = array[0];
// 访问第 5 个元素
int a5 = array[4];
// 获取数组的长度
int length = array.length;
System.out.println(a1);// 1
System.out.println(a5);// 90
System.out.println(length);// 5
动态初始化
数组存放的元素的类型[] 变量名 = new 元素类型[数组长度];
int[] array2 = new int[3];
/*
可以先声明,后分配空间
int[] array2;
array2 = new int[3];
*/
创建了一个元素类型为 int、长度为 3 的一维数组,引用名为 array2,存放了 3 个 int 类型的元素,值都默认为 0。
System.out.println(array2[0]);// 0
赋值
// 把 53 赋给下标为 0 的元素
array2[0] = 53;
array2[1] = 2;
array2[2] = 44;
System.out.println(array2[0]);// 53
不同的空
int[] arr1;// 此变量没有保存任何值,必须赋值(初始化)才能够访问
int[] arr2 = null;// 此变量保存了 null,代表空,没有指向任何对象
int[] arr3 = {};// 创建了一个长度为 0 的数组(数组无元素),地址赋给了 arr3
int[] arr4 = new int[0];// 同上
7.1.3 一维数组的遍历
数组中的元素下标从 0 到 length – 1,可以使用 for 循环访问每个元素。
int[] arr = {6, 2, 9};
for (int i = 0; i < arr.length; i++) {
int num = arr[i];
System.out.println(num);
}
注意:当访问不存在的下标时,会报数组索引越界异常。
int i = arr[3];
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 3
at Hello.main(Hello.java:5)
7.1.4 数组元素默认值
使用动态初始化时,不同类型的元素会有默认值。
数据类型 | 默认值 |
short、byte、int、long | 0 |
float、double | 0.0 |
boolean | false |
char | ‘\u0000’ |
引用类型包括 String、数组 | null |
long[] arr = new long[3];
byte b = arr[0];// 报错:从 long 转换到 byte 可能会有损失
特别说明一下:
char 的 ‘\u0000’ 并不是空格,而是空字符,代表什么都没有。
char[] arr = new char[3];
arr[1] = ' ';
System.out.println(arr[0] + 0);// 0
System.out.println(arr[1] + 0);// 32
‘\u0000’ 对应整数 0,另一种写法 ‘\0’,是字符的八进制表示。打个比方,’a’ 对应的 8、10、16 进制分别为 0141、97、0x0061,如下都是一个意思。
char c1 = 97;
char c2 = 0141;
char c3 = 0x0061;
char c4 = '\141';
char c5 = 'a';
7.1.5 数组赋值机制
int[] arr1 = {56, 6, 2};
// 将 arr1 保存的值拷贝一份,赋给 arr2
int[] arr2 = arr1;
arr1 保存的值为数组实例的内存地址,假设为 0x1111,然后拷贝一份地址(创建副本)给 arr2,本质还是值传递,arr1、arr2 指向的堆中的对象是同一个。(通过值传递实例地址)
修改 arr2 保存的值,arr1 不会受到影响。
arr2 = null;
System.out.println(arr1);// [I@15db9742(可以当作是实例的内存地址)
本来应该就此打住,但是有人可能还接触过引用传递这个概念:
值传递(pass by value):赋值时将值拷贝一份赋给另一个变量,这样在另一个变量中修改自己保存的值,不会影响到最初的变量;
类似 Ctrl + C、Ctrl + V。
引用传递(pass by reference):赋值时将值的地址赋给另一个变量,另一个变量通过内存地址定位到此值,如果修改此值,会影响原来变量保存的值;
类似 Windows 系统的创建桌面快捷方式。
值传递 | 引用传递 |
会创建副本 | 不会创建副本 |
无法改变原变量保存的值 | 可以改变原变量保存的值 |
有人就问了:
int[] arr1 = {6, 62, 2};
int[] arr2 = arr1;
// 使用 arr2 改变了数组中第一个元素的值
arr2[0] = 99;
// 你看看,这不影响了 arr1 吗?
System.out.println(arr1[0]);// 99
不不不,不是这个意思,把 arr1、arr2 当成两个独立的遥控器吧,电视只有一个,arr2 换台,受影响的是电视,而不是 arr1。
arr1 与 arr2 指向的是同一个对象,通过任一引用修改对象内的数据后,再通过任一引用访问对象内保存的数据当然会受到影响。
但这里修改的是数组中的元素,而不是 arr1 保存的值,arr1 保存的值还是这个实例的地址,没受影响。要想真正的拷贝而不受原变量影响,即相互独立,需要手动创建一个新的数组,然后手动赋值。
int[] arr1 = {6, 62, 2};
int n = arr1.length;
int[] arr2 = new int[n];
for (int i = 0; i < n; i++) {
arr2[i] = arr1[i];
}
结论:arr1 把自己保存的值拷贝一份赋给了arr2,只不过这个值恰好是地址,就当作是值传递吧。
数组实例 {6, 62, 2} 把地址赋给 arr1,才有点引用传递的味道。
由于不能直接通过实例访问到元素,必须借助引用;当没有引用时,再也不可能访问到此实例,等同于垃圾。
有人说,不对啊?必须借助引用访问元素这我知道,如 arr[0];可当没有引用时,我可以重新把这个实例的地址赋给另一个变量,又来了一个引用,这不就可以访问到了吗?
int[] arr1 = {5, 6, 2};// 保存实例的地址的变量称为引用
// 现在 arr1 不是 {5, 6, 2} 的引用了
arr1 = null;
// 重新将此实例的地址赋给 arr2
int[] arr2 = {5, 6, 2};
// 我访问到了!
System.out.println(arr2[1]);// 6
可是,你确定第 1 行与第 6 行的 {5, 6, 2} 是同一个实例吗?
要想确认是否是同一个实例,需要借助引用修改这个实例的值,再看看实例是否被修改了。如果被修改了,说明是同一个实例。
int[] arr1 = {5, 6, 2};
int[] arr2 = {5, 6, 2};
// 修改 arr1 对应的实例第一个元素为 9
arr1[0] = 9;
// 访问 arr2 对应的实例第一个元素,还是 5
System.out.println(arr2[0]);// 5
然而并没有被修改,说明不是同一个实例(包括之后面向对象中 new 出来的对象也是一样)。所以第 1 行的 {5, 6, 2} 永远不可访问到了,这种访问不到的实例已经没有用处,会在垃圾回收时被清理。
也可通过直接输出引用、双等号比较,查看是否是同一个实例:
int[] arr1 = {5, 6, 2};
int[] arr2 = arr1;
int[] arr3 = {5, 6, 2};
// arr1 与 arr2 保存的值一样
System.out.println(arr1 == arr2);// true
// arr1 与 arr3 保存的值不一样,说明保存的不是同一个实例的内存地址
System.out.println(arr1 == arr3);// false
System.out.println(arr1);// [I@15db9742
System.out.println(arr2);// [I@15db9742
System.out.println(arr3);// [I@6d06d69c
于是我们通常说:基本数据类型使用双等号比较的是值,引用类型使用双等号比较的是内存地址。
那如果有人只认为保存的内容相等就行,不在乎是否为不同实例,在源代码最上面加入 import java.util.Arrays;
int[] arr1 = {5, 6, 2};
int[] arr2 = arr1;
int[] arr3 = {5, 6, 2};
// equals 比较内容
System.out.println(Arrays.equals(arr1, arr3));// true
System.out.println(arr1 == arr3);// false
System.out.println(arr1 == arr2);// true
记住了,我们一般说拷贝数组,并不是说 int[] a2 = a1 类似的赋值,而是重新创建一个与拷贝源数据相同的数组(使用 == 比较为 false,但 equals 比较为 true)。
7.2 多维数组
内容导视:
- 二维数组声明
- 遍历二维数组
- 静态方法调用
7.2.1 二维数组声明
二维以上的数组用的很少,故略去。
声明二维数组
int[][] arr1;
int arr2[][];
int []arr3[];
一般使用第一种。
静态初始化
二维数组的组件类型是一维数组,三维数组的组件类型是二维数组…
int[] i1 = {5, 6, 7, 8};
int[] i2 = {63, 262, 2};
int[] i3 = {5, 2, 6};
int[][] arr1 = {i1, i2, i3};
int[][] arr2 = {
{252, 26, 3},
{1, 2, 3},
{6, 4, 2}
};
动态初始化
语法:
数据类型[][] 数组名 = new 数据类型[二维数组的长度][一维数组的长度];
例:
int[][] arr = new int[2][3];
代表创建了一个元素类型为 int、长度为 2 的二维数组;(对于引用类型,只保存内存地址)
二维数组中的每个组件都保存着元素类型为 int、长度为 3 的一维数组的内存地址。
二维数组本质是一维数组,只不过每个组件是一维数组的引用。
访问
// 二维数组中的每一个组件的类型都是一维数组,如 arr[0] 是 int[] 类型
int[][] arr = new int[2][3];
// arr[0] 代表二维数组中下标为 0 的组件,也就是一维数组,把一维数组的地址赋给 arr1
int[] arr1 = arr[0];
// 给 arr1 数组的下标为 0 的元素赋值 67
arr1[0] = 67;
// 获取 arr1 数组下标为 0 的元素
int num1 = arr1[0];
System.out.println(num1);
合并
int[][] arr = new int[2][3];
// 下标为 0 就是数组中的第一个组件
// 给 arr 下标为 0 的一维数组中的下标为 0 的元素赋值 67
arr[0][0] = 67;
// 访问 arr 下标为 0 的一维数组中的下标为 0 的元素
int num1 = arr[0][0];
System.out.println(num1);
例子:
int[][] arr = new int[2][3];
// 赋值
arr[0][0] = 52;
arr[0][1] = 5;
arr[0][2] = 62;// 现在下标为 0 的一维数组:{52, 5, 62}
arr[1][0] = 2;
arr[1][1] = 8;
arr[1][2] = 6;// 现在下标为 1 的一维数组:{2, 8, 6}
// 访问下标为 0 的数组的所有元素
int n1 = arr[0][0];
int n2 = arr[0][1];
int n3 = arr[0][2];
// 访问下标为 1 的数组的所有元素
int m1 = arr[1][0];
int m2 = arr[1][1];
int m3 = arr[1][2];
// 注意二维数组长度为 2,一维数组长度为 3,下标别越界。(下标别超过 length - 1)
当一维数组的长度不确定时,就不能使用上述方式动态初始化。
动态初始化 2
假如确定了二维数组的长度为 4,
int[][] arr = new int[4][]; 此时内存图如下:(因为一维数组是引用类型,不赋值默认为 null)
提示:
System.out.println(arr[0]);// null
// 使用 null 获取什么都会抛出 java.lang.NullPointerException 空指针异常
System.out.println(arr[0].length);
这类运行时异常,编译时检查不出来。
以后需要时再创建一维数组:
arr[0] = new int[1];
arr[1] = new int[2];
arr[2] = new int[3];
7.2.2 遍历二维数组
先得到每一个组件,也就是一维数组,再遍历一维数组得到每一个元素:
int[][] total = new int[3][3];
for (int i = 0; i < total.length; i++) {
int[] arr = total[i];
for (int j = 0; j < arr.length; j++) {
System.out.print(arr[j] + ",");
}
System.out.println("一维数组遍历完毕");
}
合并:
int[][] total = new int[3][3];
for (int i = 0; i < total.length; i++) {
for (int j = 0; j < total[i].length; j++) {
System.out.print(total[i][j] + "\t");
}
System.out.println();
}
7.2.3 静态方法调用
每次都是重复的代码,有点厌倦了,所以需要方法封装重复的代码了;只讲一点点。
class A {
public static void f1(int[] arr) {}
public static int sum(int a1, int a2) {return a1 + a2;}
}
方法的第 3 个单词 void、int…代表返回值类型,如果没有返回值就写 void;有返回值,就写返回值的类型,然后在方法结尾处写上 return 要返回的值 ;
f1、sum 是方法名,自己定义,符合标识符规则就行。
方法名后的是形式参数列表,可以定义任意个变量,变量之间使用英文逗号分隔,将来调用时传入实际参数,注意实际参数的个数和类型要与形式参数对应上。
如何使用(调用)方法?
在同一个类中,直接通过 方法名(实参) 调用。
class A {
public static void main(String[] args) {
// 使用 int 类型的变量接收返回值
// int num1 = 4; int num2 = 5;
int sum = sum(4, 5);
System.out.println(sum);// 9
}
// 返回 num1 + num2
public static int sum(int num1, int num2) {
return num1 + num2;
}
}
在同一个包下(同级目录),不同类中,使用 类名.方法名(实参) 调用:
class A {
public static void main(String[] args) {
System.out.println(B.f1());// 1
}
}
class B {
public static int f1() {return 1;}
}
不同包下的类需要使用 import 导入,如使用其它包下的 Arrays 类,关于方法详细请看 API 文档;如果以后编译时说找不到符号,想一想自己导入了此类没有。
import java.util.Arrays;
class A {
public static void main(String[] args) {
int[] arr = {5, 26, 3};
System.out.println(Arrays.toString(arr));// [5, 26, 3]
}
}
此外还会涉及到成员变量:
class T {
// 成员变量:类体中,方法外定义的变量,作用域为整个类
String s1;
// 带 static 的成员变量称为静态变量
static String s2;
public static void main(String[] args) {
// 在带 static 的方法中访问普通成员变量,需要使用 new 创建实例,再用引用.变量名访问
T t = new T();
t.s1 = "你好";// 不赋值默认为 null,与数组元素默认值一样
System.out.println(t.s1);// 你好
// 访问静态变量,本类中直接通过变量名访问,其它类需要使用类名.变量名访问
System.out.println(s2);// null
// 与数组一样,这是一个新的实例,与 t 无关
T t2 = new T();
System.out.println(t == t2);// false
}
}
7.x 总结回顾
数组是一种引用类型,一旦创建,长度不可变,只能存放同一类型的元素;数组中的元素的内存地址是连续的,可以通过下标快速定位到某个元素,但随机增删元素时,涉及到大量元素的位移,效率较低,适合经常查询而增删少的场景。
一维数组静态初始化:
int[] arr = {4, 5, 6, 7};
动态初始化:
int[] arr = new int[5];// 此时 5 个元素默认值为 0
arr[0] = 4;
arr[1] = 5;
获取数组长度: int length = arr.length;
遍历:
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
7.x 课后习题
7.1 编写方法用于拷贝数组。
7.2 编写方法用于反转数组,例:{12,25,67,2} —–> {2,67,25,12}。
7.3 编写一个类,有成员变量 int size、int[] arr;
- 编写方法使用 arr 添加元素,要求:能够自动扩容,而不会越界。
- 可以通过 size 获取已添加元素的个数。
- 编写方法删除元素,要求:数组长度同时也跟着缩减。示例:{42, 26, 6, 4, 9, 97} 删除 6 后,得到 {42, 26, 4, 9, 97}。
7.4 创建一个任意类型的一维数组,不手动赋值,元素的默认值为?
7.5 输出如下图形:
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
7.6 x 是二维数组,y 是一维数组。以下语句能够通过编译的有:
a) x[0] = y;
b) x[0][0] = y[0];
c) x = y[0];
d) x[0][0] = y;
e) x = y;
7.z 习题答案
7.1 编写方法用于拷贝数组。
思路:创建一个新的数组实例,长度为源数组的长度,然后遍历得到源数组的每个元素,同时将值拷贝到新的数组。
public static int[] copy(int[] arr1) {
int[] arr2 = new int[arr1.length];
for (int i = 0; i < arr1.length; i++) {
arr2[i] = arr1[i];
}
return arr2;
}
注意我们并不是拷贝地址,如 int[] arr2 = arr1,而是重新创建新的实例,如现在调用此方法传入的数组为 {1,2,6}:
JDK 已提供现成的方法供我们调用:
/*
将 arr1 数组的元素拷贝到 arr2 中;从下标 0 开始拷贝,从 arr2 下标 0 开始放;
一共拷贝 arr1.length 个元素
*/System.arraycopy(arr1, 0, arr2, 0, arr1.length);
/*
创建新的长度为 arr1.length 的数组,同时将 arr1 数组中 arr1.length
个元素拷贝到新的数组中,并返回此数组。
*/
int[] arr3 = Arrays.copyOf(arr1, arr1.length);
7.2 编写方法用于反转数组,例:{12,25,67,2} —–> {2,67,25,12}。
方法 1:创建新的数组接收值。
arr2[length – 1] 是 arr1[0]
arr2[length – 2] 是 arr1[1]
…
可以发现和为 length – 1,所以得到 arr2[i] 对应 arr1[length – i – 1]。
public static int[] reverse(int[] arr1) {
int n = arr1.length;
int[] arr2 = new int[n];
for (int i = 0; i < n; i++) {
arr2[i] = arr1[n - i - 1];
}
return arr2;
}
方法 2:自身的元素交换位置。
以 {1,2,3,4,5,6,7,8,9,10} 为例:1、10 交换,2、9 交换,一直到 arr[length / 2 – 1] 与 arr[length / 2] 交换,即 5、6 交换;一共 length / 2 次交换。
public static void reverse(int[] arr) {
int n = arr.length;
int temp;
for (int i = 0; i < n / 2; i++) {
temp = arr[i];
arr[i] = arr[n - i - 1];
arr[n - i - 1] = temp;
}
}
7.3 编写一个类,有成员变量 int size、int[] arr;
- 编写方法使用 arr 添加元素,要求:能够自动扩容,而不会越界。
- 可以通过 size 获取已添加元素的个数。
- 编写方法删除元素,要求:数组长度同时也跟着缩减。示例:{42, 26, 6, 4, 9, 97} 删除 6 后,得到 {42, 26, 4, 9, 97}。
1)add 方法添加元素
数组初始容量设为 10
每次添加元素之前判断 size + 1 是否大于数组容量
若大于数组容量则扩容为原来的 1.5 倍
class IntList {
private int size;
private int[] arr;
private static final int INIT_CAPACITY = 10;
/**
* 默认数组容量为 10
*/ public IntList() {
arr = new int[INIT_CAPACITY];
}
public boolean add(int element) {
ensureCapacityInternal(size + 1);
arr[size++] = element;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
if (minCapacity > arr.length) {
int oldCapacity = arr.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
arr = Arrays.copyOf(arr, newCapacity);
}
}
public int size() {
return size;
}
}
2)编写 remove 方法删除元素
顺序查找元素的下标
如果存在,则让此元素的后面所有元素前移一位,size--
* 使用 JDK 自带的 arraycopy 方法,让下标 index + 1 及之后的元素从下标 index 开始放置,
* 这样就覆盖了原来的下标为 index 的元素
不存在返回 false
public boolean remove(int element) {
int index = -1;
for (int i = 0; i < size; i++) {
if (arr[i] == element) {
index = i;
break;
}
}
if (index != -1) {
System.arraycopy(arr, index + 1, arr, index, size - (index + 1));
// 将最后一个元素归 0,因为上一步中已被前移一位
arr[size - 1] = 0;
size--;
return true;
} else {
return false;
}
}
3) 遍历
public void foreach() {
System.out.print("[");
for (int i = 0; i < size - 1; i++) {
System.out.print(arr[i] + ", ");
}
// 为了去掉逗号但不在循环中每次都判断是不是最后一个元素的做法
if (size - 1 >= 0) {
System.out.print(arr[size - 1]);
}
System.out.println("]");
}
7.4 创建一个任意类型的一维数组,不手动赋值,元素的默认值为?
数组动态初始化后,如果没有赋值,不同类型的数组中的元素有默认值,如下:
short byte int long 类型的数组默认值是 0
float double 是0.0
char 是空 ,对应 16 进制是 0x0000;即 char c = '\u0000' 或 char c = '\0';
boolean 是 false
String 和其它引用类型是 null
我尝试复制那个符号,却发现就是空,通过混合运算自动升级成 int 类型的特性,才知道它在字库表中的序号是 0,而不是空格 32:
7.5 输出如下图形:
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
先考虑打印如下图形:
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
观察规律:
1. 开头和结尾都是 1
2. 第 i 行有 i 个数
3. 从第 3 行开始,中间的数 = 上一行同列的数 + 上一行列数减一的数
将其看做二维数组,以第 4 行为例,arr[3][1] = arr[2][1] + arr[2][0]
考虑:
(1)定义长度为 5 的二维数组 arr 保存这些值
(2)由于一维数组长度在变,使用动态初始化
(3)遍历二维数组,i 从 0 开始,则 arr[i].length 为 i + 1
(4)遍历一维数组,开头与结尾都赋值 1,中间的元素为
arr[i][j] = arr[i - 1][j] + arr[i - 1][j - 1]
int[][] arr = new int[5][];
for (int i = 0; i < arr.length; i++) {
// 一维数组的长度为 i + 1
arr[i] = new int[i + 1];
// 开头与结尾都为 1
arr[i][0] = 1;
arr[i][i] = 1;
// 中间的元素
for (int j = 1; j < i; j++) {
arr[i][j] = arr[i - 1][j] + arr[i - 1][j - 1];
}
}
假设 arr.length = 5;
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
第 1 行前面有 4 个空格,输出 arr[0] 前先输出 arr.length – 1 个空格;
第 2 行前面有 3 个空格,输出 arr[1] 前先输出 arr.length – 2 个空格;
…
第 i + 1 行前面有 arr.length -(i + 1)个空格,输出 arr[i] 前先输出 arr.length – i – 1 个空格。
for (int i = 0; i < arr.length; i++) {
for (int m = 0; m < arr.length - i - 1; m++) {
System.out.print(" ");
}
for (int j = 0; j < arr[i].length; j++) {
System.out.print(arr[i][j] + " ");
}
System.out.println();
}
7.6 x 是二维数组,y 是一维数组。以下语句能够通过编译的有:
a) x[0] = y;
b) x[0][0] = y[0];
c) x = y[0];
d) x[0][0] = y;
e) x = y;
能通过编译的有:a、b
c 报错误: 不兼容的类型: int 无法转换为 int[][]
d 报错误: 不兼容的类型: int[] 无法转换为 int
e 报错误: 不兼容的类型: int[] 无法转换为 int[][]