zl程序教程

您现在的位置是:首页 >  工具

当前栏目

Golang 【basic_leaming】M 2022 1

Golang 2022 Basic
2023-09-11 14:14:56 时间

待续
https://www.bilibili.com/video/BV1EF411h7Xq?p=13&vd_source=4118cfa937fa2cce11388788da88b7a0

闭包

实例 1

// 闭包
package main

import "fmt"

func func1() func(int) int {
	sum := 0
	return func(val int) int {
		sum += val
		return sum
	}
}

func printFunc1() {
	sunFunc := func1()
	fmt.Println(sunFunc(1))
	fmt.Println(sunFunc(1))
}
func main() {
	printFunc1()
}
running...
1
2

实例 2

package main

import "fmt"

func func2() (val int) {
	val = 10
	defer func() {
		val += 1
	}()
	return val
}

func printFunc2() {
	fmt.Println(func2())
}

func main() {
	printFunc2()
}
running...
11

实例 3

package main

import "fmt"

func func3() int {
	val := 10
	defer func() {
		val += 1
	}()
	return 100
}

func printFunc3() {
	fmt.Println(func3())
}

func main() {
	printFunc3()
}
running...
100

goroutine

会出现什么状况,为什么,怎么解决?

// goroutine
package main

import (
	"fmt"
	"time"
)

// 题目描述:会出现什么状况,为什么,怎么解决?
func main() {
	data := make(map[int]int, 10)

	for i := 1; i <= 10; i++ {
		data[i] = i
	}
	for key, value := range data {
		go func() {
			fmt.Println("k->", key, "v->", value)
		}()
	}
	time.Sleep(time.Second * 5)
}

每次运行都不一样,是一个随机的。

running...
k-> 6 v-> 6
k-> 6 v-> 6
k-> 6 v-> 6
k-> 7 v-> 7
k-> 6 v-> 6
k-> 6 v-> 6
k-> 5 v-> 5
k-> 6 v-> 6
k-> 6 v-> 6
k-> 6 v-> 6

解决变量没有刷新方式 1

go 栈中的原因,因为变量没有刷新。

// goroutine
package main

import (
	"fmt"
	"time"
)

// 题目描述:会出现什么状况,为什么,怎么解决?
func main() {
	data := make(map[int]int, 10)

	for i := 1; i <= 10; i++ {
		data[i] = i
	}
	for key, value := range data {
		// 采用外部变量方式
		key := key
		value := value
		go func() {
			fmt.Println("k->", key, "v->", value)
		}()
	}
	time.Sleep(time.Second * 5)
}
running...
k-> 8 v-> 8
k-> 6 v-> 6
k-> 1 v-> 1
k-> 2 v-> 2
k-> 4 v-> 4
k-> 7 v-> 7
k-> 3 v-> 3
k-> 5 v-> 5
k-> 9 v-> 9
k-> 10 v-> 10

解决变量没有刷新方式 2

// goroutine
package main

import (
	"fmt"
	"time"
)

// 题目描述:会出现什么状况,为什么,怎么解决?
func main() {
	data := make(map[int]int, 10)

	for i := 1; i <= 10; i++ {
		data[i] = i
	}
	for key, value := range data {
		go func(key, value int) {
			fmt.Println("k->", key, "v->", value)
		}(key, value)
	}
	time.Sleep(time.Second * 5)
}
running...
k-> 5 v-> 5
k-> 9 v-> 9
k-> 7 v-> 7
k-> 8 v-> 8
k-> 4 v-> 4
k-> 6 v-> 6
k-> 10 v-> 10
k-> 2 v-> 2
k-> 3 v-> 3
k-> 1 v-> 1

goroutine 栈

go-v1(最小栈空间为4kB)
go-v1.2(最小栈空间为8kB)
go-v1.3(分段栈替换为连续栈)
go-v1.4(最小栈空间为2kB)


go-v1(最小栈空间为4kB)

在这里插入图片描述

go-v1.2(最小栈空间为8kB)

在这里插入图片描述

go-v1.3(分段栈替换为连续栈)

在这里插入图片描述
在这里插入图片描述

go-v1.4(最小栈空间为2kB)

在这里插入图片描述

堆与栈的区别?

栈简介

栈由操作系统自动分配释放 ,用于存放函数的参数值、局部变量等,其操作方式类似于数据结构中的栈。

堆简介

堆由开发人员分配和释放, 若开发人员不释放,程序结束时由 OS 回收,分配方式类似于链表。

