结构体的定义和实例创建

在线运行open in new window启动 AI 助手open in new window

// 新的类型
type Person struct {
	Username string
	Age      int
}

// 实现了 fmt.Stringer 接口
func (p Person) String() string {
	return fmt.Sprintf("username: %s, age: %d", p.Username, p.Age)
}

// 这个类型具备的一个行为(方法)
func (p *Person) GrowUp(years int) {
	p.Age += years
}

func main() {
	p1 := Person{"zhangsan", 20}
	fmt.Println(p1)
	fmt.Println(p1.Username)
	fmt.Println(p1.Age)

	p2 := Person{Username: "lisi", Age: 21}
	fmt.Println(p2)

	// 取这个实例的内存地址
	p3 := &Person{Username: "wangwu", Age: 22}
	fmt.Println(p3)

	// new 函数用于创建一个类型的零值
	p4 := new(Person)
	p4.Username = "liuqi"
	p4.Age = 23

	fmt.Println(p4)
	p4.GrowUp(10)
	fmt.Println(p4)
}

程序代码定义了一个名为 Person 的结构体,它有两个字段:Username 和 Age,分别表示人的姓名和年龄。这个结构体实现了 fmt.Stringer 接口,以便可以使用 fmt 包中的方法来格式化该结构体。

此外,还定义了一个名为 GrowUp 的方法,用于增加 Person 结构体中 Age 字段的值。这个方法是通过指针接收器定义的,因此在使用该方法时必须传递 Person 结构体的指针。

在 main 函数中,首先使用字面量创建了一个 Person 类型的实例 p1,然后使用 fmt 包中的方法打印出该实例的字段值和格式化后的字符串。接下来,使用点号语法分别打印出实例 p1 的 Username 和 Age 字段的值。

然后,使用命名字段的方式创建了另一个 Person 实例 p2,再次使用 fmt 包中的方法打印出该实例的字段值。

接着,使用取地址符号 & 创建了一个 Person 类型的指针实例 p3,并打印出该指针的值。

最后,使用 new 函数创建了一个 Person 类型的零值实例 p4,并通过点号语法为该实例的字段赋值,接着调用 GrowUp 方法,将 p4 实例的 Age 增加了 10 年,并再次打印出该实例的字段值。

结构体的嵌套

在线运行open in new window启动 AI 助手open in new window

type Address struct {
	Street  string
	ZipCode string
}

func (a Address) PostalCode() string {
	return a.ZipCode
}

type Person1 struct {
	Username string
	Age      int
	Address
}

type Person2 struct {
	Username string
	Age      int
	*Address
}

type Person3 struct {
	Username string
	Age      int
	Addr     *Address
}

func main() {
	p1 := Person1{Username: "zhangsan", Age: 20, Address: Address{Street: "No. 99", ZipCode: "620000"}}
	fmt.Println(p1)
	fmt.Println(p1.Username, p1.Age, p1.Street, p1.Age)
	fmt.Println(p1.Address)
	fmt.Println(p1.PostalCode())
	fmt.Println(p1.Address.PostalCode())

	p2 := Person2{Username: "lisi", Age: 21, Address: &Address{Street: "No. 99", ZipCode: "620000"}}
	fmt.Println(p2)
	fmt.Println(p2.Street)
	fmt.Println(p2.ZipCode)
	fmt.Println(p2.PostalCode())
	fmt.Println(p2.Address.PostalCode())

	p3 := Person3{Username: "lisi", Age: 21, Addr: &Address{Street: "No. 99", ZipCode: "620000"}}
	fmt.Println(p3)
	fmt.Println(p3.Addr.Street)
	fmt.Println(p3.Addr.ZipCode)
	fmt.Println(p3.Addr.PostalCode)
}

程序代码定义了三个结构体:Address、Person1、Person2 和 Person3。

Address 结构体包含两个字段:Street 和 ZipCode,并且定义了 PostalCode 方法,用于返回 Address 结构体中的 ZipCode 字段。

Person1 结构体包含三个字段:Username、Age 和 Address,其中 Address 是一个嵌入的结构体,表示该人的地址信息。在 main 函数中,使用字面量创建了一个 Person1 实例 p1,其中 Address 字段被初始化为 Address 结构体的一个实例。

在打印 p1 实例时,可以看到该实例包含了 Address 结构体中的字段。通过点号语法,可以分别访问 p1 实例的 Username、Age、Street、ZipCode 和 Address 字段的值。此外,p1 实例可以调用 PostalCode 方法,也可以通过点号语法访问 Address 实例的 PostalCode 方法。

Person2 结构体与 Person1 类似,但是 Address 字段被定义为一个指向 Address 结构体的指针。在 main 函数中,使用字面量创建了一个 Person2 实例 p2,其中 Address 字段被初始化为指向 Address 结构体的一个指针。在打印 p2 实例时,可以通过点号语法直接访问 p2 实例的 Address 指针中的字段。

Person3 结构体也与 Person1 类似,但是 Address 字段被定义为一个指向 Address 结构体的指针 Addr。在 main 函数中,使用字面量创建了一个 Person3 实例 p3,其中 Addr 字段被初始化为指向 Address 结构体的一个指针。在打印 p3 实例时,可以通过点号语法直接访问 p3 实例的 Addr 指针中的字段。

匿名的结构体

在线运行open in new window启动 AI 助手open in new window

func main() {
	p1 := struct {
		username string
		age      int
	}{"zhangsan", 20}
	fmt.Println(p1)
	fmt.Printf("%T\n", p1) // struct { username string; age int }
  fmt.Println(reflect.ValueOf(p1).Kind()) // struct

	p2 := &struct {
		username string
		age      int
	}{"zhangsan", 20}
	fmt.Println(p2)
	fmt.Printf("%T\n", p2) // *struct { username string; age int }
  fmt.Println(reflect.ValueOf(p2).Kind()) // ptr

  fmt.Printf("%p %p\n", &p1, p2) // 0xc0000a8018 0xc0000a8078

	// 空结构体不占内存空间
	n1 := struct{}{}
	n2 := struct{}{}
	n3 := struct{}{}

	// 编译后的地址一样
	fmt.Printf("%p %p %p", &n1, &n2, &n3) // 0x55a130 0x55a130 0x55a130
}

程序代码展示了如何创建和使用匿名结构体和空结构体。在 main 函数中,首先创建了一个匿名结构体 p1,其中包含 username 和 age 两个字段,并使用字面量方式初始化结构体的字段值。在打印 p1 实例时,可以看到该实例的字段值。通过使用 Printf 函数并指定格式 %T,还可以打印出 p1 实例的类型。使用 reflect.ValueOf 函数,可以获取 p1 实例的类型信息,并通过 Kind 方法获得其底层类型的种类。

接下来,使用取地址符号 & 创建了一个匿名结构体的指针 p2,并使用字面量方式初始化其字段值。在打印 p2 实例时,可以看到该实例的指针地址。通过 Printf 函数并指定格式 %T,还可以打印出 p2 实例的类型。使用 reflect.ValueOf 函数,可以获取 p2 实例的类型信息,并通过 Kind 方法获得其底层类型的种类。

在程序的最后,创建了三个空结构体实例 n1、n2 和 n3,并打印出它们在内存中的地址。由于空结构体不包含任何字段,因此其实例不占用内存空间,因此三个实例的地址是相同的。通过 Printf 函数,可以看到它们在内存中的地址都是相同的。

结构体的匿名字段

在线运行open in new window启动 AI 助手open in new window

func main() {
	// 匿名字段
	type student struct {
		int
		string
		float64
	}

	zhangsan := student{10001, "zhansan", 89.5}

	fmt.Println("学号:", zhangsan.int)
	fmt.Println("姓名:", zhangsan.string)
	fmt.Println("分数:", zhangsan.float64)
}

程序代码定义了一个名为 student 的结构体,其中包含三个匿名字段:int、string 和 float64,它们分别表示学号、姓名和分数。在 main 函数中,使用字面量创建了一个 student 实例 zhangsan,并为其三个匿名字段赋值。

在打印 zhangsan 实例时,可以通过使用点号语法访问该实例的匿名字段,并分别打印出学号、姓名和分数的值。由于这三个字段是匿名字段,因此可以使用它们的类型作为字段名来访问它们的值,而不需要提供具体的字段名。在这个例子中,int、string 和 float64 类型分别作为学号、姓名和分数的字段名。

需要注意的是,在实际开发中,建议为结构体中的字段提供有意义的字段名,以提高代码的可读性和可维护性。匿名字段通常只在特定情况下使用,例如在与外部数据源交互时,可以使用匿名字段来表示外部数据源的结构。

结构体的函数类型字段

在线运行open in new window启动 AI 助手open in new window

// 函数类型
type totalAmountFunc func(a, b int) int

// 创建结构
type Product struct {
	price    int
	quantity int
	total totalAmountFunc
}

func main() {
	p1 := Product{
		price:    8,
		quantity: 3,
		total: func(p, q int) int {
			return p * q
		},
	}

	p2 := Product{
		price:    20,
		quantity: 10,
		total: func(p, q int) int {
			return p * q / 2
		},
	}

	fmt.Println(p1.total(p1.price, p1.quantity))
	fmt.Println(p2.total(p2.price, p2.quantity))
}

程序代码定义了一个名为 totalAmountFunc 的函数类型,它接受两个 int 类型的参数,返回一个 int 类型的结果。这个函数类型被定义为一个类型别名,以方便在结构体中使用。

接下来,定义了一个名为 Product 的结构体,它包含三个字段:price、quantity 和 total。其中,price 和 quantity 分别表示产品的单价和数量,total 是一个 totalAmountFunc 类型的函数字段,用于计算产品的总金额。在 main 函数中,使用字面量方式创建了两个 Product 实例 p1 和 p2,并为它们的三个字段赋值。其中,p1 和 p2 分别表示价格为 8 元,数量为 3 个和价格为 20 元,数量为 10 个的产品。

在打印 p1 和 p2 实例的总金额时,可以使用结构体中定义的 total 字段来计算总金额。在这里,total 字段是一个函数类型,因此可以使用字面量方式为其赋值,并在调用时传递 price 和 quantity 两个参数。在这个例子中,使用 total 字段来计算 p1 和 p2 实例的总金额,并将其打印出来。需要注意的是,在计算总金额时,需要传递 price 和 quantity 两个参数,以便函数可以正确地计算总金额。

比较两个结构体是否相等

在线运行open in new window启动 AI 助手open in new window

type Person struct {
	Username string
	Age      int
}

type PersonWithMeta struct {
	Person
	Meta map[string]interface{}
}

type Address1 struct {
	Street  string
	ZipCode string
}

type Address2 struct {
	Street  string
	ZipCode string
}

func main() {
	p1 := Person{Username: "zhangsan", Age: 20}
	p2 := Person{Username: "zhangsan", Age: 20}
	p3 := &Person{Username: "zhangsan", Age: 20}
	p4 := Person{Username: "lisi", Age: 21}

	fmt.Println(p1 == p2)  // true
	fmt.Println(p1 == *p3) // true
	fmt.Println(p1 == p4)  // false

	pm1 := PersonWithMeta{Person: Person{Username: "lisi", Age: 20}, Meta: map[string]interface{}{"company": "google"}}
	pm2 := PersonWithMeta{Person: Person{Username: "lisi", Age: 20}, Meta: map[string]interface{}{"company": "google"}}
	pm3 := &PersonWithMeta{Person: Person{Username: "lisi", Age: 20}, Meta: map[string]interface{}{"company": "google"}}
	pm4 := PersonWithMeta{Person: Person{Username: "lisi", Age: 20}, Meta: map[string]interface{}{"company": "micrsoft"}}

	// fmt.Println(pm1 == pm2) // compile error: struct containing map[string]interface{} cannot be compared
	fmt.Println(reflect.DeepEqual(pm1, pm2))  // true
	fmt.Println(reflect.DeepEqual(pm1, pm3))  // false
	fmt.Println(reflect.DeepEqual(pm1, *pm3)) // true
	fmt.Println(reflect.DeepEqual(pm1, pm4))  // false

	addr1 := Address1{"No.99", "620000"}
	addr2 := Address2{"No.99", "620000"}
	// fmt.Println(addr1==addr2) // compile error: mismatched types Address1 and Address2
	fmt.Println(reflect.DeepEqual(addr1, addr2)) // false

	addr3 := Address1{}
	addr4 := Address2{}
	fmt.Println(reflect.DeepEqual(addr3, addr4)) // false

	es1 := struct{}{}
	es2 := struct{}{}
	fmt.Println(es1 == es2)                  // true
	fmt.Println(reflect.DeepEqual(es1, es2)) // true
	fmt.Printf("%p %p", &es1, &es2)          // 0x564130 0x564130
}

程序代码展示了结构体之间的比较和深度比较。

首先定义了一个名为 Person 的结构体和一个包含 Person 和一个元数据 map 的名为 PersonWithMeta 的结构体。在 main 函数中,分别使用字面量方式创建了四个 Person 实例 p1、p2、p3 和 p4,其中 p3 是一个指向 Person 实例的指针。在比较 p1 和 p2 以及 p1 和 *p3 实例时,因为它们具有相同的字段值,所以比较结果为 true;而在比较 p1 和 p4 时,由于它们的 Username 和 Age 字段不相同,因此比较结果为 false。

接下来,定义了两个结构体 Address1 和 Address2,它们具有相同的字段,但是它们的类型不同。在 main 函数中,分别创建了两个 Address1 和 Address2 实例,并尝试将它们进行比较。由于类型不同,不能直接使用 == 操作符来比较它们的值。但是可以使用 reflect.DeepEqual 函数进行深度比较,该函数会递归地比较两个值,并返回比较结果。在这里,使用 reflect.DeepEqual 函数比较了 addr1 和 addr2 的值,其结果为 false,因为它们的类型不同。

接下来,创建了两个空结构体实例 es1 和 es2,并尝试将它们进行比较。由于空结构体不包含任何字段,它们的实例值相同,因此比较结果为 true。可以使用 == 操作符或 reflect.DeepEqual 函数进行比较,它们的结果都是 true。

需要注意的是,对于包含 map、slice 和函数等引用类型的结构体,不能使用 == 操作符进行比较,因为它们只会比较两个结构体的地址是否相同。这种情况下,可以使用 reflect.DeepEqual 函数进行深度比较,或者自定义比较函数来比较结构体的值。

用常规方式打印结构体

在线运行open in new window启动 AI 助手open in new window

type Person struct {
	Username string
	Age      int
}

// 打开这个 fmt.Stinger 实现函数看差别
// func (p Person) String() string {
// 	return fmt.Sprintf("(%s,%d)", p.Username, p.Age)
// }

func main() {
	p1 := Person{"zhangsan", 20}
	p2 := &Person{"lisi", 21}

	fmt.Printf("p1=%T\n", p1)  // main.Person
	fmt.Printf("p1=%v\n", p1)  // {zhangsan 20}
	fmt.Printf("p1=%#v\n", p1) // main.Person{Username:"zhangsan", Age:20}
	fmt.Println(p1)            // (zhangsan,20)

	fmt.Printf("p2=%T\n", p2)  // *main.Person
	fmt.Printf("p2=%v\n", p2)  // (lisi,21)
	fmt.Printf("p2=%#v\n", p2) // &main.Person{Username:"lisi", Age:21}
	fmt.Println(p2)            // (lisi,21)
}

程序代码展示了使用 fmt 包中的格式化函数打印结构体的不同方式。首先定义了一个名为 Person 的结构体,在 main 函数中,使用字面量方式创建了两个 Person 实例 p1 和 p2,分别是值类型和指针类型。

在打印 p1 和 p2 实例时,使用了 fmt 包中的 Printf 和 Println 函数。其中,%T 格式化符号用于打印值的类型信息,%v 格式化符号用于打印值的默认格式,%#v 格式化符号用于打印值的 Go 语法表示,而不是默认格式。同时,也可以使用 fmt.Stringer 接口中定义的 String 方法来打印结构体的值。需要注意的是,在使用 fmt.Print 系列函数打印结构体时,如果结构体实现了 fmt.Stringer 接口,它们将使用 String 方法打印结构体的值。

在这里,使用不同的格式化函数打印了 p1 和 p2 实例,并观察了它们的输出结果。可以看到,在打印 p1 和 p2 实例时,使用了不同的格式化函数,它们的输出结果也不同。需要根据具体的场景选择合适的打印方式,以提高代码的可读性和可维护性。

结构体序列化成 JSON

在线运行open in new window启动 AI 助手open in new window

type Address struct {
	Street  string `json:"street"`
	ZipCode string `json:"postalCode"`
}

type Person struct {
	Username string   `json:"user"`
	Age      int      `json:"age"`
	Salary   int      `json:"salary,omitempty"`
	Addr     *Address `json:"address,omitempty"`
}

func main() {
	p1 := Person{"zhangsan", 20, 30000, &Address{"No. 99", "620000"}}
	jsonBytes, _ := json.Marshal(&p1)
	fmt.Println(string(jsonBytes)) // {"user":"zhangsan","age":20,"salary":30000,"address":{"street":"No. 99","postalCode":"620000"}}

	p2 := Person{"zhangsan", 20, 0, nil}
	jsonBytes, _ = json.Marshal(&p2)
	fmt.Println(string(jsonBytes)) // {"user":"zhangsan","age":20}
}

程序代码展示了如何在结构体字段上使用标签来控制 JSON 编码和解码的行为。在定义结构体 Person 和 Address 时,使用了 json:"..." 标签来指定字段在 JSON 编码和解码中所使用的名称。例如,在 Address 结构体中,将 Street 字段的 JSON 名称设置为 "street",将 ZipCode 字段的 JSON 名称设置为 "postalCode"。

在 main 函数中,使用字面量方式创建了两个 Person 实例 p1 和 p2,并使用 json.Marshal 函数将它们转换为 JSON 格式的字符串。在转换 p1 时,由于它具有非零的 Salary 字段和非空的 Addr 字段,所以它们会被包含在 JSON 中。而在转换 p2 时,由于它的 Salary 字段为零值,而 Addr 字段为 nil,所以它们不会被包含在 JSON 中,同时也不会在 JSON 字符串中留下空的字段名称。

需要注意的是,使用结构体标签可以控制字段在 JSON 编码和解码中的行为,但是如果字段类型不受支持,如函数类型,则无法通过标签来控制 JSON 的行为。同时,也可以使用 json.Unmarshal 函数将 JSON 字符串转换为结构体实例,其中也会根据结构体字段上的标签来解析 JSON 字段的名称。

JSON 反序列化为结构体

正在运行open in new window启动 AI 助手open in new window

type Address struct {
	Street  string `json:"street"`
	ZipCode string `json:"postalCode"`
}

type Person struct {
	Username string   `json:"user"`
	Age      int      `json:"age"`
	Salary   int      `json:"salary,omitempty"`
	Addr     *Address `json:"address,omitempty"`
}

func main() {
	var p1, p2 Person

	jsonBytes := []byte(`{"user":"zhangsan","age":20,"salary":30000,"address":{"street":"No. 99","postalCode":"620000"}}`)
	_ = json.Unmarshal(jsonBytes, &p1)
	fmt.Println(p1)      // {zhangsan 20 30000 0xc000060060}
	fmt.Println(p1.Addr) // &{No. 99 620000}

	jsonBytes = []byte(`{"user":"zhangsan","age":20}`)
	_ = json.Unmarshal(jsonBytes, &p2)
	fmt.Println(p2) // {zhangsan 20 0 <nil>}
}

程序代码展示了如何使用 json 包中的函数将 JSON 字符串转换为结构体实例。在定义结构体 Person 和 Address 时,使用了 json:"..." 标签来指定字段在 JSON 编码和解码中所使用的名称。在 main 函数中,使用了 json.Unmarshal 函数将 JSON 字符串转换为结构体实例。在解码时,需要提供一个指向结构体实例的指针。在本例中,使用了 p1 和 p2 两个变量来分别存储解码后的结构体实例。

在第一个示例中,使用 json.Unmarshal 函数将 JSON 字符串解码为结构体 p1。在解码后,可以通过打印 p1 变量来查看解码结果。可以看到,结构体实例的字段值被正确地填充,并且 Addr 字段是一个非空的指针,指向一个包含 Street 和 ZipCode 字段的 Address 结构体实例。

在第二个示例中,使用 json.Unmarshal 函数将 JSON 字符串解码为结构体 p2。在解码后,可以看到,结构体实例的字段值被正确地填充,但是由于 JSON 字符串中没有包含 Addr 字段,所以 Addr 字段的值为 nil。

需要注意的是,使用 json.Unmarshal 函数将 JSON 字符串解码为结构体实例时,JSON 字符串中的字段名称和结构体字段上的标签名称应该匹配,否则解码将失败。此外,如果 JSON 字符串中的某个字段的类型不符合结构体字段的类型,则解码也将失败。在实际应用中,需要仔细检查和处理 JSON 字符串和结构体实例之间的转换,以保证数据的正确性和可靠性。

