随笔- 177  文章- 0  评论- 0  阅读- 1475 

数字的定义及使用

 1.数组基本用法

1.1 什么是数组

数组本质上就是让我们能 " 批量 " 创建相同类型的变量 .
例如 :
如果需要表示两个数据 , 那么直接创建两个变量即可 int a ; int b
如果需要表示五个数据 , 那么可以创建五个变量 int a1 ; int a2 ; int a3 ; int a4 ; int a5 ;
但是如果需要表示一万个数据 , 那么就不能创建一万个变量了 . 这时候就需要使用数组 , 帮我们批量创建 .
注意事项: Java , 数组中包含的变量必须是 相同类型 .

1.2 创建数组

代码示例
// 动态初始化
数据类型[] 数组名称 = new 数据类型 [] { 初始化数据 };
// 静态初始化
数据类型[] 数组名称 = { 初始化数据 };
int[] arr = new int[]{1, 2, 3};
int[] arr = {1, 2, 3};
其实数组也可以写成
int arr[] = {1, 2, 3};
注意事项: 静态初始化的时候 , 数组元素个数和初始化数据的格式是一致的 .

1.3 数组的使用

代码示例 : 获取长度 & 访问元素
int[] arr = {1, 2, 3};
// 获取数组长度
System.out.println("length: " + arr.length); // 执行结果: 3
// 访问数组中的元素
System.out.println(arr[1]); // 执行结果: 2
System.out.println(arr[0]); // 执行结果: 1
注意事项
1. 使用 arr.length 能够获取到数组的长度 . . 这个操作为成员访问操作符 . 后面在面向对象中会经常用到 .
2. 使用 [ ] 按下标取数组元素 . 需要注意 , 下标从 0 开始计数
3. 使用 [ ] 操作既能读取数据 , 也能修改数据 .
4. 下标访问操作不能超出有效范围 [0, length - 1] , 如果超出有效范围 , 会出现下标越界异常
代码示例 : 下标越界
arr[2] = 100;
System.out.println(arr[2]); // 执行结果: 100
int[] arr = {1, 2, 3};
System.out.println(arr[100]);
// 执行结果
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 100
at Test.main(Test.java:4)
抛出了 java.lang.ArrayIndexOutOfBoundsException 异常 . 使用数组一定要下标谨防越界 .
代码示例 : 遍历数组
所谓 " 遍历 " 是指将数组中的所有元素都访问一遍 , 不重不漏 . 通常需要搭配循环语句 .
代码示例 : 使用 for-each 遍历数组
for-each for 循环的另外一种使用方式 . 能够更方便的完成对数组的遍历 . 可以避免循环条件和更新语句写错 .

2. 数组作为方法的参数

2.1 基本用法

代码示例 : 打印数组内容
int[] arr = {1, 2, 3};
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
// 执行结果
1
2
3
int[] arr = {1, 2, 3};
for (int x : arr) {
System.out.println(x);
}
// 执行结果
1
2
3
public static void main(String[] args) {
int[] arr = {1, 2, 3};
printArray(arr);
}
public static void printArray(int[] a) {
for (int x : a) {
System.out.println(x);
}
}
// 执行结果
1
2
3

在这个代码中 int[] a 是函数的形参, int[] arr 是函数实参. 如果需要获取到数组长度, 同样可以使用 a.length

2.2 理解引用类型

我们尝试以下代码
代码示例 参数传数组类型
public static void main(String[] args) {
int num = 0;
func(num);
System.out.println("num = " + num);
}
public static void func(int x) {
x = 10;
System.out.println("x = " + x);
}
// 执行结果
x = 10
num = 0
public static void main(String[] args) {
int[] arr = {1, 2, 3};
func(arr);
System.out.println("arr[0] = " + arr[0]);
}
public static void func(int[] a) {
a[0] = 10;
System.out.println("a[0] = " + a[0]);
}
// 执行结果
a[0] = 10
arr[0] = 10
我们发现 , 在函数内部修改数组内容 , 函数外部也发生改变 .
此时数组名 arr 是一个 " 引用 " . 当传参的时候 , 是按照引用传参 .
这里我们要先从内存开始说起 .
a) 当我们创建 new int[]{1, 2, 3} 的时候 , 相当于创建了一块内存空间保存三个 int
b) 接下来执行 int[] arr = new int[]{1, 2, 3} 相当于又创建了一个 int[] 变量 , 这个变量是一个引用类型 , 面只保存了一个整数( 数组的起始内存地址 )
c) 接下来我们进行传参相当于 int[] a = arr , 内存布局如图
d) 接下来我们修改 a[0] , 此时是根据 0x100 这样的地址找到对应的内存位置 , 将值改成 100
  接下来我们修改 a[0] , 此时是根据 0x100 这样的地址找到对应的内存位置 , 将值改成 100
