第七章 面向对象编程(基础) 上

在这里插入图片描述

本章重点

  1. 方法的传参机制
  2. 递归课后练习

1.类与对象

引出

●看一个养猫猫问题

张老太养了两只猫猫: 一只名字叫小白, 今年3岁, 白色. 还有一只叫小花, 今年100岁, 花色. 请编写一个程序, 当用户输入小猫的名字时, 就显示该猫的名字, 年龄, 颜色. 如果用户输入的小猫名字错误, 则显示 张老太没有这只猫咪.

●使用现有技术解决 Object01.java

import java.util.Scanner;

public class Object01 {
    //编写一个main方法
    public static void main(String[] args) {
    	/*
    	张老太养了两只猫猫: 一只名字叫小白, 今年3岁, 白色. 体重, 爱好
    	还有一只叫小花, 今年100岁, 花色. 请编写一个程序, 
    	当用户输入小猫的名字时, 就显示该猫的名字, 年龄, 颜色. 
    	如果用户输入的小猫名字错误, 则显示 张老太没有这只猫咪.

		//思路分析
    	1.单独定义变量 -> 不利于数据的管理(你把一只猫的信息拆解)
    	2.创建Scanner对象, 接收用户输入
    	 */
    	
    	//代码实现
    	//第一只猫信息
    	String cat1Name = "小白";
    	int cat1Age = 3;
    	String cat1Color = "白色";
    	//第二只猫信息
    	String cat2Name = "小花";
    	int cat2Age = 100;
    	String cat2Color = "花色";
    	
    	Scanner myScanner = new Scanner(System.in);
    	System.out.print("输入小猫的名字: ");
    	String catName = myScanne r.next();

    	//数组 ==> (1)数据类型体现不出来  (数组是多个相同类型数据的组合)
    	//		   (2)只能通过[下标]获取信息, 造成变量名字和内容的对应关系不明确
    	//		   (3)不能体现猫的行为
    	//第1只猫信息
    	String[] cat1 = {"小白", "3", "白色"}; cat1[]
    	String[] cat2 = {"小花", "100", "花色"};
	}  
}

●现有技术解决的缺点分析

不利于数据的管理
效率低 ⇒ 引出我们的新知识点, 类与对象
有很多书视频甚至还提到哲学,道家思想,形而上思想,才引出类与对象, 其实没那么复杂. java语言设计设去设计类与对象, 其实是特别简单的思想.

java设计者 引入类与对象(OOP), 根本原因就是现有的技术, 不能完美地解决新的需求

其实就是这么一点东西, 但是往往我们会把一些技术理解的过于神秘, 跟着学有关系~ 跟道家思想有关系~ 都是没有意思的

概念

一个程序就是一个世界, 有很多事物 (对象[属性, 行为])

●类与对象的关系示意图

在这里插入图片描述
1.把一种事物的共性(属性)提取出来,形成的一个叫做类的数据类型.

2.这个数据类型就是我们自定义的数据类型谁定义的? 程序员定义的
在这里插入图片描述

快速入门

●面向对象的方式解决养猫问题 Object01.java

public class Object01 {
    //编写一个main方法
    public static void main(String[] args) {
    	//使用OOP面向对象解决
    	//实例化一只猫[创建一只猫对象]
    	//解读
    	//1.new Cat() 创建一只猫
    	//2.Cat cat1 = new Cat(); 把创建的猫 赋给 cat1
    	//3.cat1 就是一个对象(猫对象)
    	Cat cat1 = new Cat();
    	cat1.name = "小白";
    	cat1.age = 3;
    	cat1. color = "白色";
    	cat1.weight = 2;
    	//创建第二只猫, 并赋给 cat2
    	//cat2也是一个对象(猫对象)
    	Cat cat2 = new Cat();
    	cat2.name = "小花";
    	cat2.age = 100;
    	cat2.color = "花色";
    	cat2.weight = 3;

    	//怎么访问对象的 属性 呢
    	System.out.println("第一只猫信息: " + cat1.name + " " 
    		+ cat1.age + " " + cat1.color + " " + cat1.weight);
    	System.out.print("第二只猫信息: " + cat2.name + " " 
    		+ cat2.age + " " + cat2.color + " " + cat2.weight);

	}  
}


//使用面向对象的方式解决养猫问题
//定义一个猫类 -> 自定义的数据类型
class Cat {
	//属性
	String name;//名字
	int age;//年龄
	String color;//颜色
	double weight;//体重

	//行为

}

●类和对象的区别和联系

通过上面的案例和讲解我们可以看出:
1)类是抽象的, 概念的, 代表一类事物, 比如人类, 猫类…, 即它是数据类型
2)对象是具体的, 实际的, 代表一个具体事物, 即 是实例
3)类是对象的模板, 对象是类的一个个体, 对应一个实例.

对象内存布局

●对象在内存中的存在形式

在这里插入图片描述

2.属性

●属性/成员变量

√ 基本介绍 Object02.java
1.从概念或叫法上看: 成员变量 = 属性 = field字段 (即 成员变量是用来表示属性的, 这里, 统一叫 属性)
2.属性是类的一个组成部分, 一般是基本数据类型, 也可以是引用类型(对象, 数组). 比如我们前面定义猫类 的 int age 就是属性.

public class Object02 {
    //编写一个main方法
    public static void main(String[] args) {

    }
}

class Car {
    String name;//属性, 成员变量, 字段 field
    double price;
    String color;
    String[] master;//属性可以是基本数据类型, 也可以是引用类型(对象, 数组)
}

√ 注意事项和使用细节

1)属性的定义语法同变量不同, 实例: 访问修饰符 属性类型 属性名;
        这里简单地介绍一下访问修饰符: 控制属性的访问范围; 有4种访问修饰符 public protected 默认 private, 后面再详细学习

