java学习笔记-第七章:面向对象编程(基础部分)

第七章:面向对象编程(基础部分)

总体内容

在这里插入图片描述

类与对象

引出类与对象

  1. 问题:
    在这里插入图片描述
  2. 以往的知识解决不了
    在这里插入图片描述
    在这里插入图片描述
  3. 类与对象体现为:对象的属性和行为
    在这里插入图片描述

类与对象概述

类与对象的关系示意图

在这里插入图片描述
类与对象的区别和联系
在这里插入图片描述
类到对象的三种说法意思是一致的
在这里插入图片描述

属性概念及其细节

  1. 概念
    在这里插入图片描述
  2. 细节
    2.1 注意:new Cat()真正开辟一个内存空间(真正的对象),cat1只是一个对象名(对象引用)
    在这里插入图片描述

类与对象快速入门案例

  1. 问题:
    在这里插入图片描述
  2. 实现步骤
    2.1 创建一个猫类
    在这里插入图片描述
    2.2 实例化两个猫对象
    注意:new Cat()真正开辟一个内存空间(真正的对象),cat1只是一个对象名(对象引用)
    在这里插入图片描述
    2.3 访问猫对象的属性并打印
    在这里插入图片描述

对象内存布局

在这里插入图片描述

类与对象内存分配机制

  1. 问题在这里插入图片描述
  2. 内存分配机制
    2.1 创建对象语句
    在这里插入图片描述
    对象创建流程文字描述
    在这里插入图片描述
    2.2 赋值语句
    在这里插入图片描述
    2.3 Person p2 = p1(就好像一个人有两个名字,但是它们指向的是同一个人)
    在这里插入图片描述
    2.4 访问对象的属性
    根据对象指向的地址,找到该属性的地址
    在这里插入图片描述
引申:java内存的结构分析

在这里插入图片描述

练习

在这里插入图片描述

创建对象的两种方法及访问属性

  1. 先声明再创建
    在这里插入图片描述
    在内存中cat先指向一个空地址,等到new的时候在堆中开辟一块空间,把这块地址赋给cat对象
    在这里插入图片描述

  2. 直接创建
    在这里插入图片描述

成员方法(简称方法)

成员方法快速入门(4个小例子)

  1. 输出一句话
    1.1 在类中写方法
    在这里插入图片描述
    1.2 调用方法
    先创建一个对象,然后调用这个方法
    在这里插入图片描述

  2. 在方法体内计算得到一个结果
    2.1 代码
    2. 有返回值(计算得到一个结果)
    2.2 调用方法
    在这里插入图片描述

  3. 一个形参(接收一个数,计算得到结果)
    3.1 注意形参:类型 形参名,调用时写实参
    在这里插入图片描述
    3.2 调用方法
    在这里插入图片描述

  4. 两个形参以及返回值(计算两个数的和)
    4.1 代码
    在这里插入图片描述
    4.2 调用方法(用一个变量接收返回值)
    在这里插入图片描述

方法调用机制

在这里插入图片描述

使用成员方法的好处

  1. 传统的方法内容冗余而且修改方法的时候需要每一个都进行修改
  2. 成员方法:复用性好,啥时需要啥时调用,而且方法修改时只需要修改类中的成员方法即可,也可以把细节封装起来,供其它成员调用
    在这里插入图片描述

方法的定义和使用时的细节

  1. 定义
    在这里插入图片描述
  2. 使用的细节
    2.1 细节第一部分
    在这里插入图片描述
    解读细节:一个方法最多有一个返回值(如果有多个返回结果的解决方法–>返回类型定义成数组)
    在这里插入图片描述
    调用时,需要用相应的数据类型去接收这个返回值,如这里返回类型是int[],则需要定义一个int[]的数组接收,用数组[下标]来访问返回的多个元素
    在这里插入图片描述
    2.2 细节第二部分
    在这里插入图片描述
    2.3 细节第三部分
    在这里插入图片描述
    解读细节:方法调用:同一类中直接调用
    在这里插入图片描述
    解读细节:方法调用:跨类中的方法A调用B类中的方法,需要通过B的对象名调用 (例如在main类中调用其它类的方法)
    在这里插入图片描述
    深入理解方法调用的内存布局
    在这里插入图片描述

