Last updated on 4 months ago
基础篇 变量类型 rune uint8
1 2 3 4 5 6 7 8 9 10 11 12 func traversalString () { s := "pprof.cn博客" for i := 0 ; i < len (s); i++ { fmt.Printf("%v(%c) " , s[i], s[i]) } fmt.Println() for _, r := range s { fmt.Printf("%v(%c) " , r, r) } fmt.Println() }
结果是
1 2 112 (p ) 112 (p ) 114 (r ) 111 (o ) 102 (f ) 46 (. ) 99 (c ) 110 (n ) 229 (å) 141 () 154 () 229 (å) 174 (®) 162 (¢)112 (p ) 112 (p ) 114 (r ) 111 (o ) 102 (f ) 46 (. ) 99 (c ) 110 (n ) 21338 (博) 23458 (客)
可以uint8是单独取出字符串中每个元素的ascii码进行处理,超出ascii码的范围就做不到了,像这种中文的就打印不出来
修改字符串 要修改一个字符串,首先需要把字符串转换成数组类型,然后再进行修改
数组赋值 1 2 3 4 5 6 7 8 9 10 11 12 var c [5 ]int {2 :40 ,4 :50 }for _,i := range c{ fmt.println (i) }
%s打印字符串 %p是打印地址 %v是检测变量类型自动打印 %#v是检测变量类型并详细的自动打印
切片 var s1 []int s2:=[]int{} 这个准确来说是
s3:=make([]int,0)
结构体 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 package main import ( "fmt" )type student struct { name string age int }func main () { var p1 student p1.name="yblue" p1.age=18 fmt.Printf("%#v\n%v\n" , p1, p1) p2:=student{} p2.name="usr0" fmt.Printf("%#v\n%v\n" , p2, p2) p3:=&student{ name: "root" , } fmt.Printf("%#v\n%v\n" , p3, p3) }
还有可能有别的创建结构体的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 type student struct { name string age int }func main () { m := make (map [string ]*student) stus := []student{ {name: "pprof.cn" , age: 18 }, {name: "测试" , age: 23 }, {name: "博客" , age: 28 }, } for _, stu := range stus { m[stu.name] = &stu } for k, v := range m { fmt.Println(k, "=>" , v.name) } }
方法和接收 Go语言中的方法(Method)是一种作用于特定类型变量的函数。这种特定类型变量叫做接收者(Receiver)。 跟c++的成员函数差不多,只有对应的接收者(go是结构体,c++是类)才能使用方法/成员函数
自定义函数跟方法的组成区别 自定义函数是func newPerson(name, city string, age int8) *person {} func后直接跟自定义的函数名,而方法是要先声明接收者变量和接收者类型
1 2 3 4 func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) { 函数体 }
举个栗子
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 type Person struct { name string age int8 }func NewPerson (name string , age int8 ) *Person { return &Person{ name: name, age: age, } }func (p Person) Dream() { fmt.Printf("%s的梦想是学好Go语言!\n" , p.name) }func main () { p1 := NewPerson("测试" , 25 ) p1.Dream() }
指针类型的接收者 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport "fmt" func (p *Person) SetAge(newage int ){ p.age=newage }type Person struct { age int name string }func main () { p1:=Person{ 18 , "小明" , } fmt.Println(p1.age) p1.SetAge(20 ) fmt.Println(p1.age) }
接口 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package mainimport "fmt" type Person interface { GetName() }type Student struct { Name string Age int }func (stu Student) GetName() { fmt.Println(stu.Name) }func main () { s := Student{ Name: "yblue" , Age: 18 , } var s1 Person = s s1.GetName() }
结构体与JSON序列化 JSON键值对是用来保存JS对象的一种方式,键/值对组合中的键名写在前面并用双引号””包裹,使用冒号:分隔,然后紧接着值;多个键值之间使用英文,分隔。
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 type Student struct { ID int Gender string Name string }type Class struct { Title string Students []*Student }func main () { c := &Class{ Title: "101" , Students: make ([]*Student, 0 , 200 ), } for i := 0 ; i < 10 ; i++ { stu := &Student{ Name: fmt.Sprintf("stu%02d" , i), Gender: "男" , ID: i, } c.Students = append (c.Students, stu) } data, err := json.Marshal(c) if err != nil { fmt.Println("json marshal failed" ) return } fmt.Printf("json:%s\n" , data) str := `{"Title":"101","Students":[{"ID":0,"Gender":"男","Name":"stu00"},{"ID":1,"Gender":"男","Name":"stu01"},{"ID":2,"Gender":"男","Name":"stu02"},{"ID":3,"Gender":"男","Name":"stu03"},{"ID":4,"Gender":"男","Name":"stu04"},{"ID":5,"Gender":"男","Name":"stu05"},{"ID":6,"Gender":"男","Name":"stu06"},{"ID":7,"Gender":"男","Name":"stu07"},{"ID":8,"Gender":"男","Name":"stu08"},{"ID":9,"Gender":"男","Name":"stu09"}]}` c1 := &Class{} err = json.Unmarshal([]byte (str), c1) if err != nil { fmt.Println("json unmarshal failed!" ) return } fmt.Printf("%#v\n" , c1) }
结构体,字段,方法,类型大统一 理解结构体、字段、方法和类型之间的关系是理解 Go 语言的重要基础。让我用一个简单的例子来说明这些概念之间的关系。
假设我们想创建一个程序来表示矩形,并能够计算其面积。我们可以使用结构体、字段、方法和类型来完成这个任务。
首先,我们定义一个矩形的结构体:
1 2 3 4 type Rectangle struct { Width float64 Height float64 }
在上面的代码中,我们创建了一个名为 Rectangle 的结构体,它有两个字段:Width 和 Height,分别表示矩形的宽度和高度。
接下来,我们可以为这个结构体定义一个方法来计算矩形的面积:
1 2 3 func (r Rectangle) Area() float64 { return r.Width * r.Height }
在上面的代码中,我们为 Rectangle 结构体定义了一个名为 Area 的方法。这个方法接收一个 Rectangle 类型的接收者(receiver),并返回一个 float64 类型的面积值。方法可以访问接收者结构体的字段,这里我们用 r.Width 和 r.Height 计算了面积。
现在,我们可以创建一个 Rectangle 类型的对象,并使用它的 Area 方法来计算面积:
1 2 3 4 5 func main () { rect := Rectangle{Width: 5.0 , Height: 3.0 } area := rect.Area() fmt.Printf("矩形的面积是: %f\n" , area) }
在上面的 main 函数中,我们创建了一个 Rectangle 类型的对象 rect,并调用了它的 Area 方法来计算面积。最终,我们打印出了矩形的面积。
这个例子涵盖了以下概念:
结构体 (Rectangle):用于组织相关字段的数据结构。 字段 (Width 和 Height):结构体中的属性或数据成员。 方法 (Area()):与特定类型关联的函数,用于执行特定的操作。 类型 (Rectangle):自定义的数据类型,具有自己的字段和方法。 希望这个例子有助于你理解结构体、字段、方法和类型之间的关系。如果你有更多问题或需要进一步解释,请随时提问。
网络编程 socket 看不懂搞不明白,过
tcp网络编程 创立客户端(client) 一个TCP客户端进行TCP通信的流程如下: 1.建立与服务端的链接 2.进行数据收发 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 package mainimport ( "bufio" "fmt" "log" "net" "os" "strings" )func main () { conn, err := net.Dial("tcp" , ":12345" ) if err != nil { log.Fatalln(err) } defer conn.Close() input := bufio.NewReader(os.Stdin) for { ip, _ := input.ReadString('\n' ) ipinfo := strings.Trim(ip, "\n\r" ) if strings.ToUpper(ipinfo) == "Q" { return } _, err := conn.Write([]byte (ipinfo)) if err != nil { log.Fatalln(err) } buf := [512 ]byte {} n, err := conn.Read(buf[:]) if err != nil { log.Fatalln(err) } fmt.Println(string (buf[:n])) } }
建立服务端(server) TCP服务端程序的处理流程:
1.监听端口
2.接收客户端请求建立链接
3.创建goroutine处理链接
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 package mainimport ( "bufio" "fmt" "log" "net" )func process (conn net.Conn) { defer conn.Close() reader := bufio.NewReader(conn) for { rd := [512 ]byte {} n, err := reader.Read(rd[:]) if err != nil { log.Fatalln(err) } fmt.Println(string (rd[:n])) recv := fmt.Sprintf("你已成功发送,数据为:%s" , string (rd[:n])) conn.Write([]byte (recv)) } }func main () { listen, err := net.Listen("tcp" , ":12345" ) if err != nil { log.Fatalln(err) } for { conn, err := listen.Accept() if err != nil { log.Fatalln(err) } go process(conn) } }
UDP网络编程 就是把前面的tcp换成udp,但教程还给了udp专门的socket的函数,但是感觉没什么大用,碰到再学吧
tcp黏包 就是数据发送过多,让数据包重合了
http编程 服务端 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 package mainimport ( "fmt" "net/http" )func main () { http.HandleFunc("/index" , myHandler) http.ListenAndServe("127.0.0.1:1234" , nil ) }func myHandler (w http.ResponseWriter, r *http.Request) { defer r.Body.Close() fmt.Println(r.RemoteAddr, ":连接成功" ) fmt.Println("method:" , r.Method) fmt.Println("url:" , r.URL.Path) fmt.Println("header:" , r.Header) fmt.Println("body:" , r.Body) w.Write([]byte ("这是服务器发来的消息" )) }
客户端 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 package mainimport ( "fmt" "io" "log" "net/http" )func main () { resp, err := http.Get("http://127.0.0.1:1234/index" ) if err != nil { log.Fatalln(err) } defer resp.Body.Close() fmt.Println(resp.Status) fmt.Println(resp.Header) buf := make ([]byte , 1024 ) for { n, err := resp.Body.Read(buf) if err != nil && err != io.EOF { log.Fatalln(err) } else { fmt.Println("读取完成" ) fmt.Println(string (buf[:n])) } } }
websocket编程(聊天室) https://github.com/taosu0216/go_stu/tree/main/Internet_coding/web_socket 基本完成,代码都能看懂但是纯自己写应该是写不出来,前端代码没看,项目不完整(不能ip:端口/路径来访问,等着再学学再说吧)
并发 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package mainimport ( "fmt" "sync" )var wg sync.WaitGroupfunc hello (i int ) { defer wg.Done() fmt.Println("Goroutine " , i, " 号开始执行" ) }func main () { for i := 1 ; i <= 10 ; i++ { wg.Add(1 ) go hello(i) go hello(i + 10 ) } wg.Wait() }
channel 通道可以关闭,但关闭通道不是必须的 ch:=make(chan []int,65535) 此时的ch就是一个用于接收数值数组的,后面的数字代表通道最大容量,可以不加 close(ch) 这是关闭通道的操作,关闭通道可以再从channel中读取,但是不能再存入了
channel分为有缓冲和无缓冲 无缓冲就是在创建channel时不加数字,此时相当于一个单纯的消息通道作用,有传入就必须有接收,否则会发生恐慌 无缓冲通道又叫同步通道 有缓冲就是有数字,此时channel类似快递站,可以暂存消息,有信息传入channel后,可以不用立刻有接收方
判断channel是否关闭 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 package mainimport ( "fmt" )func main () { ch1 := make (chan int , 20 ) ch2 := make (chan int , 20 ) go func () { for i := 0 ; i <= 10 ; i++ { ch1 <- i } close (ch1) }() go func () { for { i, ok := <-ch1 if !ok { break } ch2 <- i * i } defer close (ch2) }() for i := range ch2 { fmt.Println(i) } }
worker pool(Goroutine池) 计算一个数字的各个位数之和,例如数字123,结果为1+2+3=6 随机生成数字进行计算
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 package mainimport ( "fmt" "math/rand" )type Result struct { job *Job sum int }type Job struct { Id int Random_number int }func main () { send_channel := make (chan *Job, 128 ) result_channel := make (chan *Result, 128 ) worker_pool(64 , send_channel, result_channel) go func (result_c chan *Result) { for result := range result_c { fmt.Println("第" , result.job.Id+1 , "个job,它的随机值是:" , result.job.Random_number, "是它的结果是:" , result.sum) } }(result_channel) defer close (send_channel) defer close (result_channel) for i := 0 ; i < 1000 ; i++ { rand_num := rand.Int() job := &Job{ Id: i, Random_number: rand_num, } send_channel <- job } }func worker_pool (num int , sc chan *Job, rc chan *Result) { for i := 0 ; i < num; i++ { go func (sc chan *Job, rc chan *Result) { for job := range sc { r_num := job.Random_number sum := 0 for r_num != 0 { tmp := r_num % 10 sum += tmp r_num = r_num / 10 } re := &Result{ job: job, sum: sum, } rc <- re } }(sc, rc) } }
定时器 timer 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 type Timer struct { C <- chan Time }type Time struct { }func Now () Timefunc NewTimer (d Duration) *Timer
正式程序
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 package mainimport ( "fmt" "time" )func main () { fmt.Println(time.Now()) t := <- time.NewTimer(time.Second).C fmt.Printf("%v" , t) timer2 := time.NewTimer(time.Second) for { <-timer2.C fmt.Println("时间到" ) } fmt.Println(time.Now()) t1 := time.NewTimer(2 * time.Second) <-t1.C fmt.Println("过去了2秒" ) t := time.NewTimer(time.Second) go func () { <-t.C fmt.Println("收到时间" ) }() time.Sleep(2 * time.Second) for t.Stop() { fmt.Println("已关闭" ) } timer5 := time.NewTimer(3 * time.Second) timer5.Reset(1 * time.Second) fmt.Println(time.Now()) fmt.Println(<-timer5.C) }
ticker 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 package mainimport ( "fmt" "time" )func main () { ticker := time.NewTicker(1 * time.Second) i := 0 go func () { for { i++ fmt.Println(<-ticker.C) if i == 5 { ticker.Stop() } } }() for { } }
timer和ticker的区别 Ticker在Go语言中是专门用来实现定期任务的一个结构体。它的英文意思是“计时器”。 具体来说: Ticker代表一个计时器,可以定期生成时间脉冲。 它通过时间通道(channel)定期地发送当前时间。 不同于Timer是一次性的,Ticker是自动重复工作的。
使用Ticker的主要场景包括: 需要每隔一定时间就执行一次的重复任务,比如每隔1秒打印一次日志。 需要基于时间来驱动和同步其他goroutine工作,例如心跳检测。 需要周期性执行某些操作而不是手动定时,如每10分钟保存一次数据。
Ticker的工作流程: 使用time.NewTicker创建一个Ticker对象 它会打开一个时间通道 从该通道中周期性读取时间信号 根据时间信号驱动后续任务执行 可以调用Stop停止Ticker工作
select _,ok := <- channel 占位符代表的是从channel中拿取的数据,ok(bool)是判断是否有数据,如果ok为false则说明通道内无数据或者通道关闭
1 2 3 4 5 6 7 8 select {case <-chan1: case chan2 <- 1 : default : }
如果多个通道同时就绪,则只选择一个随机执行,剩下的数据都被丢弃,所以写的时候要注意好逻辑,否则可能会丢失数据
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 package mainimport ( "fmt" )func main () { int_chan := make (chan int , 1 ) string_chan := make (chan string , 1 ) go func () { int_chan <- 1 }() go func () { string_chan <- "hello" }() select { case value := <-int_chan: fmt.Println("int:" , value) case value := <-string_chan: fmt.Println("string:" , value) } fmt.Println("main结束" ) }
sync 讲的很清晰明了了,就是var wg sync.WaitGroup的时候要定义成全局变量 sync.Once和sync.Map暂时好像还用不到,先过
并发安全和锁 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 var x int64 var wg sync.WaitGroupfunc add () { for i := 0 ; i < 5000 ; i++ { x = x + 1 } wg.Done() }func main () { wg.Add(2 ) go add() go add() wg.Wait() fmt.Println(x) }
Mutex, mutual exclusion,即互斥锁的英文
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 package mainimport ( "fmt" "sync" "time" )var x int64 var wg sync.WaitGroupvar lock sync.Mutexfunc add () { for i := 0 ; i < 200 ; i++ { lock.Lock() x += 1 lock.Unlock() } wg.Done() }func main () { fmt.Println(time.Now()) wg.Add(2 ) go add() go add() wg.Wait() fmt.Println(x, time.Now()) }
读写锁 互斥锁是所有操作都被禁止,但是大部分应用场景是读多写少,互斥锁使用时不能读不能写,会堵塞进程降低性能,这个时候就可以使用读写锁. RWmutex
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 package mainimport ( "fmt" "sync" "time" )var ( x int64 wg sync.WaitGroup rwlock sync.RWMutex )func write () { rwlock.Lock() x = x + 1 rwlock.Unlock() time.Sleep(10 * time.Millisecond) wg.Done() }func read () { rwlock.RLock() time.Sleep(time.Millisecond) rwlock.RUnlock() wg.Done() }func main () { start := time.Now() for i := 0 ; i < 1000 ; i++ { wg.Add(1 ) go read() } for j := 0 ; j < 10 ; j++ { wg.Add(1 ) go write() } wg.Wait() end := time.Now() fmt.Println(end.Sub(start)) }
原子操作 看的不是很懂,把gmp看完再来过一遍吧
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 var x int64 var l sync.Mutexvar wg sync.WaitGroupfunc add () { x++ wg.Done() }func mutexAdd () { l.Lock() x++ l.Unlock() wg.Done() }func atomicAdd () { atomic.AddInt64(&x, 1 ) wg.Done() }func main () { start := time.Now() for i := 0 ; i < 10000 ; i++ { wg.Add(1 ) go atomicAdd() } wg.Wait() end := time.Now() fmt.Println(x) fmt.Println(end.Sub(start)) }
爬虫 爬虫步骤 明确目标(确定在哪个网站搜索) 爬(爬下内容) 取(筛选想要的) 处理数据(按照你的想法去处理)
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 package mainimport ( "fmt" "io" "net/http" "os" "regexp" "time" )var reQQemail = `(\d+)@qq.com` func Getemail (file *os.File) { start := time.Now() resp, err := http.Get("https://www.xyfinance.org/hot/516352" ) HandleErr(err, "http.Get url" ) defer resp.Body.Close() pagebody, err := io.ReadAll(resp.Body) HandleErr(err, "io.ReadAll" ) pageStr := string (pagebody) re := regexp.MustCompile(reQQemail) results := re.FindAllStringSubmatch(pageStr, -1 ) for index, result := range results { email := result[0 ] qq := result[1 ] fmt.Println("email:" , email) fmt.Println("QQ:" , qq) fmt.Fprintf(file, "%d. \nemail:%s\n" , index+1 , email) fmt.Fprintf(file, "QQ:%s\n" , qq) } end := time.Now() fmt.Println("线程用时:" , end.Sub(start)) }func HandleErr (err error , why string ) { if err != nil { fmt.Println(why, err) } }func main () { file, err := os.Create("tmp/qq_email.txt" ) HandleErr(err, "os.Create" ) defer file.Close() Getemail(file) }
并发爬虫下载图片(速度非常慢)https://github.com/taosu0216/go_stu/tree/main/Spider
context上下文 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 package mainimport ( "context" "fmt" "sync" "time" )var wg sync.WaitGroupfunc worker (ctx context.Context) { LOOP: for { fmt.Println("worker" ) time.Sleep(1 * time.Second) select { case <-ctx.Done(): break LOOP default : } } wg.Done() }func main () { ctx, cancel := context.WithCancel(context.Background()) wg.Add(1 ) go worker(ctx) time.Sleep(4 * time.Second) cancel() wg.Wait() fmt.Println("over" ) }
context.Context是一个接口,签名如下
1 2 3 4 5 6 type Context interface { Deadline() (deadline time.Time, ok bool ) Done() <-chan struct {} Err() error Value(key interface {}) interface {} }
Deadline方法需要返回当前Context被取消的时间,也就是完成工作的截止时间(deadline)
Done方法需要返回一个Channel,这个Channel会在当前工作完成或者上下文被取消之后关闭,多次调用Done方法会返回同一个Channel
Err方法会返回当前Context结束的原因,它只会在Done返回的Channel被关闭时才会返回非空的值 如果当前Context被取消就会返回Canceled错误 如果当前Context超时就会返回DeadlineExceeded错误
Value方法会从Context中返回键对应的值,对于同一个上下文来说,多次调用Value 并传入相同的Key会返回相同的结果,该方法仅用于传递跨API和进程间跟请求域的数据
Background()和TODO() Go内置两个函数:Background()和TODO(),这两个函数分别返回一个实现了Context接口的background和todo。我们代码中最开始都是以这两个内置的上下文对象作为最顶层的partent context,衍生出更多的子上下文对象。
Background()主要用于main函数、初始化以及测试代码中,作为Context这个树结构的最顶层的Context,也就是根Context。
TODO(),它目前还不知道具体的使用场景,如果我们不知道该使用什么Context的时候,可以使用这个。
background和todo本质上都是emptyCtx结构体类型,是一个不可取消,没有设置截止时间,没有携带任何值的Context。
With函数 此外,context包中还定义了四个With系列函数。
WithCancel 函数签名如下
1 func WithCancel (parent Context) (ctx Context, cancel CancelFunc)
WithCancel返回带有新Done通道的父节点的副本。当调用返回的cancel函数或当关闭父上下文的Done通道时,将关闭返回上下文的Done通道,无论先发生什么情况。 取消此上下文将释放与其关联的资源,因此代码应该在此上下文中运行的操作完成后立即调用cancel。
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 func gen (ctx context.Context) <-chan int { dst := make (chan int ) n := 1 go func () { for { select { case <-ctx.Done(): return case dst <- n: n++ } } }() return dst }func main () { ctx, cancel := context.WithCancel(context.Background()) defer cancel() for n := range gen(ctx) { fmt.Println(n) if n == 5 { break } } }
WithDeadline 通常是用cancel()就可以做到,但是有些时候可能会因为各种原因没有调用cancel(),这里的WithDeadline就是在cancel()出意外没有调用时的保底手段 签名如下
1 func WithDeadline (parent Context, deadline time.Time) (Context, CancelFunc)
设置dead时间,并返回一个新的上下文对象,这个新的上下文对象是对原来的上下文对象的复制,但是多了生效时间,后面再给函数传参时就可以传递这个新的对象而不是原来的对象 除了在超时之外自动取消,也可以用返回的cancelFunc手动取消
WithTimeOut 跟上面差不多,但timeout是隔多长时间后自动停止,设置的是一个最大时间,而前面的deadline则是具体的时间点,是绝对时间
WithValue 函数签名如下
1 func WithValue (parent Context, key, val interface {}) Context
反射 这篇讲的挺细的https://juejin.cn/post/6844903559335526407?searchId=202310120916499405104F20617909405F
reflect包封装了反射相关的方法 获取类型信息:reflect.TypeOf,是静态的 获取值信息:reflect.ValueOf,是动态的
反射获取interface值信息 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 package mainimport ( "fmt" "reflect" )func main () { x := 123.456 reflect_value(x) }func reflect_value (x interface {}) { v := reflect.ValueOf(x) fmt.Println(v) fmt.Printf("%T\n" , v) k := v.Kind() fmt.Println(k) fmt.Printf("%T\n" , k) switch k { case reflect.Int: fmt.Println("是" , v.Int()) default : fmt.Println("不是" ,v.Float()) } }
反射获取interface类型信息 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 package mainimport ( "fmt" "reflect" )func main () { x := 23.45 re(x) }func re (x interface {}) { value := reflect.TypeOf(x) fmt.Println("x的类型是:" , value) k := value.Kind() fmt.Println(k) switch k { case reflect.Float64: fmt.Println("x的类型是" , k) default : fmt.Println("不是float64" ) } }
反射修改值信息 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 package mainimport ( "fmt" "reflect" )func main () { x := 3.14 fmt.Println("一开始的x:" , x) re(&x) fmt.Println("修改后的x:" , x) }func re (x interface {}) { v := reflect.ValueOf(x) fmt.Printf("类型是:%T,值是:%v\n" , v, v) k := v.Kind() switch k { case reflect.Float64: v.SetFloat(567.89 ) fmt.Println("case 1 is " , v) case reflect.Ptr: v.Elem().SetFloat(12.3 ) fmt.Println("case 2 is " , v) fmt.Println(v.Pointer()) } }
查看类型、字段和方法 很乱,看的头晕,以后再学
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 package mainimport ( "fmt" "reflect" )type User struct { Id int Name string Age int }func (u User) Hello() { fmt.Println("hello" ) }func main () { u := User{ Id: 1 , Name: "小明" , Age: 18 , } Poni(u) }func Poni (u interface {}) { ty := reflect.TypeOf(u) fmt.Println("类型为: " , ty) fmt.Println("字符串类型: " , ty.Name()) value := reflect.ValueOf(u) fmt.Println("reflect.ValueOf(u) = " , value) for i := 0 ; i < ty.NumField(); i++ { v := ty.Field(i) fmt.Println("value.Field(i) = " , v) fmt.Printf("%s : %v\n" , v.Name, v.Type) val := value.Field(i).Interface() fmt.Println("value.Field(i).Interface() = " , val) } fmt.Println("----------------------方法---------------------" ) for i := 0 ; i < ty.NumMethod(); i++ { m := ty.Method(i) fmt.Println("m.Name: " , m.Name, " m.Type: " , m.Type) } }