函数

1. 结构体返回值

在Go语言中,函数返回结构体时,接收它的变量类型可以是值类型指针类型

1.1. 使用值类型接收值类型返回值

  • 结构体较小(如 2-3 个字段的结构体),直接复制不会有明显性能影响。
  • 结构体的数据不需要在外部修改,返回值是独立的副本。
package main

import (
    "fmt"
    "log"
)

type Person struct {
    Name string
    Age  int
}

func NewPerson() Person {
    p := Person{Name: "Alice", Age: 25}
    log.Printf("函数内结构体地址, %p", &p)
    return p
}

func main() {
    p := NewPerson()
    log.Printf("函数返回值结构体地址, %p", &p)
    fmt.Println(p.Name, p.Age)
}

输出如下

2025/03/02 07:30:45 函数内结构体地址, 0xc000010030
2025/03/02 07:30:45 函数返回值结构体地址, 0xc000010018
Alice 25

1.2. 使用指针类型接收指针类型返回值

  • 结构体较大(包含多个字段,或者嵌套结构体),使用指针避免值拷贝的开销。
  • 需要修改结构体的字段,指针允许直接修改原始对象,而不需要显式返回新的值。
package main

import (
    "fmt"
    "log"
)

type Person struct {
    Name string
    Age  int
}

func NewPerson() *Person {
    p := Person{Name: "Alice", Age: 25}
    log.Printf("函数内结构体地址, %p", &p)
    return &p
}

func main() {
    p := NewPerson()
    log.Printf("函数返回值结构体地址, %p", p)
    fmt.Println(p.Name, p.Age)
}

输出如下

2025/03/02 07:32:30 函数内结构体地址, 0xc000010018
2025/03/02 07:32:30 函数返回值结构体地址, 0xc000010018
Alice 25

2. 函数入参

2.1. 接口类型参数传递

接口本身就是引用类型,接口变量在内部实现上已经包含了一个指向实际数据的指针和一个指向方法表的指针(通常称为iface结构,包含tab和data两个指针)。接口传递是浅复制:当你传递一个接口变量时,只是复制了这两个指针,而不是底层的数据结构,所以传递成本很低。一个典型的使用场景是对context.Context参数的传递。

package main

import (
    "fmt"
)

// Shape 接口是一种抽象类型,它定义了一组方法签名,但不包含实现代码
type Shape interface {
    Area() float64
    Perimeter() float64
}

// Rectangle 实现了接口中声明的所有方法,那么它就被认为实现了该接口
type Rectangle struct {
    Width, Height float64
}

func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

func (r Rectangle) Perimeter() float64 {
    return 2 * (r.Width + r.Height)
}

func main() {
    rect := Rectangle{5.0, 4.0}
    print(rect)
}

func print(s Shape) {
    fmt.Println("Area: ", s.Area())
    fmt.Println("Perimeter: ", s.Perimeter())
}

输出如下:

Area:  20
Perimeter:  18

在Go语言里,函数参数在声明时会指定参数的类型,有时候可以不明确给出参数变量名,在接口定义、参数忽略和代码简洁性方面有重要作用。

2.2. 参数类型声明(共用类型)

在Go语言中,当函数的多个相邻参数具有相同类型时,可以只在最后一个参数后面声明类型,前面的参数就不用重复声明该类型了。也就是说,length, width float64这种写法等同于length float64, width float64

2.3. 参数变量声明

2.3.1. 方法占位

在 Go 语言中,当你定义一个接口时,接口中的方法可以只声明参数类型而不声明变量名。接口描述的是一组方法的签名,重点在于方法的输入输出类型,而不是具体的参数名。这有助于抽象出通用的行为,让不同的类型可以根据这些方法签名来实现接口。
示例代码

package main

import "fmt"

// Shape 是一个接口,定义了计算面积和周长的方法
type Shape interface {
    Area(float64, float64) float64
    Perimeter(float64, float64) float64
}

// Rectangle 是一个结构体,表示矩形
type Rectangle struct{}

// Area 实现了 Shape 接口的 Area 方法
func (r Rectangle) Area(length, width float64) float64 {
    return length * width
}

// Perimeter 实现了 Shape 接口的 Perimeter 方法
func (r Rectangle) Perimeter(length, width float64) float64 {
    return 2 * (length + width)
}

func main() {
    var s Shape = Rectangle{}
    fmt.Println("矩形面积:", s.Area(5, 3))
    fmt.Println("矩形周长:", s.Perimeter(5, 3))
}

在 Shape 接口中,Area 和 Perimeter 方法只声明了参数类型,没有指定参数名。这样做的目的是定义一个通用的方法签名,任何实现 Shape 接口的类型都需要按照这个签名来实现相应的方法。

2.3.2. 忽略参数

在某些情况下,函数可能接收了一些参数,但在函数内部并不需要使用这些参数。此时可以不声明参数变量名,以表明这些参数在函数实现中被忽略。

package main

import "fmt"

// 函数接收三个参数,但只使用了其中一个
func exampleFunc(int, str string, _ bool) {
    fmt.Println("接收到的字符串参数:", str)
}

func main() {
    exampleFunc(10, "hello", true)
}

在exampleFunc函数中,第一个参数只声明了类型 int,没有变量名,表示该参数在函数内部不会被使用。第三个参数使用了下划线 _ 作为变量名,这是 Go 语言中用于忽略变量的惯例

2.3.3. 代码简洁性

在一些简单的匿名函数或者测试代码中,不声明参数变量名可以让代码更加简洁,突出函数的主要逻辑。

package main

import "fmt"
func main() {
    // 匿名函数,不声明参数变量名
    add := func(int, int) int {
        return 1 + 2
    }
    result := add(10, 20)
    fmt.Println("计算结果:", result)
}

在这个匿名函数中,不声明参数变量名,直接实现了一个简单的加法逻辑。这样代码更加简洁,适用于一些临时的、简单的函数实现。