defer 栈

实例 1

/*
defer
*/
package main

import "fmt"

func main() {
	m := 10
	defer fmt.Printf("first defer %d\n", +m)

	m = 100
	defer fmt.Printf("second defer %d\n", m)
}
running...
second defer 100
first defer 10

实例 2

/*
 */
package main

import "fmt"

func main() {
	m := 10
	defer fmt.Printf("first defer %d\n", +m)

	m = 100
	defer func() {
		fmt.Printf("second defer %d\n", m)
	}()
	m += 10
	defer fmt.Printf("third defer %d\n", m)

	funcVal := func1()
	funcVal()
}

func func1() func() {
	fmt.Println("before return")
	return func() {
		defer fmt.Println("in the return")
	}
}
running...
before return
in the return
third defer 110
second defer 110
first defer 10

实例 3

package main

import "fmt"

func main() {
	defer fmt.Printf("main %d\n", func2())
}

func func2() (sum int) {
	sumA := 100
	sumB := 100
	sum = sumA + sumB

	defer func() {
		fmt.Printf("func2 first %d\n", sum)
	}()
	defer fmt.Printf("second first %d\n", sum)
	return sum * 10
}
running...
second first 200
func2 first 2000
main 2000

slice 扩容

实例 1

package main

import "fmt"

func main() {
	var arr1 []int64
	for i := 0; i < 1025; i++ {
		arr1 = append(arr1, int64(i))
	}
	fmt.Printf("len=%d\n cap=%d\n", len(arr1), cap(arr1))
}
running...
len=1025
 cap=1280

实例 2

package main

import "fmt"

func main() {
	var arr1 []int64
	var arr2 []int64
	for i := 0; i < 1025; i++ {
		arr2 = append(arr2, int64(i))
	}
	fmt.Printf("len=%d\n cap=%d\n", len(arr2), cap(arr2))
	arr1 = append(arr1, arr2...)
	fmt.Printf("len=%d\n cap=%d\n", len(arr1), cap(arr1))
}
running...
len=1025
 cap=1280
len=1025
 cap=1184

nil 是否一定相等?

答案是不一定。

package main

import "fmt"

func main() {
	var x *int = nil
	var y interface{} = x
	fmt.Println(x == y)
	fmt.Println(x == nil)
	fmt.Println(y == nil)
}
running...
true
true
false

并发测试

并发测试题 - 1

package main

import (
	"fmt"
	"sync"
)

var mu sync.Mutex
var chain string

func main() {
	chain = "main"
	A()
	fmt.Println(chain)
}
func A() {
	mu.Lock()
	defer mu.Unlock()
	chain = chain + " --> A"
	B()
}
func B() {
	chain = chain + " --> B"
	C()
}
func C() {
	mu.Lock()
	defer mu.Unlock()
	chain = chain + " --> C"
}
A 不能编译
B 输出 main --> A --> B --> C
C 输出 main
D panic

D panic

running...
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [semacquire]:
sync.runtime_SemacquireMutex(0x10?, 0x6?, 0x6?)
        D:/Golang/src/runtime/sema.go:77 +0x25
sync.(*Mutex).lockSlow(0xa87488)
        D:/Golang/src/sync/mutex.go:171 +0x165
sync.(*Mutex).Lock(...)
        D:/Golang/src/sync/mutex.go:90
main.C()
        E:/golang/src/main.go:30 +0x51
main.B(...)
        E:/golang/src/main.go:27
main.A()
        E:/golang/src/main.go:23 +0x10a
main.main()
        E:/golang/src/main.go:16 +0x54

D panic 死锁问题

死锁问题
fatal error: all goroutines are asleep - deadlock!

互斥锁、不可重入锁
在一个goroutine下,重复上锁的话,就会发生 panic。

package main

import "sync"

var mu sync.Mutex

func main() {
	mu.Lock()
	mu.Lock()
}
running...
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [semacquire]:
sync.runtime_SemacquireMutex(0xc00001a060?, 0x0?, 0xc000044000?)
        D:/Golang/src/runtime/sema.go:77 +0x25
sync.(*Mutex).lockSlow(0x23b040)
        D:/Golang/src/sync/mutex.go:171 +0x165
sync.(*Mutex).Lock(...)
        D:/Golang/src/sync/mutex.go:90
main.main()
        E:/golang/src/main.go:12 +0x5f
