go 接口与反射

接口与反射

go 中不需要声明 接口 与相关赋值接口和结构体的关系

只要有调用了对应的接口里面所有的方法,我们就认为实现了接口

面向对象:

  • 封装:将属性隐藏(结构体属性),提供相关接口供调用者访问和修改,调用的人不用关心我内部实现。

    • 比如User{} 结构体里面,我们在设置 age 的时候,不可能说超过 200 岁,所以这个时候我们就要给他加一个 SetAge 方法

      // 伪代码
      User {
          name string
          age int
      }
      
      func (u *User) SetAge(age int) {
          if age < 200 && age > 0 {
              u.age = age
          } else {
              fmt.Println("age 输入错误")
          }
      }
  • 继承:为了解决代码复用性问题,通过组合

    • 匿名组合 和 命名组合

  • 多态:同一个对象在不同的条件下有不同的行为。

    • 通过接口来实现的

    // 伪代码
    
    // 定义 sender 变量,类型是个 Sender 接口,赋值 EmailSender 结构体
    // 假如赋值为一个 dingding 结构体,那他就是 dingding 的行为
    var sender Sender = EmailSender 
    sender.Send("xxx")
  • 可见性:go 里面的代码分为包内和包外

    • 接口名称:

      • 大写:包外可见(公有)

      • 小写:包内可见(私有)

    • 方法名称一般大写。

接口时自定义类型,是对其他类型的方法的抽象

为什么会使用到接口:

image-20210413211533507

usb插槽就是现实中的接口。 你可以把手机,相机, u盘都插在usb插槽上,而不用担心那个插槽是专门插哪个的,原因是做usb插槽的厂家和做各种设备的厂家都遵守了统一的规定包括尺寸,排线等等。

因为代码要通用所以需要接口

接口:

接口:
    方法 => 行为
    两个对象,之间是有相同行为的,或者说是有相同方法的
    方法 => 集合 => 接口
    
    io.Copy(dst , src)
    dst = hash.Hash
    dst = os.File
    dst 接口 赋值的对象需要满足(实现 dst 接口) dst 接口的所有方法

接口的默认类型为 nil ,是指针类型

1 接口的使用

接口定义:
type 接口名 interface {
    // 在接口里面方法可以是多个的
    定义方法名1(参数列表)返回值列表
    定义方法名2(参数列表)返回值列表
    ...
}

/* 实现接口,所谓实现有两层含义。
1.把方法具体的类容写出来
2.把所有方法写出来(不能只写接口中单独一个或多个方法)
3.接口变量声明后,类型为 nil ,值为 nil
4.接口类型不能实例化(不能直接通过接口类型创建变量)
5.只能由其他实现了接口的对象进行赋值
*/
    

实现接口:
func (t 自定义类型) 方法名1 (参数列表) 返回值列表 {
    // 方法实现
}

func (t 自定义类型) 方法名2 (参数列表) 返回值列表 {
    // 方法实现
}
// 对象有接口中声明的所有方法。
// func 中的 参数列表 和 返回值列表 要和在 interface 中定义的一样
    
.
注意:
    对象是值还是指针,需要看他的方法接收者
    1.如果是值的话,方法接收者必须是值类型
    2.如果是指针类型的话,可以是值类型,也可以是指针类型

总结:

  1. 接口里所有的方法都没有方法体

  2. 接口方法都是没有实现方法,接口体现了程序设计的多态和高内聚低耦合的思想(高内聚:把所有代码写道函数里面去,低耦合:程序之间的耦合性降低了)

  3. Golang 中的接口,不需要显式的实现。只要一个变量,含有接口类型中的所有方法,那么这个变量就实现这个接口。因此,在 Golang 中没有 implement 这样的关键字

1.1 定义

当自定义类型实现了接口类型中声明的所有函数时,则该类型的对象可以赋值给接口变量,并使用接口变量调用实现的方法

结构体能赋值给接口,接口不能赋值给结构体

接口的定义使用范例如下代码

package main

import "fmt"

// 定义了 witer 接口
type Writer interface {

    // 在接口里面定义了 witer 方法
    Writer([]byte) (int, error)
}

func main() {
    // 赋值,这里将 Writer 接口赋值给了 writer 变量
    var writer Writer
    fmt.Printf("%T,%#v\n", writer, writer)
}

执行

[17:09:32 root@go codes]#go run interface.go 
<nil>,<nil>

因为接口现在还没有进行初始化,因为接口是一个没有实例化的,必须通过赋值

而且这个赋值必须要满足这个接口他所定义的这个方法。包括形参和返回值

1.2 声明

声明接口变量值需要定义变量类型为接口名,此时变量被初始化为

满足接口的方法其实就是调用了接口,用短声明只是赋值变量成为了结构体,但是结构体里面的方法,满足了接口的方法,其实也是间接的调用了接口

1.2.1 结构体指针接收者实现接口

package main

import "fmt"

// 定义了 witer 接口
type Writer interface {

    // 在接口里面定义了 witer 方法
    Writer([]byte) (int, error)
}

type MyFile struct {
}

// 给 MyFile 接口提绑定 writer 方法但是没有实现接口
func (file *MyFile) Writer() {
}

type MyFile2 struct {
}

// 给 MyFile2 接口提绑定 writer 方法,新参和返回值与 writer 一样实现了方法
func (f *MyFile2) Writer(ctx []byte) (int, error) {
    return 0, nil
}

func main() {
    // 赋值,这里将 Writer 接口赋值给了 writer 变量
    var writer Writer

    var MyFile2Pointer = new(MyFile2)
    writer = MyFile2Pointer
    fmt.Printf("%T,%#v\n", writer, writer)
}

执行

[17:22:08 root@go codes]#go run interface.go 
<nil>,<nil>
*main.MyFile2,&main.MyFile2{}

但是我要是使用的是 MyFiel2 的值类型的话就会报错如下范例:

package main

import "fmt"

// 定义了 witer 接口
type Writer interface {

	// 在接口里面定义了 witer 方法
	Writer([]byte) (int, error)
}

type MyFile2 struct {
}

func (f *MyFile2) Writer(ctx []byte) (int, error) {
	return 0, nil
}

func main() {
	// 赋值,这里将 Writer 接口赋值给了 writer 变量
	var writer Writer

	// 定义 myfile2 变量接收 MyFile2 的值
	var myfile2 MyFile2
	writer = myfile2
	fmt.Printf("%T,%#v\n", writer, writer)
}

执行:直接报错

[17:39:13 root@go codes]#go run interface.go 
# command-line-arguments
./interface.go:39:9: cannot use myfile2 (type MyFile2) as type Writer in assignment:
        MyFile2 does not implement Writer (Writer method has pointer receiver)

在我们的 myfile2 值类型中没有实现该接口的方法。

因为我们的值类型是会单独开辟一块内存空间,而单独另外开辟的那块内存空间就没有 Writer 方法从而实现不了接口的调用

1.2.2 结构体值接收者实现接口

package main

import "fmt"