2)属性的定义类型可以是任意类型, 包含 基本类型引用类型

3)属性如果不赋值, 有默认值, 规则和数组一样
byte 0, short 0, int 0, long 0, float 0.0, double 0.0, char ‘\u0000’, boolean false, String null
在这里插入图片描述
4)示意图 PropertiesDetail.java
在这里插入图片描述

public class PropertiesDetail {
    //编写一个main方法
    public static void main(String[] args) {
    	//创建Person对象
    	//p1 是对象名(对象引用)
    	//new Person() 创建的对象空间(包括数据) 才是真正的对象
    	Person p1 = new Person();

    	//对象属性的默认值, 遵守数组的规则
	    /*
	     byte 0, short 0, int 0, long 0
	     float 0.0, double 0.0
	     char ‘\u0000’, boolean false
	     String null
	    */
	   System.out.println("当前这个人的信息");
	   System.out.println("age=" + p1.age + " name=" + p1.name + " sal=" + 
	   		p1.sal + " isPass=" + p1.isPass);
    }
}

class Person {
	//四个属性
	int age;
	String name;
	double sal;
	boolean isPass;
}

创建对象访问属性

●如何创建对象

1.先声明再创建:      Cat cat; //声明对象    cat = new Cat(); //创建

2.直接创建:     Cat cat = new Cat();

在这里插入图片描述


●对象如何访问属性

对象名.属性名; cat.name; cat.age; cat.color;

对象分配机制

●类和对象的内存分配机制(重要)

√ 看一个思考题 Object03.java
我们定义一个人类(Person)(包括 名字, 年龄)

我们看看下面一段代码
Person p1 = new Person();
p1.age = 10;
p1.name = “小明”;
Person p2 = p1;//把p1 赋给了 p2; 让p2指向p1
System.out.println(p2.age);

请问: p2.age究竟是多少? 并画出内存图.
在这里插入图片描述

public class Object03 {
    //编写一个main方法
    public static void main(String[] args) {
    	Person p1 = new Person();
		p1.age = 10;
		p1.name = "小明";
		Person p2 = p1;//把p1 赋给了 p2; 让p2指向p1
		System.out.println(p2.age); 
    }
}

class Person {
	int age;
	String name;
}

√ Java内存结构分析

1.栈: 一般存放基本数据类型(局部变量)
2.堆: 存放对象(Cat cat, 数组等)
3.方法区: 常量池(常量, 比如字符串), 类加载信息
4.示意图[Person(age, name)]
在这里插入图片描述

√ Java创建对象的流程简单分析

Person p = new Person();
p.name = “jack”;
p.age = 10;

1.先加载Person类信息(属性和方法信息, 只会加载一次)
2.在堆中分配空间, 进行默认初始化[看规则]
3.把地址赋给 p, p 就指向对象
4.进行指定初始化, 比如 p.name = “jack”; p.age = 10


√ 看一个练习题, 并分析画出内存布局图, 进行分析
//我们看看下面一段代码, 会输出什么信息
Person a = new Person();
a.name = “小明”;
a.age = 10;
Person b;
b = a;
System.out.println(b.name);//小明
b.age = 200;
b = null;
System.out.println(a.age);//200
System.out.println(b.age);//报错

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

public class Object03 {
    //编写一个main方法
    public static void main(String[] args) {
    	/*Person p1 = new Person();
		p1.age = 10;
		p1.name = "小明";
		Person p2 = p1;//把p1 赋给了 p2; 让p2指向p1
		System.out.println(p2.age);*/ 
		Person a = new Person();
		a.name = "小明";
		a.age = 10;
		Person b;
		b = a;
		System.out.println(b.name);//小明
		b.age = 200;
		b = null;
		System.out.println(a.age);//200
		System.out.println(b.age);//报错
    }
}

class Person {
	int age;
	String name;
}

3.成员方法

●类定义的完善▶️
在这里插入图片描述

●基本介绍
在某些情况下, 我们需要定义成员方法(简称方法). 比如人类: 除了有一些属性外(年龄, 姓名…), 我们人类还有一些行为比如: 可以说话, 跑步…通过学习, 还可以做算术题. 这时就要用成员方法才能完成. 现在要求对Person类完善.

快速入门

●成员方法快速入门 Method01.java
1)添加speak 成员方法, 输出 我是一个好人
2)添加cal01 成员方法, 可以计算从 1 + … + 1000 的结果
3)添加cal02 成员方法, 该方法可以接收一个数n, 计算从 1 + … + n 的结果
4)添加getSum 成员方法, 可以计算两个数的和

public class Method01 {
    //编写一个main方法
    public static void main(String[] args) {
    	//方法使用
    	//1. 方法写好后, 如果不去调用(使用), 不会输出
 		//2. 先创建对象, 然后调用方法即可
 		Person p1 = new Person();
 		p1.speak();//调用方法
 		p1.cal01();//调用cal01方法
 		p1.cal02(5);//调用cal02方法, 同时给n = 5
 		p1.cal02(10);//调用cal02方法, 同时给n = 10

 		//调用getSum方法, 同时num1=10, num2=20
 		//把 方法 getSum 返回的值, 赋给 变量 returnRes
 		int returnRes = p1.getSum(40, 20);
 		System.out.println("getSum方法返回的值=" + returnRes);
    }
}

class Person {
	int age;
	String name;

	//方法(成员方法)
	//添加speak 成员方法, 输出 我是一个好人
	//解读
	//1. public 表示方法是公开的
	//2. void : 表示方法没有返回值
	//3. speak() : speak是方法名 ()是形参列表
	//4. {} 方法体, 可以写我们要执行的代码
	//5. System.out.println("我是一个好人"); 表示我们的方法就是输出一句话
	public void speak() {
		System.out.println("我是一个好人");
	}

