自学内容网 自学内容网

go语言使用总结(持续更新)

整理后的内容如下:


1. 先了解函数签名,再了解传入参数以及调用

  • 函数签名是函数的声明部分,包括函数名、参数列表和返回值列表。理解函数签名是理解函数行为的第一步,尤其是在了解参数类型、参数数量和返回值类型等方面。
  • 通过了解函数签名,可以确定函数在调用时需要什么样的输入(参数类型和数量)以及函数会返回什么样的结果。

示例

func add(a int, b int) int {
    return a + b
}
  • 函数 add 的签名 func add(a int, b int) int 表示它接收两个 int 类型的参数并返回一个 int 类型的结果。
  • 在使用一个函数前,了解其签名有助于确定如何传递参数和如何处理返回值。

2. 传入参数是某种类型,只要有变量实现了这个类型,那么这个变量就可以作为参数传入

  • 在 Go 语言中,如果一个参数类型是接口类型,只要一个变量实现了该接口中的方法,那么该变量就可以作为这个接口类型的参数传入。
  • 这就是 Go 中的接口的实现方式:只要类型实现了接口的所有方法,那么这个类型自动满足该接口,无需显式声明实现。

示例

type Speaker interface {
    Speak() string
}

type Dog struct{}

func (d Dog) Speak() string {
    return "Woof!"
}

func MakeNoise(s Speaker) {
    fmt.Println(s.Speak())
}

func main() {
    d := Dog{}
    MakeNoise(d)  // d 是 Dog 类型,但它实现了 Speak 方法,因此满足 Speaker 接口
}
  • Dog 类型实现了 Speaker 接口的方法 Speak,因此可以作为 Speaker 类型的参数传入 MakeNoise 函数。

3. 构造函数返回值一般都是指针类型有什么问题吗

  • 在 Go 中,构造函数返回指针类型(如 *Type)是常见的设计模式。返回指针类型的构造函数有几个优点:
    • 效率:对于大型结构体,返回指针避免了结构体数据的拷贝,提升了效率。
    • 易于修改:返回指针类型允许调用者对返回对象进行修改,而不会影响原有的对象。
    • 方法调用:如果方法是定义在指针类型上(如 func (t *Type) Method()),那么必须返回一个指针,才能调用这些方法。

可能的问题

  • 内存管理:返回指针会在堆上分配内存,可能增加垃圾回收的负担。因此对于小型的不可变结构体,直接返回值类型更合适。
  • 不可变性:指针类型的返回值可以被外部修改,可能导致不安全的状态变更。如果不希望对象被外部修改,返回值类型会更合适。

示例

type Config struct {
    URL string
}

func NewConfig(url string) *Config {
    return &Config{URL: url}  // 返回指针类型
}

func main() {
    config := NewConfig("http://localhost")
    fmt.Println(config.URL)  // 直接使用指针调用字段
}

在 Go 中,返回指针类型通常用于允许共享对象状态,但根据实际需求,可以选择返回值类型或指针类型。


4. 使用 Go Modules 的注意点

在使用 Go Modules 时,如果 main.gogeecache/ 在同级目录下,不能再使用 import 相对路径进行模块引用。为了在 Go Modules 中正确引入本地模块,可以在 go.mod 中声明模块替换:

require geecache v0.0.0
replace geecache => ./geecache

通过 replace 指令将 geecache 映射为本地路径 ./geecache,可以在项目中以模块方式正确引用本地包。

这是 Go 语言中的接口实现语法,具体而言,它利用了 隐式接口实现 的特性。

5. 隐式接口实现

在 Go 语言中,如果一个类型(例如 ByteView)实现了某个接口(例如 Value)的所有方法,那么它就自动满足该接口,无需显式声明“实现了”这个接口。这种机制称为 隐式接口实现。因此,在 Go 中,接口是通过结构体的方法集合来判断是否满足接口的,而不是通过显式声明。

示例解释
type Value interface {
    Len() int
}

type ByteView struct {
    b []byte
}

func (v ByteView) Len() int {
    return len(v.b)
}

在上面的代码中:

  • Value 接口定义了一个 Len() 方法。
  • ByteView 类型实现了 Len() 方法,因此满足了 Value 接口的要求。
  • Go 编译器会自动将 ByteView 视为 Value 类型,因为它实现了接口所要求的全部方法。
接口变量的多态性

因为 ByteView 实现了 Value 接口,所以可以将 ByteView 的实例赋值给一个 Value 类型的变量。这种用法允许 Go 语言的接口具有多态性

var v Value
v = ByteView{b: []byte("Hello, World!")}
fmt.Println(v.Len())  // 输出: 13

在这里:

  • v 是一个 Value 类型的接口变量,可以接受任何满足 Value 接口的类型。
  • ByteView 实现了 Len() 方法,因此可以赋值给 v
  • 调用 v.Len() 时,实际上会调用 ByteView.Len() 方法。
接口解耦

Go 的接口实现方式使得代码的模块化和解耦更加灵活:

  • Cache 可以依赖 Value 接口,而不是依赖具体的 ByteView 类型。
  • 这样一来,只要其他类型实现了 Len() 方法,也可以被 Cache 使用,具有更好的扩展性。

6. 函数式接口作用

函数式接口在 Go 中的意义主要体现在简化代码结构增强代码灵活性提高复用性方面。Go 语言中的函数式接口通常是通过类型别名实现的,允许将函数作为参数传递,使得代码更具模块化和扩展性。以下是函数式接口的核心意义和应用场景:

提高代码的灵活性

  • 函数式接口使得代码可以接受行为(函数)作为参数,这样可以根据上下文动态地改变函数的行为,而不需要创建大量的具体实现。
  • 例如,假设有一个缓存系统,需要从数据源加载数据,可以通过函数式接口将数据源的加载逻辑作为参数传入,从而实现不同数据源的适配。

示例

type LoaderFunc func(key string) ([]byte, error)

func LoadData(loader LoaderFunc, key string) ([]byte, error) {
    return loader(key)
}

在使用时,可以传入不同的 LoaderFunc 实现,使 LoadData 能够适配不同的加载逻辑。

减少冗余代码

  • 函数式接口避免了为简单的功能创建单独的结构体和实现方法。对于那些功能性较强但实现相对简单的行为,函数式接口能让代码更加简洁。
  • 在没有函数式接口的情况下,开发者可能需要为每个行为编写一个结构体实现接口,但函数式接口可以避免这种代码膨胀。

增强代码的复用性

  • 使用函数式接口,可以将通用的逻辑和策略抽象出来,赋予代码更高的复用性。例如,数据处理、过滤器、回调处理等场景都可以利用函数式接口,使通用的逻辑可以在不同场景下复用。
  • 例如,可以定义一个泛型的排序逻辑,接受一个比较函数,实现不同的排序策略。

示例

type CompareFunc func(a, b int) bool

func Sort(data []int, compare CompareFunc) {
    // 使用 compare 函数进行排序
}

简化依赖注入和测试

  • 函数式接口使得依赖注入更为简便,例如在测试时,可以传入特定的函数来替换真实的服务。这在需要模拟特定行为、简化测试环境设置时特别有用。
  • 例如,可以用一个简单的函数替换数据库查询,使测试代码不依赖于实际的数据库连接。

示例

func MockLoader(key string) ([]byte, error) {
    return []byte("mock data"), nil
}

data, _ := LoadData(MockLoader, "test-key")

原文地址:https://blog.csdn.net/weixin_51147313/article/details/143646976

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!