本文主要是介绍py面向对象特性:继承、封装、多态、鸭子模型、魔法方法,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
面向对象三大特性
- 1、面向对象特性之继承
- 1.1 继承的基本使用
- 1.2 demo
- 1.3 重写父类方法和调用父类方法 (类对象调用方法相当于 函数化调用,注意 位置传参)
- 1.3.1 重写
- 1.3.2 调用
- 1.3.3 面试题:作用域问题
- 1.3.4 继承案例
- 1.3.5 多重继承
- 1.4 判断类的继承关系:type 和isinstance方法
- 1.5 查看实例的所有属性:dir()方法和__dict__属性
- 2、面向对象特性之封装
- 2.1 **封装概述**
- 2.1.1 **封装原则**
- 2.1.2 **封装好处**
- 2.2 私有属性
- 2.2.1 私有属性定义
- 2.2.2 私有属性完整 demo
- 2.2.3 私有属性总结
- 2.3 单下划线、双下划线、头尾双下划线说明
- 2.4 私有方法
- 2.5 property属性装饰器
- 3、面向对象特性之多态
- 3.1 多态的使用
- 3.2 多态发生的前提:
- 3.3 鸭子模型 (动态类语言)
- 4、魔法方法
- 4.1 \_\_new\_\_ () 构造方法
- 4.2 \_\_init_\_() 初始化方法
- 4.3 \_\_str\_\_()
- 4.4 \__call\_\_()
- 4.5 \__del\_\_() 构析方法
- 4.6 \_\_getitem_\_() 字典化操作 属性
- 4.7 \_\_getattr_\_() attr 句点符 操作属性
- 4.8 \_\_eq_\_() 用于两个对象比较
- 4.9 \_\_len_\_()
- 5、反射
1、面向对象特性之继承
面向对象的编程带来的主要好处之一是代码的重用,实现这种重用的方法之一是通过继承机制。通过继承创建的新类称为子类或派生类,被继承的类称为基类、父类或超类。
class 派生类名(基类名)...
1.1 继承的基本使用
继承者是被继承者的特殊化,它除了拥有被继承者的特性外,还拥有自己独有得特性。例如猫有抓老鼠、爬树等其他动物没有的特性。同时在继承关系中,继承者完全可以替换被继承者,反之则不可以,例如我们可以说猫是动物,但不能说动物是猫就是这个道理,其实对于这个我们将其称之为“向上转型”。
继承定义了类如何相互关联,共享特性。对于若干个相同或者相识的类,我们可以抽象出他们共有的行为或者属相并将其定义成一个父类或者超类,然后用这些类继承该父类,他们不仅可以拥有父类的属性、方法还可以定义自己独有的属性或者方法。
同时在使用继承时需要记住三句话:
- 1、子类拥有父类非私有化的属性和方法。
- 2、子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
- 3、子类可以用自己的方式实现父类的方法。(重写)。
1.2 demo
- 无继承
class Dog:def eat(self):print("eating...")def sleep(self):print("sleep...")def swimming(self):print("swimming...")class Cat:def eat(self):print("eating...")def sleep(self):print("sleep...")def climb_tree(self):print("climb_tree...")
- 有继承
class Animal:def eat(self):print("eating...")def sleep(self):print("sleep...")class Dog(Animal):def swimming(self):print("goupaoshi...")class Cat(Animal):def climb_tree(self):print("climb_tree...")alex = Dog()
alex.run()
1.3 重写父类方法和调用父类方法 (类对象调用方法相当于 函数化调用,注意 位置传参)
1.3.1 重写
class Animal:def eat(self):print('eating ...')def sleep(self):print('sleeping ...')class Dog(Animal):def swimming(self):print('狗刨式...')def sleep(self):print('趴着睡')alex = Dog()
alex.sleep()
1.3.2 调用
super().方法(参数)
- 无参
class Animal:def eat(self):print('eating ...')def sleep(self):print('sleeping ...')class Dog(Animal):def swimming(self):print('狗刨式...')def sleep(self):# 方式1:硬编码,不推荐.# 父类对象调用 父类对象.方法(self,其他参数)# Animal.sleep(self) # 类对象调用方法相当于 函数化调用,注意 位置传参# 方式2:super关键字# super(子类对象,self).方法(参数)or super().方法(参数)# super(Dog, self).sleep() # 不推荐,还是存在硬编码super().sleep() # 推荐!! super() 实例化对象.方法 解决硬编码print('趴着睡')alex = Dog()
alex.sleep()
- 有参
继承的初始化的参数务必对齐
class Person(object):def __init__(self, name, age):self.name = nameself.age = agedef sleep(self):print("基类sleep...")class TL(Person):# def __init__(self, name, age, dep):# self.name = name# self.age = age# self.dep = dep# 注意:继承的初始化的参数务必对齐def __init__(self, name, age, dep):super().__init__(name, age)self.dep = depyuan = TL("yuan", 18, "游戏部")yuan.sleep()
print(yuan.dep)
1.3.3 面试题:作用域问题
- 输出值为多少?
class A:x = 100def __int__(self, x):self.x = xdef foo(self):print(self.x)class B(A):x = 12b = B()
b.foo()# 12
1.3.4 继承案例
- 案例:电商项目的结算功能
继承、调用思想之应用,解耦发版代码,每次活动发布都不用修改源码
import random
import datetimeprint('''1、海尔冰箱 单价3000元;2、西门子洗衣机 单价5000元;3、芝华士沙发 单价6000元''')goods = {1: ("海尔冰箱", 3000), 2: ("西门子洗衣机", 5000), 3: ("芝华士沙发", 6000)}# 结账类
class Bill(object):def __init__(self,name):self.name=namedef get_unit(self):unit = int(input("请输入商品序号>>>"))return unitdef get_number(self):number = int(input("请输入商品数量>>>"))return numberdef get_total_price(self):unit = self.get_unit()number = self.get_number()total_price = goods[unit][1] * numberret = self.discount(total_price)print("%s于%s总共花费%s元"%(self.name,datetime.datetime.today(),ret))return retdef discount(self,price):return price# bill=Bill("Yuan老师")
# bill.get_total_price()class NationalDayBill(Bill):def discount(self, price):if price > 399:return price-200return super().discount(price)# ndb=NationalDayBill("Yuan")
# ndb.get_total_price()class Double11Bill(Bill):def discount(self,price):if price > 200:free = random.randint(0, 1)if free == 0:return 0return super().discount(price)# mab=Double11Bill("Yuan")
# mab.get_total_price()# 问题:如果再加一个中秋节满五百折扣0.8
# 但是假如中秋节和国庆节是一天,所以两个优惠同时享受的话该怎样设计?class MiddleAutumeBill(NationalDayBill):def discount(self, price):if price > 500:temp_price = super().discount(price) # 国庆优惠后的价格return temp_price*0.8 # 中秋再次优惠return super().discount(price) # 国庆优惠后的价格mab=MiddleAutumeBill("Yuan")
mab.get_total_price()
1.3.5 多重继承
如果在继承元组中列了一个以上的类,那么它就被称作"多重继承" 。派生类的声明,与他们的父类类似,继承的基类列表跟在类名之后,如下所示:
class SubClassName (ParentClass1[, ParentClass2, ...]):...
继承 基类、功能类
继承中查找方法的方法顺序是:先查子类,然后从左往右,从最左边父类找,没有的话再往右边的类找
class Animal:def eat(self):print("eating...")def sleep(self):print("sleep...")# def fly(self):# print('fly.... from Animal class')# 功能类
class Fly:def fly(self):print('fly.... from Fly class')class Eagle(Animal, Fly):passclass Bat(Animal, Fly):passalex = Eagle()
alex.eat()
alex.fly()
1.4 判断类的继承关系:type 和isinstance方法
class Animal:def eat(self):print("eating...")def sleep(self):print("sleep...")class Dog(Animal):def swim(self):print("swimming...")alex = Dog()
mjj = Dog()a = Animal()print(isinstance(alex, Dog)) # True
print(isinstance(alex, Animal)) # True
print(type(alex)) # <class '__main__.Dog'>print(isinstance(a, Dog)) # False# 判断 A 是否 为 B 的子类
print(issubclass(Animal, (Dog,))) # False
print(issubclass(Dog, (Animal,))) # True
1.5 查看实例的所有属性:dir()方法和__dict__属性
dir(obj)可以获得对象的所有属性列表, 而obj.__dict__
对象的自定义属性字典
注意事项:
- dir(obj)获取的属性列表中,方法也认为属性的一种。返回的是list
obj.__dict__
只能获取自己自定义的属性,系统内置属性无法获取。返回是dict
class Student:def __init__(self, name, score):self.name = nameself.score = scoredef test(self):passyuan = Student("yuan", 100)
print("获取所有的属性列表")
print(dir(yuan))print("获取自定义属性字段")
print(yuan.__dict__)
其中,类似__xx__
的属性和方法都是有特殊用途的。如果调用len()函数视图获取一个对象的长度,其实在len()函数内部会自动去调用该对象的__len__()
方法
2、面向对象特性之封装
2.1 封装概述
是指隐藏对象的属性和实现细节,仅对外提供公共访问方式。
2.1.1 封装原则
- 将不需要对外提供的内容都隐藏起来
- 把属性隐藏,提供公共方法对其访问
2.1.2 封装好处
- 隐藏实现细节,提供公共的访问方式
- 提高了代码的复用性
- 提高安全性
2.2 私有属性
2.2.1 私有属性定义
在class内部,可以有属性和方法,而外部代码可以通过直接调用实例变量的方法来操作数据,这样,就隐藏了内部的复杂逻辑。但是,从前面Student类的定义来看,外部代码还是可以自由地修改一个实例的name
、score
属性:
class Student(object):def __init__(self, name, score):self.name = nameself.score = scorealex=Student("alex",66)
yuan=Student("yuan",88)alex.score=100
print(alex.score)
如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__
,在Python中,实例的变量名如果以__
开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问,所以,我们把Student类改一改:
class Student(object):def __init__(self, name, score):self.__name = nameself.__score = scorealex=Student("alex",66)
yuan=Student("yuan",88)print(alex.__score) # 报错
改完后,对于外部代码来说,没什么变动,但是已经无法从外部访问实例变量.__name
和实例变量.__score
。
这样就确保了外部代码不能随意修改对象内部的状态,这样通过访问限制的保护,代码更加健壮。
2.2.2 私有属性完整 demo
class Student:def foo(self):print(self.__score) # self.__score --> self._Student__scoreclass Person(Student):def __init__(self, name, score):self.name = nameself.__score = score # self.__score --> self._Person__scoredef get_score(self):print(self.__score)return self.__score # self.__score --> self._Person__scoredef set_score(self, score_val):if isinstance(score_val, int) and 0 <= score_val <= 100:self.__score = score_val # self.__score --> self._Person__scoreelse:raise ValueError('input error value!')alex = Person('alex', 66)
alex.get_score()
# print(alex.__score) # AttributeError: 'Person' object has no attribute '__score'# print(alex.__dict__) # 查看实例自定义的属性
# {'name': 'alex', '_Person__score': 66} # 这块简单转化,设置为私有属性,但也很容易拿,一切靠自觉# 私有化的伪装
# print(alex._Person__score) # 66# 内部 判断 设置 属性值 的 合理性
alex.set_score(1000) # ValueError: input error value!# 私有属性 只能在 本类 中使用
alex.foo() # AttributeError: 'Person' object has no attribute '_Student__score'
2.2.3 私有属性总结
- 私有属性 只能在 本类 使用
- 确保了外部代码不能随意修改对象内部的状态,这样通过访问限制的保护,代码更加健壮。
- 这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:
_类名__属性
, 然后就可以访问了,如 alex._Student__score
- 变形的过程只在类的内部生效, 在定义后的赋值操作,不会变形
2.3 单下划线、双下划线、头尾双下划线说明
-
__foo__
: 定义的是特殊方法,一般是系统定义名字 ,类似__init__()
之类的。 -
_foo
: 以单下划线开头的表示的是 protected 类型的变量,即保护类型只能允许其本身与子类进行访问。(约定成俗,不限语法) -
__foo
: 双下划线的表示的是私有类型(private)的变量, 只能是允许这个类本身进行访问了。
2.4 私有方法
在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的:
class Base:def __foo(self):print("foo from Base")def test(self):self.__foo() # self.__foo() --> self._Base__foo()class Son(Base):def __foo(self): # self.__foo() --> self._Son__foo()print("foo from Son")s = Son()
s.test()
# foo from Base
2.5 property属性装饰器
将 方法 通过 属性 句点符的方式 来操作
使用接口函数获取修改数据 和 使用点方法(alex.name = ‘yuan’ 或者print(yuan.name))设置数据相比, 点方法使用更方便,我们有什么方法达到 既能使用点方法,同时又能让点方法直接调用到我们的接口了,答案就是property属性装饰器:(表象就是把方法当成属性来操作)
class Student(object):def __init__(self, name, score, sex):self.__name = nameself.__score = scoreself.__sex = sex# 方法 进行属性 句点符 取值@propertydef name(self):return self.__name# 修改@name.setterdef name(self, name):if len(name) > 1:self.__name = nameelse:print("name的长度必须要大于1个长度")@propertydef score(self):return self.__score@score.setterdef score(self, score):if score > 0 and score < 100:self.__score = scoreelse:print("输入错误!")yuan = Student('alex', 18, 'male')yuan.name = 'eval' # 调用了score(self, score)函数设置数据print(yuan.name) # 调用了score(self)函数获取数据yuan.score = 199
print(yuan.score)
@property
广泛应用在类的定义中,可以让调用者写出简短的代码,同时保证对参数进行必要的检查,这样,程序运行时就减少了出错的可能性。
3、面向对象特性之多态
3.1 多态的使用
多态是指一类事物有多种形态。比如动物有多种形态,人,狗,猫,等等。文件有多种形态:文本文件,可执行文件。总而言之,多态即某类的再分类,再分的每一类就是父类的多种形态的一种。
from abc import ABCMeta, abstractmethod # (抽象方法)class Payment(metaclass=ABCMeta): # metaclass 元类 metaclass = ABCMeta表示Payment类是一个规范类def __init__(self, name, money):self.money = moneyself.name = name@abstractmethod # @abstractmethod表示下面一行中的pay方法是一个必须在子类中实现的方法def pay(self, *args, **kwargs):passclass AliPay(Payment):def pay(self):# 支付宝提供了一个网络上的联系渠道print('%s通过支付宝消费了%s元' % (self.name, self.money))class WeChatPay(Payment):def pay(self):# 微信提供了一个网络上的联系渠道print('%s通过微信消费了%s元' % (self.name, self.money))class Order(object):def account(self, pay_obj):pay_obj.pay()# 第一个订单
pay_obj = WeChatPay("yuan", 100)
# 第二个订单
pay_obj2 = AliPay("alex", 200)order = Order()
order.account(pay_obj)
order.account(pay_obj2)# yuan通过微信消费了100元
# alex通过支付宝消费了200元
在调用order对象的account()方法时,程序并不关心为该方法传入的参数对象pay_obj是谁,只要求此参数对象pay_obj包含pay()方法即可,而调用者传入的参数对象类型pay_obj是子类对象还是其他类对象,Python无所谓!多态性就是相同的消息(函数方法触发)使得不同的类做出不同的响应,这就是典型的类编程中多态性的应用实例。
3.2 多态发生的前提:
- 继承:多态一定是发生在子类和父类之间。
- 重写:多态子类重写父类方法
3.3 鸭子模型 (动态类语言)
在鸭子类型中,关注的不是对象的类型本身,而是它是如何使用的。
例如,在不使用鸭子类型的语言中,我们可以编写一个函数,它接受一个类型为"鸭子"的对象,并调用它的"走"和"叫"方法。在使用鸭子类型的语言中,这样的一个函数可以接受一个任意类型的对象,并调用它的"走"和"叫"方法。如果这些需要被调用的方法不存在,那么将引发一个运行时错误。任何拥有这样的正确的"走"和"叫"方法的对象都可被函数接受的这种行为引出了以上表述,这种决定类型的方式因此得名。
from abc import ABCMeta, abstractmethod # (抽象方法)class Payment(metaclass=ABCMeta): # metaclass 元类 metaclass = ABCMeta表示Payment类是一个规范类def __init__(self, name, money):self.money = moneyself.name = name@abstractmethod # @abstractmethod表示下面一行中的pay方法是一个必须在子类中实现的方法def pay(self, *args, **kwargs):passclass AliPay(Payment):def pay(self):# 支付宝提供了一个网络上的联系渠道print('%s通过支付宝消费了%s元' % (self.name, self.money))class WeChatPay(Payment):def pay(self):# 微信提供了一个网络上的联系渠道print('%s通过微信消费了%s元' % (self.name, self.money))class CardPay(object):def __init__(self, name, money):self.money = moneyself.name = name# 呱呱叫:也可以实现别人所拥有的功能def pay(self):print('%s 通过银行卡消费了%s元' %(self.name, self.money))class Order(object):def account(self, pay_obj):pay_obj.pay()# 第一个订单
pay_obj = WeChatPay("yuan", 100)
# 第二个订单
pay_obj2 = AliPay("alex", 200)
# 第三个订单
pay_obj3 = CardPay('eval', 1999)order = Order()
order.account(pay_obj)
order.account(pay_obj2)
order.account(pay_obj3)# yuan通过微信消费了100元
# alex通过支付宝消费了200元
# eval 通过银行卡消费了1999元
上边代码看来:只要他拥有pay()
方法,就可以正确的被调用。
// 静态语言
静态语言是在编译时变量的数据类型即可确定的语言,多数静态类型语言要求在使用变量之前必须声明数据类型。
例如:C++、Java、Delphi、C#等。// 动态语言
动态语言是在运行时确定数据类型的语言。变量使用之前不需要类型声明,通常变量的类型是被赋值的那个值的类型。
例如PHP/ASP/Ruby/Python/Perl/ABAP/SQL/JavaScript/Unix Shell等等。
上面的例子中order.account(pay_obj)
中pay_obj不需要类型声明,像java在使用时要定义好类型
(order.account(Payment pay_obj)
),所以你传入别的类型对象一定报错,但是python因为时动态语言所以传入的对象只要拥有调用的方法即可视为Payment类型对象,即所谓的鸭子类型。
4、魔法方法
4.1 __new__ () 构造方法
Base() 的执行过程:
- obj = super().new(cls) : 开辟空间对象 obj, 返回对象 return obj
- _init_(obj)
class Base(object):def __new__(cls, *args, **kwargs):"""构造方法:param args::param kwargs::return: obj"""print("我是用来开辟一块空间的")obj = super().__new__(cls)return objdef __init__(self):super().__init__()print('sef 就是__new__() 开辟的空间地址')b = Base()"""
Base() 的执行过程:
1 obj = super().__new__(cls) : 开辟空间对象 obj return obj
2 __init__(obj)
"""# 我是用来开辟一块空间的
# sef 就是__new__() 开辟的空间地址
应用:比如单例模式
4.2 __init__() 初始化方法
# 初始化方法:__init__
class A(object):def __init__(self):print("初始化执行方法")
A()
4.3 __str__()
# __str__class C(object):def __init__(self,name,age):self.name=nameself.age=agedef __str__(self): # 必须返回字符串类型return self.namec1=C("c1",20)
c2=C("c2",23)
print(c1)
print(c2)
4.4 __call__()
实例方法都有 __call__方法,实例对象() 一定会走到类的__call__()
# __call__
class D(object):def __call__(self, *args, **kwargs):print("call 被调用...")print(callable(D))
d1=D()
print(callable(d1))
d1() # 实例方法都有 __call__方法,实例对象() 一定会走到类的__call__()
- python 可调用对象:可以通过内置函数callable来判断,如:
print(callable\(p\)) #True
- 如果类定义了__call__方法,那么它的实例可以变为可调用对象
4.5 __del__() 构析方法
class F(object):def __del__(self): print("删除对象时被调用!") # 当程序运行完成后,python解释器会自动清理f=F()
# del f # 思考:注释掉为什么也会调用__del__? 答案:python解决器自动调用的
import time
time.sleep(100)
- 应用:文件自动关闭、数据库自动关闭等
class Filehandler(object):file="a.text"def __init__(self):self.f=open(self.file)def __del__(self):self.f.close()
4.6 __getitem__() 字典化操作 属性
class G(object):def __init__(self):passdef __getitem__(self,item):print("__getitem__被调用")def __setitem__(self, key, value):print("__setitem__被调用")def __delitem__(self, key):print("__delitem__被调用")g=G()
g["name"]="alex"
print(g["name"])
del g["name"]
4.7 __getattr__() attr 句点符 操作属性
class H(object):def __init__(self):passdef __getattr__(self, item):print("__getattr__被调用")def __setattr__(self, key, value):print("__setattr__被调用")def __delattr__(self, item):print("__delattr__被调用")h=H()
h.name="alex"
print(h.name)
del h.name
4.8 __eq__() 用于两个对象比较
class Person(object):def __init__(self, name, age):self.name = nameself.age = agedef __eq__(self, other):# if self.name == other.name and self.age == other.age:# return True# else:# return Falsereturn Truei1 = Person("alex", 30)
i2 = Person("yuan", 30)
print(i1 == i2) # True
注意:为了节省内存,实例方法 是 存储在 类空间内,所有 同类下的所有 实例方法 都相等
- 面试题
class Person(object):def __init__(self, name, age):self.name = nameself.age = agedef foo(self, other):passdef bar(self):passdef __eq__(self, other):print('假定都是True')return True# return self == valuealex = Person("alex", 30)
yuan = Person("yuan", 30)
print(id(yuan.foo) == id(alex.foo)) # True , 所有实例对象方法 都存在 类空间内
print(yuan.foo == alex.bar) # False, 不同实例方法未调用__eq__方法,肯定是 False
print(yuan.foo == alex.foo) # True, 相同实例方法调用且修改了__eq__方法,均返回 True
print(yuan.foo == yuan.foo) # True
实例方法都存在类空间,故id(实例方法)都相同,但对象各异,所以对象.方法 也都不同
class Person(object):def __init__(self, name, age):self.name = nameself.age = agedef foo(self, other):passdef bar(self):passdef __eq__(self, other):print('假定都是True')return True# return self == valuealex = Person("alex", 30)
yuan = Person("yuan", 30)
print(id(yuan.foo) == id(alex.foo)) # True
print(yuan.foo == alex.foo ) # False
4.9 __len__()
class G(object):def __len__(self):return 100g = G()
print(len(g))
print(g.__len__())
# 100
# 100
5、反射
反射这个术语在很多语言中都存在,并且存在大量的运用,今天我们说说什么是反射,反射主要是指程序可以访问、检测和修改它本身状态或行为的一种能力,在python中一切皆对象(类,实例,模块等等都是对象),那么我们就可以通过反射的形式操作对象相关的属性。
Python中的反射主要有下面几个方法:
1.hasattr(object,name)
判断对象中有没有一个name字符串对应的方法或属性
2.getattr(object, name, default=None)
获取对象name字符串属性的值,如果不存在返回default的值
3.setattr(object, key, value)
设置对象的key属性为value值,等同于object.key = value
4.delattr(object, name)
删除对象的name字符串属性
应用1:
class Person:def __init__(self, name, age, gender):self.name = nameself.age = ageself.gender = genderyuan = Person("yuan", 22, "male")
print(yuan.name)
print(yuan.age)
print(yuan.gender)# getattr 取值
print(getattr(yuan, 'name', 'default_val'))
print(getattr(yuan, 'xxx', 'default_val'))# hasattr 判断是否拥有此属性,返回布尔值
print(hasattr(yuan, 'names')) # False
print(hasattr(yuan, 'name')) # True# setattr 修改 对象的 属性 值
print(yuan.name) # yuan
setattr(yuan, 'name', 'alex') # 修改 name 属性值
print(yuan.name) # alexwhile 1:attr = input('请输入属性名>>>')if hasattr(yuan, attr):val = getattr(yuan, attr)print(val)else:print('没有该属性!')val = input('请为该属性赋值>>>')setattr(yuan, attr, val)
应用2: FTP
class FTP(object):def __init__(self):self.run()def run(self):print('''提示:上传: put 路径/文件名称下载: get 路径/文件名称''')while 1:input_str=input(">>>")action, params = input_str.split(" ")if hasattr(self, action):getattr(self, action)()else:print("不存在该方法")def put(self):print("上传...")def get(self):print("下载...")ftp = FTP()
这篇关于py面向对象特性:继承、封装、多态、鸭子模型、魔法方法的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!