// 定义了 witer 接口
type Writer interface {

    // 在接口里面定义了 witer 方法
    Writer([]byte) (int, error)
}

type MyFile3 struct {
}

func (f MyFile3) Writer(data []byte) (int, error) {
    return 0, nil
}

func main() {
    // 赋值,这里将 Writer 接口赋值给了 writer 变量
    var writer Writer

    // 定义 myfile3 变量接收 MyFile3 的值
    var myfile3 MyFile3
    writer = myfile3
    fmt.Printf("%T,%#v\n", writer, writer)
}

执行

[17:33:12 root@go codes]#go run interface.go 
main.MyFile3,main.MyFile3{}

因为在我们这个 MyFile3 结构体里面实现了 writer 接口的方法方法,所以能够实现接口,假如定义了一个 MyFile3 的指针类型也能够实现调用该接口

因为结构体值类型他会自动生成一个指针接收者的,如果是值类型当我们使用指针类型的时候其实是在对同一个内存空间进行操作

package main

import "fmt"

// 定义了 witer 接口
type Writer interface {

    // 在接口里面定义了 witer 方法
    Writer([]byte) (int, error)
}

type MyFile3 struct {
}

func (f MyFile3) Writer(data []byte) (int, error) {
    return 0, nil
}

func main() {
    // 赋值,这里将 Writer 接口赋值给了 writer 变量
    var writer Writer

    // 定义 myfile3 变量接收 MyFile3 的指针类型
    var myfile3 = new(MyFile3)
    writer = myfile3
    fmt.Printf("%T,%#v\n", writer, writer)
}

执行

[17:46:32 root@go codes]#go run interface.go 
*main.MyFile3,&main.MyFile3{}

1.3 赋值

当自定义类型实现了接口类型中声明的所有函数时,则该类型的对象可以赋值给接口变量,并使用接口变量调用实现的接口

比如在工作中我们需要定义业务告警功能,不仅有钉钉、微信、邮箱、短信 所以为了满足所有的告警媒介,我们不得不使用接口,从而减少代码的冗余,以后我们修改的话只需要修改接口的初始化即可。

从下面代码中我们可以看到,定义了 email 和 钉钉 两种告警媒介

package main

import "fmt"

// 定义 Email 结构体,并且定义 Send 方法
type EmailSender struct {
}

func (s *EmailSender) Send(msg string) error {
	fmt.Println("email sender:" + msg)
	return nil
}

func test(sender *EmailSender) {
	sender.Send("yy")
}

// 定义 DingDing 结构体,并且定义 Send 方法
type DingDingSender struct {
}

func (d *DingDingSender) Send(msg string) error {
	fmt.Println("dingding sender " + msg)
	return nil
}

func test1(dingding *DingDingSender) {
	dingding.Send("dingding")
}

func main() {
    // 定义了 sender 变量赋值了 DingDingSender 结构体
    // 假如我们想使用 email 的话,就需要重新赋值一个 Email 的结构体
	sender := DingDingSender{}
	sender.Send("xx")
	test1(&sender)
}

执行

[11:20:00 root@go codes]#go run sender.go 
dingding sender xx
dingding sender dingding

我们从上面代码中可以看到,其实 dingding 和 email 两个结构体中的 sender 方法完全类似,这样的代码就没有冗余,显得浪费,这时候我们就可以使用到我们的接口

如下代码:

package main

import "fmt"

// 定义 sender 接口
type Sender interface {
	// 定义 Send(string) error 方法
	Send(string) error
}

type EmailSender struct {
}

// 定义 Email 结构体中的 Send 方法,满足 Sender 接口
func (s *EmailSender) Send(msg string) error {
	fmt.Println("email sender:" + msg)
	return nil
}

// test 函数传承是一个 Sender 接口
func test(sender Sender) {
	sender.Send("yy")
}

type DingDingSender struct {
}

func (d *DingDingSender) Send(msg string) error {
	fmt.Println("dingding sender " + msg)
	return nil
}

func test1(dingding Sender) {
	dingding.Send("dingding")
}

func main() {

	// 定义 send 接口变量,在赋值的时候赋值成了 EmailSender 结构体的 send 方法
    // 以后如果想调用 dingding 的 Send 只需将 EmailSender 改为 DingDingSender 即可
	var send Sender = new(EmailSender)

	// 实现 send 接口的 Send 方法,调用的其实是 email 结构体的 send 方法
	send.Send("email")

	// 调用 test 函数,传入 email 结构体的 Send 方法
	test(send)
}

执行

[11:38:18 root@go codes]#go run sender.go 
email sender:email
email sender:yy

在赋值的时候将 EmailSender 改为 DingDingSender

package main

import "fmt"

// 定义 sender 接口
type Sender interface {
	// 定义 Send(string) error 方法
	Send(string) error
}

type EmailSender struct {
}

// 定义 Email 结构体中的 Send 方法,满足 Sender 接口
func (s *EmailSender) Send(msg string) error {
	fmt.Println("email sender:" + msg)
	return nil
}

// test 函数传承是一个 Sender 接口
func test(sender Sender) {
	sender.Send("yy")
}

type DingDingSender struct {
}

func (d *DingDingSender) Send(msg string) error {
	fmt.Println("dingding sender " + msg)
	return nil
}

func test1(dingding *DingDingSender) {
	dingding.Send("dingding")
}

func main() {

	// 定义 send 接口变量,在赋值的时候改为了 DingDingSender 结构体
	var send Sender = new(DingDingSender)

	// 实现 send 接口的 Send 方法,调用的其实是 DingDingSender 结构体的 send 方法
	send.Send("dingding")

	// 调用 test 函数,传入 DingDingSender 结构体的 Send 方法
	test(send)
}

执行

[11:43:49 root@go codes]#go run sender.go 
dingding sender dingding
dingding sender yy

接口定义以后,用了一个结构体或者说自定义类型去赋值,这个自定义类型的对象需要实现满足接口的所有方法。我们用接口对象其实就是为了调用这些结构体或者说自定义类型的方法。

1.3.1 短声明赋值案例

package main

import "fmt"

// 定义 Sender 接口,抽象方法为 send(string)error
type Sender interface {
	send(string) error
}

// 定义 dingding 结构体
type DingDing struct {
}

// 定义 send 方法,实现接口
func (d *DingDing) send(msg string) error {
	fmt.Println("dd msg " + msg)
	return nil
}

// 定义 test 函数,形参为 Sender 接口
func test(send Sender) {
	send.send("interface")
}

func main() {
    // 短声明定义 send 结构体变量
	send := new(DingDing)
	send.send("结构体")
    
    // 其实是在传入 test 函数的时候,结构体才转为了接口,因为 send 结构体变量满足了接口的所有方法
	test(send)
}

执行

[12:24:08 root@go codes]#go run duan.go 
dd msg 结构体
dd msg interface

1.3.2 注意接口只是一个方法行为的抽象

接口只是一个方法行为的抽象,当我们赋值了一个变量,然后定义为了接口方法是不能去访问或者修改结构体的属性

package main

import "fmt"

type Sender interface {
	send(string) error
}

