0
点赞
收藏
分享

微信扫一扫

day6 面向对象编程基础(上)

第6章 面向对象编程(初级)

目录

  • 类与对象
  • 成员方法*
  • 成员方法传参机制*
  • 重载(overload)
  • 可变参数
  • 作用域
  • 构造器*
  • this

类与对象

引出

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

  • 现有技术解决Object01.java
    1.单独的定义变量解决
    2.使用数组解决

  • 现有技术解决的缺点分析
    1.不利于数据的管理
    2.效率低

  • package chapter07.OOP;
    
    public class Object01 {
        public static void main(String[] args) {
            /*
            张老太养了两只猫猫:一只名字叫小白,今年3岁,白色。
            还有一只叫小花,今年100岁,花色。
            请编写一个程序,当用户输入小猫的名字时,就显示该猫的名字,年龄,颜色。
            如果用户输入的小猫名错误,则显示张老太没有这只猫猫。
             */
            //单独变量来解决-->不利于数据管理 (原因:就是把一只猫的信息拆解)
            String cat1Name = "小白";
            int cat1Age = 3;
            String cat1Color = "白色";
    
            String cat2Name = "小花";
            int cat2Age = 100;
            String cat2Color = "花色";
    
            //数组-->(1)数据类型体现不出来(2)只能通过下标获取信息,造成变量和内容
            //       对应关系不明确(3)不能体现猫的行为
            String[] cat1 = {"小白", "3", "白色"};
            String[] cat2 = {"小花", "100", "花色"};
    
    
        }
    }
    
  • 引出
    类与对象(OOP),根本原因就是现有的技术不能完美的解决现有问题

概述

  • 导入
    一个程序就是一个世界,有很多事物(对象[属性,行为])–>类是具有相同特征事物的集合,对象是类的实例

  • 类与对象关系示意图

    在这里插入图片描述

快速入门

  • 类与对象解决上述猫类问题

    package chapter07.OOP;
    
    public class Object01 {
        public static void main(String[] args) {
            /*
            张老太养了两只猫猫:一只名字叫小白,今年3岁,白色。
            还有一只叫小花,今年100岁,花色。
            请编写一个程序,当用户输入小猫的名字时,就显示该猫的名字,年龄,颜色。
            如果用户输入的小猫名错误,则显示张老太没有这只猫猫。
             */
    
            //使用OOP来解决
            //实例化一只猫
            /*
            解读:1.new Cat() 创建一只猫
            2.Cat cat_1 = new Cat(); 把创建的猫赋给cat_1
            3.cat_1就是一个对象
             */
            Cat cat_1 = new Cat();
            cat_1.name = "小白";
            cat_1.age = 3;
            cat_1.color = "白色";
    
            Cat cat_2 = new Cat();
            cat_2.name = "小花";
            cat_2.age = 100;
            cat_2.color = "花色";
    
            //怎么来访问对象的属性
            System.out.println("第一支猫信息 " + cat_1.name + " "
                    + cat_1.age + " " + cat_1.color);
            System.out.println("第二支猫信息 " + cat_2.name + " "
                    + cat_2.age + " " + cat_2.color);
        }
    }
    //=============================下面是Cat类
    //类与对象解决养猫问题
    //定义一个猫类 Cat --> 自定义类型
    class Cat {
        //属性
        String name;//年龄
        int age;//名字
        String color;//颜色
        //还可以增加行为(方法)
    }
    
  • 类和对象的区别和联系
    通过上面的案例和讲解我们可以看出:
    1.类是抽象的,概念的,代表一类事物,比如人类,猫类… 即它是数据类型
    2.对象是具体的,实际的,代表个具体事物, 即是实例
    3.类是对象的模板,对象是类的一个个体, 对应一个实例

对象内存布局

  • 对象在内存中存在形式*

    Cat cat = new Cat();
    cat.name = "小白";//本质上字符串就是引用类型
    cat.age = 12;
    cat.color = "白色";
    
  • 示意图

    在这里插入图片描述

类定义的完善

  • 示意图

    在这里插入图片描述

对象

