Golang函数语法详解:func (receiver) identifier(parameters) (returns) { code }

Golang函数语法详解:func (receiver) identifier(parameters) (returns) { code } 关于函数语法,我有以下信息:func (receiver) identifier(parameters) (returns) { code }

由于以下所有项都用括号标记:(receiver)(parameters)(returns);我该如何区分它们,或者当我在函数中看到它们时,如何知道哪个是哪个?

21 回复

父母?

更多关于Golang函数语法详解:func (receiver) identifier(parameters) (returns) { code }的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


是的,她确实有。根据我的理解,她进行了多次巡讲,并且在她的提问中也提到了这一点。

@cherilexvold1974 你完成 Go 语言之旅了吗?这是一个很好的起点。 https://tour.golang.org/

根据它们的位置。

同时请注意,虽然接收者和参数周围的括号是必需的,但返回类型周围的括号仅在有多个返回值时才需要。

NobbZ:

应该是“括号”,手机虚拟键盘的自动更正功能在捣乱。

我也一样。在这个论坛上用手机回复实在太难了。

NobbZ:

应该是“括号”

我猜可能是这样。那么现在,我会重新审视你所说的内容。

所以,foo 是标识符。而如果存在接收器,它会出现在单词“foo”之前。

cherilexvold1974:

Parents

应该是“parens”,手机虚拟键盘自动更正的结果。而“parens”只是“parentheses”(圆括号)的简称。

这是参数/形参的问题。

接收器实际上是可选的(但如果存在,括号是必需的),返回值也是如此。

形参可以为空,但括号必须存在。只需查看你发布的同一个例子中的 main 函数。

func main() {
    fmt.Println("hello world")
}
func liner(functionName string, format string, a …interface{}) {
info := fmt.Sprintf(format, a…)
fmt.Printf(“[%s] %v\n”, functionName, info)
}

你能逐段解释一下这个吗?

liner(“main”, “debug line before define x”)

请同样解释一下这个。

我仍然更喜欢简单的版本:https://golang.org/pkg/fmt/#Printf

谢谢。 根据你的建议,我开始研究这个有用的链接。

Printf 根据格式说明符进行格式化并写入标准输出。它返回写入的字节数和遇到的任何写入错误。

什么是格式说明符? 什么是写入错误?

到目前为止,我的老师一直使用 Printf 来显示代码中特定项目的类型。这如何说明上述定义?

cherilexvold1974:

我不知道那是什么。我该如何自己发现它?

这只是我用 fmt 构建的一个简单函数。你可以通过阅读文档(fmt 包 - fmt - Go 包)来查看它们,或者先用谷歌搜索新工具开始。

cherilexvold1974:

我可以在自己的环境中运行这个吗?怎么做?

是的。这只是给你测试假设的一个想法。

我注意到有时函数没有接收器。参数是否总是跟在标识符后面?而返回值(在我看来,我见过没有返回值的函数),当它们存在时,是否总是跟在参数后面?有没有函数没有参数的情况?

在这个函数中:https://play.golang.org/p/AEbN6j-ClwC,三个项中的哪一个是 (y int)?

在解释刚才提到的代码时,老师说“我们将 x 传入这里 (foo),它被赋值给 y”。

我不理解这个概念:它被赋值给 y。

hollowaykeanho:

返回值是可选的。如果存在,return 关键字必须位于遵循输出规范的代码内部。否则,它用作退出函数的指令。

很有帮助

hollowaykeanho:

foo 函数的参数接收一个整数 y。当 main 调用 foo(x) 时,它会跳转到 foo 函数。由于输入是 x,当执行进入 foo 函数时,它作为 y 被传入。这被称为“按值传递”。

需要学习。我的大脑在抗拒,但我会让它安静下来!

hollowaykeanho:

“当 main 调用 foo(x) 时,它会跳转到 foo 函数。”……这些话真的很有帮助!

hollowaykeanho:

由于输入是 x,当执行进入 foo 函数时,它作为 y 被传入。这被称为“按值传递”。

谢谢!!!

由于以下所有项都用括号标记:(接收者)、(参数)和(返回值);当我看到函数中的这些括号时,如何区分它们,或者知道哪个是哪个?

这是由其书写方式决定的规则,并且第一个单词 func 表明该行是一个函数。因此,一个函数必须包含:

func (receiver)* name(parameters) (return)* {
    ...
}

* = 可选

参数是否总是出现在标识符之后?

是的。这是一个规则,就像拇指总是朝向你的胸部;小指总是远离身体。

返回值,当它们存在时(因为在我看来我见过没有返回值的函数)

返回值是可选的。如果存在,return 关键字必须出现在代码内部以返回输出。否则,它用作退出函数的指令。

这个概念:它被赋值给 y