方法练习题(2道)

  1. 编写类,类里有一个方法:判断一个数是奇数还是偶数,返回boolean(注意:这里返回值的书写有3种方法以及接收布尔类型的2种方法
    1.1 返回值的书写的3种方法
    ① 最普通的方法if判断
class Cat{
    public boolean jio(int num){
        if(num % 2 == 0){
            return true;
        }else{
            return false;
        }
    } 
}

② 三元运算符

class Cat{
    public boolean jio(int num){
        return num % 2 == 0 ? true ; false;
    } 
}

③ (推荐)直接返回

class Cat{
    public boolean jio(int num){
        return num % 2 == 0;
    } 
}

1.2 接收布尔类型的2种方法
① 普通方法:定义一个变量接收返回的布尔值,然后用这个布尔值去做if判断

public class Object01 {
    public static void main(String[]args) {
        Cat cat = new Cat();
        //定义一个变量接收返回的布尔值,然后用这个布尔值去做if判断
        boolean isJiO = cat.jio(1);
        if(isJiO){
             System.out.println(inNum+"是一个偶数");
        }else{
             System.out.println(inNum+"是一个奇数");
        }
    }
}

① 推荐方法:直接用返回值在if中判断(因为返回值就是一个布尔值)

public class Object01 {
    public static void main(String[]args) {
        Cat cat = new Cat();
        //直接用返回值在if中判断(因为返回值就是一个布尔值)
        if(cat.jio(1)){
            System.out.println(inNum+"是一个偶数");
        }else{
             System.out.println(inNum+"是一个奇数");
        }
    }
}
  1. 根据输入的行,列及字符打印对应行数,列数和字符(简单,直接打印不需要返回值)
    2.1 在自定义类中编写方法:
    在这里插入图片描述
    2.2 在main类中调用方法:
    在这里插入图片描述
    2.3 输出结果
    在这里插入图片描述

※方法传参机制(非常重要)

基本数据类型传参机制(值传递)

注:parameter:参数

  1. 问题(答案:执行完成员方法后再次输出这两个变量的值–>10,20(没有被成员方法所改变)
    在这里插入图片描述
  2. 分析的内存图(重要)

2.1 结论
基本数据类型:实参传递给形参的是值,所以形参的任何改变不会影响实参
在这里插入图片描述
2.2 分析的内存图

解读重点理解:
① main方法和成员方法是两个独立的栈空间
② main中的a和b是main方法空间中存储的基本数据类型
③ 成员方法中的a和b是成员方法空间中存储的基本数据类型
④ 基本数据类型实参赋给形参:是main中实参的数值赋给了成员方法中形参(注意基本数据类型赋的是数值)
⑤ 调用成员方法时程序会在成员方法中找a,b变量,再把a和b的数值进行交换(实参将数值传给形参,所以成员方法中数值的变化不影响实参)
⑥ 在调用结束后,再次输出a和b,程序会在main中寻找a,b变量,然后输出,结果为10,20
在这里插入图片描述

引用数据类型传参机制(地址传递)

  1. 问题
    在这里插入图片描述
  2. 分析的内存图(重要)
    2.1 结论
    引用数据类型:实参传递给形参的值是地址,所以形参的改变会影响实参
    在这里插入图片描述
    2.2 分析的内存图

解析步骤
① 创建对象b:b指向堆中的一个地址
② 创建数组arr:arr指向堆中的一个地址
③ b对象调用成员方法,将arr作为实参传递给形参(引用数据类型:将arr的地址赋给形参–>实参和形参指向堆中同一地址,所以形参的改变会影响实参)
④ 执行成员方法,改变形参中数组的值(因为实参形参指向堆中同一地址,所以改变形参也就改变了实参)
⑤ 成员方法返回数组后,继续执行main方法下面的代码,找到main方法中的数组进行遍历
⑥ 传入对象也同理:创建对象p:p指向堆中的一个地址,该地址中存放了对象中的属性,将该对象作为实参传递给形参时,会把该对象指向的地址赋给形参,这样实参和形参指向堆中同一地址,在成员方法中改变形参也就改变了main方法中的实参

传入数组
在这里插入图片描述
传入对象
在这里插入图片描述

引用数据类型的两个小例子

  1. 在成员方法里修改对象p = null,main方法输出是10
    1.1 实参把对象指向的地址赋给形参,在成员方法中把形参p赋为null,说明形参指向的地址变成了空(null),而实参指向的仍为原先指向的地址
    1.2 也就是:
    形参指向地址 = 实参指向地址
    形参指向地址 = null
    此时没有改变实参指向地址

    在main中输出对象时,会在main栈找到实参对象,并输出,所以结果是10
    在这里插入图片描述
  2. 在成员方法里修改对象p = new Person(),main方法输出是10
    2.1 重点是:方法开出的新栈中,形参一开始是和实参指向的对象是一致的,但是在方法中,new Person()在堆中开辟了一块新空间,形参p指向了这块新空间,所以形参的变化影响不了实参,这块在堆中新创建的空间,在方法结束后就销毁了
    在这里插入图片描述

克隆对象(独立对象,只是属性相同)

  1. 要求
    在这里插入图片描述
  2. 代码(注意事项在代码的注释里)

注意1:调用方法时要写括号,括号内要考虑是否有参数
注意2:编写方法时要考虑
①返回类型–这里返回类型是自定义的数据类型Person
②方法名
③ 形参
④方法体:创建新对象,复制属性,返回新对象
创建新对象,在堆中开辟新空间,把形参传入的属性复制过来,返回就行
注意3:这里的name是string(引用数据类型),把p.name指向的地址赋给了pNew.name(所以会相互影响)–> 注意 变量,形参和实参的内存分配机制

public class Object01 {
    public static void main(String[]args) {
        Cat cat = new Cat();
        //创建要进行复制的对象
        Person p = new Person();
        p.name = "王胖子";
        p.age = 20;
        //接收复制完成的对象
        //注意:调用方法时要写括号,括号内要考虑是否有参数
        Person pNew = cat.copyPerson(p);
        System.out.println("p的属性 name = "+ p.name +" age = "+ p.age );
        System.out.println("pNew的属性 name = "+ pNew.name +
            " age = "+ pNew.age );
        //验证p和pNew指向的地址不同
        System.out.println(p == pNew);
    }
}

class Person{
    String name;
    int age;
}

class Cat{
    // 编写方法时要考虑
    // 1. 返回类型--这里返回类型是自定义的数据类型Person
    // 2. 方法名
    // 3. 形参
    // 4. 方法体:创建新对象,复制属性,返回新对象
    // 4.1 创建新对象,在堆中开辟新空间,把形参传入的属性复制过来,返回就行
    public Person copyPerson(Person p){
        Person pNew = new Person();
        //注意:这里的name是string(引用数据类型),
        //把p.name指向的地址赋给了pNew.name(所以会相互影响)
        pNew.name = p.name;
        pNew.age = p.age;
        //也可以不写这一句,返回对象是pNew,这样堆中就有两个对象空间(写了以后p空间销毁)
        p = pNew;
        return p;
    }
}

方法递归调用(recursion)

介绍

  1. 递归的介绍
    在这里插入图片描述
    想解决一个问题就必须解决上一个,上上个…问题,层层递归再层层返回,得到问题的解
  2. 递归能解决的问题
    在这里插入图片描述

※递归调用机制(重要)

打印问题(根据内存讲解递归调用机制)

  1. 代码在图上有
    在这里插入图片描述
  2. 讲解这个内存图:
    2.1 从main栈开始,执行第一条语句创建对象,该对象指向堆中的一个地址
    2.2 t1.text(4);调用方法会开辟一个新栈(text栈),实参传给形参数值,此时n = 4 ,执行方法中的内容,n > 2所以执行text(n - 1) (第一次调用)
    2.3 每次调用方法都会在栈中开辟新空间,开辟的新栈中n = 3,n > 2,所以又执行text(n - 1) (第二次调用)
    2.4 又开辟一个新空间,开辟的新栈中n = 2,不满足n > 2,所以不再调用方法,继续执行输出语句,输出n = 2 ,该调用结束,销毁该方法栈 (第三次调用)
    2.5 text(2)方法结束后返回到调用text(2)的位置(在哪调用就返回到哪里),继续执行输出语句,输出n = 3,text(3)调用结束,销毁该方法栈
    2.6 text(3)方法结束后返回到调用text(3)的位置,继续执行输出语句,输出n = 4,text(4)调用结束,销毁该方法栈,返回main方法,main方法中没有其它语句,所以程序结束
    注意:每一个方法栈都要完整的执行方法
    在这里插入图片描述

阶乘问题(factorial)–>内存讲解

从栈的顶层开始返回,递归的返回了1 * 2 * 3 * 4 * 5
在这里插入图片描述

递归的重要规则

在这里插入图片描述

递归调用的两个练习

斐波那契数(正递归)

  1. 问题要求在这里插入图片描述
  2. 思路
    2.1 当n = 1 斐波那契数 是1
    2.2 当n = 2 斐波那契数 是1
    2.3 当n >= 3 斐波那契数 是前两个数的和
    2.4 这就是一个递归的思路:想知道第8个数就得知道8前两个数,要想知道8前两个数,又得算出8前两个数的前两个数,以此类推
    2.5 递归计算前两个数,直到前两个数是n = 1和n = 2
  3. 代码
// import java.util.Scanner;
public class Object02 {
    public static void main(String[]args) {
    	Tools tools = new Tools();
    	int n = -1;
    	//有return就要有变量接收
    	int num1 = tools.recursion1(n);
    	//判断方法返回值,如果不为-1则输出该斐波那契语句
    	if(num1 != -1){
    		System.out.println("当n = "+ n +"时对应的斐波那契数 = " + num1);
    	}
    }
}

class Tools{
	public int recursion1(int n){
		if(n >= 1){
			if(n == 1 || n == 2){
				return 1;
			}else{
				//递归计算前两个数,直到前两个数是n = 1和n = 2
				return recursion1(n - 2) + recursion1(n - 1);
			}
		}else{
		    //光写输出时不行的,必须要有返回值!!!
			System.out.println("输入的数要大于等于1");
			return -1;
		}
	}
}

※猴子吃桃子问题(递归逆推)

  1. 问题要求
    在这里插入图片描述
  2. 思路分析 逆推
    2.1.day = 10 有1个桃子
    2.2 day = 9 有(day10 + 1)* 2 = 4
    2.3 day = 8 有(day9 + 1)* 2 = 10
    2.4发现规律为:前一天桃子数 = (后一天桃子数 + 1)* 2
    2.5这就是递归思想,想求第1天桃子数,就得知道第2天的,第三天的,第四天的…
  3. 代码
public class Object02 {
    public static void main(String[]args) {
    	Tools tools = new Tools();
    	int n = 30;
    	int num1 = tools.recursion2(n);
    	if(num1 != -1){
    		System.out.println("第"+ n +"天的桃子有" + num1 + "个");
    	}
    }
}

class Tools{
	public int recursion2(int day){
		//思路分析 逆推
		//1. day = 10 有1个桃子
		//2. day = 9  有(day10 + 1)* 2 = 4 
		//3. day = 8  有(day9 + 1)* 2 = 10
		//4. 发现规律为:前一天桃子数 = (后一天桃子数 + 1)* 2
		//这就是递归思想,想求第1天桃子数,
		//就得知道第2天的,第三天的,第四天的...
		if(day == 10){//记录递归最上层的条件
			return 1;
		}else if(day >= 1 && day <= 9){
			return (recursion2(day + 1) + 1) * 2;
		}else{
			System.out.println("请输入1 - 10之间的数");
			return -1;
		}
	}
}

做法自总结

  1. 递归方法注意是否有形参和返回类型
  2. 第一步先写递归终止的返回数据,比如第一个例子中前两天的数值,第二例子中,第10天的数值
  3. 第二步写进行递归的方法返回值,这个是根据找到的规律写的,比如第一个例子中返回前两个数相加return recursion1(n - 2) + recursion1(n - 1); 第二个例子中返回前一天的数值return (recursion2(day + 1) + 1) * 2;
  4. 如何用递归表示: 第一个例子:方法的目的是递归计算前两个数相加的和,那么recursion1(n - 2)就表示n-2前面两个数相加的和,recursion1(n - 1)就表示n-1前面两个数相加的和,比如第二个例子:方法的目的是计算前一天的桃子树,那么recursion2(day + 1)就表示day后一天的桃子树,然后用规律计算即可(根据递归目的直接传参)
  5. 所以递归一定要明确返回值的意义是什么(返回前一天的数,返回前两个数的和),要得到这个返回值的递归规律是什么,以及如何用递归表示(看第4条)

※递归调用的三个案例

老鼠出迷宫

在这里插入图片描述

1. 创建迷宫,设置障碍物

思路:
① 创建迷宫,用二维数组表示迷宫 int[][] map = int[8][7]
② 先规定map数组的元素值:0表示可以走,1表示障碍物
③ 将最上面一行,最下面一行,最左边一行和最右边一行设置为障碍(也就是赋为1)
④ 把没有规律的障碍单独设为1

public class MiGong {
    public static void main(String[]args) {
    	//① 创建迷宫,用二维数组表示迷宫
    	//② 先规定map数组的元素值:0表示可以走,1表示障碍物
    	int[][] map = new int[8][7];
    	//③ 将最上面一行,最下面一行设置为障碍(也就是赋为1)
    	for(int i = 0;i < 7;i++){
    		map[0][i] = 1;
    		map[7][i] = 1;
    	}
    	//③ 将最左边一行和最右边一行设置为障碍(也就是赋为1)
    	for(int i = 0;i < 8;i++){
    		map[i][0] = 1;
    		map[i][6] = 1;
    	}
    	//④ 把没有规律的障碍单独设为1
    	map[3][1] = 1;
    	map[3][2] = 1;
    	//打印迷宫
    	for(int i = 0;i < 8;i++){
    		for(int j = 0;j < 7;j++){
    			System.out.print(map[i][j] + " ");
    		}
    		System.out.println();
    	}
    }
}

迷宫效果:
在这里插入图片描述

※2. 找出迷宫的路径

思路(递归逆推的思想):
① 在自定义类中定义一个方法findWay()来找迷宫的路径
返回值:如果找到就返回true,否则返回false
因为在if中要根据方法返回值来判断是否能走通,所以这里返回类型用了布尔
形参:map 是二维数组,表示地图,i 和 j是老鼠的位置,初始化位置为(1 , 1)
方法体:递归的找路,用数组中的数字表示找到的路径–>先规定map数组中各个值的含义 0表示可以走,1表示障碍物,2表示该点能走出迷宫,3表示走过但是走不通是死路
递归结束的条件:能走出迷宫即出口位置被置为2–>map[6][5] = 2
⑥ 确定找出路的策略:下–>右–>上–>左

public class MiGong {
    public static void main(String[]args) {
        //1
    	//① 创建迷宫,用二维数组表示迷宫
    	//② 先规定map数组的元素值:0表示可以走,1表示障碍物
    	int[][] map = new int[8][7];
    	//③ 将最上面一行,最下面一行设置为障碍(也就是赋为1)
    	for(int i = 0;i < 7;i++){
    		map[0][i] = 1;
    		map[7][i] = 1;
    	}
    	//③ 将最左边一行和最右边一行设置为障碍(也就是赋为1)
    	for(int i = 0;i < 8;i++){
    		map[i][0] = 1;
    		map[i][6] = 1;
    	}
    	//④ 把没有规律的障碍单独设为1
    	map[3][1] = 1;
    	map[3][2] = 1;
    	//输出当前的迷宫
        System.out.println("====当前地图情况====");
    	for(int i = 0;i < 8;i++){
    		for(int j = 0;j < 7;j++){
    			System.out.print(map[i][j] + " ");
    		}
    		System.out.println();
    	}

        //2
        //使用findWay找出迷宫的路径
        Tool tool = new Tool();
        tool.findWay(map, 1, 1);
        //输出找路后的迷宫
        System.out.println("====找路后的地图情况====");
        for(int i = 0;i < 8;i++){
            for(int j = 0;j < 7;j++){
                System.out.print(map[i][j] + " ");
            }
            System.out.println();
        }
    }
}

class Tool{
    //在自定义类中定义一个方法findWay()来找迷宫的路径
    //返回值:如果找到就返回true,否则返回false
    //形参:map 是二维数组,表示地图,i 和 j是老鼠的位置,初始化位置为(1 , 1)
    //方法体:递归的找路,用数组中的数字表示找到的路径
    //       先规定map数组中各个值的含义:
    //       0表示可以走,1表示障碍物,
    //       2表示该点能走出迷宫,3表示走过但是走不通是死路
    public boolean findWay(int[][] map,int i,int j){
        //递归先想递归结束的条件:能走出迷宫即出口位置被置为2
        if(map[6][5] == 2){
            return true;
        }else{
            //当前位置为0,说明可以走,1,2,3均不能进行找路
            if(map[i][j] == 0){
                //先假定map[i][j]能走出迷宫,
                //不然每次返回true的时候还得写这句
                map[i][j] = 2;
                if(findWay(map, i + 1, j)){//下(递归返回该点是否能走出迷宫)
                    return true;
                }else if(findWay(map, i, j + 1)){//右
                    return true;
                }else if(findWay(map, i - 1, j)){//上
                    return true;
                }else if(findWay(map, i, j - 1)){//左
                    return true;
                }else{
                    //如果下右上左都走不通说明这个点走不出迷宫,置为3
                    map[i][j] = 3;
                    return false;
                }
            }else{
                return false;
            }
        }
    }
}

找路情况
在这里插入图片描述

⑦ 修改找出路的策略:上–>右–>下–>左

public boolean findWay2(int[][] map,int i,int j){
        if(map[6][5] == 2){
            return true;
        }else{
            if(map[i][j] == 0){
                map[i][j] = 2;
                if(findWay(map, i - 1, j)){//上
                    return true;
                }else if(findWay(map, i, j + 1)){//右
                    return true;
                }else if(findWay(map, i + 1, j)){//下
                    return true;
                }else if(findWay(map, i, j - 1)){//左
                    return true;
                }else{
                    //如果下右上左都走不通说明这个点走不出迷宫,置为3
                    map[i][j] = 3;
                    return false;
                }
            }else{
                return false;
            }
        }
    }

找路情况
在这里插入图片描述

3. 测试回溯现象

3.1 修改这段代码使得迷宫效果如图
在这里插入图片描述

map[2][2] = 1;

3.2 执行效果如图
策略要求是:下右上左
所以会先调用向下递归,发现下右上左都走不通,所以该点置为3,返回结果是false,将这个结果返回到调用它的位置(递归回溯现象),然后继续判断向右的递归
在这里插入图片描述

汉诺塔

在这里插入图片描述

思路:

  1. 返回值:每次移动都输出该盘从哪移哪(输出语句),所以返回值为void
  2. 形参:num为要移动的盘数,a, b, c分别表示A塔,B塔,C塔
    第一个位置是初始塔,第二个位置是借助塔,第三个位置是盘最终移到的塔
  3. 方法体:如果只有一个盘,直接从a移到c即可 (递归先考虑递归的顶层)
  4. 如果有多个盘,可以看成两个,最下面和最上 面的所有盘(这三步就是放盘子的重要步骤了)
    (1) 移动上面所有盘到 b,借助 c
    (2) 把最下面的盘移动到 c
    (3) 把 b塔的所有盘移动到 c,借助 a

注意形参的位置和实参的位置:

  1. 形参:第一个位置是初始塔,第二个位置是借助塔,第三个位置是盘最终移到的塔
  2. 输出语句一直都是System.out.println( a + "->" + c);但是输出的内容却不同这是因为传入的实参不同
  3. 实参:main方法调用时传入的实参是tower.move(2, 'A', 'B', 'c');,也就是a = ‘A’,b = ‘B’,c = ‘C’,在递归调用时move(num - 1, b, a, c);也就是传进去的实参是:a = ‘B’,b = ‘A’,c = ‘C’,自然输出 a + “->” + c时输出的是B->C
  4. 输出语句书写时是使用形参表示的,在执行时把实参赋给形参,所以要想输出不同内容,要为方法传入不同的实参
    在这里插入图片描述
public class HanoiTower {
    public static void main(String[]args) {
    	Tower tower = new Tower();
    	int num = 2;
    	char a = 'A';
    	char b = 'B';
    	char c = 'C';
    	tower.move(num, a, b, c);
    }
}

class Tower{
	//思路:
	//1. 返回值:每次移动都输出该盘从哪移哪(输出语句),所以返回值为void
	//2. 形参:num为要移动的盘数,a, b, c分别表示A塔,B塔,C塔
	//第一个位置是初始塔,第二个位置是借助塔,第三个位置是盘最终移到的塔
	public void move(int num,char a,char b,char c){
		//3. 方法体:如果只有一个盘,直接从a移到c即可
		if(num == 1){
			System.out.println("num = "+ num + "从" + a + "->" + c);
		}else{
			//4. 如果有多个盘,可以看成两个,最下面和最上 面的所有盘(num - 1)
			//(1) 移动上面所有盘到 b,借助 c
			move(num - 1, a, c, b);
			//(2) 把最下面的盘移动到 c
			System.out.println("num = "+ num + "从" + a + "->" + c);
			//(3) 把 b塔的所有盘移动到 c,借助 a
			move(num - 1, b, a, c);
		}
	}
}

八皇后问题(暂定)

在这里插入图片描述
问题说明
在这里插入图片描述
思路分析
在这里插入图片描述

方法重载

重载介绍与好处

System是类,out是System类的对象,println()是成员方法,这个方法里面可以接收不同类型的数据,是因为out对象里面有很多println()方法,只是其形参列表不同(同一类中方法名相同,形参列表不同叫方法重载
在这里插入图片描述

重载的使用细节

形参列表必须不同

  1. 方法名必须相同
  2. 参数类型,个数,顺序至少有一个不同
  3. (int a,int b)和(int b,int a)的形参列表是相同的,参数类型相同,参数名不同是不影响的 (主要看参数对应的类型是否不同以及方法名是否相同)
  4. 重载和返回类型无关,和方法名和形参列表有关

在这里插入图片描述

重载简单的练习

在这里插入图片描述

  1. 第一题:根据不同类型形参找到不同方法,输出对应内容
    在这里插入图片描述
    第二题:根据不同类型的形参,返回不同类型的最大值
    在这里插入图片描述
    注意的细节:
    在这里插入图片描述

可变参数

可变参数的介绍

在这里插入图片描述

可变参数入门案例

  1. 问题
    在这里插入图片描述
  2. 使用

计算2个变量和,3个变量和,4,5……
对于方法名称相同,功能相同,只是参数类型不同->可以使用可变参数

  1. int…表示接收的是可变参数,类型是int,即可以接收多个int(0…多个)
  2. 使用可变参数时,形参可以当作数组来使用,即nums可以当作数组(可变参数的本质是数组
  3. 遍历nums求和即可
  1. 代码
public class VarMethod {
    public static void main(String[]args) {
    	T t = new T();
    	System.out.println(t.sum(1, 5, 100));
    }
}

class T{
	//计算2个变量和,3个变量和,4,5……
	//对于方法名称相同,功能相同,只是参数类型不同->可以使用可变参数
	//1. int…表示接收的是可变参数,类型是int,即可以接收多个int(0…多个)
	//2. 使用可变参数时,形参可以当作数组来使用,即nums可以当作数组
	//3. 遍历nums求和即可
	public int sum(int... nums){
		int res = 0;
		for(int i = 0;i < nums.length;i++){
			res += nums[i];
		}
		return res;
	}
}

可变参数的细节

在这里插入图片描述

细节2:
在这里插入图片描述
细节4:
第一个实参给第一个形参,剩下的给可变参数
在这里插入图片描述
细节5:一个方法最多一个可变参数

可变参数的练习

  1. 问题
    在这里插入图片描述
  2. 注意

返回姓名和两门成绩总分->返回类型String

  1. 代码
public class VarMethod {
    public static void main(String[]args) {
    	T t = new T();
    	System.out.println(t.showScore("王胖子",90));
    	System.out.println(t.showScore("高瘦子",89,90));
    	System.out.println(t.showScore("小艺",93,78,86));
    }
}

class T{
	// 返回姓名和两门成绩总分->返回类型String
	public String showScore(String name,double... scores){
		double sum = 0;
		for(int i = 0;i < scores.length;i++){
			sum += scores[i];
		}
		return name + scores.length +"门课的成绩总分为 " + sum;
	}
}

※类中作用域(scope)

作用域基本使用

① 属性(成员变量)–>全局变量,可以在任何成员方法中使用,定义时可以不赋值有默认值
② 局部变量(成员方法中或者代码块中定义的)–> 只能在定义它的代码块中使用,定义时必须赋值否则会报错

在这里插入图片描述

作用域使用细节

在这里插入图片描述
在这里插入图片描述

解读细节1,2
在这里插入图片描述
解读细节4(跨类访问对象属性的两种方式)
在这里插入图片描述

构造方法(构造器)

基本介绍

看一个小例子
在这里插入图片描述
构造器介绍
在这里插入图片描述

语法以及说明
在这里插入图片描述

快速入门练习

在这里插入图片描述

在对象中定义属性
在构造器中初始化属性创建对象时把属性值传递给对象)

public class Constructor {
    public static void main(String[]args) {
    	//当我们new一个对象时,直接通过构造器指定名字和年龄
    	Person p = new Person("王胖子",20);
    	System.out.println("name:" + p.name);
    	System.out.println("age:" + p.age);
    }
}

//需求:在创建人类对象时就直接指定这个对象的年龄和姓名
class Person{
	//定义属性
	String name;
	int age;
	//※由构造器初始化属性(创建对象时把属性值传递给对象)
	public Person(String pname,int page){
		//把传递进来的属性值赋值给属性
		name = pname;
		age = page;
	}
}

使用细节

在这里插入图片描述
在这里插入图片描述
解读细节6,7
在这里插入图片描述

构造器的小练习

1. 定义无参构造器

需求
在这里插入图片描述

public class Constructor {
    public static void main(String[]args) {
    	// 输出name: null , age: 18
    	Person p1 = new Person();
    	System.out.println("p1 name:" + p1.name + " p1 age:" + p1.age);
    }
}

class Person {
	String name;
	int age;
	//第一个构造器:利用构造器设置所有人的年龄初始值都为18
	public Person(){
		age = 18;
	}
}

2. 定义有参构造器

需求
在这里插入图片描述

public class Constructor {
    public static void main(String[]args) {
    	// 输出name: "王胖子" , age: 20
    	Person p2 = new Person("王胖子",20);
    	System.out.println("p2 name:" + p2.name + " p2 age:" + p2.age);
    }
}

class Person {
	String name;
	int age;
	//第二个构造器:在创建对象的同时初始化对象的age值和name值
	public Person(String pname,int page){
		name = pname;
		age = page;
	}
}

对象创建的流程分析(引入构造器)-面试题

在这里插入图片描述

解读内存图

  1. 在方法区加载Person类的信息,只加载一次
  2. new Person在堆中为对象分配空间,根据类信息在该地址定义两个属性
  3. 属性默认初始化–>age = 0,name = null
  4. 属性显式初始化–>age = 90,name = null
  5. ("小倩",20)属性构造器初始化–>age = 20,name = 小倩
  6. Person p把对象在堆中的地址返回给栈中的p(p是对象名/对象引用)

图示
在这里插入图片描述

类定义的完善

在这里插入图片描述

this关键字

this关键字的引出

在这里插入图片描述

this介绍

用小故事来理解this

  1. 王胖子说我的眼–>这个眼是指王胖子的眼
  2. 高瘦子说我的眼–>这个眼是指高瘦子的眼
  3. 不同的对象说我的指向的是不同个体的属性
  4. 就像this,每个this指向的是不同的对象(this代表当前对象

在这里插入图片描述
代码理解:

this.name = name;中this.name指的是当前对象的name

在这里插入图片描述

this的总结和本质(内存图)

简单的说:哪个对象调用,this就代表哪个对象
在这里插入图片描述
内存图
在这里插入图片描述

this使用的细节

在这里插入图片描述
解读细节2(访问属性):
区分属性和局部变量
在这里插入图片描述
解读细节3(访问成员方法):
在这里插入图片描述
解读细节4(访问构造器):
只能在构造器中访问构造器
在这里插入图片描述

this的练习

在这里插入图片描述

注意:
//注意这样的写法,当返回值是布尔值的时候,可以return 表达式 return this.name.equals(p.name) && this.age == p.age;

public class Constructor {
    public static void main(String[]args) {
    	// 在创建对象时就初始化用到了构造器
    	Person p1 = new Person("王胖子",20);
    	Person p2 = new Person("高瘦子",23);
    	//当前对象this就是p1,传入的对象时p2
    	System.out.println("p1和p2比较的结果为:" + p1.compareTo(p2));
    }
}

class Person {
	String name;
	int age;
	// 构造器
	public Person(String name,int age){
		this.name = name;
		this.age = age;
	}
	//比较方法
	public boolean compareTo(Person p){
		//注意这样的写法,当返回值是布尔值的时候,可以return 表达式
		return this.name.equals(p.name) && this.age == p.age;
	}
}

本章案例

1. 求double数组最大值

在这里插入图片描述

注意

  1. 先完成正常业务,再考虑它的健壮性(如果输入为{}或null)
  2. 数组有两种特殊情况需要处理 ①{} ②null(需要在执行功能代码时提前判断)
    2.1 先判断是否数组是否为null,再判断数组是否为{}
    2.2 若为null则不能使用.length(空指针)
    2.3 若为{}则取不到数组第一个元素
  3. 返回值由double改成Double(double的包装类)
    3.1 因为Double类的值可以为空 (引用数据类型可以为null)
public class HomeWork {
    public static void main(String[]args) {
        //数组有两种特殊情况需要处理①{}②null
        double[] nums = {};
    	A01 a1 = new A01();
        Double res = a1.max(nums);
        if(res != null) {
            System.out.println("数组最大值是: " + res);
        }else {
            System.out.println("数组输入有误:数组不能为null 或 {}");
        }
    }
}

class A01 {
    //需求: 求某个double数组的最大值,并返回
    //先完成正常业务,再考虑它的健壮性(如果输入为{}或null)
    //注意:返回值由double改成Double(double的包装类)
    //因为Double类的值可以为空(引用数据类型可以为null)
    public Double max(double[] nums) {
        //先判断是否数组是否为null,再判断数组是否为{}
        //若为null则不能使用.length(空指针)
        //若为{}则取不到数组第一个元素
        if(nums != null && nums.length > 0) {
            //先假定第一个元素是最大值
            double maxNum = nums[0];
            for(int i = 1;i < nums.length;i++) {
                if(nums[i] > nums[i - 1]) {
                    maxNum = nums[i];
                }
            }
            return maxNum;
        }else {
            return null;
        }
    }
}

输出:
在这里插入图片描述

2. 查找某字符串是否在字符串数组中,并返回索引(易)

在这里插入图片描述

本题注意:

  1. 在方法中return时会返回值,结束该方法
  2. 所以用好return语句的位置

看好下面这段代码

for(int i = 0;i < strs.length;i++){
if(findstr.equals(strs[i])) {
//如果找到就返回索引,方法结束
return i;
}
}
//如果找不到才会执行到这里,返回-1
return -1;
public class HomeWork {
    public static void main(String[]args) {
        //作业2
        String[] strs = {"wpz" , "gsz" , "nihao"};
        String findstr = "wpz";
        A02 a2 = new A02();
        int index = a2.find(strs,findstr);
        if(index != -1) {
            System.out.println(findstr + "的索引位置为:" + index);
        }else{
            System.out.println("数组中未找到" + findstr);
        }
    }
}

class A02 {
    public int find(String[] strs,String findstr){
        if(strs != null){
            for(int i = 0;i < strs.length;i++){
                if(findstr.equals(strs[i])) {
                    //如果找到就返回索引,方法结束
                    return i;
                }
            }
            //如果找不到才会执行到这里,返回-1
            return -1;
        }else {
            return -1;
        }
    }
}

输出:
在这里插入图片描述

3. 更改类中书的价格(使用this)

在这里插入图片描述

分析

  1. 类名:Book
  2. 属性:name,price
  3. 提供一个构造器初始化属性
  4. 方法:updatePrice
    4.1 功能是更改类中属性的内容
    4.2 返回值:void(改变类中属性无需返回值)
    4.3 形参:()(改变类中属性直接使用类中属性即可,不需接收形参)
public class HomeWork {
    public static void main(String[]args) {
        // 作业3
        Book book = new Book("中华上下五千年",400);
        book.info();//更新前信息 400
        book.updatePrice();//更新价格
        book.info();//更新后信息 150
    }
}

//分析
//类名:Book
//属性:name,price
//提供一个构造器初始化属性
//方法:updatePrice
//功能是更改类中属性的内容
//返回值:void(改变类中属性无需返回值)
//形参:()(改变类中属性直接使用类中属性即可,不需接收形参)
class Book {
    String name;
    double price;
    public Book(String name,double price) {
        this.name = name;
        this.price = price;
    }
    //功能是更改类中属性的内容
    public void updatePrice() {
        if(this.price > 150) {
            this.price = 150;
        }else if(this.price >100) {
            this.price = 100;
        }
    }
    //显示书籍情况
    public void info() {
        System.out.println("书名 = " + this.name + "\t" +
         "价格 = " + this.price);
    }
}

输出:
在这里插入图片描述

4. 数组复制(易)

在这里插入图片描述

分析

  1. 类名:A03
  2. 方法:copyArr
    2.1 功能:输入一个新数组,返回一个新数组,元素和就数组一样
    2.2 返回值:int[] (新数组)
    2.3 方法名:copyArr
    2.4 形参:int[] (旧数组)
public class HomeWork {
    public static void main(String[]args) {
        // 作业4
        int[] arrOld = {1, 4 , 6, 8};
        A03 a3 = new A03();
        a3.info(arrOld);//打印旧数组
        int[] arrNew = a3.copyArr(arrOld);//复制
        a3.info(arrNew);//打印新数组
    }
}

//分析
//类名:A03
//方法:copyArr
//功能:输入一个新数组,返回一个新数组,元素和就数组一样
//返回值:int[] (新数组)
//方法名:copyArr
//形参:int[] (旧数组)
class A03 {
    //数组拷贝
    public int[] copyArr(int[] arrOld) {
        if(arrOld != null) {
            //在堆中创建一个数组,长度为arrOld.length
            int[] arrNew = new int[arrOld.length];
            //遍历旧数组,将元素拷贝到新数组
            for(int i = 0;i < arrOld.length;i++){
                arrNew[i] = arrOld[i];
            }
            return arrNew;
        }else {
            return null;
        }
    }
    //显示拷贝数组
    public void info(int[] arr) {
        for(int  i = 0;i < arr.length;i++){
            System.out.print(arr[i] + "\t");
        }
        System.out.println();
    }
}

输出:
在这里插入图片描述

5. 计算圆面积和周长(使用this)易

在这里插入图片描述

分析

  1. 类名:Circle
  2. 属性:redius
  3. 提供一个构造器初始化属性
  4. 方法①:
    4.1 方法名:area
    4.2 功能:计算面积
    4.3 返回值:double
    4.4 形参:无(使用类中的属性–>构造器初始化的)
  5. 方法②:
    5.1 方法名:len
    5.2 功能:计算周长
    5.3 返回值:double
    5.4 形参:无(使用类中的属性–>构造器初始化的)
public class HomeWork {
    public static void main(String[]args) {
        // 作业5
        Cicle cicle = new Cicle(1);
        System.out.println("面积:" + cicle.area());
        System.out.println("周长:" + cicle.len());
    }
}

class Cicle {
    double redius;
    //构造器
    public Cicle(double redius){
        this.redius = redius;
    }
    //面积
    public double area() {
        return Math.PI * this.redius * this.redius;
    }
    //周长
    public double len() {
        return 2 * Math.PI * this.redius;
    }
}

输出:
在这里插入图片描述

6. 两个数和差乘商,除数为0要提示(使用this)易

在这里插入图片描述

注意
如果没有相应返回类型的返回值,需要返回null时,用到了基本数据类型的包装类(和案例1的第三条意思一样)

分析

  1. 类名:Cale
  2. 属性:num1,num2
  3. 提供一个构造器初始化属性
  4. 方法①:
    4.1 方法名:chu
    4.2 功能:计算两数相除,如果除数为0要提示
    4.3 返回值:double
    4.4 形参:无(使用类中的属性–>构造器初始化的)
  5. 剩下三个方法大同小异不写了
public class HomeWork {
    public static void main(String[]args) {
        // 作业6
        Cale cale1 = new Cale( 8 , 2 );
        cale1.info();//显示cale1的和差积商
        System.out.println("==============");
        Cale cale2 = new Cale( 8 , 0 );
        cale2.info();//显示cale2的和差积商
    }
}

class Cale {
    double num1;
    double num2;
    //构造器
    public Cale(double num1,double num2){
        this.num1 = num1;
        this.num2 = num2;
    }
    //和
    public double sum() {
        return this.num1 + this.num2;
    }
    //差
    public double minus() {
        return this.num1 - this.num2;
    }
    //积
    public double mul() {
        return this.num1 * this.num2;
    }
    //商
    public Double div() {
        if(this.num2 != 0){
            return this.num1 / this.num2;
        }else {
            return null;
        }
    }
    //展示结果
    public void info() {
        System.out.println(this.num1 + " + " + this.num2 + " = " + sum());
        System.out.println(this.num1 + " - " + this.num2 + " = " + minus());
        System.out.println(this.num1 + " * " + this.num2 + " = " + mul());
        Double res = div();
        if(res != null) {
            System.out.println(this.num1 + " / " + this.num2 + " = " + res);
        }else {
            System.out.println("除数num2不能为0!");
        }
    }
}

输出:
在这里插入图片描述

7. 程序解读

  1. 方法中没有创建变量直接写变量名的,方法就会去全局变量中找,如果方法中没有定义同名的变量,则可以引用全局变量可以省略this
  2. 后++是先输出后++
  3. 新创建对象后属性在新开辟的空间中也会重新初始化哦

在这里插入图片描述

引申:匿名对象

匿名对象只能使用一次
在这里插入图片描述

8. 用String返回歌名和时长

在这里插入图片描述
引用
在这里插入图片描述

9. 程序解读(易)

在这里插入图片描述

10. 思考方法返回类型与形参

在这里插入图片描述

11. 复用构造器(this访问构造器)

在这里插入图片描述
在这里插入图片描述

12. 将对象作为参数传递给方法

在这里插入图片描述

注意的问题:

  1. 这里主要想强调,用一个对象根据不同的属性值求解
    1.1 如果将一个定义好的对象传进去,那么其属性值是固定的,无法计算不同属性值的面积
    1.2 所以要在Circle1中定义一个改变当前对象属性值的方法
  2. 在属性初始化后可以使用set×××()来修改属性值
public class HomeWork {
    public static void main(String[]args) {
        //作业12
        Circle1 c1 = new Circle1();
        PassObject p = new PassObject();
        p.printAreas(c1,5);
    }
}

class Circle1 {
    double radius;
    //构造器
    public Circle1(double radius) {
        this.radius = radius;
    }
    public Circle1(){

    }
    //返回当前对象的面积
    public double findArea(double radius) {
        return Math.PI * radius * radius; 
    }
    //修改当前对象属性值
    public void setRadius(double radius) {
        this.radius = radius;
    }
}

class PassObject {
    //如果将一个定义好的对象传进去,那么其属性值是固定的,无法计算不同属性值的面积
    //所以要在Circle1中定义一个改变当前对象属性值的方法
    public void printAreas(Circle1 c1,int times) {
        System.out.println("Radius\tArea");
        for(int i = 1;i <= times;i++){
            c1.setRadius(i);//修改当前对象的属性值
            System.out.print(c1.radius + "\t");
            System.out.print(c1.findArea(c1.radius));
            System.out.println();
        }
    }
}

13. 石头剪刀布(扩展)

在这里插入图片描述

import java.util.Scanner;
public class HomeWork {
    public static void main(String[]args) {
        Play13 p13 = new Play13();
        p13.manu(p13);
    }
}

class Play13 {
    //用户输入的石头剪刀布
    int user;
    //机器生成的石头剪刀布
    int maRes;
    //用户输赢数组
    int[] userRes = new int[10];
    //当前数组下标
    int index;
    //表示菜单选择
    int manuRes;
    //构造器
    public Play13() {}
    public Play13(int user) {
        this.user = user;
    }
    //菜单
    public void manu(Play13 p13){
        Scanner sn = new Scanner(System.in);
        this.manuRes = -1;
        System.out.println("====菜单====");
        System.out.println("1. 开始对局");
        System.out.println("2. 查看所有对局结果");
        System.out.println("3. 结束对局");
        System.out.print("请输入您的选择:");
        manuOption(p13);
    }
    public void manuOption(Play13 p13) {
        Scanner sn = new Scanner(System.in);
        //判断选项进行对应操作(调用对应方法)
        do{
            this.manuRes = sn.nextInt();
            if(this.manuRes == 1){
                //1. 开始对局,进行用户操作
                manuUser(p13);
            }else if(this.manuRes == 2) {
                //2. 查看所有对局结果
                totalRes(p13);
            }else if(this.manuRes == 3) {
                System.out.println("====使用结束,再见====");
                return;
            }else {
                System.out.println("输入选项有误,请重新输入(1,2,3):");
            }
        }while(this.manuRes != 1&&this.manuRes != 2&&this.manuRes != 3);
    }
    //1. 开始对局,进行用户操作
    //1.1 将用户输入的数存到类中setUser(userInfo)
    //1.2 显示输赢show(p13)
    public void manuUser(Play13 p13) {
        Scanner sn = new Scanner(System.in);
        //变量userInfo:表示用户输入的石头剪刀布
        int userInfo = -1;
        System.out.println("====对局开始====");
        System.out.println("0表示石头,1表示剪刀,2表示布");
        System.out.print("请输入您的选择(0,1,2):");
        do{
            userInfo = sn.nextInt();
            if(userInfo == 0||userInfo == 1||userInfo == 2) {
                p13.setUser(userInfo);
                System.out.println(show(p13));
                System.out.print("请选择您下一步选择的菜单选项:");
                manuOption(p13);
            }else{
                System.out.println("输入有误,请重新输入:");
            }
        }while(userInfo != 0&&userInfo != 1&&userInfo != 2);
    }
    //1.1 设置用户输入的数字
    public void setUser(int user) {
        this.user = user;
    }
    //1.2 将数字转换为字符串:赢/输/平局
    //用到了isWin()
    //用到了changeShow()
    public String show(Play13 p13) {
        System.out.println("====本局结果====");
        this.userRes[index++] = p13.isWin();
        return changeShow(this.userRes[index-1]);
    } 
    //将数字转换为字符串:赢/输/平局
    public String changeShow(int index1) {
        if(index1 == 3) {
            return "输了";
        }else if(index1 == 2) {
            return "赢了";
        }else{
            return "平局";
        }
    }
    //isWin()判断是否输赢
    //返回结果3是输,1是平,2是赢
    //用到了randomNum()是机器生成数
    //用到了nameString(res)是把数字表示的石头剪刀布转换为字符串表示
    public int isWin() {
        this.maRes = randomNum();
        System.out.println("机器出" + nameString(this.maRes));
        System.out.println("我 出" + nameString(this.user));
        if(this.user == this.maRes) {
            return 1;
        }else if(this.user == 2 && this.maRes == 0) {
            return 2;
        }else if(this.user == 0 && this.maRes == 1) {
            return 2;
        }else if(this.user == 1 && this.maRes == 2) {
            return 2;
        }else{
            return 3;
        }
    }
    //随机生成0,1,2(石头,剪刀,布)
    public int randomNum() {
        while(true){
            int res = (int)(Math.random() * 10);
            if(res == 0 || res == 1 || res == 2){
                return res;
            }
        }
    }
    //将数字转换为字符串:石头剪刀布
    public String nameString(int res) {
        if(res == 0) {
            return "石头";
        }else if(res == 1) {
            return "剪刀";
        }else{
            return "布";
        }
    }
    //2. 查看所有对局结果
    public void totalRes(Play13 p13) {
        if(this.userRes[0] != 0) {
            System.out.println("====所有对局结果====");
            System.out.println("我\t机器\t输赢情况");
            for(int i = 0;i < this.userRes.length;i++){
                if(userRes[i] != 0) {
                    System.out.println(nameString(this.user) + "\t" +
                     nameString(this.maRes) + "\t" + changeShow(userRes[i]));
                }
            }
            System.out.print("请选择您下一步选择的菜单选项:");
            manuOption(p13);
        }else {
            System.out.println("暂时没有进行过的对局");
            System.out.print("请选择您下一步选择的菜单选项:");
            manuOption(p13);
        }
    }
}

输出:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值