创建对象访问属性

  • 如何创建对象
    1.先声明再创建

    Cat cat;//声明对象 cat
    cat = new Cat();//创建
    

    2.直接创建

    Cat cat = new Cat();
    

    3.有空间就有地址

  • 如何访问属性
    基本语法:对象名.属性名;
    案例演示:赋值和输出

    cat.name;
    cat.age;
    cat.color;
    

对象分配机制

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

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

    Person p1 = new Person();
    p1.age = 10;
    p1.name = "小明";
    Person p2 = p1;//p1赋给了p2
    System.out.println(p2.age);
    //请问:p2.age究竟是多少?并画出内存图
    //10 让p2指向p1 地址传递
    

    内存图

    在这里插入图片描述

  • 类和对象的内存分配机制

    Java内存的结构分析

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

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

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

1.先加载类信息(属性和方法信息,只会加载一次)
2.在堆中分配空间,进行默认初始化(规则参照数组)
3.把地址赋给p,p就指向对象
4.进行指定初始化,比如p.name = "jack"等

对象机制练习

  • 练习题,并分析画出内存布局图,进行分析

  • 我们看看下面一段代码,会输出什么信息:

  Person a = new Person();
  a.age = 10;
  a.name = "小明";
  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);//空指针异常 nullpointexception
  • 在这里插入图片描述

属性(成员变量)

  • 基本介绍

    1.从概念或叫法上看:成员变量 = 属性 = field(字段)(即成员变量是用来表示属性的,授课中,统一叫属性)
    案例演示:Car(name,price,color) Object.02
    2.属性是类的一个组成部分,一般是基本数据类型,也可是引用类型(对象,数组),比如我们前面定义猫类的int age就是属性

  • 注意事项和细节说明PropertiesDetail.java

    1.属性的定义语法同变量,示例:访问修饰符 属性类型 属性名;
    **访问修饰符:**控制属性的访问范围。4种访问修饰符:public,protected,默认(default),private
    2.属性的定义类型可以为任意类型,包含基本类型或引用类型
    3.属性如果不赋值,有默认值,规则和数组一致。int 0, short 0, byte 0, long 0, float 0.0, double 0.0, char \u0000, boolean false, String null

  • 案例演示:[Person类]

    package chapter07.OOP;
    
    public class PropertiesDetail {
        public static void main(String[] args) {
            //p1对象名(对象引用) new P()创建的对象空间才是真正的对象
            Person p1 = new Person();
    
            //对象的默认值,遵守数组的规则
            //int 0, short 0, byte 0, long 0, float 0.0, double 0.0, char \u0000, boolean false, String null
            System.out.println("\n当前人的信息");
            System.out.println("age=" + p1.age + " name=" + p1.name
                        + " sal=" + p1.sal + " ispass=" + p1.ispass);
    
        }
    }
    
    class Person {//4个属性(四个成员变量)
        int age;
        String name;
        double sal;
        boolean ispass;
    }
    
  • 示意图:

    在这里插入图片描述

成员方法

前言

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

快速入门

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

  • 方法的调用机制原理:重要! 示意图
    提示:画出程序执行过程[cal02]+说明

  • package chapter07.OOP;
    
    public class Method01 {
        public static void main(String[] args) {
            //1.方法写好后,入伙不去调用,就不会输出
            //2.先创建一个对象,然后再调用其方法即可
            Person01 p01 = new Person01();
            p01.speak();//调用方法
            p01.cal01();//调用cal01方法
            p01.cal02(5);//调用cal01方法 5代表实际参数
            //实参传给形参,是值传递,返回的也是一个值,所以需要一个变量接收
            //把方法getSum返回的值,赋给 变量returnRes 保存
            int returnRes = p01.getSum(10,20);//调用getSum 10,20分别代表输入的两个实参
            System.out.println("getSum方法返回的值=" + returnRes);
        }
    
    }
    
    class Person01 {
        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 sum = 0;
            for (int i = 0; i <= 1000; i++) {
                sum += i;
            }
            System.out.println("cal01方法计算结果=" + sum);
        }
        //添加cal02 成员方法,该方法可以接收一个数n,计算从1+...+n的结果
        //(int 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 1 int 2) 形式列表 2个形参,可以接收两个输入
        5.return res; 代表 res 的值,返回给调用者
         */
        public int getSum (int num1, int num2) {
            int res = num1 + num2;
            return res;
        }
    
    }
    
  • 方法调用机制示意图(getSum)

    1.堆里有对象是因为有new person()相当于一个p1的对象空间或对象数据,而p1是对象的名字
    2.getSum栈叫栈帧
    3.类似与c语言的函数调用

    在这里插入图片描述