	//添加cal01 成员方法, 可以计算从 1 + ... + 1000 的结果
	public void cal01() {
		/*
		可以用循环语句
		1.循环1-1000
		2.定义一个变量 int sum, 累积求和
		 */
		int sum = 0;
		for (int i = 1; i <= 1000; i++) {
			sum += i;
		}
		System.out.println("cal01方法 计算结果=" + sum);
	}

	//添加cal02 成员方法, 该方法可以接收一个数n, 计算从 1 + ... + n 的结果
	//解读
	//1. (int n) 形参列表, 表示当前有一个形参 n, 可以接收用户输入
	public void cal02(int n) {
		//循环完成
		int sum = 0;
		for (int i = 1; i <= n; i++) {
			sum += i;
		}
		System.out.println("cal02方法 计算结果=" + sum);
	}

	//添加getSum 成员方法, 可以计算两个数的和
	//解读
	//1. public 表示方法是公开的
	//2. int : 表示方法执行后, 返回一个 int值
	//3. getSum 方法名
	//4. (int num1, int num2) 形参列表, 2个形参, 可以接收用户传入的两个数
	//5. return res; 表示把 res 的值, 返回
	public int getSum(int num1, int num2) {
		int res = num1 + num2;
		return res;
	}
}

调用机制

●方法的调用机制原理
画出 程序执行过程[getSum]+说明
在这里插入图片描述

●为什么需要成员方法 Method02.java
√ 看一个需求:
请遍历一个数组, 输出数组的各个元素值 int[][] map = {{0, 0, 1}, {1, 1, 1}, {1, 1, 3}};
√ 解决思路1: 传统的方法, 就是使用单个for循环, 将数组输出, 大家看看问题是什么?

public class Method02 {
    //编写一个main方法
    public static void main(String[] args) {
    	//请遍历一个数组, 输出数组的各个元素值 
    	int[][] map = {{0, 0, 1}, {1, 1, 1}, {1, 1, 3}};

    	//遍历map数组
    	//传统的解决方式就是直接遍历
    	for (int i = 0; i < map.length; i++) {
    		for (int j = 0; j < map[i].length; j++) {
    			System.out.print(map[i][j] + " ");
    		}
    		System.out.println("");
    	}

        // 再次遍历数组
        for (int i = 0; i < map.length; i++) {
            for (int j = 0; j < map[i].length; j++) {
                System.out.print(map[i][j] + " ");
            }
            System.out.println("");
        }

        // 再次遍历数组
        for (int i = 0; i < map.length; i++) {
            for (int j = 0; j < map[i].length; j++) {
                System.out.print(map[i][j] + " ");
            }
            System.out.println("");
         }
    }
}

√ 解决思路2: 定义一个类 MyTools, 然后写一个成员方法, 调用方法实现, 看看效果又如何.

public class Method02 {
    //编写一个main方法
    public static void main(String[] args) {
    	//请遍历一个数组, 输出数组的各个元素值 
    	int[][] map = {{0, 0, 1}, {1, 1, 1}, {1, 1, 3}};

        //使用方法完成输出, 创建MyTools对象
        MyTools tool = new MyTools();

        //使用方法
        tool.printArr(map);

        tool.printArr(map);

        tool.printArr(map);
    }
}

//把输出的功能, 写到一个类的方法中, 然后调用该方法即可
class MyTools {
    //方法, 接收一个二维数组
    public void printArr(int[][] map) {
        System.out.println("======");
        //对传入的map数组进行遍历输出
        for (int i = 0; i < map.length; i++) {
            for (int j = 0; j < map[i].length; j++) {
                System.out.print(map[i][j] + "_");
            }
            System.out.println("");
         }
    }
}

●成员方法的好处
√ 提高代码的复用性
√ 可以将实现的细节封装起来, 然后供其它用户来调用即可


●成员方法的定义
访问修饰符 返回数据类型 方法名 (参数列表…) {//方法体
         语句;
         return 返回值;
}

1.形参列表: 表示成员方法输入 cal(int n), getSum(int num1 int num2)
2.返回数据类型: 表示成员方法输出, void 表示没有返回值
3.方法主体: 表示为了实现某一功能代码块
4.return 语句不是必须的
5.温馨提示: 结合前面的示意图, 理解.

注意事项和细节

●注意事项和细节 MethodDetail.java

√ 访问修饰符 (作用是控制 方法使用的范围)
如果不写默认访问修饰符. [有4种: public, protected, 默认, private]


√ 返回数据类型
1.一个方法最多只有一个返回值 [思考, 如何返回多个结果. 可以返回数组]
2.返回类型可以是任意类型, 包含基本类型或引用类型(数组, 对象)
3.如果方法要求有返回数据类型, 则方法体中最后的执行语句必须为return 值, 而且要求返回值类型必须和return的值类型一致或兼容
4.如果方法值是void, 则方法体中可以没有return语句, 或者 只写 return;
√ 方法名
遵循驼峰命名法, 最好见名知意, 表达出该功能的意思即可. 比如, 得到两个数的和getSum.


√ 形参列表
1.一个方法可以有0个参数, 也可以有多个参数, 中间用逗号隔开, 比如 getSum(int n1, int n2)
2.参数类型可以为任意类型, 包含基本类型或引用类型, 比如 printArr(int[][] map)
3.调用带参数的方法时, 一定要对应着参数列表, 传入相同类型或兼容类型的参数.
4.方法定义时的参数称为形式参数, 简称形参; 方法调用时传入的参数称为实际参数, 简称实参. 实参和形参的类型要一致或兼容. 个数, 顺序必须一致!


