2. 流程控制|方法|数组|二维数组|递归

文章目录

  • 流程控制
    • 代码块
    • 选择结构
    • 循环结构
    • 跳转控制关键字
  • 方法
    • 方法的概述
    • 方法的重载
    • Junit单元测试初识
      • 全限定类名
  • Debug 小技巧
  • 数组
    • 数组的基本概念
    • 数组的基本使用
      • 数组的声明
      • 数组的初始化
    • JVM内存模型
      • 什么是引用数据类型
      • 基本数据类型和引用数据类型的区别
      • 堆和栈中内容的区别
    • 数组异常
      • 长度为0的数组 和 数组是null
    • 遍历数组多种操作
    • 可变参数
    • 方法的传参问题
  • 二维数组
    • 二维数组的基本概念
    • 二维数组的基本使用
    • 二维数组的进阶操作
  • 递归
    • 递归注意事项
    • 异常

流程控制

代码块

在深入学习选择结构和循环结构前,我们还需要了解块(block)的概念。

什么是代码块?代码块的定义是:由若干条Java语句组成,并且用一对大括号括起来的结构,叫做代码块。

很显然,我们写的main方法就是一个代码块。

那么代码块有什么用处呢?主要是:

  1. 代码块决定了块中的变量的作用域,也就是块中的变量只在当前块中生效。这意味着同一个代码块中,不可能有同名的变量。
  2. 定义在块中的变量,被块限制了作用域,称之为局部变量。这是一个非常重要的概念

最后谈两点注意事项:

  1. 代码块是可以嵌套定义的。
  2. 关于代码块,我们后面会专门讲解,这里先暂且不谈。

选择结构

其实和c++写的差不多,个人感觉上没有区别:

选择结构有两种实现方式,if 和 switch:

  1. if的使用场景
    1. 针对结果是布尔类型的判断。
    2. 多分支if结构,可以使用多个判断条件。
    3. if的判断条件可以是一个连续的取值范围
  2. switch的使用场景
    1. 针对结果是固定类型、固定值的判断,尤其注意不能是boolean类型。
    2. switch始终只能对一个条件进行选择
    3. 每个分支的取值,只能是固定的且离散的。这是switch和if最本质的区别。
  3. 如果碰到if和switch都可以的情况,建议选择if,因为if语法简单不容易出错。而如果是针对离散值的判断,那就选择switch,因为它更加就简洁。实际开发中,99%以上的情况都在使用if而不是switch。

还有就是不管if后面有几条语句,都需要进行大括号括起来(规范)。

循环结构

看着语法来说,也和C++一样,这边就不细说了。

while、for和do…while之间的异同:

  1. while和for都要先判断条件是否成立,再决定是否执行循环体语句。
  2. do…while先执行循环体语句,再执行判断条件。所以无论如何,do…while都要执行一次循环体语句。
  3. while、for是先判断再干,do…while是干了再说。

跳转控制关键字

还是和C++一致,break,continue,return,至于goto(爬)

提到C/C++当中的循环控制关键字,还有一个非常著名的绕不过去,那就是goto

goto关键字,可以帮助我们在循环的过程中跳转到循环任何地方,极大增强了循环的灵活性。但是goto的缺点也是显而易见的,它破坏了程序的结构,使得循环的逻辑被打破,循环的结果容易变得不可控。

到了现在,虽然C++中依然保留goto,但实际上也是不推荐使用的。Java语法更直接摒弃了它,没有将它作为一个关键字,而是作为一个没有任何意义的保留字。

作为保留字,也是提醒程序员:这东西没用,你也不要定义一个东西叫goto。

方法

方法的概述

对于方法来说,实际上和C++的函数相类似,只是java当中每一个东西都被当作是一个类,所以我们需要加上public static 作为前面的标识,实际上就是,原本的方法的意识,这边给出样例:

[修饰符列表] 返回值类型 方法名 (形式参数列表){//方法体
}

例子:

public static void main(String[] args) {// Result of 'NewDemo.sum()' is ignored// 方法既然有返回值,那么建议去接收或者使用这个返回值int sumValue = sum(10, 20);System.out.println(sumValue);int num1 = 100;int num2 = 200;// 操作方法调用就是操作方法的返回值System.out.println(sum(num1, num2));System.out.println(sum(num1, num2) + 100);
}// 定义一个方法,来完成求两个int类型数值的和
public static int sum(int num1, int num2) {return num1 + num2;
}

然后就是概念向的:

  1. 我们把[修饰符列表] 返回值类型 方法名 (形式参数列表)称作方法头,或者方法的声明
  2. 我们把方法名 (形式参数列表)称作方法的签名,而对于方法的签名来说,在一个类当中必须是唯一的。

