内容简介:标题为啥用函数不用方法?方法和函数的区别在我看来,无非是方法多了一个参数,该参数指代方法的宿主,所以方法是函数的延伸,一个特例。编程语言的函数原型来自于数学,源远流长,在广大人群中已经根深蒂固,而且它的表意也是非常明确的,有未知数,视同输入,有函数值,视同输出。总而言之,我偏好函数。函数本身是一个虚拟的抽象概念,而我们现在做的事就是在抽象中进一步抽象,也就是抽象的抽象。在我看来,抽象是简化的同义词,都是试图提取重要的共性,忽略不重要的细节。抽象的产物的我们一般用模型指代,所以整个流程我称之为建模。这个特性
标题为啥用函数不用方法?方法和函数的区别在我看来,无非是方法多了一个参数,该参数指代方法的宿主,所以方法是函数的延伸,一个特例。编程语言的函数原型来自于数学,源远流长,在广大人群中已经根深蒂固,而且它的表意也是非常明确的,有未知数,视同输入,有函数值,视同输出。总而言之,我偏好函数。
2. 建模
函数本身是一个虚拟的抽象概念,而我们现在做的事就是在抽象中进一步抽象,也就是抽象的抽象。在我看来,抽象是简化的同义词,都是试图提取重要的共性,忽略不重要的细节。抽象的产物的我们一般用模型指代,所以整个流程我称之为建模。
2.1 函数的特性
2.1.1 都具有输入输出
这个特性已经被 程序员 烂熟于心,以至于在这里显得有些多余,正是因为他的普适,而更加说明这是不可或缺的一个重要组成元素。
输入是已知的,而输出是未知的,是我们需要求索的答案。函数经常被看做是一个黑盒子,我们把输入放进去,输出就会自动出来,而黑盒子里是什么,如果你只是一个使用者,则无需关心。这种方式极大的简化函数调用者的开发难度。
graph LR 输入-->函数 函数-->输出 复制代码
这里,可能有人说了,有些函数没有输出,“这里没有输出”意指函数没有返回值,有些编程语称也把这些没有返回值的函数称为过程,但是我这里的输出再次重申为不仅限于函数返回值的输出,而包括其他形式的输出,比如函数副作用,产生对外部状态的改变的行为也统称为输出,如果一个函数封闭式的内部改变,只进不出,虽不能否定其存在,但是从实用角度来讲毫无意义。输入同理,也不应该理解为狭义的编程语言函数的参数列表,而应该理解为外部状态的输入。
通常输入的具体实现是一个参数列表,该参数列表至少包含每个参数的变量名称和类型,如果是弱类型语言,类型也可省略;或者再不济,函数内部也至少提供访问到参数的路径,这样连整个参数列表都可以省略,比如 js
中函数内部内置变量 arguments
就可以访问到调用者所有实际参数,而不需要任何形式参数的声明;再或者这个函数没有参数列表,但这不代表,它没有输入,它有隐式输入,例如函数代码可以访问到某些外部状态,比如获取当前时间,我们也认为这是输入的一种其他形式 。
而输出不需要任何名称标识,但不是绝对的,比如 go
语言提供了返回值名称声明,方便了在函数多返回值时内部直接赋值,所以只能说返回标识不是必要的。强类型语言可能会需要提供返回类型声明,而弱类型语言连返回类型都可以省略。
接下来,我们来看看不同编程语言的一个函数应该长啥样。
go
package main import "fmt" func main() { result := foo("medivh") fmt.Println("result: " + result); } func foo(name string) string { fmt.Println("hello " + name) return name } 复制代码
python
def foo(name): print("hello " + name) return name; result = foo("medivh"); print("result: " + result) 复制代码
javascript
function foo(name) { console.log('hello ' + name); return name; } var result = foo("medivh") console.log("result:", result); 复制代码
java
class Main { public static void main(String[] args) { String result = foo("medivh"); System.out.println("result: " + result); } static String foo(String name) { System.out.println("hello " + name); return name; } } 复制代码
c#
using System; class Program { static void Main(string[] args) { string result = Foo("medivh"); Console.WriteLine("result: " + result); } static string Foo(string name) { Console.WriteLine("hello " + name); return name; } } 复制代码
以上代码都是绝对可运行的,本人亲测!同时安利一个好用的工具,可以在本地搭建服务运行多种编程语言代码,它的名字是 nodebook 。我这里的代码即是使用该 工具 测试运行完成。
我们可以观察 foo
函数的定义,基本上所有编程语言语法都大同小异,都有参数列表和函数名称构成函数签名,强类型语言普遍有返回类型声明,而弱类型语言普遍没有返回类型声明,因为多此一举。那么这么多的表现形式,我们该怎么分析?记住下面这句话:
记剑意,不要记剑招。
剑意是其本质,是种子,可以诞生无穷无尽的剑招。 所以这里的剑意就是一个函数一定要有访问输入和产生输出的机制。
2.1.2 重载
事实证明重载不是必要的,例如 go
就没有重载,我不知道是 go
没来得及实现,还是设计构想中就没有重载这项,不过现在使用 go
语言编程的人也没有说因为缺少这一特性而而导致严重的问题出现,顶多是不习惯,不顺手。
何为重载?重载就是复用相同的函数名称,但是参数列表不同,进而函数签名不同,从而提供可靠的差异性。要知道一句名言,我说的,如下:
编程最难的事是命名,几乎占编程 80% 的时间。
为啥命名这么难,因为一个名字与其使用环境有着强关联,名字就是背后复杂逻辑简化的代名词,你能想出好的名字,也就意味着你理解背后的逻辑。所以名字只是一个表征,反映了你对程序的理解程度。我相信对于一个你十分熟悉的程序编写,命名的效率应该会很快。但是名字也是稀缺资源,同一个命名空间下,名字是用一个少一个,好用的名字不多,你只能在几乎有限的名字池中筛选一个最合适的。而对于同一功能不同版本,命名更是难上加难,重载非常完美的解决这个问题,只要参数列表不同,重用相同的函数名称是可能的。
重载的缺点是啥?是由参数列表的微小差异带来的近似混淆。由于参数列表差异微小,致使调用者稍不留神,就编写错误的调用代码。还有就是兼容类型的处理,特别强调隐式转换带来的坑。
还有,弱类型语言大多数是没有重载的,因为参数列表没有强类型制造差异,导致程序执行时无法识别具体调用的目标是谁,当然也有意外, python3
通过特有的机制去实现重载,比如使用装饰器 functools.singledispatch
。而强类型语言重载基本上是标配。具体编程语言重载示例如下所示:
java
class Main { public static void main(String[] args) { foo(); foo("medivh"); } static void foo() { System.out.println("hello world"); } static void foo(String name) { System.out.println("hello " + name); } } 复制代码
c#
using System; class Program { static void Main(string[] args) { Foo(); Foo("medivh"); Console.ReadKey(); } static void Foo() { Console.WriteLine("hello world"); } static void Foo(string name) { Console.WriteLine("hello " + name); } } 复制代码
2.1.3 闭包
闭包是什么?闭包是一种机制,一种行为,是函数使用方式的进一步发明与创造,它可以让函数携带状态。
我们知道,影响函数行为有两大因数,一是函数的参数列表,二是外部状态的访问,例如函访问全局作用域变量。但是函数本身是没有状态的,所以它只能依据前面提的两大因数的而产生行为变化。
那闭包做了什么,它可以让一个函数内置状态,也就是说闭包让函数携带了状态,依据这些状态,它的行为很可能相应发生变化,这种携带状态的函数可以称之为运行时函数。
闭包只能发生在函数内部,所谓的“闭”是指封闭在某一函数内部而且不外泄的意思,一般情况下是函数返回一个被构建好的函数,这个被构建出来的函数携带了构建函数产生的状态信息,即使是构建函数返回值之后,这些状态依然被保留下来,可谓是如影随行,除非函数引用为零,这些状态信息所占用的内存才会被释放掉。
闭包代码如下所示:
javascript
function fooConstructor(status){ return function foo() { console.log("status: " + status); }; } var foo = fooConstructor(47) foo() 复制代码
python3
def fooConstructor(status): def foo(): print("status: " + str(status)) return foo foo = fooConstructor(47) foo() 复制代码
2.1.4 位置参数、可变参数、默认参数和可选参数
关于参数列表这里还有很多花样可以折腾,虽然增加了理解的复杂度,但是方便了函数调用者的使用。下面一一介绍。
位置参数是按照位置来传参的函数参数声明,它是必传参数,也就是严格按照顺序一一传入,少一个也不行,这种情况想必大家都一清二楚,是最常见的参数形式。示例代码如下:
go
package main import "fmt" func main() { foo("a", "b") } func foo(arg1 string, arg2 string) { fmt.Printf("argument list: arg1 = %s, arg2 = %s\n", arg1, arg2) } 复制代码
可变参数顾名思义,也就是可以有不确定数量的参数声明,为了不产生二义性,它只能放在固定参数后面,通常在函数内部,以类数组的方式去访问。示例代码如下:
go
package main import "fmt" func main() { foo("a", "b", "c") } func foo(arg1 string, someArgs ... string) { fmt.Printf("argument list: arg1 = %s, arg2 = %s\n", arg1, someArgs) } 复制代码
默认参数意为在函数调用之前会有默认值赋给参数变量,如果调用者,没传这个参数,则内部访问到就是默认参数。示例代码如下:
c#
using System; class Program { static void Main(string[] args) { Foo("medivh"); Console.ReadKey(); } static void Foo(string name, int age = 18) { Console.WriteLine("My name is " + name + ", and my age is " + age + " years old."); } } 复制代码
可选参数意指可以传递也可以不传递的参数声明,细分可以划分为两种类型,一种称之为可变命名参数,也就是调用时可以通过指定名字赋值的参数声明,它可以在固定参数后以任意顺序指定,另一种称之为可选位置参数,它是以固定顺序传参的参数声明,它可以省略末尾的,但是不能省略中间或者排在前面的可选位置参数。可选参数通常会和默认参数配合使用,示例代码将以 Dart
举例,因为他的语法在我看来比较优雅,代码如下:
dart
void main() { foo("medivh", age:24); foo2("medivh", "female", 17); } // 可选命名参数 void foo(String name, {String sex = "male", int age, String hobby="code"}) { print("name: " + name + ", sex: " + sex + ", age: " + age.toString() + ", hobby: " + hobby); } // 可选位置参数 void foo2(String name, [String sex = "male", int age, String hobby="code"]) { print("name: " + name + ", sex: " + sex + ", age: " + age.toString() + ", hobby: " + hobby); } 复制代码
3 总结
函数是编程语言不可或缺的模块,使用它通常是为了封装代码以达到复用的目的。这里为函数建造出的模型覆盖了多种编程语言函数的共有特性,这些特性是值得反复推敲,也可思考替代某一种特性的其他实现方式,只不过,沉淀至今,更好的替代方案会越来越难找寻了。
以上所述就是小编给大家介绍的《编程语言特性:函数》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- Go 语言函数式编程系列教程(十八) —— 函数篇:函数的基本定义和调用
- Go 语言函数式编程系列教程(十九) —— 函数篇:函数的传参和返回值
- Elixir 1.8 发布,函数式编程语言
- OCaml 4.08.0 发布,函数式编程语言
- OCaml 4.09.0 发布,函数式编程语言
- Elixir 1.4.4 版本发布,函数式编程语言
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
深度探索C++对象模型
[美] Stanley B. Lippman / 侯捷 / 华中科技大学出版社 / 2001-5 / 54.00元
这本书探索“对象导向程序所支持的C++对象模型”下的程序行为。对于“对象导向性质之基础实现技术”以及“各种性质背后的隐含利益交换”提供一个清楚的认识。检验由程序变形所带来的效率冲击。提供丰富的程序范例、图片,以及对象导向观念和底层对象模型之间的效率测量。一起来看看 《深度探索C++对象模型》 这本书的介绍吧!