√方法体
里面写完成功能的具体的语句, 可以是输入, 输出, 变量, 运算, 分支, 循环, 方法调用, 但是里面不能再定义方法! 即: 方法不能嵌套定义.

public class MethodDetail {

	//编写一个main方法
	public static void main(String[] args) {
		AA a = new AA();
		int[] resArr = a.getSumAndSub(1, 4);
		System.out.println("和=" + resArr[0]);
		System.out.println("差=" + resArr[1]);

		//细节:调用带参数的方法时, 一定要对应着参数列表, 传入相同类型或兼容类型的参数.  
		byte b1 = 1;
		byte b2 = 2;
		a.getSumAndSub(b1, b2);//byte -> int (ok)
		// a.getSumAndSub(1.1, 1.2);//double -> int (×)
		//细节: 实参和形参的类型要一致或兼容, 个数, 顺序必须一致!
		// a.getSumAndSub(1);//× 实际参数列表和形式参数列表 个数不一致
		a.f3("zzw", 23);//ok
		//a.f3(23, "zzw");//× 实际参数列表和形式参数列表顺序不对
	}
}

class AA {
	//细节: 方法不能嵌套定义
	public void f4() {
		//错误
		// public void f5() {

		// }
	}

	public void f3(String str, int n) {

	}

	//1.一个方法最多有一个返回值 [思考:如何返回多个结果 返回数组]
	public int[] getSumAndSub(int n1, int n2) {
		int[] resArr = new int[2];//创建一个数组
		resArr[0] = n1 + n2;
		resArr[1] = n1 - n2;
		return resArr;
	}

	//2.返回类型可以是任意类型, 包含基本类型或引用类型(数组, 对象)
	//  参考getSumAndSub

	//3.如果方法要求有返回数据类型, 则方法体中最后的执行语句
	//  必须为return 值, 而且要求返回值类型必须和return的值类型
	//  一致或兼容
	public double f1() {
		double d1 = 1.1 * 3;
		int n = 100;
		return n;//int -> double
		// return d1;//ok? double - int(错误)
	}

	//4.如果方法值是void, 则方法体中可以没有return语句,或者 只写 return
	//提示: 在实际工作中, 我们的方法都是为了完成某个功能, 所以方法名要有一定含义
	//, 最好是见名知意
	public void f2() {
		System.out.println("hello");
		System.out.println("hello");
		System.out.println("hello");
		// int n = 1;
		// return n;
		return;
	}
}

√ 方法调用细节说明 MethodDetail02.java
1.同一个类中的方法调用: 直接调用即可. 比如 print(参数);
2.跨类中的方法A调用B类方法: 需要通过对象名调用. 比如 对象名.方法名(参数);
3.特别说明一下: 跨类的方法调用和方法的访问修饰符相关, 后面学到访问修饰符时, 再仔细学习.

public class MethodDetail02 {

	//编写一个main方法
	public static void main(String[] args) {
		A a = new A();
		// a.sayOk();
		a.m1();
	}
}

class A {
	//同一个类中的方法调用: 直接调用即可. 比如 print(参数);
	public void print(int n) {
		System.out.println("print()方法被调用 n=" + n);
	}

	public void sayOk() {//sayOk 调用 print(直接调用即可)
		print(10);
		System.out.println("继续执行sayOk...");
	}

	//跨类中的方法A调用B类方法: 需要通过对象名调用.
	public void m1() {
		//先创建B对象, 然后再调用方法即可
		System.out.println("m1() 方法被调用");
		B b = new B();
		b.hi();
		System.out.println("m1() 方法继续执行");
	}
}

class B {
	public void hi() {
		System.out.println("B类中的 hi()被执行");
	}
}

本质就是开栈, 执行完毕, 返回
在这里插入图片描述

在这里插入图片描述

课堂练习

MethodExercise01.java
1.编写类AA, 有一个方法: 判断一个数是奇数还是偶数, 返回boolean

public class MethodExercise01 {

	//编写一个main方法
	public static void main(String[] args) {
		AA aa = new AA();
		if (aa.isOdd(2)) {//T, 以后这样的方法还会看多很多
			System.out.println("是奇数");
		} else {
			System.out.println("是偶数");
		}
	}
}

//编写类AA, 有一个方法: 判断一个数是奇数还是偶数, 返回boolean
class AA {

	//思路
	//1.方法的返回类型 boolean
	//2.方法的名字 isOdd
	//3.方法的形参 int num
	//4.方法体 判断语句
	public boolean isOdd(int num) {
		/*if (num % 2 != 0) {
			return true;
		} else {
			return false;
		}*/

		// return num % 2 != 0 ? true : false;

		return num % 2 != 0;
	}
}

2.根据行, 列, 字符打印 对应行数和列数的字符. 比如: 行: 4, 列: 4, 字符#, 则打印相应的效果.

public class MethodExercise02 {

	//编写一个main方法
	public static void main(String[] args) {
		AA aa = new AA();
		//使用print方法
		aa.print(10, 20, '*');
	}
}

class AA {

    //根据行, 列, 字符打印 对应行数和列数的字符. 
    //比如: 行: 4, 列: 4, 字符#, 则打印相应的效果.
	/*
		####
		####
		####
		####
	 */
    //思路
	//1.方法的返回类型 void
	//2.方法的名字 print
	//3.方法的形参 (int row, int column, char c)
	//4.方法体 是一个双层for循环
	public void print(int row, int column, char c) {
		for (int i = 1; i <= row; i++) {//外层循环1-row次
			for (int j = 1; j <= column; j++) {
				System.out.print(c);
			}
			System.out.println();
		}
	}
}

