这段 go 代码始终理解不到

2024-03-13 17:27:51 +08:00
 Grocker
package main

import "fmt"

type Greeting func(name string) string

func (g Greeting) say(n string) {
	fmt.Println(g(n))
}

func english(name string) string {
	return "Hello, " + name
}

func main() {
	greet := Greeting(english)
	greet.say("World")
}

这段代码为什么会输出 Hello, World,始终理解不到

7544 次点击
所在节点    Go 编程语言
69 条回复
leonshaw
2024-03-14 16:23:03 +08:00
@FYFX
Greeting 是一个新的类型,它的 underlying 类型是 func(name string) string
english 是一个签名是 func(name string) string 的函数
aw2350
2024-03-14 16:26:41 +08:00
多态,委托 ,先好好补习一下面向对象
yiqiao
2024-03-14 16:32:40 +08:00
OP 有用过 PHP 吗?类似:call_user_func_array
flyv2x
2024-03-14 16:36:56 +08:00
简单说,其实你的例子,如果去掉 type 定义,直接等价于下面代码:
```
package main

import "fmt"

func say(g func(name string) string, n string) {
fmt.Println(g(n))
}

func english(name string) string {
return "Hello, " + name
}

func main() {
say(english, "World")
}
```
CHchenkeyi
2024-03-14 16:39:29 +08:00
他们都说的复杂了,其实很好理解,因为 english 的函数签名和 Greeting 一致,所以可以转换,这边是转换就像 int64 ->int , 是不是就是 int(int64) 这种写法, 那么 greet 就是 english ,然后调用了 say ('world')=>greet(n) 方法, 实际上就是 english('world)
Rache1
2024-03-14 16:55:22 +08:00
就是存了个回调函数吧,等效于 JS 的。


function Foo (func) {
this._func = func;

this.say = function (str) {
console.log(this._func(str));
};

if (!(this instanceof Foo)) {
return new Foo(func);
}
}

function english (str) {
return 'Hello, ' + str;
}

// 1
Foo(english).say('World');

// 2
(new Foo(english)).say('World');
zhangyq008
2024-03-14 17:01:49 +08:00
类似接口型函数,用处多多
看下这篇文章 https://geektutu.com/post/7days-golang-q1.html
hb751968840
2024-03-14 17:14:51 +08:00
js 好理解
function english(name) {
return "Hello, " + name
}
function Greeting (english) {
return {
say: (name) => {
english(name)
}
}
}
function main() {
const greet = Greeting(english)
greet.say("World")
}
largezhou
2024-03-14 17:19:25 +08:00
首先 Greeting 的类型是一个函数
然后 english 这个函数跟 Greeting 的函数签名一样
所以可以把 english 这个函数,强转成 Greeting 类型,即:greet := Greeting(english)
然后调用 Greeting 的“实例”方法 say
由于 Greeting 本身是个函数,可以直接调用,say 里面就是调用 g(n)
实际就是调用的 english("World")
Vegetable
2024-03-14 17:25:25 +08:00
写成这样同事不打人的吗
svnware
2024-03-14 17:49:20 +08:00
学过 C/C++的,一看就明白
sztink
2024-03-14 17:50:45 +08:00
**方法本质就是普通的函数,方法的接收者就是隐含的第一个参数。**

```go
greet.say("World");
say(greet, "World");
```
上面两者是等效的,从这个角度理解就简单了不少。具体介绍可以看:[深入 Go 语言之旅: 方法]( https://go.cyub.vip/function/method/)
via
2024-03-14 18:08:52 +08:00
鸭子类型,english 入餐像鸭子,出餐也像鸭子,那我们就可以认为 English 就是鸭子。

上面的鸭子就是 Greeting
via
2024-03-14 18:09:44 +08:00
OP 可以断言下:english.(Greeting) 看看会不会报错
sztink
2024-03-14 18:44:13 +08:00
@sztink 回复咋不支持 markdown 格式,另外怎么发链接,我发的链接都显示成纯文本(没使用 markdown ,直接发个链接也这样),看别人发的可以直接点击?有 V 友可以告知怎么操作链接吗。
FengMubai
2024-03-14 19:19:13 +08:00
你在`return "Hello, " + name`处打个断点, 观察调用栈能就明白
cosiner
2024-03-14 20:05:49 +08:00
type 类型名称 类型定义

type Greeting func(name string)
type Name string
type Data struct {
Name string
}

type 类型名称 类型名称
type Data2 Data


Go 里面自己使用 type 定义的新类型名称都可以给它加方法,
你这个例子里面是把 english 相当于是匿名的 func(string)类型, 然后把它转换成了 Greeting 类型(实际都是 func(string)类型,Go 里面能随便转换), 就可以调用 Greeting 的 say 方法了,和接口,鸭子类型没关系
wqtacc
2024-03-14 20:24:18 +08:00
```go
package main

import "fmt"

// Greeting 定义为一个函数
type Greeting func(name string) string

func (g Greeting) say(n string) {
// g 调用函数自身
s := g(n)
fmt.Println(s)
}

func english(name string) string {
return "Hello, " + name
}

func main() {
// english 函数与 Greeting 签名相同,进行强转
greet := Greeting(english)
greet.say("World")


var greet2 Greeting = func(s string) string {
return "Hello, " + s
}
fmt.Println(greet2("World!"))
greet2.say("World!")
}

```
wqtacc
2024-03-14 20:26:08 +08:00
```go
package main

import "fmt"

// Greeting 定义为一个函数
type Greeting func(name string) string

func (g Greeting) say(n string) {
// g 调用函数自身
s := g(n)
fmt.Println(s)
}

func english(name string) string {
return "Hello, " + name
}

func main() {
// english 函数与 Greeting 签名相同,进行强转
greet := Greeting(english)
greet.say("World")

// 完整的长生命方法, 同时去除 english 的干扰项
var greet2 Greeting = func(s string) string {
return "Hello, " + s
}
fmt.Println(greet2("World!"))
greet2.say("World!")
}

```
yuzo555
2024-03-14 21:59:39 +08:00
Claude 3 的解释:
这段代码的输出 "Hello, World" 是因为以下几个原因:

1. 在代码中定义了一个名为 Greeting 的函数类型,它是一个接受字符串参数,并返回字符串的函数。

2. 定义了一个名为 english 的函数,它满足 Greeting 类型的要求,即接受一个字符串参数,并返回一个字符串 "Hello, " 加上传入的参数。

3. 在 main 函数中,创建了一个名为 greet 的变量,它的类型是 Greeting,并将 english 函数转换为 Greeting 类型赋值给了 greet 。

4. 对于 Greeting 类型的变量,Go 语言会自动为其提供一个 say 方法,这个方法接受一个字符串参数,并调用 Greeting 类型的函数,传入该字符串参数。

5. 在 main 函数中,调用了 greet.say("World")。这相当于先调用了 greet("World"),也就是调用了 english("World")函数,该函数返回了 "Hello, World"。然后将这个返回值作为参数打印出来。

所以,最终输出的就是 "Hello, World"。这段代码展示了 Go 语言中将函数作为值进行传递和赋值的能力,以及通过为某个类型定义方法来扩展其功能的特性。

在这段代码中,比较难以理解的一个点是:

func (g Greeting) say(n string) {
fmt.Println(g(n))
}
这里为 Greeting 类型(它是一个函数类型)定义了一个 say 方法。

具体来说,有以下几点需要注意:

1. Greeting 是一个函数类型,它本身并不是一个具体的函数值,而是一种函数签名的描述。

2. Go 语言允许为任何命名类型(包括内置类型和自定义类型)定义方法,这里是为 Greeting 这个函数类型定义了一个 say 方法。

3. say 方法的接收者是 g Greeting,表示这个方法会为所有 Greeting 类型的值(函数值)提供 say 方法。

4. 在 say 方法的实现中,它直接以调用函数的方式 g(n) 来执行该 Greeting 类型的函数值,并将结果打印出来。

这种为函数类型定义方法的做法,看起来有点违反直觉,因为我们一般会认为方法只能为结构体或对象这样的数据类型定义。但在 Go 语言中,函数作为一等公民,也可以为其定义方法。

这样做的好处是,可以为某个函数类型扩展一些通用的辅助方法,而不需要为每个具体的函数值都实现这些方法。这提高了代码的可重用性和可维护性。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1023324

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX