Golang面向API编程-interface(接口)

                    Golang面向API编程-interface(接口)

                                          作者:尹正杰

版权声明:原创作品,谢绝转载!否则将追究法律责任。

 

 

 

  Golang并不是一种典型的面向对象编程(Object Oriented Programming,OOP,面向对象程序设计)语言。它在语法上不支持类和继承的概念。没有继承是否就无法拥有多态行为了呢?答案是否定的,我们知道 Golang 中没有 class 的概念,而是通过 interface 类型转换支持在动态类型语言中常见的“鸭子类型”达到运行时多态的效果。

 

一.什么是interface

  简单的说,interface是一组method的组合,我们通过interface来定义对象的一组行为。换句话说,一个 interface 类型定义了一个“方法集合”作为其接口。 interface类型的变量可以保存含有属于这个interface类型的任何类型的值,这时我们就说这个类型实现了这个接口。未被初始化的interface类型变量的零值为空(nil)。

 

二.interface类型和值

  接口类型实际上是一组method(方法)签名的清单。interface 类型定义了一组方法,如果某个对象实现了某个接口的所有方法,则此对象就实现了此接口。接口也是一种数据类型。如果你声明了一个接口变量,这个变量能够存储任何实现该接口的对象类型。最后,任意的类型都实现了空interface(我们这样定义:interface{}),也就是包含 0 个method的interface。所以我喜欢给它起一个绰号叫“大胃王”。

  定义一个interface以及调用方式如下:

 1 /*
 2 #!/usr/bin/env gorun
 3 @author :yinzhengjie
 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
 5 EMAIL:y1053419035@qq.com
 6 */
 7 
 8 package main
 9 
10 import (
11     "fmt"
12     "strings"
13 )
14 
15 type Human struct {
16     Name string            //定义姓名
17     string                //内置的匿名字段,我们用它来定义家庭住址。
18     phone int            //电话号码
19 }
20 
21 type Student struct {
22     Human                 //匿名字段,其类型就是我们上面自定义的类型。
23     Classroom string    //教室名称
24     Score float64        //考试成绩
25 }
26 
27 type Teacher struct {
28     Human                 //匿名字段
29     Position string        //职位信息
30     salary float64        //薪资情况
31 }
32 
33 
34 func (h Human) SayHi() {                    //定义结构体“Human”自我介绍的方法。
35     fmt.Printf("Hello, my name is 【%s】. My phone number is【%d】.My home address is【%s】\n", h.Name,
36         h.phone,h.string)                    //格式化输出是可以换行的哟。
37 }
38 
39 
40 func (h Human) Sing(Name string) {          //定义结构体“Human”唱歌的方法
41     fmt.Printf("【%s】:我有一只小毛驴我从来也不骑,有一天我心血来潮骑它去赶集.....\n", Name)
42 }
43 
44 
45 func (t Teacher) SayHi() {                    //定义结构体“Teacher”自我介绍的方法。
46     fmt.Printf("Anyway, I'm your 【%s】 teacher, you can call me 【%s】,My salary is...【%f】\n", t.Position,
47         t.Human.Name,t.salary)
48 }
49 
50 
51 type Superman interface {                    //定义一个接口
52     SayHi()                                    //这个接口包含“SayHi()    ”方法。
53     Sing(Name string)                        //该接口也包含“Sing(Name string)”方法。
54 }
55 
56 func main() {
57     yzj := Student{Human{"尹正杰", "北京", 7474741}, "三年级一班", 95} //初始化我们定义的结构体。我们也可以将这个过程叫做实例化。而将“yzj”叫做实例。
58     hsy := Student{Human{"韩森雨", "北京", 2424241}, "一年级五班", 100}
59     bingan := Teacher{Human{"饼干", "北京", 6464641}, "Golang", 30000}
60 
61 
62     var yinzhengjie Superman  //声明一个变量,其类型为我们定义的接口。
63 
64     yinzhengjie = yzj                        //注意了,这是接受了我们定义第一个类型。
65     yinzhengjie.SayHi()                        //并且可以调用这个类型下的“SayHi()”方法已经“Sing("高音唱")”方法。
66     yinzhengjie.Sing("高音唱")
67 
68 
69     fmt.Println("\n",strings.Repeat("*",30),"我是分割线",strings.Repeat("*",30),"\n")
70 
71     yin := make([]Superman,3)    //处理上面的那种最普遍的玩法,当然也可以用make方法将其定义成切片的方式。
72     yin[0], yin[1], yin[2] = hsy, bingan, yzj //用下标来区分不同的实力。这个时候我们可以发现Superman类型可以接受“Student”和“Teacher”类型的数据,尽管这是两个不同的结构体,但是照样可以通过一个接口来调用,因此我叫它“大胃王”。
73 
74     for _, value := range yin{ //然后我们就可以同时调用3个方法啦!
75         value.SayHi()
76     }
77 }
78 
79 
80 
81 #以上代码执行结果如下:
82 Hello, my name is 【尹正杰】. My phone number is【7474741】.My home address is【北京】
83 【高音唱】:我有一只小毛驴我从来也不骑,有一天我心血来潮骑它去赶集.....
84 
85  ****************************** 我是分割线 ****************************** 
86 
87 Hello, my name is 【韩森雨】. My phone number is【2424241】.My home address is【北京】
88 Anyway, I'm your 【Golang】 teacher, you can call me 【饼干】,My salary is...【30000.000000】
89 Hello, my name is 【尹正杰】. My phone number is【7474741】.My home address is【北京】

   通过上面的代码我们可以知道,interface 可以被任意的对象实现。同理,一个对象可以实现任意多个interface。你会发现interface 就是一组抽象方法的集合,它必须由其他非interface类型实现,而不能自我实现, go 通过 interface 实现了duck-typing:即"当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子"。

 

