go基础

基础#

函数#

命名返回值可以在函数体内作为一个普通的局部变量使用,而不需要在函数体内通过 :=var 声明。

闭包#

闭包就是一个函数“捕获”了和它在同一作用域的其他常量和变量。这就意味着当闭包被调用的时候,不管在程序什么地方调用,闭包都能够使用这些常量或者变量。它不关心这些捕获了的变量和常量是否已经超出了作用域,所以只要闭包还在使用它,这些变量就还会存在。

闭包是由函数及其相关引用环境组合而成的实体,一般通过在匿名函数中引用外部函数的局部变量或包全局变量构成。

如果函数返回的闭包引用了该函数的局部变量(参数或函数内部变量):

  1. 多次调用该函数,返回的多个闭包所引用的外部变量是多个副本,原因是每次调用函数都会为局部变量分配内存。
  2. 用一个闭包函数调用多次,如果该闭包修改了其引用的外部变量,则每一次调用该闭包对该外部变量都有影响,因为闭包函数共享外部引用。(如果一个函数调用返回的闭包引用修改了全局变量,则每次调用都会影响全局变量)
  3. 同一个函数返回的多个闭包共享该函数的局部变量。

空结构体#

在 Go 语言中,我们可以定义空结构体(empty struct),即没有任何成员变量的结构体,使用关键字 struct{} 来表示。这种结构体似乎没有任何用处,但实际上它在 Go 语言中的应用非常广泛。

1、空结构体的定义和初始化

空结构体是指不包含任何字段的结构体。在 Golang 中,可以使用 struct{} 来定义一个空结构体。下面是一个简单的示例:

