初始函数

什么是函数

函数就相当于具备某一功能的工具

使用函数必须遵循一些规则:

  • 先定义
  • 后调用

为何要使用函数

  1. 组织结构不清晰,可读性差
  2. 代码冗余,臃肿
  3. 因为代码冗余导致可维护性,扩展性差

函数的定义

函数是一个工具,函数名应该定义为动词,而不是名词。

def function_name(parameters):
    """函数文档字符串"""
    # 函数体
    # 可以包含多条语句
    return expression
  1. def 是定义函数使用的关键字

  2. function_name是函数名,符合变量命令规范即可。

    • 函数的命名应该反应出函数的功能

    • 函数名指向函数内存地址,是对函数体代码的引用

    • 函数名加上一个小括号就实现了这个函数的功能

  3. ( )括号内放函数需要输入的参数,即使函数不需要输入任何参数,也不能省略( )

  4. parameters是函数的参数,确切的说是形参,即形式参数,如果函数不需要形参,可以不传参

  5. : 括号后面要加上冒号,然后下一行开始缩进编写函数体的代码

  6. """函数文档字符串"""这部分是函数的注释部分,描述函数的功能和参数的意义

  7. 函数体代码块:函数体代码块是完成函数的功能部分

  8. return后面跟函数的返回值,可以没有,也可以1个或多个

    • 没有返回值,默认返回None

定义函数发生的事情

  1. 申请内存空间保存函数体代码
  2. 将上述内存地址绑定给函数名
  3. 定义函数不会执行函数体代码,但是会检测函数体的语法

调用函数发生的事情

  1. 通过函数名找到函数的内存地址
  2. 然后加括号就是在触发函数体代码的执行

形式1:无参函数

def func():
    print('哈哈哈')
 
func()

# 哈哈哈

示范1

def bar():  # bar=函数的内存地址,所以可以直接访问到
    print("from bar")


def foo():
    print(bar)  # 打印bar()函数的内存地址
    bar()  # 内存地址加括号可以执行函数体代码
    print("from foo")


foo()
<function bar at 0x0000018F1054F0A0>
from bar
from foo

示范2

def foo():
    print(bar)  # 打印bar()函数的内存地址
    bar()  # 内存地址加括号可以执行函数体代码
    print("from foo")


def bar():  # bar=函数的内存地址,所以可以直接访问到
    print("from bar")


foo()
<function bar at 0x000001B62C1D4670>
from bar
from foo

示范3

def foo():
    # 因为bar在
    print(bar)  # 打印bar()函数的内存地址
    bar()  # 内存地址加括号可以执行函数体代码
    print("from foo")


foo()


def bar():
    print("from bar")

# 结果报错:NameError: name 'bar' is not defined

在给出的代码中,调用 bar() 的语句位于 foo() 函数的定义之前,这会导致 NameError 异常。

在 Python 中,函数必须在调用它们之前被定义。因此,当 foo() 函数在定义的时候尝试调用 bar() 函数时,由于 bar() 函数还没有被定义,Python 会引发 NameError 异常。

要解决这个问题,您可以将 bar() 函数的定义移动到 foo() 函数的上方,或者将 bar() 函数的定义放在整个代码块的最上方。

以下是修改后的代码示例:

def bar():
    print("from bar")

def foo():
    print(bar)  # 打印bar()函数的内存地址
    bar()  # 内存地址加括号可以执行函数体代码
    print("from foo")

foo()

这样修改后,首先定义了 bar() 函数,然后定义了 foo() 函数,并在其中调用了 bar() 函数。现在,代码将按预期运行,输出如下:

<function bar at 0x00000123ABCD>
from bar
from foo

请注意,函数的定义顺序对代码的执行顺序非常重要。确保在调用函数之前先定义它们。

形式2:有参函数

def func(x, y):  # x=1 y=2
    print(x, y)


func(1, 2)  
# 1 2

形式3:空函数,函数体代码为pass或者三个点...

一般都是使用pass,遵循python原则

def func(x, y):
    pass


func(1, 2)
# 无结果输出
def func(x, y):
    ...


func(1, 2)
# 无结果输出

三种定义方式各用在何处

判断函数是否被写死了,就是看需不需要外部传参

1. 无参函数的应用场景

