【写在前面】为进一步提高自己的python代码能力,打算把几本经典书籍重新过一遍,形成系统的知识体系,同时适当记录一些学习笔记,我尽量及时更新!先从经典的《Python编程:从入门到实战》书籍开始吧。有问题欢迎在评论区讨论,互相学习,good good study,day day up!上一章《Python编程:从入门到实战》(第2版)学习笔记 第8章 函数介绍了函数,这章介绍类。
面向对象编程是最有效的软件编写方法之一。在面向对象编程中,你编写表示现实世界中的事物和情景的类,并基于这些类来创建对象。根据类来创建对象被称为实例化。在本章,将编写一些类并创建其实例,指定可在实例中存储什么信息,执行哪些操作;学习类的继承。
【特别说明】这是第二版的《Python编程:从入门到实战》,书本的Python版本是3.7.2,我自己运行代码的环境是3.7.0,不同Python版本的功能不同。
使用类几乎可以模拟任何东西,如编写一个表示小狗的类Dog——它表示任何小狗。对于大多数宠物狗,它们都有名字和年龄;大多数小狗还会蹲下和打滚。Dog类将包含上述两项信息(名字和年龄)和两种行为(蹲下和打滚)。
根据Dog类创建的每个实例都将存储名字和年龄,同时赋予小狗蹲下(sit())和打滚(roll_over())的能力:
dog.py
>>> class Dog():
>>> """一次模拟小狗的简单尝试"""
>>> def __init__(self, name, age):
>>> """初始化属性name和age"""
>>> self.name = name
>>> self.age = age
>>> def sit(self):
>>> """模拟小狗蹲下"""
>>> print(f"{self.name} is now sitting.")
>>> def roll_over(self):
>>> """模拟小狗打滚"""
>>> print(f"{self.name} rolled over!")
在Python中,首字母大写的名称指的是类。这个类定义中的括号是空的,因为我们要从空白创建这个类。
类中的函数称为方法;前面有关函数的一切都适用于方法,目前而言,唯一重要的差别是调用方法的方式。方法__init__()是一个特殊的方法,当根据Dog类创建新实例时,Python会自动运行它。注意开头和末尾都有两个下划线。它包含三个形参:self、name和age。形参self必不可少,且必须位于其他形参的前面。为何?因为调用__init__()方法创建实例时,将自动传递实参self,它是一个指向实例本身的引用,让实例能够访问类中的属性和方法。创建Dog实例时,将通过实参向Dog()传递名字和年龄;self会自动传递,因此不需要传递它,只需给最后两个形参(name和age)提供值。
以self为前缀的变量都可供类中的所有方法使用,可以通过类的任何实例来访问。self.name = name获取与形参name关联的值,并将其赋给变量name,然后该变量被关联到当前创建的实例。像这样可通过实例访问的变量称为属性。
Dog类还定义了两个方法:sit()和roll_over()。这些方法不需要额外的信息,它们只有一个形参self。当前,sit()和roll_over()所做的有限,只是打印一条消息,后续可以扩展。
可将类视为有关如何创建实例的说明。Dog类是关于如何创建特定小狗的一系列说明。
下面创建一个实例:
>>> my_dog = Dog('willie', 6) #创建一条名字为'willie'、年龄为6的小狗
>>> print(f"My dog's name is {my_dog.name}.")
>>> print(f"My dog is {my_dog.age} years old.")
Python使用实参'willie'和6调用Dog类中的方法__init__(),创建一个表示特定小狗的示例,并使用'willie'和6来设置属性name和age。方法__init__()并未显式地包含return语句,自动返回一个表示这条小狗的实例,将实例存储在变量my_dog中。命名约定:首字母大写的名称(如Dog)指的是类,而小写的名称(如my_dog)指的是根据类创建的实例。
a. 访问属性
要访问实例的属性,可用句点表示法。例如:my_dog.name。先找到实例my_dog,再找与这个实例的属性name。在Dog类中引用这个属性时,使用的是self.name。输出如下:
My dog's name is Willie.
My dog is 6 years old.
b. 调用方法
创建实例后可用句点表示法来调用类中定义的任何方法。下面让小狗蹲下和打滚:
>>> my_dog = Dog('willie', 6)
>>> my_dog.sit()
>>> my_dog.roll_over()
要调用方法,指定实例的名称和要调用的方法,并用句点分隔它们。遇到代码my_dog.sit()时,Python在类Dog中查找方法sit()并运行代码。my_dog.roll_over()类似。
输出:
Willie is now sitting.
Willie rolled over!
c. 创建多个实例
可按需求根据类创建任意数量的实例。
>>> my_dog = Dog('willie', 6)
>>> your_dog = Dog('lucy', 3)
>>> print(f"My dog's name is {my_dog.name}.")
>>> print(f"My dog is {my_dog.age} years old.")
>>> my_dog.sit()
>>> print(f"\nYour dog's name is {your_dog.name}.")
>>> print(f"Your dog is {your_dog.age} years old.")
>>> your_dog.sit()
上面创建了两条小狗Willie和Lucy,每条小狗都是一个独立的实例,有自己的一组属性,能够执行相同的操作:
My dog's name is Willie.
My dog is 6 years old.
Willie is now sitting.
Your dog's name is Lucy.
Your dog is 3 years old.
Lucy is now sitting.
即使给第二条小狗指定同样的名字和年龄,Python依然会根据Dog类创建另一个实例。你可按需求根据一个类创建任意数量的实例,条件是将每个实例都存储在不同的变量中或占用列表或字典的不同位置。
类编写好后,大部分时间都将花在使用根据类创建的实例上。当需要修改实例的属性时,可以直接修改,也可以编写方法进行修改。
下面编写一个表示汽车的类,它存储了有关汽车的信息,还有一个汇总这些信息的方法:
car.py
>>> class Car():
>>> def __init__(self, make, model, year): #制造商、型号和生产年份
>>> """初始化描述汽车的属性"""
>>> self.make = make
>>> self.model = model
>>> self.year = year
>>>
>>> def get_descriptive_name(self):
>>> """返回整洁的描述性信息"""
>>> long_name = f"{self.year} {self.make} {self.model}"
>>> return long_name.title()
>>> my_new_car = Car('audi', 'a4', 2019)
>>> print(my_new_car.get_descriptive_name())
输出指出我们拥有的是一辆什么样的汽车:
2019 Audi A4
创建实例时,有的属性无须通过形参来定义,可在方法__init__()内指定默认值。
下面来添加一个属性odometer_reading,其初始值为0,还添加了一个名为read_odometer()的方法,用于读取汽车的里程表:
>>> class Car():
>>> def __init__(self, make, model, year):
>>> """初始化描述汽车的属性"""
>>> self.make = make
>>> self.model = model
>>> self.year = year
>>> self.odometer_reading = 0
>>>
>>> def get_descriptive_name(self):
>>> --snip--
>>> def read_odometer(self):
>>> """打印一条指出汽车里程的消息"""
>>> print(f"This car has {self.odometer_reading} miles on it.")
>>>
>>> my_new_car = Car('audi', 'a4', 2019)
>>> print(my_new_car.get_descriptive_name())
>>> my_new_car.read_odometer()
一开始汽车的里程为0:
2019 Audi A4
This car has 0 miles on it.
有三种方式修改属性的值:直接通过实例进行修改;通过方法进行设置;通过方法进行递增(增加特定的值)。
a. 直接修改属性的值
要修改属性的值,最简单的方式是通过实例直接访问它。
>>> my_new_car = Car('audi', 'a4', 2019)
>>> print(my_new_car.get_descriptive_name())
>>> my_new_car.odometer_reading = 23 #使用句点表示法将里程表读数设置为23
>>> my_new_car.read_odometer()
输出:
2019 Audi A4
This car has 23 miles on it.
b. 通过方法修改属性的值
如果有替你更新属性的方法,可将值传递给方法,由它在内部进行更新。
下面添加一个名为update_odometer()的方法:
>>> class Car():
>>> --snip--
>>> def update_odometer(self, mileage):
>>> """将里程表读数设置为指定的值"""
>>> self.odometer_reading = mileage #接受mileage,将其存储到self.odometer_reading中
>>> my_new_car = Car('audi', 'a4', 2019)
>>> print(my_new_car.get_descriptive_name())
>>> my_new_car.update_odometer(23)
>>> my_new_car.read_odometer()
打印:
2019 Audi A4
This car has 23 miles on it.
可对方法update_odometer()进行扩展,禁止任何人将里程表读数往回调:
>>> def update_odometer(self, mileage):
>>> """将里程表读数设置为指定的值,禁止回调里程表读数 """
>>> if mileage >= self.odometer_reading:
>>> self.odometer_reading = mileage
>>> else:
>>> print("You can't roll back an odometer!") #不能将里程表往回拨
c. 通过方法对属性的值进行递增
有时候需要将属性值递增特定的量,而不是设置为全新的值。假设我们购买了一辆二手车,且从购买到登记期间增加了100英里的里程,下面的方法让我们能够传递这个增量,并相应地增加里程表读数:
>>> class Car():
>>> --snip--
>>> def update_odometer(self, mileage):
>>> --snip--
>>> def increment_odometer(self, miles):
>>> """将里程表读数增加指定的量"""
>>> self.odometer_reading += miles
>>>
>>> my_used_car = Car('subaru', 'outback', 2015)
>>> print(my_used_car.get_descriptive_name())
>>> my_used_car.update_odometer(23500)
>>> my_used_car.read_odometer()
>>> my_used_car.increment_odometer(100)
>>> my_used_car.read_odometer()
输出:
2015 Subaru Outback
This car has 23500 miles on it.
This car has 23600 miles on it.
注意 你可使用类似上面的方法来控制用户修改属性值,但能够访问程序的人都可以通过直接访问属性来将里程表修改为任何值。
编写类时,并非总要从空白开始。如果你要编写的类是另一个现成类的特殊版本,可使用继承。原有的类称为父类,而新类称为子类。子类自动继承其父类的所有属性和方法,同时还可以定义自己的属性和方法。
在既有类的基础上编写新类时,通常要调用父类的方法 __init__() 。这将初始化在父类__init__() 方法中定义的所有属性,从而让子类包含这些属性。
例如,下面来模拟电动汽车(一种特殊的汽车),因此Car类的基础上创建新类ElectricCar,这样只需为电动汽车特有的属性和行为编写代码。
electric_car.py
>>> class Car():
>>> """一次模拟汽车的简单尝试"""
>>> def __init__(self, make, model, year):
>>> self.make = make
>>> self.model = model
>>> self.year = year
>>> self.odometer_reading = 0
>>>
>>> def get_descriptive_name(self):
>>> long_name = f"{self.year} {self.make} {self.model}"
>>> return long_name.title()
>>>
>>> def read_odometer(self):
>>> print(f"This car has {self.odometer_reading} miles on it.")
>>>
>>> def update_odometer(self, mileage):
>>> if mileage >= self.odometer_reading:
>>> self.odometer_reading = mileage
>>> else:
>>> print("You can't roll back an odometer!")
>>>
>>> def increment_odometer(self, miles):
>>> self.odometer_reading += miles
>>> class ElectricCar(Car):
>>> """电动汽车的独特之处"""
>>> def __init__(self, make, model, year):
>>> """初始化父类的属性"""
>>> super().__init__(make, model, year)
>>>
>>> my_tesla = ElectricCar('tesla', 'model s', 2019)
>>> print(my_tesla.get_descriptive_name())
2019 Tesla Model S
创建子类时,父类必须包含在当前文件中,且位于子类前面。定义子类时,必须在括号内指定父类的名称。方法__init__()接受创建Car实例所需的信息。super()是一个特殊函数,将父类和子类关联起来,通过调用ElectricCar的父类的方法__init__(),让ElectricCar实例包含父类的所有属性。父类也称超类(superclass)。
让一个类继承另一个类后,可添加区分子类和父类所需的新属性和方法。下面添加一个电动汽车的属性(电瓶容量)以及方法:
>>> class Car():
>>> --snip--
>>> class ElectricCar(Car):
>>> """电动汽车的独特之处"""
>>> def __init__(self, make, model, year):
>>> """初始化父类的属性,再初始化电动汽车特有的属性"""
>>> super().__init__(make, model, year)
>>> self.battery_size = 75 #添加新属性,设置其初始值
>>> def describe_battery(self):
>>> """打印一条描述电瓶容量的消息"""
>>> print(f"This car has a {self.battery_size}-kWh battery.")
>>>
>>> my_tesla = ElectricCar('tesla', 'model s', 2019)
>>> print(my_tesla.get_descriptive_name())
>>> my_tesla.describe_battery()
输出:
2019 Tesla Model S
This car has a 75-kWh battery.
对于ElectricCar类的特殊化程度没有任何限制。
对于父类的方法,只要它不符合子类的行为,都可对其进行重写,需要在子类中定义一个与要重写的父类同名的方法。
假设Car类有一个方法fill_gas_tank(),它对全电动汽车来说毫无意义,于是重写它:
>>> class ElectricCar(Car):
>>> --snip--
>>> def fill_gas_tank():
>>> """电动汽车没有油箱"""
>>> print("This car doesn't need a gas tank!")
这样对电动汽车调用fill_gas_tank()方法时,会忽略Car类的方法,而是运行上述代码。
当类的属性和方法清单以及文件都越来越长时,可将类的一部分作为一个独立的类提取出来,可将大型类拆分成多个协同工作的小类。
例如,不断给ElectricCar类添加细节时,我们发现包含很多专门针对汽车电瓶的属性和方法,可将其提取出来,放到名为Battery的类中,并将一个Battery实例用作ElectricCar类的一个属性:
>>> class Car():
>>> --snip--
>>> class Battery():
>>> """模拟电动汽车电瓶"""
>>> def __init__(self, battery_size=75): #形参battery_size是可选的
>>> """初始化电瓶的属性"""
>>> self.battery_size = battery_size
>>> def describe_battery(self):
>>> """打印一条描述电瓶容量的消息"""
>>> print(f"This car has a {self.battery_size}-kWh battery.")
>>>
>>> class ElectricCar(Car):
>>> """电动汽车的独特之处"""
>>> def __init__(self, make, model, year):
>>> """初始化父类的属性,再初始化电动汽车特有的属性"""
>>> super().__init__(make, model, year)
>>> self.battery = Battery()
>>>
>>> my_tesla = ElectricCar('tesla', 'model s', 2019)
>>> print(my_tesla.get_descriptive_name())
>>> my_tesla.battery.describe_battery()
输出:
2019 Tesla Model S
This car has a 75-kWh battery.
下面给Battery类添加一个方法,它根据电瓶容量报告汽车的续航里程:
>>> class Battery():
>>> --snip--
>>> def get_range(self):
>>> """打印一条消息,指出电瓶的续航里程"""
>>> if self.battery_size == 75:
>>> range = 260
>>> elif self.battery_size == 100:
>>> range = 315
>>> print(f"This car can go about {range} miles on a full charge.")
>>> class ElectricCar(Car):
>>> --snip--
>>> my_tesla = ElectricCar('tesla', 'model s', 2019)
>>> print(my_tesla.get_descriptive_name())
>>> my_tesla.battery.describe_battery()
>>> my_tesla.battery.get_range()
输出指出了汽车的续航里程(这取决于电瓶的容量):
2019 Tesla Model S
This car has a 75-kWh battery.
This car can go approximately 260 miles on a full charge.
模拟较复杂的物件(如电动汽车)时,需要解决一些有趣的问题。续航里程是电瓶的属性还是汽车的属性呢?如果我们只需描述一辆汽车,那么将方法get_range()放在Battery类中也许是合适的;但如果要描述一家汽车制造商的整个产品线,应该将方法get_range()移到ElectricCar类中。在这种情况下,get_range()依然根据电瓶容量来确定续航里程,但报告的是一款汽车的续航里程。也可以将方法get_range()还留在Battery类中,但向它传递一个参数,如car_model;在这种情况下,方法get_range()将根据电瓶容量和汽车型号报告续航里程。【这进入了程序员的另一个境界:解决上述问题时,你从较高的逻辑层面(而不是语法层面)考虑】
随着你不断地给类添加功能,文件可能变得很长,即便你妥善地使用了继承。为遵循Python的总体理念,应让文件尽可能整洁。Python允许你将类存储在模块中,然后在主程序中导入所需的模块。
下面来创建一个只包含Car类的模块,将Car类存储在一个名为car.py的模块中:
car.py
>>> """一个可用于表示汽车的类""" #创建的每个模块都应编写文档字符串
>>> class Car():
>>> """一次模拟汽车的简单尝试"""
>>> def __init__(self, make, model, year):
>>> """初始化描述汽车的属性"""
>>> self.make = make
>>> self.model = model
>>> self.year = year
>>> self.odometer_reading = 0
>>> def get_descriptive_name(self):
>>> """返回整洁的描述性名称"""
>>> long_name = f"{self.year} {self.make} {self.model}"
>>> return long_name.title()
>>> def read_odometer(self):
>>> """打印一条消息,指出汽车的里程"""
>>> print(f"This car has {self.odometer_reading} miles on it.")
>>> def update_odometer(self, mileage):
>>> """将里程表读数设置为指定的值,拒绝将里程表往回调"""
>>> if mileage >= self.odometer_reading:
>>> self.odometer_reading = mileage
>>> else:
>>> print("You can't roll back an odometer!")
>>> def increment_odometer(self, miles):
>>> """将里程表读数增加指定的量"""
>>> self.odometer_reading += miles
下面来创建另一个文件my_car.py,在其中导入Car类并创建其实例:
my_car.py
>>> from car import Car #打开模块car,并导入其中的Car类
>>> my_new_car = Car('audi', 'a4', 2019)
>>> print(my_new_car.get_descriptive_name())
>>> my_new_car.odometer_reading = 23
>>> my_new_car.read_odometer()
输出:
2019 Audi A4
This car has 23 miles on it.
导入类是一种有效的编程方式。
虽然同一个模块中的类之间应存在某种相关性,但可根据需要在一个模块中存储任意数量的类。类Battery和ElectricCar都可帮助模拟汽车,因此都加入模块car.py中:
car.py
>>> """一组用于表示燃油汽车和电动汽车的类"""
>>> class Car():
>>> --snip--
>>> class Battery():
>>> """模拟电动汽车电瓶"""
>>> def __init__(self, battery_size=75): #形参battery_size是可选的
>>> """初始化电瓶的属性"""
>>> self.battery_size = battery_size
>>> def describe_battery(self):
>>> """打印一条描述电瓶容量的消息"""
>>> print(f"This car has a {self.battery_size}-kWh battery.")
>>> def get_range(self):
>>> """打印一条消息,指出电瓶的续航里程"""
>>> if self.battery_size == 75:
>>> range = 260
>>> elif self.battery_size == 100:
>>> range = 315
>>> print(f"This car can go about {range} miles on a full charge.")
>>> class ElectricCar(Car):
>>> """电动汽车的独特之处"""
>>> def __init__(self, make, model, year):
>>> """初始化父类的属性,再初始化电动汽车特有的属性"""
>>> super().__init__(make, model, year)
>>> self.battery = Battery()
新建一个名为my_electric_car.py的文件,导入ElectricCar类,并创建一辆电动汽车:
my_electric_car.py
>>> from car import ElectricCar
>>> my_tesla = ElectricCar('tesla', 'model s', 2019)
>>> print(my_tesla.get_descriptive_name())
>>> my_tesla.battery.describe_battery()
>>> my_tesla.battery.get_range()
输出和之前一样,大部分逻辑都隐藏在一个模块中:
2019 Tesla Model S
This car has a 75-kWh battery.
This car can go approximately 260 miles on a full charge.
可在程序文件中导入任意数量的类。如果我们要在同一个程序中创建普通汽车和电动汽车,就需要将Car和ElectricCar类都导入:
my_cars.py
>>> from car import Car, ElectricCar
>>> my_beetle = Car('volkswagen', 'beetle', 2019)
>>> print(my_beetle.get_descriptive_name())
>>> my_tesla = ElectricCar('tesla', 'roadster', 2019)
>>> print(my_tesla.get_descriptive_name())
输出:
2019 Volkswagen Beetle
2019 Tesla Roadster
你还可以导入整个模块,再使用句点表示法访问需要的类,使用语法module_name.class_name访问需要的类。这种导入方法很简单,代码也易于阅读。由于创建类实例的代码都包含模块名,因此不会与当前文件使用的任何名称发生冲突。
下面导入整个car模块:
my_cars.py
>>> import car
>>> my_beetle = car.Car('volkswagen', 'beetle', 2019)
>>> print(my_beetle.get_descriptive_name())
>>> my_tesla = car.ElectricCar('tesla', 'roadster', 2019)
>>> print(my_tesla.get_descriptive_name())
要导入模块中的每个类,可使用下面的语法:
from module_name import *
不推荐使用这种导入方式,其原因有二:(1)最好只看文件开头的import语句,就能清楚地知道程序使用了哪些类;但这种导入方式没有明确地指出使用了模块中的哪些类(2)这种导入方式还可能引发名称的困惑。如果你不小心导入了一个与程序文件中其他东西同名的类,将引发难以诊断的错误。
需要从一个模块中导入很多类时,最好使用上一节的方法,虽然文件开头并没有列出用到的所有类,但清楚地知道在程序的哪些地方使用了导入的模块;避免了导入模块中的每个类可能引发的名称冲突。
有时候,需要将类分散到多个模块中,以免模块太大,或在同一个模块中存储不相关的类。将类存储在多个模块中时,你可能会发现一个模块中的类依赖于另一个模块中的类。在这种情况下,可在前一个模块中导入必要的类。
例如,下面将Car类存储在一个模块中,并将ElectricCar和Battery类存储在另一个模块中。我们将第二个模块命名为electric_car.py(这将覆盖前面创建的文件electric_car.py),并将Battery和ElectricCar类复制到这个模块中:
electric_car.py
>>> """一组可用于表示电动汽车的类"""
>>> from car import Car
>>> class Battery():
>>> --snip--
>>> class ElectricCar(Car):
>>> --snip--
ElectricCar类需要访问其父类Car,因此直接将Car类导入该模块中。我们还需要更新模块car,使其包含Car类:
car.py
>>> """一个可用于表示汽车的类"""
>>> class Car():
>>> --snip--
现在可以分别从每个模块中导入类,以根据需要创建任何类型的汽车了:
my_cars.py
>>> from car import Car #从模块car中导入了Car类
>>> from electric_car import ElectricCar #从模块electric_car中导入ElectricCar类
>>> my_beetle = Car('volkswagen', 'beetle', 2019)
>>> print(my_beetle.get_descriptive_name())
>>> my_tesla = ElectricCar('tesla', 'roadster', 2019)
>>> print(my_tesla.get_descriptive_name())
输出:
2019 Volkswagen Beetle
2019 Tesla Roadster
导入类时可指定别名。例如需要创建大量电动汽车实例,需要反复输入ElectricCar,为简化代码,可以使用别名:
>>> from electric_car import ElectricCar as EC
创建实例时:
>>> my_tesla = EC('tesla', 'roadster', 2019)
在组织大型项目的代码方面,Python提供了很多选项。熟悉这些选项很重要,这样你才能确定哪种项目组织方式是最佳的,并能理解别人开发的项目。
一开始应让代码结构尽可能简单。先尽可能在一个文件中完成所有的工作,确定一切都能正确运行后,再将类移到独立的模块中。如果你喜欢模块和文件的交互方式,可在项目开始时就尝试将类存储到模块中。先找出让你能够编写出可行代码的方式,再尝试让代码更为组织有序。
Python标准库是一组模块,安装的Python都包含它。你现在大致了解函数和类的工作原理,可以开始使用其他程序员编写好的模块。可使用标准库中的任何函数和类,为此只需在程序开头包含一条简单的import语句。下面来看模块random。
在这个模块中,一个有趣的函数是randint() 。它将两个整数作为参数,并随机返回一个位于这两个整数之间(含)的整数。下面演示如何生成一个位于1和6之间的随机整数:
>>> from random import randint
>>> randint(1, 6)
3
另一个有用的函数是choice() 。它将一个列表或元组作为参数,并随机返回其中的一个元素:
>>> from random import choice
>>> players = ['charles', 'martina', 'michael', 'florence', 'eli']
>>> first_up = choice(players)
>>> first_up
'florence'
创建与安全相关的应用程序时,请不要使用模块random ,但它创建众多有趣的项目。
注意 还可以从其他地方下载外部模块,具体看第二部分的项目。
你必须熟悉有些与类相关的编码风格问题,当编写的程序较复杂时尤其如此。
在本章中,你学习了:
同时了解了使用继承可简化相关类的创建工作;将一个类的实例用作另一个类的属性可让类更简洁。通过将类存储在模块中,并在需要使用这些类的文件中导入它们,可让项目组织有序。
在第10章中,将学习如何使用文件,这让你能够保存你在程序中所做的工作,以及你让用户做的工作。还将学习异常,这是一种特殊的Python类,用于帮助你在发生错误时采取相应的措施。
说明:记录学习笔记,如果错误欢迎指正!写文章不易,转载请联系我。