38-面向对象编程(基础)2-(成员方法和递归)

成员方法

基本介绍

在某些情况下,我们需要定义成员方法(简称方法)。比如人类:除了有一些属性外(年龄,姓名),我们人类还有一些行为:可以说话跑步…通过学习可以做算术题。这时就要用到成员方法来完成

入门

请添加图片描述

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(10, 20); 
		System.out.println("getSum方法返回的值=" + returnRes);
	}
}

class Person {
	
	String name;
	int age;
	//方法(成员方法)
	//添加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() {
		//循环完成
		int res = 0;
		for(int i = 1; i <= 1000; i++) {
			res += i;
		}
		System.out.println("cal01方法 计算结果=" + res);
	}
	//添加cal02 成员方法,该方法可以接收一个数n,计算从 1+..+n 的结果
	//老韩解读
	//1. (int n) 形参列表, 表示当前有一个形参 n, 可以接收用户输入
	public void cal02(int n) {
		//循环完成
		int res = 0;
		for(int i = 1; i <= n; i++) {
			res += i;
		}
		System.out.println("cal02方法 计算结果=" + res);
	}

	//添加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;
	}
}

方法的调用机制原理示意图

请添加图片描述

成员方法的必要性

  • 多次遍历数组:传统方法:在一个程序中多次用for循环遍历数组,代码的复用性差
  • 定义一个类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();

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

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


		//...再次遍历
		//
		// for(int i = 0; i < map.length; i++) {
		// 	for(int j = 0; j < map[i].length; j++) {
		// 		System.out.print(map[i][j] + "\t");
		// 	}
		// 	System.out.println();
		// }
		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();
		}
	}
}

成员方法的定义

public 返回数据类型 方法名(形参列表){
	//方法体
	语句;
	return 返回值;
} 
  1. 形参列表:表示成员方法输入cal(int n),getSum(int num1 , int num2)
  2. 返回数据类型:表示成员方法输出,void表示没有返回值
  3. 方法主体:表示为例实现某一功能代码块
  4. return 语句不是必须的

成员方法的注意事项和使用细节

  • 修饰符(作用是控制方法使用范围):
    如果不写,默认访问(public ,protected , 默认, private)
  • 返回数据类型
  1. 一个方法最多有一个返回值
  2. 返回类型可以为任意数据类型包含基本类型或引用类型(数组,对象)
  3. 如果方法要求有返回数据类型,则方法中最后要执行的语句必须为return值,而且要求返回值类型必须如return的值类型一致或兼容
  4. 如果方法是void,则方法体可以没有return语句,或者只写return;
  • 方法名
    遵守驼峰命名法,最好见名知意,表达出该功能的意思即可,比如得到两个数之和:getSum
  • 形参列表
  1. 一个方法可以有0个参数,也可以有多个参数,中间用逗号隔开,比如getSum(int n1, int n2)
  2. 参数类型可以为任意类型
  3. 调用带参数的方法时,一定对应这参数列表传入相同类型或兼容的参数
  4. 方法定义时的参数称为形式参数,简称形参;方法调用时的传入参数称为实际参数,简称实参,实参和形参的类型要一致或兼容,个数顺序必须一致
  • 方法体
    里面写完成功能的具体的语句,可以输入,输出,变量,运算,分支,方法调用,但里面不能再定义方法,即方法不能嵌套定义
public class MethodDetail { 

	
	public static void main(String[] args) {

		AA a = new AA();
		int[] res = a.getSumAndSub(1, 4);
		System.out.println("和=" + res[0]);
		System.out.println("差=" + res[1]);


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

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 
	}

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

		System.out.println("hello1");
		System.out.println("hello1");
		System.out.println("hello1");
		int n = 10;
		//return ;
	}

}

方法调用细节说明

  • 同一个类中的方法调用:直接调用即可
  • 跨类中的方法A类调用B类方法:需要通过对象名调用
public class MethodDetail02 { 

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

		A a = new A();
		//a.sayOk();

		a.m1(); 
	}
}

class A {
	//同一个类中的方法调用:直接调用即可
	//
	
	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()被执行");
	}
}

成员方法传参机制

  • 基本数据类型的传参机制
    请添加图片描述
    结论:基本数据类型,传递值(值拷贝),形参的任何改变不影响实参
  • 引用数据类型的传参机制
    请添加图片描述
    结论:引用类型传递的是地址(值传递也是值,但是值是地址),可以通过形参影响实参
  • 总结:
    new 一个对象 ,在堆中开辟一个对象空间
    调用一次方法,栈中开辟一个方法空间
    在方法中的变量和main中的变量符号相同,但是也是两个变量,即使是引用类型传地址,也始终是两个便变量赋有相同地址而已

案例(克隆对象)

请添加图片描述

public class MethodExercise02 { 

	//编写一个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  + " 名字=" + p.name);
		System.out.println("p2的属性 age=" + p2.age  + " 名字=" + p2.name);
		//这里老师提示: 可以同 对象比较看看是否为同一个对象
		System.out.println(p == p2);//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;
	}
}

内存图:
请添加图片描述

方法递归调用

基本介绍

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

递归解决的问题

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

打印问题与阶乘问题

public class Recursion01 { 

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

		T t1 = new T();
		t1.test(4);//输出什么? n=2 n=3 n=4
		int res = t1.factorial(5); 
		System.out.println("5的阶乘 res =" + res);
	}
}

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

	//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,2,3.5,8,13…给你一个整数n,求出它的值是多少

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...给你一个整数n,求出它的值是多
		思路分析
		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-1) + fibonacci(n-2);
				}
			} else {
				System.out.println("要求输入的n>=1的整数");
				return -1;
			}
		}
	
}

猴子吃桃问题请添加图片描述

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);
		// } 
		// 
		//桃子问题
		int day = 0;
		int peachNum = t1.peach(day);
		if(peachNum != -1) {
			System.out.println("第 " + day + "天有" + peachNum + "个桃子");
		}


	}
}

class T {
		/*
		请使用递归的方式求出斐波那契数1,1,2,3,5,8,13...给你一个整数n,求出它的值是多
		思路分析
		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-1) + fibonacci(n-2);
				}
			} else {
				System.out.println("要求输入的n>=1的整数");
				return -1;
			}
		}

		/*
		猴子吃桃子问题:有一堆桃子,猴子第一天吃了其中的一半,并再多吃了一个!
		以后每天猴子都吃其中的一半,然后再多吃一个。当到第10天时,
		想再吃时(即还没吃),发现只有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;
			}
		}
	
}

老鼠迷宫

请添加图片描述

public class MiGong { 

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

		//思路
		//1. 先创建迷宫,用二维数组表示 int[][] map = new int[8][7];
		//2. 先规定 map 数组的元素值: 0 表示可以走 1 表示障碍物 
		
		int[][] map = new int[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;
		}
		map[3][1] = 1;
		map[3][2] = 1;
		map[2][2] = 1; //测试回溯 
		// map[2][1] = 1;
		// map[2][2] = 1;
		// map[1][2] = 1;

		//输出当前的地图
		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();
		}

		//使用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; 
			}
		}
	}

	//修改找路策略,看看路径是否有变化
	//下->右->上->左 ==> 上->右->下->左
	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; 
			}
		}
	}
}



汉诺塔

请添加图片描述

public class HanoiTower { 

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

		Tower tower = new Tower();
		tower.move(64, '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);
		}
	}
}

代码理解:
请添加图片描述
请添加图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值