foo 函数的参数接收一个整数 y。当 main 函数调用 foo(x) 时,它会跳转到 foo 函数。由于输入是 x,当执行进入 foo 函数时,它作为 y 被传入。这被称为“按值传递”。

hollowaykeanho:

mt.Sprintf

显式参数索引:

PrintfSprintfFprintf 中,默认行为是每个格式化动词依次格式化调用中传入的参数。然而,紧接在动词之前的 [n] 符号表示要格式化的是第 n 个(索引从 1 开始)参数。在表示宽度或精度的 '*' 之前使用相同的符号,可以选择持有该值的参数索引。在处理完带括号的表达式 [n] 之后,除非另有指定,后续的动词将使用参数 n+1、n+2 等。

例如:

fmt.Sprintf("%[2]d %[1]d\n", 11, 22)

紧接在动词之前的符号 [n]

之前? fmt. ?因为我看到 Sprintf 前面是 fmt.

Sprintf 是动词吗?

或者可能是 %? 还是 d

cherilexvold1974:

Sprintf 是动词吗? 或者是 %?还是 d?

哇,这也是一种解释方式。

我认为它指的是后续的参数。所以对于 PrintfSprintfFprintfErrorf 或任何以 f 结尾的打印输出函数,你可以指定字符串的格式(格式化后续的参数?)。然后,其余的参数是可选的。为了解释清楚,我仍然更喜欢简单的版本:fmt package - fmt - Go Packages

%??? 实际上被称为动词。使用 *****f 的过程被称为 字符串格式化。有许多类型的动词可供使用,如 Go by Example: String Formatting 所示。

动词的位置对应于后续的参数。这意味着,例如,namemoney 根据动词的位置进行填充。

                  name                    money
                   🡣                       🡣
fmt.Printf("DEBUG: %s, the return value is %d.\n", name, money)

