zoukankan      html  css  js  c++  java
  • 第四章 函数、函数调用、返回值、函数的传参、名称空间、函数名、闭包、装饰器

    1.什么是函数,为什么要使用函数                                 

    1.1什么是函数

        函数是对程序逻辑进行结构化或者过程化的一种编程方法。能将整块代码巧妙地隔离成易于管理的小块,把重复代码放到函数中而不是进行大量的拷贝。

    1.2为什么要使用函数

    # 例:len函数的编写
    # 在没有函数的时候输出字符串、列表的长度
    s1 = 'asdfjkl;'
    count = 0
    for i in s1:
        count += 1
    print(count)
    
    l1 = 'asdfjkl;'
    count = 0
    for i in l1:
        count += 1
    print(count)
    
    ### 这样编写的缺点:
    # 重复代码多
    # 可读性差

    2.函数结构调用                                                

    2.1函数结构:

    def 函数名(参数列表): # 函数名的命名规则与变量与变量相同

      函数体

    '''
    规则:
      1.函数名是由数字字母下划线任意组合。
      2.函数名不能是数字开头。
      3.函数名不能是Python中的关键字。
       ['and', 'as', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'exec', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'not', 'or', 'pass', 'print', 'raise', 'return', 'try', 'while', 'with', 'yield']
      4.函数名要具有可描述性。
      5.函数名不能使用中文。
      6.函数名不能过长。
    推荐:
        字母和下划线的组合
    '''

    2.2函数调用:

    函数名()

    #
    def func1():
        pass
    func1() # 函数名+()这是一个整体,表示执行函数,这个整体是函数的调用者
    print(func1())
    # 1- 执行函数func1(),得到返回值None
    # 2- 输出None

    3.函数的返回值return                                         

        函数内部尽量不要使用print,因为函数是以功能为导向的。

    函数返回值的作用

    # 1- 遇到return,结束函数。
    def func1():
        print(11)
        print(22)
        return
        print(33)
        print(44)
    
    func1()
    # 11
    # 22
    
    # 2- 给函数的调用者(执行者)返回值
    # 函数名+()这是一个整体,表示执行函数,这个整体是函数的调用者
    # 返回值的个数对应返回内容
    # 2.1- 无return 返回None
    def ret():
        pass
    print(ret(), type(ret()))
    # None <class 'NoneType'>
    
    # 2.2- 无return
    # 不写或者None 返回None
    def ret():
        pass
        return
    print(ret(), type(ret()))
    # None <class 'NoneType'>
    def ret():
        pass
        return None
    print(ret(), type(ret()))
    # None <class 'NoneType'>
    
    # 返回单个数 返回相应数据类型
    def ret():
        pass
        return 6
    print(ret(), type(ret()))
    # 6 <class 'int'>
    
    # 返回多个数据 将多个数放在元组中返回
    def ret():
        pass
        return 6, 'abc', [1, 2, 3]
    print(ret(), type(ret()))
    # (6, 'abc', [1, 2, 3]) <class 'tuple'>
    
    # 可以使用类似变量的分别赋值
    a, b, c = ret()
    print(a)
    print(b)
    print(c)
    # 6
    # abc
    # [1, 2, 3]

    4.函数的传参                                                 

    4.1实参与形参

    '''
    实参和形参
      实参:实际参数,调用函数时传给函数的参数,可以是常量、变量、表达式、函数,传给形参
      形参:形式参数,不占内存空间,形参变量只有在调用时才分配内存单元,目的是函数调用时接收实参
    二者区别:
      实参:是一个变量,占用内存空间,数据传送单向,实参传给形参,不能形参传给实参
      形参:虚拟的,不占用内存空间,形参变量只有被调用时才分配内存单元
    '''
    #
    s1 = 'asdfjkl;'
    def my_len(a): # 函数的定义()放的是形式参数,形参
        count = 0
        for i in a:
            count += 1
        return count
    
    ret = my_len(s1) # 函数的执行()放的是实际参数,实参
    print(ret)
    # 8

    4.2按实参分类

    # 按实参来分
    # 1- 位置参数,实参个数必须与形参个数一一对应,而且有顺序的
    #
    def func1(x, y):
        print(x, y)
    func1(1)
        # func1(1)
    # TypeError: func1() missing 1 required positional argument: 'y'
    func1(1, 2)
    # 1 2
    
    # 2- 关键字参数,实参个数必须与形参个数一一对应,不分顺序
    #
    def func1(x, y, z):
        print(x, y, z)
    func1(y=2, x=1, z=5)
    # 1 2 5
    
    # 3- 混合参数,实参个数必须与形参个数一一对应,而且关键字参数必须在位置参数后面。
    #
    def func2(a, b, c):
        print(a)
        print(b)
        print(c)
    func2(1, 2, c=4)
    # 1
    # 2
    # 4
    func2(1, c=4, 2) # 关键字参数必须在位置参数后面
    #     func2(1, c=4, 2)
    # SyntaxError: positional argument follows keyword argument

    4.3比较大小练习

    # 练习:比大小
    def max(x, y):
        if x > y:
            return x
        else:
            return y
    print(max(5, 3))
    # 5
    
    ### 三元运算表达式
    def max(x, y):
        ret = x if x > y else y
        return ret
    print(max(4, 3))
    # 4
    # 简写1
    def max(x, y):
        return x if x > y else y
    print(max(4, 3))
    # 4
    # 简写2
    def max(x, y):return x if x > y else y
    print(max(4, 3))
    # 4

    4.4按形参分类

    # 1- 位置参数,形参个数必须与实参个数一一对应,而且有顺序的
    #
    def func1(x, y):
        print(x, y)
    func1(1)
        # func1(1)
    # TypeError: func1() missing 1 required positional argument: 'y'
    func1(1, 2)
    # 1 2
    
    # 2- 默认参数,必须在位置参数后面
    # 例:人员信息录入,可以减少大量重复信息的输入
    def register(name, sex=''):
        with open('register', encoding='utf-8', mode='a') as f1:
            f1.write('{} {}
    '.format(name, sex))
    
    while True:
        name = input('请输入姓名:/q或Q退出 ')
        if name.upper() == 'Q': break
        if 'f' in name:
            sex = input('请输入性别:')
            register(name, sex)
        else:
            register(name)
    '''
    请输入姓名:/q或Q退出 张三
    请输入姓名:/q或Q退出 李四
    请输入姓名:/q或Q退出 f小红
    请输入性别:女
    请输入姓名:/q或Q退出 q
    # 张三 男
    # 李四 男
    # f小红 女
    '''
    
    # 3- 动态参数 *args,**kwargs 万能参数
    def func2(*args, **kwargs):
        print(args)  # 元组(实参的所有位置参数)
        print(kwargs) # 字典(实参的所有关键字参数的键值对形式)
    
    func2(1, 2, 3, 4, 5, 'alex', [2, 3, 4], a='www', b=222)
    # (1, 2, 3, 4, 5, 'alex', [2, 3, 4])
    # {'a': 'www', 'b': 222}

    4.5形参顺序

    # 形参最终顺序:位置参数,*args,默认参数,**kwargs
    # 1- *args位置
    def func3(a, b, sex='', *args):
        print(a)
        print(b)
        print(sex)
        print(args)
    func3(1, 2, '老男孩', 'alex', 'wusir')
    # 如果默认参数在args前面,默认参数会被覆盖掉
    # 1
    # 2
    # 老男孩
    # ('alex', 'wusir')
    
    def func3(a, b, *args, sex=''):
        print(a)
        print(b)
        print(sex)
        print(args)
    func3(1, 2, '老男孩', 'alex', 'wusir')
    # 1
    # 2
    #
    # ('老男孩', 'alex', 'wusir')
    
    # **kwargs位置
    # def func3(a, b, *args, **kwargs, sex='男'):
        # SyntaxError: invalid syntax 语法错误
    def func3(a, b, *args, sex='', **kwargs):
        print(a)
        print(b)
        print(sex)
        print(args)
        print(kwargs)
    func3(1, 2, '老男孩', 'alex', 'wusir', sex='', name='wusir')
    # 1
    # 2
    #
    # ('老男孩', 'alex', 'wusir')
    # {'name': 'wusir'}

    4.6参数的聚合和打散(*、**)

    #
    def func1(*args, **kwargs): # 函数定义时*表示聚合
        print(args)
        print(kwargs)
    l1 = [1, 2, 3, 4]
    l2 = ['alex', 'wusir', 4]
    func1(l1, l2)
    # ([1, 2, 3, 4], ['alex', 'wusir', 4])
    func1(*l1, *l2)
    # 表示将列表l1和l2打散,把每一个元素当成位置参数,传入函数
    # (1, 2, 3, 4, 'alex', 'wusir', 4)
    # 函数的执行:*打散功能,把每一个元素当成位置参数,传入函数
    func1(1, 2, 3, 4, 'alex', 'wusir', 4)
    # (1, 2, 3, 4, 'alex', 'wusir', 4)
    
    dic1 = {'name1':'alex'}
    dic2 = {'name2':'laonanhai'}
    func1(dic1, dic2) # 不添加**表示两个位置参数,传入函数args,输出元组
    ({'name1': 'alex'}, {'name2': 'laonanhai'})
    func1(**dic1, **dic2) # 添加**表示将字典打散,当成两个关键字参数,传入函数kwargs,输出字典
    # {'name1': 'alex', 'name2': 'laonanhai'}

    5.名称空间                                                   

     5.1名称空间的由来

        Python代码运行的时候遇到函数是怎么做的,从Python解释器开始执行之后,就在内存中开辟了一个空间,每当遇到一个变量的时候,就把变量名和值之间对应的关系记录下来,但是当遇到函数定义的时候,解释器只是象征性的将函数名读入内存,表示知道这个函数存在了,至于函数内部的变量和逻辑,解释器根本不关心。

      等执行到函数调用的时候,Python解释器会再开辟一块内存来储存这个函数里面的内容,这个时候,才关注函数里面有哪些变量,而函数中的变量会储存在新开辟出来的内存中,函数中的变量只能在函数内部使用,并且会随着函数执行完毕,这块内存中的所有内容也会被清空。

        我们给这个‘存放名字与值的关系’的空间起了一个名字-------命名空间。

    #
    # python是解释型编程语言,当程序开始执行时,将代码一行一行的解释成二进制,执行。
    name = 'alex'
    # 在内存中开辟一块空间,将变量name与值'alex'的对应关系存入内存
    age = 12
    # 在内存中开辟一块空间,将变量age与值12的对应关系存入内存
    def func1():
    # 定义函数的时候,解释器在内存中开辟一块空间,象征性的将函数名存入内存,**并不关系函数内部
    name1 = 'wusir'
    # 函数中的变量只在函数内部使用
    age1 = 34
    # 函数中的变量只在函数内部使用
    func1() # 执行函数的时候,解释器会在内存中再开辟一块空间,才会关注函数内部的结构逻辑,而函数中的变量只在函数内部使用,随着函数的结束,这块内存中的内容也会被清空
    5.2名称空间的分类
    内置名称空间:中存放了python解释器为我们提供的名字:input,print,str,list,tuple...它们都是我们熟悉的,拿过来就可以用的方法。
    全局名称空间:代码在运行伊始,创建的存储“变量名与值的关系”的空间
    局部名称空间:在函数的运行中开辟的临时的空间,该空间在函数调用时生效,调用结束后失效
    加载顺序:内置名称空间------>全局名称空间----->局部名称空间
    名字的查找顺序:局部名称空间------>全局名称空间----->内置名称空间
    5.3作用域
    全局作用域:全局名称空间,内置名称空间
    局部作用域:局部名称空间
    5.4加载顺序,取值顺序
    加载顺序:内置名称空间-->全局名称空间-->局部名称空间(函数执行时)
    取值顺序:局部名称空间(函数执行时)-->全局名称空间-->内置名称空间
    # 例:取值顺序
    name1 = 'wusir'
    def func1():
        name1 = 'alex'
        print(name1)
    func1()
    # alex
    # 说明:函数先从小的作用域(局部作用域)取值,也就是函数内部取值,打印,结果就为alex
    5.5输出全局名称空间变量和局部名称空间变量内置函数globals()和locals()
    # globals() 输出全局名称空间的变量
    name1 = 'wusir'
    def func1():
        name1 = 'alex'
        print(globals())
    func1()
    # {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x000001D4349BB278>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'E:/temp.py', '__cached__': None, 'name1': 'wusir', 'func1': <function func1 at 0x000001D432B42EA0>}
    
    # locals() 输出局部名称空间的变量
    name2 = 'wusir'
    def func2():
        name2 = 'alex'
        print(locals())
    func2()
    # {'name2': 'alex'}
    
    # 在局部名称空间,可以对全局名称空间的变量进行引用,但是不能改变
    count = 1
    def func1():
        count += 1
        print(count)
    func1()
    #     count += 1
    # UnboundLocalError: local variable 'count' referenced before assignment
    
    

    5.6关键字global、nonlocal

    global

    # global 1- 在局部名称空间声明一个全局变量,只要函数运行一次,全局变量即生效
    #        2- 如果变量存在,可以更改全局变量
    # 在全局名称空间引用局部名称空间变量会报错
    def func1():
        name = 'alex'
        return
    print(name)
    #     print(name)
    # NameError: name 'name' is not defined
    
    # 添加global关键字,如果全局变量不存在,可以声明全局变量,只要函数运行一次,全局变量即生效
    def func1():
        global  name
        name = 'alex'
        return
    func1() # 必须执行一次,函数内部的结构,才能加载到内存中
    print(name)
    # alex
    
    # 不添加global,如果全局变量存在,全局变量不会改变
    name = 'wusir'
    def func1():
        # global name
        name = 'alex'
        return
    func1()
    print(name)
    # wusir
    
    # 添加global,如果全局变量存在,可以修改全局变量
    name = 'wusir'
    def func1():
        global name
        name = 'alex'
        return
    func1()
    print(name)
    # alex

    nonlocal

    # nonlocal 1- 不能修改全局变量。
    #         2- 在局部作用域中,对父级作用域(或者更外层作用域非全局作用域)的变量进行引用和修改,并且引用的哪层,从那层及以下此变量全部发生改变。
    # 例1
    name1 = 'alex'
    def func1():
        nonlocal name1
        name1 = 'wusir'
    func1()
    #    nonlocal name1
    #     ^
    # SyntaxError: no binding for nonlocal 'name1' found
    
    # 例2
    def func1():
        name1 = 'alex'
        print('1.'+name1) # 打印局部变量name1 -- alex
        def inner():
            nonlocal name1 # 修改局部变量name1 -- wusir
            name1 = 'wusir'
            print('2.'+name1) # 打印修改后的局部变量name1 -- wusir
        inner()
        print('3.'+name1) # 依然在局部名称空间中,打印修改后的局部变量name1 -- wusir
    func1()
    # 1.alex
    # 2.wusir
    # 3.wusir

    6.函数名                                                      

    # 函数名-- 有普通变量的功能和加()就执行的功能
    # 1.可以互相赋值,将函数赋值f1,变量加括号就执行
    def func1():
        print(666)
    f1 = func1
    f1()
    # 666
    
    # 2.函数名可以当成函数的参数
    def func1():
        print(666)
    def func2(argv):
        argv()
        print(777)
    func2(func1)
    # 666
    # 777
    
    # 3.可以当成容器类数据类型的参数
    def func1():
        print(666)
    def func2():
        print(777)
    def func3():
        print(888)
    l1 = [func1, func2, func3]
    for i in l1:
         i()
    # 666
    # 777
    # 888
    
    # 4.函数名可以当成函数的返回值,普通变量有的功能函数名都有
    def func1():
        print(666)
    
    def func2(argv):
        print(777)
        return argv
    
    ret = func2(func1)
    ret()
    # 777
    # 666

    7.闭包                                                        

    # 闭包 内层函数对外层函数非全局变量的引用,叫做闭包
    # 闭包的好处:如果python检测到闭包
    # 他有一个机制,你的局部作用域不会随着函数的结束而结束,爬虫的重复利用机制
    #
    # 这种形式:
    def wrapper():
        name1 = 'laonanhai'
        def inner():
            print(name1)
        inner()
    wrapper()
    # laonanhai
    
    # 这不是闭包,因为引用了全局变量,所以返回None
    name1 = 'laonanhai'
    def wrapper():
        def inner():
            print(name1)
        inner()
        print(inner.__closure__)
    wrapper()
    # None
    
    # 这是闭包 内层函数对外层函数非全局变量的引用
    def wrapper(argv):
        # argv = 'alex'
        def inner():
            print(argv)
        inner()
        print(inner.__closure__)
    name = 'alex'
    wrapper(name)
    # (<cell at 0x00000159C8B885E8: str object at 0x00000159C8B4C378>,)
    
    # 例:简单爬虫
    from urllib.request import urlopen
    def index():
        url = "http://www.cnblogs.com/gnaix"
        def get():
            return urlopen(url).read()
        return get
    
    content1 = index()()
    content2 = index()()
    print(content1)

    7.装饰器                                                     

    7.1用两种方式执行函数的内嵌函数

    # 用两种方式执行函数的内嵌函数
    def wrapper():
        def inner():
            name1 = 'alex'
            print(name1)
    wrapper()
    
    # 方法一: 在wrapper函数内执行inner函数
    def wrapper():
        def inner():
            name1 = 'alex'
            print(name1)
        inner()
    wrapper()
    
    # 方法二:利用函数的返回值,将inner函数名返回wrapper函数的调用者
    def wrapper():
        def inner():
            name1 = 'alex'
            print(name1)
        return inner
    
    ret = wrapper()
    ret()
    #
    wrapper()()
    # alex
    # alex
    
    # 错误实例:
    def wrapper():
        def inner():
            name1 = 'alex'
            print(name1)
    inner()
    # 错误提示
    #     inner()
    # NameError: name 'inner' is not defined
    # 原因分析
    # 取值问题:取值顺序,有内而外
    # 加载顺序:内置名称空间-->全局名称空间-->局部名称空间(函数执行时)
    # 取值顺序:局部名称空间(函数执行时)-->全局名称空间-->内置名称空间

    7.2装饰器的推导过程

    # 装饰器的产生,测试函数的执行效率(时间)
    # 1.代码方式检测
    import time
    def func1():
        print('晚上回去吃烧烤...')
        time.sleep(0.3)
    
    start_time = time.time()
    func1()
    end_time = time.time()
    print('函数的执行效率为 %s' % (end_time - start_time))
    # 晚上回去吃烧烤...
    # 函数的执行效率为 0.3010380268096924
    
    # 2.简单函数方式,不可以重复利用
    import time
    def func1():
        print('晚上回去吃烧烤...')
        time.sleep(0.3)
    
    def timer():
        start_time = time.time()
        func1()
        end_time = time.time()
        print('函数的执行效率为 %s' % (end_time - start_time))
    
    timer()
    # 晚上回去吃烧烤...
    # 函数的执行效率为 0.30023193359375
    
    # 3.通过传参的函数方式,可以重复利用
    # 问题:原来执行func1 -- func1(),现在执行func1 -- timer(func1)
    # 如果函数过多,需要修改大量函数执行方式,而且函数的参数会有问题
    import time
    def func1():
        print('晚上回去吃烧烤...')
        time.sleep(0.3)
    
    def func2():
        print('晚上回去喝啤酒...')
        time.sleep(0.3)
    
    def timer(f1):
        start_time = time.time()
        f1()
        end_time = time.time()
        print('函数的执行效率为 %s' % (end_time - start_time))
    
    timer(func1)
    timer(func2)
    # 晚上回去吃烧烤...
    # 函数的执行效率为 0.30021190643310547
    # 晚上回去喝啤酒...
    # 函数的执行效率为 0.30055713653564453
    
    # 4.通过传参的函数方式,可以重复利用
    # 问题:原来执行func1 -- func1(),现在执行func1 -- timer(func1)
    # 如果函数过多,需要修改大量函数执行方式,而且函数的参数会有问题
    import time
    def func1():
        print('晚上回去吃烧烤...')
        time.sleep(0.3)
    
    def func2():
        print('晚上回去喝啤酒...')
        time.sleep(0.3)
    
    def timer(f1):
        start_time = time.time()
        f1()
        end_time = time.time()
        print('函数的执行效率为 %s' % (end_time - start_time))
    
    f = func1
    func1 = timer
    func1(f)
    # 晚上回去吃烧烤...
    # 函数的执行效率为 0.30002880096435547
    
    # 5.最简单版的装饰器
    import time
    def func1():
        print('晚上回去吃烧烤...')
        time.sleep(0.3)
    
    def func2():
        print('晚上回去喝啤酒...')
        time.sleep(0.3)
    
    def timer(f1):
        def inner():
            start_time = time.time()
            f1()
            end_time = time.time()
            print('函数的执行效率为 %s' % (end_time - start_time))
        return inner
    
    # 关系转换
    func1 = timer(func1) # inner
    func1() # inner(),这个func1是一个新的变量
    # 晚上回去吃烧烤...
    # 函数的执行效率为 0.3011753559112549
    # 遇到等号,先执行等号右面的,右面等于执行timer(func1)这里的func1仅代表函数名
    # func1 == inner
    # Python代码运行的时候遇到函数是怎么做的,从Python解释器开始执行之后,就在内存中开辟了一个空间,每当遇到一个变量的时候,就把变量名和值之间对应的关系记录下来, 但是当遇到函数定义的时候,解释器只是象征性的将函数名读入内存,表示知道这个函数存在了,至于函数内部的变量和逻辑,解释器根本不关心。
    # 等执行到函数调用的时候,Python解释器会再开辟一块内存来储存这个函数里面的内容,这个时候,才关注函数里面有哪些变量,而函数中的变量会储存在新开辟出来的内存中, 函数中的变量只能在函数内部使用,并且会随着函数执行完毕,这块内存中的所有内容也会被清空。
    
    # 6.语法糖
    # 装饰器,在不改变原函数即原函数的调用的情况下,为原函数
    # 增加一些额外的功能,打印日志,执行时间,登录认证等等。
    import time
    def timer(f1):
        def inner():
            start_time = time.time()
            f1()
            end_time = time.time()
            print('函数的执行效率为 %s' % (end_time - start_time))
        return inner
    
    @timer # 关系转换,func1 = timer(func1) # inner
    def func1():
        print('晚上回去吃烧烤...')
        time.sleep(0.3)
    func1()
    # 晚上回去吃烧烤...
    # 函数的执行效率为 0.30006885528564453
    
    # 7.被装饰函数带参数
    import time
    def timer(f1): # f = func1
        def inner(*args, **kwargs):
            start_time = time.time()
            f1(*args, **kwargs)
            end_time = time.time()
            print('函数的执行效率为 %s' % (end_time - start_time))
        return inner
    
    @timer # 关系转换,func1 = timer(func1) # inner
    # func1 = timer(func1)
    # 遇到等号先执行等号右面的,timer(func1)此时func1是以函数名
    # 的形式传入timer的参数,返回给timer的调用者inner函数名
    # 所以func1 ==> inner
    
    def func1(a, b):
        print(a, b)
        print('晚上回去吃烧烤...')
        time.sleep(0.3)
    func1(111, 222) # ==>timer(func1)(111, 222)
    # 111 222
    # 晚上回去吃烧烤...
    # 函数的执行效率为 0.3001117706298828
    
    # 8.带返回值的装饰器
    import time
    def timer(f1):
        def inner(*args, **kwargs):
            start_time = time.time()
            ret = f1(*args, **kwargs) # func1()的调用者
            end_time = time.time()
            print('函数的执行效率为 %s' % (end_time - start_time))
            return ret
        return inner
    @timer # func1 = timer(func1) inner
    def func1(a, b):
        print(a, b)
        print('晚上回去吃烧烤...')
        time.sleep(0.3)
        return 666
    
    print(func1(111, 222))
    # 111 222
    # 晚上回去吃烧烤...
    # 函数的执行效率为 0.3004331588745117
    # 666
    
    # 9.最终版本装饰器
    # 简单版
    def wrapper(f1):
        def inner():
            f1()
        return inner
    
    # 带参数,返回值版
    def wrapper(f1): #f1被装饰函数的函数名
        def inner(*args, **kwargs):
            '''调用函数之前的操作'''
            ret = f1(*args, **kwargs)
            '''调用函数之后的操作'''
            return ret
        return inner
    一鼓作气,再而衰,三而竭。
  • 相关阅读:
    box-sizing
    js词法作用域
    焦点轮播图
    绑定事件统一方法
    自动展示收起广告功能
    使用js实现瀑布流
    回到顶部效果
    电商网站的放大镜功能
    CSS清除浮动
    CSS的水平居中和垂直居中方式
  • 原文地址:https://www.cnblogs.com/gongniue/p/8855993.html
Copyright © 2011-2022 走看看