// 定义 dingding 结构体,定义了以下几个属性
type DingDing struct {
	addr string
	user string
	to   string
}

func (d *DingDing) send(msg string) error {
	fmt.Println("dd msg " + msg)
	return nil
}

func test(send Sender) {
	send.send("interface")
}

func main() {
    // 赋值了 send 变量为 Sender 接口
	var send Sender = new(DingDing)
    
    // send 接口不能调用结构体属性
	fmt.Println(send.addr)
}

执行

[12:24:26 root@go codes]#go run duan.go 
# command-line-arguments
./duan.go:26:18: send.addr undefined (type Sender has no field or method addr)

# 为生命 addr 属性

1.3.3 接口类型之间的变量赋值

当我们把一个接口赋值给另外一个接口的时候,需要提前实现所有的方法

接口在赋值的时候会隐藏一些属性

package main

import "fmt"

// 定义 Sender 接口,抽象的方法为 send(string) error
type Sender interface {
	send(string) error
}

// 定义 SenderAll 接口,抽象的方法有两个,其中一个实现了 Sender 接口的 send(string) error 方法
type SenderAll interface {
	send(string) error
	sendAll([]string) error
}

// 定义 DingDing 结构体,两个方法都满足了 SenderAll 接口
type DingDing struct {
	addr string
	user string
	to   string
}

func (d *DingDing) send(msg string) error {
	fmt.Println("dingding msg " + msg)
	return nil
}

func (d *DingDing) sendAll(msg []string) error {
	fmt.Println("dingding sender all", msg)
	return nil
}

func main() {
	// 将 new(DingDing) 结构体赋值给 sendAll 和 sender 两个方法,满足 Sender 和 SenderAll 接口
	var sendAll SenderAll = new(DingDing)
	var sender Sender = new(DingDing)

    // 这里将 sender 接口赋值给 sendAll 接口,是错误的,因为在 sender 接口中没有满足 sendAll([]string) error 抽象方法
	sendAll = sender
	fmt.Println(sendAll, sender)

}

执行

[13:09:39 root@go codes]#go run duan.go 
# command-line-arguments
./duan.go:64:10: cannot use sender (type Sender) as type SenderAll in assignment:
        Sender does not implement SenderAll (missing sendAll method)
        
# 无法将发件人(类型sender)用作分配中的类型senderal:发送方未实现senderal(缺少sendAll方法)

但是我们将 sendAll 接口赋值给 sender 接口就可以,因为在 sendAll 接口里面实现了 sender 接口的方法

package main

import "fmt"

// 定义 Sender 接口,抽象的方法为 send(string) error
type Sender interface {
	send(string) error
}

// 定义 SenderAll 接口,抽象的方法有两个,其中一个实现了 Sender 接口的 send(string) error 方法
type SenderAll interface {
	send(string) error
	sendAll([]string) error
}

// 定义 DingDing 结构体,两个方法都满足了 SenderAll 接口
type DingDing struct {
	addr string
	user string
	to   string
}

func (d *DingDing) send(msg string) error {
	fmt.Println("dingding msg " + msg)
	return nil
}

func (d *DingDing) sendAll(msg []string) error {
	fmt.Println("dingding sender all", msg)
	return nil
}

func main() {
	// 将 new(DingDing) 结构体赋值给 sendAll 和 sender 两个方法,满足 Sender 和 SenderAll 接口
	var sendAll SenderAll = new(DingDing)
	var sender Sender = new(DingDing)

   // 将 sendAll 赋值给了 sender 接口
	sender = sendAll
    
    // 在调用的时候只能调用 send 
	sender.send("SENDER 方法")
	fmt.Println(sendAll, sender)

}

执行

[13:12:40 root@go codes]#go run duan.go 
&{  } &{  }

1.4 接口继承

所谓的接口继承其实就是我们的一个接口继承了另一个接口的使用的方法,也就说说当我们有多个结构体,满足不同接口的时候,既可以通过接口继承来满足。


1.5 类型断言

  • 当父集接口或者类型对象赋值给接口变量后,需要将接口变量重新转换为原来的类型,需要使用类型断言/查询

  • 类型断言是一个使用在接口值上的操作

  • 有时候,我们可能需要知道某个接口类型的实际类型,比如某个方法需要接收多种类型的数据并需要做分别处理,我们可以把形参设为空接口类型并接收任意类型的值,但是我们怎么反向知道里面实际保存的是哪个类型的对象呢?

接口是没有办法调用结构体里面的属性,也没有办法去调用该结构体其他的方法,接口只能调用它就接口定义方法。

断言在转换的时候有两个返回值,第一个是断言成功后的对象,还有一个是 OK ,Ok 用来判断是否断言成功了还是断言失败了

范例:

package main

import "fmt"

type Sender interface {
	Send(string) error
}

type EmailSender struct {
	Addr string
	To   string
}

func (e *EmailSender) Send(msg string) error {
	fmt.Println("email sender : ", msg)
	return nil
}

func (e *EmailSender) Test() {
	fmt.Println("test !")
}

func main() {
	// 定义接口,同时将 EmailSender 结构体里面的属性进行初始化
	var sender Sender = &EmailSender{"xxx", "yyy"}

	// 调用接口 Send 方法
	sender.Send("zzz")

	// 类型断言,由于接口不能直接访问到结构体的属性,所以需要类型断言来判断是否断言成功
	obj, ok := sender.(*EmailSender)
	fmt.Printf("%T\n%T\n", obj, ok)

	fmt.Println("断言成功后的值", obj, ok)
}

执行

[16:17:17 root@go codes]#go run asset.go 
email sender :  zzz
*main.EmailSender					# 现在 obj 变量断言成功就是 *main.EmailSender 结构体类型
bool								# ok 是一个 bool 类型
断言成功后的值 &{xxx yyy} true			# 断言成功后就可以正常访问该结构体

1.5.1 类型断言后访问数据

当我们类型断言成功之后就可以正常访问该结构体里面的属性。

package main

import "fmt"

type Sender interface {
	Send(string) error
}

type EmailSender struct {
	Addr string
	To   string
}

func (e *EmailSender) Send(msg string) error {
	fmt.Println("email sender : ", msg)
	return nil
}

func (e *EmailSender) Test() {
	fmt.Println("test !")
}

func main() {
	// 定义接口,同时将 EmailSender 结构体里面的属性进行初始化
	var sender Sender = &EmailSender{"xxx", "yyy"}

	// 类型断言
	obj, ok := sender.(*EmailSender)

    // 也可以直接访问,不用通过 if 进行判断
    fmt.Println(obj.Addr)
    
	// ok 为真就访问 obj.Addr, obj.To 两个属性
	if ok {
		fmt.Println(obj.Addr, obj.To)
        // 访问 Test() 方法
        obj.Test()				
	}

}

执行

[16:57:46 root@go codes]#go run asset.go 
xxx
xxx yyy
test !

1.5.2 类型断言注意事项

当我们在有的多个结构体使用接口方法的时候,但是有的结构体没有被赋值,所以就会类型断言错误

