Java学习 day04_参数传递&二维数组&递归

7 值传递和引用传递

1. 基本概念

调用方法时,我们需要传递实参给方法,在方法内部会改变这些实参的值,那么方法执行完毕后

这些实参的值会怎么变呢?

首先介绍两种值传递的方式

  • 值传递(call by value),指的是调用方法时,传入的是实参的拷贝而不是实参本身
    • 这样做的结果是,方法改变的是实参拷贝的值,原本实参的值是不会变的
  • 引用传递(call by reference),指的是调用方法时,传入的是实参的变量地址
    • 注意,基本数据类型也是有地址的
    • 这样做的结果,方法改变的是实参本身的值,原来实参的值会跟着变化

Java到底是哪一种传递模式?又或者对基本数据类型是值传递,对引用类型是引用传递?

对于这个问题,首先要搞清楚一些概念

  • 引用数据类型和基本数据类型的区别
    • 基本数据类型,值就直接保存在栈上的变量中
    • 引用数据类型,栈上变量保存的是堆上对象的地址,也就是引用
  • 赋值运算符“=”的作用
    • 基本数据类型,赋值符号会直接覆盖原本的值,原先的值就没了
    • 引用数据类型,赋值符号会直接覆盖引用中原先保存的地址值,原先的地址被覆盖掉
      • 注意,原先引用地址所指向的对象不会发生变化(重点)

2. Java的值传递

实际上严格来说,Java只存在值传递,方法得到的是变量的拷贝,而不是变量的地址,因为如果在方法中改变传递过来的引用,并不会对main里的引用造成任何的影响。

  • 对于基本数据类型而言,方法调用过程如下

    • 在调用的方法的栈帧中初始化一个实参的副本
    • 对这个副本进行操作,原先main方法栈帧的变量的值不会发生变化
    • 并且这个方法执行完毕后,随着方法出栈,这个实参的副本也被销毁了
  • 对于引用数据类型而言,方法调用过程如下

    • 在调用的方法的栈帧中声明一个引用的拷贝,因为是拷贝,所以这个引用中存的内存地址和原先的引用一样

      • 结果就是,这个副本仍然指向了原先引用所指向的对象
    • 对这个引用的副本进行操作,原先main方法栈帧的引用不会发生变化,但由于指向的是同一对象

      • 结果就是,引用和引用拷贝指向的对象本身被改变了
    • 方法结束后,这个引用拷贝也被销毁了

3. 证明Java值传递

很多程序语言(尤其是C/C++)提供了两种传参方式,导致很多人也认为Java也是两种传参方式

这里举一个反例

  • 声明初始化两个数组

  • 提供一个交换数组的方法swap

    • public static void swapArray(int[] arr1,int[] arr2){
          int[] temp;
          temp = arr1;
          arr1 = arr2;
          arr2 = temp;
      }
      // 在main中,两个数组的值并没有发生交换,也就是说在swapArray中数组的地址虽然交换了,但是原本数组的地址并未发生交换
      

    结论:Java当中只存在值传递
    两数组交换地址过程

  • 方法的调用过程如下

    • 首先在调用方法的栈帧中声明两个数组的引用拷贝,仍指向原先的数组
    • 交换这两个拷贝的值,实际上就是交换它们指向的数组,这里两个拷贝完成了数组交换
      • 原先的数组引用不受影响
    • 方法结束后,这两个引用拷贝被销毁

4. 总结

总结:Java方法对方法参数能做什么?

  • 不能修改基本数据类型的参数的值(局部变量)
    • 实际开发中也不会真的有让你把基本数据类型通过方法去改变
  • 可以改变引用数据类型中对象里的数据(称之为改变对象的状态,改变对象的属性值)
  • 无法直接改变参数引用所指向的地址(引用指向的对象)

5. 启示

写在最后

不要纠结概念,Java到底是什么传值方式?即便是到了今天,仍然会有人在网络上争吵

有些人会说:Java方法传值的本质是值传递不错,但是对于引用类型,确实可以改变对象里的数据

应该叫引用传递。所以Java是值传递和引用传递共存,表面有引用传递,本质是值传递

  • 不要和喷子和杠精对线,就告诉他们他们是正确的
  • 理解本质,关注原理
  • 概念是死的,思想是活跃的