/*
并发测试题 - 1
*/
package main

import (
	"fmt"
	"sync"
	"time"
)

var mu sync.Mutex

func main() {
	mu.Lock()
	go func() {
		mu.Lock()
		fmt.Println("I'm OK!!!")
		defer mu.Unlock()
	}()
	mu.Unlock()
	time.Sleep(time.Second)
	return
}
running...
I'm OK!!!

如何调用 panic

package main

func proc() {
	panic("ok")
}

func main() {
	go func() {
		// 1 在这里需要你算法
		// 2 要求每秒钟调用一次 proc 函数
		// 3 要求程序不能退出
	}()

	select {}
}
running...
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [select (no cases)]:
main.main()
        E:/golang/src/main.go:17 +0x2a

实例 1

package main

import (
	"fmt"
	"time"
)

func proc() {
	panic("ok")
}

func main() {
	go func() {
		// 1 在这里需要你算法
		// 2 要求每秒钟调用一次 proc 函数
		// 3 要求程序不能退出
		t := time.NewTicker(time.Second)
		// fmt.Println(time.Now())
		for {
			select {
			case <-t.C:
				go func() {
					defer func() {
						if err := recover(); err != nil {
							fmt.Println(err)
						}
					}()
					fmt.Println(time.Now())
					proc()
				}()
			}
		}
	}()

	select {}
}
running...
2022-12-29 17:12:04.1589622 +0800 CST m=+1.011997801
ok
2022-12-29 17:12:05.1567679 +0800 CST m=+2.009803501
ok
2022-12-29 17:12:06.1537221 +0800 CST m=+3.006757701
ok
2022-12-29 17:12:07.1562807 +0800 CST m=+4.009316301
...

算法

LRU 缓存

设计和构建一个“最近最少使用”缓存,该缓存会删除最近最少使用的项目。

缓存应该从键映射到值(允许你插入和检索特定键对应的值),并在初始化时指定最大容量。

当缓存被填满时,它应该删除最近最少使用的项目。

它应该支持以下操作: 获取数据 get 和 写入数据 put 。

获取数据 get(key) - 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。

写入数据 put(key, value) - 如果密钥不存在,则写入其数据值。当缓存容量达到上限时,它应该在写入新数据之前删除最近最少使用的数据值,从而为新的数据值留出空间。

示例:

LRUCache cache = new LRUCache( 2 /* 缓存容量 */ );

cache.put(1, 1);
cache.put(2, 2);
cache.get(1);       // 返回  1
cache.put(3, 3);    // 该操作会使得密钥 2 作废
cache.get(2);       // 返回 -1 (未找到)
cache.put(4, 4);    // 该操作会使得密钥 1 作废
cache.get(1);       // 返回 -1 (未找到)
cache.get(3);       // 返回  3
cache.get(4);       // 返回  4
package main

// 双线链表
type DLinkNode struct {
	key, val  int
	pre, next *DLinkNode
}

type LRUCache struct {
	size, capacity int
	cache          map[int]*DLinkNode
	head, tail     *DLinkNode
}

func initDLinkNode(key, val int) *DLinkNode {
	return &DLinkNode{key: key, val: val}
}

func Constructor(capacity int) LRUCache {
	l := LRUCache{
		capacity: capacity,
		size:     0,
		cache:    map[int]*DLinkNode{},
		head:     initDLinkNode(0, 0),
		tail:     initDLinkNode(0, 0),
	}
	l.head.next = l.tail
	l.tail.pre = l.head
	return l
}

// 移除节点
func removeNode(node *DLinkNode) {
	node.pre.next = node.next
	node.next.pre = node.pre
}

// 移除尾部节点
func (this *LRUCache) removeTail() *DLinkNode {
	node := this.tail.pre
	removeNode(node)
	return node
}

// 添加数据
func (this *LRUCache) addHead(node *DLinkNode) {
	node.next = this.head.next
	node.pre = this.head
	this.head.next.pre = node
	this.head.next = node
}

// 移动到头部
func (this *LRUCache) moveToHead(node *DLinkNode) {
	removeNode(node)
	this.addHead(node)
}

func (this *LRUCache) Get(key int) int {
	if node, ok := this.cache[key]; ok {
		this.moveToHead(node)
		return node.val
	}
	return -1
}