三.空interface

   空interface(interface{})不包含任何的 method,正因为如此,所有的类型都实现了空interface。空 interface 对于描述起不到任何的作用(因为它不包含任何的 method),但是空interface 在我们需要存储任意类型的数值的时候相当有用,因为它可以存储任意类型的数值。一个函数把interface{}作为参数,那么他可以接受任意类型的值作为参数,如果一个函数返回interface{},那么也就可以返回任意类型的值。是不是瞬间就觉得interface很神奇!

 1 /*
 2 #!/usr/bin/env gorun
 3 @author :yinzhengjie
 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
 5 EMAIL:y1053419035@qq.com
 6 */
 7 
 8 package main
 9 
10 import (
11     "fmt"
12     "reflect"
13 )
14 
15 func Myecho(a interface{}) {
16     fmt.Printf("变量的值是:\033[31;1m【%v】\033[0m,其类型是:\033[31;1m【%v】\033[0m\n",a,reflect.TypeOf(a))
17 }
18 
19 func main()  {
20       Name := "尹正杰"                      //我这里定义了三种不同的类型,即字符串,整数,字节数等等。
21       Age := 18
22       Language := []byte("Golang")
23       fmt.Println(reflect.TypeOf(Name),reflect.TypeOf(Age),reflect.TypeOf(Language))
24       var yinzhengjie interface{}            //定义一个空的interface,由于每种数据类型都实现了空interface。因此我们利用这个特性可以接受任意类型的数据。
25       yinzhengjie = Name
26       Myecho(yinzhengjie)
27       yinzhengjie = Age
28       Myecho(yinzhengjie)
29       yinzhengjie = Language
30       Myecho(yinzhengjie)
31 }
32 
33 
34 
35 #以上代码输出结果如下:
36 string int []uint8
37 变量的值是:【尹正杰】,其类型是:【string】
38 变量的值是:【18】,其类型是:【int】
39 变量的值是:【[71 111 108 97 110 103]】,其类型是:【[]uint8】

 

 

四.interface 函数参数

   interface 的变量可以持有任意实现该 interface 类型的对象,这给我们编写函数(包括method)提供了一些额外的思考,我们是不是可以通过定义 interface 参数,让函数接受各种类型的参数。举个例子:fmt.Println 是我们常用的一个函数,但是你是否注意到它可以接受任意类型的数据。打开fmt的源码文件,你会看到这样一个定义:

 

  接下来我们就来模拟实现“fmt.Println()”的stringer方法吧:

 1 /*
 2 #!/usr/bin/env gorun
 3 @author :yinzhengjie
 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
 5 EMAIL:y1053419035@qq.com
 6 */
 7 
 8 package main
 9 
