• 面向对象编程


    前言

    本篇博客整合了以下知识点



    面向对象的概念

    面向对象是一种编程思想。

    是对现实世界中一类事物的抽象,在编程中可以理解为是一种建立现实世界事物的模型

    面向对象与其他对比

    面向过程编程vs函数式编程vs面向对象编程

     面向过程编程 测量对象的元素个个数。
    s1 = 'fjdsklafsjda'
    count = 0
    for i in s1:
        count += 1
        
    函数式编程    
    def func(s):
        count = 0
        for i in s:
            count += 1
        return count
    func('fdsafdsa')
    func([1,2,3,4])
    
    class A:
        __in
    

    总结

    面向过程式编程:
    好处: 出色的完成你之前布置的所有的需求.
    坏处: 但凡更改或者增加一条需求,可能整个项目都随之改变.

    函数编程较之面向过程编程最明显的两个特点:
    1,减少代码的重用性。
    2,增强代码的可读性。

    面向对象编程:
    是一类相似功能函数的集合,使你的代码更清晰化,更合理化。
    面向对象,要拥有上帝的视角看问题,类其实就是一个公共模板(厂房),对象就从具体的模板实例化出来。

    面向对象的语法结构

    class A:
        hoppy='玩'静态属性
        def __init__(self,name):双下方法
            self.name=name属性
        def func(self):方法
            print(666)
    A.func()
    
    class 是关键字类似函数的def
    A是类名要具有描述性,类名首字母大写,类名不宜用_
    hoppy是类的静态属性,在类中可有可不要
    

    从不同角度调用类的属性和方法

    从类名的角度使用类

    类名可以调用类中的属性和方法

    ps注意: 类名调用了方法必须穿参 不然报错 类名可以对类中的属性 方法增删改查

    class A:
        hobby='玩'静态属性
        def __init__(self,name):双下方法
            self.name=name属性
        def func(self):方法
            print(666)
     1. 查看类中的所有的内容.  类名.__dict__只用于获取类中的全部.
     print(A.__dict__)
     print(A.__dict__['hoppy'])
    
    
     2. 万能的 .点.
    print(A.hobby)   查
    A.func(1)
    A.cloth = '校服'   增
     print(A.__dict__)
    A.examination = '不考试!'   改
     del A.hoppy   删
    del A.func   删
     print(A.__dict__)
    

    从实列化对象的角度

    注意 :实例化对象发生了三件事

     实例化一个对象时发生了三件事:
     1. 利用父类__new__ 在内存中创建一个对象空间.
     2. 自动执行__init__方法,并且将对象空间传给self参数.
     3. 执行__init__方法里面的代码,给对象空间封装其属性.
    

    对象调用对象的属性和方法

    • 对象查看类中的属性.但是不可以修改
    • 对象可以操作对象空间的属性 万能的点 进行增删改查
    class Student:
        daily = '学习'
        examination = '考试'
        def __init__(self,n,a,h):
            self.name = n
            self.age = a
            self.hobby = h
    
        def work(self,c):
            self.color = c
            print(f'{self.name}每天要上课')
    
        def homework(self):
            print('家庭作业')
    
    mc_sq = Student('骚强',18,'高端会所')   类名() 过程就叫做实例化过程,实例化一个对象
    mc_sq.age = '29'   增
    del mc_sq.n   删
    mc_sq.sex = '女' 改
    print(mc_sq.n)   查
    print(mc_sq.__dict__)
    
     对象查看类中的属性.
     mc_sq = Student('骚强',18,'高端会所')
      print(mc_sq.daily)
     mc_sq.daily = '骚麦'
     print(mc_sq.daily)
     print(mc_sq.__dict__)
     对象调用类中的方法
    mc_sq.homework()
    

    小问题

     什么是self?
     self 就是类中方法的第一个位置参数,
      如果通过对象执行此方法,解释器就自动的将此对象空间当做实参传给self.
     约定俗称: 类中的方法第一个参数一般都设置成self.
    
     一个类可以实例化多个对象
    无数个
    

    从空间的角度研究类

     类外面可以给对象封装属性
     类内部封装属性
     对象如果修改父类属性其实就是给自己空间增加这个属性
     对象如果查询一个属性: 对象空间  ----> 类空间 ----> 父类空间  --->
     类查询一个属性: 类空间  ----> 父类空间  ---->
     单向不可逆
     对象与对象之间原则上互相独立(除去组合这种特殊的关系之外).
    

    类与类间关系

     1. 依赖关系(主从关系)
     依赖关系: 将一个类的类名或者对象传给另一个类的方法中.
     2. 组合关系(关联组合聚合)
     组合: 将一个类的对象封装成另一个类的对象的属性.
     3. 继承关系
    单继承 多继承
    

    依赖关系实列

     依赖关系(主从关系)实列  将一个类的类名或者对象传给另一个类的方法中.
    class A:
        name='AAAAA'
        def __init__(self,name ):
            self.name=name
        def open(self,obj1):
            print('我是A类中函数')
            obj1.open_door()
    class B:
        def __init__(self,name ):
            self.name=name
        def func_b(self,c):
            print(f'我是B类函数 传的是什么呀---{c.name}')
    a= A('a1')
    b=B('b1')
    b.func_b(A)
    

    组合关系实列

    class A:
        name='AAAAA'
        def __init__(self,name ):
            self.name=name
        def open(self):
            print('我是A类中函数')
    class B:
        def __init__(self,name ):
            self.name=name
        def func_b(self,c):
            print(f'我是B类函数 传的是什么呀---{c.name}')
    a= A('a1')
    b=B(a)
    b.name.open()
    

    面向对象之继承

    什么是继承?

    专业角度: B 继承 A类, B就叫做A的子类,派生类,
    A叫做B的父类,基类,超类.
    B类以及B类的对象使用A类的所有的属性以及方法.
    字面意思: 继承就是继承父母所有的资产.
    

    继承的优点

    节省代码.
    增强的耦合性.
    代码规范化.
    

    单继承

    单继承
    class A:
        pass
    class B(A):
        pass
     B 子类,派生类
     A 父类, 基类, 超类
     单继承: 使用.
     子类对象执行父类的一切.  实例化对象一定一定会执行三件事. 一定会执行__init__
      注意: 子类以及子类对象只能调用父类的属性以及方法,不能操作(增删改).
     子类将父类的方法覆盖了,(重写父类的方法) 调用就不找父类 类指针
     如何既要执行父类方法又要执行子类方法
             Animal.__init__(self, name, age, sex)
              方法二:
              super(Person, self).__init__(name, age, sex)
              super().__init__(name, age, sex)
             self.hobby = hobby
    

    多继承

    在python2x版本中存在两种类.:
      ⼀个叫经典类. 在python2.2之前. ⼀直使⽤的是经典类. 经典类在基类的根如果什么都不写.
      ⼀个叫新式类. 在python2.2之后出现了新式类. 新式类的特点是基类的根是object类。
    python3x版本中只有一种类:
    python3中使⽤的都是新式类. 如果基类谁都不继承. 那这个类会默认继承 object

    经典类的多继承

    虽然在python3中已经不存在经典类了. 但是经典类的MRO最好还是学⼀学. 这是⼀种树形结构遍历的⼀个最直接的案例. 在python的继承体系中. 我们可以把类与类继承关系化成⼀个树形结构的图. 来, 上代码:

    在经典类中采⽤的是深度优先,遍历⽅案. 什么是深度优先. 就是⼀条路走到头. 然后再回来. 继续找下⼀个.

    class A:
        pass
    class B(A):
        pass
    class C(A):
        pass
    class D(B, C):
        pass
    class E:
        pass
    class F(D, E):
        pass
    class G(F, D):
        pass
    class H:
        pass
    class Foo(H, G):
        pass
    
    

    img

    答案 类的MRO: Foo-> H -> G -> F -> E -> D -> B -> A -> C.

    新式类的多继承

    一般我们用 mro方法就行

    举例

    print(A.mro())
    

    面向对象的3大特性

    封装:
        把很多数据封装到⼀个对象中. 把固定功能的代码封装到⼀个代码块, 函数, 对象, 打包成模块.
        这都属于封装的思想. 具体的情况具体分析. 比如. 你写了⼀个很⽜B的函数. 那这个也可以被称为封装. 在⾯向对象思想中. 是把⼀些看似⽆关紧要的内容组合到⼀起统⼀进⾏存储和使⽤. 这就是封装.
        
    继承:
    	⼦类可以⾃动拥有⽗类中除了私有属性外的其他所有内容
    多态:
        python中 定义变量不用规定变量的类型.
        
    鸭子类型
        A,B两个类,没有任何关系,独立两个,但是里面的功能相似,所以python一般会将类似于A,B两个类
        里面的相似的功能让其命名相同.
        1. A,B虽然无关系,但是很默契的制定了一个规范.让你使用起来更方便.
    

    类的约束与归一化设计

     Python接口与归一化设计
     1.什么是接口(interface)
         接口(interface)是面向对象编程语言中接口操作的关键字,功能是把所需成员组合起来,用来装封一定功能的集合。它好比一个模板,在其中定义了对象必须实现的成员,通过类或结构来实现它。接口不能直接实例化,接口不能包含成员的任何代码,只定义成员本身。接口成员的具体代码由实现接口的类提供。
     2.归一化
         使用接口的意义在于归一化,什么叫归一化:就是只要是基于同一个接口实现的类,那么所有的这些类产生的对象在使用时,从用法上来说都一样。归一化的好处在于:
     归一化让使用者无需关心对象的类是什么,只需要的知道这些对象都具备某些功能就可以了,这极大地降低了使用者的使用难度。
     归一化使得上层的外部使用者可以不加区分的处理所有接口兼容的对象集合
     1. 就好象linux的泛文件概念一样,所有东西都可以当文件处理,不必关心它是内存、磁盘、网络还是屏幕(当然,对底层设计者,当然也可以区分出“字符设备”和“块设备”,然后做出针对性的设计:细致到什么程度,视需求而定)。
     2. 再比如:我们有一个汽车接口,里面定义了汽车所有的功能,然后由本田汽车的类,奥迪汽车的类,大众汽车的类,他们都实现了汽车接口,这样就好办了,大家只需要学会了怎么开汽车,那么无论是本田,还是奥迪,还是大众我们都会开了,开的时候根本无需关心我开的是哪一类车,操作手法(函数调用)都一样
     在上面的情况下(在一些重要的逻辑,与用户数据相关等核心部分),我们要建立一种约束,避免发生此类错误.
     支付接口等
     类的约束有两种解决方式:
     1. 在父类建立一种约束.
     2. 模拟抽象类(指定一种规范)的概念,建立一种约束.
     第一种解决方式:  约定俗称定义一种规范,子类要定义pay方法. python推荐
     第一种约束: 在父类定义一个pay方法,主动抛出异常,如果子类没有定义pay方法,并且沿用了父类的pay方法  即会报错.   python推荐的一种约束方式.
     第二种
     利用抽象类的概念: 基类如上设置,子类如果没有定义pay方法,在实例化对象时就会报错.
    

    第一种方法实列

     第一种
     第一种解决方式:
     class Payment:
         def pay(self,money):   约定俗称定义一种规范,子类要定义pay方法.
             raise Exception('子类必须定义此方法')
     class QQpay(Payment):
         def pay(self, money):
             print(f'利用qq支付了{money}')
     class Wechatpay(Payment):
         def fuqian(self,money):
             print(f'利用微信支付了{money}')
     def pay(obj,money,):   归一化设计
         obj.pay(money)
      假如
     obj=Wechatpay()
     pay(obj,300)
    

    第2种方法实列

     第二种: 固定写法
    from abc import ABCMeta,abstractmethod
    class Payment(metaclass=ABCMeta):
                 抽象类 接口类  规范和约束  metaclass指定的是一个元类
        @abstractmethod
        def pay(self, money):
            pass   抽象方法
    class Wechatpay(Payment):
        def fuqian(self,money):
            print(f'利用微信支付了{money}')
         def pay(self,money):
             pass
    obj3 = Wechatpay()
     利用抽象类的概念: 基类如上设置,子类如果没有定义pay方法,在实例化对象时就会报错.
    

    细分类的组成成员

    成员有以下:
    1、字段: 静态字段 普通字段
    2、方法: 静态方法 类方法 普通方法
    3、特性/属性 普通特性

    成员修饰符 修饰成员
    公有的:没有限制
    私有的:以__开头 仅仅内部可以访问,不能被继承,仅自己可访问。私有的成员可通过公有的成员间接访问

    何时用类调用,何时用对象调用?
    类调用: 无self
    对象调用:self

    结论

    1、静态字段和静态方法和类方法通过类来访问,普通字段和方法通过对象来访问
    2、静态方法,使用@staticmethod来装饰
    3、类方法,使用@classmethod来装饰
    4、属性(特性),使用@property来装饰,定义的时候类似普通方法一样,但调用的时候像普通字段一样
    5、属性的getter和setter,通过@property来装饰的方法return来getter,之后通过@方法名.setter装饰的方法来setter

    一些特殊的成员

    init 构造方法,创建对象时调用
    del 析构方法,销毁对象时调用
    call 对象() 调用
    getitem 对象[] 来调用
    setitem 对象[] = xxx 来调用
    delitem del 对象[] 来调用
    dict 列出所有的成员 用途:在表单对象中获取表单所有的字段
    参考字典对象dict
    str 类似java中的toString方法,直接打印对象输出的就是该方法的返回值

    方法部分

    class A:
        company_name = 'mcsq'   静态变量(静态字段)
        __iphone = '1353333xxxx'   私有静态变量(私有静态字段)
        def __init__(self,name,age): 特殊方法
            self.name = name  对象属性(普通字段)
            self.__age = age   私有对象属性(私有普通字段)
        def func1(self):   普通方法
            pass
        def __func(self): 私有方法
            print(666)
        @classmethod   类方法
        def class_func(cls):
            """ 定义类方法,至少有一个cls参数 """
            print('类方法')
        @staticmethod  静态方法
        def static_func():
            """ 定义静态方法 ,无默认参数"""
            print('静态方法')
        @property   属性
        def prop(self):
            pass
    class A:
        num = 1
        def func(self):
            print('实例方法')
        @classmethod   类方法: 由类名直接调用的方法,他会自动的将类名传给cls
        def a_func(cls,name):
            print(f'cls---> {cls}')
            print(cls,name)
    A.a_func(66)
    obj = A()
    obj.a_func(777)   对象也可以调用类方法,但是会自动将其从属于的类名传给cls
    

    类的私有成员

     类中的私有成员: 私有类的静态属性, 私有对象属性,私有方法
     对于类的公有静态属性,类的外部,类的内部,派生类都可以访问.
     私有静态属性: 类外部不能访问,类内部可以访问 ,派生类不可访问
     对象的私有属性 类的内部可以使用 ,类的外部不能访问 ,派生类中也不可访问
    
     如果想设定一些私有的或者是不想让类外面用到,密码,加密方式,等设置成私有成员.
     拓展: 私有成员除了在类内部,当真访问不到么?
     python中所有的私有成员: 就是在私有成员前面加上 _类名而已.
     print(A._A__girlnum)   千万不要这么去访问!!!
    

    类方法与静态方法

     类方法
     @classmethod   类方法: 由类名直接调用的方法,他会自动的将类名传给cls
     对象也可以调用类方法,但是会自动将其从属于的类名传给cls
      @staticmethod  静态方法: 不依赖于类,也不依赖于对象,他就是一个普通的函数放置于类中是结构更加清晰与合理.
    

    属性

     我们要让类方法伪装成属性,虽然在代码级别没有提升,但是看起来更合理.
     @property
     def aaa(self):
         print('get的时候运行我啊')
     @aaa.setter
     def aaa(self,v):
         print('修改的时候执行我')
     @aaa.deleter
     def aaa(self):
         print('删除的时候执行我')
    

    isinstance issubclass

     isinstance(obj,N): 判断 obj对象 是由N类(N的派生类)实例化的对象 返回 True.
     issubclass(M,N) 判断是M类是N类的子孙.
    
     type 到底是什么?
     type 元类 python中一切皆对象 , 一个类也是一个对象.
     么这个(类)对象肯定是由类实例化出来的.
     python中你创建的所有类,以及大部分list str等等这些类,都是从type元类实例化得来的.
     python中继承object类都是新式类.
     object 也是由type元类实例化得来的.
     type
    

    异常处理

     什么叫异常?
     你的程序出现中断,飘红,致使你的整个项目中断了.
     语法错误.
     语法错误就不应该现在你的代码中.
     逻辑错误:
    
     我们用 用if进行异常处理
     try
     单分支
    
     多分支
     及时解决异常,避免程序中断.
     程序的分流.
     第四种 万能 + 多分支
     异常处理其他成员
     finally:  在整个程序终止之前,执行finally 用于文件操作关闭文件句柄
     主动抛出异常: 为什么要有
     raise Exception('fdkslafjdslkf')
     断言:
     assert 条件   源码上assert.
     assert 1 == 2
     print(11)
     print(22)
     print(33)
     自定义异常(了解)
    class Connection(BaseException):
        def __init__(self,msg):
            self.msg = msg
    raise Connection('触发了连接异常')
    raise Connection('触发了连接异常')
     异常处理总结:
         异常处理不能经常使用:异常处理耗费性能.有些错误是需要进行分流使用.代码的可读性变差.
         关键节点使用.
     万能异常 Exception as e: 可以起别名
     什么时候用万能异常,什么时候用多分支?
    如果你只是想把这个异常处理掉,让程序继续执行. 万能异常.
     如果出现了异常,你是想根据不同的异常执行不同的逻辑流程,你要采取多分支.
    

    判断是函数还是类方法

    绑定方法和非绑定方法

    绑定方法(绑定给谁,谁来调用就自动将它本身作为第一个参数传进去)

    • 绑定给类:没有加任何装饰器的方法,其实对象也可以调用。只不过内部将类作为第一个参数传进去。
    • 绑定给对象:加classmethod装饰器的方法,其实类也可以调用,只不过要将对象作为第一个参数传进去。

    非绑定方法

    • 在类的内部使用staticmethod装饰器的函数叫做非绑定方法,谁都可以调用,没有自动传值一说,就是普通函数。
     1 通过函数名可以大致判断
     print(func)   <function func at 0x00000000005D1EA0> 函数
     obj = A()
     print(obj.func)   <bound method A.func of <__main__.A object at 0x0000000001DE1CF8>> 方法
    
     2. 通过types模块去验证
    from types import FunctionType 返回bool True就是函数
    from types import MethodType
     类名调用func 就是一个函数
     print(isinstance(A.func, FunctionType)) True   函数
     对象调用func 就是一个方法
     print(isinstance(obj.func, FunctionType))
     对于静态方法的研究
     print(isinstance(A.f, FunctionType))
    
     结论
     1. 类⽅法.不论任何情况,都是⽅法.
     2. 静态方法,不论任何情况.都是函数
     3. 实例方法,如果是实例访问.就是⽅法.如果是类名访问就是函数.
     函数与方法
     函数: 全都是显性传参
     方法: 存在隐性传参
    

    反射

    1.什么是反射?以及应⽤场景?(2分)
    通过字符串的形式操作对象相关的属性。python中的⼀切事物都是对象(都可以使⽤反射)
     python面向对象中的反射:通过字符串的形式操作对象相关的属性。python中的一切事物都是对象(都可以使用反射)
     从实例的角度去研究反射 可以使用
     getattr(obj,'func')()
     从类的角度研究反射 可以使用
     getattr(A,'func')(obj)
     从当前脚本研究反射 可以使用
     hasattr(this_module, 's1')
     在其他模块研究反射 可以使用
     getattr(obj,'test')()
     实列
    class A():
        def __init__(self,name):
            self.name=name
    obj=A('SQ')
    print(hasattr(obj,'name'))True
    print(getattr(obj,'name',666))SQ 加第3个参数 不会报错
    setattr(obj,'hobby','玩')增加属性   可以覆盖 左边属性 右边属性值
    print(getattr(obj,'hobby'))
    delattr(obj,'name')删除属性
    print(getattr(obj,'name','么有了呀'))
    
     总结
     print(hasattr(obj,'name'))查询 返bool 确认 有没有这个属性
     print(getattr(obj,'name','没有'))查询 返回 对应属性值  加第3个参数 不会报错
     setattr(obj,'aa','aa')增加属性第2个参数是键第3个是值   可以覆盖
     delattr(obj,'name')删除属性
    

    双下方法

     总结
     一个对象之所以可以使用len()函数,根本原因是这个对象从输入的类中有__len__方法,
     hash(obj) 会调用obj这个对象的类(基类)的__hash__方法
     print(str(obj))   会触发__str__
     print(obj)   打印输出实例会触发__str__
     print('此对象为%s' %obj)   格式化输出会触发__str__
     print(obj)   触发__repr__
     print('此对象是%r' %obj)   触发__repr__
     print(obj) 先触发__str__方法
     __call__ 方法的执行是由对象后加括号触发的,而构造方法的执行是由创建对象触发的
     print(a == b)   对一个类的两个对象进行比较操作,就会触发__eq__方法
     __new__ 构造方法
     __new__创造并返回一个新对象.
     类名() 先触发__new__ 并且将类名自动传给cls.
     __item__对对象进行类似于字典的操作
     对一个对象类似于进行with语句上下文管理的操作, 必须要在类中定义__enter__ __exit__
     单列模式
     这个类的对象不是个性化的,主要是实例化对象之后去执行类中的方法.
     开辟一个空间__new__创造并返回一个空间
     __new__ 在执行__init__
     自己定义的类继承于object
     第1步 定义一个静态属性 执行自己的__new__
     第2步 进行判断 执行父类__new__产生一个新空间
     修改属性值 返回 第一次创造的对象
    class A:
        __a=None
        def __new__(cls, *args, **kwargs):类名等于 cls
            if not cls.__a:
                obj=object.__new__(cls)执行父类__new__
                cls.__a=obj
            return cls.__a
    

    len

    class B:
        def __len__(self):
            print(666)
    
    b = B()
    len(b)  len 一个对象就会触发 __len__方法。
    

    hash

    class A:
        def __init__(self):
            self.a = 1
            self.b = 2
    
        def __hash__(self):
            return hash(str(self.a)+str(self.b))
    a = A()
    print(hash(a))
    

    str

    class A:
        def __init__(self):
            pass
        def __str__(self):
            return '太白'
    a = A()
    print(a)
    print('%s' % a)
    
    

    repr

    如果一个类中定义了__repr__方法,那么在repr(对象) 时,默认输出该方法的返回值。

    class A:
        def __init__(self):
            pass
        def __repr__(self):
            return '太白'
    a = A()
    print(repr(a))
    print('%r'%a)
    
    

    call

    对象后面加括号,触发执行。

    注:构造方法__new__的执行是由创建对象触发的,即:对象 = 类名() ;而对于 call 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()

    class Foo:
    
        def __init__(self):
            pass
        
        def __call__(self, *args, **kwargs):
    
            print('__call__')
    
    
    obj = Foo()  执行 __init__
    obj()        执行 __call__
    
    

    eq

    class A:
        def __init__(self):
            self.a = 1
            self.b = 2
    
        def __eq__(self,obj):
            if  self.a == obj.a and self.b == obj.b:
                return True
    a = A()
    b = A()
    print(a == b)
    
    

    del

    析构方法,当对象在内存中被释放时,自动触发执行。

    注:此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。

    new 单列模式

    class A:
        def __init__(self):
            self.x = 1
            print('in init function')
        def __new__(cls, *args, **kwargs):
            print('in new function')
            return object.__new__(A, *args, **kwargs)
    
    a = A()
    print(a.x)
    
    

    __item__系列

    class Foo:
        def __init__(self,name):
            self.name=name
    
        def __getitem__(self, item):
            print(self.__dict__[item])
    
        def __setitem__(self, key, value):
            self.__dict__[key]=value
        def __delitem__(self, key):
            print('del obj[key]时,我执行')
            self.__dict__.pop(key)
        def __delattr__(self, item):
            print('del obj.key时,我执行')
            self.__dict__.pop(item)
    
    f1=Foo('sb')
    f1['age']=18
    f1['age1']=19
    del f1.age1
    del f1['age']
    f1['name']='alex'
    print(f1.__dict__)
    
    
  • 相关阅读:
    Codevs 2597 团伙(并查集)
    Codevs 1074 食物链 2001年NOI全国竞赛
    Bzoj 3831 [Poi2014]Little Bird
    Codevs 4600 [NOI2015]程序自动分析
    Codevs 3287 货车运输 2013年NOIP全国联赛提高组(带权LCA+并查集+最大生成树)
    段落排版--中文字间距、字母间距
    段落排版--行间距(行高)
    段落排版--缩进
    文字排版--删除线
    文字排版--下划线
  • 原文地址:https://www.cnblogs.com/saoqiang/p/12177179.html
Copyright © 2020-2023  润新知