传参机制

方法的传参机制对我们今后的编程非常重要, 一定要搞得清清楚楚明明白白, 我们通过案例来学习

●基本数据类型的传参机制 MethodParameter01.java
1)有一个案例, 分析结果是什么?

public class MethodParameter01 {

	//编写一个main方法
	public static void main(String[] args) {
		int a = 10;
		int b = 20;
		//创建AA对象
		AA obj = new AA();
		obj.swap(a, b);//调用swap

		System.out.println("main方法\na=" + a + "\tb=" + b);//a=10 b=20
	}
}

class AA {
	public void swap(int a, int b) {
		System.out.println("a和b交换前的值\na=" + a + "\tb=" + b);//a=10 b=20
		//完成了 a 和 b 的交换
		int temp = a;
		a = b;
		b = temp;
		System.out.println("a和b交换后的值\na=" + a + "\tb=" + b);//a=20 b=10
	}
}

2)结论及示意图
基本数据类型, 传递的是值(值拷贝), 形参的任何改变不影响实参!
在这里插入图片描述

在这里插入图片描述


●引用数据类型的传参机制 MethodParameter02.java
1)看一个案例:
B类中编写一个方法test100, 可以接收一个数组, 在方法中修改该数组, 看看原来的数组是否变化? 会变化

public class MethodParameter02 {

	//编写一个main方法
	public static void main(String[] args) {
		//测试
		B b = new B();
		int[] arr = {1, 2, 3};
		b.test100(arr);//调用方法
		//遍历数组
		System.out.println("===main arr数组===");
		for (int i = 0; i < arr.length; i++) {
			System.out.print(arr[i] + "\t");
		}
	}
}

class B {
	//B类中编写一个方法test100, 可以接收一个数组
	//, 在方法中修改该数组, 看看原来的数组是否变化?
	public void test100(int[] arr) {//形参被修改, 是否会造成实参的变化?
		arr[0] = 123;
		//遍历数组
		System.out.println("===test100 arr数组===");
		for (int i = 0; i < arr.length; i++) {
			System.out.print(arr[i] + "\t");
		}
		System.out.println();
	} 
}

2)结论及示意图
引用类型传递的是地址(传递的也是值, 但是值是地址), 可以通过形参影响实参!
在这里插入图片描述

在这里插入图片描述

3)B类中编写一个方法test200, 可以接收一个Person(age, sal)对象, 在方法中修改该对象属性, 看看原来的对象是否变化? 会变化

public class MethodParameter02 {

	//编写一个main方法
	public static void main(String[] args) {
		//测试
		B b = new B();
		Person p = new Person();
		p.name = "jack";
		p.age = 23;
		b.test200(p);
		System.out.println("main方法的 p.age=" + p.age);//10000
	}
}

class Person {
	String name;
	int age;
}

class B {
	public void test200(Person p) {
		p.age = 10000;//修改对象属性
	}
}

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


4) p = null; 对应示意图

在这里插入图片描述

p = new Person(); 对应示意图

在这里插入图片描述

克隆对象

●成员方法返回类型是引用类型应用实例
1)编写MyTools类, 编写一个方法可以打印二维数组的数据. MethodExercise03.java

public class MethodExercise03 {

	//编写一个main方法
	public static void main(String[] args) {
    	//请遍历一个数组, 输出数组的各个元素值 
    	int[][] map = {{0, 0, 1}, {1, 1, 1}, {1, 1, 3}};

        //使用方法完成输出, 创建MyTools对象
        MyTools tool = new MyTools();

        //使用方法
        tool.printArr(map);

        tool.printArr(map);

        tool.printArr(map);
	}
}

//把输出的功能, 写到一个类的方法中, 然后调用该方法即可
class MyTools {
    //方法, 接收一个二维数组
    public void printArr(int[][] map) {
        System.out.println("======");
        //对传入的map数组进行遍历输出
        for (int i = 0; i < map.length; i++) {
            for (int j = 0; j < map[i].length; j++) {
                System.out.print(map[i][j] + "_");
            }
            System.out.println("");
         }
    }
}

2)编写一个方法copyPerson, 可以复制一个Person对象, 返回复制的对象. 克隆对象, 注意要求得到新对象和原来的对象是两个独立的对象, 只是它们的属性相同. MethodExercise04.java

public class MethodExercise04 {

	//编写一个main方法
	public static void main(String[] args) {
		Person p = new Person();
		p.name = "milan";
		p.age = 100;
		//创建tools对象
		MyTools tools = new MyTools();
		Person p2 = tools.copyPerson(p);

		//到此, p 和 p2 是Person对象, 但是是两个独立的对象, 属性相同
		System.out.println("p的属性 age=" + p.age + " name=" + p.name);
		System.out.println("p2的属性 age=" + p2.age + " name=" + p2.name);
		//温馨提示: 可以通过 对象比较 看看是否是同一个对象
		System.out.println(p == p);//false
	}
}

class Person {
	String name;
	int age;
}

class MyTools {

	//编写一个方法copyPerson, 可以复制一个Person对象, 返回复制的对象. 
	//克隆对象, 注意要求得到新对象和原来的对象是两个独立的对象, 只是它们的属性相同.

	//编写方法的思路
	//1.方法的返回类型 Person
	//2.方法的名字 copyPerson
	//3.方法的形参 (Person p)
	//4.方法体 创建一个新对象, 并复制属性, 返回即可
	public Person copyPerson(Person p) {
		//创建一个新的对象
		Person p2 = new Person();
		p2.name = p.name;//把原来对象的名字赋给p2.name
		p2.age = p.age;//把原来对象的年龄赋给p2.age

		return p2;
	}
}