Copy Highlighter-hljs
func main() { var s struct{} fmt.Printf("%#v", s) // 输出: struct {}{} }

注意,在打印时使用了 %#v 占位符,这个占位符可以将变量以 Go 语法格式输出。

需要注意的是,空结构体变量实际上不占用任何内存空间,也就是说,它的大小是 0 字节。

2、空结构体的大小和内存占用

正如上面提到的,空结构体的大小是 0 字节。这意味着它不占用任何内存空间。这一点可以通过使用 unsafe.Sizeof 函数来验证:

Copy Highlighter-hljs
package main import ( "fmt" "unsafe" ) func main() { var s struct{} fmt.Printf("Size of struct{}: %v", unsafe.Sizeof(s)) // 输出: Size of struct{}: 0 }

需要注意的是,尽管空结构体的大小为 0,但它并不意味着它不能被作为函数参数或返回值传递。因为在 Go 中,每个类型都有自己的类型信息,可以用于类型检查和转换。因此,即使是空结构体,在类型系统中也有它自己的位置和作用。

3、空结构体作为占位符

空结构体最常见的用途是作为占位符。在函数或方法签名中,如果没有任何参数或返回值,那么可以使用空结构体来标识这个函数或方法。下面是一个简单的示例:

Copy Highlighter-hljs
func doSomething() struct{} { fmt.Println("Doing something") return struct{}{} } func main() { doSomething() }

在 main 函数中,我们调用 doSomething 函数。由于它没有返回任何值,所以我们不需要将其结果存储在变量中。

需要注意的是,在这个示例中,我们将返回值的类型显式指定为 struct{}。这是因为如果不指定返回值的类型,那么 Go 编译器会将它默认解析为 interface{} 类型。在这种情况下,每次调用 doSomething 函数都会分配一个新的空接口对象,这可能会带来性能问题。

4、空结构体作为通道元素

空结构体还可以用作通道的元素类型。在 Go 中,通道是一种用于在协程之间进行通信和同步的机制。使用通道时,我们需要指定通道中元素的类型。

如果我们不需要在通道中传输任何值,那么可以使用空结构体作为元素类型。下面是一个简单的示例:

Copy Highlighter-hljs
func main() { c := make(chan struct{}) go func() { fmt.Println("Goroutine is running") c <- struct{}{} }() <-c fmt.Println("Goroutine is done") }

在这个示例中,我们创建了一个名为 c 的通道,并将其元素类型指定为 struct{}。然后,我们在一个新的协程中运行一些代码,并在协程中向通道中发送一个空结构体。在 main 函数中,我们从通道中接收一个元素,这里实际上是在等待协程的结束。一旦我们接收到了一个元素,我们就会打印出 "Goroutine is done"。

需要注意的是,在这个示例中,我们并没有向通道中发送任何有用的数据。相反,我们只是使用通道来同步协程之间的执行。这种方法对于实现复杂的并发模型非常有用,因为它可以避免使用显式的互斥量或信号量来实现同步和通信。

5、空结构体作为 map 的占位符

在 Go 中,map 是一种用于存储键值对的数据结构。如果我们只需要一个键集合,而不需要存储任何值,那么可以使用空结构体作为 map 的值类型。下面是一个简单的示例:

Copy Highlighter-hljs
func main() { m := make(map[string]struct{}) m["key1"] = struct{}{} m["key2"] = struct{}{} m["key3"] = struct{}{} fmt.Println(len(m)) // 输出: 3 }

在这个示例中,我们创建了一个名为 m 的 map,并将其值类型指定为 struct{}。然后,我们向 map 中添加了三个键,它们的值都是空结构体。最后,我们打印了 map 的长度,结果为 3。

需要注意的是,在这个示例中,我们并没有使用空结构体的任何其他特性。我们只是使用它作为 map 的值类型,因为我们不需要在 map 中存储任何值。

6、空结构体作为方法接收器

在 Go 中,方法是一种将函数与特定类型相关联的机制。如果我们不需要访问方法中的任何接收器字段,那么可以使用空结构体作为接收器类型。下面是一个简单的示例:

Copy Highlighter-hljs
type MyStruct struct{} func (m MyStruct) DoSomething() { fmt.Println("Method is called") } func main() { s := MyStruct{} s.DoSomething() }

在 main 函数中,我们创建了一个 MyStruct 实例 s,然后调用了它的 DoSomething 方法。由于我们不需要在方法中访问接收器的任何字段,所以我们可以使用空结构体作为接收器类型。

需要注意的是,即使我们在方法中使用空结构体作为接收器类型,我们仍然可以将其他参数传递给该方法。例如,我们可以像下面这样修改 DoSomething 方法:

Copy Highlighter-hljs
func (m MyStruct) DoSomething(x int, y string) { fmt.Println("Method is called with", x, y) }

在这个示例中,我们向 DoSomething 方法添加了两个参数。然而,我们仍然可以使用空结构体作为接收器类型。

7、空结构体作为接口实现

在 Go 中,接口是一种定义对象行为的机制。如果我们不需要实现接口的任何方法,那么可以使用空结构体作为实现。下面是一个简单的示例:

Copy Highlighter-hljs
type MyInterface interface { DoSomething() } type MyStruct struct{} func (m MyStruct) DoSomething() { fmt.Println("Method is called") } func main() { s := MyStruct{} var i MyInterface = s i.DoSomething() }

我们还定义了一个名为 MyStruct 的结构体,并为其实现了 DoSomething 方法。

在 main 函数中,我们创建了一个 MyStruct 实例 s,然后将其分配给 MyInterface 类型的变量i。由于 MyStruct 实现了 DoSomething 方法,所以我们可以调用 i.DoSomething 方法,并打印出一条消息。

需要注意的是,在这个示例中,我们并没有为接口实现添加任何特殊。我们只是使用空结构体作为实现,因为我们不需要实现接口的任何方法。

8、 空结构体作为信号量

在 Go 中,我们可以使用空结构体作为信号量,以控制并发访问。下面是一个简单的示例

Copy Highlighter-hljs
package ch1 import ( "fmt" "io/ioutil" "net/http" "sync" "testing" ) func worker(urls []string, wg *sync.WaitGroup, limitCh chan struct{}) { defer wg.Done() wg1 := sync.WaitGroup{} for _, url := range urls { limitCh <- struct{}{} // 占用一个通道位置,限制并发数量 wg1.Add(1) go func(url string) { defer wg1.Done() defer func() { <-limitCh }() // 完成任务后释放通道位置 res, err := http.Get(url) if err != nil { fmt.Printf("error fetching %s:%s", url, err) return } //处理响应数据 defer res.Body.Close() bytes, err := ioutil.ReadAll(res.Body) if err != nil { fmt.Println(err) return } else { fmt.Println(string(bytes)) } }(url) } wg1.Wait() } func TestChan(t *testing.T) { urls := []string{ "http://127.0.0.1:8080/page1", "http://127.0.0.1:8080/page2", "http://127.0.0.1:8080/page3", "http://127.0.0.1:8080/page4", "http://127.0.0.1:8080/page5", } maxWorks := 2 // 最大并发数 limitCh := make(chan struct{}, maxWorks) var wg sync.WaitGroup wg.Add(maxWorks) fmt.Println(urls[:3]) fmt.Println(urls[3:]) go worker(urls[:3], &wg, limitCh) go worker(urls[3:], &wg, limitCh) // time.Sleep(2 * time.Second) wg.Wait() }

在上述代码中,我们创建了两个工作协程(worker),每个协程负责处理一部分URL列表。limitCh 通道的容量被设置为 maxWorkers,这样可以确保同时进行的请求不超过指定的并发数。

在每个协程的任务循环中,我们首先通过 limitCh <- struct{}{} 获取一个通道位置,限制了并发请求数量。然后,通过匿名函数启动一个新的goroutine来执行HTTP请求。在请求完成后,通过 defer func() { <-limitCh }() 释放占用的通道位置。

使用这种方式,我们可以控制同时进行的请求数量,以避免对服务器施加过大的负载。

通常使用空结构体 {} 是为了节省内存,因为空结构体不占用任何空间。它只是作为一个信号来表示某个事件的发生或者触发一些操作。

需要注意的是,在这个示例中,我们使用空结构体作为信号量,以控制并发访问。由于空结构体不占用任何内存空间,所以它非常适合作为信号量。

限流--空结构体和管道#

Copy Highlighter-hljs
package main import ( "fmt" "sync/atomic" "time" ) var ( concurrent int32 concurrent_limit = make(chan struct{}, 10) ) func readDB() string { // 计数器进来加一 完成后减一 计数器支持并发 atomic.AddInt32(&concurrent, 1) fmt.Printf("readDB()调用并发 %d\n", atomic.LoadInt32(&concurrent)) time.Sleep(200 * time.Millisecond) atomic.AddInt32(&concurrent, -1) return "ok" } func handler3() { // 10的时候会阻塞,这样readDB()并发度不会超过10 concurrent_limit <- struct{}{} readDB() <-concurrent_limit return } func main() { for i := 0; i < 100; i++ { go handler3() } time.Sleep(3 * time.Second) }

atomic原子操作:

Golang的atomic包的原子操作是通过CPU指令实现的。在大多数CPU架构中,原子操作的实现都是基于32位或64位的寄存器。Golang的atomic包的原子操作函数会将变量的地址转换为指针型的变量,并使用CPU指令对这个指针型的变量进行操作。

例如,当我们调用AddInt32函数时,Golang会将变量的地址转换为int32类型的指针,并使用CPU提供的原子指令对这个指针型的变量进行加法操作。这样,就可以保证对共享变量的操作是原子性的。

需要注意的是,不同的CPU架构可能会提供不同的原子指令。因此,在使用atomic包的原子操作时,需要根据具体的CPU架构来选择合适的原子操作函数。

Golang的atomic包提供了一组原子操作函数,包括Add、CompareAndSwap、Load、Store、Swap等函数。这些函数的具体作用如下:

  • Add函数:用于对一个整数型的变量进行加法操作,并返回新的值。

  • CompareAndSwap函数:用于比较并交换一个指针型的变量的值。如果变量的值等于旧值,就将变量的值设置为新值,并返回true;否则,不修改变量的值,并返回false。

  • Load函数:用于获取一个指针型的变量的值。

  • Store函数:用于设置一个指针型的变量的值。

  • Swap函数:用于交换一个指针型的变量的值,并返回旧值。

接口超时控制#

Copy Highlighter-hljs
package main import ( "net/http" "time" ) func readDB() string { time.Sleep(200 * time.Millisecond) return "ok" } func home(w http.ResponseWriter, req *http.Request) { var resp string done := make(chan struct{}, 1) go func() { resp = readDB() done <- struct{}{} }() // 谁先解除阻塞 select { case <-done: case <-time.After(100 * time.Millisecond): resp = "timeout" } w.Write([]byte(resp)) } func main() { http.HandleFunc("/", home) http.ListenAndServe("127.0.0.1:5678", nil) }

context#

Copy Highlighter-hljs
https://www.bilibili.com/read/cv24661572

切片#

Copy Highlighter-hljs
make([]T, size, cap)

切片的本质就是对底层数组的封装,它包含了三个信息:底层数组的指针、切片的长度(len)和切片的容量(cap)。

要检查切片是否为空,请始终使用len(s) == 0来判断。

切片唯一合法的比较操作是和nil比较。 一个nil值的切片并没有底层数组,一个nil值的切片的长度和容量都是0

切片的赋值拷贝:

Copy Highlighter-hljs
func main() { arr := make([]int,3,5) //[0 0 0] arr[0],arr[1],arr[2] = 2,7,9 // 上面已声明 可以直接赋值 brr := arr //将arr直接赋值给brr,arr和brr共用一个底层数组 brr[0] = 100 fmt.Println(arr) //[100 7 9] fmt.Println(brr) //[100 7 9] }
Copy Highlighter-hljs
func main() { arr := make([]int,3,5) arr[0],arr[1],arr[2] = 2,7,9 // 注意: arr[3] 是不能赋值的 brr := append(arr,8) // 构造一个新切片 brr fmt.Printf("%d %d\n",len(arr),len(brr)) // 3 4 fmt.Printf("%d %d\n",cap(arr),cap(brr)) // 5 5 fmt.Printf("%v %v\n",arr,brr) // [2 7 9] [2 7 9 8] brr = append(brr,8) arr[0] = 4 fmt.Println(brr[0]) // 4 brr = append(brr, 8) // 注意:此时 arr 和 brr 指向的内存地址已经不是同一个了(扩容引起的) arr[1] = 5 fmt.Println(brr[1]) // 7 fmt.Println(len(brr),cap(brr)) // 6 10 fmt.Println(len(arr),cap(arr)) // 5 5 }

由于切片是引用类型,所以a和b其实都指向了同一块内存地址。修改b的同时a的值也会发生变化。

append()方法为切片添加元素。

每个切片会指向一个底层数组,这个数组的容量够用就添加新增元素。当底层数组不能容纳新增的元素时,切片就会自动按照一定的策略进行“扩容”,此时该切片指向的底层数组就会更换。“扩容”操作往往发生在append()函数调用时,所以我们通常都需要用原变量接收append函数的返回值。

copy()函数可以迅速地将一个切片的数据复制到另外一个切片空间中copy(destSlice, srcSlice []T)srcSlice: 数据来源切片 destSlice: 目标切片

要从切片a中删除索引为index的元素,操作方法是a = append(a[:index], a[index+1:]...)

字符串#

字符串的本质是 【不可修改的byte数组】 可使用for range遍历

defer#

Copy Highlighter-hljs
func main() { c := 8 // 注意:defer后直接跟的是go语句,go语句中涉及的变量,注册的时候就已经 // 把这个变量值给计算好了 // 如果跟的是匿名函数,这个函数体里涉及的变量,它是在defer执行的时候才去计算的 defer fmt.Println("111",c) // 8 fmt.Println(c) defer fmt.Println("222",c) //8 defer func() { fmt.Println("333",c) // 100 }() c = 100 }

接口#

接口是一组行为规范的集合。

时间相关#

Copy Highlighter-hljs
package main import ( "fmt" "time" ) const ( DATE = "2006-01-02" TIME = "2006-01-02 15:04:05" ) func main() { t := time.Now() // time.Time fmt.Println(t.Unix()) // 时间戳 time.Sleep(500*time.Millisecond) // 时间相减 t1 := time.Now() // Time - Time = Duration diff := t1.Sub(t) fmt.Println(diff.Milliseconds()) // 更常用的 相减 fmt.Println(time.Since(t).Milliseconds()) // time.Duration 表示时间段的持续时间 // 使用time.Duration 来进行时间间隔的计算和处理 // Time + Duration = Time d := time.Duration(2*time.Second) t2 := t.Add(d) fmt.Println(t2.Unix()) // 时间格式化 fmt.Println(t.Format(DATE)) s := t.Format(TIME) fmt.Println(s) // 日期 转换为 Time 格式 t3,_ := time.Parse(TIME,s) fmt.Println(t3.Unix()) // 指定时区 loc,_ := time.LoadLocation("Asia/Shanghai") t4,_ := time.ParseInLocation(TIME,s,loc) fmt.Println(t4.Unix()) }

文件相关#

Copy Highlighter-hljs
package main import ( "bufio" "fmt" "io" "os" ) func main() { // 只能读 file,err := os.Open("a.txt") // 根据第二个参数,可以读写和创建 // file, err := os.Openfile("a.txt",os.O_RDWR|os.O_CREATE,755) // file.Write() file.Read() if err != nil { fmt.Println("打开文件失败",err) return } defer file.Close() reader := bufio.NewReader(file) for { line,err := reader.ReadString('\n') if err != nil{ if err == io.EOF{ // End Of Line fmt.Println(line) break; }else{ fmt.Println("读文件发生错误",err) } }else{ // ReadString 已经将换行加入到line中 fmt.Print(line) } } }

单元测试和基准测试#

Copy Highlighter-hljs
// 单元测试 文件 json_test.go package main import ( "encoding/json" "fmt" "testing" ) type Student struct { Name string Age int Gender bool } type Class struct { Id string Students []Student } var ( s = Student{"张三",18,true} c = Class{ Id:"1(2)班", Students: []Student{s,s}, } ) // 单元测试文件必须以 下换线 _test.go 结尾 // 运行 go test json_test.go -v // -v 使 print函数正常输出 // 多个测试函数时 只需要运行一个 加 -run=Json 参数 -count=1 去除缓存 // 单元测试 函数的参数必须是 *testing.T // 单元测试目的是证明一段函数它的逻辑是正确的 func TestJson(t *testing.T){ bytes,err := json.Marshal(c) if err != nil { // 整个函数会直接退出 后面的代码不再执行 t.Fail() } var c2 Class err = json.Unmarshal(bytes,&c2) if err != nil { t.Fail() } fmt.Println("json没毛病") }

基准测试实际上是性能测试,要把一个函数反复的调很多次,来看看他的速度怎么样以及在CPU在内存的开销,

*文件名需要以_test.go结尾,函数需要以Benchmark开头,参数是 b testing.B

Copy Highlighter-hljs
package ch1 import ( "encoding/json" "testing" ) type Student struct { Name string Age int Gender bool } type Class struct { Id string Students []Student } var ( s = Student{"张三",18,true} c = Class{ Id:"1(2)班", Students: []Student{s,s}, } ) // 运行 go test json_test.go -bench=Json // 加上与内存相关的 -benchmem func BenchmarkJson(b *testing.B){ // b.N不是一个常量,它会根据你实际的运行耗时来动态的选择b.N // N是一个很大的数 for i := 0; i < b.N; i++ { bytes,_ := json.Marshal(c) var c2 Class json.Unmarshal(bytes,&c2) } }

依赖管理#

Copy Highlighter-hljs
// 开始一个项目 新建mod module名称可以随便取 go mod init moduleName // 所有的依赖都会依赖于模块的名称 moduleName // 导入包 首先是 go.mod里面这个module 名称 moduleName,下一级就是目录名称 import ( "fmt" "moduleName/util" // 目录名称 ) func main(){ fmt.Println(util.Name) // 注意:这个util是 文件中 package util 的util } // 注意:加载第三方库的两种方法: // 1、go get github.com/bytedance/sonic // 2、 go mod tidy

MPG调度模型#

CSP并发模型 即:通信顺序进程, 它的核心观点是:不要以共享内存的方式来通信,而是要通过通信来共享内存。

go并发的MPG模型:

  1. M 代表着一个内核线程,也可以称为一个工作线程。goroutine就是跑在M之上的
  2. P 代表着(Processor)处理器 它的主要用途就是用来执行goroutine的,一个P代表执行一个Go代码片段的基础(可以理解为上下文环境),所以它也维护了一个可运行的goroutine队列,和自由的goroutine队列,里面存储了所有需要它来执行的goroutine
  3. G 代表着goroutine 实际的数据结构(就是你封装的那个方法),并维护者goroutine 需要的栈、程序计数器以及它所在的M等信息。
  4. Seched 代表着一个调度器 它维护有存储空闲的M队列和空闲的P队列,可运行的G队列,自由的G队列以及调度器的一些状态信息等。

对于一个运行的GO程序,在调度器内部存在着:

  1. 全局M,P,G列表,分别用于记录当前所有的M,P,G信息;

  2. 空闲M,P,G列表,分别用于记录当前空闲可调度的M,P,G信息,以减少M,P,G的创建从而提升性能;

  3. 可运行G列表,存放等待调度器分配给P的G。

panic

Copy Highlighter-hljs
package main import ( "fmt" "time" ) func f1() { defer func() { err := recover() if err != nil { fmt.Printf("发生panic %s\n", err) } }() a, b := 3, 0 fmt.Println(a,b) _ = a/b // 发生painc fmt.Println("f1 finish") } func main() { go f1() time.Sleep(1*time.Second) fmt.Println("main fiinsh") }

并发安全:

Copy Highlighter-hljs
package main import ( "fmt" "sync" // "sync/atomic" ) var ( n int32 lock = sync.Mutex{} ) // 并发安全性 多个协程修改一个全局变量 最终的结果是不可预知的 func f1() { for i := 0; i < 100000; i++ { lock.Lock() // 确保只有一个协程进入,通过锁机制 n++ //atomic.AddInt32(&n,1) //也可以使用这个方法 原子性 原子操作 lock.Unlock() } fmt.Printf("n=%d\n",n) } func main() { wg := sync.WaitGroup{} wg.Add(2) go func(){ defer wg.Done() f1() }() go func(){ defer wg.Done() f1() }() wg.Wait() fmt.Printf("main n=%d\n",n) // 注意:n正常应该等于200000 实际结果不是 并发不安全应 }

管道#

先进先出

Copy Highlighter-hljs
func main() { ch := make(chan int,0) fmt.Println(time.Now().Unix()) go func(){ // 管道为0的时候发生阻塞,先执行下面协程的读取管道内容后,再执行下面的打印 ch <- 4 fmt.Println(time.Now().Unix(),"写入4成功") }() time.Sleep(2*time.Second) go func(){ a := <-ch fmt.Println("%d 读出 %d\n",time.Now().Unix(),a) }() time.Sleep(1*time.Second) }
Copy Highlighter-hljs
package main import ( "fmt" "sync" ) func main() { ch := make(chan int, 100) wg := sync.WaitGroup{} wg.Add(2) // 2个生产者,网channel终写入元素 go func() { defer wg.Done() for i := 0; i < 10; i++ { ch <- i } }() go func() { defer wg.Done() for i := 0; i < 10; i++ { ch <- i } }() // 1个消费者 // wg2 := sync.WaitGroup{} // wg2.Add(1) mc := make(chan struct{},0) go func() { sum := 0 for { a, ok := <-ch if !ok { // channel被关闭,且channel为空,此时ok才为false break } else { sum += a } } fmt.Printf("sum=%d\n",sum) // wg2.Done() mc <- struct{}{} // 执行完成后 帮main 解除下面的阻塞 }() wg.Wait() close(ch) //关闭管道后 只能读取管道 不能往里写 // wg2.Wait() // 这里的管到 只是充当一个 协程之间同步的功能 <-mc }

死锁#

Copy Highlighter-hljs
import ( "fmt" "sync" ) func main() { ch := make(chan int, 0) wg := sync.WaitGroup{} wg.Add(1) go func(){ defer wg.Done() // 阻塞 ch <- 1 fmt.Println("over") }() wg.Wait() }

gorm#

Copy Highlighter-hljs
CREATE TABLE `user` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `uid` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '用户id', `keywords` text NOT NULL COMMENT '索引词', `degree` char(2) DEFAULT NULL COMMENT '学历', `gender` char(1) DEFAULT NULL COMMENT '性别', `city` char(2) NOT NULL COMMENT '城市', PRIMARY KEY (`id`), UNIQUE KEY `uid` (`uid`) ) ENGINE=MyISAM AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;
Copy Highlighter-hljs
package main import ( "fmt" "os" "gorm.io/driver/mysql" "gorm.io/gorm" ) // 表中有多个字段,程序中不需要其他的字段 可以不写 type User struct{ // User 默认表users // Id int `gorm:"column:aid,primaryKey"` // 表中主键为aid的时候 Id int // 主键 Keyword string `gorm:"column:keywords"` // 指定和数据库的一致 City string } func (User) TableName() string { return "user" } func read(db *gorm.DB,city string) *User{ var users []User // 错误处理 err := db.Select("id,city").Where("city=?",city).Find(&users).Error if err != nil{ fmt.Println(err) } if len(users) > 0 { return &users[0] }else{ return nil } } func main() { dsn := "root:root@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Local" // db,err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) db,err := gorm.Open(mysql.Open(dsn), nil) if err != nil { fmt.Println(err) os.Exit(1) } user := read(db,"北京") if user != nil{ fmt.Printf("%+v\n",user) } user1 := User{ Id:2, Keyword: "golang", City: "天津", } db.Create(user1) }

go-redis#

Copy Highlighter-hljs
package main import ( "fmt" "os" "time" "github.com/go-redis/redis" ) // string func stringkey(client *redis.Client) { key := "name" value := "caibaotimes" // 0 永不过期 err := client.Set(key, value, 1*time.Second).Err() // 设置过期时间 client.Expire(key, 3*time.Second) checkError(err) v2, err := client.Get(key).Result() checkError(err) fmt.Println(v2) client.Del(key) } // list func list(client *redis.Client) { key := "list" values := []interface{}{1, 2, "中国"} err := client.RPush(key, values...).Err() checkError(err) v2, err := client.LRange(key, 0, -1).Result() checkError(err) fmt.Println(v2) } func hashTable(client *redis.Client) { m1 := map[string]interface{}{"Name": "张三", "Age": 18, "Height": 173.5} err := client.HMSet("学生1", m1).Err() checkError(err) name, err := client.HMGet("学生1", "Name", "Age").Result() checkError(err) fmt.Println(name) maps := client.HGetAll("学生1").Val() for k, v := range maps { fmt.Println(k, v) } err = client.HSet("学生2", "Name", "李四").Err() checkError(err) name1, err := client.HGet("学生2", "Name").Result() checkError(err) fmt.Println(name1) } func main() { client := redis.NewClient(&redis.Options{ Addr: "127.0.0.1:6379", Password: "", DB: 0, }) stringkey(client) list(client) hashTable(client) } func checkError(err error) { if err != nil { if err == redis.Nil { fmt.Println("key 不存在") } else { fmt.Println(err) os.Exit(1) } } }

发起http请求:#

Copy Highlighter-hljs
package ch1 import ( "fmt" "io/ioutil" "net/http" "net/url" "strings" "testing" ) func TestGetPost(t *testing.T){ // url.Values map[string][]string resp,err := http.PostForm("http://127.0.0.1/get_post",url.Values{"student_id":[]string{"学生1"}}) if err != nil{ fmt.Println(err) t.Fail() }else{ defer resp.Body.Close() bytes,err := ioutil.ReadAll(resp.Body) if err != nil{ fmt.Println(err) t.Fail() }else{ fmt.Println(string(bytes)) } } } func TesGetJson(t *testing.T){ client := http.Client{} reader := strings.NewReader(`{"student_id":"学生1"}`) // 注意:加反引号是为了json里面的引号不转义 request,err := http.NewRequest("POST","http://127.0.0.1:12345/get_json",reader) if err != nil{ fmt.Println(err) return } request.Header.Add("Content-Type","application/json") resp,err := client.Do(request) if err != nil{ fmt.Println(err) t.Fail() }else{ defer resp.Body.Close() bytes,err := ioutil.ReadAll(resp.Body) if err != nil{ fmt.Println(err) t.Fail() }else{ fmt.Println(bytes) } } }

pflag应用构建工具#

https://github.com/spf13/pflag

flag的作用是用来解析命令行的参数。

Pflag 包 FlagSet 定义:

FlagSet 是一些预先定义好的 Flag 的集合,几乎所有的 Pflag 操作,都需要借助 FlagSet 提供的方法来完成。在实际开发中,我们可以使用两种方法来获取并使用 FlagSet:

  • 方法一:调用NewFlagSet创建一个FlagSet

  • 方法二:使用pflag包定义的全局FlagSet:CommandLine。实际上CommandLine也是由NewFlagSet函数创建的。

    Copy Highlighter-hljs
    // 自定义FlagSet var version bool flagSet := pflag.NewFlagSet("test",pflag.ContinueOnError) flagSet.BoolVar(&version,"version",true,"Print version information and quit.") flagSet.Parse(os.Args[1:]) fmt.Println(version) //使用全局FlagSet var version bool pflag.BoolVarP(&version,"version","v",true,"Print version information and quit.")

    在一些不需要定义子命令的命令行工具中,我们可以直接使用全局的 FlagSet,更加简单方便。

flag.Parse():会把用户传递的命令行参数 解析为对应变量的值(一个是解析参数,一个是解析参数的值)

按flag值存储位置分为两种:保存在指针或绑定到变量

flag值保存在指针:

Copy Highlighter-hljs
// 1、支持长选项,默认值和使用说明文本 var name = pflag.String("name","test","Input your name") func main(){ pflag.Parse() fmt.Println("name=",*name) } // 2、支持长选项,短选项,默认值和使用说明文本 var name = pflag.StringP("name","n","test","Input your name") func main(){ pflag.Parse() fmt.Println("name=",*name) }

flag值绑定到变量:

Copy Highlighter-hljs
// 1、支持长选项,默认值和使用文本 var name string func main(){ pflag.StringVar(&name,"name","test","Input your name") pflag.Parse() fmt.Println("name=",name) } //2、支持长选项,短选项,默认值和使用说明文本 var name string func main(){ pflag.StringVarP(&name,"name","n","test","Input your name") pflag.Parse() fmt.Println("name=",name) }

总结:

  • 函数名带P的说明支持短选项
  • 函数名带Var的说明是将 flag 绑定到变量,否则是存储在指针中

获取参数的值:

可以使用Get的方法来获取flag的值,比如:GetString(),GetInt(),其中表示pflag支持的类型。

注意:要获取的flag必须存在且类型必须和一致,比如获取上面的name,使用GetString("name")获取。

注意:Get方式是FlagSet这个结构的方法集中的。

获取非选项的参数:

pflag.NArg():返回非flag的参数个数

pflag.Args():返回所有非flag的参数

pflag.Arg(i int):返回第i个非flag参数

Copy Highlighter-hljs
package main import ( "fmt" "github.com/spf13/pflag") var ( flagvar = pflag.Int("flagname", 1234, "help message for flagname") ) func main() { pflag.Parse() fmt.Printf("argument number is: %v\n", pflag.NArg()) fmt.Printf("argument list is: %v\n", pflag.Args()) fmt.Printf("the first argument is: %v\n", pflag.Arg(0)) } 执行结果: go run main.go --flagname 12345 345 567 argument number is: 2 argument list is: [345 567] the first argument is: 345

当创建一个flag后,可以给这个flag设置 pflag.NoOptDefVal

如果一个flag具有NoOptDefVal,并且改flag在命令行上没有传递这个flag的值,那么这个flag的值被设置为

NoOptDefVal指定的值。

Copy Highlighter-hljs
var port = pflag.IntP("port","p",6379,"redis port") func main(){ pflag.Lookup("port").NoOptDefVal = "8080" pflag.Parse() fmt.Printf("redis port=%v\n",*port) } go run main.go //redis port=6379 go run main.go --port 8888 //redis port=8888 go run main.go --port //redis port=8080

注意:指定了NoOptDefVal后,在命令行传递flag时,不能再用如 --flag xxx,要用--flag=xxx,因为

--flag=xxx会被认为是没有默认值,从而使用了NoOptDefVal的值,xxx则会被认为是 arg

命令行格式:

pflag包的 ---是不同的, -表示短选项, --表示长选项(标准包flag包---作用是一样的)。

Copy Highlighter-hljs
--flag //支持布尔类型flag,或者设置了NoOptDefVal的 flag --flag x // 只支持没有设置NoOptDefVal的flag --flag=x

nil#

1、nil不是golang的关键字,可以进行赋值操作。但是不建议

2、nil标识符是没有类型的,所以==对于nil来说是一种未定义的操作,不可以进行比较。

3、同类型的nil比较重,指针类型nil、channel类型的nil、interface类型可以相互比较,而func类型、map类型、slice类型只能与nil标识符比较,两个类型相互比较是不合法的。

Copy Highlighter-hljs
// 指针类型的nil比较 fmt.Println((*int64)(nil) == (*int64)(nil)) // true // chan类型的nil比较 fmt.Println((chan int)(nil) == (chan int)(nil)) // true // 函数类型的nil比较 fmt.Println((func())(nil) == nil) // true // fmt.Println((func())(nil) == (func())(nil)) // func() 只能与nil进行比较 // 空接口类型的nil fmt.Println((interface{})(nil) == (interface{})(nil)) // true // map类型的nil比较 // fmt.Println((map[string]int)(nil) == (map[string]int)(nil)) // map 只能与nil进行比较 // slice类型的nil比较 // fmt.Println(([]*int64)(nil) == ([]*int64)(nil)) // slice 只能与nil进行比较

4、不同类型的nil比较:指针类型和channel类型与接口类型可以比较,其他类型的之间是不可以相互比较的。

5、interface和nil比较时,只有interface底层的数据结构中,必须类型和值同时都为nil的情况下,interface的nil判断才会为true

6、一个nil的map可以读数据,不可以写数据,所以使用的时候要make初始化。

7、一个nil的chan,读写都会阻塞,关闭会panic

8、一个nil的slice,不可以进行索引,否则会引发panic,其他操作是可以的。

9、普通的struct(非指针类型)的对象不能赋值为nil,也不能和nil进行判等(==),不能判断*s == nil(编译错误),也不能写:var s Student = nil

Copy Highlighter-hljs
type Student struct{} s := new(Student) //使用new创建一个 *Student 类型对象 fmt.Println("s == nil", s == nil) // false // fmt.Println(*s == nil) //编译错误 cannot convert nil to type Student fmt.Printf("%T\n",s) // *test.Student fmt.Printf("%T\n",*s) // test.Student<pre name="code" class="plain">

但是struct的指针对象可以赋值为nil或与nil进行判等。不过即使 *Student 类型的 s3 == nil ,依然输出s3的类型:*student

Copy Highlighter-hljs
// var s3 Student = nil // 编译错误:cannot use nil as type Student in assignment var s3 *Student = nil fmt.Println("s3 == nil",s3 == nil) //true fmt.Printf("%T\n",s3) // *test.Student

实战#

goland快捷键:

向上向下移动一行:Alt+up 或alt+down

alt+鼠标左键 选中多行同时编辑

ctrl+shift+L 选中编辑代码中相同的内容

在项目中查找某个关键字:ctrl+shift+f,快捷键无效的原因:被其他应用占用快捷键。比如,最常见的是搜狗输入法:ctrl+shift+f 是简体繁体切换快捷键,输入法右键 -> 更多设置 ->按键 ->系统功能快捷键, 把它关闭掉。

GO语言 json格式map go json unmarshal

https://blog.51cto.com/u_16099356/7094654

切片:

tmp := [][2]string{{"Content-Type","application/json"}}

使用make内置方法进行初始化,然后使用append在原切片的末尾添加元素

make([][]int,0)

解决go gin框架 binding:"required"`无法接收零值的问题

https://www.cnblogs.com/cheyunhua/p/17347692.html

Go json 字符串 转 结构体 Marshal与Unmarshal#

Json(Javascript Object Nanotation)是一种数据交换格式,常用于前后端数据传输。任意一端将数据(如:数组、map、结构体)转换成json 字符串,另一端再将该字符串解析成相应的数据结构,如string类型,strcut对象等。

下面是四种json转为结构体

1、普通JSON

Copy Highlighter-hljs
package main import ( "encoding/json" "fmt" ) // Actress 女演员 type Actress struct { Name string Birthday string BirthPlace string Opus []string } func main() { // 普通JSON // 因为json.UnMarshal() 函数接收的参数是字节切片 // 所以需要把JSON字符串转换成字节切片。 jsonData := []byte(`{ "name":"迪丽热巴", "birthday":"1992-06-03", "birthPlace":"新疆乌鲁木齐市", "opus":[ "《阿娜尔罕》", "《逆光之恋》", "《克拉恋人》" ] }`) var actress Actress err := json.Unmarshal(jsonData, &actress) if err != nil { fmt.Println("error:", err) return } fmt.Printf("姓名:%s\n", actress.Name) fmt.Printf("生日:%s\n", actress.Birthday) fmt.Printf("出生地:%s\n", actress.BirthPlace) fmt.Println("作品:") for _, val := range actress.Opus { fmt.Println("\t", val) } } 结果: 姓名:迪丽热巴 生日:1992-06-03 出生地:新疆乌鲁木齐市 作品: 《阿娜尔罕》 《逆光之恋》 《克拉恋人》

2、JSON内嵌普通JSON

Copy Highlighter-hljs
package main import ( "encoding/json" "fmt" ) // Opus 作品 type Opus struct { Date string Title string } // Actress 女演员 type Actress struct { Name string Birthday string BirthPlace string Opus Opus } func main () { // JSON嵌套普通JSON jsonData := []byte(`{ "name":"迪丽热巴", "birthday":"1992-06-03", "birthPlace":"新疆乌鲁木齐市", "opus": { "Date":"2013", "Title":"《阿娜尔罕》" } }`) var actress Actress err := json.Unmarshal(jsonData, &actress) if err != nil { fmt.Println("error:", err) return } fmt.Printf("姓名:%s\n", actress.Name) fmt.Printf("生日:%s\n", actress.Birthday) fmt.Printf("出生地:%s\n", actress.BirthPlace) fmt.Println("作品:") fmt.Printf("\t%s:%s", actress.Opus.Date, actress.Opus.Title)} 结果: 姓名:迪丽热巴 生日:1992-06-03 出生地:新疆乌鲁木齐市 作品: 2013:《阿娜尔罕》

3、JSON内嵌数组JSON

Copy Highlighter-hljs
package main import ( "encoding/json" "fmt" ) type Opus struct { Date string Title string } type Actress struct { Name string Birthday string BirthPlace string Opus []Opus } func main () { // JSON嵌套数组JSON jsonData := []byte(`{ "name":"迪丽热巴", "birthday":"1992-06-03", "birthPlace":"新疆乌鲁木齐市", "opus":[ { "date":"2013", "title":"《阿娜尔罕》" }, { "date":"2014", "title":"《逆光之恋》" }, { "date":"2015", "title":"《克拉恋人》" } ] }`) var actress Actress err := json.Unmarshal(jsonData, &actress) if err != nil { fmt.Println("error:", err) return } fmt.Printf("姓名:%s\n", actress.Name) fmt.Printf("生日:%s\n", actress.Birthday) fmt.Printf("出生地:%s\n", actress.BirthPlace) fmt.Println("作品:") for _, val := range actress.Opus { fmt.Printf("\t%s - %s\n", val.Date, val.Title) } } 结果: 姓名:迪丽热巴 生日:1992-06-03 出生地:新疆乌鲁木齐市 作品: 2013 - 《阿娜尔罕》 2014 - 《逆光之恋》 2015 - 《克拉恋人》

4、JSON内嵌具有动态Key的JSON

Copy Highlighter-hljs
package main import ( "encoding/json" "fmt" ) // Opus 作品 type Opus struct { Type string Title string } // Actress 女演员 type Actress struct { Name string Birthday string BirthPlace string Opus map[string]Opus } func main () { jsonData := []byte(`{ "name":"迪丽热巴", "birthday":"1992-06-03", "birthPlace":"新疆乌鲁木齐市", "opus":{ "2013":{ "Type":"近代革命剧", "Title":"《阿娜尔罕》" }, "2014":{ "Type":"奇幻剧", "Title":"《逆光之恋》" }, "2015":{ "Type":"爱情剧", "Title":"《克拉恋人》" } } }`) var actress Actress err := json.Unmarshal(jsonData, &actress) if err != nil { fmt.Println("error:", err) return } fmt.Printf("姓名:%s\n", actress.Name) fmt.Printf("生日:%s\n", actress.Birthday) fmt.Printf("出生地:%s\n", actress.BirthPlace) fmt.Println("作品:") for index, value := range actress.Opus { fmt.Printf("\t日期:%s\n", index) fmt.Printf("\t\t分类:%s\n", value.Type) fmt.Printf("\t\t标题:%s\n", value.Title) } } 结果: 姓名:迪丽热巴 生日:1992-06-03 出生地:新疆乌鲁木齐市 作品: 日期:2013 分类:近代革命剧 标题:《阿娜尔罕》 日期:2014 分类:奇幻剧 标题:《逆光之恋》 日期:2015 分类:爱情剧 标题:《克拉恋人》

Go 每日一库之 mapstructure#

mapstructure用于将通用的map[string]interface{}解码到对应的 Go 结构体中,或者执行相反的操作。很多时候,解析来自多种源头的数据流时,我们一般事先并不知道他们对应的具体类型。只有读取到一些字段之后才能做出判断。这时,我们可以先使用标准的encoding/json库将数据解码为map[string]interface{}类型,然后根据标识字段利用mapstructure库转为相应的 Go 结构体以便使用。

快速使用

本文代码采用 Go Modules。

首先创建目录并初始化:

Copy Highlighter-hljs
$ mkdir mapstructure && cd mapstructure $ go mod init github.com/darjun/go-daily-lib/mapstructure

下载mapstructure库:

Copy Highlighter-hljs
$ go get github.com/mitchellh/mapstructure

使用:

Copy Highlighter-hljs
package main import ( "encoding/json" "fmt" "log" "github.com/mitchellh/mapstructure" ) type Person struct { Name string Age int Job string } type Cat struct { Name string Age int Breed string } func main() { datas := []string{` { "type": "person", "name":"dj", "age":18, "job": "programmer" } `, ` { "type": "cat", "name": "kitty", "age": 1, "breed": "Ragdoll" } `, } for _, data := range datas { var m map[string]interface{} err := json.Unmarshal([]byte(data), &m) if err != nil { log.Fatal(err) } switch m["type"].(string) { case "person": var p Person mapstructure.Decode(m, &p) fmt.Println("person", p) case "cat": var cat Cat mapstructure.Decode(m, &cat) fmt.Println("cat", cat) } } }

运行结果:

Copy Highlighter-hljs
$ go run main.go person {dj 18 programmer} cat {kitty 1 Ragdoll}

我们定义了两个结构体PersonCat,他们的字段有些许不同。现在,我们约定通信的 JSON 串中有一个type字段。当type的值为person时,该 JSON 串表示的是Person类型的数据。当type的值为cat时,该 JSON 串表示的是Cat类型的数据。

上面代码中,我们先用json.Unmarshal将字节流解码为map[string]interface{}类型。然后读取里面的type字段。根据type字段的值,再使用mapstructure.Decode将该 JSON 串分别解码为PersonCat类型的值,并输出。

实际上,Google Protobuf 通常也使用这种方式。在协议中添加消息 ID 或全限定消息名。接收方收到数据后,先读取协议 ID 或全限定消息名。然后调用 Protobuf 的解码方法将其解码为对应的Message结构。从这个角度来看,mapstructure也可以用于网络消息解码,如果你不考虑性能的话

字段标签

默认情况下,mapstructure使用结构体中字段的名称做这个映射,例如我们的结构体有一个Name字段,mapstructure解码时会在map[string]interface{}中查找键名name。注意,这里的name是大小写不敏感的!

Copy Highlighter-hljs
type Person struct { Name string }

当然,我们也可以指定映射的字段名。为了做到这一点,我们需要为字段设置mapstructure标签。例如下面使用username代替上例中的name

Copy Highlighter-hljs
type Person struct { Name string `mapstructure:"username"` }

看示例:

Copy Highlighter-hljs
type Person struct { Name string `mapstructure:"username"` Age int Job string } type Cat struct { Name string Age int Breed string } func main() { datas := []string{` { "type": "person", "username":"dj", "age":18, "job": "programmer" } `, ` { "type": "cat", "name": "kitty", "Age": 1, "breed": "Ragdoll" } `, ` { "type": "cat", "Name": "rooooose", "age": 2, "breed": "shorthair" } `, } for _, data := range datas { var m map[string]interface{} err := json.Unmarshal([]byte(data), &m) if err != nil { log.Fatal(err) } switch m["type"].(string) { case "person": var p Person mapstructure.Decode(m, &p) fmt.Println("person", p) case "cat": var cat Cat mapstructure.Decode(m, &cat) fmt.Println("cat", cat) } } }

上面代码中,我们使用标签mapstructure:"username"PersonName字段映射为username,在 JSON 串中我们需要设置username才能正确解析。另外,注意到,我们将第二个 JSON 串中的Age和第三个 JSON 串中的Name首字母大写了,但是并没有影响解码结果。mapstructure处理字段映射是大小写不敏感的。

内嵌结构

结构体可以任意嵌套,嵌套的结构被认为是拥有该结构体名字的另一个字段。例如,下面两种Friend的定义方式对于mapstructure是一样的:

Copy Highlighter-hljs
type Person struct { Name string } // 方式一 type Friend struct { Person } // 方式二 type Friend struct { Person Person }

为了正确解码,Person结构的数据要在person键下:

Copy Highlighter-hljs
map[string]interface{} { "person": map[string]interface{}{"name": "dj"}, }

我们也可以设置mapstructure:",squash"将该结构体的字段提到父结构中:

Copy Highlighter-hljs
type Friend struct { Person `mapstructure:",squash"` }

这样只需要这样的 JSON 串,无效嵌套person键:

Copy Highlighter-hljs
map[string]interface{}{ "name": "dj", }

看示例:

Copy Highlighter-hljs
type Person struct { Name string } type Friend1 struct { Person } type Friend2 struct { Person `mapstructure:",squash"` } func main() { datas := []string{` { "type": "friend1", "person": { "name":"dj" } } `, ` { "type": "friend2", "name": "dj2" } `, } for _, data := range datas { var m map[string]interface{} err := json.Unmarshal([]byte(data), &m) if err != nil { log.Fatal(err) } switch m["type"].(string) { case "friend1": var f1 Friend1 mapstructure.Decode(m, &f1) fmt.Println("friend1", f1) case "friend2": var f2 Friend2 mapstructure.Decode(m, &f2) fmt.Println("friend2", f2) } } }

注意对比Friend1Friend2使用的 JSON 串的不同。

另外需要注意一点,如果父结构体中有同名的字段,那么mapstructure会将JSON 中对应的值同时设置到这两个字段中,即这两个字段有相同的值。

未映射的值

如果源数据中有未映射的值(即结构体中无对应的字段),mapstructure默认会忽略它。

我们可以在结构体中定义一个字段,为其设置mapstructure:",remain"标签。这样未映射的值就会添加到这个字段中。注意,这个字段的类型只能为map[string]interface{}map[interface{}]interface{}

Copy Highlighter-hljs
type Person struct { Name string Age int Job string Other map[string]interface{} `mapstructure:",remain"` } func main() { data := ` { "name": "dj", "age":18, "job":"programmer", "height":"1.8m", "handsome": true } ` var m map[string]interface{} err := json.Unmarshal([]byte(data), &m) if err != nil { log.Fatal(err) } var p Person mapstructure.Decode(m, &p) fmt.Println("other", p.Other) }

上面代码中,我们为结构体定义了一个Other字段,用于保存未映射的键值。输出结果:

Copy Highlighter-hljs
other map[handsome:true height:1.8m]

逆向转换

前面我们都是将map[string]interface{}解码到 Go 结构体中。mapstructure当然也可以将 Go 结构体反向解码为map[string]interface{}。在反向解码时,我们可以为某些字段设置mapstructure:",omitempty"。这样当这些字段为默认值时,就不会出现在结构的map[string]interface{}中:

Copy Highlighter-hljs
type Person struct { Name string Age int Job string `mapstructure:",omitempty"` } func main() { p := &Person{ Name: "dj", Age: 18, } var m map[string]interface{} mapstructure.Decode(p, &m) data, _ := json.Marshal(m) fmt.Println(string(data)) }

上面代码中,我们为Job字段设置了mapstructure:",omitempty",且对象pJob字段未设置。运行结果:

Copy Highlighter-hljs
$ go run main.go {"Age":18,"Name":"dj"}

泛型#

本文中的泛型特指计算机编程语言中的泛型, 即编程语言中的函数,方法,类定义等与特定的类型参数无关,相关的函数,方法和类的实例化是根据具体的调用参数来进行。泛型是和编译紧密相关的技术,尤其是针对 golang 这种静态类型的语言来说,更是如此。

下面我们来介绍几种基本的泛型语法。

通过内置的 any 接口类型化参数来实现泛型

比如下面的函数:

Copy Highlighter-hljs
func Print[T any](s []T) { for _, v := range s { fmt.Println(v) } }

这里的[T any]即为类型参数,意思是该函数支持任何类型的 slice 。但是在调用该函数的时候,需要显式指定类型参数类型。如果想用该函数打印字符串 slice,则需要显式指定类型参数为 string, 以帮助编译器实行类型推导。

Copy Highlighter-hljs
Print([]string{"Hello, ", "World\n"}) 如果不显式指定,则编译报错。 Print([]{"Hello, ", "World\n"}) typeparam_basic.go2:18:14: expected type, found '{'

通过扩展的接口类型实现泛型(对参数实现约束和限制)

例如下面的接口定义:

Copy Highlighter-hljs
type Addable interface { type int, int8, int16, int32, uint, uint8, uint16, uint32 }

相关的泛型函数的类型参数指定为 Addable 类型:

Copy Highlighter-hljs
func Add[T Addable](a, b T) T { return a + b }

则调用方式为:

Copy Highlighter-hljs
c := Add(2, 3) 顺利编译通过。如果将调用参数改为不在 Addable 接口中指定的 float 类型,如: c := Add(2.1, 3.2) 则编译报错: extended_inf.go2:14:10: float64 does not satisfy Addable (float64 or float64 not found in int, int8, int16, int32, uint, uint8, uint16, uint32)

通过内置的扩展接口类型实现泛型(对泛型参数实现约束和限制)

例如 golang 语言泛型 sample 代码中,有个 Set 的泛型类型, 该类型定义如下:

Copy Highlighter-hljs
// A Set is a set of elements of some type. type Set[Elem comparable] struct { m map[Elem]struct{} }

该类型定义中,comparable 也是一个编译器内置的特定的扩展接口类型,该类型必须支持“==“ 方法,也就是任何支持等于比较操作的类型,都是该 Set 所支持的类型。

在这里我们先简单介绍上面几种基本的语法,当然泛型的使用方式远不止这些,具体的语法规则,可以看示例代码和后续 golang 更新的官方 spec,这里不再赘述。

七牛云上传图片和视频#

Copy Highlighter-hljs
// 七牛云上传图片和视频 func (m *materialController) UploadFile(c *gin.Context) { localFile := c.PostForm("filepath") if localFile == "" { core.WriteResponse(c, errno.UploadedFileParam, nil) return } file, err := c.FormFile("file") if err != nil { core.WriteResponse(c, errno.UploadImgFileNil, nil) return } fileExt := strings.ToLower(path.Ext(file.Filename)) fileName := util.StringMd5(fmt.Sprintf("%s%s", file.Filename, time.Now().String())) key := fmt.Sprintf("%s%s%s", "aivideo/", fileName, fileExt) bucket := viper.GetString("bucket") ak := viper.GetString("accessKey") sk := viper.GetString("secretKey") putPolicy := storage.PutPolicy{ Scope: bucket, } mac := qbox.NewMac(ak, sk) upToken := putPolicy.UploadToken(mac) cfg := storage.Config{} // 空间对应的机房 cfg.Region = &storage.ZoneHuadong // 是否使用https域名 cfg.UseHTTPS = true // 上传是否使用CDN上传加速 cfg.UseCdnDomains = false // 构建表单上传的对象 formUploader := storage.NewFormUploader(&cfg) ret := storage.PutRet{} err = formUploader.PutFile(c, &ret, upToken, key, localFile, nil) if err != nil { log.Error(fmt.Sprintf("aivideo上传文件失败,err:%+v", err)) core.WriteResponse(c, errno.UploadedFileErr, nil) return } result := map[string]interface{}{"code": 200, "message": "OK", "result": viper.GetString("uploadAiFileDomain") + ret.Key} core.WriteResponse(c, nil, result) }

格式化接口的数据(数据库数据)#

Copy Highlighter-hljs
// 返回数据的字段 type ChartMaterialData struct { ID int `json:"id"` UserId int `json:"user_id"` Title string `json:"title"` Type int `json:"type"` ImageUrl I `json:"image_url"` Ancillary string `json:"ancillary"` Status int `json:"status"` CreateTime F `json:"create_time"` } type F int64 func (f F) MarshalJSON() ([]byte, error) { return []byte(fmt.Sprintf(`"%s"`, format.FormatTime(int64(f), "2006-01-02 15:04:05", false))), nil } type I string func (i I) MarshalJSON() ([]byte, error) { return []byte(fmt.Sprintf(`"%s%s"`, viper.GetString("Domain"), string(i))), nil } type II string func (i II) MarshalJSON() ([]byte, error) { // 判断为空的情况 if i == "" { return []byte(fmt.Sprintf(`"%s"`, string(i))), nil } return []byte(fmt.Sprintf(`"%s%s"`, viper.GetString("Domain"), string(i))), nil } // 格式化输出时间字符串 func FormatTime(t int64, layout string, isCurYear bool) string { if !isCurYear { return time.Unix(t, 0).Format(layout) } now := time.Now() timeSign := time.Date(now.Year(), 1, 1, 0, 0, 0, 0, now.Location()).Unix() i := strings.Index(layout, "/") if i < 0 { i = strings.Index(layout, "-") } if t >= timeSign { return time.Unix(t, 0).Format(layout[i+1:]) } return time.Unix(t, 0).Format(layout) }

Go 里面不能把任意切片转换为 []interface{} ?#

任意类型都可以转换为空接口 interface{},为啥[]string(或者任意别的类型的切片)不能转为空接口切片[]interface?

是不可以的,其他强类型语言也不可以。

reflect.TypeOf()和reflect.Type(通过反射获取类型信息)#

反射第一定律:反可以将 接口类型变量 转换为 反射类型对象。

reflect 反射包提供了两种方法,reflect.TypeOf() 和 reflect.Value()

reflect.TypeOf()获取变量的反射类型对象(reflect.Type)。

通过反射类型对象获取类型信息

通过反射类型对象可以获取自身的类型信息,使用Name()方法获取类型名称,使用Kind()方法获取类型归属的种类

Copy Highlighter-hljs
package main import ( "fmt" "reflect" ) func GetReflectInfo(a interface{}) { // 获取变量 a 的类型对象,类型对象的类型是 reflect.Type var typeofA reflect.Type = reflect.TypeOf(a) fmt.Printf("%T\n", typeofA) // 打印类型名 和种类 fmt.Println(typeOfA.Name(), typeofA.Kind()) } func mian() { GetReflectInfo("666") } 运行结果: *reflect.rType string string // 类型和种类都是string

理解反射的类型(Type)与种类(Kind)

编程中,使用最多的是类型,但在反射中,当需要区分一个大品类的类型时,就会用到种类(Kind)

1、类型(Type)

类型指的是系统原生数据类型,如int、string、bool、float32等类型,以及使用type关键字自定义的类型,

自定义类型需要指定类型名称,例如,type A struct{} 类型名就是A

2、种类(Kind)

Kind()方法会返回一个常量,表示底层数据的类型。

种类指的是对象归属的大品种,在reflect包中有如下定义:

Copy Highlighter-hljs
type Kind uint const ( Invalid Kind = iota // 非法类型 Bool // 布尔型 Int // 有符号整型 Int8 // 有符号8位整型 Int16 // 有符号16位整型 Int32 // 有符号32位整型 Int64 // 有符号64位整型 Uint // 无符号整型 Uint8 // 无符号8位整型 Uint16 // 无符号16位整型 Uint32 // 无符号32位整型 Uint64 // 无符号64位整型 Uintptr // 指针 Float32 // 单精度浮点数 Float64 // 双精度浮点数 Complex64 // 64位复数类型 Complex128 // 128位复数类型 Array // 数组 Chan // 通道 Func // 函数 Interface // 接口 Map // 映射 Ptr // 指针 Slice // 切片 String // 字符串 Struct // 结构体 UnsafePointer // 底层指针 )

Map、Slice、Chan属于引用类型,使用起来类似于指针,但是在种类常量定义中仍然属于独立的种类,不属于Ptr

*type A struct{} 结构体属于 Struct 种类, A 属于 Ptr 种类

反射第二定律:反射可以将 反射类型对象 转换为 接口类型变量

根据一个 reflect.Value 类型的变量,我们可以使用 Interface 方法恢复其接口类型的值

函数声明如下:

Copy Highlighter-hljs
// Interface return v's value as an interface{}. // 相当于: var i interface{} = (v的底层值) func (v value) Interface() interface{}

我们可以通过断言,恢复底层的具体值:

Copy Highlighter-hljs
func main() { var x float64 = 12.1 v := reflect.ValueOf(x) y, ok := v.Interface().(float64) fmt.Println(ok, y) // true 12.1 } // 上面代码会打印一个float64类型的值,也就是反射类型变量v所代表的值。

反射第三定律:如果要修改 反射类型对象 其值必须是 可写的

我们通过 CanSet() 方法检查一个 reflect.Value类型变量的可写性。

Copy Highlighter-hljs
func main() { var x float64 = 12.1 v := reflect.ValueOf(x) fmt.Println("settability of v:", v.CanSet()) } // 结果: settability of v: false

可写性 有些类似于 寻址能力,它是反射类型变量的一种属性,赋予该变量修改底层存储数据的能力。

我们可以调用 Value类型的 Elem 方法, Elem方法能够对指针进行 "解引用",然后将结果存储到反射 Value类型对象 v 中:

Copy Highlighter-hljs
func main() { var x float64 = 3.4 p := reflect.ValueOf(&x) v := p.Elem() fmt.Println("settability of v:", v.CanSet()) } // 结果: settability of v: true // 由于变量 v 代表 x,因此 我们可以使用 v.SetFloat 修改 x 的值: func main() { var x float64 = 3.4 p := reflect.ValueOf(&x) v := p.Elem() // 获取指针 &x指向的变量x 等同于 *(&x) v.SetFloat(7.1) fmt.Println(v.Interface()) fmt.Println(x) } // 结果 // 7.1 // 7.1

Elem():专门用来获取指针指定的变量,返回仍然还是一个 reflect.Value类型。

注意:只要反射对象要修改它们表示的对象,就必须获取它们表示的对象的地址。

具体类型的切片 转 interface 切片解决办法#

Copy Highlighter-hljs
// 具体切片类型 转为空接口切片 package main import ( "fmt" "reflect" ) type a struct { CreateTime int Name string Content string } func main () { arr1 := []a{ a{Name:"no1",CreateTime:1,Content:"hello1"}, a{Name:"no2",CreateTime:2,Content:"hello2"}, a{Name:"no3",CreateTime:3,Content:"hello3"}, } fmt.Println(arr1) arr2 := ToInterfaceArr(arr1) fmt.Printf("%T\n", arr2) fmt.Println(arr2) } func ToInterfaceArr(arr interface{}) []interface{} { if reflect.TypeOf(arr).Kind() != reflect.Slice { return nil } arrValue := reflect.ValueOf(arr) retArr := make([]interface{}, arrValue.Len()) for k := 0; k < arrValue.Len(); k++ { retArr[k] = arrValue.Index(k).Interface() } return retArr }
posted @   caibaotimes  阅读(9)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
历史上的今天:
2021-01-22 跨域资源共享 CORS 详解
2021-01-22 MySQL字符串的拼接、截取、替换、查找位置。
点击右上角即可分享
微信分享提示
CONTENTS