反序列化事先不确定的类型

在线运行open in new window启动 AI 助手open in new window

type Message struct {
	Type      string          `json:type`
	CreatedAt string          `json:createdAt`
	Data      json.RawMessage // 本质是 []byte 类型
}

type Event struct {
	Type    string `json:type`
	Creator string `json:creator`
}

type News struct {
	Type    string `json:type`
	Creator string `json:creator`
}

func pasrseUnsureData(m *Message) {
	if m.Type == "event" {
		var e Event
		_ = json.Unmarshal([]byte(m.Data), &e)
		fmt.Println(e)
	} else if m.Type == "news" {
		var n News
		_ = json.Unmarshal([]byte(m.Data), &n)
		fmt.Println(n)
	}
}

func main() {
	var m Message

	jsonStr := []byte(`{"type": "event","data": {"type": "paid","creator": "zhangsan"}}`)
	_ = json.Unmarshal(jsonStr, &m)
	pasrseUnsureData(&m)

	jsonStr = []byte(`{"type": "news","data": {"type": "war","creator": "lisi"}}`)
	_ = json.Unmarshal(jsonStr, &m)
	pasrseUnsureData(&m)
}

程序代码展示了如何使用 json 包中的函数将 JSON 字符串转换为不确定的类型(Event 或 News)。

在定义结构体 Message 时,使用了 json.RawMessage 类型的 Data 字段来表示不确定类型的数据。使用 json.Unmarshal 函数将 JSON 字符串解码为 Message 结构体实例。在解码时,Data 字段被解码为一个 json.RawMessage 类型的值。因为 json.RawMessage 的本质是 []byte 类型,可以将其作为参数传递给 json.Unmarshal 函数,并将结果解码为 Event 或 News 结构体。

在函数 parseUnsureData 中,根据 Message 实例的 Type 字段的值,决定将 Data 字段解码为 Event 或 News 结构体,并将解码后的结构体打印出来。在 main 函数中,通过解析不同的 JSON 字符串来测试 parseUnsureData 函数的功能。

需要注意的是,如果 JSON 字符串中的某个字段的类型不符合结构体字段的类型,则解码将失败。在实际应用中,需要仔细检查和处理 JSON 字符串和结构体实例之间的转换,以保证数据的正确性和可靠性。

获取结构体成员的 tag 信息

在线运行open in new window启动 AI 助手open in new window

type Person struct {
	Username string `json:"username"`
	Age      int    `json:"username" db:"age,omitempty"`
}

func main() {
	p1 := &Person{"zhangsan", 20}
	field, _ := reflect.TypeOf(p1).Elem().FieldByName("Username")
	tagName := field.Tag.Get("json")
	fmt.Println(tagName) // username

	p2 := Person{"lisi", 20}
	field, _ = reflect.TypeOf(p2).FieldByName("Age")
	tagName = field.Tag.Get("db")
	fmt.Println(tagName) // age,omitempty

	tagName, exists := field.Tag.Lookup("xml")
	if !exists {
		fmt.Println("xml tag not exists")
	}
}

程序代码演示了如何使用反射(reflect)包的函数获取结构体字段的标签(tag)。在结构体定义中,通过在字段后添加反引号括起来的字符串来定义字段的标签。标签字符串可以包含多个键值对,键和值之间使用冒号分隔,多个键值对之间使用空格分隔。

在程序中,定义了一个结构体 Person,它包含两个字段 Username 和 Age,并在它们的定义中添加了标签 json 和 db。在 main 函数中,使用 reflect.TypeOf 函数获取结构体类型,再调用 FieldByName 或者 TypeOf().FieldByName 函数获取结构体字段类型的反射值,然后调用 Tag.Get 函数获取标签的值。如果标签中包含多个键值对,可以使用 Lookup 函数获取特定键的值。

需要注意的是,在使用反射获取标签值之前,必须使用 Elem 函数获取指向结构体的指针类型,并且该指针类型的指针才能获取到结构体的类型,否则会出现 panic。另外,如果标签值不存在,Tag.Get 和 Tag.Lookup 函数都将返回空字符串,可以通过返回值的长度是否为 0 来判断标签是否存在。