# 无参函数,假如用到30个地方
def interactive():
    name = input("输入姓名:")
    age = input("输入年龄:")
    gender = input("输入性别:")
    
    msg = f"姓名:{name} 年龄:{age} 性别:{gender}."
    print(msg)


interactive()
interactive()
输入姓名:小满
输入年龄:3
输入性别:女
姓名:小满 年龄:3 性别:女.
输入姓名:大乔
输入年龄:4
输入性别:女
姓名:大乔 年龄:4 性别:女.

2. 有参函数的应用场景

如果把函数想象成工厂,形参相当于函数的材料,返回值相当于产品。

def add(x, y):  # 参数--> 原材料
    result = x + y
    return result  # 产品


res = add(10, 2)
print(res)  # 12

3. 空函数的应用场景

主要用于构思程序的时候,可以适量写提示语或者文档描述

def auth_user():
    pass


def download_file():
    pass


def upload_file():
    pass

函数的调用

1. 语句的形式:只加括号调用函数

interactive()
add(1, 2)

2. 表达式形式

def add(x, y):  # 参数 --> 原材料
    result = x + y
    return result


# 赋值表达式
r1 = add(1, 2)
print(r1)  # 3

# 数学表达式
res = add(10, 2) * 10
print(res)  # 120

3. 函数调用可以当做参数

def add(x, y):  # 参数 --> 原材料
    result = x + y
    return result  # 产品


# 函数调用可以当做参数
res = add(add(1, 2), 10)  # 第一次add(1, 2)返回3  然后add(3, 10)结果13
print(res)  # 13

4. 间接调用

def func(x, y):
    print(x + y)
    
add = func
add(2, 3)

函数对象

精髓:可以把函数当成变量去用
func = 内存地址

def func():
	print('from func')

可以被赋值

def func():
    print('from func')


f = func   # 指向了同一个内存地址
print(f)
print(func)
f()

# 结果如下
<function func at 0x0000021E45D2D700> 
<function func at 0x0000021E45D2D700>
from func

可以当做函数的参数传给另外一个函数

def func():
    print('from func')


def foo(x):  # x = func的内存地址
    print(x)


foo(func)

>>> <function func at 0x00000182DF17D700>
def func():
    print('from func')


def foo(x):  # x = func的内存地址
    x()


foo(func)

>>> from func

可以当做另外一个函数的返回值

def func():
    print('from func')


def foo(x):  # x = func的内存地址
    return x


res = foo(func)
res()

>>> from func

可以当做容器类型的一共元素
列表, 字典, 元组等....

def func():
    print('from func')

l = [func]
print(l)
l[0]()

# 结果如下
[<function func at 0x0000028ED007D700>]
from func
def func():
    print('from func')


dic = {'k1': func}
print(dic)
dic['k1']()

# 结果如下
{'k1': <function func at 0x000001CAA467D700>}
from func

函数的返回值

1. 什么时候需要返回值

看用户需求

理想状态是返回一个布尔类型的标志位和一个表达式

2. 创建返回值

  1. return是函数结束的标志,即函数体代码一旦运行到return会立刻终止函数的运行,并且会将return后的值当做本次运行的结果返回。
    • 即便写了很多死循环,只要遇到return立马终止函数的运行
  2. 返回值None的几种情况:
    1. 函数体内没有return
    2. return后没有任何代码(即单独一个return)
def func():
    while True:
        while True:
            while True:
                while True:
                    while True:
                        print("就打印了一次就结束了")
                        # return 之后什么都不写
                        return
                    

res = func()
print(res)
就打印了一次就结束了
None

返回一个值

return 值

def func():
    result = 10
    return result


res = func()
print(res)  # 10

返回多个值

用逗号分割开多个值,会被return返回成元组

def func():
    return 10, "小满", True, [3, 4], {1: 1}


res = func()
print(res, type(res))
# (10, '小满', True, [3, 4], {1: 1}) <class 'tuple'>

函数作为参数

def plus(num):
    return num + 10


def handler(func):
    res = func(100)
    msg = "执行func,并获取到的结果为:{}".format(res)
    print(msg)  # 执行func,并获取到的结果为:110


# 执行handler函数,将Plus作为参数传递给handler的形式参数func
handler(plus)

代码解读