在进行类型断言的时候,必须要提前把结构体进行初始化赋值

package main

import "fmt"

type Sender interface {
	Send(string) error
}

type EmailSender struct {
	Addr string
	To   string
}

func (e *EmailSender) Send(msg string) error {
	fmt.Println("email sender : ", msg)
	return nil
}

func (e *EmailSender) Test() {
	fmt.Println("test !")
}

type DingDing struct {
}

func (d *DingDing) Send(msg string) error {
	fmt.Println("dingding ", msg)
	return nil
}

func main() {
	// 定义接口,同时将 EmailSender 结构体里面的属性进行初始化
	var sender Sender = &EmailSender{"xxx", "yyy"}

	// 调用接口 Send 方法
	// sender.Send("zzz")

	// 类型断言,由于接口不能直接访问到结构体的属性
	obj, ok := sender.(*EmailSender)
	if ok {
		fmt.Println(obj.Addr, obj.To)
		obj.Test()
	}
    
    // 没有对 DingDing 结构体初始化赋值,这调用的还是 EmailSender 结构体的赋值变量 sender
    // DingDing 结构体没有被赋值
    dingding, ok := sender.(*DingDing)
	fmt.Println(dingding, ok)
}

执行

[16:58:32 root@go codes]#go run asset.go 
xxx yyy
test !
<nil> false				# false 因为我们没有对 dingding 结构体进行初始化赋值

1.5.3 通过类型断言调用结构体

package main

import "fmt"

type Sender interface {
	Send(string) error
}

type EmailSender struct {
	Addr string
	To   string
}

func (e *EmailSender) Send(msg string) error {
	fmt.Println("email sender : ", msg)
	return nil
}

func (e *EmailSender) Test() {
	fmt.Println("test !")
}

type DingDing struct {
}

func (d *DingDing) Send(msg string) error {
	fmt.Println("dingding ", msg)
	return nil
}

func test(sender Sender) {
	// 类型断言,如果 sender 是 EmailSender 打印 email sender
	if obj, ok := sender.(*EmailSender); ok {
		fmt.Println("email sender", obj.Addr)	// 调用 obj.Addr 属性

		// 类型断言,如果 sender 是 DingDing 打印 dingding sender
	} else if _, ok := sender.(*DingDing); ok {
		fmt.Println("dingding sender")
	} else {
		fmt.Println("error")
	}
}

func main() {
	// 定义 sender 接口变量,赋值 EmailSender
	var sender Sender = &EmailSender{"xxx", "yyy"}
	test(sender)

	// 传入 test 函数的 DingDing 结构体
	test(new(DingDing))
}

执行

[17:27:46 root@go codes]#go run asset.go 
email sender xxx
dingding sender

1.5.4 switch 类型查询

可以通过 switch-case+接口变量.(type) 查询变量类型,并选择对应的分支块

语法

switch case + 接口变量.(type)

// 是固定语法,只能用在 switch 里面

范例

package main

import "fmt"

type Sender interface {
    Send(string) error
}

type EmailSender struct {
    Addr string
    To   string
}

func (e *EmailSender) Send(msg string) error {
    fmt.Println("email sender : ", msg)
    return nil
}

func (e *EmailSender) Test() {
    fmt.Println("test !")
}

type DingDing struct {
}

func (d *DingDing) Send(msg string) error {
    fmt.Println("dingding ", msg)
    return nil
}

func query(sender Sender) {
    // 类型查询
    // switch case + 接口变量.(type)
    // 判断这个接口的类型,是否为 EmailSender 或者 DingDing
    switch sender.(type) {
    case *EmailSender:
        fmt.Println("email")
    case *DingDing:
        fmt.Println("dingding")
    default:
        fmt.Println("error")
    }
}

func main() {
    // 通过 new 函数指定调用 EmailSender
    query(new(EmailSender))

    // 定义 sender 接口变量类型为 DingDing
    var sender Sender = &DingDing{}
    // 传入 sender
    query(sender)
}

执行

[17:21:19 root@go codes]#go run asset.go 
email
dingding

1.5.4.1 接口查询

接口查询其实就是满足就近原则,顺序是由 switch - case 来决定

package main

import "fmt"

type Sender interface {
    Send(string) error
}

type AllSender interface {
    Send(string) error
    SendAll([]string) error
}

type EmailSender struct {
    Addr string
    To   string
}

func (e *EmailSender) Send(msg string) error {
    fmt.Println("email", msg)
    return nil
}

func (e *EmailSender) SendAll(msg []string) error {
    fmt.Println("emaill All sender", msg)
    return nil
}

func main() {
    // EmailSender 中满足了 Sender 和 AllSender 两个接口的方法所以能够赋值
    var sender Sender = &EmailSender{"xxx", "zzz"}
    var allSender AllSender = &EmailSender{"111", "222"}

    // 由于 allsender 接口变量中满足 Send() 方法所以能赋值给 sender
    sender = allSender

    // 通过 switch 对 sender 类型进行查询
    switch sender.(type) {
    case *EmailSender:          // 我们把 *EmailSender 结构体写在最近的位置输出 email
        fmt.Println("email")
    case AllSender:
        fmt.Println("allsender")
    default:
        fmt.Println("default")
    }
}

执行

[18:50:49 root@go codes]#go run assetinteface.go 
email

假如我把 AllSender 写在最近的位置输出的就是 allsender

image-20210627185317917

执行

[18:50:55 root@go codes]#go run assetinteface.go 
allsender

所以由此可见,接口查询其实就是满足就近原则

1.5.5 类型查询调用结构体属性

package main

import "fmt"

type Sender interface {
	Send(string) error
}

type EmailSender struct {
	Addr string
	To   string
}

func (e *EmailSender) Send(msg string) error {
	fmt.Println("email sender : ", msg)
	return nil
}

func (e *EmailSender) Test() {
	fmt.Println("test !")
}

type DingDing struct {
}

func (d *DingDing) Send(msg string) error {
	fmt.Println("dingding ", msg)
	return nil
}


func query(sender Sender) {
	// 类型查询
	// switch case + 接口变量.(type),定义 obj 变量接收 接口的结构体类型
	switch obj := sender.(type) {
	case *EmailSender:
		fmt.Println("email", obj.Addr, obj.To)	// 调用了 EmailSender 属性
	case *DingDing:
		fmt.Println("dingding")
	default:
		fmt.Println("error")
	}
}

func main() {
    // 定义 sender 变量,类型为 EmailSender 结构体同时给属性赋值 "xxx", "yyy"
	var sender Sender = &EmailSender{"xxx", "yyy"}
	query(sender)
    
	query(new(DingDing))

}

执行

[17:27:52 root@go codes]#go run asset.go 
email xxx yyy			# 执行发现已经调用 xxx  yyy 属性
dingding

1.5.6 接口断言接口

通过接口的断言,实现对接口的类型访问

package main

import "fmt"

type Sender interface {
	Send(string) error
}

type AllSender interface {
	Send(string) error
	SendAll([]string) error
}

type EmailSender struct {
	Addr string
	To   string
}

