📚 7月31日课程笔记下:流程控制、大数值运算、数组与多维数组
第一部分:流程控制
1.1 块作用域
定义:一对花括号{}定义的代码块,其内部的变量仅在该块内有效。
核心规则:
- 块内不能声明与外部同名的变量,否则会引发编译冲突
- 块内声明的变量,作用域仅限于该代码块,执行到块结束的
}时变量即被回收,之后无法访问 - 嵌套代码块中同样不能声明同名变量,这与条件语句(如if判断)的作用域规则一致
比喻理解:就像在一个大房间里已经放了一盏灯(变量a),你不能在里面的小房间再放一盏同名的灯,否则就冲突了。
注意:这里的报错是因为变量重复声明,而不是因为没赋值。
1.2 分支语句:if / else
基本结构:根据条件判断执行不同分支的代码。
执行规则:
- 条件判断是从上往下依次执行的
- 只要满足某个条件,就执行对应的代码块并立即结束判断
- 只有前面所有条件都不满足时,才会执行最后的
else部分
⚠️ 条件顺序的重要性:
处理连续范围时,条件的排列顺序至关重要。就像筛子要从大孔到小孔排列才有效:
- ❌ 错误:先判断
age > 20,再判断age > 80→ 80岁的人会被错误归入"大于20" - ✅ 正确:先判断
age > 80,再判断age > 30,最后判断age > 20
范围判断的正确写法:
想判断年龄在20到30岁之间,不能只写if(age > 20),必须同时满足两个条件:
if (age > 20 && age < 30) { System.out.println("青年"); }if-else vs switch-case 的适用场景:
if-else:适合范围判断(大于、小于、区间)switch-case:适合精确值匹配(等于某个具体值)
1.3 分支语句:switch / case
适用条件:变量取值明确、有限,比如 a 只能是 5、10 或 15 这几个固定值。
执行流程:程序从上到下逐个比较 case,匹配到对应值后执行代码块,遇到break就立即退出整个 switch 结构。
switch 括号内允许的类型:
char、byte、short、int- 枚举值(枚举就是取值固定的类型)
- ❌ 不能放自定义的复杂对象
⚠️ case 击穿现象:
如果某个 case 匹配后没有写 break,程序会继续执行后面所有 case 的代码,直到遇到 break 或 switch 结束。就像打穿所有关卡一样。
default语句的 break 可以省略,因为已经是最后一个了- 当变量值不匹配任何 case 时,程序会执行
default部分(处理意外情况的保险机制)
代码演示:
int a = 10; switch (a) { case 5: System.out.println("五"); break; case 10: System.out.println("十"); // 匹配到这里 break; // 没有break就会继续往下执行! case 15: System.out.println("十五"); break; default: System.out.println("其他"); }1.4 循环语句
① while 循环
特点:先判断条件,再执行循环体。如果初始条件就不满足,循环体一次都不会执行。
int a = 10; while (a > 4) { System.out.println(a); a--; } // 输出: 10 9 8 7 6 5(a=4时不满足条件,直接退出)② do-while 循环
特点:先执行一次循环体,再判断条件。即使初始条件不满足,也至少执行一次。就像"先上车后补票"。
int a = 4; do { System.out.println(a); // 先执行:打印4 a--; // a变成3 } while (a > 4); // 再判断:3>4为false,退出 // 输出: 4(虽然条件一开始就不满足,但还是执行了一次)while vs do-while 对比:
| 对比项 | while | do-while |
|---|---|---|
| 判断时机 | 先判断后执行 | 先执行后判断 |
| 最少执行次数 | 0次 | 1次 |
| 适用场景 | 不确定是否需要执行 | 至少要执行一次 |
③ for 循环
特点:用于已知循环次数的场景,包含初始化、循环条件和迭代三个部分。
结构说明:
- 初始化(如
i=0)可以写在循环外部,但通常放在第一个分号前 - 循环条件写在第二个位置
- 迭代语句(如
i++)既可以放在第三个位置,也可以移到循环体末尾,效果一样 - 两个分号必不可少,但三个部分都可以省略(不写循环条件时要注意避免死循环)
// 标准写法 for (int i = 0; i < 10; i++) { System.out.println(i); } // 省略写法(分号不能少) int i = 0; for (; i < 10; ) { System.out.println(i); i++; }④ for-each 循环
特点:用于遍历数组或集合,简化了索引操作。前面的变量会自动依次代表数组中的每个元素,从第一个到最后一个。
适用场景:
- 不确定数组长度时(如某些方法返回的数组),用 for-each 更安全,不用担心越界
- 已知长度时用普通 for 循环更合适
⚠️ 重要限制:for-each 中的变量不能直接修改原数组的值。就像用放大镜观察标本,你能看到每个细节(打印数据),但无法用放大镜直接改变标本本身。
int[] arr = {1, 2, 3, 4, 5}; // for-each遍历(只读) for (int x : arr) { System.out.println(x); // x依次代表1,2,3,4,5 } // ❌ 这样不能修改原数组 for (int x : arr) { x = x * 2; // 只修改了临时变量x,arr不变! }1.5 循环控制:break 与 continue
break
作用:直接终止整个循环,跳出循环体,执行循环后面的代码。
for (int i = 0; i < 10; i++) { if (i == 4) { break; // i=4时直接终止,5,6,7,8,9都不会执行 } System.out.println(i); } // 输出: 0 1 2 3continue
作用:只跳过当前这一次循环的剩余代码,直接进入下一次循环。循环本身还会继续。
for (int i = 0; i < 10; i++) { if (i == 4) { continue; // i=4时跳过打印,但循环继续 } System.out.println(i); } // 输出: 0 1 2 3 5 6 7 8 9(跳过了4)对比总结
| 对比项 | break | continue |
|---|---|---|
| 效果 | 彻底退出循环 | 跳过本轮,继续下一轮 |
| 后续迭代 | 不再执行 | 仍然执行 |
| 比喻 | 直接离开教室 | 这节课不听,下节课还来 |
1.6 带标签的 break 与 continue(了解即可)
可以给外层循环设置一个标签,然后在 break 或 continue 后面加上标签名,控制具体跳出哪个循环。
outer: for (int i = 0; i < 5; i++) { for (int j = 0; j < 5; j++) { if (j == 3) { break outer; // 直接结束整个外层循环 // continue outer; // 跳过外层循环的本次迭代,进入i的下一轮 } System.out.println(i + "," + j); } }注意:
break outer:直接结束整个外层循环(不是内层)continue outer:跳过外层循环的本次迭代,直接开始下一次外层循环- 实际开发中很少使用这种写法,了解即可
1.7 嵌套循环的执行过程
外层循环每执行一次,内层循环会完整执行一轮。
for (int i = 0; i < 3; i++) { for (int j = 0; j < 4; j++) { System.out.println("i=" + i + ", j=" + j); } } // i=0时,j从0到3全部执行 // i=1时,j从0到3全部执行 // i=2时,j从0到3全部执行注意:break 只会影响当前所在的循环层,不会跨层。
第二部分:大数值运算
2.1 基本类型的局限
- 基础数据类型(int、float、double)存在数值范围和精度问题
- 64位系统最多只能用64个比特位来表示数据,超出范围运算结果就会出错
- float 和 double 并不能精确表示所有小数,计算结果可能出现偏差
- 即使是64位的 double 类型,也有它的表示上限
典型问题:货币运算等需要精确计算的场景,不能用 float 或 double。
2.2 BigInteger 与 BigDecimal
| 类 | 用途 |
|---|---|
BigInteger | 大整数运算(超出long范围的整数) |
BigDecimal | 高精度小数运算(如货币计算) |
2.3 创建方式
两种方式:
- valueOf() 方法 / 直接赋值:数值必须在 long 类型的范围内,否则报错
- new + 字符串参数:可以传入超出 long 范围的字符串,表示超大数值
// BigInteger BigInteger a = BigInteger.valueOf(100); // 方式1:范围内 BigInteger b = new BigInteger("99999999999999999999"); // 方式2:超范围用字符串 // BigDecimal(推荐用字符串创建,避免精度问题) BigDecimal c = new BigDecimal("3.14159265358979323846"); BigDecimal d = BigDecimal.valueOf(3.14);2.4 运算方式
核心规则:不使用传统的+、-、*、/运算符,而是调用相应的方法进行运算。
就像不能直接用 + 号把两个箱子相加,而要打开箱子取出里面的东西才能计算。
BigInteger a = new BigInteger("100"); BigInteger b = new BigInteger("200"); BigInteger sum = a.add(b); // 加法 BigInteger diff = a.subtract(b); // 减法 BigInteger product = a.multiply(b); // 乘法 BigInteger quotient = a.divide(b); // 除法 System.out.println(sum); // 300注意事项:
- 方法签名很关键:前面是返回类型,括号里是参数类型
- 接收结果时要匹配返回类型(如 add() 返回 BigInteger 就要用 BigInteger 接收)
- 参数传递必须严格按方法要求的类型来
- BigDecimal 同理,所有运算都通过方法调用实现
第三部分:数组
3.1 基本概念
- 数组是一种数据结构,用于存储同一种类型的数据
- 元素在内存中连续存放
- 访问元素通过从0 开始的下标
- 声明数组时必须确定长度(这是原生数组的特点)
3.2 数组初始化
多种写法:
// 方式1:声明时直接赋值 int[] a = {1, 2, 3, 4, 5}; // 方式2:new关键字创建(指定长度,元素有默认值) int[] b = new int[100]; // 100个元素,默认值都是0 // 方式3:new + 赋值 int[] c = new int[]{5, 7, 4, 10}; // 匿名数组(不赋值给变量,只能临时使用,用完就扔) new int[]{1, 2, 3}; // 必须用new,直接用括号不行各类型的默认初始值:
| 类型 | 默认值 |
|---|---|
| int | 0 |
| double / float | 0.0 |
| boolean | false |
| String / 引用类型 | null |
注意:int[] a只是定义了一个数组变量,只有用new int[100]初始化后,才真正创建了存储空间。要区分声明和实际创建数组的区别。
补充:Java 提供了更灵活的ArrayList,底层虽然是数组,但能自动扩容、动态调整大小。
3.3 数组拷贝
浅拷贝(等号赋值)
本质:传递的是地址值。两个变量指向同一块内存空间,修改其中一个会影响另一个。
int[] small = {1, 2, 3}; int[] luckyNumbers = small; // 浅拷贝:只复制了引用地址 luckyNumbers[0] = 99; System.out.println(small[0]); // 99(原数组也变了!)比喻:两个人拿着同一把钥匙开同一个保险箱,一个人往里面放了东西,另一个人打开时自然能看到变化。
深拷贝(创建新数组)
本质:创建一个全新的数组,将源数组的每个元素值复制到新数组中。两个变量指向独立的空间,互不影响。
int[] arr = {5, 7, 4, 10}; int[] brr = new int[arr.length]; // 新开辟内存空间 // 手动逐个复制 for (int i = 0; i < arr.length; i++) { brr[i] = arr[i]; } brr[0] = 99; System.out.println(arr[0]); // 5(原数组不受影响)比喻:就像复印文件,原文件和复印件是完全独立的。
Arrays.copyOf() 方法(实现深拷贝)
int[] arr = {5, 7, 4, 10}; int[] copy = Arrays.copyOf(arr, arr.length); // 参数:源数组, 拷贝长度 copy[0] = 99; System.out.println(arr[0]); // 5(互不影响,是深拷贝)底层原理:
- 传入参数20,就会创建一个长度为20的新数组,然后依次复制原数组的值
- 如果原数组长度不够,多出来的位置会自动补零
- 本质上和手动遍历复制是一样的
3.4 数组遍历
int[] arr = {1, 2, 3, 4, 5}; // 方式1:普通for循环(已知长度) for (int i = 0; i < arr.length; i++) { System.out.println(arr[i]); } // 方式2:for-each(不确定长度时更安全) for (int x : arr) { System.out.println(x); }3.5 命令行参数(args)
main方法里的args参数就是一个字符串数组,用来接收外部传入的参数- 在 IDE 里直接运行时,args 是空的
- 通过命令行执行 jar 包时,可以在命令后面添加参数传入
- 不知道 args 有多长时,用 for-each 遍历最方便
// 遍历命令行参数 for (String a : args) { System.out.println(a); }3.6 Arrays 类常用方法
| 方法 | 功能 | 说明 |
|---|---|---|
Arrays.sort(arr) | 排序 | 底层采用快速排序算法,默认从小到大 |
Arrays.toString(arr) | 转为字符串 | 方便打印数组内容 |
Arrays.fill(arr, val) | 填充 | 把所有元素批量修改为指定值 |
Arrays.equals(arr1, arr2) | 比较 | 比较两个数组内容是否相同 |
Arrays.copyOf(arr, len) | 深拷贝 | 创建新数组并复制元素 |
Arrays.binarySearch(arr, key) | 二分查找 | 需传入起始和结束下标 |
代码演示:
int[] arr = {4, 1, 3, 2}; // 排序(从小到大) Arrays.sort(arr); System.out.println(Arrays.toString(arr)); // [1, 2, 3, 4] // 填充 Arrays.fill(arr, 1); System.out.println(Arrays.toString(arr)); // [1, 1, 1, 1] // 比较(比较内容,不是地址) int[] a = {1, 2, 3}; int[] b = {1, 2, 3}; System.out.println(a == b); // false(地址不同) System.out.println(Arrays.equals(a, b)); // true(内容相同)⚠️ 重点:
- 直接用
==比较两个数组判断的是内存地址是否相同,往往不是我们想要的结果 - 正确做法是调用
Arrays.equals(),它重写了 equals 方法,比较的是实际内容 - 因为是 static 方法,可以直接通过类名
Arrays调用,不需要创建对象 - 只要两个数组的下标和对应值完全相同,就判定为相等
从大到小排序:Java 没有直接的从大到小 sort 方法,可以用循环倒序遍历来实现。实际编程中从小到大排序更常用。
第四部分:多维数组
4.1 基本概念
- 多维数组本质上就是一维数组的嵌套
- 一维数组里存放数组 → 二维数组
- 二维数组里再嵌套数组 → 三维数组,以此类推
- 就像俄罗斯套娃:外层是容器,内层才是实际内容
4.2 内存结构
- 二维数组在内存中并非完全连续存储
- 只有每个子数组内部是连续存放的
- 外壳数组存储的是各个子数组的地址引用
- 子数组里直接存放具体数值
数组长度:由最外层元素数量决定。比如外壳数组包含4个子数组,长度就是4。
4.3 访问方式
通过多层下标访问元素,像剥洋葱一样层层深入:
int[][] arr = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}; // arr[i][j]:i是外层数组的索引,j是内层数组的索引 System.out.println(arr[1][2]); // 6(第2个子数组的第3个元素)4.4 遍历(双重 for-each)
for-each 循环只能处理一维数组,遍历二维数组需要嵌套循环:
int[][] arr = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}; // 外层循环:i依次代表每个子数组(i本身又是一个数组) for (int[] i : arr) { // 内层循环:处理子数组里的每个元素 for (int j : i) { System.out.print(j + " "); } System.out.println(); } // 输出: // 1 2 3 // 4 5 6 // 7 8 9三维数组同理,无非是多套一层循环。
4.5 不规则数组
定义:内层数组长度不一致的二维数组。
原理:存储的是指向不同长度数组的地址,所以每行长度可以不同。
// 三角形结构:第1行长度1,第2行长度2,第3行长度3 int[][] triangle = { {1}, {2, 3}, {4, 5, 6} }; // 遍历(同样用双重for-each,自动适配不同长度) for (int[] row : triangle) { for (int val : row) { System.out.print(val + " "); } System.out.println(); } // 输出: // 1 // 2 3 // 4 5 6应用场景:矩阵运算、图形打印等需要动态调整每行元素数量的场景。
4.6 deepToString 方法
用于将多维数组转为字符串表示,内部逻辑就是逐层遍历数组元素进行字符串拼接:
int[][] arr = {{1, 2}, {3, 4}}; System.out.println(Arrays.deepToString(arr)); // [[1, 2], [3, 4]]