【零基础学Java知识梳理超详细版】第六章-面向对象编程(基础)

【零基础学Java知识梳理超详细版】第六章-面向对象编程(基础)

面向对象是一种开发软件的方法,使分析、设计和实现一个系统的方法尽可能接近人们认识一个系统的方法。包括三个方面:面向对象分析、面向对象设计、面向对象程序设计。

Java 语言是纯面向对象的语言。其所有数据类型都有相应的类,程序可以完全基于对象编写。

6.1 类与对象(OOP)

类 就是数据类型。可以是 int 也可以是 人类

对象 就是其中具体的实例。可以是 100 也可以是 韩顺平

从 类 到 对象,可以称为 创建一个对象,也可以说 实例化一个对象,或者 把对象实例化

  1. 类 是抽象的、概念的,代表一类事物
  2. 对象 是具体的、实际的,代表一个个具体事物
  3. 类 是 对象 的模板,对象 是 类 的一个个体,对应一个实例

下面,我们定义了一个类 Cat 并创建了一些 对象 cat1 cat2

public class Code6_1{
	public static void main(String[] args){

		Cat cat1 = new Cat();
		cat1.name = "福福";
		cat1.age = 2;
		Cat cat2 = new Cat();
		cat2.name = "妞子";
		cat2.age = 1;
		System.out.println(cat1.name);
	}
}

class Cat{
	String name;
	int age;
}

6.1.1 属性/成员变量

从概念或叫法上看:成员变量 = 属性 = field(字段)

class Cat{
	String name;
	int age;
}

其中,String name; 就是一个成员变量(属性)。

属性可以是基本数据类型,也可以是引用数据类型。

  1. 属性的定义语法同变量。访问修饰符 属性类型 属性名
    • 访问修饰符:控制属性的访问范围。有四种:publie protected 默认(空) private
  2. 属性的定义类型可以为任意类型,包含 基本类型 或 引用类型
  3. 属性如果不赋值,有默认值。规则同 [5.1.1 数组 - 使用细节 - 3]

6.1.2 创建对象

  • 先声明再创建:

Cat cat1;  				    //声明对象cat1
cat1 = new Cat();			//创建对象
  • 直接创建:

  • Cat cat2 = new Cat();
    

    注意事项:

    1. 声明对象的场合,只是在内存中建立了一个引用。此时,该地址引用不指向任何内存空间。

      对象的引用,也被称为对象的句柄。

    2. 使用 new 运算符创建对象实例时,会为对象分配空间,就会调用类的构造方法。那之后,会将该段内存的首地址赋给刚才建立的引用。

    6.1.3 访问对象

    基本语法:对象名.属性名

    System.out.println(cat1.name);
    

    6.1.4 类与对象的内存访问机制

    栈:一般存放基本数据类型(局部变量)

    堆:存放对象(如Cat cat1 = new Cat(),是在这里开辟的空间)

    方法区:常量池(常量,比如字符串),类加载信息

    1. 创建对象时,先加载 类 信息,然后在 堆 中分配空间,栈 中的对象名被赋予指向那个空间的地址。
    2. 之后进行指定初始化。该对象的 属性 中,是 基本数据类型 的直接记录在 堆 中;是 字符串 的记录一个地址,该地址指向 方法区,那里的常量池有该字符串。

    6.2 成员方法

    在某些情况下,我们需要定义成员方法。比如 Cat 除了有属性(name age)外,还可以有一些行为比如玩耍。

    修饰符 返回数据类型 方法名(形参列表){
    	方法体语句;
    	returen 返回值;			//返回数据类型是 void 的场合,return语句不是必须的
    }
    
    1. 方法名必须是一个合法的标识符

    2. 返回类型即返回值的类型。如果方法没有返回值,应声明为 void

    3. 修饰符段可以有几个不同的修饰符。

      比如

    public static strictfp final void method() {
            System.out.println("哦嚯嚯");
        }
    

    其中 public(访问修饰符)、static(static 关键字)、final(final 关键字)

    1. 参数列表是传递给方法的参数表。各个元素间以 , 分隔。每个元素由一个类型和一个标识符表示的参数组成。

    特别地,参数类型... 标识符 这样的参数被称为可变参数

    1. 方法体是实际要执行的代码块。方法体一般用 return 作为方法的结束。

    使用 成员方法,能提高代码的复用性。而且能把实现的细节封装起来,供其他用户调用。

    class Cat{
    	String name;				//属性 name
    	int age;					//属性 age
    
    	public void speak(){		//方法 speak()
      		System.out.println("喵~");
    	}
    }
    
    1. 方法写好后,不去调用就不会输出
    2. 先创建对象,然后调用方法即可

    下面,展示一个含有成员方法的代码:

    public class Code6_2{
        public static void main(String[] args){
           Cat cat1 = new Cat();
           cat1.speak(10, 15);				//调用 speak 方法,并且给 n1 = 10, n2 = 15
           int r = cat1.speak2(15, 135);	//调用 speak2 方法,返回值赋给 r
        }
    }
    
    class Cat{
        public void speak(int n1, int n2){	//(int n1, int n2)形参列表,当前有两个形参 n1,n2
           int res = n1 + n2;
           System.out.println("喵~" + n1 + " + " + n2 +" 的值是:" + res);
        }
    
        public int speak2(int n1, int n2){	//int 表示方法执行后,返回一个 int 值
           int res = n1 + n2;
           return res;						//返回 res 的值
        }
    }
    

    6.2.1 方法的调用机制

    以前文代码为例:

    ...
    		int r = cat1.speak2(15, 135);		
    ...
    public int speak2(int n1, int n2){	
       int res = n1 + n2;
       return res;
    }
    ...
    
    1. 当程序执行到方法时,在 栈 中开辟一个新的 栈空间。该空间里储存 n1 = 15 n2 = 135,之后计算并储存结果 res = 150
    2. 当方法执行完毕,或执行到 return 语句时,就会返回
    3. 把 新栈空间 中的 res = 150 返回 main栈 中调用方法的地方
    4. 返回后,继续执行该方法的后续代码

    6.2.2 使用细节

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

      • 不写(默认访问控制范围)
      • public:公共
      • protected:受保护
      • private:私有
  • 返回数据类型:

    • 一个方法最多有一个返回值。要返回多个结果可以使用 数组。
    • 返回类型为任意类型。包括 基本数据类型 和 引用数据类型。
    • 如果方法要求有返回数据类型,则方法体中最后的执行语句必为 return 值,且返回类型必须和 return 的值一致。
    • 如果 返回数据类型 为 void,则可以不写 return 语句
  • 方法名:

    • 遵循驼峰命名法,最好见名知意,表达出该功能的意思。
  • 参数列表(形参列表):

    • 一个方法可以有 0 个参数,也可以有多个参数。参数间用 , 间隔。
    • 参数类型可以为任意类型,包含 基本类型 和 引用类型。
    • 调用带参数的方法时,一定对应着 参数列表 传入 相同类型 或 兼容类型 的参数。
    • 方法定义时的参数称为 形式参数 ,简称 形参;方法调用时的参数(传入的参数)称为 实际参数,简称 实参。实参 与 形参 的类型、个数、顺序必须一致。
  • 方法体:

    • 写完成功能的具体语句。方法中不能再定义方法。即:方法不能嵌套定义。
  • 调用细节:

    • 同一个类中的方法调用,可以直接调用。

    • 跨类的方法调用,需要创建新对象,然后再调用方法。

  • class C1{
    	public void m1(){
    	}
    	public void m2(){
       		m1();					//同一个类中的方法调用,可以直接调用。
    	}
    }
    
    class C2{
    	public void m3(){
       		C1 c = new C1();
       		c.m2();					//跨类的方法调用,需要创建新对象,然后				再调用方法。
    	}
    }
    

    6.2.3 成员方法传参机制

    Java 语言对对象采用的是 值传递,方法得到的总是那个传入对象的副本。

    • 方法不能修改基本数据类型的参数。基本数据类型传递的是一个值,形参不影响实参。

    • 方法可以改变对象参数的状态。

      引用类型传递的是一个地址,形参和实参指向一处,两者总会相关。

      但改变那个形参地址指向的场合,实参的指向不会改变。

    6.3 方法递归调用

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

    下面,示范一个斐波那契数列方法

    class T{
       public int fib(int n){
           if(n == 1 || n == 2){
               return 1;
           }else{
               return (fib(n - 1)) + (feb(n - 2));
           }
       }
    }
    

    6.3.1 使用细节

    1. 执行一个方法时,就创建一个新的受保护的独立 栈空间。
    2. 方法的局部变量是独立的,不会相互影响。
    3. 如果方法中使用的是引用变量,就会共享数据。
    4. 递归必须向退出递归的条件逼近,否则就是无限递归,会提示 StackOverflowError “死龟”
    5. 当一个方法执行完毕,或遇到 return 就会返回。遵守谁调用就返回给谁。同时当方法执行完毕或返回时,该方法也执行完毕。

    6.4 方法重载

    方法重载(Overload):Java 中允许同一类中,多个同名方法的存在,但要求 形参列表 不一致。

    这样,减轻了起名和记名的麻烦。

    使用细节:

    1. 方法名:必须相同
    2. 形参列表:必须不同(参数的类型、个数、顺序,这其中至少一个不同)
    3. 返回值:无要求

    签名:

    由于重载的存在,要完整的描述一个方法,要指定方法名及参数类型。这叫做方法的签名。

    如:

    public void act() {}
    public int act(int n) {
        return n;
    }
    

    两个方法的签名分别是:act()act(int n)

    6.5 可变参数

    Java 允许将同一个类中多个同名同功能但参数个数不同的方法,封装成一个方法。

    语法:访问修饰符 返回类型 方法名(数据类型... 形参名){代码块;}

    public void m(int... n){
    	//此时,n 相当于一个 数组。
    	int length = n.length;
    	int num1 = n[0];
    }
    

    6.5.1 使用细节

    1. 可变参数 的实参可以是 0 个,也可以是 任意多 个。

    2. 可变参数 的实参可以是数组

    3. 可变参数 本质就是数组

      因此,出现:

    public void met(int... n){				//这个方法与下面的方法不能构成重载
    }
    

    的场合,不能有方法:

    public void met(int[] n){				//这个方法与上面的方法不能构成重载
    }
    
  • 可变参数 和 普通参数 可以一起放在形参列表,但必须保证 可变参数 在最后

  • public void m(double dou, int... n) {}
    
  • 一个形参列表最多出现 一个 可变参数。

  • 6.6 作用域

    1. 在 Java 编程中,主要的变量就是 属性(成员变量)和 局部变量。
    2. 我们说的 局部变量 一般是指在成员方法中定义的变量。
    3. 作用域的分类
      • 全局变量:也就是 属性,作用域为整个类体
      • 局部变量:除了属性外的其他变量。作用域为定义它的代码块中
    4. 全局变量(属性)可以不赋值直接使用,那个场合有默认值。局部变量必须赋值使用

    6.6.1 使用细节

    1. 属性 和 局部变量 可以重名,访问时遵循就近原则

    2. 在同一作用域中,两个局部变量不能重名

    3. 属性 的生命周期较长。其伴随对象的创建而创建,伴随对象的销毁而销毁。

      局部变量 生命周期较短。其伴随代码块的执行而创建,伴随代码块的结束而销毁。

    4. 全局变量/属性 可以被本类使用,也可以被其他类(通过对象)使用。

      局部变量 只能被本类的对应方法中调用

    5. 全局变量/属性 可以加 修饰符

      局部变量 不能加 修饰符

    6.7 构造方法、构造器

    构造方法又叫构造器(constructor),是类的一种特殊的方法。它的主要作用是完成对新对象的初始化。

    语法:[修饰符] 方法名(形参列表){方法体}

    1. 构造器的修饰符可以是默认。也可以是别的
    2. 参数列表 规则同 成员方法

    以下示范一个构造器:

    class T{
    	String name;
    	int mun;
        //下面这块就是构造器
    	public T(String str, int i){
    		name = str;
    		num = i;
    	}
    }
    

    6.7.1 使用细节

    1. 构造器本质也是方法。所以,可以 构造器重载。

    2. 构造器名 和 类名 相同

    3. 构造器无返回值

    4. 构造器是完成对象的初始化,而不是创建

    5. 创建对象时,系统自动调用构造器

    6. 如果程序员没有定义构造器,系统会自动给类生成一个无参构造器(默认构造器)

    7. 一旦定义了自己的构造器,就不能用无参构造器了。除非显式的定义一个无参构造器

    6.7.2 流程分析

    Person p1 = new Person("Amy", 10);
    
    ...
        
    class Person{
    	String name;
    	int age = 20;
    	public Person(String pName, int pAge){
    		name = pName;
    		age = pAge;
    	}
    }
    
    1. 加载 类信息(方法区)

    2. 在 堆 中开辟空间(地址)

    3. 完成对象初始化

      • 首先默认初始化。age = 0; name = null

      • 之后显式初始化。age = 20; name = null

        其中,显式初始化和代码块初始化按编写的先后顺序依次进行。

      • 之后构造器的初始化。age = 10; name = "Amy"

    4. 把对象在 堆 中的 地址,返回给 p1

    6.8 this 关键字

    JVM 会给每个对象分配 this 代表当前对象。

    相当于在 堆 中,this 指向自己(对象)

    在类定义的方法中,Java 会自动用 this 关键字把所有变量和方法引用结合在一起。

    遇到有同名的局部变量的场合,需要程序员加入 this 关键字进行区分。不加入 this 关键字的场合,Java 遵循就近原则。

    class Example{
        int n = 0;
    	public void act(int n) {}
    }
    

    上面这个类的 act() 方法实际有 2 个参数。对其调用:

    class Example{
        int n = 0;
    	public void act(int n) {}
    }
    

    可见,出现在方法名前的参数 e,以及出现在方法名后的括号中的参数 100

    出现在方法名前的参数被称为 隐式参数(也称为 方法调用的 目标 或 接收者)

    出现在方法名后的参数被称为 显式参数,就是所谓的实参

    在每一个方法中,用 this 指代隐式参数。

    public void act(int n) {
        this.n = n;
    }
    

    此时,再以相同方式调用方法:

    e.act(100);					// <———— 相当于 e.n = 100;
    

    6.8.1 使用方法

    1. this 关键字可以用来访问本类的属性、方法、构造器

    2. this 用于区分当前类的 属性 和 局部变量

    3. 访问本类中成员方法的语法:this.方法名

    4. 访问构造器的语法:this(参数列表);

      注意:只能在构造器中访问另一个构造器。而且,如果有这个语法,必须放置在第一条语句。

    5. this 不能在类定义的 外部 使用,只能在类定义的 方法中 使用

    附录

    迷宫游戏代码

    /**
    *@author Melody
    *@version v1.2.6
    **/
    
    //迷宫
    import java.util.Scanner;
    public class MazeOut{
    
        public static void main(String[] args){
            //tools 方便后面调用方法。 inP 可以接收用户输入
            T tools = new T();
            Scanner inP = new Scanner(System.in);
            
            //提示并接收用户输入信息
            System.out.println("\n输入迷宫宽度(至少为6):");
            int x = inP.nextInt();
            System.out.println("\n输入迷宫长度(至少为6):");
            int y = inP.nextInt();  
            //若用户输入的长或宽超出范围,则将其重置为正常值
            if(x < 6){
                x = 6;
            } else if(x > 110){
            	x = 110;
            }
            if(y < 6){
                y = 6;
            } else if(y > 60){
            	y = 60;
            }
            System.out.println("\n输入迷宫的困难度(请输入1 - 6的数字,数字越高越不容易获胜):");
            int hard = inP.nextInt();
            if(hard == 7){
            	System.out.println("\n\n您选择了找点麻烦");
            } else if (hard == 8 || hard == 9){
            	System.out.println("\n\n您选择了给自己添堵");
            }
            System.out.println("\n\t迷宫生产完毕\n");
            
            //设置一个 count 值,记录步数。设为数组,以便数据通用。第一位记录当前值,第二位为最大值。
            int[] count = {0, 0};
            //调用方法,生成迷宫
            char[][] maze =new char[y][x];
            tools.newMaze(maze.length, maze[0].length, maze, hard);
            //调用方法,展示迷宫
            tools.showMaze(maze);
            
            //提示用户开始游戏
            System.out.println("\n召唤一个探索者,来探索迷宫吧(随便输点什么吧)");
            //输入 r 或 c 则采用递归方法,其余采用爬墙方法
            char inC = inP.next().charAt(0);
            if(inC == 'c'){
                System.out.println("\n您触发了迷宫之神的眷顾。");
                if(hard > 5){
                	System.out.println("\n迷宫之神眉头一皱,发现事情并不简单。");
                }
                if(x > 12 || y > 12){
                    System.out.println("看到地图这么大,迷宫之神悻悻而归。他只喜欢12格以下的地图。");
                    return; 
                }
            } else if(inC == 'r'){
                System.out.println("\n您引来了一群无畏小黄鸡。他们视死如归,一心想着寻找出口");
            } else {
                System.out.println("\n我们找来了一只小蜘蛛。试试看吧。");
            }
            System.out.println("\n");
            //调用方法,解密
            if(inC == 'r' || inC == 'c'){
                tools.outMazeRec(maze, inC, count);
            } else {
                tools.outMaze(maze, count);
            }
    
        }
    }
    
    
    
    
    
    class T{   
        //=======================================================================================    
        
        //方法 newMaze:让 n3 生成随机的 长 * 宽 = n1 * n2 的迷宫,其困难度为 n4
        public void newMaze(int n1, int n2, char[][] n3, int n4){
            //构建迷宫墙壁,以'#'表示。并随机向其中填充石块,以'O'表示
            墙壁是迷宫的 开头和结尾行 以及 每行的开头和结尾
            for(int i = 0; i < n1; i++){
                for(int j = 0; j < n2; j++){
                    if(i == 0 || i == n1 - 1 ||j == 0 || j == n2 - 1){
                        n3[i][j] = '#';
                    }else{
                        //ran 是一个随机值,此处是概率生成挡路的石块'O'。其概率与 n4 值的大小正相关
                        //此外,若 n4(即用户输入的难度值 hard)超过范围,则按照 难度6 计算
                        int ran;
                        if(n4 <= 9 && n4 >= 0){
                            ran = (int)(Math.random() * (9 - n4) + 1);
                        }else{
                            ran = (int)(Math.random() * 3 + 1);
                        }
                        n3[i][j] = (ran == 1) ? 'O' : ' ';
                    }
                }
            }
            //生成起点、终点,优化地形
            n3[1][1] = 'B';
            n3[2][1] = ' ';
            n3[1][2] = ' ';
            n3[n1 - 2][n2 - 2] = 'F';
            n3[n1 - 3][n2 - 2] = ' ';
            n3[n1 - 2][n2 - 3] = ' ';
        }
        
        
        
    
        
        //方法 showMaze:展示一个迷宫
        public void showMaze(char[][] n1){
            for(int i = 0; i < n1.length; i++){
                for(int j = 0; j < n1[i].length; j++){
                    System.out.print(" " + n1[i][j]);
                }
                System.out.println();
            }
        }
        //=======================================================================================   
    
    
    
    
    
    
        //=======================================================================================
        //方法 outMazeRec:递归方法迷宫游戏入口。可以接入普通递归方法,或最短路径方法。
        public void outMazeRec(char[][] n1, char n2, int[] count){
            //out:是否走出迷宫
            boolean out = false;
            //将迷宫的起止位置记为通路
            n1[1][1] = ' ';
            n1[n1.length - 2][n1[0].length -2] = ' ';
            //如果输入的是'c',则采用最短路径法。反之采用普通递归方法
            if(n2 == 'c'){
                out = outCountMaze(1, 1, n1, count);
            }else{
                out = outMazeRecursion(1, 1, n1, count);           
            }
            //把迷宫起始位置重新标注出来
            n1[1][1] = 'B';
            //判断是否解谜成功。如果成功,迷宫终点显示'V',并展示步数,否则显示'F'
            if(out){
                n1[n1.length - 2][n1[0].length -2] = 'V';
                showMaze(n1);
                System.out.println("\t YOU WIN!!!");
                System.out.println("通过路径为 " + count[1] + " 格");    
            } else {
                n1[n1.length - 2][n1[0].length -2] = 'F';
                showMaze(n1);
                System.out.println("\t YOU LOSE");
            }
            
        }
        //=======================================================================================
    
    
    
    
        //=======================================================================================
        //方法 outMazeRecursion:迷宫游戏,普通递归方法
        public boolean outMazeRecursion(int y, int x, char[][] n3, int[] count){
            count[1]++;
            if(n3[n3.length - 2][n3[0].length - 2] == '.'){
                return true;
            } else if(n3[y][x] == ' '){
                n3[y][x] = '.';
                if(outMazeRecursion(y, x + 1, n3, count)){
                    return true;
                } else if(outMazeRecursion(y + 1, x, n3, count)){
                    return true;
                } else if(outMazeRecursion(y, x - 1, n3, count)){
                    return true;
                } else if(outMazeRecursion(y - 1, x, n3, count)){
                    return true;
                } else{
                    count[1]--;
                    n3[y][x] = '+'; 
                }
            } else {
                count[1]--;
                return false;
            }
            count[1]--;
            return false;
        }
        //=======================================================================================    
    
    
    
    
    
    
        //=======================================================================================
        //方法 outCountMaze:迷宫游戏,最短路径法的入口。这个入口由普通递归法接入。
        public boolean outCountMaze(int y, int x, char[][] n, int[] count){
    
            //首先,创建一个里数组。该数组用于 part1,原数组用于 part2。
            //似乎没必要作此设计。但我还是予以保留。
            char[][] inMaze = new char[n.length][n[0].length];
            for(int i = 0; i < n.length; i++){
                for(int j = 0; j < n[0].length; j++){
                    inMaze[i][j] = n[i][j];
                }
            }
    
            //首先进行 part1,然后必定进行 part2。因为 part1 总会返回一个 false
            if(countMazeRec(y, x, inMaze, count) || true){
                count[0] = 0;
                return outMazeRecC(y, x, n, count);
            }
            return false;
        }
    
    
        //方法 countMazeRec:迷宫游戏,最短路径法,part1
        //该方法是先统计最短路径。最终总会返回 false
        public boolean countMazeRec(int y, int x, char[][] n3, int[] count){
            count[0]++;
            if(y == n3.length - 2 && x == n3[0].length - 2){
                if(count[0] < count[1] || count[1] == 0){
                    count[1] = count[0];
                }
            } else if(n3[y][x] == ' '){
                n3[y][x] = '.';           
                if(countMazeRec(y, x + 1, n3, count)){
                    return true;
                } else if(countMazeRec(y + 1, x, n3, count)){
                    return true;
                } else if(countMazeRec(y, x - 1, n3, count)){
                    return true;
                } else if(countMazeRec(y - 1, x, n3, count)){
                    return true;
                } else{
                    n3[y][x] = ' ';
                    count[0]--;
                    return false;
                }
            } else {
                count[0]--;
                return false;
            }
            count[0]--;
            return false;
        }
    
    
    
    
    
    
        //方法 outMazeRecC:迷宫游戏,最短路径法,part2
        //该方法是在 part1 统计完最短路径后,按最短路径走出迷宫,并绘制路径
        public boolean outMazeRecC(int y, int x, char[][] n3, int[] count){
            count[0]++;
            if(y == n3.length - 2 && x == n3[0].length - 2){
                if(count[0] <= count[1]){
                    return true;
                } else {
                    n3[n3.length - 2][n3[0].length - 2] = ' ';
                    count[0]--;
                    return false;
                }
            } else if(n3[y][x] == ' '){
                n3[y][x] = '.';
                if(outMazeRecC(y, x + 1, n3, count)){
                    return true;
                } else if(outMazeRecC(y + 1, x, n3, count)){
                    return true;
                } else if(outMazeRecC(y, x - 1, n3, count)){
                    return true;
                } else if(outMazeRecC(y - 1, x, n3, count)){
                    return true;
                } else{
                    n3[y][x] = ' ';
                    count[0]--;
                    return false;
                }
            } else {
                count[0]--;
                return false;
            }
    
        }
        //=======================================================================================
    
    
        
    
    
    
        //=======================================================================================
        //方法 outMaze:爬墙方法迷宫游戏入口
        public void outMaze(char[][] n1, int[] count){
            //boolean out:记录是否走出迷宫
            boolean out = false;
            //角色光标 m
            n1[1][1] = 'm';
            
            //创建一系列变量,后面解释用法
            //创建 角色坐标
            int x = 1;
            int y = 1;
            //创建 辅助坐标 及 方向字符。初始方向为右。
            int xi = 1;
            int yi = 0;
            char dir = 'r';  
            //创建 里迷宫,标记起止点。
            char[][] inMaze = new char[n1.length][n1[0].length];
            inMaze[1][1] = 'B';
            inMaze[n1.length - 2][n1[0].length - 2] = 'F';
            
            //开始走迷宫。
            //如果一个迷宫有出路,则沿着一侧的墙壁走就一定能走到出路。以下方法就是基于这个原理。
            //角色坐标 y,x 是角色所在的位置坐标。辅助坐标 yi,xi 是角色靠近的墙壁坐标。
            //dir 代表角色此时的朝向。只要角色按照墙壁延申的方向向前,就一定不会迷路。
            //里迷宫的大小和真迷宫相同,坐标也一一对应。目的是为了记录数据,这些数据不会被用户看到。
            //里迷宫记载了 起始点 和 终点 的位置。如角色回到起点,则必定失败。到达终点则成功。
            for(;;){
    
                //判断 是否走出迷宫。如若是,则展示迷宫,记录脱出为真,并退出寻路
                if(inMaze[y][x] == 'F'){
                    n1[y][x] = 'V';
                    n1[1][1] = 'B';
                    showMaze(n1);
                    out = true;
                    break;
                }
    
                //通过爬墙方式试图走出迷宫
                //这是方向朝右时的情况
                if(dir == 'r'){
                    //如果角色面对墙壁,意味着走到了墙角,则角色坐标不变,调整墙壁坐标,并转向
                    if(n1[y][x + 1] == '#' || n1[y][x + 1] == 'O'){
                        dir = yi > y ? 'u' : 'd';
                        yi = y;
                        xi = x + 1;
                    //如果面前有路,且墙壁延伸,则前进
                    } else if (n1[yi][xi + 1] == '#' || n1[yi][xi + 1] == 'O'){
                        n1[y][x] = '.';
                        x++;
                        xi++;
                        n1[y][x] = 'm';
                        count[1]++;
                    //如果面前有路,但墙壁不延伸,则是遇到了转角。角色移动,转向,但墙壁坐标不变
                    } else {
                        dir = yi > y ? 'd' : 'u';
                        n1[y][x] = '.';
                        n1[y][x + 1] = '.';
                        y = yi;
                        x = xi + 1;
                        n1[y][x] = 'm';
                        count[1] += 2;
                    }
                //这是方向朝左的情况
                } else if(dir == 'l'){
                    if(n1[y][x - 1] == '#' || n1[y][x - 1] == 'O'){
                        dir = yi > y ? 'u' : 'd';
                        yi = y;
                        xi = x - 1;
                    } else if(n1[yi][xi - 1] == '#' || n1[yi][xi - 1] == 'O'){
                        n1[y][x] = '.';
                        x--;
                        xi--;
                        n1[y][x] = 'm';
                        count[1]++;
                    } else {
                        dir = yi > y ? 'd' : 'u';
                        n1[y][x] = '.';
                        n1[y][x - 1] = '.';
                        y = yi;
                        x = xi - 1;
                        n1[y][x] = 'm';
                        count[1] += 2;
                    }
                //这是方向朝下的情况
                } else if(dir == 'd'){
                    if(n1[y + 1][x] == '#' || n1[y + 1][x] == 'O'){
                        dir = xi < x ? 'r' : 'l';
                        yi = y + 1;
                        xi = x;
                    } else if(n1[yi + 1][xi] == '#' || n1[yi + 1][xi] == 'O'){
                        n1[y][x] = '.';
                        y++;
                        yi++;
                        n1[y][x] = 'm';
                        count[1]++;
                    } else {
                        dir = xi < x ? 'l' : 'r';
                        n1[y][x] = '.';
                        n1[y + 1][x] = '.';
                        y = yi + 1;
                        x = xi;
                        n1[y][x] = 'm';
                        count[1] += 2;
                    }
                //这是方向朝上的情况
                } else if(dir == 'u'){
                    if(n1[y - 1][x] == '#' || n1[y - 1][x] == 'O'){
                        dir = xi < x ? 'r' : 'l';
                        yi = y - 1;
                        xi = x;
                    } else if(n1[yi - 1][xi] == '#' || n1[yi - 1][xi] == 'O'){
                        n1[y][x] = '.';
                        y--;
                        yi--;
                        n1[y][x] = 'm';
                        count[1]++;
                    } else {
                        dir = xi < x ? 'l' : 'r';
                        n1[y][x] = '.';
                        n1[y - 1][x] = '.';
                        y = yi - 1;
                        x = xi;
                        n1[y][x] = 'm';
                        count[1] += 2;
                    }
                }
    
                //判断 是否回到起点。如若是,则一定是迷宫无解。展示迷宫并退出寻路
                if(inMaze[y][x] == 'B'){
                    showMaze(n1);
                    break;
                }
            }
            //输出结果
            if(out){
                System.out.println("\t YOU WIN!!!\n\t您的步数为:" + count[1]);
            } else {
                System.out.println("\t YOU LOSE");
            }
        }
    }
    

    八皇后代码

    import java.util.Scanner;
    public class EightQueen{
    
        public static void main(String[] args){
            T tools = new T();
            char[][] chess = new char[8][8];
            //调用方法,建立棋盘
            tools.buildChess(chess);
            //调用方法,开始游戏
            tools.eightQueen(chess);
    
        }
    }
    
    
    
    class T{
        //buildChess:建立一个新棋盘。该棋盘白色格子用' '表示,黑色格子用'#'表示
        public void buildChess(char[][] chess){
            for(int i = 0; i < chess.length; i++){
                for(int j = 0; j < chess[0].length; j++){
                    chess[i][j] = ((i + j) % 2 == 0) ? ' ' : '#';
                }
            }
        }
    
    
    
    
        //eightQueen:八皇后游戏的接入口
        public void eightQueen(char[][] chess){
        	//建立 里棋盘 inward 及 计数数组 count。里棋盘用于计算问题,原棋盘输出给用户看。
        	//计数 count 使用数组,这样其数据在所有方法都能通用
            char[][] inward = new char[chess.length][chess[0].length];
            int[] count = {0} ;
            //进行游戏。因为穷举所有方法,最后返回的一定是 false。反正我们不在意。
            boolean isFinished = gameEQS(0, 0, chess, inward, count);
        }
    
    
    
        //gameEQS:八皇后游戏的基本方法
        //八皇后游戏方法。y 代表当前位置的纵坐标,x 是横坐标。chess 是棋盘,inward 是里棋盘,count 是计数数组
        public boolean gameEQS(int y, int x, char[][] chess, char[][] inward, int[] count){
            //当 y 超出棋盘 时,显然已经完成八皇后。
            //由于要进行穷举,此时我们计数并输出棋盘,然后返回 false 使其继续计算
            if(y == inward.length){
                count[0]++;
                System.out.println();
                gameEQS2(chess, inward, count);
                return false;
            //当 x 超出棋盘 时,显然棋盘该列已经无合法放置位置。我们返回 false
            } else if(x == inward[0].length){
                return false;
            //gameEQS1,这个方法是查看该格子是否是合法放置位置。如若是,返回 true,而且在该位置放置棋子'Q'
            //当这个位置合法,我们进入下一行,从头开始判断。
            //如果后面的判断为 false,我们就拿掉这枚棋子。如果后面判断为 true 说明我们找到了一个方法。
            //特别地,由于代码目前是穷举模式,我想我们永远不会在此输出 true
            } else if(gameEQS1(y, x, inward)){
                if(gameEQS(y + 1, 0, chess, inward, count)){
                    return true;
                } else {
                    inward[y][x] = ' ';
                }
            }
            //如果代码进行到这个位置,证明我们所在的格子不适合放置棋子。我们只好去看看下一格如何。
            return gameEQS(y, x + 1, chess, inward, count);
        }
    
    
    
        //gameEQS1:该方法是输入一个坐标,并输入里棋盘地址,在里棋盘上查看该位置是否合法
        //什么是合法的位置:就是该坐标的 同列、同行、同斜线 没有别的棋子
        //如果是合法位置,我们放置一个棋子,并返回 true
        public boolean gameEQS1(int y, int x, char[][] inward){
            for(int i = 0; i < inward.length; i++){
                for(int j = 0; j < inward[0].length; j++){
                    if(j == x || i == y || i - j == y - x || i + j == y + x){
                        if(inward[i][j] == 'Q'){
                            return false;
                        }
                    }
                }
            }
            inward[y][x] = 'Q';
            return true;
        }
    
    
    
        //gameEQS2:这个方法是把当前 里棋盘 的棋子放置到棋盘上,输出棋盘 并 输出计数。
        //在输出完成后,会清空棋盘。
        public void gameEQS2(char[][] chess, char[][] inward,int[] count){
            for(int i = 0; i < chess.length; i++){
                for(int j = 0; j < chess[0].length; j++){
                    if(inward[i][j] == 'Q'){
                        chess[i][j] = 'Q';
                    }
                    System.out.print(" " + chess[i][j]);
                }
                System.out.println();
            }
            System.out.print("\n" + count[0] + "\n");
            buildChess(chess);
        }
    
    
    
        //gameEQSDebug
        //输出里棋盘。测试用。
        public void gameEQSDebug(char[][] inward){
            for(int i = 0; i < inward.length; i++){
                for(int j = 0; j < inward[0].length; j++){
                    System.out.print(" " + inward[i][j]);
                }
                System.out.println();
            }
            System.out.println();
        }
    }
    
  • 6
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Eric天哥

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

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

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

打赏作者

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

抵扣说明:

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

余额充值