此时已经将 0x100 地址的数据改成了 100 . 那么根据实参 arr 来获取数组内容 arr[0] , 本质上也是获取 0x100
地址上的数据 , 也是 100.
总结 : 所谓的 " 引用 " 本质上只是存了一个地址 . Java 将数组设定成引用类型 , 这样的话后续进行数组参数传参 , 其实
只是将数组的地址传入到函数形参中 . 这样可以避免对整个数组的拷贝 ( 数组可能比较长 , 那么拷贝开销就会很大 ).

2.3 认识 null

null Java 中表示 " 空引用 " , 也就是一个无效的引用 .
null 的作用类似于 C 语言中的 NULL ( 空指针 ), 都是表示一个无效的内存位置 . 因此不能对这个内存进行任何读写操
. 一旦尝试读写 , 就会抛出 NullPointerException.

2.4 初识 JVM 内存区域划分

一个宿舍楼会划分成几个不同的区域 : 大一学生 , 大二学生 ... 计算机专业学生 , 通信专业学生 ....
内存也是类似 , 这个大走廊被分成很多部分 , 每个区域存放不同的数据 .
JVM 的内存被划分成了几个区域 , 如图所示 :
程序计数器 (PC Register): 只是一个很小的空间 , 保存下一条执行的指令的地址 .
虚拟机栈 (JVM Stack): 重点是存储 局部变量表 ( 当然也有其他信息 ). 我们刚才创建的 int[] arr 这样的存储地
址的引用就是在这里保存 .
本地方法栈 (Native Method Stack): 本地方法栈与虚拟机栈的作用类似 . 只不过保存的内容是 Native 方法的局
部变量 . 在有些版本的 JVM 实现中 ( 例如 HotSpot), 本地方法栈和虚拟机栈是一起的 .
(Heap): JVM 所管理的最大内存区域 . 使用 new 创建的对象都是在堆上保存 ( 例如前面的 new int[]{1, 2,
3} )
方法区 (Method Area): 用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数
. 方法编译出的的字节码就是保存在这个区域 .
运行时常量池 (Runtime Constant Pool): 是方法区的一部分 , 存放字面量 ( 字符串常量 ) 与符号引用 . ( 注意 JDK
1.7 开始 , 运行时常量池在堆上 ).
Native 方法 :
JVM 是一个基于 C++ 实现的程序 . Java 程序执行过程中 , 本质上也需要调用 C++ 提供的一些函数进行和操
作系统底层进行一些交互 . 因此在 Java 开发中也会调用到一些 C++ 实现的函数 .
这里的 Native 方法就是指这些 C++ 实现的 , 再由 Java 来调用的函数 .
我们发现 , 在上面的图中 , 程序计数器 , 虚拟机栈 , 本地方法栈被很多个原谅色的 , 名叫 Thread( 线程 ) 的方框圈起来了 ,
并且存在很多份 . 而 堆 , 方法区 , 运行时常量池 , 只有一份 . ( 关于线程 , 这是我们后面重点讲解的内容 ).
关于上面的划分方式 , 我们随着后面的学习慢慢理解 . 此处我们重点理解 虚拟机栈 和 堆 .
局部变量和引用保存在栈上 , new 出的对象保存在堆上 .
堆的空间非常大 , 栈的空间比较小 .
堆是整个 JVM 共享一个 , 而栈每个线程具有一份 ( 一个 Java 程序中可能存在多个栈 ).

3. 数组作为方法的返回值

