37-面向对象编程(基础)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 返回值;
}
- 形参列表:表示成员方法输入cal(int n),getSum(int num1 , int num2)
- 返回数据类型:表示成员方法输出,void表示没有返回值
- 方法主体:表示为例实现某一功能代码块
- return 语句不是必须的
成员方法的注意事项和使用细节
- 修饰符(作用是控制方法使用范围):
如果不写,默认访问(public ,protected , 默认, private) - 返回数据类型
- 一个方法最多有一个返回值
- 返回类型可以为任意数据类型包含基本类型或引用类型(数组,对象)
- 如果方法要求有返回数据类型,则方法中最后要执行的语句必须为return值,而且要求返回值类型必须如return的值类型一致或兼容
- 如果方法是void,则方法体可以没有return语句,或者只写return;
- 方法名
遵守驼峰命名法,最好见名知意,表达出该功能的意思即可,比如得到两个数之和:getSum - 形参列表
- 一个方法可以有0个参数,也可以有多个参数,中间用逗号隔开,比如getSum(int n1, int n2)
- 参数类型可以为任意类型
- 调用带参数的方法时,一定对应这参数列表传入相同类型或兼容的参数
- 方法定义时的参数称为形式参数,简称形参;方法调用时的传入参数称为实际参数,简称实参,实参和形参的类型要一致或兼容,个数顺序必须一致
- 方法体
里面写完成功能的具体的语句,可以输入,输出,变量,运算,分支,方法调用,但里面不能再定义方法,即方法不能嵌套定义
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;
}
}
内存图:
方法递归调用
基本介绍
简单的说:递归就是方法自己调用自己,每次调用时传入不同的变量.递归有助于编程者解决复杂问题,同时可以让代码变得简洁
递归解决的问题
- 各种数学问题如:8皇后问题,汉诺塔,阶乘问题,迷宫问题,球和篮子的问题(googl
le编程大赛) - 各种算法中也会使用到递归,比如快排,归并排序,二分查找,分治算法等.
- 将用栈解决的问题–>递归代码比较简洁
打印问题与阶乘问题
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;
}
}
}
打印问题内存分布图:
阶乘问题的内存图:
递归重要规则
- 执行一个方法时,就创建一个新的受保护的独立空间(栈空间)
- 方法的局部变量是独立的,不会相互影响,比如n变量
- 如果方法中使用的是引用类型变量(比如数组),就会共享该引用类型的数据.
- 递归必须向退出递归的条件逼近,否则就是无限递归,出现StackOverflowError,死龟了
- 当一个方法执行完毕,或者遇到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;
}
}
}
猴子吃桃问题![请添加图片描述](https://i-blog.csdnimg.cn/blog_migrate/049368e39a51dcdce5f1e2d8149cce3e.png)
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);
}
}
}
代码理解: