<Java>6 面向对象编程(基础)

本文最后更新于:2022年7月14日 晚上

6 面向对象编程(基础)

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

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 关键字)

    —— 访问修饰符见 [7.3 访问修饰符]

    —— static 关键字见 [9.1.1 类变量]

    —— final 关键字见 [9.5 final 关键字]

    —— strictfp 关键字见 [12.1.4 strictfp 关键字]

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

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

    —— 可变参数见 [6.5 可变参数]

  5. 方法体是实际要执行的代码块。方法体一般用 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:私有

    —— 访问修饰符见 [7.3访问修饰符]

  2. 返回数据类型:

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

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

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

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

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

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

      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. 如果方法中使用的是引用变量,就会共享数据。(因为 [6.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){				//这个方法与上面的方法不能构成重载
    }
  4. 可变参数 和 普通参数 可以一起放在形参列表,但必须保证 可变参数 在最后

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

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 个参数。对其调用:

Example e = new Exmaple();
e.act(100);

可见,出现在方法名前的参数 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();
    }
}

<Java>6 面向对象编程(基础)
https://i-melody.github.io/2021/11/29/Java/入门阶段/6 面向对象编程(基础)/
作者
Melody
发布于
2021年11月29日
许可协议