func (this *LRUCache) Put(key int, value int) {
	if node, ok := this.cache[key]; ok {
		node.val = value
		this.moveToHead(node)
	} else {
		newNode := initDLinkNode(key, value)
		this.addHead(newNode)
		this.cache[key] = newNode
		this.size++
		if this.size > this.capacity {
			tail := this.removeTail()
			this.size--
			delete(this.cache, tail.key)
		}
	}
}

func main() {

}

无重复字符的最长子串

给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。

示例 1:

输入: s = "abcabcbb"
输出: 3 

解释:
因为无重复字符的最长子串是 “abc”,所以其长度为 3。

示例 2:

输入: s = "bbbbb"
输出: 1

解释:
因为无重复字符的最长子串是 “b”,所以其长度为 1。

示例 3:

输入: s = "pwwkew"
输出: 3

解释:
因为无重复字符的最长子串是 “wke”,所以其长度为 3。

请注意,你的答案必须是 子串 的长度,“pwke” 是一个子序列,不是子串。

提示:

  • 0 <= s.length <= 5 * 104
  • s 由英文字母、数字、符号和空格组成。
package main

import (
	"fmt"
	"strings"
)

func lengthOfLongestSubstring(s string) int {
	start := 0
	end := 0
	for i := 0; i < len(s); i++ {
		index := strings.Index(s[start:i], string(s[i]))
		if index == -1 && (i+1) > end {
			end = i + 1
		} else {
			start += index + 1
			end += index + 1
		}
	}
	return end - start
}

func main() {
	num := lengthOfLongestSubstring("pwwkew")
	fmt.Println(num)
}
building...
running...
3

快速排序

package main

import "fmt"

func QuickSort(nums []int) []int {
	if len(nums) <= 1 {
		return nums
	}
	low := make([]int, 0, 0)
	high := make([]int, 0, 0)
	mid := make([]int, 0, 0)
	flag := nums[0]
	mid = append(mid, flag)
	for i := 1; i < len(nums); i++ {
		if nums[i] < flag {
			low = append(low, nums[i])
		} else if nums[i] > flag {
			high = append(high, nums[i])
		} else {
			mid = append(mid, nums[i])
		}
	}
	low, high = QuickSort(low), QuickSort(high)
	return append(append(low, mid...), high...)
}

func main() {
	fmt.Println(QuickSort([]int{5, 2, 3, 9, 1, 4, 8, 7}))
}
running...
[1 2 3 4 5 7 8 9]

反转链表

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

示例 1:

在这里插入图片描述

输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]

示例 2:

在这里插入图片描述

输入:head = [1,2]
输出:[2,1]

示例 3:

输入:head = []
输出:[]

提示:

  • 链表中节点的数目范围是 [0, 5000]
    -5000 <= Node.val <= 5000

进阶:链表可以选用迭代或递归方式完成反转。
你能否用两种方法解决这道题?

利用两个切片实现

设计思想:

1、确定切片长度。
2、获取最后一个元素。
3、以相反的顺序在新切片中添加最后一个元素到第一个位置。

package main

import "fmt"

func main() {
	s := []string{"hello", "foo", "bar", "go", "abc", "zzz"}

	// 定义新的反转切片
	reverseOfS := []string{}

	// 遍历原切片 s
	for i := range s {
		fmt.Println(i)
		reverseOfS = append(reverseOfS, s[len(s)-1-i])
	}
	fmt.Println(reverseOfS)
}

运行结果:

running...
0
1
2
3
4
5
[zzz abc go bar foo hello]

显然,这种方式会额外花费一个相同空间的切片,空间复杂度为 O(n)。

前后两两原地交换

我们可以写一个简易的 reverse 函数来进行数据的反转,通过循环原切片的一半,然后依次与对应的元素进行交换,比如::

package main

import "fmt"

func reverse(s []string) []string {
	for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
		s[i], s[j] = s[j], s[i]
	}
	return s
}

func main() {
	s := []string{"hello", "foo", "bar", "go", "abc", "zzz"}

	reverseOfS := reverse(s)

	fmt.Println(reverseOfS)
}

执行结果:

running...
[zzz abc go bar foo hello]

但是,上面的 reverse 函数都是通过切片按值传递,其实我们在修改传递中的 []string 切片,实际上,可以通过以下方式进一步简写:

package main

import (
	"fmt"
)

func reverse(s []string) {
	for i := 0; i < len(s)/2; i++ {
		// fmt.Println(len(s))
		// os.Exit(11)
		j := len(s) - i - 1
		fmt.Println(j)
		s[i], s[j] = s[j], s[i]
	}
}

