Skip to content

第二章 函数与结构体 v1.0

第一部分 函数

一、基本形式

go
func 函数名(参数列表) 返回值类型{
  函数体
  return 返回值
}
go
func 函数名(参数列表) (返回值类型列表){
  函数体
  return 返回值列表
}  //一般业务返回值有两个,第二个是错误信息

二、指针

指针在Go中可以用于修改传入的参数值

go
func 函数名(*参数名) 返回值类型{
  函数体
  return 返回值
}  //用指针可修改传入参数值

三、函数处理

go
package main

import "fmt"

func add(nums ...int) int {
    sum := 0
    for _, num := range nums {
        sum += num
    }
    return sum
}

func main() {
    fmt.Println(add(1, 2))         // 输出: 3
    fmt.Println(add(1, 2, 3, 4))   // 输出: 10
}
go
package main

import "fmt"

func add(a interface{}, b interface{}) interface{} {
    switch a.(type) {
    case int:
        return a.(int) + b.(int)
    case float64:
        return a.(float64) + b.(float64)
    default:
        return nil
    }
}

func main() {
    fmt.Println(add(1, 2))         // 输出: 3
    fmt.Println(add(1.1, 2.2))     // 输出: 3.3
}

四、接口

Go语言中,接口是一种抽象类型,定义一组方法,用于实现多态和解耦合。

1.定义

go
type 接口名 interface {
   方法名1() 方法返回值1
   方法名2() 方法返回值2
}
go
type Speaker interface {
    Speak() string
}

2.实现接口

Go语言中没有显式的implements关键字,接口的实现是隐式的,任何类型只要实现了接口中定义的所有方法,就被认为实现了该接口

go
type Person struct {
    Name string
}

func (p Person) Speak() string {
    return "Hello, my name is " + p.Name
}

3.使用接口

接口类型变量可以存储任何实现了该接口的值,以此实现多态与解耦

go
func Greet(s Speaker) {
    fmt.Println(s.Speak())
}

func main() {
    p := Person{Name: "Alice"}
    Greet(p) // 输出: Hello, my name is Alice
}

4.空接口

Go语言中,空接口interface{}是一个特殊的接口,它没有定义任何方法,因此所有类型都实现了空接口,常用于存储任意类型的值

go
func PrintAnything(v interface{}) {  // 该函数接收任意类型变量并将变量值输出到标准输出
    fmt.Println(v)
}

func main() {
    PrintAnything(42)        // 输出: 42
    PrintAnything("Hello")   // 输出: Hello
    PrintAnything(3.14)      // 输出: 3.14
}

5.类型断言

类型断言能够将接口类型的变量转换为具体类型

go
value, ok := 待转换变量.(待转换类型)  // ok 为 boolean类型值,表示是否断言转换成功
go
func main() {
    var i interface{} = "Hello"
    
    s, ok := i.(string)
    if ok {
        fmt.Println(s) // 输出: Hello
    } else {
        fmt.Println("Type assertion failed")
    }
}

6.类型选择

一种特殊的 switch 语句,用于根据接口变量的具体类型执行不同的分支

go
switch 变量标识 := 变量值.(type) {
case 变量类型1:  // 变量值属于 变量类型1,执行操作1
   操作1
...
default:       // 不属于以上所有类型,执行操作n
   操作n
}
go
func TypeSwitchDemo(i interface{}) {
    switch v := i.(type) {
    case int:
        fmt.Println("Integer:", v)
    case string:
        fmt.Println("String:", v)
    default:
        fmt.Println("Unknown type")
    }
}

func main() {
    TypeSwitchDemo(42)       // 输出: Integer: 42
    TypeSwitchDemo("Hello")  // 输出: String: Hello
    TypeSwitchDemo(3.14)     // 输出: Unknown type
}

第二部分 结构体

一、结构体的定义与使用

1.结构体定义

go
type 结构体名 struct{
  成员变量名1  成员变量数据类型1 [`json:"json对应变量名"`]  // 添加结构体标签,常用于json解析
  成员变量名2  成员变量数据类型2
  ...
}

2.结构体使用

go
变量名 := 结构体名{成员变量名1:变量值1, 成员变量名2:变量值2, ...}  //显式指定赋值
变量名 := 结构体名{变量值1, 变量值2, ...}  //按照顺序依次赋值给各成员变量
变量名 := new(结构体名)  // 不指定初始值,使用new关键字零值实例化
var 变量名 结构体名  // 不指定初始值,使用var关键字零值实例化
go
结构体变量名.成员变量名  // 访问结构体实例字段
结构体变量名.成员变量名 = 新值  // 修改结构体实例字段

结构体成员变量不能直接指定默认值,默认值是由Go语言的零值机制决定的。
变量没有被显式初始化时,Go语言会自动为其赋予一个默认值,例如:

  • 数字类型(如int、float)的零值是0。
  • 布尔类型的零值是false。
  • 字符串类型的零值是空字符串""。
  • 指针、切片、映射、通道、接口的零值是nil。

3.匿名字段

Go语言支持匿名字段(嵌入字段),允许将一个结构体嵌入到另一个结构体中

go
type Address struct {
    City    string
    ZipCode string
}

type Person struct {
    Name string
    Age  int
    Address
}

func main() {
    p := Person{
        Name: "Alice",
        Age:  30,
        Address: Address{
            City:    "New York",
            ZipCode: "10001",
        },
    }
    fmt.Println(p.City) // 直接访问嵌入的字段
}

二、结构体方法

go语言中没有像类的概念,但是可以通过结构体struct实现面向对象编程。使用结构体方法使结构体可以做到类似于其他语言的类的形式,使结构体可以调用其成员方法。

1.结构体方法