这段代码定义了两个函数plus(num)handler(func),并进行了函数调用。下面是对代码的解读:

  1. 定义了一个函数plus(num),它接受一个参数num,并返回num加上10的结果。

  2. 定义了一个函数handler(func),它接受一个函数作为参数func。在函数体内,它使用传入的函数func,并将参数100传递给func进行调用,将调用结果赋值给变量res。然后,使用字符串格式化的方式构建了一条消息msg,其中包含了func函数的执行结果。最后,通过打印输出了这条消息。

  3. 调用handler(plus),将plus函数作为参数传递给handler函数。在handler函数内部,它将接收到的plus函数进行调用,并将参数100传递给plus函数。plus(100)的结果为110,赋值给res。然后,构建一条消息msg,其中包含了110,并打印输出这条消息。

总结:这段代码展示了函数作为参数的概念。通过将函数作为参数传递给另一个函数,可以在接收函数的函数内部对其进行调用。在这个例子中,handler函数接收到plus函数作为参数,然后调用plus函数并获取执行结果,最后打印输出结果。在最后一行代码中,调用handler(plus)实际上是将plus函数传递给handler函数进行处理。


函数作为返回值

def plus(num):
    return num + 100

def handler():
    print("执行handler函数")
    return plus

result = handler()  # result = plus
data = result(20)  # 函数加括号 执行Plus(20)
print(data)  # data 相当于返回值

代码解读

这段代码定义了两个函数plus(num)handler(),并进行了一系列函数调用和赋值操作。下面是对代码的解读:

  1. 定义了一个函数plus(num),它接受一个参数num,并返回num加上100的结果。

  2. 定义了一个函数handler(),它在执行时打印一条消息"执行handler函数",然后返回plus函数本身(没有调用plus函数,只是返回了函数对象)。

  3. 执行handler()函数,并将返回的结果赋值给变量result。由于handler函数返回的是plus函数本身,所以result也指向了plus函数。

  4. 调用result函数,传入参数20,并将结果赋值给变量data。由于result指向了plus函数,所以这里实际上是调用了plus(20),将20作为参数传递给plus函数,得到结果120,并将结果赋值给data

  5. 打印变量data,输出结果为120

总结:这段代码展示了函数的定义、函数的返回值可以是函数本身,函数的调用和结果的赋值等基本概念。通过handler函数返回plus函数本身,可以将函数作为对象进行传递和调用。最终,代码输出了plus(20)的结果120

函数的参数

形参

在定义函数阶段的参数称之为形式参数,简称形参

相当于变量名

实参

在调用阶段传入的值成为实际参数,简称实参

相当于变量值

形参与实参的关系

  1. 在调用阶段,实参(变量值)会绑定给形参(变量名)
  2. 这种绑定关系只能在函数体内使用
  3. 实参与形参的绑定关系在调用时生效,函数调用结束后解出绑定关系
def func(x, y):  # x y 形参
    print(x, y)
    

func(1, 2)  # 1 2实参,结果也是1 2

实参是传入的值,但是可以是以下形式

形式1

func(1, 2)

形式2

a = 1
b = 2
func(a, b)

形式3

func(int("1"), 2)
func(func(1, 2), func(2, 3), 333)

形参与实参的具体使用

位置参数

按照从左到右的顺序依次定义的参数称之为位置参数

位置形参

在函数定义阶段,按照从左到右的顺序直接定义的“变量名”

特点:必须被传值,多一个不行少一个也不行

def func(x, y):
    print(x, y)


func(1, 2)  # 1 2

位置不够,报错的一般是miss...

def func(x, y):
    print(x, y)


func(1)  # 报错TypeError: func() missing 1 required positional argument: 'y'

参数多了,报错的一般是but ... given

def func(x, y):
    print(x, y)


func(1, 2, 3)  # 报错TypeError: func() takes 2 positional arguments but 3 were given

位置实参

在函数调用阶段,按照从左到右的顺序依次传入的值

特点:按照顺序与形参一一对应

弊端:只能按照参数的位置传入指定的参数,一旦位置调换就会产生错乱

def func(x, y):
    print(x, y)


func(1, 2)  # 1 2

关键字实参

在调用函数阶段,按照key=value的形式传入的值

特点:指名道姓的给某个形参传值,可以完全不参考顺序

def func(x, y):
    print(x, y)


func(y=2, x=1)  # 1 2