func main() {
	s := []string{"hello", "foo", "bar", "go", "abc", "zzz"}

	reverse(s)
	fmt.Printf("%v\n", s)
}
building...
running...
5
4
3
[zzz abc go bar foo hello]

反转为原切片的副本

package main

import (
	"fmt"
)

func reverse(s []string) []string {

	newS := make([]string, len(s))
	for i, j := 0, len(s)-1; i <= j; i, j = i+1, j-1 {
		// fmt.Println(i, j) 0 5
		// os.Exit(1)
		newS[i], newS[j] = s[j], s[i]
	}
	return newS
}

func main() {
	s := []string{"hello", "foo", "bar", "go", "abc", "zzz"}

	fmt.Printf("原字符串切片:%v\n", s)
	fmt.Printf("反转后的切片:%v\n", reverse(s))
}
unning...
原字符串切片:[hello foo bar go abc zzz]
反转后的切片:[zzz abc go bar foo hello]

可以看到,原切片是没有变化的。

当然,因为我们没有就地修改原切片,因此又可以回到最初的方法 append,看代码:

func reverse(s []string) []string {

    newS := make([]string, 0, len(s))

    for i := len(s)-1; i >= 0; i-- {
        newS = append(newS, s[i])
    }
    return newS
}

Go语言结构体(struct)

package main

import "fmt"

type Node struct {
	data interface{}
	next *Node
}

// 反转单链表
func (head *Node) reverse() *Node {
	//  空链表
	if head == nil {
		return nil
	}

	// 反转后单链表的头结点
	var reverseHead *Node

	var preNode *Node
	curNode := head

	for curNode != nil {
		nextNode := curNode.next
		if nextNode == nil {
			// 尾结点转换为头结点
			reverseHead = curNode
		}
		// 反转实现,当前结点的前驱结点变成后驱结点
		curNode.next = preNode
		// 设置下一个结点的前驱结点
		preNode = curNode
		curNode = nextNode
	}

	// 返回反转后的头结点
	return reverseHead
}

// 遍历单链表
func (head *Node) traverse() {
	node := head
	for node != nil {
		fmt.Printf("%v ", node.data)
		node = node.next
	}
}

func main() {
	first := &Node{data: 1}
	second := &Node{data: 2}
	third := &Node{data: 3}

	first.next = second
	second.next = third

	head := first

	fmt.Print("反转前: ")
	head.traverse()
	fmt.Println()

	newHead := head.reverse()

	fmt.Print("反转后: ")
	newHead.traverse()
	fmt.Println()
}
running...
反转前: 1 2 3
反转后: 3 2 1

Golang 递归实现链表反转

package main

import "fmt"

type List struct {
	value string
	next  *List
}

func main() {
	// 头指针
	var head *List
	// 尾指针
	var last *List
	for i := 'A'; i < 'Z'+1; i++ {
		// 记录尾部的指针
		last = GenerateList(last, string(i))
		if head == nil {
			// 记录头部指针
			head = last
		}
	}
	fmt.Print("反转前的链表:")
	show(head)
	Recursion(head)
	fmt.Print("反转后的链表:")
	show(last)
	fmt.Println("")
}

// 递归反转
func Recursion(l *List, parme ...int) *List {
	// 第一次调用,不能传参
	if len(parme) == 0 {
		parme = []int{0}
	}
	// 节点不存在返回空,也就是最后一个节点的next值
	if l == nil {
		return nil
	}
	// 通过递归,获取到下一个节点的值
	next := Recursion(l.next, parme[0]+1)
	// 最后一个则返回当前节点
	if next == nil {
		return l
	}
	// 反转接,将下一个节点指向上一个节点
	next.next = l
	if parme[0] == 0 {
		l.next = nil

	}
	// 返回当前节点
	return l
}

// 生成链表值
func GenerateList(l *List, value string) *List {
	// 头链表直接返回
	if l == nil {
		return &List{value: value}
	}
	l.next = &List{value: value}
	return l.next
}

// 显示链表的值
func show(l *List) {
	for l != nil {
		fmt.Print(l.value)
		if l.next != nil {
			fmt.Print("->")
		}
		l = l.next
	}
	fmt.Println()
}
running...
反转前的链表:A->B->C->D->E->F->G->H->I->J->K->L->M->N->O->P->Q->R->S->T->U->V->W->X->Y->Z
反转后的链表:Z->Y->X->W->V->U->T->S->R->Q->P->O->N->M->L->K->J->I->H->G->F->E->D->C->B->A