func (e *EmailSender) Send(msg string) error {
	fmt.Println("email", msg)
	return nil
}

func (e *EmailSender) SendAll(msg []string) error {
	fmt.Println("emaill All sender", msg)
	return nil
}

func main() {
	// 定义 sender 的 Sender 接口变量,类型是 EmailSender 结构体
	var sender Sender = &EmailSender{"xxx", "zzz"}

	// 定义 allsend 的 AllSender 接口变量,类型也是 EmailSender 结构体
	var allsend AllSender = &EmailSender{"111", "222"}

	// 因为 sender 和 allsend 接口里面都有 Send(string) error 方法
	// 所以 allsend 能赋值给 sender
	sender = allsend
	sender.Send("xxxx")

	// 接口的类型断言,因为 allsend 已经赋值给了 sender 所以 sender 变量就有 AllSender 接口
    // allsend1 为 EmailSender 结构体类型
	allsend1, ok := sender.(AllSender)
	fmt.Printf("%T\n", allsend1)
}

执行

[17:56:08 root@go codes]#go run assetinteface.go 
email xxxx
&{111 222} true
*main.EmailSender		# 是一个 EmailSender 结构体类型

当然由于现在 allsend1 是 EmailSender 结构体,所以也能够实现对 EmailSender 结构体方法的访问

1.5.7 接口赋值原理图

image-20210627183521311

我们从上图可以看到,当我们需要赋值的时候,赋值给被赋值的变量时,赋值的这个变量必须满足被赋值变量的方法。

如下范例

package main

import "fmt"

type Sender interface {
	Send(string) error
}

type AllSender interface {
	Send(string) error
	SendAll([]string) error
}

type EmailSender struct {
	Addr string
	To   string
}

func (e *EmailSender) Send(msg string) error {
	fmt.Println("email", msg)
	return nil
}

func (e *EmailSender) SendAll(msg []string) error {
	fmt.Println("emaill All sender", msg)
	return nil
}

func main() {
	// EmailSender 中满足了 Sender 和 AllSender 两个接口的方法所以能够赋值
	var sender Sender = &EmailSender{"xxx", "zzz"}
	var allSender AllSender = &EmailSender{"111", "222"}

	// 由于 allsender 接口变量中满足 Send() 方法所以能赋值给 sender
	sender = allSender
	sender.Send("sender")

	// 通过类型断言,将 EmailSender 结构体类型赋值给 esend ,此时 esend 就是 EmailSender 结构体类型
	fmt.Println()
	esend, ok := sender.(*EmailSender)
	fmt.Println("esend 变量:", esend, ok)
	fmt.Println(esend.Addr, esend.To)	
	esend.Send("esend")
	esend.SendAll([]string{"aaa", "bbb"})
	fmt.Printf("%T\n", esend)

}

执行

[18:42:20 root@go codes]#go run assetinteface.go 
email sender

esend 变量: &{111 222} true		# 输出了 EmailSender 中 Addr 和 To 属性值, ok 为 true 为真
111 222							  # 通过 esned 单独访问 Addr 和 To 属性
email esend						  # 调用 EmailSender 中的 Send 方法
emaill All sender [aaa bbb]		  # 调用 EmailSender 中的 SendAll 方法
*main.EmailSender				  # 类型为 EmailSender 结构体

1.6 接口组合

接口之中也可以嵌入已存在的接口,从而实现接口的扩展,组合是为了实现代码的复用性

如下面例子中,有一个网络连接的一个接口,对于网络连接来说一般有以下几个过程

  1. Open(打开)

  2. Send(发送消息)

  3. Recive(接收)

  4. Close(关闭)

image-20210628095129982

package main

import "fmt"

type Sender interface {
	Send(string) error
}

// 编写一个网络连接接口
type Connection interface {
	Sender // 匿名组合 Sender 接口,复用了 Sender 接口
	Open() error
	Recive() (string, error)
	Close() error
}

// 假如我们需要将 Connection 赋值给 TcpConnection 结构体
// TcpConnection 就需要满足 Connection 的 4 种方法
type TcpConnection struct {
}

func (c *TcpConnection) Open() error {
	return nil
}

func (c *TcpConnection) Send(msg string) error {
	return nil
}

func (c *TcpConnection) Recive() (string, error) {
	return "", nil
}

func (c *TcpConnection) Close() error {
	return nil
}

func main() {
	var conn Connection = new(TcpConnection)
	fmt.Printf("%T\n,%#v\n", conn, conn)

    // 现在 TcpConnection 结构体赋值给了 conn 接口变量,所以 conn 能够调用 TcpConnection 结构体的方法
    conn.Open()
	conn.Send("xxx")
	conn.Recive()
	conn.Close()
}

执行

# 通过执行 TcpConnection 结构体满足了 Connection 接口的 4 个方法所以能够赋值
[09:50:50 root@go codes]#go run combind.go 
*main.TcpConnection
,&main.TcpConnection{}

1.6.1 结构体组合匿名接口(不常用)

我们使用结构体组合接口的时候就可以通过实例变量来访问该结构体里面接口对应的满足了该接口的结构体方法方法

package main

import "fmt"

type Sender interface {
	Send(string) error
}

// 编写一个网络连接接口
type Connection interface {
	Sender // 匿名组合 Sender 接口,复用了 Sender 接口
	Open() error
	Recive() (string, error)
	Close() error
}

// 假如我们需要将 Connection 赋值给 TcpConnection 结构体
// TcpConnection 就需要满足 Connection 的 4 种方法
type TcpConnection struct {
}

func (c *TcpConnection) Open() error {
	return nil
}

func (c *TcpConnection) Send(msg string) error {
	return nil
}

func (c *TcpConnection) Recive() (string, error) {
	return "", nil
}

func (c *TcpConnection) Close() error {
	return nil
}

// 结构体组合接口
type Client struct {
	Connection // 这里嵌入的而是一个 Connection 的接口
	Name       string
}

func main() {
	// 定义 conn 接口变量,赋值 TcpConnection 结构体
	var conn Connection = new(TcpConnection)

	// 在赋值的时候需要满足匿名嵌入接口方法的结构体进行赋值
	// 如这的 conn 变量,由于赋值的是 TcpConnection 结构体,满足了 Connection 接口定义的方法所以能够初始化
	client := Client{conn, "zzz"}
	client.Open()
	client.Send("xxx")
	client.Recive()
	fmt.Printf("%T\n,%#v\n", client, client)
}

1.6.2 结构体组合命名接口

package main

import "fmt"

type Sender interface {
	Send(string) error
}

// 编写一个网络连接接口
type Connection interface {
	Sender // 匿名组合 Sender 接口,复用了 Sender 接口
	Open() error
	Recive() (string, error)
	Close() error
}

// 假如我们需要将 Connection 赋值给 TcpConnection 结构体
// TcpConnection 就需要满足 Connection 的 4 种方法
type TcpConnection struct {
}

func (c *TcpConnection) Open() error {
	return nil
}

func (c *TcpConnection) Send(msg string) error {
	return nil
}

