6. 函数方法
🤖 作者:包瑞清(richie bao): lastmod: 2024-11-08T16:11:12+08:00
6.1 基本语法规则
6.1.1 函数/方法的定义与调用
函数(方法)是用于执行特定任务或计算的代码块,其基本组成包括标识函数的函数名;传入数据用于执行任务的参数列表;执行任务的函数体;和函数执行完后返回的结果,即返回值(如果有)。通过函数(方法)定义,使代码模块化,即将代码分解成小块,每块处理一个独立的任务,便于理解和维护;通过调用已定义的函数避免重复编写相同的代码,实现代码重用;也可以通过函数隐藏复杂的实现,用户只需要关注函数的接口,而不必关心函数(方法)的内部实现。表为 Python 和 C 系列语言函数定义的基本语法。
Py | C/C++,C# |
---|---|
Python 函数定义使用 |
在 C/C++ 和 C# 中的函数定义整体结构相似,通过指定返回值类型、函数名和参数列表定义函数。但需要注意,C# 中的函数为定义在类内部的方法,因此通常在 C# 中将函数称为方法,通过实例化对象来调用实例方法,或直接调用静态方法。调用函数(方法)时,使用函数(方法)名并传入参数;如果无传入参数,则为空。 |
|
|
6.1.2 函数/方法的用法
用法 | Py | C | C++ | C# | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
无参数传入 函数定义与调用 定义的函数如果无参数传入,则圆括号内的参数列表可以为空。对于 C/C++ 也可以传入 |
|
|
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
有参数传入 函数定义与调用 Python 为动态类型语言,与定义变量时一样,不需要指定函数传入参数的数据类型和函数返回值的类型。而 C/C++ 和 C# 为静态类型语言,要在函数声明和定义中明确传入参数的类型和函数(方法)返回值的类型。 Python 引入了类型提示(Type Hinting)附1,允许在函数定义时为参数和返回值添加类型注释,以提高代码的可读性和维护性。类型提示为在变量后使用冒号 |
|
|
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
参数类型和传递方式 Python、C、C++ 和 C# 中,函数(方法)的参数传递方式有所不同。其参数传递方式的对比如表。
说明:
|
函数的参数匹配包括两个位置,一是定义函数时传入的参数;二是调用函数时传入的参数。常规模式为位置参数,按照顺序从左到右对应参数;调用时可以给定关键字参数,不受位置参数顺序的影响,但是需要将关键字参数放置于位置参数之后。如果是在定义函数时,给定关键字参数,则为该参数指定默认值,即默认参数。当调用时,不传递该参数值,则以提供的默认值替代;收集参数(Varargs collecting)包括只有一个星号 不同的匹配语法可以根据需要自由组合。但排序通常为,一般模式在前,再跟元组收集,再跟字典收集。如果位置不对,会引发异常提示,可以根据提示修改位置,直至满足要求。
在 Python 中,函数的参数传递分为可变(mutable)和不可变(immutable)数据类型两种。不可变类型包括:
|
C 语言函数参数处理需要显示管理内存和类型,通过指针和
默认情况下,C 语言的函数参数是按值传递。函数接收的是实参的副本,而非原始变量本身。但可以通过指针传递参数,实现间接修改变量值。而对于需要传递但不修改的数组或指针,可以使用
|
C++ 在 C 语言的基础上支持现代编程的多种需求,更加灵活和丰富,是的函数的参数使用方式更强大,并保持了对性能的高要求。
在 C++ 中,函数参数传递的方法主要包括按值传递、按引用传递、按指针传递和常量引用传递。当不需要修改参数,并其数据量较小时,通常用按值传递;如需修改原始值或避免复制大型数据对象时,考虑使用按引用传递;需要动态分配内存或处理数组时,一般用按指针传递;当只访问大型数据对象时,可以用按常量引用传递。
|
C# 中的函数参数支持多种传递方式和特性。默认情况下,参数按值传递,传递的是参数的副本;通过
🤖 泛型函数
由 C# 语言定义了一个泛型方法 1. 方法声明
2. 方法体
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
返回值 Python 是动态类型语言,函数的返回值类型在定义函数时无需明确指定;C/C++ 和 C# 是静态类型语言,函数的返回值类型在定义函数时需要明确指定。表为几种语言的返回值类型支持和返回多个值时的方式。
|
|
|
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
函数声明 Python 中不需要提前声明函数,其函数在代码中首次定义时自动声明。解释器在运行时按顺序执行代码,即代码的执行顺序就是定义和调用的顺序,因此只要在调用前定义了该函数,Python 解释器就可以识别并调用该函数。 C/C++ 语言中,函数声明(function declaration,function prototype)用于告诉编译器函数的名称,返回类型和参数类型(函数的签名),以便在代码中调用函数时,编译器可以识别并验证函数的正确性。函数声明通常位于文件的顶部、头文件中,或者在调用函数之前,其语法格式为 C# 编译器会自动识别代码中的方法和成员,只要方法存在于同一个类或命名空间中,编译器便可识别,不用在调用之前先进行声明。 |
Python 不用函数声明。 |
下述示例调换了函数
|
下述示例调换了函数
|
C# 不用函数声明。 |
6.2 特殊的函数/方法
用法 | Py | C | C++ | C# | ||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
匿名函数(Lambda) Lambda 表达式(函数/方法)是表示匿名函数(不需要命名的函数)的一种简洁方式。不同编程语言中的 lambda 语法和功能有所不同。Python 的 lambda 表达式最简洁、灵活,适合快速定义函数; C 语言本身没有原生的 lambda 表达式,只能通过函数指针或
|
Python 的匿名函数由
|
C 语言不支持匿名函数,但可以借助函数指针来模拟匿名函数行为。
🤖
1. 函数声明
2. 函数体
3. 返回值 返回值用于 |
C++11 引入了 lambda 表达式(匿名函数),其基本语法为
🤖 解释
1. 声明并初始化
2. 调用
3. lambda 函数
🤖 解释
|
C# 中的匿名函数可以通过委托
🤖 解释
1.
2. = delegate(int x,int y)
3. 方法体: {return x + y}
🤖 解释 lambda 表达式处理事件的订阅和触发
1. 创建
2. 订阅事件并使用 lambda 表达式
3. 触发事件
|
||||||||||||||||||||||||||||||
委托(Delegate)和回调函数 委托(Delegate)是编程语言中的一种类型,允许将方法或函数作为参数传递给其他函数,或者使得函数可以在运行时被动态调用。不同语言对委托的实现有不同的概念和实现方式,Python 使用函数对象、回调函数或闭包来模拟委托; C 使用函数指针来模拟委托;C++ 可以使用函数指针、 |
在 Python 中,没有明确的“委托”类型,但可以使用函数对象、回调函数或闭包模拟委托行为。
在 Python 中,函数本身是对象,可以直接传递给其他函数,类似于委托。
在 Python 中,闭包(Closure)是指一个函数对象(通常是嵌套函数)能够“记住”并访问其外部函数的局部变量,即使外部函数已经返回。闭包的本质是函数和它的外部作用域(nonlocal 或 global)变量的绑定关系。闭包包括外部函数(封装一个局部变量);内部函数(引用了外部函数的局部变量);外部函数返回内部函数:这使得内部函数在外部函数执行完毕后依然访问外部函数的局部变量。
闭包能够创建带有状态的函数,可以实现数据隐藏和封装;维护一个函数的内部状态,而不暴露该状态。例如用闭包实现一个计时器的程序。
|
C 中没有内置的委托类型,但可以通过函数指针模拟委托。函数指针允许将函数传递给其他函数,或者动态地调用不同地函数。下列中将函数地地址(函数指针)传递给
|
C++ 中,委托通常通过函数指针、Lambda表达式或
使用 Lambda 表达式
使用成员函数指针
存储和调用回调函数
存储多个回调函数
|
C# 的委托(Delegate)是一个类型安全的函数指针,允许方法作为参数传递,或者允许在运行时动态调用方法。委托可以用来实现事件处理、回调、及灵活的代码设计。委托是一个类型,定义了方法签名(即方法的返回类型和参数类型),例如
多播委托可以指向多个方法。多个方法会按顺序执行,并且返回值会被忽略,除非最后一个方法返回一个值。
委托常用于事件处理,指定事件处理方法的签名。
|
||||||||||||||||||||||||||||||
内联函数 内联函数(inline function)是一种优化机制。Python 通过解释执行不进行内联;C/C++ 提供了 |
不支持。 |
|
|
|
||||||||||||||||||||||||||||||
函数/方法重载 函数/方法重载(Function Overloading)是指在同一作用域内,可以定义多个同名但参数列表不同的函数,编译器会根据函数调用时传递的参数类型和数量来决定调用哪个版本的函数。Python 和 C 不支持函数重载。C++ 和 C# 支持函数/方法重载,会根据参数的类型、数量和顺序来选择正确的函数。 |
不支持。 |
不支持。 |
|
|
||||||||||||||||||||||||||||||
递归函数 递归是指函数/方法调用自身的一种编程技术。在递归中,问题被分解成更小的相同问题,直到遇到一个基准条件(终止条件),终止递归。因此递归函数通常包含两部分,一是基准条件,即避免无限递归的条件;二是递归调用,将问题简化成更小的子问题。阶乘(Factorial)即为一个经典的递归问题,定义为
在这个过程中, |
|
|
|
|
6.3 Python 的函数装饰器
Python 的函数装饰器是一种函数,用于修改或扩展其他函数的行为,其本质是接受一个函数作为参数并返回一个新函数的函数。这种机制可以在不改变原始函数代码的情况下,动态的为函数添加功能,例如添加日志、权限校验、性能计时等,并可实现更复杂的功能扩展。
解释过程 | 代码示例 |
---|---|
1. 函数调用另一个函数(函数作为参数) 定义3个函数,其中 |
|
2.内置/嵌套函数(inner functions) 如果函数内部存在多个内置/嵌套函数,函数定义的前后位置并不重要,主要由执行语句的顺序确定。同时,内部/嵌套函数属于父函数 |
|
3.函数返回值为一个函数 Python 允许使用函数作为返回值,示例 |
|
4.简单的装饰器
内置函数 |
|
|
|
5.语法糖(Syntactic Sugar) 上面的装饰器方法笨拙。为了简化代码过程,Python 允许用 |
|
6.带参数的装饰器
|
|
7.装饰器的返回值 如果装饰器要返回值, |
可以接收装饰器内置/嵌套函数的返回值,赋予变量
|
8.保留原始函数的信息-自省(introspection)调整 自省是指一个对象在运行时了解自己属性的能力。例如,一个函数知道它自己的名字和文档。在上述示例中,通过 |
|
9. 带参数的装饰器 装饰器中可以带参数,例如 |
|
10.多个装饰器装饰一个函数 可以将多个装饰器堆叠在一起,应用在一个函数上。此时装饰器执行的顺序是从内到外,例如示例中先执行 |
|
11. 使用decorator模块②库的 |
|
6.4 作用域和命名空间
6.4.1 作用域
在编程中,作用域是指变量、函数或对象在代码块中的可访问范围。
作用域 | Py | C | C++ | C# | ||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
作用域类型 |
为函数或方法内部定义的变量,其作用范围仅限于该函数或方法内。局部变量的生命周期从函数调用开始,到函数执行结束。
嵌套函数(函数内部定义函数)中,外部函数的局部作用域。内部函数可以访问外部函数的变量,但不能直接修改,除非使用
模块级别定义的变量,作用范围为整个模块。全局变量可以在模块内的任何位置被访问,但在函数内修改时需用
由 Python 解释器提供的内置作用域,包括标准函数(如
Python 按照 LEGB 顺序查找变量,依次为 局部(Local) -> 嵌套(Enclosing) -> 全局(Global)-> 内置(Built-in)。
|
由大括号
在所有函数之外定义的变量或函数具有文件作用域,其在文件中从定义的位置开始,到文件末尾都可见。全局变量具有文件作用域,可以在整个文件中访问。通过
仅
函数参数在函数原型中有作用域。参数的作用域仅限于函数原型内部,函数定义中可以使用不同的名字。
当嵌套的代码块中定义了与外部代码块同名的变量时,内部变量会屏蔽外部变量。
使用
|
在所有函数或类之外定义的标识符,具有全局作用域,其在整个文件中都可以访问,除非被局部变量隐藏,并在程序开始时分配内存,程序结束时销毁。
在函数、语句块或循环内部定义的标识符,仅在所在的函数或语句块内有效,超出范围则不可访问。当函数被调用时分配内存,函数返回时销毁。
在类内定义的成员(变量、函数、类型等)具有类作用域。类的成员可以通过类的对象访问,但如果成员是
在一对
为在函数内部定义的标识符,通常指的是形参和局部变量,仅在函数体内有效。函数被调用时,内存被分配;返回时内存被销毁。
C++ 的作用域机制通过限制标识符的有效范围来避免命名冲突,并帮助管理变量的生命周期。通过合理使用作用域,可以使代码更易于维护和理解。 |
C# 中的作用域是指程序中定义的变量、方法或其他成员可以被访问的范围,其影响着代码的可读性、维护性以及变量的生命周期。
局部变量是在方法、构造函数或代码块(
类作用域适用于类的字段、方法和属性,其可以在类的范围内访问。使用访问修饰符(
静态成员属于类而不是特定实例,作用域为整个程序,其不需要实例化类即可访问,并在程序运行期间唯一存在,不会重复创建。
块作用域指变量仅在特定代码块(
方法的参数在方法的内部具有作用域,其在方法外不可访问,并参数变量覆盖同名的外部变量。
命名空间作用域包含在命名空间中定义的所有类型和成员。如果导入一个命名空间,则该命名空间内的所有内容对导入对象都可用。应防止名称冲突。
C# 没有传统意义上的全局变量,但可以使用静态类或文件范围的功能实现类似效果,其静态成员对整个程序可见。但不能直接在顶层定义变量。
|
||||||||
常见问题和注意事项 |
函数内的局部变量会覆盖同名的全局变量。
函数内若引用未定义的变量会报错。
使用
|
A. 静态存储期 当在变量声明前使用
如果
当
B. 限制链接性
|
|
6.4.2 命名空间
命名空间(Namespace)是编程语言中的一种机制,用于组织代码并防止命名冲突,通过创建一个逻辑容器、将标识符(如变量名、函数名、类名等)隔离在不同的空间中,从而避免命名冲突。命名空间的主要作用有,
1. 避免命名冲突: 当项目规模变大时,不同部分可能使用相同的名称。命名空间可以确保这些名称在逻辑上是隔离的。
2. 组织代码: 命名空间将相关的功能分组,使代码结构清晰易读。
3. 访问控制: 通过命名空间限制或明确标识某些符号的作用域,增强代码的安全性和可维护性。
命名空间中的标识符互不影响(隔离性);且很多语言支持嵌套命名空间,形成层级结构;并允许使用完整路径访问标识符,也可以通过别名或导入简化访问(灵活引用);一些语言也支持运行时加载命名空间(如 Python 的动态导入等)。如果将命名空间类比作文件夹结构,全局命名空间则为根目录;子命名空间为文件夹;不同文件夹可以包含相同的文件名(标识符),但访问时需要指定路径(命名空间)。
命名空间是一种代码组织和隔离机制,以避免命名冲突,提升代码可读性和可维护性。不同编程语言对命名空间的支持和实现方法虽有所不同,但核心目标一致,即为开发者提供清晰、安全的代码管理工具。命名空间在大型项目开发中可以通过分模块的命名空间组织代码,便于团队协作;集成第三方库时,避免引入库后产生命名冲突;而同一项目中的不同功能模块可以使用相同的标识符,因为命名空间隔离而互不干扰。
Py | C | C++ | C# |
---|---|---|---|
在 Python 中,命名空间是一个字典(内部实现),用于映射变量名到对象。每个作用域都有一个相关的命名空间。但 Python 没有显示的
|
C 语言中没有像 C++ 和 C# 那样明确的命名空间概念。C 使用文件级作用域(文件内的标识符)和全局作用域(程序中的所有函数和全局变量)的组合来管理命名冲突,控制变量的生命周期和可访问性。
|
C++ 引入了显示的
|
C# 使用
|
Python、C、C++ 和 C# 命名空间的对比。Python 中通过作用域来管理命名空间,不需要显示的命名空间定义;C 语言使用简单的作用域规则管理全局和局部变量,但没有namespapce
关键字;C++ 和 C# 提供了强大的命名空间功能,允许显示地用namespace
定义和管理命名空间,特别适用于大型项目,以避免命名冲突。
特性 | Python | C | C++ | C# |
---|---|---|---|---|
显示命名空间 | 无 | 无 | namespace |
namespace |
命名空间定义 | 使用作用域规则(LEGB)管理 | 通过全局变量和局部变量管理 | 使用namespace 关键字 |
使用namespace 关键字 |
嵌套支持 | 无 | 无 | 支持嵌套命名空间 | 支持嵌套命名空间 |
命名空间访问 | 通过作用域规则, globals() 和locals() |
通过作用域规则 | 使用:: 运算符访问命名空间成员 |
使用. 或using 访问命名空间成员 |
[附1]Python 类型提示
Python 的类型提示(Type Hinting)是一种用来提高代码可读性和可靠性的工具,允许开发者在定义变量、函数参数和返回值时指定其类型。类型提示不会在运行时强制执行,而主要用于静态代码检查工具(如mypy③ )和集成开发环境(IDE)的代码补全及错误检测。
mypy 是 Python 的静态类型检查器,有助于确保在代码中正确使用变量和函数。如将类型提示(PEP 484④ )添加到 Python 代码中,可以使用 mypy 检查是否错误的使用了这些类型。因为 Python 是一种动态语言,所以通常只有在运行时才会检查错误,返回异常信息。而 mypy 是一个静态检查器,当提供了类型提示后,不需运行程序即可进行错误检查。使用 mypy, 需在所在 Python 环境终端执行pip install mypy
进行安装,并执行mypy fileName.py
(所在目录)进行检查。例如定义一个名为 typingChecking.py 的模块(一个.py 后缀名的 Python 文件),其代码为:
def greeting(name: str) -> str:
return 'Hello ' + name
greeting(3)
greeting(b'Alice')
greeting("World!") # 无错误
def bad_greeting(name: str) -> str:
return 'Hello ' * name
当在 Python 环境终端中执行mypy typingChecking.py
,将会返回,
(PYCourse) C:\Users\richie\omen_richiebao\TEMP>mypy typingChecking.py
typingChecking.py:4: error: Argument 1 to "greeting" has incompatible type "int"; expected "str" [arg-type]
typingChecking.py:5: error: Argument 1 to "greeting" has incompatible type "bytes"; expected "str" [arg-type]
typingChecking.py:9: error: Unsupported operand types for * ("str" and "str") [operator]
Found 3 errors in 1 file (checked 1 source file)
如果并不提供类型提示,执行上述语句将不会有任何错误信息返回,而仅打印类似Success: no issues found in 1 source file
的信息。
- 基础用法
变量的类型提示
|
函数参数和返回值
|
函数无返回值
|
- 常用类型
列表和字典
|
元组
|
联合类型(可以是包含的多种类型之一)
|
可选类型(允许为
|
Any 类型(任意类型)
|
- 高级用法
类型别名
|
泛型
|
Callable
|
TypeDict(字典的固定结构)
|
[附2]指针*
、取地址&
、解引用*
和引用&
在 C/C++ 中, 指针、取地址、解引用和引用在内存管理、函数参数传递等方面起到至关重要的作用。
指针 |
引用 |
---|---|
指针( 取地址( 解引用(
|
在 C++ 中,
|
注释(Notes):
① functools,为Python 模块,用于高阶函数:作用于或返回其他函数的函数。一般来说,任何可调用对象都可以被视为该模块的函数。(https://docs.python.org/3/library/functools.html)。
② decorator 模块,是一个 Python 装饰器模块,目标是使定义保持签名的函数装饰器和装饰器工厂变得容易。(https://pypi.org/project/decorator/)。
③ mypy,是 Python 的静态类型检查器(https://mypy.readthedocs.io/en/stable/index.html)。
④ PEP 484,Python 的 Type Hints 文档(https://peps.python.org/pep-0484/)。