10 import (
11     "strconv"
12     "fmt"
13 )
14 
15 type Student struct {
16     Name string   //定义姓名
17     age int         //定义年龄
18     string        //定义住址,这是匿名字段
19 }
20 
21 func (s Student)String()string { //给Student实现来String方法,如果我们把String前面加个其他字母或是进行其他修改,可能会导致该方法的内容不会被调用。
22     return "My name is "+ s.Name+",I am "+strconv.Itoa(s.age)+" years old.I live in "+s.string
23 }
24 
25 func main() {
26     yzj := Student{"尹正杰",18,"北京"}
27     fmt.Println("This people is :",yzj)
28 }
29 
30 
31 
32 #以上代码输出结果如下:
33 This people is : My name is 尹正杰,I am 18 years old.I live in 北京

 

五.interface 变量存储的类型

  我们知道interface 的变量里面可以存储任意类型的数值(该类型实现了interface)。那么我们怎么反向知道这个变量里面实际保存了的是哪个类型的对象呢?目前常用的有两种方法:“Comma-ok 断言” 和“switch 测试”。

1.Comma-ok 断言

 1 /*
 2 #!/usr/bin/env gorun
 3 @author :yinzhengjie
 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
 5 EMAIL:y1053419035@qq.com
 6 */
 7 
 8 package main
 9 import (
10     "fmt"
11     "strconv"
12 )
13 type Element interface{}
14 
15 const   (    //这是定义一个常量的关键字
16     pi = 3.14
17 )
18 
19 type Student struct {
20     Name string
21     age int
22 }
23 
24 //定义了 String 方法,实现了fmt.Stringer
25 func  (p Student) String() string {
26     return "(name: " + p.Name + " - age: "+strconv.Itoa(p.age)+ " years old!)"
27 }
28 func main() {
29     list := make([]Element, 4)
30     list[0] = 1                                               // 定义一个“int”类型的数据。
31     list[1] = "Hello"                                         // 定义一个“string”类型的数据。
32     list[2] = Student{"Yinzhengjie", 18}    // 定义一个“Student”类型的数据。
33     list[3] = pi                                            //定义一个常量。
34 
35     for index, element := range list {      //接下来就是判断里面的每一个元素属于哪一种类型。
36         if value, ok := element.(int); ok {        //判断当前的数据类型是否为“int”类型
37             fmt.Printf("list[%d] is an int and its value is %d\n", index, value)
38         } else if value, ok := element.(string); ok {    //判断当前的数据类型是否为“string”类型
39             fmt.Printf("list[%d] is a string and its value is %s\n", index, value)
40         } else if value, ok := element.(Student); ok {    //判断当前的数据类型是否为自定义的“Student”类型
41             fmt.Printf("list[%d] is a Student and its value is %s\n", index, value)
42         } else {
43             fmt.Printf("list[%d] is of a different type!", index)
44         }
45     }
46 }
47 
48 
49 
50 
51 #以上代码输出结果如下:
52 list[0] is an int and its value is 1
53 list[1] is a string and its value is Hello
54 list[2] is a Student and its value is (name: Yinzhengjie - age: 18 years old!)
55 list[3] is of a different type!

 

2.switch 测试

  如果上面的那种方式你能看懂,并且之前我也分享过golang流程控制的笔记,那么下面的这种断言方式对你来说就是小case啦~从代码的易读性的话我推荐使用这种方式进行对数据类型的断言。

 1 /*
 2 #!/usr/bin/env gorun
 3 @author :yinzhengjie
 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
 5 EMAIL:y1053419035@qq.com
 6 */
 7 
 8 
 9 package main
10 
11 import (
12     "strconv"
13     "fmt"
14 )
15 
16 type Element interface {}
17 
18 
19 type Student struct {
20     Name string
21     age int
22 }
23 
24 func (p Student) String() string    {
25     return "My name is "+ p.Name+" and I am "+ strconv.Itoa(p.age) +" years old!"
26 }
27 
28 func main() {
29     list := make([]Element,3)
30     list[0] = 1
31     list[1] = "yinzhengjie"
32     list[2] = Student{"yinzhengjie",18}
33 
34     for k,v := range list {
35         switch value := v.(type) {         //element.(type) 语法不能在switch 外的任何逻辑里面使用,如果你要在switch 外面判断一个类型就使用 comma-ok 。
36         case int:
37             fmt.Printf("list[%d] is an int and its value is %d\n",k,value)
38         case string:
39             fmt.Printf("list[%d] is an string and its value is %s\n",k,value)
40         case Student:
41             fmt.Printf("list[%d] is an Student and its value is %v\n",k,value)
42         default:
43             fmt.Printf("list[%d] is  of a different\n",)
44         }
45     }
46 }
47 
48 
49 
50 
51 #以上代码输出结果如下:
52 list[0] is an int and its value is 1
53 list[1] is an string and its value is yinzhengjie
54 list[2] is an Student and its value is My name is yinzhengjie and I am 18 years old!

 

 

