Go 语言中的一等公民:看似普通的函数,凭什么?

开发 后端
在 Go 语言中,一提函数,大家提的最多的就是 “Go 语言的函数是一等公民”。这个定义来的非常突然,我们先了解一下什么是一等公民,他又凭什么?

[[388289]]

本文转载自微信公众号「脑子进煎鱼了」,作者陈煎鱼  。转载本文请联系脑子进煎鱼了公众号。

大家好,我是煎鱼。

在 Go 语言中,一提函数,大家提的最多的就是 “Go 语言的函数是一等公民”。这个定义来的非常突然,我们先了解一下什么是一等公民,他又凭什么?

根据维基百科的一等公民(First-class citizen)的定义:

In programming language design, a first-class citizen (also type, object, entity, or value) in a given programming language is an entity which supports all the operations generally available to other entities. These operations typically include being passed as an argument, returned from a function, modified, and assigned to a variable.

在编程语言设计中,给定编程语言中的一等公民(也就是类型,对象,实体或值)可以把函数赋值给变量,也可以把函数作为其它函数的参数或者返回值来直接使用。

Go 语言的函数也满足这个定义,因此常被称为 “一等公民”,非常有意思。了解清楚背景后,接下来进一步展开。

普通函数

在 Go 语言中普通函数的定义格式为 func [函数名](入参)(出参),如下:

  1. func callFuncA(x, y string) (s string, err error) { 
  2.  return x + y, nil 
  3.  
  4. func main() { 
  5.  callFuncA("炸""煎鱼"

在示例代码中声明了一个函数名为 callFuncA 的方法,他只允许在包内调用,因此首字母为小写。

其具有两个入参,分别是 x 和 y,类型都为 string。而出参为变量 s 和 err,类型分别为 string 和 error。

另外在函数体内返回值时,也可以采用快捷返回的方式:

  1. func callFuncA(x, y string) (s string, err error) { 
  2.  s = x + y 
  3.  return 

在出参时所声明的变量名称,是可以应用到自身函数的。因此若直接执行 return 则会隐式返回已经声明的出参变量。

在函数定义时,其入参还支持可变参数的语法:

  1. func callFuncA(x ...string) (s string, err error) { 
  2.  s = strings.Join(x, ","
  3.  return 
  4.  
  5. func main() { 
  6.  fmt.Println(callFuncA("炸""煎鱼")) 

在入参变量上声明为 x ...string,则表示变量 x 是 string 类型的可变变量,能够在入参时传入多个 string 参数。

可变变量所传入的格式为切片(slice)类型,该类型我们会在后面的章节进行讲解,你可以理解为不受长度限制的动态数组:

  1. [0: 炸 1: 煎鱼] 

一般对可变变量的常见后续操作多是循环遍历处理,又或是进行拼接等操作。

匿名函数

Go 语言也默认支持匿名函数的声明,声明的方式与普通函数几乎一样:

  1. func main() { 
  2.  s := func(x, y string) (s string, err error) { 
  3.   return x + y, nil 
  4.  } 
  5.  
  6.  s("炸""煎鱼"

匿名函数可以在任意地方声明,且不需要定义函数名,如果在函数体后马上跟 () 则表示声明后立即执行:

  1. func main() { 
  2.  s, _ := func(x, y string) (s string, err error) { 
  3.   return x + y, nil 
  4.  }("炸""煎鱼"

而在所有的函数类使用中,有一点非常重要,那就是函数变量作用域的理解:

  1. func main() { 
  2.  x, y := "炸""煎鱼" 
  3.  s, _ := func() (s string, err error) { 
  4.   return x + y, nil 
  5.  }() 
  6.  fmt.Println(s) 

该匿名函数没有形参,函数内部没有定义相应的变量,此时其读取的是全局的 x、y 变量的值,输出结果是 “炸煎鱼”。

  1. func main() { 
  2.  x, y := "炸""煎鱼" 
  3.  _, _ = func(x, y string) (s string, err error) { 
  4.   x = "吃" 
  5.   return x + y, nil 
  6.  }(x, y) 
  7.  fmt.Println(x, y) 

该匿名函数有形参,但是在函数内部又重新赋值了变量 x。那么最终外部所输出的变量 x 的值是什么呢?输出结果是 “炸 煎鱼”。

为什么明明在函数内已经对变量 x 重新赋值,却依然没有改变全局变量 x 的值呢?

其本质原因是作用域不同,函数内部所修改的变量 x 是函数内的局部变量。而外部的是全局的变量,所归属的作用域不同。

结构方法

在结合结构体(struct)的方式下,可以声明归属于该结构体下的方法:

  1. type T struct{} 
  2.  
  3. func NewT() *T { 
  4.  return &T{} 
  5.  
  6. func (t *T) callFuncA(x, y string) (s string, err error) { 
  7.  return x + y, nil 
  8.  
  9. func main() { 
  10.  NewT().callFuncA("炸""煎鱼"

具体的函数的使用方法与普通函数一样,无其他区别。

而与结构体有关的值传递、引用传递的方法调用将在具体后面的章节再展开。

内置函数

Go 语言本身有支持一些内置函数,这些内置函数的调用不需要引用第三方标准库。内置函数的作用是用于配合 Go 语言的常规使用,数量非常少。如下:

  • 用于获取某些类型的长度和容量:len、cap。
  • 用于创建并分配某些类型的内存:new、make。
  • 用于错误处理机制(异常恐慌、异常捕获):panic、recover。
  • 用于复制和新增切片(slice):copy、append。
  • 用于简单输出信息:print、println。
  • 用于处理复数:complex、real、imag。

针对每个内置函数的真实使用场景,我们会在后续的章节再进一步展开,因为每个内置函数本质上都对应着各类型的使用场景。

总结

在本章节中,我们介绍了 Go 语言的函数为什么称是一等公民,并且针对函数的各类变形:普通函数、匿名函数、结构方法、内置函数进行了基本的说明。

面对新手入门最容易犯错的函数作用域问题,也进行了基本的梳理。这块建议大家要多多深入思考、理解,避免日后踩坑。

 

责任编辑:武晓燕 来源: 脑子进煎鱼了
相关推荐

2022-03-27 23:11:39

Go语言函数

2023-03-28 07:26:37

2021-01-27 05:25:44

Go语言函数

2022-11-07 18:12:54

Go语言函数

2015-04-27 09:48:46

Kubernetes数据中心

2023-01-12 08:52:50

GoroutinesGo语言

2021-07-15 23:18:48

Go语言并发

2024-04-07 11:33:02

Go逃逸分析

2023-12-21 07:09:32

Go语言任务

2022-07-19 12:25:29

Go

2023-11-30 08:09:02

Go语言

2021-06-08 07:45:44

Go语言优化

2023-07-29 15:03:29

2024-03-26 11:54:35

编程抽象代码

2012-06-15 09:56:40

2023-12-25 09:58:25

sync包Go编程

2023-12-30 18:35:37

Go识别应用程序

2021-07-13 06:44:04

Go语言数组

2023-11-21 15:46:13

Go内存泄漏

2024-01-08 07:02:48

数据设计模式
点赞
收藏

51CTO技术栈公众号