其他的细节:

  1. Java中的一个类中的方法,是平行的关系,位置不同,不会影响方法调用的结果
  2. 同类中的static和static修饰的方法之间可以互相调用,直接用方法名调用即可

以及和C++当中一样实参与返回值实际上会进行自动类型转换(由小变大)。

小技巧:

  1. 提取方法的快捷键 Ctrl+Alt+M.
    在这里插入图片描述
    在这里插入图片描述

  2. 快速生成方法:直接写方法调用,编译报错后使用 alt+enter 快速生成方法
    在这里插入图片描述
    在这里插入图片描述

方法的重载

语法要求:
一个类中的多个方法,可以具有相同的方法名,但是它们的形参列表必须不同。

形参列表不同意味着:

  1. 形参数量不同
  2. 形参数量相同时,形参的数据类型不同
  3. 形参数量和数据类型都相同时,形参的数据类型的顺序不同

而具体使用什么函数则和C++一样满足的是就近原则,但此时就有人会这么做:

// 方法1
public static void test(int a,double b){}
// 方法2
public static void test(double a,int b){}

调用:

test(10, 10);

那么此时调用的是啥呢?

显然不好确定,无论是1还是2都需要类型转换才能匹配,既然都转换,并且都是int—>double,那么到底谁"近"呢?实际上这个方法的调用,是一个模糊的调用,会编译报错。这一点在开发中,多个方法组成方法重载时,要格外注意。

Junit单元测试初识

Junit就是用来做单元测试的,而这一部分存在于编译器当中,也属于第三方库,导入过程就需要alt + Enter进行操作

通俗点说,junit可以实现在一个类中实现多个main方法的效果

