任务1:创建圆的处理类
下面代码能够创建圆的处理类Circle。要求:
1、将圆的周长和面积计算分别修改为对象属性perimeter、area
2、程序保存到s9A.py
class Circle( ):
def __init__(self,radius):
self.radius=radius
def get_perimeter(self):
return 2*3.1415926*self.radius
def get_area(self):
return 3.1415926*self.radius*self.radius
r=float(input("输入圆的半径:"))
cir=Circle(r)
print("圆周长=",cir.get_perimeter( ))
print("圆面积=",cir.get_area( ))
"""
(1)属性和方法(教材P80)
类中可以定义数据成员和成员函数,数据成员用于描述对象特征,成员函数用于描述对象行为。
数据成员也被称为属性,类似前面章节中学习的变量。例如:
radius=radius #简单变量赋值
self.radius=radius #类中属性赋值
成员函数也被称为方法,类似前面章节中学习的函数。例如:
aera = 0
def fun(r): #函数定义
global area #声明全局变量
perimeter = 2 * 3.1415926 * r #变量赋值
area = 3.1415926 * r * r #变量赋值
return perimeter
# 主程序
r = float(input("输入圆的半径:"))
print("圆周长=", fun(r)) #函数调用
print("圆面积=", area)
注意,这里函数fun(r)只有一个返回值perimeter。但主程序中,获得了perimeter和area两个值。其中圆面积area的值,是通过全局变量 global area返回的。
和普通函数相比,在类中定义函数只有一点不同:第1个参数永远是类本身的实例变量self。例如:
def get_area(self)
“self”参数代表将要创建的实例对象本身。在类的实例方法中访问实例属性时,需要以“self”为前缀,例如:
def get_area(self):
return 3.1415926*self.radius*self.radius
但在外部通过对象名调用对象方法时,并不需要传递这个参数。例如:
print("圆面积=",cir.get_area( ))。
(2)类属性和实例属性(教材P83)
类中定义的属性是类属性,为所有实例对象共享。可以通过对象名或类名进行访问。
实例属性为每个实例对象各自拥有,相互独立,只能通过对象名访问。
例如:
class Car:
wheels=4 #类属性,默认为公共属性
#主程序
my_car=Car( ) #创建实例对象my_car
print(my_car.wheels) #通过对象访问类属性,输出4
Car.wheels=6 #修改类属性
my_car.wheels=8 #修改实例属性
print(my_car.wheels) #输出8
your_car=Car( ) #创建实例对象your_car
print(your_car.wheels) #输出6
类定义中,类属性wheels=4,默认为公共属性。类属性和前面普通函数中的全局变量(global area)相似。全局变量可以在函数和主程序之间,方便传递数据。
而类的实例对象,都可以任意访问类属性。可以看出,类属性“Car.wheels=6”的更改,影响了后面新建的实例对象your_car。
这种特点,与函数和类的封装思想,是相违背的。函数中过度使用全局变量,会降低代码的可读性和可维护性。而类属性被外部代码轻易访问,同样会破坏类的封装性能。
(3)构造方法/构造函数__init__(教材P82)
每个类都有一个默认的__init__( )方法(构造方法,以两个下画线“--”开头和结束)。一个类定义__init__( )方法后,创建实例对象时,Python解释器会自动为新生成的实例,调用__init__( )方法。
构造方法,一般用于数据成员设置初值,或进行其他必要的初始化工作。如果用户未定义__init__( )方法,Python解释器会调用默认的__init__( )方法。
__init__ 方法的第一个参数永远是self,表示创建的实例本身。在__init__方法的内部,可以将各种属性绑定到self,为实例属性设置初始值。例如:
class Circle( ):
def __init__(self,radius): #radius为函数形参
self.radius=radius #self.radius为实例属性
self.perimeter=2*3.1415926*radius #实例属性
def get_area(self):
return 3.1415926 * self.radius * self.radius
#主程序
r=float(input("输入圆的半径:"))
cir=Circle(r) #创建实例对象cir时,传入参数r
print("圆周长=",cir.perimeter)
print("圆面积=",cir.get_area( ))
其中,构造方法__init__( )中的实例属性perimeter,可以通过对象名访问,即cir.perimeter。
get_area(self)为类定义中的公有方法,可以通过参数self,访问__init__( )中的实例属性,还可以访问类的其他方法。
任务2:井字棋游戏
1、下面代码是一个简单的双人对战“井字棋”游戏,请根据注释将代码补充完整。
2、程序保存到s9B.py。
class Board( ): #棋盘类
def ( ① ): #定义类的构造函数。需补充代码
self.face=[0]*9 #棋盘落子索引。0无子;1先手落子;2后手落子
self.free=list(range(9)) #可用位置索引
self.flag=(" ","★","●") #棋子或空位
"""① 定义构造函数,参考教材P82。按照参数的有无,可分为有参和无参构造方法。从实例属性self.face、self.free和self.flag的初始值可以看出,整个构造函数未涉及其他变量。这里的构造函数,应该属于无参构造方法(self除外)。
def show( ② ): #显示棋盘,需补充代码
"""② 后面主程序中,访问实例方法的代码为:bd.show( ),没有参数传递。可以看出,在类中定义的函数Show( ),应该也没有参数(self除外)。
a=[ self.flag[x] for ( ③ ) ] #确定各显示位置显示的字符,需补充代码
"""③ 列表推导式生成的结果a,显示的字符内容("★/●"或空位),从self.flag中获取;而显示位置取决于self.face。因此,索引x应该在self.face中,遍历一次,读取self.face中的位置信息。