六.嵌入 interface

  看到官方使用的嵌入interface方法你是否想到我们之前说的匿名字段啦?Go里面真正吸引人的是他内置的逻辑语法,就像我们在学习Struct 时学习的匿名字段,多么的优雅啊,那么相同的逻辑引入到 interface 里面,那不是更加完美了。如果一个interface1 作为 interface2 的一个嵌入字段,那么 interface2 隐式的包含了interface1 里面的method。接下来我们就来举个例子自定义实现以下嵌入interface的案例吧,具体代码如下:

 1 /*
 2 #!/usr/bin/env gorun
 3 @author :yinzhengjie
 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
 5 EMAIL:y1053419035@qq.com
 6 */
 7 
 8 package main
 9 
10 import "fmt"
11 
12 type Student struct {    // 定义结构 Employee
13     Name   string
14     age    int
15     salary int
16     gender string
17 }
18 
19 // 定义结构 Employee 的方法
20 func (self *Student) GetName() string {
21     return self.Name
22 }
23 
24 func (self *Student) GetAge() int {
25     return self.age
26 }
27 
28 func (self *Student) GetSalary() int {
29     return self.salary
30 }
31 
32 func (self *Student) Help() {
33     fmt.Println("Don't ask me, ask me, I won't tell you!")
34 }
35 
36 func (self *Student) GetGender() string {
37     return self.gender
38 }
39 
40 type MiyoshiStudents interface {    // 定义接口类型 MiyoshiStudents 包含获取基本信息的方法
41     GetName() string
42     GetAge() int
43 }
44 
45 type Teacher interface {    // 定义接口类型 Teacher 包含获取薪水的方法且 Teacher 接口中嵌入了 MiyoshiStudents 接口,前者将获取后者的所有方法。
46     MiyoshiStudents        //这就是嵌入interface和嵌入匿名字段的用法有点相似。
47     GetSalary() int
48     Help()
49 }
50 
51 func main() {
52     yzj := Student{        // yzj 实现了 MiyoshiStudents 和 Teacher 这两个接口
53         Name:   "尹正杰",
54         age:    18,
55         salary: 100000000,
56         gender: "Male",
57     }
58     fmt.Println("yzj is: ", yzj)
59     yzj.Help()
60     fmt.Println("yzj.name = ", yzj.GetName())
61     fmt.Println("yzj.age = ", yzj.GetAge())
62     fmt.Println("yzj.salary = ", yzj.GetSalary())
63 
64     var yinzhengjie Teacher = &yzj
65 
66     switch yinzhengjie.(type) {    // 接口类型转换,从超集到子集的转换是可以的,从方法集的子集到超集的转换会导致编译错误,这种情况下 switch 不支持 fallthrough。
67     case nil:
68         fmt.Println("空接口(nil)")
69     case MiyoshiStudents:
70         fmt.Println("MiyoshiStudents 接口")
71     default:
72         fmt.Println("位置接口")
73     }
74 }
75 
76 
77 
78 
79 #以上代码执行结果如下:
80 yzj is:  {尹正杰 18 100000000 Male}
81 Don't ask me, ask me, I won't tell you!
82 yzj.name =  尹正杰
83 yzj.age =  18
84 yzj.salary =  100000000
85 MiyoshiStudents 接口

 

七.匿名接口

  还记得匿名字段吗?我们可以在一个结构体中定义一个匿名字段,这个匿名字段可以是内置的也可以是我们自定义的,而interface是一种特殊的数据类型,因为golang认为所有的数据类型都实现了空接口,也就是说所有数据都是空interface的子集。换句话说,我们可以说一个空的interface是可以接受任何类型的数据的。通过这一点,它会给我们很多启发吗,我们可以通过interface来接受任何类型的参数,也可以通过interface来返回任何类型的参数,接下来我们一起看下匿名interface的使用案例吧:

 1 /*
 2 #!/usr/bin/env gorun
 3 @author :yinzhengjie
 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
 5 EMAIL:y1053419035@qq.com
 6 */
 7 
 8 package main
 9 