在这里插入图片描述

4.递归

方法递归调用

●基本介绍
简单地说: 递归就是方法自己调用自己, 每次调用时传入不同的变量. 递归有助于编程者解决复杂问题, 同时可以让代码变得简洁.

●递归能解决什么样的问题?
1.各种数学问题如: 八皇宫问题, 汉诺塔, 阶乘问题, 迷宫问题, 球和篮子的问题(google编程大赛)
2.各种算法中也会使用到递归, 比如快排, 归并排序, 二分查找, 分治算法等.
3.将用栈解决的问题 ==> 递归代码比较简洁

●递归调用应用实例 - 八皇后问题
√ 八皇后问题说明
八皇后问题, 是一个古老而著名的问题, 是回溯算法的典型案例. 该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出: 在8 x 8格的国际象棋上摆放八个皇后, 使其不能互相攻击. 即: 任意两个皇后都不能处于同一行, 同一列或同一斜线上, 问有多少种摆法.


●递归举例
列举两个小案例, 来帮助大家理解递归调用机制

1.打印问题 Recursion01.java

public class Recursion01 {

	//编写一个main方法
	public static void main(String[] args) {
    	T t1 = new T();
        t1.test(4);//输出什么?
	}
}

class T {
    //分析
    public void test(int n) {
        if (n > 2) {
            test(n - 1);
        }
        System.out.println("n=" + n);
    }
}

在这里插入图片描述

拓展

    public void test(int n) {
        if (n > 2) {
            test(n - 1);
        } else {
            System.out.println("n=" + n);
        }
    }

在这里插入图片描述

2.阶乘问题

public class Recursion01 {

	//编写一个main方法
	public static void main(String[] args) {
    	T t1 = new T();
        int res = t1.factorial(5);
        System.out.println("5的阶乘 res=" + res);
	}
}

class T {
    //factorial 阶乘
    public int factorial(int n) {
        if (n == 1) {
            return 1;
        } else {
            return factorial(n - 1) * n;
        }
    }
}

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

●递归重要规则
1.执行一个方法时, 就创建一个新的受保护的独立空间(栈空间)
2.方法的局部变量是独立的, 不会相互影响, 比如变量n
3.如果方法中使用的是引用类型变量(比如数组, 对象), 就会共享该引用类型的数据
4.递归必须向退出递归的条件逼近, 否则就是无限递归, 出现StackOverflowError, 死龟了
5.当一个方法执行完毕, 或者遇到return, 就会返回, 遵守谁调用, 就将结果返回给谁, 同时当方法执行完毕或者返回时, 该方法也就执行完毕.

课堂练习

1.请使用递归的方式求出斐波那契数列1, 1, 2, 3, 5 ,8 ,13… 给你一个整数, 求出它的值是多少 RecursionExercise01.java
什么是斐波那契数列: 前两个数是1, 到了第3个数, 就是前两个数的和, 依此类推.

public class RecursionExercise01 {

	//编写一个main方法
	public static void main(String[] args) {
        T t1 = new T();
        int n = 7;
        int res = t1.fibonacci(n);
        if (res != -1) {
            System.out.println("当n=" + n + "时, 对应的斐波那契数=" + res);
        }
	}
}

class T {
    /*
    请使用递归的方式求出斐波那契数列1, 1, 2, 3, 5 ,8 ,13... 给你一个整数, 求出它的值是多少   

    思路分析
    1.当n = 1, 斐波那契数 是1
    2.当n = 2, 斐波那契数 是1
    3.当n >= 3, 斐波那契数 是前两个数的和
    4.这里就是一个递归的思路
     */
    public int fibonacci(int n) {
        if (n >= 1) {
            if (n == 1 || n == 2) {
                return 1;
            } else {
                return fibonacci(n-2) + fibonacci(n-1);
            }     
        } else {
            System.out.println("要求输入n>=1的整数");
            return -1;
        }
    }
}

2.猴子吃桃子问题: 有一堆桃子, 猴子第一天吃了其中的一半, 并再多吃了一个! 以后每天猴子都吃其中的一半, 然后再多吃一个. 当到第10天时, 想再吃的时候(即还没有吃), 发现只有1个桃子了. 问题: 最初共多少个桃子? RecursionExercise02.java

public class RecursionExercise02 {

	//编写一个main方法
	public static void main(String[] args) {
        T t1 = new T();
        //桃子问题
        int day = 1;
        int peachNum = t1.peach(day);
        if (peachNum != -1) {
            System.out.println("第" + day + "天有" + peachNum + "个桃子");
        }
	}
}

class T {
    /*
    猴子吃桃子问题: 有一堆桃子, 猴子第一天吃了其中的一半, 并再多吃了一个! 
    以后每天猴子都吃其中的一半, 然后再多吃一个. 当到第10天时, 
    想再吃的时候(即还没有吃), 发现只有1个桃子了. 问题: 最初(第1天)共多少个桃子?

    思路分析 逆推
    1. day = 10 时 有 1个桃
    2. day = 9  时 有 (day10 + 1) * 2 = 4
    3. day = 8  时 有 (day9  + 1) * 2 = 10
    4. 规律就是  前一天的桃子 = (后一天的桃子 + 1) * 2 //就是我们的能力
    5. 递归
     */
    
    public int peach(int day) {
        if (day == 10) {//第10天, 只有1个桃
            return 1;
        } else if (day >= 1 && day <= 9) {
            return (peach(day + 1) + 1) * 2;//规则, 自己要想
        } else {
            System.out.println("day在1-10");
            return -1;
        }
    }
}

迷宫问题

●递归调用应用实例 - 迷宫问题 MiGong.java

