Java 面向对象编程(基础)

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

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

类与对象(OOP)

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

对象 就是其中具体的实例。可以是 100 

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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;
}

属性/成员变量

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

1
2
3
4
class Cat{
String name;
int age;
}

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

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

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

创建对象

  • 先声明再创建:

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

    1
    Cat cat2 = new Cat();

注意事项:

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

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

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

访问对象

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

1
System.out.println(cat1.name);

在Java中,任何对象变量的值都是对储存在另外一个地方的某个对象的引用。

类与对象的内存访问机制

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

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

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

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

成员方法

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

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

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

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

    1
    2
    3
    public static strictfp final void method() {
    System.out.println("哎咿呀~ 哎咿呀~");
    }

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

    —— 访问修饰符见  Java 面向对象编程(中级)

    —— static 关键字见 Java 面向对象编程(高级)

    —— final 关键字见 Java 面向对象编程(高级)

    —— strictfp 关键字见 Java 常用类

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

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

    —— 可变参数见 Java 面向对象编程(基础)

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

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

1
2
3
4
5
6
7
8
class Cat{
String name; //属性 name
int age; //属性 age

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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 的值
}
}

方法的调用机制

以前文代码为例:

1
2
3
4
5
6
7
8
...
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. 返回后,继续执行该方法的后续代码

使用细节

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

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

    —— 访问修饰符见  Java 面向对象编程(中级)

  2. 返回数据类型:

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

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

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class C1{
public void m1(){
}
public void m2(){
m1(); //同一个类中的方法调用,可以直接调用。
}
}

class C2{
public void m3(){
C1 c = new C1();
c.m2(); //跨类的方法调用,需要创建新对象,然后再调用方法。
}
}

成员方法传参机制

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

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

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

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

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

方法递归调用

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

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

1
2
3
4
5
6
7
8
9
class T{
public int fib(int n){
if(n == 1 || n == 2){
return 1;
}else{
return (fib(n - 1)) + (feb(n - 2));
}
}
}

使用细节

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

方法重载

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

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

使用细节:

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

签名:

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

如:

1
2
3
4
public void act() {}
public int act(int n) {
return n;
}

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

可变参数

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

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

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

使用细节

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

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

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

    因此,出现:

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

    的场合,不能有方法:

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

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

作用域

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

使用细节

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

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

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

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

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

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

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

    局部变量 不能加 修饰符

构造方法、构造器

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

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

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

以下示范一个构造器:

1
2
3
4
5
6
7
8
9
class T{
String name;
int mun;
//下面这块就是构造器
public T(String str, int i){
name = str;
num = i;
}
}

使用细节

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

  2. 构造器名 和 类名 相同

  3. 构造器无返回值

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

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

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

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

流程分析

1
2
3
4
5
6
7
8
9
10
11
12
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

this 关键字

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

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

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

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

1
2
3
4
class Example{
int n = 0;
public void act(int n) {}
}

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

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

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

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

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

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

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

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

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

使用方法

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

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

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

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

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

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

附录

迷宫游戏代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
/**
*@author sinarcsinx
*@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");
}
}
}

八皇后代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
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();
}
}