10 import "fmt"
11 
12 
13 type Student struct {    // 定义结构体Student
14     Name   string
15     age    int
16     salary int
17     gender string
18 }
19 
20 func (self *Student) GetName() string {        // 定义结构 Student 的方法
21     return self.Name
22 }
23 
24 func (self *Student) GetAge() int {
25     return self.age
26 }
27 
28 func (self *Student) GetSalary() int {
29     return self.salary
30 }
31 
32 func (self *Student) Help() {
33     fmt.Println("This is help info.")
34 }
35 
36 type MiyoshiStudents struct {
37     GetInfo interface {    // 匿名接口可以被用作变量或者结构属性类型,我们定义了一个“GetInfo”的匿名接口,里面可以存储各种数据属性。
38         GetGender() string
39         GetSalary() int
40         GetAge()    int
41         GetName() string
42     }
43 }
44 
45 func (self *Student) GetGender() string {
46     return self.gender
47 }
48 
49 
50 func main() {
51     yzj := MiyoshiStudents{&Student{        // 匿名接口对象的使用
52         Name:   "尹正杰",
53         age:    18,
54         salary: 10000000000,
55         gender: "男孩",
56     }}
57     fmt.Println("姓名:",yzj.GetInfo.GetName())
58     fmt.Println("年龄:",yzj.GetInfo.GetAge())
59     fmt.Println("性别: ", yzj.GetInfo.GetGender())
60     fmt.Println("期望薪资:",yzj.GetInfo.GetSalary())
61 
62 }
63 
64 
65 
66 #以上代码运行结果如下:
67 姓名: 尹正杰
68 年龄: 18
69 性别:  男孩
70 期望薪资: 10000000000

 

八.进阶知识-Go语言的反射三定律

1.什么是反射

  反射是指程序可以访问、检测和修改它本身状态或行为的一种能力,所以给的定义就是说明了它能干嘛。我们平时用反射主要做:获取类型的相关信息,动态调用方法,动态构造对象,从程序集中获得类型。

 

2.为什么需要反射

  Go是静态类型语言。每个变量都有且只有一个静态类型,在编译时就已经确定。尽管变量两个边路都具有共同的底层数据类型,但它们的只要他们静态类型不一样。不经过类型转换直接相互赋值时,编译器会报错。相信大家通过一段熟悉的代码就应该明白了:

 1 /*
 2 #!/usr/bin/env gorun
 3 @author :yinzhengjie
 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
 5 EMAIL:y1053419035@qq.com
 6 */
 7 
 8 package main
 9 
10 import "fmt"
11 
12 type Myint int
13 
14 type Element interface{}    //定义一个空接口
15 
16 var (
17     x int
18     y Myint  //尽管变量 x 和 y 具有共同的底层类型 int,但它们的静态类型并不一样。
19 )
20 func main() {
21     x = 100
22     y = 100
23     list := make([]Element, 2)
24     list[0] = x
25     list[1] = y
26     fmt.Println(list)
27     for k,v := range list{
28         switch value := v.(type) {        //我们队数据类型进行断言。
29         case int:
30             fmt.Printf("list[%d] is an int(整型) and its value is %d\n",k,value)
31         case string:
32             fmt.Printf("list[%d] is an string(字符串) and its value is %d\n",k,value)
33         case Myint:
34             fmt.Printf("list[%d] is an Myint(自定义类型) and its value is %d\n",k,value)
35         default:
36             fmt.Printf("list[%d] is  of a different\n",)
37         }
38     }
39 }
40 
41 
42 
43 
44 #以上代码执行结果如下:
45 [100 100]
46 list[0] is an int(整型) and its value is 100
47 list[1] is an Myint(自定义类型) and its value is 100

 

3.反射第一定律:从接口值到反射对象的反射(Reflection goes from interface value to reflection object)

 1 /*
 2 #!/usr/bin/env gorun
 3 @author :yinzhengjie
 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
 5 EMAIL:y1053419035@qq.com
 6 */
 7 
 8 package main
 9 