必要性

  • 为什么需要方法Method02.java
    请遍历一个数组,输出数组的各个元素值–>int[] [] = { {0,0,1}, {1,1,1}, {1,1,3} };
    1.解决思路1:传统的方法,就是使用单个for循环,将数组输出,大家看看问题是什么?
    2.解决思路2:定义一个类MyTools然后写一个成员方法,调用方法实现,看看效果又如何?

  • 成员方法的好处

    1.提高代码的复用性
    2.可以将实现的细节封装起来,然后供其他用户来调用即可。

  • package chapter07.OOP;
    
    public class Method02 {
        public static void main(String[] args) {
            //遍历一个二维数组
            int[][] map = {{0, 0, 1}, {1, 1, 1}, {1, 1, 3}};
            //传统:遍历数组就是for循环
            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();
            }
            //要求再次遍历map数组时,粘贴复制
    
            //调用方法来输出,先创建一个MyTools对象
            MyTools myTools = new MyTools();
            myTools.printArr(map);
            System.out.println("======");
            myTools.printArr(map);
    
        }
    }
    //把输出的功能,写到一个类的方法中,然后调用方法即可
    class MyTools {
        //方法,接收一个二维数组
        public void printArr(int[][] 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] + " ");
                }
                System.out.println();
            }
        }
    }
    

详细定义

  • 成员方法的定义

    public(访问修饰符) 返回数据类型 方法名 (形参列表..) {
        //方法体语句;
        return返回值;
    }
    

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

注意事项和细节

  • 注意事项MethodDetail.java

    1.访问修饰符(作用:是控制方法使用的范围)

    • 可选:不写默认访问,写的话有四种:public/protected/private/默认–>后面具体再说

    2.返回数据类型

    • 1.一个方法最多有只能有一个返回值–>思考:如何返回多个结果?
    • 2.返回类型可以为任意类型,包含基本类型或引用类型(数组,对象)
    • 3.如果方法要求有返回数据类型,则方法体中最后的执行语包必须为return值;而且要求返回值类型必须和return的值类型一致或兼容
    • 4.如果方法是void,则方法体中可以没有return语,或者只写return;

    3.方法名

    • 遵循驼峰命名法,最好见名知义,表达出该功能的意思即可,比如得到两个数的和getSum,开发中按照规范
  • package chapter07.OOP;
    
    public class MethodDetail {
        public static void main(String[] args) {
            //1.一个方法最多有只能有一个返回值-->思考:如何返回多个结果?
            //可以返回数组
            AA a = new AA();
            int[] res = a.getSumAndSub(1,4);
            System.out.println("和等于" + res[0] + "\t差等于" + res[1]);
    
            //2.看上述操作就是引用类型
            //3.如果方法要求有返回数据类型,则方法体中最后的执行语包必须为return值;而且要求返回值类型必须和return的值类型一致或兼容
            //4.
        }
    }
    
    class AA {
        public int[] getSumAndSub(int n1, int n2) {
            int [] resArr = new int[2];//创建一个数组
            resArr[0] = n1 + n2;//第一个
            resArr[1] = n1 - n2;
            return resArr;
        }
    
        public double num() {//接收的
            double d = 1.1 * 3;
            int i = 100;
            return i;//可
            //return i;//int-->double可 ,但double-->int不可不兼容
        }
        
        public void f2(){
            System.out.println("你好,世界");
            return;//(因为f2形参后无返回值,所以return后无法加具体值)
        }
        
    }
    

    4.形参列表

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

    5.方法体

    • 里面写完成功能的具体的语句,可以为输入、输出、变量、运算、分支、循环、方法调用,但里面不能再定义方法,即:方法不能嵌套定义。[演示]
  • 方法调用细节说明MethodDetail02.java

    • 1.同一个类中的方法调用:直接调用即可。比如print(参数);–>案例演示:A类sayOk调用print()
    • 2.跨类中的方法A类调用B类方法:需要通过对象名调用。比如对象名.方法名(参数);–>案例演示:B类sayHello调用print()
    • 3.特别说明一下:跨类的方法调用和方法的访问修饰符相关,先暂时这么提一下,后面我们讲到访问修饰符时,还要再细说。