func (c *TcpConnection) Recive() (string, error) {
	return "", nil
}

func (c *TcpConnection) Close() error {
	return nil
}

// 结构体组合接口
type Client struct {
	c    Connection // 命名组合,嵌入 Connection 接口
	Name string
}

func main() {
	// 定义 conn 接口变量,赋值 TcpConnection 结构体
	var conn Connection = new(TcpConnection)

	// 在赋值的时候需要满足匿名嵌入接口方法的结构体进行赋值
	// 如这的 conn 变量,由于赋值的是 TcpConnection 结构体,满足了 Connection 接口定义的方法所以能够初始化
	client := Client{conn, "zzz"}
	client.c.Open()			// 访问的时候需要指定命名组合的字段名
	client.c.Send("xxx")
	client.c.Recive()
	fmt.Printf("%T\n,%#v\n", client, client)
}

执行

[10:08:55 root@go codes]#go run combind.go 
main.Client
,main.Client{c:(*main.TcpConnection)(0x57b400), Name:"zzz"}

1.7 匿名接口

在定义变量时将类型指定为接口的函数签名的接口,此时叫匿名接口。匿名接口常用于初始化一次接口变量的场景

匿名(接口、函数、结构体):无非就是在定义的时候不定义名称,而是直接定义一个变量。

package main

import "fmt"

type EmailSender struct {
}

// EmailSender 结构体满足 Send 方法
func (e *EmailSender) Send(msg string) error {
	fmt.Println("emailSender", msg)
	return nil
}

func main() {
	// 定义匿名接口
	// 直接定义了一个 sender 变量,类型为 interface
	var sender interface {
		Send(msg string) error
	}

	// 在赋值的时候,如果实现了 Send(msg string) error 方法就能赋值
	// 将 EmailSender 结构体指针赋值给 sender 变量
	sender = new(EmailSender)
	sender.Send("email")
}

执行

[10:13:33 root@go codes]#go run lambda.go 
emailSender email

1.8 空接口

不包含任何函数签名的接口叫空接口,空接口声明的变量可以赋值为任何类型的变量(任意接口)

package main

import "fmt"

// 全局变量定义空接口
type Empty interface{}

func main() {
    // 定义 empty 变量,类型为 Empty
	var empty Empty
	empty = 1
	fmt.Printf("type = %T value = %v\n", empty, empty)

	empty = "str"
	fmt.Printf("type = %T value = %v\n", empty, empty)

	empty = true
	fmt.Printf("type = %T value = %v\n", empty, empty)

	empty = struct {
		Name     string
		Password string
	}{"x", "y"}
	fmt.Printf("type = %T value = %v\n", empty, empty)
}

执行

[10:51:45 root@go codes]#go run empty.go 
type = int value = 1
type = string value = str
type = bool value = true
type = struct { Name string; Password string } value = {x y}

# 空接口可以赋值成任何类型

1.8.1 空接口操作

当我们需要对空接口进行运算,或者字符串链接等操作的时候就需要使用到类型断言。

package main

import "fmt"

// 全局变量定义空接口
type Empty interface{}

type User struct {
	Name     string
	Password string
}

func main() {
	// 定义 empty 变量,类型为 Empty
	var empty Empty

	// 空接口运算操作:类型断言,v 是断言后的值
	empty = 1
	if v, ok := empty.(int); ok {
		fmt.Println("运算:", v+2)
	}

	// 空接口字符串链接操作:类型断言,v 是断言后的值
	empty = "str"
	if v, ok := empty.(string); ok {
		fmt.Println("字符串拼接:", v+"xixi")
	}

	// 空接口对结构体操作,类型断言,v 是断言后的值,也就是 User 结构体
	empty = User{"xx", "yy"}
	if v, ok := empty.(User); ok {
		fmt.Println("结构体:", v.Name, v.Password)
	}
}

执行

[11:08:53 root@go codes]#go run empty.go 
运算: 3
字符串拼接: strxixi
结构体: xx yy

1.8.2 匿名空接口(常用)

package main

import "fmt"

type User struct {
	Name     string
	Password string
}

func main() {
	// 定义 empty 变量为匿名空接口
	var empty interface{}

	// empty 匿名空接口赋值为 int 类型
	empty = 1
	fmt.Println(empty)

	// empty 匿名空接口赋值为 string 类型
	empty = "xxxx"
	fmt.Println(empty)

	// empty 匿名空接口赋值为 User 结构体类型
	empty = User{"xx", "111"}
	fmt.Println(empty)
}

执行

[11:14:20 root@go codes]#go run empty_v1.go 
1
xxxx
{xx 111}

1.8.2.1 匿名空接口使用

匿名接口的访问,以及对数据的操作,就需要使用到类型断言

package main

import "fmt"

type User struct {
	Name     string
	Password string
}

func main() {
	// 定义 empty 变量为匿名空接口
	var empty interface{}

	// empty 匿名空接口赋值为 int 类型
	empty = 1
    
    // 类型断言
	if v, ok := empty.(int); ok {
		fmt.Println(v + 2)
	}

	// empty 匿名空接口赋值为 string 类型
	empty = "xxxx"

    // 类型断言
	if v, ok := empty.(string); ok {
		fmt.Println(v + "12")
	}

	// empty 匿名空接口赋值为 User 结构体类型
	empty = User{"xx", "111"}
    
     // 类型断言
	if v, ok := empty.(User); ok {
		fmt.Println(v.Name, v.Password)
	}
}

执行

[11:15:38 root@go codes]#go run empty_v1.go 
3
xxxx12
xx 111

1.9 接口的应用

1.9.1 String 方法

image-20210628162720487

结构体中定义 string 方法,就可以自动打印

package main

import "fmt"

// 定义结构体 User 
type User struct {
    Name     string
    Password string
}

// 定义一个 String 方法
func (u User) String() string {
    return fmt.Sprintf("User[Name=%s]", u.Name)
}

func main() {
    // 定义 user 变量类型为 User 结构体
    user := User{
        Name:     "xxx",
        Password: "xxx",
    }

    // 输出 user 会自动调用 String() 方法
    fmt.Println(user)

    // 指针类型的 puser 也是会自动调用 String() 方法
    puser := &User{"xxx", "xxx"}
    fmt.Println(puser)
}

执行

[16:19:44 root@go codes]#go run string.go 
User[Name=xxx]
User[Name=xxx]

1.9.2 sort.Slice 切片方法

image-20210628170004121

该方法可以通过内置函数将其字段的大小值进行排序

package main

import (
    "fmt"
    "sort"
)

type User struct {
    Id       int
    Name     string
    Password string
}

// 调用官方的 String() 方法
func (user User) String() string {
    return fmt.Sprintf("user[Id = %d],Name = %s]\n", user.Id, user.Name)
}

// 定义 Users 类型是 User 结构体的切片类型
type Users []User

func main() {
    // 定义 users 变量并赋值
    users := []User{
        {1, "z21", "pppp"},
        {20, "z222", "pppp"},
        {187, "zads", "pppp"},
        {230, "z1", "pppp"},
        {100, "zyy", "pppp"},
        {98, "zgg", "pppp"},
    }

    // 通过 Slice 进行排序
    sort.Slice(users, func(i, j int) bool {
        return users[i].Id < users[j].Id
    })
    fmt.Println(users)
}