8 Java可变参数

Java1.5增加了新特性,可变参数

适用于参数个数不确定,类型确定的情况,Java会自动把可变参数当作数组处理

1. 基本使用

怎么使用可变参数?

  • 可变参数用于形参列表中,并且只能出现在形参列表的最后

  • 语法

    • [修饰符列表] 返回值类型 方法名 (形式参数列表,数据类型... 变量名){
      	//方法体
      }
      
  • 可变参数的三个点位于数据类型和变量名之间,前后有无空格都可以

    • 最好是前无空格,后有空格
  • 调用可变参数的方法时,编译器为该可变参数隐含创建一个数组,在方法体中以数组的形式访问可变参数

  • 可变参数对本质是数组(参考代码),可变参数对原理是jvm底层把输入的不限定个数的参数,封装成数组

2. 注意事项

注意事项

  • 调用方法时,如果有一个固定参数的方法匹配的同时,也可以与可变参数的方法匹配,则选择固定参数的方法
  • 调用方法时,如果出现两个可变参数的方法都能匹配,则报错,这两个方法都无法调用了
    • 严格避免带有可变参数的方法发生重载,否则很容易导致方法永远无法调用
  • 一个方法只能有一个可变长参数,并且这个可变长参数必须是该方法的最后一个参数

①二维数组的引入

1. 二维数组的引入

假设我们要建立一张表用来存储班级学生的成绩,对于这种表格形式的数据,可以使用二维数组来存储

一班学生成绩二班学生成绩三班学生成绩四班同学成绩
90867890
77867654
86678760

2. 理解二维数组

怎么理解二(多)维数组?

  • 一维数组可以看成上面表格中的一列数据
  • 二维数组可以看成上面的表格数据
    • 实际上表格是由很多列数据组成的
    • 二维数组就是由很多一维数组组成的
  • 二维数组(two-dimensional-array),就是某个一维数组作为另一个数组的元素而存在了
    -** 二维数组是一维数组的数组**
    • 本质上来说,从内存角度看,并没有多维数组的概念
      在这里插入图片描述
      经过赋值之后的二维数组
      被赋默认初始值的二维数组

②二维数组的使用

二维数组的理解

  • 二维数组是一维数组的数组
  • 内存中并没有存在真正的二维数组,只不过是一维数组中装了一维数组

1. 二维数组的声明和初始化

要想使用二维数组,首先要进行声明和初始化

  • 一些奇怪的面试题目

    • int a,b; 
      int[] m,n[];  //相当于定义了一个m[], 和一个n[][]
      