10 import (
11     "fmt"
12     "reflect"
13 )
14 
15 func main() {
16     var yzj float64 = 5.2
17     fmt.Println("type:", reflect.TypeOf(yzj))         //reflect.Typeof 签名里就包含了一个空接口。当我们调用reflect.Typeof(yzj)的时候,
18     // yzj首先被保存到一个空接口中,这个空接口然后被作为参数传递。reflect.Typeof 会把这个空接口拆包(unpack)恢复出类型信息。
19 
20     fmt.Println("value:", reflect.ValueOf(yzj))       //当然,reflect.Valueof可以把值恢复出来,Valueof方法会返回一个Value类型的对象
21 }
22 
23 
24 
25 #以上代码执行结果如下:
26 type: float64
27 value: 5.2

  reflect.Type和reflect.Value这两种类型都提供了大量的方法让我们可以检查和操作这两种类型。有以下两点要注意:

  第一,Value类型有一个Type方法可以返回reflect.Value类型的Type,这个方法返回的是值的静态类型即“static type”,也就是说如果定义了“type MyType string”,那么这个函数返回的是“MyType”类型而不是“string”。有关Value类型的带有名字诸如“String”,“Int”,“Uint”“Bytes”等等的方法可让我们获取存在里面的值。

  第二,Type和Value都有一个Kind方法可以返回一个常量用于指示一个项到底是以什么形式存储的,也就是底层类型即“underlying type”。这些常量包括:Unit, Float64, Slice等等。

  具体用法我们可以以下的代码:

 1 /*
 2 #!/usr/bin/env gorun
 3 @author :yinzhengjie
 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
 5 EMAIL:y1053419035@qq.com
 6 */
 7 
 8 package main
 9 
10 import (
11     "reflect"
12     "fmt"
13 )
14 
15 type MyType string
16 
17 func main() {
18     var y MyType = "yinzhengjie"
19     Type := reflect.TypeOf(y) //得到类型的元数据,通过 t 我们能获取类型定义里面的所有元素.
20     Value := reflect.ValueOf(y)    //得到实际的值,通过 v 我们获取存储在里面的值,还可以去改变值.
21     fmt.Println("type\t\t\t:",Type)
22     fmt.Println("underlying type    :",Type.Kind())    //Type和Value都有一个Kind方法可以返回一个常量,以判断出它的底层数据到底是什么类型。
23     fmt.Println("value\t\t\t:",Value)
24     fmt.Println("static type        :",Value.Type())    //Value类型有一个Type方法可以返回reflect.Value类型的Type(这个方法返回的是值的静态类型即static type.)
25     fmt.Println("underlying type    :",Value.Kind())
26     fmt.Println("kind is string    :",Value.Kind() == reflect.String)
27     fmt.Println("value\t\t\t:",Value.String())    //通过Value类型String方法来让我们获取存在里面的值。如果是底层数据是“int”就用“Int”方法获取。
28 }
29 
30 
31 
32 
33 #以上代码执行结果如下:
34 type            : main.MyType
35 underlying type    : string
36 value            : yinzhengjie
37 static type        : main.MyType
38 underlying type    : string
39 kind is string    : true
40 value            : yinzhengjie

 

4.反射第二定律:从反射对象到接口值的反射(Reflection goes from reflection object to interface value)

   就像物理学上的作用力和反作用力,我们可以从接口值到反射对象,与此同时,我们也可以从反射对象到接口值。

  给定一个reflect.Value,我们能用Interface方法把它恢复成一个接口值;效果上就是这个Interface方法把类型和值的信息打包成一个接口表示并且返回结果。简要的说,Interface方法是Valueof函数的逆,除了它的返回值的类型总是interface{}静态类型。重申一遍:反射就是从接口值到反射对象,然后再反射回来。(Reflection goes from interface value to reflection object and back again.)

 1 /*
 2 #!/usr/bin/env gorun
 3 @author :yinzhengjie
 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
 5 EMAIL:y1053419035@qq.com
 6 */
 7 
 8 package main
 9 
10 import (
11     "reflect"
12     "fmt"
13 )
14 
15 type MyType string
16 
17 
18 func main() {
19     var y MyType = "yinzhengjie"
20     Value := reflect.ValueOf(y)    //得到实际的值,通过 v 我们获取存储在里面的值,还可以去改变值.
21     fmt.Println(Value)            //Value是一个reflect.Value.
22     
23     x := Value.Interface()        //我们想要的是Value里面保存的具体值.我们不需要对v.Interface方法的结果调用类型断言
24     fmt.Println(x)
25 }
26 
27 
28 
29 #以上代码执行结果如下:
30 yinzhengjie
31 yinzhengjie

 