1.9.3 自定义类型的时候进行排序

package main

import (
    "fmt"
    "sort"
)

// 定义 User 结构体
type User struct {
    Id       int
    Name     string
    Password string
}

// 定义 String 方法进行默认输出
func (user User) String() string {
    return fmt.Sprintf("user[Id = %d],Name = %s]\n", user.Id, user.Name)
}

// 定义 Users 类型为 User 结构体的切片
type Users []User


// 定义  Len() 、 Less(i, j int) bool、Swap(i, j int) 方法实现 sort.Slice()
func (users Users) Len() int {
    return len(users)
}

func (users Users) Less(i, j int) bool {
    return users[i].Id < users[j].Id
}

func (users Users) Swap(i, j int) {
    users[i], users[j] = users[j], users[i]
}

func main() {
    // 强制类型为 Users 类型将 User 结构体转为切片
    users := Users([]User{
        {1, "z21", "pppp"},
        {20, "z222", "pppp"},
        {187, "zads", "pppp"},
        {230, "z1", "pppp"},
        {100, "zyy", "pppp"},
        {98, "zgg", "pppp"},
    })
    
    // 然后再通过 sort.Sort() 进行排序
    sort.Sort(users)
    fmt.Println(users)
}

2 反射

反射是指在运行时动态的访问和修改任意类型对象的结构和成员,在 go 语言中提供 reflect 包提供反射的功能,每一个变量都有两个属性:类型(Type)和值(Value)

1.相关功能
    reflect 
2.反射技术应用
3.相关功能底层技术使用了反射技术

2.1 json

json 其实就是格式化的字符串。

我们在做文件操作的时候呢,不可避免的要去讨论 json

概述:

JSON(Java Script Object Notation) java 对象的一个标记法,是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。

最经典的用法就是 key-val

JSON 在 2001 年开始推广使用的数据格式,目前已经成为主流的数据格式。

JSON易于机器解析和生成,并有效地提升网络传输效率 , 通常程序在网络传输时会先将数据(结构体、map等)序列化成 json 字符串,到接收方得到 json 字符串时,在反序列化恢复成原来的数据类型(结构体、map等)。这种方式已然成为各个语言的标准。

image-20210504172434994

json 的数据类型

json 是跨语言的

基本数据类型
    数值类型:   1,2,1.1
    字符串类型: "xxxxx"
    布尔类型:   true , fals
列表:列表类型其实就是一个中括号里面表示的
    ["x","y","z"]
映射:其实就是一个大括号,就是 key(一般都是字符串) / value(任意类型)
    {
    "key" : value
    }

2.2 json 应用场景

web 编程中的应用:

image-20210504171422419

左边有一个 web 服务器是通过 go 语言写的,然后下面有一个浏览器,

比如我们要在 web 服务器上给浏览器返回一个数组,首先需要给他进行 json 序列化,序列化之后就得到了一个 json 字符串。

这个时候我们将 json 字符串返回给浏览器(传输)

浏览器接收到 json 字符串过后呢,一般会进行反序列化,也就是说我们的浏览器接收到了 json 字符串。

我们往往还需要将 json 字符串反序列成一个数组或者其他的数据。

反序列化目的:又还原成数组或者其他数据类型

通过这个数组在进行其他的 dom 编程显式操作

tcp 编程中的应用:

image-20210504172135299

这块用 go 语言写了一个聊天系统,因为是聊天系统数据要从 A 客户端发送到 B 客户端。

这个时候也可以通过 json 来进行数据传输

如图,这里有客户端 A 和 客户端 B,A 和 B 通讯往往需要进行中转服务器,这时候这个后台中转服务器是通过 go 语言写的。这个时候我们假设要把客户端 A 的信息发送给 B ,也会通常选用 json 的格式来进行处理。

比如客户端 A 中有一个数组,我们先将其通过序列化转换为 json 字符串,一旦拿到这个字符串过后呢,我们就将这个字符串传给 go 后台服务器。

这个 go 后台服务器拿到 json 字符串以后呢,在原封不动的把这个 json 字符串在传回给 B 客户端。

B 客户端拿到数据之后,其实 B 客户端拿到的是一个 json 字符串,往往会将其进行反序列化。

此时 B 客户端有会得到一个对应的数组,然后在显示

好处:

这样的好处是,大家如果都遵守 json 这种数据格式的话,我们的程序就相对比较好控制了

也就是说大家都遵守 json 格式,我们在转换的时候都比较方便

2.3 json 数据格式说明

在Js语言中,一切都是对象。因此,任何数据类型都可以通过 JSON 来表示,例如字符串、数字、对象、数组、map、结构体 等.

JSON 键值对是用来保存数据一种方式,也就是说在描述一个信息的时候总是通过 键值对来描述

键:我们一般称为属性

值:就是具体对应的数据

键/值对组合中的键名写在前面并用双引号 ""包裹,使用冒号分隔,然后紧接着值 :

{"key1":val1,"key2":val2,"key3":val3,"key4":[val4,val5]}

比如:

{"firstName":"Json"}
# firstName 这个就是键
# json 就是值


{"name":"tom","age":18,"address":["北京","上海"]}
# name 就是键
# tom 为值
# age 键
# 18 值
# address 键
# 北京 上海 为值


[{"name":"tom","age":18,"address":["北京","上海"]},
{"name":"mary","age":28,"address":["广州","重庆"]}]
# 多个属性信息,可以用 [] 包起来,然后再里面单个信息用 {} 分开

好处:

json 的扩展性特别好,而且特别灵活,因为这个键值对可以无限的增加

2.4 json 数据在线解析

http://www.json.cn/ 这个网站可以验证一个 json 格式的数据是否正确。尤其是在我们编写比较复杂的 json 格式数据的时候,非常有用。

也就是说,我们在写一个 json 格式的时候可以通过这个网站来看数据是否正确

在下面这个图中,我们编写多了 json 数据,并且右边为我们的解析之后的信息

image-20210504175218638

2.5 json 编码示例

介绍:

json序列化是指,将有 key-value 结构的数据类型(比如结构体、map、切片)序列化成json字符串的操作。

应用案例: 这里我们介绍一下结构体、map和切片的序列化,其它数据类型的序列化类似。

需要引入 encoding/json

image-20210505155131466

func json.Marshal(v interface{}) ([]byte, error)
func json.MarshalIndent(v interface{}, prefix string, indent string) ([]byte, error)

在 json 中没有结构体字段,所以在转换的时候只能够转为映射结构

package main

import (
    "encoding/json"
    "fmt"
)

type User struct {
    Id       int
    Name     string
    Password string
    age      int // 首字母小写,所以 json.Marshal 包不能访问当转换的时候就没有该字段
    Tel      string
    Addr     string
}