1.2 初始化(initialization)

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

    • 静态初始化

    • 动态初始化格式一

      • 二维数组名 = new 数据类型[m][n];
        
      • 通过这种方式创建的二维数组,里面的每个一维数组的长度都相同

    • 动态初始化格式二

      • 二维数组名 = new 数据类型[m][];
        
      • m代表二维数组当中,一维数组的个数

    • 没有直接给出一维数组的长度,可以动态给出

    • 该形式定义的二维数组仍不能使用,还需要手动初始化二维数组中的每一个一维数组

          double[][] arr5 = new double[3][];
          arr5[0] = new double[5];
          arr5[1] = new double[9];
          arr5[2] = new double[]{1, 2, 3};
          ```
      
      
  • 错误的格式

    • 除了三种正确的,都是错误的,例如下面

      • // 二维数组名 = new 数据类型[][n]; 错误❌
        // 二维数组名 = new 数据类型[m][n]{{元素1,元素2,元素..},{元素1..}...}; 错误❌
        

2. 二维数组的访问

二维数组访问元素和一维数组并无实质不同,只不过是一维数组套一维数组

2.1 数组长度

  • 获取二维数组的长度

    • 语法

      • 二维数组名.length;
        
    • 二维数组的长度实质上是其中一维数组的个数

    • 二维数组的长度和动态初始化中的m数值相等

  • 获取二维数组中一维数组的长度

    • 语法

      • 二维数组名[m].length;
        
      • 只有二维数组中的一维数组完成初始化,才能够使用这种形式

        • 不然会空指针异常

2.2 访问二维数组中的某个一维数组

  • 语法

    • 二维数组名[m];
      
    • 只有二维数组中的一维数组完成初始化,才能够使用这种形式

    • m必须是一个存在的下标,不然会数组越界

    • 获取一维数组后,可以进行各种一维数组的操作

2.3 获取指定位置的元素

  • 语法

    • 二维数组名[m][n];
      

③二维数组的操作

3.1 数组的遍历

遍历二维数组(traverse)

    public static void traverseTwoDArray2(int[][] arr) {
        System.out.print("[");
        //一维数组使用for循环,二维数组使用双层for循环去遍历
        //外层for应该遍历二维数组中的一维数组
        for (int i = 0; i < arr.length; i++) {
            //内层for应该遍历每一个一维数组
            for (int j = 0; j < arr[i].length; j++) {
                //这里就是具体元素的值
                if (j == 0 && j != arr[i].length - 1) {
                    //如果是每个数组中的开头元素且不是最后一个元素
                    System.out.print("[" + arr[i][j] + ", ");
                } else if (j == 0) {
                    //是开头也是最后的元素
                    System.out.print("[" + arr[i][j] + "], ");
                } else if ((j == arr[i].length - 1 && i != arr.length - 1)) {
                    //如果是每个一维数组的末尾元素,除最后一个
                    System.out.print(arr[i][j] + "], ");
                } else if ((i == arr.length - 1) && (j == arr[arr.length - 1].length - 1)) {
                    //如果是整个二维数组的最后一个元素
                    System.out.print(arr[i][j] + "]");
                } else {
                    //普通的在中间的元素
                    System.out.print(arr[i][j] + " ,");
                }
            }
        }
        System.out.println("]");
    }

数组工具类中也有这样的功能

Arrays.deepToString(数组)

3.2 杨辉三角

打印杨辉三角

南宋时期数学家杨辉,根据北宋时期的数学家贾宪的作品(现已遗失),发现的一种几何排列规律

被称之为“杨辉三角”,“贾宪三角”,“帕斯卡三角” 16xx 明末清初

1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
...
  • 规律就是
    • 每一行的元素和行数一样,即行数等于列数
    • 且第一行,第二行固定为1
    • 从第三行开始
      • 每一行的开头和结尾都是1
      • 其余位置的值,是上一行同列元素和同列-1列元素之和
  • 于是
    • 声明动态初始化一个二维数组,再用for循环分配每个一维数组的长度
      • 其中每个一维数组的长度等于该数组在二维数组中的下标+1
    • 为每一个一维数组的首位元素赋值1
    • 为其他元素赋值
      • 需要注意从第三行开始,并且每一列的两边不用赋值(因为已经赋值过了)
      • 元素值 = 上一行同列的元素值 + 上一行上一列的元素值
    • 遍历
		//1.声明一个二维数组,长度为10
		int[][] arr = new int[10][];	
	
		//2.为二维数组的每一个元素分配内存空间:第一个元素的长度为1,第二个元素的长度为2
		for(int i=0;i<arr.length;i++){
			arr[i] = new int[i+1];	
		}
 
		//3.为二维数组的第一个元素和最后一个元素赋值为1
		for(int i=0;i<arr.length;i++){
			arr[i][0] = 1;
			arr[i][arr[i].length-1] = 1   //注意:这里是arr[i].length-1 !!!
		}
		
		//4.为二维数组的其他元素赋值
		for(int i=2;i<arr.length;i++){
			for(int j=1;j<i;j++){
				arr[i][j] = arr[i-1][j-1]+arr[i-1][j];
			}
		}
 
		//5.打印
		for (int i = 0; i < arr.length; i++) { 
			for (int j = 0; j < arr[i].length; j++) {
				System.out.print(arr[i][j] + "\t");
			}
			System.out.println("");
		}
		
	}


递归

概念上不做过多解释,不难理解

汉诺塔问题

public class Hanoi {
    public static void main(String[] args) {
        System.out.println(count(3));
    }
    public static int count(int n){
        if(n == 1) return 1;
        return count(n-1) + 1 + count(n-1);
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值