s=f"""
┌─┬─┬─┐
│{a[0]}│{a[1]}│{a[2]}│
├─┼─┼─┤
│{a[3]}│{a[4]}│{a[5]}│
├─┼─┼─┤
│{a[6]}│{a[7]}│{a[8]}│
└─┴─┴─┘"""#s变量为三引号括起来的f格式字符串
print(s)
def down(self,index,chess): #在指定位置index落子。chess为落子方(=1或2)
if not ( ④ ): return 0 #index值无效时不处理。需补充代码
"""④self.free中,存放的可用位置索引。如果落子位置index值不在self.free中,则表示落子无效。
self.face[index]=( ⑤ ) #修改棋盘数据,需补充代码
"""⑤self.face中,存放的棋盘落子索引。0无子,1先手落子,2后手落子。这里形参chess,表示落子方。
self.free.remove( ⑥ ) #修改可用位置索引,需补充代码
"""⑥self.free中,存放的可用位置索引。落子生效时,除了修改棋盘数据,还要将落子位置index,从self.free中移除。
return 1 #函数返回1,表示落子有效,返回0,落子无效
def check(self,index,chess):
#检查棋面状态。返回值:0游戏未完成;1平局;2胜局
#index为落子位置、chess为落子方(=1或2)
a=[[0,1,2],[3,4,5],[6,7,8],[0,3,6],[1,4,7], [2,5,8],[0,4,8],[2,4,6]] #检查方向的位置索引列表
for p in a: #检索各方向是否为同一玩家落子
if index in p: #如果落子位置在某个方向
for c in ( ⑦ ): #需补充代码
"""⑦外循环p遍历a得到的值,分别为8个方向的坐标信息,[0,1,2],[3,4,5].......;内循环c,应该遍历p,得到某个方向上,3个落子位置的具体坐标 。
if self.face[c]!=chess:( ⑧ ) #需补充代码
"""⑧棋盘落子索引self.face[c]和当前落子方chess不同,则中止该方向的后续检查。
else:return ( ⑨ ) #需补充代码
"""⑨这里的else,与内循环for c......匹配。else语句与循环语句搭配使用,表示循环正常执行后,执行的代码。也就是说,内循环中的break语句,没有执行过!说明当前方向上,所有的落子方,与当前落子chess,是相同的。说明已经有人胜出了,应该返回胜局。
if not self.free:return ( ⑩ ) #需补充代码
"""⑩这里的if语句,不属于前面的内循环和外循环。也就是说,经过内循环和外循环的检查,没有决出胜负(如果已经决出胜负,肯定由前面return语句,提前返回了)。这时,可用位置索引self.free为空,说明当前棋盘,已经没有地方落子!应该返回平局。
Python规定,0、空字符串、None都视为False,其他数值和非空字符串均为True。(教材P19)。这里的if not self.free,等同于 if self.free==False。
else:return 0
def reset(self): #重置棋盘
self.face=[0]*9 #棋盘落子索引。0无子;1先手落子;2后手落子
self.free=list(range(9)) # 可用位置索引
#主程序
bd=( ① ) #创建棋盘对象,需补充代码
"""①对象的创建(P80)。前面类Board的定义中,构造函数不需要传递参数。
pr=1 #当前玩家。1先手;2后手
bd.show( )
while True:
n=int(input(f"玩家{pr}输入落子位置(0-8):"))
while ( ② )==0: #落子无效,需补充代码
"""②调用实例方法bd.down( ),传递当前落子位置n和当前玩家pr两个参数。bd.down( )返回值为0或1。0表示落子无效。
这里的while语句不能改为if语句。判断落子无效后,应该继续落子。
n=int(input("落子位置无效,重新输入:"))
bd.show( )
m=bd.check( ③ ) #检查棋面胜负。需补充代码
"""③实例方法bd.check( )中,形参index为落子位置、chess为落子方。这里的实参,n为落子位置,pr为落子方,也就是当前玩家。
if m>0:
s="平局" if m==1 else f"玩家{pr}取胜"
s=input(f"{s},是否继续?\n输入1继续,输入其他字符结束:")
if s!="1": ( ④ ) #需补充代码
"""④这里的if s!="1"语句,与前面的玩家、胜负没有关系,就是简单判断“输入1继续,其他字符结束!"
bd.reset( ); pr=1
else: pr=2 if pr==1 else 1
"""这条语句的作用,就是让玩家pr变量,交替变换。