不过要小心,如果你正在处理时间敏感的项目,不加仔细考虑就动态使用字符串格式化(特别是使用 %v 或 Go 特有的 %#v)可能会产生影响。否则,为了调试方便,请随意使用它。

hollowaykeanho:

fmt.Sprintf

我不知道那是什么。我该如何自己发现它?

我有可能自己运行这个吗?怎么做?

hollowaykeanho:

package main import ( 
"fmt" 
) 
func liner(functionName string, format string, a ...interface{}) {
 info := fmt.Sprintf(format, a...) 
fmt.Printf("[%s] %v\n", functionName, info)
 } 
func main() { 
liner("main", "debug line before define x")
 x := 2 
liner("main", "debug line before calling foo, x=%v", x) 
foo(x)
 liner("main", "debug line after foo, x=%v", x)
 fmt.Println(x) 
liner("main", "debug line after foo, after print, x=%v", x)
 } 

func foo(y int) { 
liner("foo", "debug line enters foo. y=%v", y)
 fmt.Println(y)
liner("foo", "debug line, after print y, y=%v", y)
	y = 43
	liner("foo", "debug line, after set y, y=%v", y)
	fmt.Println(y)
	liner("foo", "debug line, end foo, y=%v", y)
}

cherilexvold1974:

func liner(functionName string, format string, a …interface{}) {
info := fmt.Sprintf(format, a…)
fmt.Printf(“[%s] %v\n”, functionName, info)
}

你能逐段解释一下这个吗?

当然可以。

这个函数的输入部分:functionName 顾名思义,就是一个名字。至于 formata,它们就是我们所说的可变参数函数的参数,你可以向 a 中放入任意多个参数而无需修改代码。这里的 a 是一个切片。

其目的基本上是为了打印一条语句,就像你的 fmt.Printf(...) 一样。唯一的区别在于我添加了函数名前缀。

函数首先使用 fmt.Sprintf(...) 处理消息(formata),它会将结果输出为一个字符串(保存到 info 中),而不是直接打印到控制台。然后,我使用 fmt.Printf(...)functionName 作为前缀添加到消息前面。

有了这个新的 liner(...) 打印函数,我就可以轻松地在任何地方打印语句了。

cherilexvold1974:

liner(“main”, “debug line before define x”)

请同样解释一下这个。

那么当我调用 liner(...) 时,我手动提供了函数名(因为显然这可能会使代码复杂化),以及我想要打印的消息。所以,我传入了:

  1. main,它将传递给 functionName 变量。
  2. debug line before define x,它将传递给 format
  3. 由于我没有第3个变量,可变参数 a 是空的。

cherilexvold1974:

需要学习。我的大脑在抗拒,但我会让它安静下来!

一种简单的理解方法是在代码行前后注入调试信息。考虑以下修改后的代码:

package main

import (
	"fmt"
)

func liner(functionName string, format string, a ...interface{}) {
	info := fmt.Sprintf(format, a...)
	fmt.Printf("[%s] %v\n", functionName, info)
}

func main() {
	liner("main", "debug line before define x")
	x := 2
	liner("main", "debug line before calling foo, x=%v", x)
	foo(x)
	liner("main", "debug line after foo, x=%v", x)
	fmt.Println(x)
	liner("main", "debug line after foo, after print, x=%v", x)
}

func foo(y int) {
	liner("foo", "debug line enters foo. y=%v", y)
	fmt.Println(y)
	liner("foo", "debug line, after print y, y=%v", y)
	y = 43
	liner("foo", "debug line, after set y, y=%v", y)
	fmt.Println(y)
	liner("foo", "debug line, end foo, y=%v", y)
}

输出中混入了 liner 函数的调试信息:

[main] debug line before define x
[main] debug line before calling foo, x=2
[foo] debug line enters foo. y=2
2
[foo] debug line, after print y, y=2
[foo] debug line, after set y, y=43
43
[foo] debug line, end foo, y=43
[main] debug line after foo, x=2
2
[main] debug line after foo, after print, x=2

main 函数由 [main] 标签指示,而 foo 函数由 [foo] 标签指示。

请注意,在 CPU 进入函数后,[foo] 标签的第一行中,y 的值与 x 相同,而不是 0。这就是你的讲师所说的意思,值是从 x 传递给了 y

由于“按值传递”的特性,当 [foo] 退出后 [main] 恢复执行时,x 仍然是 2,而不是 43,就像你在 foo 函数内部修改的那样。这本质上意味着函数将值克隆到了 y 中,而不是直接修改 x

注意:

你只能在处理非并发代码时进行这个 liner 实验。否则,它是没有意义且难以理解的。对于并发场景,你需要使用不同的工具。

在Go语言中,函数声明的语法结构确实使用了括号来包裹不同的部分,但每个部分都有明确的语法规则和上下文含义。以下是详细的区分方法:

1. (receiver) - 方法接收器

  • 位置:紧接在func关键字之后
  • 作用:定义方法所属的类型(将函数绑定到特定类型)
  • 语法(variableName Type)(variableName *Type)
  • 特点:只能有一个参数,且必须指定类型

示例:

type Circle struct {
    Radius float64
}

// (c Circle) 就是接收器
func (c Circle) Area() float64 {
    return 3.14 * c.Radius * c.Radius
}

// 指针接收器
func (c *Circle) Scale(factor float64) {
    c.Radius *= factor
}

2. (parameters) - 函数参数

  • 位置:在函数名之后,返回值之前
  • 作用:定义函数调用时需要传入的参数
  • 语法(param1 Type1, param2 Type2, ...)()
  • 特点:可以有零个或多个参数,多个参数用逗号分隔

示例:

// 无参数
func SayHello() {
    fmt.Println("Hello")
}

// 单个参数
func Double(x int) int {
    return x * 2
}

// 多个参数
func Add(a, b int) int {
    return a + b
}

// 可变参数
func Sum(numbers ...int) int {
    total := 0
    for _, num := range numbers {
        total += num
    }
    return total
}

3. (returns) - 返回值

  • 位置:在参数列表之后,函数体之前
  • 作用:定义函数返回值的类型
  • 语法(returnType)(type1, type2, ...)()
  • 特点:可以返回零个、一个或多个值

示例:

// 无返回值
func PrintMessage(msg string) {
    fmt.Println(msg)
}

// 单个返回值
func Square(x int) int {
    return x * x
}

// 多个返回值
func Divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    return a / b, nil
}

// 命名返回值
func GetCoordinates() (x, y int) {
    x = 10
    y = 20
    return // 自动返回x和y
}

完整示例展示所有部分:

package main

import "fmt"

type Calculator struct {
    value int
}

// 完整的方法声明:
// func (receiver) identifier(parameters) (returns) { code }
func (c *Calculator) AddAndMultiply(x, y int) (sum, product int) {
    sum = x + y
    product = x * y
    c.value = sum
    return // 返回sum和product
}

func main() {
    calc := &Calculator{}
    s, p := calc.AddAndMultiply(3, 4)
    fmt.Printf("Sum: %d, Product: %d\n", s, p) // Sum: 7, Product: 12
}

关键区分点:

  1. 接收器总是第一个括号,紧接在func之后
  2. 参数在函数名之后
  3. 返回值在参数之后,函数体之前
  4. 接收器只能有一个参数(变量名+类型),而参数列表可以有多个
  5. 如果函数不是方法(不绑定到类型),则没有接收器部分

通过位置和上下文,可以明确区分这三个部分。接收器将普通函数转换为方法,参数定义输入,返回值定义输出。

回到顶部