练习题

  • MethodExercise01.java
    1.编写类C,有一个方法:判断一个数是奇数odd还是偶数,返回boolean
    2.根据行、列、字符打印对应行数和列数的字符,比如:行:4,列:4,字符#,则打印相应的效果

  • package chapter07.OOP;
    
    public class MethodExercise01 {
        public static void main(String[] args) {
    
            C c = new C();
            if (c.isOdd(1)) {//T 这样的写法很多
                System.out.println("是奇数");
            }else{
                System.out.println("是偶数");
            }
    
            c.print(10,10,'0');
            
        }
    }
    
    class C {
        //方法书写步骤:
        /*方法的返回类型 boolean
        方法名          isOdd
        方法形参     (int num)
        方法体 判断
         */
        public boolean isOdd(int num) {
            /*
            if(num % 2 != 0) {
                return true;
            } else {
                return false;
            }//比较啰嗦
             */
            //可以改写成三元运算符
            //return num % 2 != 0 ? true : false;
            return num % 2 != 0;//甚至直接写不等于0即可
        }
    
        //根据行、列、字符打印对应行数和列数的字符,
        //比如:行:4,列:4,字符#,则打印相应的效果
        /*
        ####
        ####
        ####
        ####
        方法的返回类型   void
        方法名       print
        方法形参     (int row, int col, char c)
        方法体       循环
        */
        public void print(int row, int col, char c) {
            for (int i = 0; i < row; i++) {
                for (int j = 0; j < col; j++) {//输出每一行
                    System.out.print(c);
                }
                System.out.println();//换行
            }
        }
    
    }
    

成员方法传参机制

基本数据类型传参

  • MethodParameter01.java

    思考:方法的传参机制对我们今后的编程非常重要,一定要搞的清清楚楚明明白白。通过案例来学习基本数据类型的传参机制。

    1.看一个案例,分析结果是什么?

    public void swap(int a,int b) {
        int tmp = a;
        a = b;
        b = tmp;
        System.out.println("a=" + a + "\tb=" + b);
    }
    

    2.结论及示意图

    package chapter07.OOP;
    
    public class MethodParameter01 {
        public static void main(String[] args) {
            int a = 10;
            int b = 20;
            //创建AAA对象
            BB bb = new BB();
            bb.swap(a,b);//调用swap
            System.out.println("主方法a=" + a + "\tb=" + b);//10,20
            
        }
    }
    
    //自己写一个类 AAA 然后把交换方法放入到a和b中
    class BB{
        public void swap(int a,int b) {
            System.out.println("交换前a=" + a + "\tb=" + b);//10,20
            int tmp = a;
            a = b;
            b = tmp;
            System.out.println("交换后a=" + a + "\tb=" + b);//20,10
        }
    }
    
  • 在这里插入图片描述

  • 基本数据类型,传递的是值(即值拷贝),形参的任何改变都不影响实参!

引用数据类型传参

  • MethodParameter02.java
    1.看案例:

    1.1 D类中编写一个方法test100,可以接收一个数组,在方法中修改该数组,看看原来的数组是否变化?

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

2.结论及示意图

答:引用类型传递的是地址(传递也是值,但是值是地址) ,可以通过形参影响实参!

3.在看一个案例,下面的方法会对原来的对象有影响吗?

3.1 p = null

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

回答:会变化。

  • //1.1
    package chapter07.OOP;
    
    public class MethodParameter02 {
        public static void main(String[] args) {
            //测试
            D d = new D();//创建对象
            int[] arr = {1,2,3};//初始化
            d.test100(arr);//调用方法
            System.out.println("main的数组");
            //遍历主方法(main) 中数组
            for (int i = 0; i < arr.length; i++) {
                System.out.print(arr[i] + " ");
            }
            System.out.println();
    
        }
    }
    
    class D {//类的设置
        //编写一个方法test100
        //可以接收一个数组,在方法中修改该数组,看看原来的数组是否变化
        public void test100(int[] arr) {
            arr[0] = 200;//修改了第一个元素
            //遍历数组
            System.out.println("test100的数组");
            for (int i = 0; i < arr.length; i++) {
                System.out.print(arr[i] + " ");
            }
            System.out.println();
        }
    }
    
  • 1.1 示意图
    在这里插入图片描述

  • 1.1说明:

    1.main方法在主栈中new了一个指向堆中的对象
    2.int[] arr = {};即指向堆中的一个地址如ox1122(内存三个,用来存放初始值1,2,3)
    3.b.test100(arr);属于调用方法会在main主栈中生成一个新栈(独立的空间,即栈帧)–>并指向栈帧
    4.而test100中有是涉及形参arr的调用(此处形参int[] arr又为数组,是引用类型,采用地址传递就会把栈帧arr指向–>[ox1122])
    5.下面又把地址中的arr[0] = 100;这会直接导致地址中第一元素的改变
    6.后又遍历数组,即ox1122下的{200,2,3}
    7.回到主方法的arr,也遍历相同地址的数ox1122 为{200,2,2}

  • 1.2 D类中编写一个方法test200,可以接收一个Person1(age,sal)对象,在方法中修改该对象属性

    在这里插入图片描述

  • 3.1 如果在 p = null,结果输出仍然为10。

    在这里插入图片描述

  • 3.2 如果 p = new person1();结果也是10。

    在这里插入图片描述

  • package chapter07.OOP;
    
    public class MethodParameter02 {
        public static void main(String[] args) {
            //测试1.1
            D d = new D();
    
            //测试1.2 在主方法中new一个person1对象 /3.1 /3.2
            Person1 p = new Person1();//main主方法中的p和class D中的p不同
            p.name = "yuri";
            p.age = 10;
            d.test200(p);
            System.out.println("main 的p.age=" + p.age);
    
        }
    }
    
    class Person1 {
        String name;
        int age;
    }
    
    class D {
        //编写一个test200
        public void test200(Person1 p) {
            //1.2 修改对象属性
            //p.age = 10000;
    
            //3.1 形参置空结果还是10,并未影响实参的变化
            //p = null;
    
            //3.2 如果将 p = new person1();结果又是怎么样?
            //结果也是10
            p = new Person1();
            p.name = "tom";
            p.age = 99;
        }
    }
    

传参练习题(克隆对象)

  • 成员方法返回类型是引用类型应用实例
    MethodExercise02.java
    1.编写类MyTools类,编写一个方法可以打印二维数组的数据。
    2.编写一个方法copyPerson,可以复制一个Person对象,返回复制的对象。克隆对象,注意要求得到新对象和原来的对象是两个独立的对象,只是他们的属性相同。

  • package chapter07.OOP;
    
    import javax.tools.Tool;
    
    public class MethodExercise02 {
        public static void main(String[] args) {
        //编写一个方法copyPerson,可以复制一个Person对象,返回复制的对象。
        //克隆对象,注意要求得到新对象和原来的对象是两个独立的对象,只是他们的属性相同。
    
            //创建p对象
            Person p = new Person();
            p.name = "yuri";
            p.age = 18;
            //创建tools对象
            MyTool tool = new MyTool();
            Person p2 = tool.copyPerson(p);//调用方法,然后这儿是实际参数为p,为上面的p. 将得到的p赋值给p2
            //此时的 p 和 p2 是Person对象,但是是两个独立的对象,属性相同
            System.out.println("p的属性 age=" + p.age + " 名字=" + p.name);//地址不同!
            System.out.println("p2的属性 age=" + p2.age + " 名字=" + p2.name);
            //提示:可以通过 输出对象的比较(hashcode or ==)看看对象是否是同一个
            System.out.println(p == p2);//false
        }
    }
    //一、先创建一个Person类(在propertiesDetail中已创建,就不能重复创建了)
    // class Person {
    //      String name;
    //      int age;
    //    }
    
    //二、编写copyP的思路
    class MyTool {
        //1.方法的返回类型 Person(因为是要得到一个复制的Person新对象)
        //2.方法的名字 copyPerson
        //3.方法的形参 (Person p)(因为是要复制一个Person,所以需要原来的Person的p对象)
        //4.方法体 创建一个新对象,并复制属性,返回即可
        public Person copyPerson(Person p) {//这儿这个p名字可以任意改变
            //创建一个新对象
            Person p2 = new Person();
            p2.name = p.name;//把原来对象的名字赋给p2
            p2.age = p.age;//把原来对象的年龄赋给p2
            //此处不直接将p2 = p,是因为要求地址不同,里面的方法和属性自然就不同了
            return p2;
        }
    }
    
  • 示意图

    在这里插入图片描述

方法递归调用

递归解决什么问题

  1. 各种数学问题如:8皇后问题,汉诺塔,阶乘问题,迷宫问题,球和篮子问题(google编程大赛)[简单演示]
  2. 各种算法中也会使用到递归, 比如快排,归并排序,二分查找,分治算法等
  3. 将用栈解决的问题–>递归代码比较简洁
  • 递归调用应用实例–>八皇后问题[同学们先尝试做,后面老师评讲]
    八皇后问题说明:八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例。该问题是国际西洋棋棋手
    马克斯·贝瑟尔于1848年提出:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即:任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。

递归执行机制

  • 递归举例一:列举两个小案例,来帮助大家理解递归调用机制
    1.打印问题
    2.阶乘问题

  • 1.打印问题//递归调用的本质仍然是方法调用

    package chapter07.Recursion;
    
    public class Recursion01 {
        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);
        }
    
    
  • 示意图//在从栈返回时由于调用的是test方法,需要完整的走完每一个sout,即会在输出时从上到下以此输入每个独立栈中的n,即n=2,3,4;压栈后上面的栈空间也就依次释放不存在了。

    在这里插入图片描述

  • 升级讨论:如果在上述代码的T方法中在if (n > 2) {

    test (n - 1)

    }else{

    sout(“n=” + n)

    }//结果又会变成什么样?n = 2;因为在执行到第一个栈等于2之后,压到下一个栈时由于已经在开辟栈时已经执行过if else语句(只是之前只是没有满足else语句而已),所以不会再对else里面的语句执行了。

  • 2.阶乘问题

    package chapter07.Recursion;
    
    public class Recursion01 {
        public static void main(String[] args) {
            T t1 = new T();
            t1.test(4);
            int res = t1.factorial(5);//返回了一个int,需要用一个int res接收一下
            System.out.println("res=" + res);
        }
    }
    
    //先写一个类
    class T {
        //分析下列代码
        public void test(int n) {
            if (n > 2) {
                test(n - 1);
            }//else { 修改成else后结果有会变成什么样
                System.out.println("n=" + n);
            //}
        }
    
        //将阶乘的方法写在 T 类(class)里面
        public int factorial(int n) {
            if (n == 1) {
                return 1;
            }else {
                return factorial(n -1) * n;
            }
        }
    
    }
    

    //谁调用返回给谁(这里的返回也可以理解为继续执行?)

    在这里插入图片描述

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

练习题

RecursionExercise01.java

  • 1.请使用递归的方式求出斐波那契数1,1,2,3,5,8,13…给你一个整数n,求出它的值是多少?

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

  • package chapter07.Recursion;
    
    public class RecursionExercise01 {
        public static void main(String[] args) {
            Tool tool = new Tool();
            System.out.println("当n=1时的斐波那契额数" + tool.fibonacci(1));
            //桃子问题
            int day = 1;
            int peachNum = tool.peach(day);
            if (peachNum != day) {
                System.out.println("第" + day + "天有" + peachNum + "个桃子");
            }
        }
    }
    
    class Tool {
        //用递归的方式计算一下斐波那契数的第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("要求输入的数有误");
                return n;//else的返回语句
            }
            //return n;//else的返回语句 放在里面or外面都可以
        }
        /*
        思路分析:每天吃一半再多加一个,逆向递推
        1.day = 10 有 1 个桃子
        2.day = 9  有 (day10 + 1) * 2 解方程的思路很好理解
        3.day = 8  有 (day9 + 1) * 2 解方程的思路很好理解-->设未知数和单位值
        4.规律就是:前一天的桃子数 =(后一天的桃子 + 1) * 2
        */
        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 day;
            }
        }
    
    }
    