内存对齐

type DemoA struct {
	A int8
	B int64
	C int16
}

type DemoB struct {
	A int8
	C int16
	B int64
}

这两个结构体占用的内存大小一样吗?

func main() {
	fmt.Println(unsafe.Sizeof(DemoA{}))
	fmt.Println(unsafe.Sizeof(DemoB{}))
}
running...
24
16

计算机存储数据都是以字节为单位来划分的,但是大部分处理器(CPU)都不是一个字节一个字节来存取内存的。

一般都是以 2 、 4 、8 、16 、32 这种2的幂次方来存取数据的。

为了让处理器的读取更加高效,程序在编译的时候一般会安装处理器一次存储的字节数的倍数来存储数据。

如果没有内存对齐的话如下图:

在这里插入图片描述
计算机需要读取两次内存才可以将数据读取出来,如果进行了内存对齐,那么只需要一次就可以将数据读出。并且如果是单次读取,可以一次就完成这个原子操作,不会被其他操作或打断。

代码解释 DemoA

  • 第一个元素 int8 是一个字节,填充7字节。
  • 第二个元素 int64 是 8 字节。
  • 第三个元素 int16 是 2 字节,填充 6 字节。
    加起来就是 24 字节。

代码解释 DemoB

  • 第一个元素 int8 与第二个元素加起来是3字节,填充5字节,加上第三个元素8字节,所以是16字节。

反转字符串

反转字符串,可能包含有中文、数字、英文。
(要求反转原有字符串,不创建新的存储空间)

package main

import "fmt"

func reverse(str string) string {
	s := []rune(str)
	for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
		s[i], s[j] = s[j], s[i]
	}
	return string(s)
}

func main() {
	fmt.Println(reverse("知其黑,受其白CBA234"))
}
running...
432ABC白其受,黑其知

协程顺序执行问题

使用三个协程,每秒钟打印 cat、dog、fish。

顺序不能变化(协程 1 打印 cat、协程 2 打印 dog、协程 3 打印 dish)

无限循环即可。

package main

import (
	"fmt"
	"sync"
	"time"
)

func cat(fishCH, catCH chan struct{}, wg *sync.WaitGroup) {
	wg.Add(1)
	go func() {
		for {
			fmt.Println("cat")
			catCH <- struct{}{}
			<-fishCH
		}
		wg.Done()
	}()
}

func dog(catCH, dogCH chan struct{}, wg *sync.WaitGroup) {
	wg.Add(1)
	go func() {
		for {
			<-catCH
			fmt.Println("dog")
			dogCH <- struct{}{}
		}
		wg.Done()
	}()
}

func fish(dogCH, fishCH chan struct{}, wg *sync.WaitGroup) {
	wg.Add(1)
	go func() {
		for {
			<-dogCH
			fmt.Println("fish")
			time.Sleep(time.Second)
			fishCH <- struct{}{}
		}
		wg.Done()
	}()
}

func main() {
	catCH := make(chan struct{})
	dogCH := make(chan struct{})
	fishCH := make(chan struct{})

	wg := sync.WaitGroup{}
	cat(fishCH, catCH, &wg)
	dog(catCH, dogCH, &wg)
	fish(dogCH, fishCH, &wg)
	wg.Wait()

}

在这里插入图片描述

tag 的原理

tag 的原理其实就是反射。

package main

import (
	"fmt"
	"reflect"
)

type UserInfo struct {
	Name     string `csdn:"BLOG" abc:"name"`
	PublicWX string `csdn:"BLOG_PUBLIC" abc:"publicABC"`
}

func PrintTag(ptr interface{}) {
	reType := reflect.TypeOf(ptr)
	if reType.Kind() != reflect.Ptr || reType.Elem().Kind() != reflect.Struct {
		panic("传入的参数不是结构体指针")
		return
	}
	v := reflect.ValueOf(ptr).Elem()
	fmt.Println(v)
	for i := 0; i < v.NumField(); i++ {
		field := v.Type().Field(i)
		tag := field.Tag
		labelTag := tag.Get("csdn")
		fmt.Println(labelTag)
	}
}

func main() {
	userInfo := &UserInfo{
		Name:     "博客",
		PublicWX: "csdn",
	}
	PrintTag(userInfo)
}
running...
{博客 csdn}
BLOG
BLOG_PUBLIC