混合使用(强调)

  1. 位置参数必须放在关键之参数之前

    一般报错 follows ...

    def func(x, y):
        print(x, y)
    
    
    func(1, y=2)  # 1 2
    func(y=2, 1) # 报错 语法错误 SyntaxError: positional argument follows keyword argument
    
  2. 不能为同一形参重复传值

    一般报错 multiple ...

    def func(x, y):
        print(x, y)
    
    
    func(1, y=2, x=3)  # 报错
    func(1, 2, x=3, y=4)  # 报错
    # TypeError: func() got multiple values for argument 'x'
    

默认形参

在定义函数阶段,就已经被赋值的形参,称之为默认形参

特点:在定义阶段就已经被赋值,意味着在调用阶段可以不用为其赋值

如果默认参数不想填写,可以写一个None

def func(x, y=3):
    print(x, y)


func(x=1)  # 1 3
func(5)  # 5 3 
func(x=1, y=444)  # 1 444

什么情况下使用位置参数和默认形参

看情况,实例分析注册功能

def register(name, age, gender="男"):
    print(name, age, gender)


register("老夫子", 18)
register("夏侯淳", 19)
register("阿珂", 16, "女")
register("小满", 3, "女")
老夫子 18 男
夏侯淳 19 男
阿珂 16 女
小满 3 女

位置形参与默认形参混用(强调)

  1. 位置形参必须在默认形参的左边

    def func(y=2, x):  # 报错 语法错误 SyntaxError: non-default argument follows default argument
        pass
    

    正确演示

    def func(x, y=2): 
        pass
    
  2. 默认参数的值是在函数定义阶段被赋值的,准确的说被赋值的是内存地址

    示范1

    m = 2
    
    
    def func(x, y=m):  # y --> 2的内存地址
        print(x, y)  # 1 2
    
    
    func(1)
    

    示范2

    m = 2
    
    
    def func(x, y=m):  # y --> 2的内存地址
        print(x, y)  # 1 2
    
    
    m = 333333333
    func(1)
    

    示范3

    m = [11111]
    
    
    def func(x, y=m):  # y --> [11111]的内存地址
        print(x, y)  # 1 [11111, 3333333333]
    
    
    m.append(3333333333)
    func(1)
    

虽然默认值可以被指定为任意数据类型,但是不推荐使用可变类型

解决可变类型形参

如果有默认参数为可变类型的需求,下面是推荐的写法,将形参默认值设置为None内部进行判断,增加可变类型

def func(x, y, z, ls=None):
    if ls is None:
        ls = []

    ls.append(x)
    ls.append(y)
    ls.append(z)

    print(ls)


new_ls = [44, 55, 66]
func(1, 2, 3)
func(77, 88, 99, new_ls)
[1, 2, 3]
[44, 55, 66, 77, 88, 99]

函数最理想的状态

函数的调用只跟函数本身有关系,不受外界代码的影响

可变长度参数(*与**的用法)

可变长度指的是在调用函数时,传入的值(实参)的个数不固定,而实参是用来为形参赋值的,所以对应着,针对溢出的实参必须有对应的形参来接收。

可变长度的位置参数

*形参名:用来接收溢出的位置参数,溢出的位置实参会被*保存成元组的格式,然后赋值给紧跟其后的形参名

*后面跟的可以是任意名字,但是约定俗成应该是args

def func(x, y, *args):  # (3, 4, 5, 6)
    print(x, y, args)
    print(type(args))


func(1, 2, 3, 4, 5, 6)
1 2 (3, 4, 5, 6)
<class 'tuple'>
def my_sum(*args):
    count = 0
    for index in args:
        count += index
    return count


res = my_sum(1, 24, 4, 5, 7)
print(res)  # 41

可变长度的关键字参数

**形参名:用来接收溢出的关键字实参,**会将溢出的关键字实参保存成字典格式,然后赋值给紧跟其后的形参名,**后跟的可以是任意名字,但是约定俗成应该是kwargs

def func(x, y, **kwargs):
    print(x, y, kwargs)
    print(type(kwargs))
    

func(1, y=2, a=1, b=2, c=3)
1 2 {'a': 1, 'b': 2, 'c': 3}
<class 'dict'>

*可以用在实参中,实参中带*,先*后被打散成位置实参

需要注意的是,数量需要一一对应,实参*后只要是可以被for循环的类型都行(list str tuple ...)