5.反射第三定律:为了修改一个反射对象,值必须是settable的(To modify a reflection object, the value must be settable)

 1 /*
 2 #!/usr/bin/env gorun
 3 @author :yinzhengjie
 4 Blog:http://www.cnblogs.com/yinzhengjie/tag/GO%E8%AF%AD%E8%A8%80%E7%9A%84%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
 5 EMAIL:y1053419035@qq.com
 6 */
 7 
 8 package main
 9 
10 import (
11     "reflect"
12     "fmt"
13 )
14 
15 func main() {
16     var yzj string = "yinzhengjie"
17     p := reflect.ValueOf(&yzj)                                 //注意这里哦!我们把yzj地址传进去了!
18     fmt.Println("type of p:", p.Type())                //我们是讲地址传进去的,所以得到的应该是一个指针类型的string.
19     fmt.Println("settability of p:", p.CanSet())        //反射对象p不是settable的,因此返回值应该是一个false!
20 
21     v := p.Elem()                                            //反射对象p不是settable的,但是我们想要设置的不是p,而是(效果上来说)*p,为了得到p指向的东西,我们调用Value的Elem方法。
22 
23     fmt.Println(v.Interface()) //查看v里面的值
24     s := v.String()
25     s = "尹正杰"        //我们此处修改的只是“yzj”变量中的一个副本
26     fmt.Println(s)
27     fmt.Println(yzj)    //忧郁s修改的是副本,所以对本尊是一点影响的都没有的,源数据应该还是“yinzhengjie”
28 
29 
30     fmt.Println("settability of v:", v.CanSet())        //反射对象v是settable的,因此返回值应该是一个true!
31     v.SetString("Golang")    //想要修改源数据,还是得调用该SetString,SetInt,SetFloat,等方法去修改相应的数据类型。
32     fmt.Println(yzj)    //由于已经通过SetString方法对源数据进行了修改,因此我们再看yzj这个变量的值就已经被修改了。
33 }
34 
35 
36 
37 
38 #以上代码执行结果如下:
39 type of p: *string
40 settability of p: false
41 yinzhengjie
42 尹正杰
43 yinzhengjie
44 settability of v: true
45 Golang

 

 

 

 

 

 

 

转载于:https://www.cnblogs.com/yinzhengjie/p/7733420.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/280029.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

笔记本禁用键盘命令符_如何在Windows中禁用命令提示符和“运行”程序

笔记本禁用键盘命令符The Command Prompt and the Run program are pretty powerful tools in the Windows world. If you’d rather specific users on a computer not have access to them, it’s not too hard to do. 命令提示符和运行程序是Windows世界中非常强大的工具。 …

MySQL Date 函数

2019独角兽企业重金招聘Python工程师标准>>> MySQL 中最重要的内建日期函数: NOW() 返回当前的日期和时间 CURDATE() 返回当前的日期 CURTIME() 返回当前的时间 DATE() 提取日期或日期/时间表达式的日期部分 EXTRACT() 返回日期/时间按的…

C# WPF This用法详解(经典)

概述this在C#中有多种用法,也比较常见,这节主要针对它常用的四种用法展开讲解.用法1:构造函数串联执行;用法2:通过this区分传参和类中全局的定义;用法3:方法扩展类;用法4:将对象作为参数传递;代码实例using System.Text;namespace Caliburn.Micro.Hello.…

前端node 和vue开发之环境搭建

下载nvm nodejs 的快捷键是配置后自动生成的 nvm 的 setting.txt配置 root: C:\dev\nvmpath: C:\dev\nodejsarch: 32proxy: root指向 nvm.exeroot: C:\dev\nvmpath: C:\dev\nodejs 配置环境变量 变量名 变量值 GIT_HOME C:\dev…

如何从特定位置开始分享YouTube视频

Tech tutorials that start with 3 minutes of “hey guys what’s up” are the worst. Get to the point! Here’s how you can bypass that nonsense when sharing a video with your friends. 最糟糕的是从3分钟的“嗨,大家好起来”开始的技术教程。 讲到重点&a…

解决git提交问题error: The requested URL returned error: 403 Forbidden while accessing

2019独角兽企业重金招聘Python工程师标准>>> git提交代码时,出现这个错误“error: The requested URL returned error: 403 Forbidden while accessing https” 解决方法: 编辑.git目录下的config文件即可。 vim .git/config [core] …

基于.NetCore开发博客项目 StarBlog - (24) 统一接口数据返回格式

