有关以下内容的概述:匿名,高阶,闭包,并发,延迟和其他类型的Golang函数。
这篇文章是Go中不同类型的func的摘要。 我将在以后的文章中更详细地介绍它们,因为它们应该得到更多。 这只是一个开始。
什么是功能?
函数是一个单独的可重复使用的代码块,可以一次又一次地运行。 函数可以接受输入值,也可以返回输出值。
我们为什么需要功能?
- 提高可读性,可测试性和可维护性
- 使代码的某些部分可单独执行
- 从较小的事物组成事物
- 为类型添加行为
- 组织代码
- 待 干燥
命名为Funcs
命名的func具有名称,并在包级别声明- 在另一个func的主体之外 。
👉我在 这里的 另一篇文章中详细解释了它们 。
可变参数功能
可变参数函数接受可变数量的输入值-零个或多个。 输入类型前面的省略号(三点)前缀使函数可变。
例
让我们创建一个Logger,在运行时可以使用选项模式更改详细程度和前缀选项:
type Logger struct { verbosity prefix string }
SetOptions将选项应用于记录器,以使用可变参数选项参数更改其行为:
func (lo * Logger ) SetOptions(opts ...option ) { for _, applyOptTo := range opts { applyOptTo(lo) } }
让我们创建一些函数,这些函数返回选项func作为关闭以更改Logger行为的结果:
func HighVerbosity() option { return func (lo * Logger ) { lo.verbosity = High } }
func HighVerbosity() option { return func (lo * Logger ) { lo.verbosity = High } }
func Prefix(s string ) option { return func(lo * Logger ) { lo.prefix = s } }
现在,让我们使用默认选项创建一个新的Logger:
logger := & Logger {}
然后通过可变参数为记录器提供选项:
logger. SetOptions ( HighVerbosity(), Prefix("ZOMBIE CONTROL"), )
现在让我们检查输出:
logger.Critical("zombie outbreak!")
// [ZOMBIE CONTROL] CRITICAL: zombie outbreak!
logger.Info("1 second passed")
// [ZOMBIE CONTROL] INFO: 1 second passed
👉要了解更多有关它们的信息,请 在此处 查看我关于可变参数功能的文章 。
方法
将功能附加到类型时,功能将成为该类型的方法 。 因此,可以通过该类型调用它。 Go在调用时会将类型( 接收者)传递给方法。
例
创建一个新的计数器类型并为其添加方法:
type Count int
func (c Count) Incr() int { c = c + 1 return int (c) }
上面的方法与此功能类似:
func Incr(c Count) int
价值接收者
Count实例的值将被复制并在调用时传递给方法。
var c Count; c.Incr(); c.Incr()
var c Count; c.Incr(); c.Incr()
// output: 1 1
它不会增加,因为“ c”是一个价值接收者。
指针接收器
为了增加计数器的值,你需要进行增量FUNC连接到 计数指针类型 - *计数。
func (c *Count) Incr() int { *c = *c + 1 return int(*c) }
var c Count
c.Incr(); c.Incr()
c.Incr(); c.Incr()
// output: 1 2
我以前的帖子中还有更多示例: 在这里和这里 。
接口方式
让我们使用接口方法重新创建上述程序。 让我们创建一个名为Counter的新接口:
type Counter interface { Incr() int }
下面的onApiHit函数可以使用具有Incr()int方法的任何类型:
func onApiHit(c Counter) { c.Incr() }
现在就使用我们的虚拟计数器- 您也可以使用真实的api计数器 :
dummyCounter := Count(0)
onApiHit(&dummyCounter)
// dummyCounter = 1
因为Count类型在其方法列表中具有Incr()int方法,所以onApiHit函数可以使用它来增加计数器—我将dummyCounter的指针传递给onApiHit,否则它不会增加计数器。
接口方法与普通方法之间的区别在于,接口更加灵活且耦合松散。 您可以跨软件包切换到不同的实现,而无需更改onApiHit等内部的任何代码。
一流的功能
一流意味着函子是值对象,就像可以存储和传递的任何其他值一样。
例:
此处的示例程序通过使用一片Crunchers作为名为“ crunch ”的函数的输入参数值来处理数字序列。
声明一个新的“ 用户定义的函数类型 ”,该类型需要一个int并返回一个int。
这意味着使用此类型的任何代码都将接受具有以下确切 签名 的func :
type Cruncher func ( int ) int
声明一些紧缩功能:
func mul(n int ) int { return n * 2 }
func add(n int ) int { return n + 100 }
func sub(n int ) int { return n - 1 }
紧缩FUNC处理的一系列使用整数的可变参数 排排坐funcs中 :
func crunch(nums [] int , a ...Cruncher) (rnums [] int ) {
// create an identical slice rnums = append(rnums, nums...)
for _, f := range a { for i, n := range rnums { rnums[i] = f(n) } }
// create an identical slice rnums = append(rnums, nums...)
for _, f := range a { for i, n := range rnums { rnums[i] = f(n) } }
return }
用一些数字声明一个int切片并处理它们:
nums := [] int {1, 2, 3, 4, 5}
crunch(nums, mul, add, sub)
输出:
[101 103 105 107 109]
匿名功能
一个NONAME FUNC是匿名FUNC和它的声明为inline 使用 函数文本 。 当它用作闭包,高阶函数,延迟函数等时,它将变得更加有用。
签名
一个命名的func:
func Bang(energy int ) time.Duration
匿名函数:
func (energy int ) time.Duration
它们都具有相同的签名,因此可以互换使用:
func ( int ) time.Duration
例
让我们使用匿名函数从上面的First-Class Funcs部分重新创建Cruncher程序。 在主功能内部将处理程序声明为匿名功能。
func main() {
crunch(nums, func (n int ) int { return n * 2 }, func (n int ) int { return n + 100 }, func (n int ) int { return n - 1 })
}
之所以可行,是因为紧缩功能仅需要Cruncher功能类型,而不管它们是命名功能还是匿名功能。
为了提高可读性,您还可以在传递给紧缩功能之前将它们分配给变量:
mul := func (n int ) int { return n * 2 }
add := func (n int ) int { return n + 100 }
sub := func (n int ) int { return n - 1 }
crunch(nums, mul, add, sub)
高阶函数
高阶函数可能会使用一个或多个函数,也可能会返回一个或多个函数。 基本上,它使用其他功能来完成其工作。
下面的闭包部分中的split func是高阶函数。 结果返回一个分词器功能类型 。
关闭
闭包可以记住定义它的所有周围的值。 封闭的好处之一是,只要您愿意,它就可以在捕获的环境中运行-当心泄漏!
例
声明一个新的func类型,该类型返回分割后的字符串中的下一个单词:
type tokenizer func () (token string , ok bool )
下面的split func是一个高阶函数 ,它通过分隔符分割字符串并返回一个闭包 ,该闭包使遍历分割后的字符串的单词成为可能。 返回的闭包可以使用周围的变量:“令牌”和“最后一个”。
我们试试吧:
const sentence = "The quick brown fox jumps over the lazy dog"
iter := split(sentence, " ") for { token, ok := iter() if !ok { break }
fmt.Println(token) }
- 在这里,我们使用split func将句子拆分为单词,然后获得一个新的iter func作为结果值并将其放入iter变量中。
- 然后,我们开始一个无限循环,该循环仅在iter函数返回false时才终止。
- 每次调用iter函数都将返回下一个单词。
结果:
The quick brown fox jumps over the lazy dog
延迟功能
延迟的功能仅在其父功能返回后才执行。 也可以使用多个延迟器,它们以一个堆栈一个一个地运行。
例
Go运行时会在注册延迟时( 而不是在运行时)将所有传递的参数保存到延迟功能中。
声明一个注册了延迟关闭的伪函数。 它还使用命名结果值“ n”来第二次增加传递的数字 :
func count(i int ) (n int ) {
defer func () { n = n + i }(i)
i = i * 2 n = i
return }
我们试试吧:
count(10)
// output: 30
发生了什么?
在某些情况下 ,如示例所示, defer可以通过使用 命名结果值 来帮助您在返回之前更改结果值 。
👉要了解有关它们的更多信息,请 在此处 查看我有关Go defers的帖子 。
并发函数
go func()与其他goroutines同时运行传递的func。
goroutine是一种较轻的线程机制,可让您有效地构造并发程序。 主函数在main-goroutine中执行。
例
在这里,“开始”匿名函数成为并发函数,当使用“ go”关键字调用时,该函数不会阻止其父函数的执行:
start := func () { time.Sleep(2 * time.Second) fmt.Println( "concurrent func: ends" ) }
go start()
fmt.Println(" main: continues... ") time.Sleep(5 * time.Second) fmt.Println(" main: ends ")
输出量
main: continues... concurrent func: ends main: ends
如果主函数中没有睡眠调用,则主函数将终止,而无需等待并发函数完成:
main: continues... main: ends
其他种类
递归函数
您可以像在其他任何lang中一样使用递归函数,在Go中没有实际的实际区别。 但是,您一定不要忘记每个调用都会创建一个新的调用堆栈 。 但是,在Go中,堆栈是动态的,它们可以根据func的需求而收缩和增长。 如果您可以解决问题而无需递归,请改用该方法。
黑洞功能
黑洞函数可以定义多次,并且不能以通常的方式调用它们。 它们有时对测试解析器很有用:请参阅this 。
func _() {} func _() {}
内联函数
Go链接器将func放入可执行文件中,以便以后在运行时调用它。 与直接执行代码相比,有时调用函数是一项昂贵的操作。 因此,编译器将func的主体注入调用程序。 要了解更多关于他们:阅读这个和这个和这个 还有这个 。
外部功能
如果您省略函子的主体而仅声明其签名,则链接程序将尝试在可能已在其他地方编写的外部函子中找到它。 例如, 此处仅使用签名声明Atan函数 ,然后在此处实现 。
with与您的朋友分享这篇文章。 谢谢! 💓
我也在为Go创建在线课程→ 加入我的新闻 ←
“让我们每周保持联系以获取新的教程和技巧”
阅读更多:
最初于 2017 年11月9日 发布在 blog.learngoprogramming.com 上。
From: https://hackernoon.com/the-zoo-of-go-functions-8f6458f51ed1