def func(x, y, z):
    print(x, y, z)


func(*[11, 22, 33]) 
ls = [44, 55, 66]
func(*ls)
11 22 33
44 55 66
def func(x, y, z):
    print(x, y, z)  # x y z


func(*{"x": 1, "y": 2, "z": 3})

形参与实参都带*

def func(x, y, *args):
    print(x, y, args)  # 1 2 ([3, 4, 5, 6],)


func(1, 2, [3, 4, 5, 6])  
def func(x, y, *args):
    print(x, y, args)  # 1 2 (3, 4, 5, 6)


func(1, 2, *[3, 4, 5, 6])  
def func(x, y, *args):
    print(x, y, args)  # h e ('l', 'l', 'o')


func(*"hello")

**可以用在实参中(**后面跟的只能是字典),实参中带**,先**后的值打散成关键字实参

def func(x, y, z):
    print(x, y, z)  # 1 2 3


func(**{"x": 1, "y": 2, "z": 3})  # 理解为 func(x=1, y=2, c=3)

形参与实参都带*

def func(x, y, **kwargs):
    print(x, y, kwargs)  # 111 222 {'a': 333, 'z': 444}


func(**{"y": 222, "x": 111, "a": 333, "z": 444})  # func(y=222, x=111, a=333, z=444)

混用***

*args必须在**kwargs之前

def func(**kwargs, *args):
    pass

# 结果报错 语法错误
def func(*args, **kwargs):
    print(args, kwargs)  # (1, 2, 3, 4, 5) {'a': 6, 'b': 7, 'c': 9}


func(1, 2, 3, 4, 5, a=6, b=7, c=9)

拓展(掌握好这个对语法糖非常有帮助)

def index(x, y, z):
    print("index======>", x, y, z)  # index======> 1 2 3


def wrapper(a, b, c):
    index(a, b, c)


wrapper(1, 2, 3)

关键字命名参数(了解)

命名关键字参数:

在定义函数时,*后自定义的参数,如下所示,称之为命名关键字参数

特点

命名关键字实参必须按照key=value的形式为其传值

不传参不行,位置参数也不行

def func(x, y, *, a, b):  # # 其中a和b成为命名关键字参数
    print(x, y)  # 1 2
    print(a, b)  # 222 111


func(1, 2, a=222, b=111)

*后面的key=value并不是默认参数

# 不会报错
def func(x, y, *, b=222, a):  # 次处的b就不叫默认参数了,可以理解为为关键字参数b赋了一个默认值
    print(x, y)  # 1 2
    print(a, b)  # 222 222


func(1, 2, a=222)

组合使用(了解)

基本上没有应用场景,仅做了解.

形参混用的顺序:位置形参,默认形参,*args, 命名关键字形参,**kwargs

def func(a, b, c, *args, x=500, y, **kwargs):
    print('a=', a)
    print('b=', b)
    print('c=', c)
    print('x=', x)
    print('y=', y)
    print('args=', args)
    print('kwargs=', kwargs)


func(20, 50, '纯二', '哈利波特', '会魔法', y=9527, music='天空之城', movie='哈尔的移动城堡', author='宫崎骏')
a= 20
b= 50
c= 纯二
x= 500
y= 9527
args= ('哈利波特', '会魔法')
kwargs= {'music': '天空之城', 'movie': '哈尔的移动城堡', 'author': '宫崎骏'}

实参混用的顺序:
遵循位置参数在关键字参数左边就行,包括***的参数

def func(x, y, z, a, b, c):
    print('x=', x)
    print('y=', y)
    print('z=', z)
    print('a=', a)
    print('b=', b)
    print('c=', c)


func(111, 333, 444, a=222, **{'b':555, 'c':666})
# func(111, 333, 444, a=222, b=555, c=666)
x= 111
y= 333
z= 444
a= 222
b= 555
c= 666
def func3(a1, a2, a3, a4=10, *args, a5=20, **kwargs):
    print(a1, a2, a3, a4, a5, args, kwargs)


func3(11, 22, 33, 44, 55, 66, 77, a5=10, a10=123)
11 22 33 44 10 (55, 66, 77) {'a10': 123}

案例演示

不好的演示(x)

太多的elif判断,而且不方便功能的增删(函数)这样写代码大部分会被公司炒掉。不推荐这样写...