func main() {
    user := User{1, "zz", "123", 24, "177xxxx", "重庆"}
    // 内存结构 => []byte (编码)
    // encod/json
    // Marshal()
    // 转换之后为映射 {} key = 属性名(字符串类型) value = 属性值
    
    // 在生产环境一般使用 json.Marshal() 压缩的 json 格式进行传输,这样比较节省网络资源
    if jsonUser, err := json.Marshal(user); err == nil {
        fmt.Printf("%v\n\n", string(jsonUser))
    }

    // 这是直接转成了 json 格式,一般用于调试时候使用,生产不推荐
    if jsonUser, err := json.MarshalIndent(user, "", "\t"); err == nil {
        fmt.Println(string(jsonUser))
    }
}

执行

[19:41:21 root@go codes]#go run json.go 
{"Id":1,"Name":"zz","Password":"123","Tel":"177xxxx","Addr":"重庆"}

{
        "Id": 1,
        "Name": "zz",
        "Password": "123",
        "Tel": "177xxxx",
        "Addr": "重庆"
}

# 输出我们会发现没有 age 字段以及属性对应的内容

2.6 json 解码示例

我们拿到了编码的字符串数据之后,如何将其转换为内存结构体,我们需要使用到 json.Unmarshal()

func json.Unmarshal(data []byte, v interface{}) error
package main

import (
    "encoding/json"
    "fmt"
)

type User struct {
    Id       int
    Name     string
    Password string
    age      int // 首字母小写,所以 json.Marshal 包不能访问当转换的时候就没有该字段
    Tel      string
    Addr     string
}

func main() {
    // []byte => 内存结构(解码)

    jsonUser := `{"Id":1,"Name":"zz","Password":"123","Tel":"177xxxx","Addr":"重庆"}`
    var user2 User
    
    // 将拿到编码后的数据是一个 string 类型需要转为 []byte
    // 第二个参数解码以后需要存入到一个内存结构体中,所以需要放一个同样类型的地址进来
    if err := json.Unmarshal([]byte(jsonUser), &user2); err != nil {
        fmt.Println("json err = ", err)
    } else {
        fmt.Printf("%#v\n", user2)  // 全格式输出
    }
}

执行

[20:01:29 root@go codes]#go run json.go 
main.User{Id:1, Name:"zz", Password:"123", age:0, Tel:"177xxxx", Addr:"重庆"}

# 由于我们在转换的过程中没有 age 的值,所以输出是 0 

2.7 结构体属性 tag

tag 其实就是对属性的说明,在结构体里面的各个属性打上 tag 标签,让他们输出的时候是小写。

这样的好处是,以后再序列化之后,返回给客户端的时候 浏览器可以用 key 是小写的方式来进行反序列

每一个 tag 一般有标签名和标签值,标签名一般是用来定义给那种格式使用的,标签值一般需要使用 "" 引起来

package main

import (
    "encoding/json"
    "fmt"
)

type User struct {
    // json 格式使用,标签名为 json 值为对应的小写字母
    Id       int    `json:"id"` 
    Name     string `json:"name"`
    Password string `json:"password"`
    Age      int    `json:"age"`
    Addr     string `json:"addr"`
}

func main() {
    // []byte => 内存结构(编码)
    fmt.Println("编码:")
    user1 := User{1, "gg", "12345", 123, "北京"}
    jsonUser, err := json.Marshal(user1)
    if err == nil {
        fmt.Println(string(jsonUser))
    }

    // 解码
    fmt.Println("解码:")
    var user2 User
    
    // 拿到上面编码后的 jsonUser 变量进行解码操作
    if err := json.Unmarshal(jsonUser, &user2); err == nil {
        fmt.Println(user2)
    } else {
        fmt.Println("json err", err)
    }
}

执行

[20:18:31 root@go codes]#go run json.go 
编码:
{"id":1,"name":"gg","password":"12345","age":123,"addr":"北京"}
解码:
{1 gg 12345 123 北京}

# 通过输出可以发现编码后就是对应的小写字母

当然我们在网络传输的时候不想把我们的 password 密码进行传输,可以在打 tag 的时候使用 - 忽略这个值

package main

import (
    "encoding/json"
    "fmt"
)

type P struct {
    // 打 tag 使用 -
    Password string `json:"-"`
}

func main() {
    p := P{"123"}
    
    // 解码
    if p, err := json.Marshal(p); err == nil {
        fmt.Println(string(p))
    }
}

执行

[20:26:32 root@go codes]#go run json2.go 
{}

# 通过输出我们会发现并没有将 password 的值进行输出

2.8 json 解码到文件(持久化)

在 json 中也可以把将数据序列化到文件中

func json.NewEncoder(w io.Writer) *json.Encoder:NewEncoder 返回写入 w 的新编码器。

func (*json.Encoder).Encode(v interface{}) error:Encode将v的json编码写入流,后跟换行符。

package main

import (
    "encoding/json"
    "fmt"
    "os"
)

type User struct {
    Id   int    `json:"id"`
    Name string `json:"name"`
    Age  int    `json:"age"`
    Addr string `json:"addr"`
}

func main() {
    // 创建一个 user 结构体切片
    user := []User{
        {1, "zz", 21, "重庆"},
        {2, "gg", 23, "重庆"},
        {3, "yy", 24, "重庆"},
    }

    // 创建一个文件
    filePath := "user.json"
    file, err := os.Create(filePath)
    if err != nil {
        fmt.Println(err)
    }
    defer file.Close()

    // 创建一个 json 编码器,存放位置是 file 变量
    encoder := json.NewEncoder(file)
    
    // 将 user 进行 json 编码后写入
    encoder.Encode(user)
}

执行

[20:26:46 root@go codes]#go run jsonfile.go 

# 已经写入到文件中
[20:53:25 root@go codes]#cat user.json 
[{"id":1,"name":"zz","age":21,"addr":"重庆"},{"id":2,"name":"gg","age":23,"addr":"重庆"},{"id":3,"name":"yy","age":24,"addr":"重庆"}]

2.9 对 json 文件进行解码操作

func json.NewDecoder(r io.Reader) *json.Decoder:NewDecoder返回从 r 读取的新解码器。

func (*json.Decoder).Decode(v interface{}) errorDecode从输入中读取下一个JSON编码的值,并将其存储在v所指向的值中。

package main

import (
    "encoding/json"
    "fmt"
    "os"
)

// 定义一个和需要解码的结构体一样的数据类型
type User struct {
    Id   int
    Name string
    Age  int
    Addr string
}

func main() {
    // 创建一个 user 结构体切片,因为我们解码的对象是一个 user 结构体切片
    user := []User{}

    // 打开 json 文件
    file, err := os.Open("user.json")
    if err != nil {
        fmt.Println(err)
    }
    defer file.Close()

    // 创建一个 json 解码器
    decode := json.NewDecoder(file)

    // 
    if err := decode.Decode(&user); err != nil {
        fmt.Println(err)
    }
    fmt.Println(user)
}

执行

[20:56:39 root@go codes]#go run jsonfile.go 
[{1 zz 21 重庆} {2 gg 23 重庆} {3 yy 24 重庆}]
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