●说明
迷宫用二维数组来表示, 假设左上角有一个小球, 要求小球能够走到右下角, 即找到出迷宫的路径.

●分析
1.小球得到的路径, 和程序员设置的找路策略有关系. 即: 找路的上下左右的顺序相关
2.再得到小球路径时, 可以先使用(下右上左), 再改成(上右下左), 看看路径是不是有变化
3.测试回溯现象
4.扩展思考: 如何求出最短路径? 思路(1)穷举法 (2)图->求出最短路径

1.模拟地图
1.案例
在这里插入图片描述

2.实际效果
在这里插入图片描述

public class MiGong {

	//编写一个main方法
	public static void main(String[] args) {
        //思路
        //1.先创建迷宫, 用二维数组表示 int[][] map = new int[8][7];//8行 7列
        //2.先规定 map 数组的元素值: 0 表示可以走 1表示障碍物

        int[][] map = new int[8][7];//8行 7列
        //3.将最上面的一行和最下面的一行 全部设置为1
        for (int i = 0; i < 7; i++) {
            map[0][i] = 1;
            map[7][i] = 1;
        }
        //4.将最左面的一列和最右面的一列 全部设置为1
        for (int i = 0; i < 8; i++) {
            map[i][0] = 1;
            map[i][6] = 1;
        }
        //5.单独设置障碍物
        map[3][1] = 1;
        map[3][2] = 1;

        //输出当前的迷宫
        for (int i = 0; i < map.length; i++) {
            for (int j = 0; j < map[i].length; j++) {
                System.out.print(map[i][j] + " ");//输出一行
            }
            System.out.println("");
        }
    }
}

2.下右上左策略
●代码实现

public class MiGong {

	//编写一个main方法
	public static void main(String[] args) {
		...
		
        //使用findWay给老鼠找路
        T t1 = new T();
        t1.findWay(map, 1, 1);

        System.out.println("\n===找路的情况如下===");
        for (int i = 0; i < map.length; i++) {
            for (int j = 0; j < map[i].length; j++) {
                System.out.print(map[i][j] + " ");//输出一行
            }
            System.out.println("");
        }
    }
}

class T {

    //使用递归回溯的思想来解决老鼠出迷宫
    
    //老师解读
    //1. findWay方法就是专门来找 出迷宫的路径
    //2. 如果找到, 就返回 true, 否则返回false
    //3. map 就是二维数组, 即表示迷宫
    //4. i, j 就是老鼠的位置, 初始化的位置为(1,1)
    //5. 因为我们是递归地找路, 所以先规定好 map数组的各个值的含义
    //   0表示可以走 1表示障碍物 2表示可以走通 3表示走过, 但是走不通是死路
    //6. 当map[6,5]= 2 就说明找到通路, 就可以结束, 否则就继续找.
    //7. 先确定老鼠找路策略 下->右->上->左
    public boolean findWay(int[][] map, int i, int j) {
        if (map[6][5] == 2) {//说明已经找到
            return true;
        } else {
            if (map[i][j] == 0) {//当前这个位置为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 {
                    map[i][j] = 3;
                    return false;
                }

            } else {//map[i][j] = 1, 2, 3
                return false;
            }
        }
    }
}

●下右上左: 模拟结果

在这里插入图片描述

●下右上左 :实际结果

在这里插入图片描述

如果还是没有看懂, 那么这里举一个非常极端的例子. 比如我们把 (2,1) (2,2) (1,2) 设置成障碍物.

map[2][1] = 1;
map[2][2] = 1;
map[1][2] = 1;

此时小球上下左右都走不通, 四个方法都返回false, 只能进入else分支, (1,1)这个位置被置为3, 最后返回false, 意味着闯关失败

模拟结果
在这里插入图片描述

实际结果
在这里插入图片描述

●这里最关键的地方有几点
1.一定要找到什么时候退出
2.先要去制定一个规则, 即什么数据表示什么含义, 这个要准备好
3.还要有一个策略. 那么如果策略不同会不会对结果有影响?

3.上右下左策略
这里改变一下策略, 看一看策略变化了会不会有影响

修改找路策略: 上->右->下->左

//修改找路策略, 看看路径是否有变化
//下->右->上->左 ==> 上->右->下->左
public boolean findWay2(int[][] map, int i, int j) {
    if (map[6][5] == 2) {//说明已经找到
        return true;
    } else {
        if (map[i][j] == 0) {//当前这个位置为0, 说明可以走
            //我们假定可以走通
            map[i][j] = 2;
            //使用找路策略, 来确定该位置是否真的可以走通
            //上->右->下->左
            if (findWay2(map, i - 1, j)) {//先尝试走上
                return true;
            } else if (findWay2(map, i, j + 1)) {//再尝试走右
                return true;
            } else if (findWay2(map, i + 1, j)) {//再尝试走下
                return true;
            } else if (findWay2(map, i, j - 1)) {//再尝试走左
                return true;
            } else {
                map[i][j] = 3;
                return false;
            }

        } else {//map[i][j] = 1, 2, 3
            return false;
        }
    }
}

●上右下左: 模拟结果
在这里插入图片描述

●上右下左: 实际运行结果
在这里插入图片描述


4.测试回溯

map[3][1] = 1;
map[3][2] = 1;
/*
map[2][1] = 1;
map[2][2] = 1;
map[1][2] = 1;
*/
map[2][2] = 1;//测试回溯

●模拟结果

在这里插入图片描述

●实际结果

在这里插入图片描述

汉诺塔

●递归调用应用实例-汉诺塔
在这里插入图片描述