Maze.java

  • 迷宫问题

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

  • 在这里插入图片描述

  • package chapter07.Recursion;
    
    public class Maze {
        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;//遍历第1行
                map[7][i] = 1;//遍历最后1行
            }
            //4.将最左边的一列和最右边的一列 全部设置为1
            for (int i = 0; i < 8; i++) {
                map[i][0] = 1;
                map[i][6] = 1;//或许可以直接在把map的for循环添加到上面的循环体中
            }
            //5.在[3][1]、[3][2]的位置
            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();
            }
    
            //使用findWay来找路
            T2 t2 = new T2();
            t2.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 T2 {
        //使用递归回溯的思想来解决老鼠出迷宫
        //1.findWay方法就是专门找出迷宫的路径
        //2.如果找到,就返回ture ,否则返回flase
        //3.map 就是二维数组,即代表迷宫
        //4.i,j就是老鼠初始下标为[1][1]的位置,而下标在[6][5]就代表走出迷宫
        //5.因为时递归找路,所以先规定map 数组的各个值的含义
        //0 可以走 1 障碍物 2 小红旗(代表可以走) 3 走过,但是路不同是死路
        //6.当[6][5]变成2时,就说明找到通路就可以退出了,否则就继续找
        //7.探测的策略,比如下->右->上->左
        public boolean findWay(int[][] map, int i, int j) {
            if (map[6][5] == 2) {
                return true;//入宫等于2,即说明等于真
            }else {
                if (map[i][j] == 0) {//当前位置为0,说明可以走
                    //假定可以走通,先把她设置成2
                    map[i][j] = 2;
                    //使用策略来确定该点是否真的可以走通
                    //下->右->上->左
                    if (findWay(map,i + 1,j)) {//先尝试着走下-->上:i-1
                        return true;
                    }else if (findWay(map, i, j + 1)) {//右
                        return true;
                    }else if (findWay(map, i - 1, j)) {//上-->下:i+1,其余不变
                        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;
                }
            }
        }
    
    }
    
  • 在这里插入图片描述

  • plus:将此处的[1] [1]上下左右都变成红色障碍,让他无法走通变成3

  • 3.回溯现象(出现一个限制红点进行障碍)

    package chapter07.Recursion;
    
    public class Maze {
        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;//遍历第1行
                map[7][i] = 1;//遍历最后1行
            }
            //4.将最左边的一列和最右边的一列 全部设置为1
            for (int i = 0; i < 8; i++) {
                map[i][0] = 1;
                map[i][6] = 1;//或许可以直接在把map的for循环添加到上面的循环体中
            }
            //5.在[3][1]、[3][2]的位置
            map[3][1] = 1;
            map[3][2] = 1;
            map[2][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();
            }
    
            //使用findWay来找路
            T2 t2 = new T2();
            t2.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 T2 {
        //使用递归回溯的思想来解决老鼠出迷宫
        //1.findWay方法就是专门找出迷宫的路径
        //2.如果找到,就返回ture ,否则返回flase
        //3.map 就是二维数组,即代表迷宫
        //4.i,j就是老鼠初始下标为[1][1]的位置,而下标在[6][5]就代表走出迷宫
        //5.因为时递归找路,所以先规定map 数组的各个值的含义
        //0 可以走 1 障碍物 2 小红旗(代表可以走) 3 走过,但是路不同是死路
        //6.当[6][5]变成2时,就说明找到通路就可以退出了,否则就继续找
        //7.探测的策略,比如下->右->上->左
        public boolean findWay(int[][] map, int i, int j) {
            if (map[6][5] == 2) {
                return true;//入宫等于2,即说明等于真
            }else {
                if (map[i][j] == 0) {//当前位置为0,说明可以走
                    //假定可以走通,先把她设置成2
                    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;
                }
            }
        }
        
    }
    

HanoiTower.java

汉诺塔传说

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

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

  • package chapter07.Recursion;
    
    public class HanoiTower {
        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);
            }
        }
    }
    

八皇后

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

  • 八皇后思路分析
    1.第一个皇后先放第一行第一列;
    2.第二个皇后放在第二行第一列,然后判断是否OK,如果不OK,继续放在第二列、第三列、依次把所有列都放完,找到一个合适位置;
    3.第三个皇后,还是第一列、第二列…直到第8个皇后也能放在一个不冲突的位置,算是找到了一个正确解;
    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列。

举报

相关推荐

第6章 面向对象编程基础

面向对象编程(上)

ARM day6

作业day6

Python Day6

0 条评论