1前言开发接口,是给客户端(Web前端、App)用的,前面说的RESTFul,是接口的规范,有了统一的接口风格,客户端开发人员在访问后端功能的时候能更快找到需要的接口,能写出可维护性更高的代…

如何将C# 7类库升级到C# 8?使用可空引用类型

这篇文章将介绍将C# 7类库升级到C# 8(支持可空引用类型)的一个案例。本案例中使用的项目Tortuga Anchor由一组MVVM风格的基类、反射代码和各种实用程序函数组成。之所以选择这个项目,是因为它很小,并且同时包含了惯用和不常用的C#…

android 设备名称_如何更改您的Android TV的设备名称

android 设备名称Android TV is Google’s attempt at taking over the living room, and with some units being available for under $99, it’s not unheard of for users to have more than one box. The problem is, when multiple devices identify themselves identical…

AD-查找符合指定条件的用户Get-User

以下服务器为Exchange 2010一、使用 Get-User 命令查找部门为IT的用户Get-User -ResultSize Unlimited | ? { $_.Department -Eq "IT" } | ft Name,Department二、查找注释为多行内容的指定用户如下图:注释Notes信息为多行要使用 match 和 (?*) 来做匹配…

目标检测算法之Fast R-CNN算法详解

在介绍Fast R-CNN之前我们先介绍一下SPP Net 一、SPP Net SPP:Spatial Pyramid Pooling(空间金字塔池化) 众所周知,CNN一般都含有卷积部分和全连接部分,其中,卷积层不需要固定尺寸的图像,而全连…

WPF-21 基于MVVM员工管理-01

接下来我们通过两节课程使用MVVM来开发一个简单的Demo,首先我们创建一个项目名称WPF-22-MVVM-Demo,目录结构如下:我们在Models文件下创建Employee类并让该类实现INotifyPropertyChanged接口,该类中定义编号、姓名和角色三个基本属…

qt 苹果应用程序_什么是苹果的电视应用程序,您应该使用它吗?

qt 苹果应用程序Apple’s TV app, which recently appeared on iOS devices and Apple TV, is meant to help users discover and watch shows across an increasingly expanding lineup of television channels, as well as iTunes movies and shows, in one central app. App…

细说flush、ob_flush的区别

ob_flush/flush在手册中的描述, 都是刷新输出缓冲区, 并且还需要配套使用, 所以会导致很多人迷惑… 其实, 他们俩的操作对象不同, 有些情况下, flush根本不做什么事情.. ob_*系列函数, 是操作PHP本身的输出缓冲区. 所以, ob_flush是刷新PHP自身的缓冲区. 而flush, 严格来讲, 这…

关于jHipster框架在构建中的出现的error修复

jhipster The JDL object and the database type are both mandatory.这个错误应该是在构建基于jHipster的spring-cloud项目中经常遇到的,因为这个在这个过程中会读取.yo-rc文件,之后生成相关的.json文件,再之后生成相关的.java文件&#xff…

定制.NET 6.0的Middleware中间件

大家好,我是张飞洪,感谢您的阅读,我会不定期和你分享学习心得,希望我的文章能成为你成长路上的垫脚石,让我们一起精进。在本文中,我们将学习中间件,以及如何使用它进一步定制应用程序。我们将快…

删除microsoft_如何从您的Microsoft帐户中删除设备

删除microsoftWhen you sign into Windows 8 or 10 using your Microsoft account (and other Microsoft devices, like an Xbox), those devices become associated with your account. If you want to remove an old device you’ve gotten rid of, you’ll have to pay a vi…

线程的语法 (event,重要)

Python threading模块 2种调用方式 直接调用 12345678910111213141516171819import threadingimport timedef sayhi(num): #定义每个线程要运行的函数print("running on number:%s" %num)time.sleep(3)if __name__ __main__:t1 threading.Thread(targetsayhi,args(…

求最大值和下标值

本题要求编写程序&#xff0c;找出给定的n个数中的最大值及其对应的最小下标&#xff08;下标从0开始&#xff09;。 输入格式: 输入在第一行中给出一个正整数n&#xff08;1<n≤10&#xff09;。第二行输入n个整数&#xff0c;用空格分开。 输出格式: 在一行中输出最大值及…

windows应用商店修复_如何修复Windows应用商店中的卡死下载

windows应用商店修复Though it’s had its share of flaky behavior since being introduced in Windows 8, the Windows Store has gotten more reliable over time. It still has the occasional problems, though. One of the more irritating issues is when an app update…