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}
}

第三部分 包与依赖

一、包

包(Package)是Go代码组织单位。每个Go程序都是由包构成的。程序运行的入口是main包。

go
package main

import (
    "fmt"
    "mypackage"
)

func main() {
    fmt.Println(mypackage.Hello())  # 输出 Hello
}
go
package mypackage

func Hello() string {
    return "Hello!"
}

二、依赖管理的三种方式

1.Go Path

a.简介

Go Path是一个环境变量,结构一般为:

  • bin:项目编译的二进制文件
  • pkg:项目编译的中间产物,用于加速编译
  • src:项目源码
b.原理

项目代码直接依赖于src下的代码,使用go get下载下载最新版本的包至src目录下

c.弊端

无法实现package的多版本控制

2.Go Vendor

a.依赖管理原理

在项目目录下添加vendor文件,所有依赖包副本形式放在$ProjectRoot/vender

依赖寻址方式:vender=>GOPATH

b.优点

通过每个项目引入一份依赖的副本,解决了需要同个package依赖的冲突问题

c.弊端

无法控制依赖的版本,更新项目可能会出现依赖冲突,导致编译出错

3.Go Module

a.简介

通过go.mod文件管理依赖包版本,通过go get/go mod指令工具管理依赖包

b.优点

可以定义版本规则和管理项目依赖关系

三、Go Module

1.依赖管理

  1. 配置文件,描述依赖:go.mod
  2. 中心仓库管理依赖库:Proxy
  3. 本地工具:go get/mod

GOPROXY是Go语言中的一个环境变量,用于指定Go模块代理服务器。

go
GOPROXY="https://proxy1.cn,https://proxy1.cn,direct"  //direct代表源站点
//查找顺序:proxy1->proxy2->Direct
bash
go env -w GO111MODULE=on    # 强制启用 Go Modules 功能
go env -w GOPROXY=https://goproxy.cn,direct  # 修改 GOPROXY

2.依赖配置

go
module example/project/app  //依赖管理基本单元

go 1.16  //原生库

require(
    example/lib v1.0.0
    ...  //标识依赖:[Module Path][Version/Pseudo-version]
)
  1. version
    • 语义化版本:${MAJOR}.${MINOR}.${PATCH}
      • MAJOR版本:大版本,不同MAJOR代码之间可能不兼容
      • MINOR版本:小版本,一般可能新增函数,需要做到至少向下兼容
      • PATCH版本:BUG修复版本,一般做一些代码Bug修复
    • 基于commit伪版本:vX.0.0-yymmddhhmmss-哈希码 //前面类似于语义化版本
  2. 定义关键字
    1. 间接依赖:在其后加//indirect作为关键字
    2. 对于主版本在2以上的模块会在模块路径后增加/vN后缀
    3. 对于没有go.mod文件并且主版本在2以上的的依赖,在后加+incompatible
    go
    require(
        example/lib4 v0.0.0-20180306012644-bacd9c7ef1dd  //indirect
        example/lib5/v3 v3.0.2
        example/lib6 v3.2.0+incompatible
    )

3.go工具

a.go get

使用指令:go get example.org/pkg [指令]

指令功能
@update默认
@none删除依赖
@v1.0.0下载指定tag版本,语义版本
@23dffdd5下载特定的commit版本
@master下载分支的最新commit
b.go mod

使用指令:go mod [指令]

指令功能
init初始化,创建go.mod文件
download下载模块到本地缓存
tidy增加需要的依赖,删除不需要的依赖

注:在实际开发过程中,尽量提交之前执行go tidy,减少构建时无效依赖包的拉取。

go mod需要开启使用,go env中默认:GO111MODULE=off,此时go命令行将不会支持module功能,将会沿用旧版本通过vendor目录或GOPATH模式来查找