当前位置:   article > 正文

Go语言高级特性_go高级特性

go高级特性

高级特性

  • 并发编程
  • 基于接口类型的轻量级对象
  • 内存管理
  • defer
  • channel
  • panic&recover

Go语言的Context

什么是Context

  • 协程的上下文
  • Go语言1.7版本中引入的标准库的接口
  • 传递跟踪、取消信号和超时消息

Context的结构

type Context interface {
	// 当Context被取消或者到了deadline,返回一个被关闭的channel
	// 它是一个只读的channel,也就是说在整个生命周期都不会有写入操作,只有当这个channel被关闭时。
	// 才会读取到这个channel对应类型的零值,否则是无法取到任何值的。
	// 正是因为这个机制,当子协程从这个channel中读取到零值后可以做一些收尾工作,让子协程尽快推出
	Done() <-chan struct{}
	
	// 在channel Done 关闭后,返回Context取消原因,这里只有两种原因:1.取消  2.超时
	Err() error 
	
	// 返回Context是否会被取消以及自动取消时间(即deadline)
	// 通过这个时间我们可以判断是否有必要进行接下来的操作,如果剩余时间太短则可以选择不继续执行一些任务,
	// 可以节省系统资源
	Deadline() (deadline time.Time, ok bool)
	
	// 获取key对应的value
	Value(key interface{}) interface{}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

Context的使用场景

  • 信息传递
  • 取消任务
  • 超时控制
func slowOperation(ctx context.Context) (string, error) {
	// 获取Request id
	fmt.Println("get request_id from ctx", ctx.Value("request_id"))

	// 使用一个 select 语句来监听 Context 的状态变化
	select {
	case <-time.After(3 * time.Second): // 模拟操作需要3s才能完成
		return "Done", nil
	case <-ctx.Done(): // 如果Context 被取消或者超时,返回相应的错误信息
		return "", ctx.Err()
	}
}

// 模拟一个HTTP服务器的处理函数,使用Context来控制超时
func handler(w http.ResponseWriter, r *http.Request) {
	requestID := uuid.New().String()
	fmt.Println("gen request_id ", requestID)
	// 传递request_id
	ctr := context.WithValue(r.Context(), "request_id", requestID)

	// 从请求中获取 Context 并设置一个 1 秒钟的超时时间
	ctx, cancel := context.WithTimeout(ctr, 1*time.Second)

	// 在函数返回时调用取消函数
	defer cancel()

	// 调用耗时操作,并传递Context
	result, err := slowOperation(ctx)
	if err != nil {
		// 如果操作失败,返回错误信息和状态码
		http.Error(w, err.Error(), http.StatusGatewayTimeout)
		return
	}
	// 如果操作成功,返回结果
	fmt.Fprintln(w, result)
}

func main() {
	// 创建一个HTTP服务器,并注册处理函数
	http.HandleFunc("/", handler)
	fmt.Println("Server is running on http://localhost:9090")
	// 启动服务器,并监听9090端口
	err := http.ListenAndServe(":9090", nil)
	if err != nil {
		fmt.Println(err)
	}
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

Context的一些最佳实践

  • 在函数签名中传递Context参数
type MyStruct struct {
	// 不要在这里存储Context
}
func (m *MyStruct) myMethod(ctx context.Context, arg1 string, arg2 int) error {
	// 使用ctx传递请求上下文
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 将Context作为第一个参数传递,可以使代码更加清晰,容易理解。也可以避免我们错误地使用Context。
  • 不要在内层函数创建Context。
  • 及时取消Context。
  • 不要在Context中存储大量数据
  • 不要滥用Context

Switch的一些细节

Switch的一些常识

  • Switch语句中的case表达式可以是任意类型
  • Switch语句中的case表达式可以是多个值
switch x {
	case 1,2,3:
		fmt.Println("ok")
}
  • 1
  • 2
  • 3
  • 4
  • Switch语句还可以是表达式或者函数调用
switch x + y {
	case 1:
		fmt.Println("x+y=1")
	case 2:
		fmt.Println("x+y=2")
	default:
		fmt.Println("default")	
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • default分支的前后顺序没有限制, default分支可以在其他case分支之间

关于Switch的一些细节

  • 常量case表达式不能重复,但是布尔常量除外。
  • Switch表达式的缺省默认值为bool类型并且其值为true
func main() {
	switch {  // 等价于 switch true
		case true: fmt.Println("true")
		case false: fmt.Println("false")
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • Switch和case分支的数据类型必须一致,否则无法通过编译。
  • case分支默认只执行一个,可用fallthrough继续执行。
  • case分支的变量作用域为当前case分支的代码块内。
	//rand.Seed(time.Now().UnixNano()) // Go 1.20之前需要
	switch n := rand.Intn(100) % 5; n {
	case 0, 1, 2, 3, 4:
		fmt.Println("n =", n)
		fallthrough // 跳到下个case分支
	case 5, 6, 7, 8:
		// 一个新声明的n,它只在当前分支代码块内可见。
		n := 999
		fmt.Println("n =", n) // 999
		fallthrough           //跳到下个case分支
	default:
		// 这里的n和第一个分支中的n是同一个变量,它们均为switch表达式"n"。
		fmt.Println("n =", n)
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • fallthrough语句不能出现在最后一个case分支中。

defer底层数据结构是什么?

defer是什么?

  • 用来指定在函数返回前执行的代码。
  • defer关键字的代码段可以延迟到函数执行的最后执行。
  • 主要用于资源的释放和异常的捕获等收尾工作。

defer的底层数据结构

// 源码位置:$GOPATH/src/runtime/runtime2.go
type _defer struct {
	// 参数和返回值的内存大小
	siz int32

	//表示该_defer语句是否已经开始执行
	started bool

	//表示该_defer语句的优先级
	//当一个_defer语句被执行时,它会被添加到_defer链表中,而heap字段则用于将_defer语句添加到一个优先队列中,
	//以便在函数返回时按照一定的顺序执行_defer语句。在_defer链表中,后添加的_defer语句会先被执行,而在优先队列中,
	//heap值较小的_defer语句会先被执行。这个字段的值是在_defer语句被添加到_defer链表时根据一定规则计算出来的,
	//通常是根据_defer语句的执行顺序和作用域等因素计算而得。在函数返回时,Go语言会按照heap值的大小顺序执行_defer语句。
	//如果多个_defer语句的heap值相同,则它们会按照它们在_defer链表中的顺序依次执行。
	//这个机制可以确保_defer语句按照一定的顺序执行,从而避免了一些潜在的问题。
	heap bool

	// 表示该_defer用于具有开放式编码_defer的帧。开放式编码_defer是指在编译时已经确定_defer语句的数量和位置,
	//而不是在运行时动态添加_defer语句。在一个帧中,可能会有多个_defer语句,但只会有一个_defer结构体记录了所有_defer语句的信息,
	//而openDefer就是用来标识该_defer结构体是否是针对开放式编码_defer的
	openDefer bool

	//_defer语句所在栈帧的栈指针(stack pointer)
	//在函数调用时,每个函数都会创建一个新的栈帧,用于保存函数的局部变量、参数和返回值等信息。
	//而_defer语句也被保存在这个栈帧中,因此需要记录栈指针以便在函数返回时找到_defer语句。
	//当一个_defer语句被执行时,它会被添加到_defer链表中,并记录当前栈帧的栈指针。
	//在函数返回时,Go语言会遍历_defer链表,并执行其中的_defer语句。而在执行_defer语句时,
	//需要使用保存在_defer结构体中的栈指针来访问_defer语句所在栈帧中的局部变量和参数等信息。
	//需要注意的是,由于_defer语句是在函数返回之前执行的,因此在执行_defer语句时,函数的栈帧可能已经被销毁了。
	//因此,_sp字段的值不能直接使用,需要通过一些额外的处理来确保_defer语句能够正确地访问栈帧中的信息。
	sp uintptr

	//_defer语句的程序计数器(program counter)
	//程序计数器是一个指针,指向正在执行的函数中的下一条指令。在_defer语句被执行时,它会被添加到_defer链表中,
	//并记录当前函数的程序计数器。当函数返回时,Go语言会遍历_defer链表,并执行其中的_defer语句。
	//而在执行_defer语句时,需要让程序计数器指向_defer语句中的函数调用,以便正确地执行_defer语句中的代码。
	//这就是为什么_defer语句需要记录程序计数器的原因。需要注意的是,由于_defer语句是在函数返回之前执行的,
	//因此在执行_defer语句时,程序计数器可能已经指向了其它的函数或代码块。因此,在执行_defer语句时,
	//需要使用保存在_defer结构体中的程序计数器来确保_defer语句中的代码能够正确地执行。
	pc uintptr // pc 计数器值,程序计数器

	// defer 传入的函数地址,也就是延后执行的函数
	fn *funcval

	//defer 的 panic 结构体
	_panic *_panic

	//用于将多个defer链接起来,形成一个defer栈
	//当程序执行到一个 defer 语句时,会将该 defer 语句封装成一个 _defer 结构体,并将其插入到 defer 栈的顶部。
	//当函数返回时,程序会从 defer 栈的顶部开始依次执行每个 defer 语句,直到 defer 栈为空为止。
	//每个 _defer 结构体中的 link 字段指向下一个 _defer 结构体,从而将多个 _defer 结构体链接在一起。
	//当程序执行完一个 defer 语句后,会将该 defer 从 defer 栈中弹出,并将其 link 字段指向的下一个 _defer 结构体设置为当前的 defer 栈顶。
	//这样,当函数返回时,程序会依次执行每个 defer 语句,从而实现 defer 语句的反转执行顺序的效果。
	//需要注意的是,由于 _defer 结构体是在运行时动态创建的,因此 defer 栈的大小是不固定的。
	//在编写程序时,应该避免在单个函数中使用大量的 defer 语句,以免导致 defer 栈溢出。
	link *_defer
}



func deferproc(siz int32, fn *funcval) { // arguments of fn follow fn
	gp := getg() //获取goroutine结构
	if gp.m.curg != gp {
		// go code on the system stack can't defer
		throw("defer on system stack")
	}
	...
	d := newdefer(siz) //新建一个defer结构
	if d._panic != nil {
		throw("deferproc: d.panic != nil after newdefer")
	}
	d.link = gp._defer // 新建defer的link指针指向g的defer
	gp._defer = d      // 新建defer放到g的defer位置,完成插入链表表头操作
	d.fn = fn
	d.pc = callerpc
	d.sp = sp
	...
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79

多个defer执行顺序是怎样的?在哪些场景使用?

defer的执行顺序

  • defer是一个用链表实现的栈的结构,最后声明的defer会被插入到链表的头部。

defer的一些经典使用场景

  • 关闭文件或者网络连接
  • 关闭HTTP响应
  • 关闭数据库连接
  • 释放锁
  • 对异常进行捕获
  • 取消任务
  • 记录程序的耗时
func main() {
	defer fmt.Println(1)
	defer fmt.Println(2)
	defer fmt.Println(3)
}

// 关闭文件句柄
func readFile(filename string) (string, error) {
	f, err := os.Open(filename)
	if err != nil {
		return "", err
	}
	defer f.Close() // 使用 defer 语句关闭文件
	content, err := ioutil.ReadAll(f)
	if err != nil {
		return "", err
	}
	return string(content), nil
}

// 关闭http响应
func request() {
	url := "http://www.baidu.com"

	resp, err := http.Get(url)
	if err != nil {
		log.Fatal(err)
	}

	defer resp.Body.Close() // 关闭响应体

	// 处理响应数据
}

// 关闭数据库链接
func queryDatabase() error {
	db, err := sql.Open("mysql", "user:password@tcp(localhost:3306)/database")
	if err != nil {
		return err
	}
	defer db.Close() // 在函数返回前关闭数据库连接
	// 查询数据库
	return nil
}

var mu sync.Mutex
var balance int

// 释放锁
func lockRelease(amount int) {
	mu.Lock()
	defer mu.Unlock() // 在函数返回前解锁
	balance += amount
}

// 捕获异常
func f() {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println(string(debug.Stack()))
		}
	}()
	panic("unknown")
}

// 取消任务
func runTask() error {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel() // 在函数返回前取消任务
	go doTask(ctx)
	// 等待任务完成
	return nil
}

func doTask(ctx context.Context) {
	select {
	case <-ctx.Done():
		return
	}

	//do something
}

// 记录程序耗时
func trackTime() {
	start := time.Now()
	defer func() {
		log.Printf("Time took: %v", time.Since(start).Milliseconds())
	}()

	// 执行一些操作
	time.Sleep(time.Second * 3)
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93

defer表达式与return表达式

func main() {
	fmt.Println(f(2))
}
func f(x int) (r int) {
	defer func() {
		r += x  // 修改返回值
	}
	return x+x // 等价于 r=x+x; return
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

defer 中变量的估值时刻

  • 在声明时估值

defer中实参的估值时刻

// 最后输出结果,x=0.x的值为它被压入defer栈时x的值
func f2() {
	x := 0
	defer func(paramx int) {
		fmt.Println(paramx)
	}(x)
	x = 1
	fmt.Println("done")
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

defer中闭包的估值时刻

// 最后输出结果x值为1,为该变量的最新的值
func f3() {
	x := 0
	defer func() {
		fmt.Println(x)
	}()
	x = 1
	fmt.Println("Done")
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
// f8()输出结果:
// 123
// 123
// 123
// 0

type number int

func (n number) print() {
	fmt.Println(n)
}

func (n *number) ptrprint() {
	fmt.Println(*n)
}

func f8() {
	var n number

	defer n.print()
	defer n.ptrprint()
	defer func() { n.print() }()
	defer func() { n.ptrprint() }()

	n = 123
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

避免defer导致的内存的泄漏

  • 避免在for循环中使用defer。
  • 避免在闭包函数中引用大对象或者闭包变量。

容易被忽略的panic和recover的一些细节问题

panic

  • Go程序在运行时出现的一个异常情况
  • 未被捕获并恢复的panic会导致程序终止

两类panic

  • Go运行时发生异常导致
  • 调用panic函数主动触发

触发panic后的执行过程被Go语言称为panicking

panicking的执行过程

  • 发生panic的函数立即停止继续执行,已求值的defer正常执行。
  • 对应函数调用者而言,调用发生panic的函数跟直接调用panic类似。
func fn() {
	println("call fn")
	fn1()
	println("exit fn")
}

func fn1() {
	println("call fn1")
	defer func() {
		fmt.Println("defer before panic in fn1")
	}()
	panic("panic in fn1")
	defer func() {
		fmt.Println("defer after panic in fn1")
	}()
	fn2()
	println("exit fn1")
}

func fn2() {
	println("call fn2")
	println("exit fn2")
}

func main() {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println("捕获了一个panic: ", err)
			fmt.Println("防止了程序崩溃")
		}
	}()
	println("call main")
	fn()
	println("exit main")
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

panic 和 recover的一些细节

  • recover必须在defer声明的匿名函数中执行
  • recover与函数调用在同一个协程时才能捕捉到当前函数的panic。
  • 当前goroutine中的panic会被defer中的panic覆盖。
  • 注意多个defer语句中panic的执行顺序
func panicInDefer() {
	defer func() {
		fmt.Println("defer1")
		panic("defer1 panic")
	}()

	defer func() {
		fmt.Println("defer2")
		panic("defer2 panic")
	}()

	panic("main panic")
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 结果:
defer2
defer1
panic: main panic
        panic: defer2 panic
        panic: defer1 panic

goroutine 1 [running]:
main.panicInDefer.func1()
        D:/GoProject/训练营/go_basic_interview/panic_practice.go:32 +0x65
panic({0x4b3ae0, 0x4e6810})
        D:/Go/src/runtime/panic.go:838 +0x207
main.panicInDefer.func2()
        D:/GoProject/训练营/go_basic_interview/panic_practice.go:37 +0x65
panic({0x4b3ae0, 0x4e6820})
        D:/Go/src/runtime/panic.go:838 +0x207
main.panicInDefer()
        D:/GoProject/训练营/go_basic_interview/panic_practice.go:40 +0x54
main.main()
        D:/GoProject/训练营/go_basic_interview/panic_practice.go:53 +0x17

Process finished with the exit code 2
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 多个调用链捕获panic时,会优先被当前协程的recover所捕获,即就近捕获
func f() {
	go func() {
		defer func() {
			if err := recover(); err != nil {
				fmt.Println("recover", err)
			}
		}()
	}()
	panic("未知错误")
}

func CoveredByCurrentGoroutine() {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println(err)
		}
	}()
	f()
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

如何正确的使用panic&recover

  • 根据程序对panic的忍受程度决定是否使用recover。
  • 使用panic来提示潜在bug。
  • 不要混淆错误与异常

panic/recover机制的妙用

// 不使用panic/recover
func doSomething() (err error) {
	isContinue, err := doStep1()
	if !isContinue {
		return err
	}
	isContinue, err = doStep2()
	if !isContinue {
		return err
	}
	isContinue, err = doStep3()
	if !isContinue {
		return err
	}
	return
}

func doStep1() (isContinue bool, err error) {
	// do something for doStep1
	return
}

func doStep2() (isContinue bool, err error) {
	// do something for doStep2
	return
}

func doStep3() (isContinue bool, err error) {
	// do something for doStep3
	return
}
// 使用panic/recover
func doSomething1() (err error) {

	defer func() {
		err, _ = recover().(error)
	}()
	doStep_1()
	doStep_2()
	doStep_3()
	return
}

func doStep_1() {
	var err error
	var done bool
	// do something for doStep1
	//err = errors.New("err ")
	if err != nil {
		panic(err)
	}
	if done {
		panic(nil)
	}
	fmt.Println("doStep_1")
}

func doStep_2() {

	var err error
	var done bool
	// do something for doStep2
	if err != nil {
		panic(err)
	}
	if done {
		panic(nil)
	}
	fmt.Println("doStep_2")
}

func doStep_3() {
	var err error
	var done bool
	// do something for doStep3
	if err != nil {
		panic(err)
	}
	if done {
		panic(nil)
	}
	fmt.Println("doStep_3")
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83

channel底层的数据结构是什么?

理解channel

  • 用于goroutine之间的通信的机制
  • 实现并发编程中的数据传递和同步操作
  • Go语言通过通信来共享内存 而不是通过共享内存来通信的设计哲学的实现工具。

channel的数据结构

  • 由一个双向链表和一个锁组成
  • 每个channel都有一个缓冲区,用于存储传递的数据。
    在这里插入图片描述
// 源码位置:$GOPATH/src/runtime/chan.go
type hchan struct {
	//当前队列中元素的个数。当我们向channel发送数据时,qcount会增加1;当我们从channel接收数据时,qcount会减少1
	qcount uint

	//如果我们在创建channel时指定了缓冲区的大小,那么dataqsiz就等于指定的大小;否则,dataqsiz为0,表示该channel没有缓冲区。
	dataqsiz uint

	//buf字段是一个unsafe.Pointer类型的指针,指向缓冲区的起始地址。如果该channel没有缓冲区,则buf为nil。
	buf unsafe.Pointer

	//表示缓冲区中每个元素的大小。当我们创建channel时,Golang会根据元素的类型计算出elemsize的值。
	elemsize uint16

	// channel 是否已经关闭,当我们通过close函数关闭一个channel时,Golang会将closed字段设置为true。
	closed uint32

	//表示下一次接收元素的位置.当我们从channel接收数据时,Golang会从缓冲区中recvx索引的位置读取数据,并将recvx加1
	recvx uint

	//表示下一次发送元素的位置。在channel的发送操作中,如果缓冲区未满,则会将数据写入到sendx指向的位置,并将sendx加1。
	//如果缓冲区已满,则发送操作会被阻塞,直到有足够的空间可用。
	sendx uint

	// 等待接收数据的 goroutine 队列,用于存储等待从channel中读取数据的goroutine。
	//当channel中没有数据可读时,接收者goroutine会进入recvq等待队列中等待数据的到来。
	//当发送者goroutine写入数据后,会将recvq等待队列中的接收者goroutine唤醒,并进行读取操作。
	//在进行读取操作时,会先检查recvq等待队列是否为空,如果不为空,则会将队列中的第一个goroutine唤醒进行读取操作。
	//同时,由于recvq等待队列是一个FIFO队列,因此等待时间最长的goroutine会排在队列的最前面,最先被唤醒进行读取操作。
	recvq waitq

	// 等待发送数据的 goroutine 队列。sendq 字段是一个指向 waitq 结构体的指针,waitq 是一个用于等待队列的结构体。
	//waitq 中包含了一个指向等待队列中第一个协程的指针和一个指向等待队列中最后一个协程的指针。
	//当一个协程向一个 channel 中发送数据时,如果该 channel 中没有足够的缓冲区来存储数据,那么发送操作将会被阻塞,
	//直到有另一个协程来接收数据或者 channel 中有足够的缓冲区来存储数据。当一个协程被阻塞在发送操作时,
	//它将会被加入到 sendq 队列中,等待另一个协程来接收数据或者 channel 中有足够的缓冲区来存储数据。
	sendq waitq

	//channel的读写锁,确保多个gorutine同时访问时的并发安全,保证读写操作的原子性和互斥性。
	//当一个goroutine想要对channel进行读写操作时,首先需要获取lock锁。如果当前lock锁已经被其他goroutine占用,
	//则该goroutine会被阻塞,直到lock锁被释放。一旦该goroutine获取到lock锁,就可以进行读写操作,并且在操作完成后释放lock锁,
	//以便其他goroutine可以访问channel底层数据结构。
	lock mutex
}

// 等待队列是一个包含多个 sudog 结构体的链表,用于存储正在等待发送或接收数据的 goroutine。
// 当有数据可用时,等待队列中的 goroutine 会被唤醒并继续执行。
type waitq struct {
	first *sudog // 队列头部指针
	last  *sudog // 队列尾部指针
}

// sudog 结构体是一个用于等待队列中的 goroutine 的结构体,
// 它包含了等待的 goroutine 的信息,如等待的 channel、等待的元素值、
// 等待的方向(发送或接收)等。
type sudog struct {
	// 等待的 goroutine
	g *g

	// 指向下一个 sudog 结构体
	next *sudog

	// 指向上一个 sudog 结构体
	prev *sudog

	//等待队列的元素
	elem unsafe.Pointer

	// 获取锁的时间
	acquiretime int64

	// 释放锁的时间
	releasetime int64

	//用于实现自旋锁。当一个gorutine需要等待另一个gorutine操作完成,
	//而等待时间很短的情况下就会使用自旋锁。
	//它会先获取当前的ticket值,并将其加1。然后,它会不断地检查结构体中的ticket字段是否等于自己的ticket值,
	//如果相等就说明获取到了锁,否则就继续自旋等待。当锁被释放时,另一个goroutine会将ticket值加1,从而唤醒等待的goroutine。
	//需要注意的是,自旋锁适用于等待时间很短的场景,如果等待时间较长,就会造成CPU资源的浪费
	ticket uint32

	// 等待的 goroutine是否已经被唤醒
	isSelect bool

	//success 表示通道 c 上的通信是否成功。
	//如果 goroutine 是因为在通道 c 上接收到一个值而被唤醒,那么 success 为 true;
	//如果是因为通道 c 被关闭而被唤醒,那么 success 为 false。
	success bool

	//用于实现gorutine的堆栈转移
	//当一个 goroutine 调用另一个 goroutine 时,它会创建一个 sudog 结构体,并将自己的栈信息保存在 sudog 结构体的 parent 字段中。
	//然后,它会将 sudog 结构体加入到等待队列中,并等待被调用的 goroutine 执行完成。
	//当被调用的 goroutine 执行完成时,它会将 sudog 结构体从等待队列中移除,并将 parent 字段中保存的栈信息恢复到调用者的栈空间中。
	//这样,调用者就可以继续执行自己的任务了。
	//需要注意的是,sudog 结构体中的 parent 字段只在 goroutine 调用其他 goroutine 的时候才会被使用,
	//因此在普通的 goroutine 执行过程中,它是没有被使用的。
	parent *sudog // semaRoot binary tree

	//用于连接下一个等待的 sudog 结构体
	//等待队列是一个链表结构,每个 sudog 结构体都有一个 waitlink 字段,用于连接下一个等待的 sudog 结构体。
	//当被等待的 goroutine 执行完成时,它会从等待队列中移除对应的 sudog 结构体,
	//并将 sudog 结构体中的 waitlink 字段设置为 nil,从而将其从等待队列中移除。
	//需要注意的是,waitlink 字段只有在 sudog 结构体被加入到等待队列中时才会被使用。
	//在普通的 goroutine 执行过程中,waitlink 字段是没有被使用的。
	waitlink *sudog // g.waiting list or semaRoot

	//等待队列的尾部指针,waittail 字段指向等待队列的尾部 sudog 结构体。
	//当被等待的 goroutine 执行完成时,它会从等待队列中移除对应的 sudog 结构体,并将 sudog 结构体中的 waitlink 字段设置为 nil,
	//从而将其从等待队列中移除。同时,waittail 字段也会被更新为等待队列的新尾部。
	//需要注意的是,waittail 字段只有在 sudog 结构体被加入到等待队列中时才会被使用。
	//在普通的 goroutine 执行过程中,waittail 字段是没有被使用的。
	waittail *sudog // semaRoot

	//在golang中,goroutine是轻量级线程,其调度由golang运行时系统负责。当一个goroutine需要等待某些事件的发生时,
	//它可以通过阻塞等待的方式让出CPU资源,等待事件发生后再被唤醒继续执行。这种阻塞等待的机制是通过wait channel实现的。
	//在sudog结构体中,c字段指向的wait channel是一个用于等待某些事件发生的channel。
	//当一个goroutine需要等待某些事件时,它会创建一个sudog结构体,并将该结构体中的c字段指向wait channel。
	//然后,它会将该sudog结构体加入到wait channel的等待队列中,等待事件发生后再被唤醒继续执行。
	//当一个goroutine需要等待某些事件时,它会将自己加入到wait channel的等待队列中,并阻塞等待事件发生。
	//当事件发生后,wait channel会将等待队列中的goroutine全部唤醒,让它们继续执行。
	//这种机制可以有效地避免busy waiting,提高CPU利用率。
	c *hchan // channel
}

type lockRankStruct struct {
}

// Mutual exclusion locks.  In the uncontended case,
// as fast as spin locks (just a few user-level instructions),
// but on the contention path they sleep in the kernel.
// A zeroed Mutex is unlocked (no need to initialize each lock).
// Initialization is helpful for static lock ranking, but not required.
type mutex struct {
	// Empty struct-demo if lock ranking is disabled, otherwise includes the lock rank
	lockRankStruct
	// Futex-based impl treats it as uint32 key,
	// while sema-based impl as M* waitm.
	// Used to be a union, but unions break precise GC.
	key uintptr
}

// Needs to be in sync with ../cmd/link/internal/ld/decodesym.go:/^func.commonsize,
// ../cmd/compile/internal/reflectdata/reflect.go:/^func.dcommontype and
// ../reflect/type.go:/^type.rtype.
// ../internal/reflectlite/type.go:/^type.rtype.
type _type struct {
	size       uintptr
	ptrdata    uintptr // size of memory prefix holding all pointers
	hash       uint32
	tflag      tflag
	align      uint8
	fieldAlign uint8
	kind       uint8
	// function for comparing objects of this type
	// (ptr to object A, ptr to object B) -> ==?
	equal func(unsafe.Pointer, unsafe.Pointer) bool
	// gcdata stores the GC type data for the garbage collector.
	// If the KindGCProg bit is set in kind, gcdata is a GC program.
	// Otherwise it is a ptrmask bitmap. See mbitmap.go for details.
	gcdata    *byte
	str       nameOff
	ptrToThis typeOff
}

type g struct {

	// goroutine 的栈信息
	stack       stack
	stackguard0 uintptr // offset known to liblink
	stackguard1 uintptr // offset known to liblink

	_panic *_panic // innermost panic - offset known to liblink
	_defer *_defer // innermost defer
	m      *m      // current m; offset known to arm liblink

	// goroutine 调度器上下文信息
	sched gobuf

	syscallsp uintptr // if status==Gsyscall, syscallsp = sched.sp to use during gc
	syscallpc uintptr // if status==Gsyscall, syscallpc = sched.pc to use during gc
	stktopsp  uintptr // expected sp at top of stack, to check in traceback
	// param is a generic pointer parameter field used to pass
	// values in particular contexts where other storage for the
	// parameter would be difficult to find. It is currently used
	// in three ways:
	// 1. When a channel operation wakes up a blocked goroutine, it sets param to
	//    point to the sudog of the completed blocking operation.
	// 2. By gcAssistAlloc1 to signal back to its caller that the goroutine completed
	//    the GC cycle. It is unsafe to do so in any other way, because the goroutine's
	//    stack may have moved in the meantime.
	// 3. By debugCallWrap to pass parameters to a new goroutine because allocating a
	//    closure in the runtime is forbidden.
	param unsafe.Pointer

	// 原子级别的 goroutine 运行状态
	atomicstatus atomic.Uint32

	stackLock uint32 // sigprof/scang lock; TODO: fold in to atomicstatus
	goid      uint64
	schedlink guintptr

	waitsince int64 // approx time when the g become blocked

	// goroutine 等待的原因
	waitreason waitReason // if status==Gwaiting

	preempt       bool // preemption signal, duplicates stackguard0 = stackpreempt
	preemptStop   bool // transition to _Gpreempted on preemption; otherwise, just deschedule
	preemptShrink bool // shrink stack at synchronous safe point

	// asyncSafePoint is set if g is stopped at an asynchronous
	// safe point. This means there are frames on the stack
	// without precise pointer information.
	asyncSafePoint bool

	paniconfault bool // panic (instead of crash) on unexpected fault address
	gcscandone   bool // g has scanned stack; protected by _Gscan bit in status
	throwsplit   bool // must not split stack
	// activeStackChans indicates that there are unlocked channels
	// pointing into this goroutine's stack. If true, stack
	// copying needs to acquire channel locks to protect these
	// areas of the stack.
	activeStackChans bool
	// parkingOnChan indicates that the goroutine is about to
	// park on a chansend or chanrecv. Used to signal an unsafe point
	// for stack shrinking.
	parkingOnChan atomic.Bool

	raceignore     int8     // ignore race detection events
	sysblocktraced bool     // StartTrace has emitted EvGoInSyscall about this goroutine
	tracking       bool     // whether we're tracking this G for sched latency statistics
	trackingSeq    uint8    // used to decide whether to track this G
	trackingStamp  int64    // timestamp of when the G last started being tracked
	runnableTime   int64    // the amount of time spent runnable, cleared when running, only used when tracking
	sysexitticks   int64    // cputicks when syscall has returned (for tracing)
	traceseq       uint64   // trace event sequencer
	tracelastp     puintptr // last P emitted an event for this goroutine
	lockedm        muintptr

	// goroutine 等待的信号
	sig uint32

	writebuf   []byte
	sigcode0   uintptr
	sigcode1   uintptr
	sigpc      uintptr
	gopc       uintptr         // pc of go statement that created this goroutine
	ancestors  *[]ancestorInfo // ancestor information goroutine(s) that created this goroutine (only used if debug.tracebackancestors)
	startpc    uintptr         // pc of goroutine function
	racectx    uintptr
	waiting    *sudog         // sudog structures this g is waiting on (that have a valid elem ptr); in lock order
	cgoCtxt    []uintptr      // cgo traceback context
	labels     unsafe.Pointer // profiler labels
	timer      *timer         // cached timer for time.Sleep
	selectDone atomic.Uint32  // are we participating in a select and did someone win the race?

	// goroutineProfiled indicates the status of this goroutine's stack for the
	// current in-progress goroutine profile
	goroutineProfiled goroutineProfileStateHolder

	// Per-G GC state

	// gcAssistBytes is this G's GC assist credit in terms of
	// bytes allocated. If this is positive, then the G has credit
	// to allocate gcAssistBytes bytes without assisting. If this
	// is negative, then the G must correct this by performing
	// scan work. We track this in bytes to make it fast to update
	// and check for debt in the malloc hot path. The assist ratio
	// determines how this corresponds to scan work debt.
	gcAssistBytes int64
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271

有缓冲channel和无缓冲channel有何区别?

channel分类

按读写分
  • 单向只读channel (var readOnlyChan <- chan int)
  • 单向只写channel (var writeOnlyChan chan <- int)
  • 双向可读可写channel (var ch chan int)
  • 写一个只读channel或者读一个只写channel都会导致编译不通过
  • 双向channel可以隐式转换成单向channel,反之则不行。
按是否有缓冲区分
  • 带缓冲区channel
  • 不带缓冲区channel
readOnlyChanWithBuff := make(<-chan int, 2)  // 只读且带缓冲
readOnlyChan := make(<-chan int)  // 只读且不带缓冲
wirteOnlyChanWithBuff := make(chan <- int, 4)  // 只写且带缓冲
wirteOnlyChan := make(chan <- int)  // 只写且不带缓冲
ch := make(chan int, 10)  // 可读可写且带缓冲
  • 1
  • 2
  • 3
  • 4
  • 5

无缓冲channel

  • 无缓冲channel发送和接收操作是同步的。
  • 只有当发送方和接收方同时准备好才不会导致死锁。

有缓冲channel

  • 有缓冲的channel发送和接收操作是非阻塞的

两个goroutine中有缓冲的channel相互等待而产生的死锁

func f14() {
	ch := make(chan int, 2)
	ch1 := make(chan int, 2)
	go func() {
		for {
			select {
			case <-ch:
				ch1 <- 1
			}
		}
	}()

	for {
		select {
		case <-ch1:
			ch <- 1
		}
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

关闭的channel发送和接收数据会发生什么?

关闭的channel的读写

func f1() {
	ch := make(chan int)
	close(ch)
	go func() {
		fmt.Println("开始接收数据:")
		fmt.Println("<-ch:", <-ch)
		fmt.Println("数据接收完成")
	}()

	go func() {
		fmt.Println("开始发送数据")
		ch <- 1
		fmt.Println("发送数据完毕")
	}()
	time.Sleep(time.Second)
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 关闭一个nil的channel会导致panic
  • 向一个已经关闭的channel里发送数据会导致panic
  • 从一个已经关闭的channel里读取数据不会panic,不会阻塞 而且可以一直读取
func f2() {
	ch := make(chan int)
	close(ch)
	for {
		select {
		case c := <-ch:
			fmt.Println("读取<-ch: ", c)
			time.Sleep(time.Second)
		}
	}
}

func f3() {
	ch := make(chan int)
	close(ch)
	<-ch
	println("完成") // 如果这句话会输出则说明不会阻塞
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

channel使用的一些误区

  • channel只允许关闭一次,多次关闭channel会触发运行时错误。
// 多次关掉 channel 会触发运行时错误
func f7() {
	c := make(chan int)
	defer close(c)
	go func() {
		c <- 1
		close(c)
	}()
	fmt.Println(<-c)
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

如何通过interface实现鸭子类型

  • Go语言中的“对象”是一种更加轻量级和更加灵活的实现方式

函数式编程VS面向对象

  • 函数式编程更加关注数据流和处理,而不是数据的状态和操作。
  • 面向对象更加关注数据的状态和操作。
  • 本质区别在于它们处理数据和实现代码的方式不同。

鸭子类型

  • 鸭子类型是一种动态类型,它关注对象的行为,而不是类型。
type Duck interface {
	Quack()
}

type YellowDuck struct {
}

func (yd YellowDuck) Quack() {
	fmt.Println("葛诗颖 葛诗颖")
}

type NormalDuck struct {
}

func (nd NormalDuck) Quack() {
	fmt.Println("诗诗 诗诗 诗诗")
}

func Quack(d Duck) {
	d.Quack()
}

func main() {
	yd := YellowDuck{}
	nd := NormalDuck{}
	Quack(yd)
	Quack(nd)
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

Go语言支持重载么?如何实现重写?

重载

  • 一个类中定义多个同名的方法
  • 重载可以提高代码的复用性和灵活性,使代码更加简洁易懂。
  • Go语言不支持重载

重写

  • 在子类中定义一个与父类同名、同参数的方法,但方法的实现不同。
  • 可以实现多态,提高代码的可扩展性和可维护性。
type Animal struct {
}

func (a *Animal) eat() {
	fmt.Println("葛诗颖")
}

// Cat继承Animal
type Cat struct {
	Animal
}

// Cat子类也可以有eat方法,且实现可以跟父类Animal不同
func (c *Cat) eat() {
	fmt.Println("诗诗")
}

func main() {

	a := &Animal{}
	c := &Cat{}
	a.eat()
	c.eat()
}


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

Go语言中如何实现继承

继承

  • 继承是种代码复用和扩展的机制
  • 提高代码的重用性,简化代码设计,实现多态,提高代码可读性。
  • 通过实现接口的方式:
type Iperson interface {
	Talk()
	Walk()
}
type Person struct {
	Iperson
}

func (p *Person) Talk() {
	fmt.Println("talk")
}

//func (p *Person) Walk() {
//	fmt.Println("walk")
//}

func Talk(p Iperson) {
	p.Talk()
}

type Teacher struct {
}

func (t *Teacher) Talk() {
	fmt.Println("Teacher talk")
}

func (t *Teacher) Walk() {
	fmt.Println("Teacher walk")
}
func main() {
	p := &Person{}
	var i Iperson
	i = p
	fmt.Println(i)
	p.Talk()
	p.Walk()
	t := &Teacher{}
	Talk(p)
	Talk(t)

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 通过结构体内嵌的方式,但是如果。子结构体内嵌的多个父结构体含有同名的方法,则子结构体必须重写全部的同名方法,否则编译时会报错。
type People struct{}

func (p *People) Run() {
	fmt.Println("Run")
	p.Eat()
}
func (p *People) Eat() {
	fmt.Println("Eat")
}

type Person struct{}

func (p *Person) Run() {
	fmt.Println("Person Run")
	p.Eat()
}
func (p *Person) Eat() {
	fmt.Println("Person Eat")
}

type Teacher struct {
	//People
	*People
	*Person
}

func (t *Teacher) Speck() {
	fmt.Println("teacher Speck")
}

func (t *Teacher) Run() {
	fmt.Println("teacher Eat")
}

func (t *Teacher) Eat() {
	fmt.Println("teacher Eat")
}

func main() {
	t := Teacher{}
	t.Run()
	t.Eat()
	t.Speck()
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

Go语言如何实现多态

多态

  • 同一种行为或者方法在不同情况下的表现形态和实现方式不同。
  • 提高程序的扩展性,使得代码更加灵活。
  • 在Go语言里,多态可以通过接口实现。
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/557629
推荐阅读
相关标签
  

闽ICP备14008679号