go
func (结构体变量名 结构体名) 成员方法名(参数列表) 返回值类型{
    函数体
}
go
type Config struct {
    Host string
    Port int
}

func (c *Config) SetDefaults() {
    if c.Host == "" {
        c.Host = "localhost"
    }
    if c.Port == 0 {
        c.Port = 8080
    }
}

2.构造方法

为结构体成员变量指定默认值,可以通过定义一个构造函数来实现

go
func 构造函数名(参数列表) *结构体类型名{
    return &结构体类型名{
        参数名: 初始值,
        ...
    }
}
go
type Config struct {
    Host string
    Port int
}

func NewConfig(host string, port int) *Config {
    return &Config{
        Host: host,
        Port: port,
    }
}

func main() {
    // 使用构造函数创建结构体实例
    config := NewConfig("example.com", 9090)
    fmt.Printf("Host: %s, Port: %d\n", config.Host, config.Port)  # 输出:Host: example.com, Port: 9090
}
go
type Person struct {
    Name string
    Age  int
}

type Option func(*Person)  # 定义函数类型Option

func WithName(name string) Option {
    return func(p *Person) {
        p.Name = name
    }
}

func WithAge(age int) Option {
    return func(p *Person) {
        p.Age = age
    }
}

func NewPerson(opts ...Option) *Person {
    person := &Person{
        Name: "Unknown", // 默认值
        Age:  0,         // 默认值
    }

    for _, opt := range opts {
        opt(person)
    }

    return person
}

func main() {
    p1 := NewPerson(WithName("Alice"), WithAge(30))
    p2 := NewPerson(WithName("Bob"))
    p3 := NewPerson(WithAge(25))

    fmt.Println(p1) // 输出: &{Alice 30}
    fmt.Println(p2) // 输出: &{Bob 0}
    fmt.Println(p3) // 输出: &{Unknown 25}
}

第三部分 泛型

Go 1.18 版本引入的重要特性,它允许开发者在定义函数、类型或方法时使用类型参数,而不必指定具体类型,从而实现更通用、可复用的代码

一、核心概念

1.类型参数 (Type Parameters)

类型参数是泛型定义中的占位符,用于表示未来将被具体类型替换的位置

泛型定义中用占位符表示 “待确定的类型”,例如T、K、V,使用时需指定具体类型。

go
// 格式
[占位符 约束]

2.类型约束 (Type Constraints)

类型约束定义了类型参数可以接受的类型集合,确保泛型代码能够安全地操作传入的类型

go
any         // 等价于 interface{}, 表示任意类型,是最宽松的约束
comparable  // 表示支持 == 和 != 操作的可比较类型
go
// 通过接口类型定义自定义约束
type Integer interface {
    int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 | uintptr
}

type Number interface {
    ~int | ~int64 | ~float64 // ~表示“底层类型为该类型”(如自定义type MyInt int也符合)
}

3.实例化

使用泛型时,将类型参数替换为具体类型的过程,实例化时需要指定具体类型

go
// 实例化泛型类型
TypeName[InstanceType]

二、泛型函数

泛型函数是可以接受一个或多个类型参数的函数,能够处理符合类型约束的多种数据类型

go
func FunctionName[T TypeConstraint](param T) T {
    // 函数体
}
go
// 定义一个泛型打印函数,可打印任意类型的切片
func PrintSlice[T any](s []T) {
    for _, v := range s {
        fmt.Printf("%v ", v)
    }
    fmt.Println()
}

// 定义一个泛型查找函数,查找元素在切片中的索引
func FindIndex[T comparable](s []T, target T) int {
    for i, v := range s {
        if v == target {
            return i
        }
    }
    return -1
}

三、泛型类型与泛型方法

1.泛型类型

泛型类型是指在定义结构体、切片、映射、通道等类型时,引入类型参数,使该类型能适配多种数据类型

go
// 类型定义时使用泛型
type TypeName[T TypeConstraint] struct {
    // 结构字段定义
}

2.泛型方法

泛型方法,即带类型参数的方法,可具体分为两种情况:

泛型类型的关联方法

方法的接收者是泛型类型,方法的类型参数与接收者一致,无需额外声明

go
// 首先需要定义泛型结构体
type TypeName[T TypeConstraint] struct {
    // 结构字段定义
}

// 再定义泛型方法
func (t *TypeName[T]) FunctionName(item T) {
    // 方法实现
}
go
// 定义泛型结构体(队列)
// T为类型参数,any表示任意类型均可作为队列元素
type Queue[T any] struct {
    elements []T // 存储T类型的元素
}

// 入队
// 接收者为*Queue[T],与泛型结构体的类型参数一致
func (q *Queue[T]) Enqueue(item T) {
    q.elements = append(q.elements, item)
}

// 出队
// 接收者为*Queue[T],返回队首元素和是否成功
func (q *Queue[T]) Dequeue() (T, bool) {
    if len(q.elements) == 0 {
        var zero T // 泛型类型的零值(如int为0,string为空串)
        return zero, false
    }
    // 取出队首元素(切片第一个元素)
    item := q.elements[0]
    q.elements = q.elements[1:] // 移除队首元素
    return item, true
}

// 获取队列长度
func (q *Queue[T]) Len() int {
    return len(q.elements)
}

独立类型参数的方法

方法自身声明独立的类型参数,接收者可以是普通类型(非泛型类型)

go
func (c *MyType) FunctionName[T any](t []T) {
    // 方法实现
}

四、泛型接口

泛型接口是指接口声明中包含类型参数,或接口的方法签名使用类型参数,使接口能适配不同类型的实现

go
// 接口定义时使用泛型
type interfaceName[T TypeConstraint] interface {
    // 接口方法定义
}