Go语言高级编程 二

函数、方法和接口

go函数有具名函数和匿名函数,具名函数一般对应于包级别的函数,是匿名函数的特例,匿名函数引用了外部作用域中的变量就成了闭包函数。
方法是绑定到具体类型的特殊函数
接口定义了方法的集合,接口对应的方法是在运行时动态绑定的。
go初始化顺序,从main.main函数开始,先导出包,然后创建和初始化整个包的变量和常量,在调用init方法(有多个的情况,按顺序调用),最后进入main.main 函数。
ps:在main.main函数执行之前所有代码都运行在同一个goroutine,也就是程序的主系统线程中。如果init中启动了新的gooutiune,需要等到进入main.main 之后才会被执行到。

函数

// 具名函数
func Add(a, b int) int {
    return a+b
}

// 匿名函数
var Add = func(a, b int) int {
    return a+b
}

go 函数支持可变数量的参数,可变数量的参数必须是最后出现的参数,可变数量的参数其实是一个切片类型的参数。

func Inc() (v int) {
    defer func(){ v++ } ()
    return 42
}

defer 语句延迟执行了一个匿名函数,这个匿名函数捕获了外部函数的局部变量v,一般叫闭包
闭包对捕获的外部变量不是传值方式访问的,而是以引用的方式访问的。

任何可以通过函数参数修改调用参数的情形,都是因为函数参数显式或隐式传入了指针参数
函数参数传值是只针对数据结构中固定的部分传值的,eg:字符串或者切片对应结构体中的指针或字符串长度结构体传入,并不包含指针中间接指向的内容。
切片中的底层数组部分是通过隐式指针传递(指针本身依然是传值的,但是指针指向的却是同一份数据),所以被调用的函数时可以通过指针修改掉调用参数切片中的数据。因为除了数据外,切片结构还包括了切片长度和切片容量信息,这两个也是传值的,如果被调用函数修改的话,就无法反映到调用参数的切片中,一般会通过返回修改后的切片来更新之前的切片。
go语言函数中递归调用深度逻辑是没有限制的,函数调用的栈不会出现溢出错误的,go语言运行的时候会根据需要动态调整函数栈的大小。
每一个goroutine 启动的时候,会分配很小的栈(4或8kb),根据需求动态调整栈的大小,可以达到GB级别。
Go语言中指针不再是固定不变的了(因此不能随意将指针保持到数值变量中,Go语言的地址也不能随意保存到不在GC控制的环境中,因此使用CGO时不能在C语言中长期持有Go语言对象的地址)

方法

方法是面向对象编程的一个特性,go语言的方法是关联到类型的,可以在编译阶段完成方法的静态绑定。
go中每种类型对应的方法和类型的定义都必须在同一个包中
对于给定的类型,每一个方法的名字都必须是唯一的,同时方法和函数一样不支持重载
方法是由函数演变而来,只是将函数的第一个对象参数移动到了函数名。

// 关闭文件
func (f *File) Close() error {
    // ...
}

// 读文件数据
func (f *File) Read(int64 offset, data []byte) int {
    // ...
}

go 语言中,通过在结构体内置匿名函数来实现继承。
通过嵌入匿名的成员,可以实现继承匿名成员的内部成员,也可以继承匿名成员类型所对应的方法。但是不支持多态特性,所有继承来的方法的接收者参数都是匿名成员本身,不是当前的变量。
eg:

type Cache struct {
    m map[string]string
    sync.Mutex
}

func (p *Cache) Lookup(key string) string {
    p.Lock()
    defer p.Unlock()

    return p.m[key]
}

Cache结构体类型通过内嵌 sync.Mutex来继承 Lock和Unlock方法。调用的时候,p.Lock()和p.Unlock() ,p不是真正的接收者,而是将它们展开为p.Mutex.Lock()和p.Mutex.Unlock()调用,在编译期完成,没有运行时代价。
go语言通过嵌入匿名函数的方式来继承基类方法,且在编译时静态绑定,如果需要函数的多态性,需要用接口来实现。

接口

go的接口类型不会和特定的实现细节绑定在一起,可以让对象更加灵活和更有适应能力。
Go语言的接口类型是延迟绑定,可以实现类似虚函数的多态功能
go语言中基础类型(非接口类型)不支持隐式的转换,无法将一个int类型的值赋值给int64类型的变量,也无法将int类型的值赋值给底层是int类型的新定义命名类型的变量。
但是对象和接口之间的转换、接口和接口之间的转换都可能是隐式的转换。

package main

import (
    "fmt"
    "testing"
)

type TB struct {
    testing.TB
}

func (p *TB) Fatal(args ...interface{}) {
    fmt.Println("TB.Fatal disabled!")
}

func main() {
    var tb testing.TB = new(TB)
    tb.Fatal("Hello, playground")
}

可以通过嵌入匿名接口或匿名指针对象来实现继承是一种纯虚继承,继承的只是接口指定的规范,真正的实现在运行时才被注入。

参考:https://books.studygolang.com/advanced-go-programming-book/ch1-basic/ch1-04-func-method-interface.html

posted @   lars_huan  阅读(6)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示