代码示例 : 写一个方法 , 将数组中的每个元素都 * 2
// 直接修改原数组
class Test {
public static void main(String[] args) {
int[] arr = {1, 2, 3};
transform(arr);
printArray(arr);
}
public static void printArray(int[] arr) {
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
public static void transform(int[] arr) {
for (int i = 0; i < arr.length; i++) {
arr[i] = arr[i] * 2;
}
}
}

这个代码固然可行 , 但是破坏了原有数组 . 有时候我们不希望破坏原数组 , 就需要在方法内部创建一个新的数组 , 并由
方法返回出来 .
这样的话就不会破坏原有数组了 .
另外由于数组是引用类型 , 返回的时候只是将这个数组的首地址返回给函数调用者 , 没有拷贝数组内容 , 从而比较高
.

4. 数组练习

4.1 数组转字符串

代码示例
// 返回一个新的数组
class Test {
public static void main(String[] args) {
int[] arr = {1, 2, 3};
int[] output = transform(arr);
printArray(output);
}
public static void printArray(int[] arr) {
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
public static int[] transform(int[] arr) {
int[] ret = new int[arr.length];
for (int i = 0; i < arr.length; i++) {
ret[i] = arr[i] * 2;
}
return ret;
}
}
import java.util.Arrays
int[] arr = {1,2,3,4,5,6};
String newArr = Arrays.toString(arr);
System.out.println(newArr);
// 执行结果
[1, 2, 3, 4, 5, 6]

使用这个方法后续打印数组就更方便一些 .
Java 中提供了 java.util.Arrays , 其中包含了一些操作数组的常用方法 .
什么是包 ?
例如做一碗油泼面 , 需要先和面 , 擀面 , 扯出面条 , 再烧水 , 下锅煮熟 , 放调料 , 泼油 .
但是其中的 " 和面 , 擀面 , 扯出面条 " 环节难度比较大 , 不是所有人都能很容易做好 . 于是超市就提供了一些直接
已经扯好的面条 , 可以直接买回来下锅煮 . 从而降低了做油泼面的难度 , 也提高了制作效率 .
程序开发也不是从零开始 , 而是要站在巨人的肩膀上 .
像我们很多程序写的过程中不必把所有的细节都自己实现 , 已经有大量的标准库 (JDK 提供好的代码 ) 和海量的
第三方库 ( 其他机构组织提供的代码 ) 供我们直接使用 . 这些代码就放在一个一个的 " " 之中 . 所谓的包就相当
于卖面条的超市 . 只不过 , 超市的面条只有寥寥几种 , 而我们可以使用的 " " , 有成千上万 .
我们实现一个自己版本的数组转字符串

4.2 数组拷贝

代码示例
public static void main(String[] args) {
int[] arr = {1,2,3,4,5,6};
System.out.println(toString(arr));
}
public static String toString(int[] arr) {
String ret = "[";
for (int i = 0; i < arr.length; i++) {
// 借助 String += 进行拼接字符串
ret += arr[i];
// 除了最后一个元素之外, 其他元素后面都要加上 ", "
if (i != arr.length - 1) {
ret += ", ";
}
}
ret += "]";
return ret;
}

注意事项: 相比于 newArr = arr 这样的赋值 , copyOf 是将数组进行了 深拷贝 , 即又创建了一个数组对象 , 拷贝原有
数组中的所有元素到新数组中 . 因此 , 修改原数组 , 不会影响到新数组 .
实现自己版本的拷贝数组

4.3 找数组中的最大元素

给定一个整型数组 , 找到其中的最大元素 ( 找最小元素同理 )
代码示例
import java.util.Arrays
int[] arr = {1,2,3,4,5,6};
int[] newArr = Arrays.copyOf(arr, arr.length);
System.out.println("newArr: " + Arrays.toString(newArr));
arr[0] = 10;
System.out.println("arr: " + Arrays.toString(arr));
System.out.println("newArr: " + Arrays.toString(newArr));
// 拷贝某个范围.
int[] newArr = Arrays.copyOfRange(arr, 2, 4);
System.out.println("newArr2: " + Arrays.toString(newArr2));
public static int[] copyOf(int[] arr) {
int[] ret = new int[arr.length];
for (int i = 0; i < arr.length; i++) {
ret[i] = arr[i];
}
return ret;
}

类似于 " 打擂台 " 这样的过程 . 其中 max 变量作为 擂台 , 比擂台上的元素大 , 就替换上去 , 否则就下一个对手 .

4.4 求数组中元素的平均值

给定一个整型数组 , 求平均值
代码示例
public static void main(String[] args) {
int[] arr = {1,2,3,4,5,6};
System.out.println(max(arr));
}
public static int max(int[] arr) {
int max = arr[0];
for (int i = 1; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
return max;
}
// 执行结果
6
public static void main(String[] args) {
int[] arr = {1,2,3,4,5,6};
System.out.println(avg(arr));
}
public static double avg(int[] arr) {
int sum = 0;
for (int x : arr) {
sum += x;
}
return (double)sum / (double)arr.length;
}
// 执行结果
3.5

注意事项: 结果要用 double 来表示 .

 posted on   dapaige  阅读(5)  评论(0编辑  收藏  举报  
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示