遍历结构体成员的类型

在线运行open in new window启动 AI 助手open in new window

func checkFieldType(x interface{}) {
	val := reflect.ValueOf(x)
	if val.Kind() == reflect.Ptr {
		val = val.Elem()
	}

	switch val.Kind() {
	case reflect.Int:
		fmt.Println("int", val)
	case reflect.String:
		fmt.Println("string", val)
	case reflect.Struct:
		for i := 0; i < val.NumField(); i++ {
			checkFieldType(val.Field(i).Interface())
		}
	case reflect.Slice, reflect.Array:
		for i := 0; i < val.Len(); i++ {
			checkFieldType(val.Index(i).Interface())
		}
	case reflect.Map:
		for _, key := range val.MapKeys() {
			checkFieldType(val.MapIndex(key).Interface())
		}
	case reflect.Chan:
		for v, ok := val.Recv(); ok; v, ok = val.Recv() {
			checkFieldType(v.Interface())
		}
	case reflect.Func:
		res := val.Call(nil)
		for _, v := range res {
			checkFieldType(v.Interface())
		}
	}
}

func main() {
	x := struct {
		Username string
		Age      int
		Address  struct {
			Street  string
			ZipCode string
		}
		Tags   []string
		Extra  map[string]interface{}
		Salary func() int
	}{
		"zhangsan", 20,
		struct {
			Street  string
			ZipCode string
		}{"No. 99", "620000"},
		[]string{"one", "two"},
		map[string]interface{}{"1": "one", "2": 2},
		func() int {
			return 30000
		}}

	/*
	   string zhangsan
	   int 20
	   string No. 99
	   string 620000
	   string one
	   string two
	   string one
	   int 2
	   int 30000
	*/
	checkFieldType(x)
}

程序定义了一个名为checkFieldType的函数,它接受一个interface{}类型的参数。该函数使用reflect包来确定参数的类型,并根据类型执行不同的操作。具体来说,函数检查参数是否为指针。如果是指针,则使用Elem()方法对其进行解引用以获取实际值。

如果值是整数或字符串,则函数打印"int"或"string",后跟变量的值。

如果值是结构体,则函数使用NumField()方法获取字段数量和Field()方法获取每个字段的值,对结构体的每个字段递归调用自身。

如果值是切片或数组,则函数使用Len()方法获取长度和Index()方法获取每个元素的值,对切片或数组的每个元素递归调用自身。

如果值是映射,则函数使用MapKeys()方法获取键和MapIndex()方法获取值,并对映射的每个键和值递归调用自身。

如果值是通道,则函数使用Recv()方法接收值,并对通道接收到的每个值递归调用自身。

如果值是函数,则函数使用Call()方法调用该函数,并对每个返回值递归调用自身。

该程序还定义了一个main()函数,它创建一个包含各种类型字段的结构体,并在其上调用checkFieldType()。期望的输出是结构体中各个字段的类型和值的列表。

实现接口的结构体对象

在线运行open in new window启动 AI 助手open in new window

type Moveable interface {
	Move(step int)
}

type Person struct {
	X, Y int
}

func (p *Person) Move(step int) {
	p.X += step
	p.Y += step
}

type Snake struct {
	X, Y int
}

func (s *Snake) Move(step int) {
	s.X += 10 * step
	s.Y += 10 * step
}

func main() {
	var m Moveable

	m = &Person{}
	m.Move(2)
	fmt.Println(m)           // &{2 2}
	fmt.Println(m.(*Person)) // &{2 2}

	m = &Snake{}
	m.Move(5)
	fmt.Println(m)          // &{50 50}
	fmt.Println(m.(*Snake)) // &{50 50}
}

程序定义了一个接口Moveable和两个结构体Person和Snake,它们都实现了Moveable接口的Move方法。Move方法接受一个整数参数,并将对象的坐标向右上方移动。

在main函数中,首先定义一个Moveable类型的变量m。然后,将m分别赋值为&Person{}和&Snake{}的指针,通过调用Move方法移动它们,并输出它们的值和类型。这里要注意的是,在将m赋值为具体类型的指针时,需要使用类型断言m.(*Person)或m.(*Snake)将其转换为指向该类型的指针,才能正确地输出它们的值和类型。

Last Updated:
Contributors: Bob Wang