√ 汉诺塔传说
汉诺塔: 汉诺塔(又称河内塔) 问题是源于印度一个古老传说的益智玩具. 大梵天创造世界的时候做了三根金刚石柱子, 在一根柱子上从下往上按照大小顺序摞着64片圆盘. 大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上. 并且规定, 在小圆盘上不能放大圆盘, 在三根柱子之间一次只能移动一个圆盘.

假如每秒钟移动一次, 共需多长时间呢? 移完这些金片需要5845.54亿年以上, 太阳系的预期寿命据说也就是数百亿年. 真的过了5845.54亿年, 地球上的一切生命, 连同梵塔, 庙宇等, 都早已经灰飞烟灭.


√ 汉诺塔代码实现 HanoiTower.java

汉诺塔小游戏

public class HanoiTower {

	//编写一个main方法
	public static void main(String[] args) {

        Tower tower = new Tower();
        tower.move(5, 'A', 'B', 'C');
	}
}

class Tower {

    //方法
    //num 表示要移动的个数 a, b, c 分别表示A塔, B塔, C塔
    public void move(int num, char a, char b, char c) {
        //如果只有一个盘 num = 1
        if (num == 1) {
            System.out.println(a + "->" + c);
        } else {
            //如果有多个盘, 可以看成两个, 最下面的和上面的所有盘(num - 1)
            //(1)先移动上面所有的盘到 b, 借助 c
            move(num - 1, a, c, b);
            //(2)把最下面的这个盘, 移动到 c
            System.out.println(a + "->" + c);
            //(3)再把 b塔的所有的盘, 移动到c, 借助a
            move(num - 1, b, a, c);
        }
    }
}

八皇后

●递归调用应用实例-八皇后问题 EightQueens.java
在这里插入图片描述

√ 八皇后问题说明
八皇后问题, 是一个古老而著名的问题, 是回溯算法的典型案例. 该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出: 在8 x 8格的国际象棋上摆放八个皇后, 使其不能互相攻击. 即: 任意两个皇后都不能处于同一行, 同一列或同一斜线上, 问有多少种摆法.

√ 八皇后思路分析
1.第一个皇后先放在第一行第一列
2.第二个皇后放在第二行第一列, 然后判断是否OK, 如果不OK, 继续放在第二列, 第三列, 依次把所有列都放完, 找到一个合适的位置.
3.继续第三个皇后, 还是第一列, 第二列…直到第八个皇后也能放在一个不冲突的位置, 算是找到了一个正确解.
4.当得到一个正确解时, 在栈回退到上一个栈时, 就会开始回溯, 即将第一个皇后, 放到第一行的所有正确解, 全部得到.
5.然后回头继续第一个皇后放第一行第二列, 后面继续循环执行1, 2, 3, 4的步骤


说明: 理论上应该创建一个二维数组来表示棋盘, 但是实际上可以通过算法, 用一个一维数组即可解决问题.
arr[8] = {0, 4, 7, 5, 2, 6, 1, 3}; //对应arr 下标 表示第几行, 即第几个皇后; arr[i] = val, val 表示第i + 1个皇后, 放在第i + 1行的第val + 1列

代码未完成

public class EightQueens {

    //编写一个main方法
    public static void main(String[] args) {
        /*
        说明: 
        理论上应该创建一个二维数组来表示棋盘, 但是实际上可以通过算法, 用一个一维数组即可解决问题. 
        arr[8] = {0, 4, 7, 5, 2, 6, 1, 3}; //对应arr 下标 表示第几行, 即第几个皇后; arr[i] = val, val 表示第i + 1个皇后, 放在第i + 1行的第val + 1列             
         */
        int[] arr = new int[8];

        //使用findPosition找到八皇后的位置
        MyTools myTools = new MyTools();
        myTools.findPosition(arr, 1);

        //输出arr
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + " ");
        }
    }
}

class MyTools {

    /*
    思路
    1.第一个皇后先放在第一行第一列
    2.第二个皇后放在第二行第一列, 然后判断是否OK, 如果不OK, 继续放在第二列, 第三列, 依次把所有列都放完, 找到一个合适的位置.
    3.继续第三个皇后, 还是第一列, 第二列.....直到第八个皇后也能放在一个不冲突的位置, 算是找到了一个正确解.
    4.当得到一个正确解时, 在栈回退到上一个栈时, 就会开始回溯, 即将第一个皇后, 放到第一行的所有正确解, 全部得到.
    5.然后回头继续第一个皇后放第一行第二列, 后面继续循环执行1, 2, 3, 4的步骤
     */
    
    //使用递归回溯的思想来解决八皇后问题
    //1. findPosition方法就是专门来找 八皇后位置
    //2. 如果找到, 就返回 true, 否则返回false
    //3. arr 就是一维数组, 表示八皇后摆放的位置
    //4. 初始化的位置arr[0] = 0, 表示第一个皇后放在第一行第一列
    //
    
    //arr[i] 表示第i+1行第arr[i]+1列元素
    public boolean findPosition(int[] arr, int i) {
        if (arr[7] != 0) {//说明已经找到所有八皇后位置
            return true;
        } else {
            for (int j = 0; j < i; j++) {//j 遍历之前的每一行
                if (arr[i] == arr[j] || //不能在同一列
                    //任意两个不能在一条斜线上(即行数之差不能等于列数之差)
                    i- j == Math.abs(arr[i] - arr[j])) {
                    if (arr[i] >= 7) {
                        return false;
        ![在这里插入图片描述](https://img-blog.csdnimg.cn/9780d02eb68b4117bdcdcbc96194cb6e.png)
            }
                    arr[i] += 1;
                    return findPosition(arr, i);
                }
            }
            //假定k放置成功
            return findPosition(arr, i + 1);
        }
    }   
}

第七章 面向对象编程(基础) 下

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

~ 小团子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值