Junit的使用:

  1. 首先要创建一个类,而且这个类最好不要叫Test
  2. 然后在这个类的类体中,方法外面,写注解@Test
    注解: annotation,它是一种和class同等级别的数据类型,是一种引用数据类型
    "@Test"当中,其中Test是这个注解的名字,相当于类名
    "@"加上这个注解名,表示创建这个注解的对象(这是一种比较独特的创建对象方式)
    "@Test"注解是Junit的一个基本注解,它表示创建一个测试方法,这个测试方法类似于main方法
  3. 直接写注解肯定会报错,原因和直接在代码中写Scanner类似,是因为缺少导包的步骤,需要导包,但是注解@Test和Scanner导包还有所不同,Scanner是JDK中原本就存在的类,可以直接导包,注解@Test不是JDK中已经存在的注解,而是第三方开发出来的工具当中的注解。 这时需要一个将三方工具包导入当前工程的过程,称之为"导入依赖" 正常情况下,依赖导入需要通过依赖管理工具(maven)去导入,或者手动下载,手动导入,但是Junit这个第三方依赖很特殊,它的依赖包已经存在于IDEA本地文件当中,可以直接通过"alt+回车"导入依赖,并完成导包。
  4. 写完注解后,还需要写测试方法,格式如下:
 public void 方法名(){// 方法体}

注意事项:

  1. Junit单元测试的格式上:
    1. public void 是固定格式,不可修改,修改会报错。
    2. 并且测试方法正常情况下,不能添加形参,必须是空参方法。
  2. 做单元测试的类中,不要写main方法,没有太大意义。

具体的导入依赖的操作:
在对应标红色的位置
在这里插入图片描述
我这边由于自己已经导入了,所以就不再次进行尝试了,这边就讲个操作方式。

全限定类名

为什么Junit的使用最好不要在一个Test类中呢?

因为由于Junit的测试方法需要使用注解@Test,其中Test是注解的名字,相当于类名,而注解和类是同等级别的数据类型,所以如果直接在Test类中,写注解@Test,会优先选择使用自身类Test作为一个注解,但是Test类本身就不是注解,而且也不是我们想要的那个注解Test,这肯定是不可行的!这也是一种就近原则.

如何解决?如果非要在Test类中使用注解Test咋办呢?
因为我要使用的注解Test是org.junit包下的Test注解,而不是自身的类Test,所以在写注解时,就不能简单写注解的名字了,要带上包名,由于在Java中,同包下绝不可能存在同名类(注解),所以包名+类名(注解名)是可以唯一的确定一个类(注解)的,我们把直接写"类名"的形式,称之为"简单类名",而把"包名+类名"的形式,称之为"全限定类名",全限定类名的作用是唯一确定一个类 。

例子:

public class Test {// 这里不能使用简单类名,要使用全限定类名@org.junit.Testpublic void test() {System.out.println("hello world!");}
}

Debug 小技巧

  1. 如果想要进入查看源代码(类或者方法等)时,可以按快捷键: CTRL + 左键。
  2. 查看代码时:从A—> B —> C —> D,我把D看完了,想继续看C的代码,怎么办呢?使用快捷键 ctrl + alt + 左右键 回到上一次鼠标的位置(左键) 去到下一次鼠标的位置(右键)

数组

数组的基本概念

数组最显著的特征是支持随机访问

随机访问:指的是在访问某个元素时,无需经过其它元素,直接定位访问它

非随机访问:指的是访向某个元素时,需要先访问其它元素。

显然随机访问的效率很高,时间复杂度是常数级别的O(1)。

而数组的随机访问实现方式是:根据数组的首地址和下标,通过寻址公式直接计算出对应的内存地址,最终找出数据。要想使用这种方式实现随机访问,显然数组对数据结构和数组中的元素都是有要求的:

  1. 存储结构必须是连续的(有序),这样才能连续计算。
  2. 存储的元素必须数据类型相同,这样每个存储单元的地址偏移量都是相同的。

综上,数组是用一段连续的内存空间,来存储一组具有相同类型的数据的结构。

数组的基本使用

数组的声明

要想使用数组,首先要声明(declaration)数组,类似于变量的声明。

声明数组的两种语法格式:

  1. 格式一
数据类型[] 数组名;
  1. 格式二
数据类型 数组名[];
  1. 第一种格式具有更好的可读性,可以直观的看到这个数组是一个什么数据类型的数组。
  2. 第二种格式,是Java沿袭自C语言的一种声明方式。我们都知道,早期很多Java开发者都是C转过来的,所以很多开发者在开发Java时,一时改不了使用习惯。我们在Java早期的源代码中,可以发现很多格式二的使用案例。Java为了代码的兼容性考虑,不太可能会取消这一声明格式。(但是像C#这种和Java同源的设计语言,已经取消了数组的声明格式二)
  3. 但是,我们毕竟不是开发者,而几乎所以Java规范中都禁止使用格式二定义数组。规范的Java代码应该永远采用格式一。

数组的初始化

  1. 静态初始化
    由程序员显式的,指定数组中每个元素的初始值,数组的长度由系统决定(实际上也是由程序员给出的)
// 和数组的声明写在一起,语法格式就是:
数据类型[] 数组名 = new 数据类型[]{元素1,元素2,元素3...};// 静态初始化有简写的形式,可以省略new关键字,但省略使用必须和声明一起写,而带new的,实际上可以自己放在外面进行使用,自己本身就已经是一个数组了。
数据类型[] 数组名 = {元素1,元素2,元素3...};
  1. 动态初始化
    动态初始化指的是:程序员只是指定数组的长度,数组中每个元素的初始值由系统(实际上是JVM)决定。
数据类型[] 数组名 = new 数据类型[数组长度];

注意事项:

  1. 动态初始化没有给出具体元素的赋值,但仍然能够初始化完成,这是因为数组中的元素具有默认值。
  2. 数组的长度必须是一个int范围内的非负数。
  3. 动态初始化数组相对更灵活,是更常用的方式。
  4. 数组的初始化一旦完成,它的长度就不能改变了!一般来说都会使用集合类中ArrayList(ArrayList底层实现仍然是数组,它有下标,能够随机访问,具有数组的优点,但是它没有长度的限制。它是通过将元素放入更长的数组中实现扩容的。这点我们后面会学习。)

还有就是小的tips:

int a = 10;
int[] arr = new int[3];// 直接输出数组名/*首先arr是数组名,同时它更是一个局部变量我们可以输出数组名,看一下这个局部变量中到底存放了什么输出的结果是:[I@6d6f6e28解释:[ 一个左中括号表示这是一个一维数组。([[就表示二维数组)I 表示这个一维数组是int类型的@后面跟的是一个十六进制的数,这个十六进制的数表示的是地址所以arr中存储的是地址
*/
System.out.println(arr);

输出:

[I@6d6f6e28

JVM内存模型

在这里插入图片描述

JVM的五大区域:

  1. JVM栈(以后简称栈,stack):描述的是Java的(普通)方法执行时的所占内存的内存模型。程序运行时调用方法的代价是:方法中有局部变量需要开辟空间存储,方法的执行过程会产生中间变量,方法执行完毕还需要存储返回地址等等。JVM栈正是Java的(普通)方法执行时所占用的内存空间,局部变量会直接存储在栈帧中。

    于是,方法的执行流程,在JVM内存中,就变成下面这样:

    1. 每当Java程序执行一个方法,都会在栈上分配一块只属于该方法的内存区域,称之为栈帧
    2. 每当Java程序执行一个方法,都会将一个存储该方法信息的栈帧压入栈中,称之为方法进栈
    3. 方法进栈的同时局部变量开辟内存空间存储值,局部变量生效。
    4. 当方法执行完毕后,该方法的栈帧随之销毁,称之为方法的出栈
    5. 方法栈帧被销毁的同时,局部变量也被销毁,局部变量失效。

    注:栈中只有处于栈顶的栈帧才会生效,表示正在执行的方法。称之为当前栈帧,当前方法。

  2. 堆(heap):堆是JVM内存中最大的一块,new出来的东西(称之为对象或者实例)都在堆上。所以new关键字的语义就是:在堆上开辟一片空间给相应的对象。而这片空间(对象)是有内存地址的,这个内存地址是留给外界访问用的。

    注:引用数据用比较运算符比较的地址就是这个地址,即比较对象的内存地址。

  3. 方法区(method area):面向对象详细讲。

  4. 本地方法栈:和JVM栈类似,区别是本地方法栈是给本地(native)方法使用的,而不是普通方法。

  5. 程序计数器:JVM执行代码解释执行的,即是一行一行执行字节码的,程序计数器用来记录当前执行的行数。

很明显,在JVM内存模型中,相对比较重要的,和程序的执行联系更紧密的是:堆和JVM栈。堆内存用来存储对象,由于Java是面向对象语言,Java面向对象程序中将会有非常多的对象,所以 堆内存主要决定了Java程序的数据如何存储的问题。而JVM栈用来表示方法的执行流程, 它决定了程序如何执行,或者说如何处理数据。

或者举个例子,当我们在写了一个函数:

public static void test()
{int a = 1;double[] b = new Double[]{0.1,0.2,0.3};String[] arr3 = {"hello", "abc", "666"};
}

对于上面的东西,我们进行分析,首先明白test函数是放在栈区的,然后栈区当中的这个函数内部又会存储局部变量a 的值,以及b的引用(由于b是数组属于引用数据类型,所以实际上存储了一个b的地址,引用),然后这些0.1啊什么的是存储在堆区当中,相同的arr3这些也是一样的arr3也是把引用存在栈区当中,当函数的具体调用的时候,会拿着这个引用(地址)来去堆区拿数据来使用。

而需要注意的是,我们讨论中,在堆区创建的数据(也就是在main下创建的数据)是有默认值的,比如我们可以直接在main下int[] a = new Double[3],这让内部的值都为0,但是在局部变量当中这是没有默认值的 ,这需要十分的注意

什么是引用数据类型

引用数据类型是Java的两大数据类型之一,通过数组初始化的内存分配过程来一窥引用数据类型的特点。

引用数据类型的创建分为两部分:

  1. 首先是在栈上分配一片空间给引用数据类型的引用,简称引用,它是一个局部变量,直接存储在栈帧中。
  2. 在堆上开辟一片空间,用于存放引用数据类型的实际信息,称之为对象或者实例

虽然有两个部分,但对象才是引用数据类型的实质,栈上的引用通过存储对象的地址,指向了堆上对象,这样就可以通过引用间接访问堆上的对象。总结来说就是:对象是实质,但我们不能直接访问堆上的对象,而是通过栈上的引用间接访问。

基本数据类型和引用数据类型的区别

基本数据类型的变量必然都是局部变量,你可能会疑惑,数组的元素也可以是基本数据类型,那它们不是局部变量啊。实际上我们不应该这么去思考,数组中的元素其实已经是(数组)对象的一部分了,它不应该单独拎出来看。所以它们的区别在于:

  1. 存储位置(本质区别)
    1. 基本数据类型不存在引用的概念,数据都是直接存储在栈上的栈帧里;
    2. 引用数据类型在栈帧中存储引用,引用作为一个局部变量,存储的只是该引用类型在堆上对象的内存地址。 存储在堆上的对象存储具体信息,才是引用数据类型的实质。引申出,打印变量名区别:
      1. 基本数据类型,打印变量名就是该变量具体的数值
      2. 引用数据类型,没有办法直接访问对象,打印变量名(引用)会显示该引用存储的堆上的对象的内存地址。

堆和栈中内容的区别

我们从以下三个角度来分析这个问题:

  1. 从存储的类型来看:
    1. 堆上存储的是new出来的东西,是引用数据类型的实质——对象。
    2. 栈上存储的是局部变量(基本数据类型和引用类型的引用)
  2. 从默认值来看:
    1. 堆上的变量具有默认值

      1. 整形(byte、short、int、long)默认值为0
      2. 浮点类型(float、double)默认值为0.0
      3. 字符类型(char)默认值是’\u0000’ 表示编码值为0的字符,一个绝对空字符。
      4. 布尔类型(boolean)默认值是false
      5. 引用数据类型默认值是null ,null既不是对象也不是任何一种数据类型,它仅是一个特殊的值,任何引用数据类型的引用都可以指向null,指向null并不意味着没有初始化,可以认为引用指向了虚无,反正没有指向任何一个对象。
      6. 对象才是引用数据类型的实质,没有指向对象的引用实际上没有任何意义,指向null的引用是无法正常使用的
      7. 基本数据类型不能等于null
    2. 栈上的局部变量没有默认值,声明局部变量后必须显式的初始化,否则无法使用。

  3. 生命周期来看:
    1. 堆上的对象使用完毕后,随着方法的出栈,对象的引用就会被销毁。这个时候对象就没有引用指向它,而是孤零零的单独存在于堆上,这种对象意味着我们就无法再次使用它了,这种对象没有意义了。在Java中,我们把这种对象称之为垃圾或者垃圾对象,它们会等待垃圾回收器进行内存回收。
    2. 关于Java的垃圾回收机制(Garbage Collection简称GC):
      堆上的对象变成垃圾后,并不是立刻就会被回收,而是需要GC通过一系列的算法来决定它是否被回收。Java的GC机制是全自动的,程序员几乎无法干涉和主动回收垃圾。这一方面为Java程序员的开发节省了大量的精力(无需花费大量精力来管理堆内存),相比于C++的全手动回收垃圾对象,Java在GC机制上的创新是Java能够如此流行的重要原因之一。但另一方面,一旦GC这种机制出现问题,对Java而言将会是非常难以解决的问题。垃圾回收是Java和C++之间的一道围墙,墙外的人想进来,墙内的人却想出去。
    3. 栈上的局部变量的生命周期和栈帧保持一致。方法栈帧进栈后,局部变量开辟空间生效了,方法出栈后,局部变量就被销毁了。

数组异常

在数组当中比较常见的异常名称:

  1. 数组下标越界异常:ArrayIndexOutOfBoundsException
  2. 空指针异常:NullPointerException

长度为0的数组 和 数组是null

对于下面的需要进行相关的理解:

  1. 数组未初始化: 这个数组完全是不可用的,没有初始化的数组毫无意义,一旦使用会编译报错。
  2. 数组长度为0和数组为null都是可以使用的,可以认为是经过初始化的,但它们都不是正常数组:
    1. 长度为0的数组,只在内存中存在结构但没有存储单元,不能存储任何数据。它的操作中:
      • 直接打印数组名可以获取数组对象的地址。
      • 不能访问任何数组下标,否则都会抛出数组下标越界异常。
      • 输出数组的长度为0
    2. 数组为null,表示数组的引用指向了null,数组(对象)无法进行任何操作。
      • 直接打印数组名得到一个null字符串。
      • 不能访问任何数组下标,也不能打印数组长度,都会报空指针异常。

一个长度为0的数组的经典用途:

假如方法的返回值是一个数组,而又有无返回数据的需求,这个时候普遍有两种做法 :

  1. 返回一个长度为0的数组
  2. 返回一个为null的数组

以上两种方式皆可,但是建议优先选装返回长度为0的数组,避免空指针异常

遍历数组多种操作

Arrays.toString(数组)

数组遍历并输出值,是非常常见的操作。所以如果你仅仅是想看一下数组里的元素长啥样,完全不需要自己手写实现。而是直接使用下面的方式

范围for 或者叫 增强for操作:

for(数据类型 变量名 : 要遍历的数组或者集合){System.out.println(变量名);
}

这边补充一下,可以直接快捷键,输入iter,直接输出,而java当中的引用采用的都是值引用,不论是函数传参还是什么别的方法,采用的都是值引用。

可变参数

这个… 实际上就是数组,我们实际上按照数组来进行使用即可。 区别在于: 原先的数组是程序员自己创建的,可变参数的数组是编译器帮助创建的,像这种实现原理不变,但是简化了程序员的操作,让代码更灵活的语言特性就是可变参数。

public static int sum(int... var) {// 遍历数组求和int sumValue = 0;for (int ele : var) {sumValue += ele;}return sumValue;}

注意事项:

  1. 调用方法时,如果有一个固定参数的方法匹配的同时,也可以与可变参数的方法匹配,则选择固定参数的方法。
  2. 调用方法时,如果出现两个可变参数的方法都能匹配,则报错,这两个方法都无法调用了
  3. 一个方法只能有一个可变长参数,并且这个可变长参数必须是该方法的最后一个参数。
  4. 可变参数的书写格式,推荐"数据类型… 变量名"
  5. 带有可变参数的方法本质上是带有数组形参的一个方法,所以它也是可以和其他方法构成方法重载的!
    1. 尤其是当可变参数和固定参数的方法组成方法重载时,优先匹配固定参数的方法
    2. 当两个可变参数的方法构成方法重载时,要千万小心
    3. 很容易导致两个方法同时无法调用的情况

方法的传参问题

在java当中的所有的传参所传递的都是值传递,java当中没有引用传递这种说法,(值传递,传递值,或者说传递拷贝,引用传递,传递的是别名,也即自己的值的别名)

常见误解:
认为当传递数组的时候,是引用传递:

    public static void doubleElementValue(int[] arr) {for (int i = 0; i < arr.length; i++) {arr[i] *= 2;}}

当我们在main当中调用这个函数,传递一个arr给他,我们会发现arr内部的数据是会进行修改的,但此时我们把其当作是引用传递,这是很有问题的,事实上值传递,也即传递引用数据类型的地址,我们也能根据这个地址去改变堆中数据的大小的。

看下面的例子进行分辨:

	public static void swapReference(int[] arr1, int[] arr2) {int[] temp = arr1;arr1 = arr2;arr2 = temp;System.out.println("交换的方法中arr1"+Arrays.toString(arr1));System.out.println("交换的方法中arr2"+Arrays.toString(arr2));}

当调用这个函数的时候,我们会发现在main函数中arr1和arr2并没有发生变化,怎么理解呢?我们先假设如果是引用传递会发生什么?

  1. 当发生引用传递的时候:我们传入的是main当中数组的别名,也即两个对应的是同一个东西,所以最后在arr1和arr2会发生变化,arr1和arr2相互交换。
  2. 当发生值传递的时候:传入的是main的值,也即拷贝,所以我们交换的只是jvm栈当中的arr1和arr2,外面的并没有进行调换。

总结,由于java值传递的特性,可以得到以下结论:

  1. 不能修改基本数据类型的实参的值,也不能修改引用数据类型实参引用的取值,一个方法不能修改其它方法中局部变量的取值。一旦违反上述原则,那么局部变量就不"局部"了。
  2. 可以改变引用数据类型中对象里的数据(称之为改变对象的状态,改变对象的属性值)
  3. 换句话来说,就是没办法改变引用数据类型的值,但可以改变引用数据类型内部数据的值

二维数组

二维数组的基本概念

在这里插入图片描述
二维数组的实质,仍然是一维数组。二维数组的数据类型由其中一维数组的类型决定,只能存储相同类型的一维数组,但值得注意的是和c/c++不一样的是,这个一维数组的大小可以不同,也即二维数组中的每一个一维数组的大小可以不一样,所以java就不存在和c/c++一样的连续存储的情况,他的每一个一维数组的存储都是随机的。

二维数组的基本使用

二维数组的声明:

数据类型[][] 二维数组名;

二维数组的初始化有三种格式:

  1. 静态初始化
   二维数组名 = new 数据类型[][]{{元素1,元素2,元素..},{元素1..}...};//可以简写为下面格式数据类型[][] 二维数组名 = {{元素1,元素2,元素..},{元素1..}...};
  1. 动态初始化格式一
   二维数组名 = new 数据类型[m][n];

其中:

  1. m表示二维数组的长度,代表二维数组当中,一维数组的个数。
  2. n代表二维数组当中,每个一维数组的长度,即一维数组能存储元素的个数。

注:

  1. 动态初始化格式一创建的二维数组,里面的每个一维数组的长度都相同。
  2. 其中的每一个一维数组相当于动态初始化,里面的元素都具有默认值, 可以直接使用。内存图如下:

在这里插入图片描述

  1. 动态初始化格式二
   二维数组名 = new 数据类型[m][];

其中:
m表示二维数组的长度,代表二维数组当中,一维数组的个数。

注:
这种方式创建的二维数组,实际上只有默认值null,不能直接使用。内存图如下:

除了上述三种三种初始化方式,其它任何方式都是错误的,比如下面:

三种初始化方式中,静态初始化很固定地为元素赋值。动态初始化格式一创建完毕后,一维数组的长度都是一样的,但可以直接使用。而格式二直接使用会空指针异常,还需要手动初始化二维数组中的每一个一维数组,每个一维数组的长度可以自己给出,相对灵活。

二维数组的进阶操作

便利打印:

Arrays.deepToString(某个二维数组)

由于他的每一层的数组大小不一样,所以他的遍历有点不一样,理解一下,下面的即可:

import java.util.Arrays;public class Main {public static void main(String[] args) {int[][] a = {{1, 2, 3, 4}, {2, 3, 4}, {3, 4}};System.out.println(Arrays.deepToString(a));print(a);}private static void print(int[][] a) {System.out.print("[");for (int i = 0; i < a.length; i++) {for (int j = 0; j < a[i].length; j++) {if (j==0){System.out.print("["+a[i][j]+",");} else if (j==a[i].length-1) {System.out.print(a[i][j]+"],");}else {System.out.print(a[i][j]+",");}}}System.out.print("\b]");}
}

输出:

[[1, 2, 3, 4], [2, 3, 4], [3, 4]]
[[1,2,3,4],[2,3,4],[3,4]]

递归

递归注意事项

使用的递归的注意事项:

  1. 首先需要找到方法体中,自身调用自身的代码,这称之为递归体,没有递归体肯定不是递归。
  2. 光有递归体的递归肯定是不行的,方法只进栈不出栈一定会栈溢出,所以需要给出一个条件让方法结束调用出栈,这称之为"递归的出口", 对于一个正常的递归而言,递归的出口必不可少。
  3. 思考: 一个有递归的出口就一定不会出错吗?一定不会栈溢出?一定安全吗?不一定,递归即便有出口,也需要考虑递归的次数,称之为"递归的深度",递归的深度不能超过栈的空间大小,否则仍然会栈溢出。

总之: 递归是很危险的,使用递归之前一定要严格考虑递归的出口和递归的深度.递归虽然很好,但是能不用尽量不用

    public static int sum(int num) {// 递归需要出口,否则一定栈溢出if (num == 0) {return 0;}return num + sum(num - 1);}

异常

递归栈溢出错误:
递归是方法自我调用的过程,但是“只递不归”的套娃会导致程序崩溃,报错递归栈溢出错误(StackOverflowError),是一个错误(Error),是比Exception要更加的严重的错误。

产生栈溢出错误的原因在于:

  1. Java程序运行时,调用方法是有代价的:要占用栈(stack)中的内存空间
  2. 方法执行结束后,方法出栈,释放内存,所以一般情况下,栈内存不会溢出,始终够用
  3. 无限制的递归调用方法,会导致方法只进栈不出栈,很快栈内存空间就不够用了
  4. 这种情况就是"栈溢出错误",对程序而言是致命错误,程序必须停止执行。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/171348.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PTA NeuDs_数据库题目

二.单选题 1.数据库应用程序的编写是基于数据库三级模式中的。 A.模式 B.外模式 C.内模式 D.逻辑模式 用户应用程序根据外模式进行数据操作&#xff0c;通过外模式一模式映射&#xff0c;定义和建立某个外模式与模式间的对应关系 2.对创建数据库模式一类的数据库对象的授权…

Typescript基础面试题 | 02.精选 ts 面试题

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

智能优化算法应用:基于海鸥算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于海鸥算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于海鸥算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.海鸥算法4.实验参数设定5.算法结果6.参考文献7.MATLAB…

痤疮分级实验笔记-ResNet

组织数据集 方式1&#xff1a;根据txt文件分类的数据集组织形式&#xff08;放弃&#xff09; 你可以使用Python来读取txt文件中的训练集图片信息&#xff0c;并将这些图片从原始文件夹复制到目标文件夹中。 当程序无法找到标签对应的图片或者目标文件夹中已经存在同名图片时…

C语言编译过程再解析

多年以前,分析过编译过程,并写了一篇博客,现在对编译过程有了更广阔的认识,记录在此 编译过程 中的 链接与 编译 编译过程分为1. 预处理2. 编译3. 汇编4. 链接其中有 2个过程比较特殊,1. 编译2. 链接对于C程序来说,链接分为提前链接(静态链接)对应下图第1行运行时链接(动态链…

QGIS安装及简单使用

QGIS&#xff08;Quantum GIS&#xff09;是一个自由、开源的跨平台桌面地理信息系统&#xff08;GIS&#xff09;应用程序&#xff0c;它允许用户创建、编辑、查看、分析和发布地理空间数据和地图。 操作系统&#xff1a;Windows 10 QGIS版本&#xff1a;QGIS Desktop 3.28.…

【二叉树】oj题

在处理oj题之前我们需要先处理一下之前遗留的问题 在二叉树中寻找为x的节点 BTNode* BinaryTreeFind(BTNode* root, int x) {if (root NULL)return NULL;if (root->data x)return root;BTNode* ret1 BinaryTreeFind(root->left, x);BTNode* ret2 BinaryTreeFind(ro…

【华为OD】B\C卷真题:100%通过:找城市 C/C++实现

【华为OD】B\C卷真题&#xff1a;100%通过&#xff1a;找城市 C/C实现 题目描述&#xff1a; 一张地图上有n个城市&#xff0c;城市和城市之间有且只有一条道路相连&#xff1a;要么直接相连&#xff0c;要么通过其它城市中转相连&#xff08;可中转一次或多次&#xff09;。…

【计算机网络学习之路】日志和守护进程

文章目录 前言一. 日志介绍二. 简单日志1. 左字符串2. 右字符串 三. 守护进程1. ps -axj命令2. 会话扩展命令 3. 创建守护进程 结束语 前言 本系列文章是计算机网络学习的笔记&#xff0c;欢迎大佬们阅读&#xff0c;纠错&#xff0c;分享相关知识。希望可以与你共同进步。 本…

canvas扩展001:利用fabric绘制图形,可以平移,旋转,放缩

canvas实例应用100 专栏提供canvas的基础知识&#xff0c;高级动画&#xff0c;相关应用扩展等信息。 canvas作为html的一部分&#xff0c;是图像图标地图可视化的一个重要的基础&#xff0c;学好了canvas&#xff0c;在其他的一些应用上将会起到非常重要的帮助。 文章目录 示例…

串口通信基础知识介绍

一、串行通讯与并行通讯 在通信和计算机科学中&#xff0c;串行通信(Serial Communication)是一个通用概念&#xff0c;泛指所有的串行的通信协议&#xff0c;如RS232、RS422、RS485、USB、I2C、SPI等。 串行通讯是指仅用一根接收线和一根发送线就能将数据以位进行传输的一种…

JAVA时间常用操作工具类

小刘整理了JAVA中对时间的常用操作&#xff0c;封装了几种方法&#xff0c;简单方便&#xff0c;开箱即用。时间转字符串格式&#xff0c;字符串转时间&#xff0c;以及过去和未来的日期。除此之外&#xff0c;还新增了时间戳之差计算时分秒天的具体方案。 public static void …

如何使用nginx部署静态资源

Nginx可以作为静态web服务器来部署静态资源&#xff0c;这个静态资源是指在服务端真实存在&#xff0c;并且能够直接展示的一些文件数据&#xff0c;比如常见的静态资源有html页面、css文件、js文件、图片、视频、音频等资源相对于Tomcat服务器来说&#xff0c;Nginx处理静态资…

DGL在异构图上的GraphConv模块

回顾同构图GraphConv模块 首先回顾一下同构图中实现GraphConv的主要思路&#xff08;以GraphSAGE为例&#xff09;&#xff1a; 在初始化模块首先是获取源节点和目标节点的输入维度&#xff0c;同时获取输出的特征维度。根据SAGE论文提出的三种聚合操作&#xff0c;需要获取所…

蓝桥杯第四场双周赛(1~6)

1、水题 2、模拟题&#xff0c;写个函数即可 #define pb push_back #define x first #define y second #define int long long #define endl \n const LL maxn 4e057; const LL N 5e0510; const LL mod 1e097; const int inf 0x3f3f; const LL llinf 5e18;typedef pair…

vue3+ts 兄弟组件之间传值

父级&#xff1a; <template><div><!-- <A on-click"getFlag"></A><B :flag"Flag"></B> --><A></A><B></B></div> </template><script setup lang"ts"> i…

01、copilot+pycharm

之——free for student 目录 之——free for student 杂谈 正文 1.for student 2.pycharm 3.使用 杂谈 copilot是github推出的AI程序员&#xff0c;将chatgpt搬到了私人终端且无token限制&#xff0c;下面是使用方法。 GitHub Copilot 是由 GitHub 与 OpenAI 合作开发的…

2023年3月电子学会青少年软件编程 Python编程等级考试一级真题解析(判断题)

2023年3月Python编程等级考试一级真题解析 判断题(共10题,每题2分,共20分) 26、在Python编程中,print的功能是将print()小括号的内容输出到控制台,比如:在Python Shell中输入print(北京,你好)指令,小括号内容可以输出到控制台 答案:错 考点分析:考查python中print…

【【Linux编程介绍之关键配置和常用用法】】

Linux编程介绍之关键配置和常用用法 Hello World ! 我们所说的编写代码包括两部分&#xff1a;代码编写和编译&#xff0c;在Windows下可以使用Visual Studio来完成这两部&#xff0c;可以在 Visual Studio 下编写代码然后直接点击编译就可以了。但是在 Linux 下这两部分是分开…

2024年第十六届山东省职业院校技能大赛中职组 “网络安全”赛项竞赛正式卷任务书

2024年第十六届山东省职业院校技能大赛中职组 “网络安全”赛项竞赛正式卷任务书 2024年第十六届山东省职业院校技能大赛中职组 “网络安全”赛项竞赛正式卷A模块基础设施设置/安全加固&#xff08;200分&#xff09;A-1&#xff1a;登录安全加固&#xff08;Windows, Linux&am…