def login():
    print('登录功能')

def transfer():
    print('转账功能')

def check_banlance():
    print('查询余额')

def withdraw():
    print('提现功能')

def register():
    print('注册功能')

while True:
    print("""
    0 退出
    1 登录
    2 转账
    3 查询余额
    4 提现
    5 注册
    """)

    choice = input('请选择功能:')
    if not choice.isdigit():
        print('必须输入编号,傻叉')
        continue

    if choice == '0':
        break

    if choice == '1':
        login()
    elif choice == '2':
        transfer()
    elif choice == '3':
        check_banlance()
    elif choice == '4':
        withdraw()
    elif choice == '5':
        register()
    else:
        print('输入的指令不存在')

优化后(√)

优化后通过字典的值作为函数的名字, 下面的判断如果选择在字典中,直接取到字典的值(函数)加括号就是执行具体函数的内部代码了.

def login():
    print('登录功能')

def transfer():
    print('转账功能')

def check_banlance():
    print('查询余额')

def withdraw():
    print('提现功能')

def register():
    print('注册功能')

dic = {
    '1': login,
    '2': transfer,
    '3': check_banlance,
    '4': withdraw,
    '5': register
}

while True:
    print("""
    0 退出
    1 登录
    2 转账
    3 查询余额
    4 提现
    5 注册
    """)

    choice = input('请选择功能:')
    if not choice.isdigit():
        print('必须输入编号,傻叉')
        continue

    if choice == '0':
        break

    if choice in dic:
        dic[choice]()
    else:
        print('输入的指令不存在')

进一步优化(get√)

优化后只需要修改字典就行, 因为字典里面键的元素运行后不需要被修改,所以本着严谨的态度,这里推荐使用元组而不是列表.

def login():
    print('登录功能')

def transfer():
    print('转账功能')

def check_banlance():
    print('查询余额')

def withdraw():
    print('提现功能')

def register():
    print('注册功能')

dic = {
    '0': ('退出', None),
    '1': ('登录', login),
    '2': ('转账', transfer),
    '3': ('查询余额', check_banlance),
    '4': ('提现', withdraw),
    '5': ('注册', register)
}

while True:
    for k, v in dic.items():
        print(k, v[0])

    choice = input('请选择功能:')
    if not choice.isdigit():
        print('必须输入编号,傻叉')
        continue

    if choice == '0':
        break

    if choice in dic:
        dic[choice][1]()
    else:
        print('输入的指令不存在')

函数嵌套

  1. 函数的嵌套调用:在调用了一共函数的过程中又调用了其他的函数
    实例:比较大小
def max2(x, y):
    if x > y:
        return x
    else:
        return y

def max(a, b, c, d):
    # 第一步:比较a, b得到res1
    res1 = max2(a, b)
    # 第二步:比较res1, c得到res2
    res2 = max2(res1, c)
    # 第三步:比较res2, d得到res3
    res3 = max2(res2, d)
    return res3

res = max(1, 2 ,3 ,4)
print(res)

>>> 4

函数的嵌套定义:在函数内定义其他函数
实例:求圆形的面积和周长

def cirale(radius, action=0):
    from math import pi

    # 求圆形的周长 2*pi*radius
    def perimiter(radius):
        return 2*pi*radius

    # 求圆形的面积 pi*(radius**2)
    def area(radius):
        return pi*(radius**2)

    if action == 0:
        return perimiter(radius)
    elif action == 1:
        return area(radius)


res = cirale(33, action=0)
print(res)

>>> 207.34511513692635

总结:

  1. 建议按关键字传参
  2. 返回值一般建议返回一个标志位布尔值如果有表达式可以返回一个标志位和表达式
  3. 函数的理想状态
    • 函数的调用只跟函数本身有关系,不受外界代码的影响
  4. *args只能按照位置传参
  5. **kwargs只能按照关键字传参
  6. ***混用时,**只能放在*的后面
  7. 参数和动态参数混合时,动态参数只能放在最后
  8. 命名关键字参数*后面的参数只能通过关键字传参
  9. 写函数功能的时候,尽量不要让代码超过一屏(屏幕)不然很low,就是不要有太多冗余的代码。
posted @ 2023-12-06 18:17  小满三岁啦  阅读(18)  评论(0编辑  收藏  举报