本节主要内容:
1. 结构体和方法
2. 接口
1. 结构体和方法
(1). 用来自定义复杂数据结构
(2). struct里面可以包含多个字段(属性)
(3). struct类型可以定义方法,注意和函数的区分
(4). struct类型是值类型
(5). struct类型可以嵌套
(6). Go语言没有class类型,只有struct类型
(1). struct 声明:
type 标识符 struct {
field1 type
field2 type
}
1 type Student struct { 2 Name string 3 Age int 4 Score int 5 }
(2). struct 中字段访问:和其他语言一样,使用点(.)
1 var stu Student 2 3 stu.Name = "tony" 4 stu.Age = 18 5 stu.Score=20 6 7 fmt.Printf("name=%s age=%d score=%d", stu.Name, stu.Age, stu.Score)
(3). struct定义的三种形式:
(a) var stu Student
(b) var stu *Student = new (Student)
(c) var stu *Student = &Student{}
其中b和c返回的都是指向结构体的指针,访问形式如下:
stu.Name、stu.Age和stu.Score 或者 (*stu).Name、(*stu).Age。
1 package main 2 3 import "fmt" 4 5 type Student struct { 6 Name string 7 Age int 8 score float32 9 } 10 11 func main() { 12 //下面定义并初始化 13 var stu1 Student = Student { 14 Name : "zhangsan", 15 Age : 10, 16 score : 99.99, 17 } 18 19 //struct定义的形式1 20 var stu2 Student 21 stu2.Name = "zhangsan2" 22 stu2.Age = 15 23 stu2.score = 99.66 24 25 //struct定义的形式2 26 var stu3 *Student = new(Student) 27 stu3.Name = "lisi" //(*stu1).Name = "lisi" 28 stu3.Age = 20 //(*stu1).Age = 20 29 stu3.score = 88.88 //(*stu1).score = 88.88 30 31 //struct定义的形式3 32 var stu4 *Student = &Student{ 33 Name:"wangwu", 34 Age:19, 35 score:99.88, 36 } 37 38 fmt.Println(stu1) //{zhangsan 10 99.99} 39 fmt.Println(stu2) //{zhangsan2 15 99.66} 40 fmt.Println(stu3) //&{lisi 20 88.88} 41 fmt.Println(stu4) //&{wangwu 19 99.88} 42 }
(4). struct的内存布局:struct中的所有字段在内存是连续的,布局如下:
1 package main 2 3 import "fmt" 4 5 type Student struct { 6 Name string 7 Age int 8 score float32 9 } 10 11 func main() { 12 var stu Student 13 14 stu.Age = 18 15 stu.Name = "hua" 16 stu.score = 80 17 18 var stu1 *Student = &Student{ 19 Age: 20, 20 Name: "hua", 21 } 22 23 var stu3 = Student{ 24 Age: 20, 25 Name: "hua", 26 } 27 28 fmt.Println(stu1.Name) 29 fmt.Println(stu3) 30 fmt.Printf("Name:%p ", &stu.Name) //Name:0xc042002720 31 fmt.Printf("Age: %p ", &stu.Age) //Age: 0xc042002730 32 fmt.Printf("score:%p ", &stu.score) //score:0xc042002738 33 }
(5). 链表定义
type Student struct {
Name string
Next* Student
}
每个节点包含下一个节点的地址,这样把所有的节点串起来了,通常把链表中的第一个节点叫做链表头。
1 package main 2 3 import ( 4 "fmt" 5 "math/rand" 6 ) 7 8 type Student struct { 9 Name string 10 Age int 11 Score float32 12 Id string 13 next *Student 14 } 15 16 //遍历链表 17 func trans(p *Student) { 18 for p != nil { 19 fmt.Printf("name = %s, Age = %d, Score = %f, id = %s, next = %p ", p.Name, p.Age, p.Score, p.Id, p.next) 20 p = p.next 21 } 22 fmt.Println() 23 } 24 25 //头部插入 26 func insertHead(head **Student, new_node *Student) { 27 new_node.next = *head 28 *head = new_node 29 } 30 31 //尾部插入 32 func insertTail(p *Student, new_node *Student) { 33 for p.next != nil { 34 p = p.next 35 } 36 p.next = new_node 37 } 38 39 //删除节点 40 func delNode(p *Student, id string) { 41 var pre_node *Student = p 42 for p != nil { 43 if p.Id == id { 44 pre_node.next = p.next 45 break 46 } 47 pre_node = p 48 p = p.next 49 } 50 } 51 52 //当前节点后面插入 53 func addNode(p *Student, id string, add_node *Student) { 54 for p != nil { 55 if p.Id == id { 56 add_node.next = p.next 57 p.next = add_node 58 break 59 } 60 p = p.next 61 } 62 } 63 64 func checkNode(p *Student, id string) { 65 for p != nil { 66 if p.Id == id { 67 fmt.Printf("name = %s, Age = %d, Score = %f, id = %s, next = %p ", p.Name, p.Age, p.Score, p.Id, p.next) 68 return 69 } 70 p = p.next 71 } 72 fmt.Printf("Do not find id = %s ", id) 73 } 74 75 func main() { 76 var stu1 Student = Student { 77 Name:"name1", 78 Age:rand.Intn(100), 79 Score:rand.Float32()*100, 80 Id:"000001", 81 } 82 trans(&stu1) 83 84 var head *Student = &stu1 85 86 var stu2 Student = Student { 87 Name:"name2", 88 Age:rand.Intn(100), 89 Score:rand.Float32()*100, 90 Id:"000002", 91 } 92 insertHead(&head, &stu2) //头部插入 93 trans(head) 94 95 var stu3 Student = Student { 96 Name:"name3", 97 Age:rand.Intn(100), 98 Score:rand.Float32()*100, 99 Id:"000003", 100 } 101 102 insertTail(head, &stu3) //尾部插入 103 trans(head) 104 105 for i := 4; i < 10 ; i++ { 106 stu := Student { 107 Name:fmt.Sprintf("name%d", i), 108 Age:rand.Intn(100), 109 Score:rand.Float32()*100, 110 Id:fmt.Sprintf("00000%d", i), 111 } 112 113 addNode(head, "000003", &stu) //增加节点 114 } 115 trans(head) 116 117 delNode(head, "000005") //删除节点 118 trans(head) 119 120 checkNode(head, "000006") //查 121 checkNode(head, "0000010") 122 }
(6). 双链表定义
type Student struct {
Name string
Next* Student
Prev* Student
}
如果有两个指针分别指向前一个节点和后一个节点,我们叫做双链表
(7). 二叉树定义
type Student struct {
Name string
left* Student
right* Student
}
如果每个节点有两个指针分别用来指向左子树和右子树,我们把这样的结构叫做二叉树
1 package main 2 3 import "fmt" 4 5 type Student struct { 6 Name string 7 Age int 8 Score float32 9 left *Student 10 right *Student 11 } 12 13 func trans(root *Student) { 14 if root == nil { 15 return 16 } 17 fmt.Println(root) 18 19 trans(root.left) 20 trans(root.right) 21 22 } 23 24 func main() { 25 var root *Student = new(Student) 26 27 root.Name = "stu01" 28 root.Age = 18 29 root.Score = 100 30 31 var left1 *Student = new(Student) 32 left1.Name = "stu02" 33 left1.Age = 18 34 left1.Score = 100 35 36 root.left = left1 37 38 var right1 *Student = new(Student) 39 right1.Name = "stu04" 40 right1.Age = 18 41 right1.Score = 100 42 43 root.right = right1 44 45 var left2 *Student = new(Student) 46 left2.Name = "stu03" 47 left2.Age = 18 48 left2.Score = 100 49 50 left1.left = left2 51 52 trans(root) 53 }
(8). 结构体是用户单独定义的类型,不能和其他类型进行强制转换
1 package main 2 3 func main() { 4 type Student struct { 5 Number int 6 } 7 8 type Stu Student //alias 9 10 var a Student 11 a.Number = 10 12 13 var b Stu 14 a = b // cannot use b (type Stu) as type Student in assignment 15 }
1 package main 2 3 import "fmt" 4 5 type integer int 6 7 type Student struct { 8 Number int 9 } 10 11 type Stu Student //alias 12 13 func main() { 14 15 var i integer = 1000 16 var j int = 100 17 18 // j = i //cannot use i (type integer) as type int in assignment 19 j = int(i) //进行强制转换 ok 20 fmt.Println(j) 21 22 var a Student 23 a = Student{30} 24 25 var b Stu 26 a = Student(b) //进行强制转换 ok 27 fmt.Println(a) //{0} 28 }
(9).(工厂模式) golang中的struct没有构造函数,一般可以使用工厂模式来解决这个问题
1 package main 2 3 import "fmt" 4 5 type student struct { 6 Name string 7 Age int 8 } 9 10 func NewStudent(name string, age int) *student { 11 return &student{ 12 Name:name, 13 Age:age, 14 } 15 } 16 17 func main() { 18 s := new (student) 19 s = NewStudent("tony", 20) 20 fmt.Println(s) //&{tony 20} 21 }
(10). 再次强调
a). make 用来创建map、slice、channel
b). new用来创建值类型
(11). (struct中的tag) 我们可以为struct中的每个字段,写上一个tag。这个tag可以通过反射的机制获取到,最常用的场景就是json序列化和反序列化
type student struct {
Name stirng "this is name field"
Age int "this is age field"
}
1 package main 2 3 import ( 4 "encoding/json" 5 "fmt" 6 ) 7 8 type Student struct { 9 Name string `json:"student_name"` 10 Age int `json:"age"` 11 Score int `json:"score"` 12 } 13 14 type Student2 struct { 15 name string 16 age int 17 score int 18 } 19 20 func main() { 21 var stu Student = Student{ 22 Name: "stu01", 23 Age: 18, 24 Score: 80, 25 } 26 27 data, err := json.Marshal(stu) 28 if err != nil { 29 fmt.Println("json encode stu failed, err:", err) 30 return 31 } 32 33 fmt.Println(string(data)) //{"student_name":"stu01","age":18,"score":80} 34 35 var stu2 Student2 = Student2{ 36 name: "stu02", 37 age: 20, 38 score: 90, 39 } 40 41 data2, err2 := json.Marshal(stu2) 42 if err2 != nil { 43 fmt.Println("json encode stu failed, err:", err2) 44 return 45 } 46 47 fmt.Println(string(data2)) // {} 由于结构体成员变量首字母小写,在json序列化时对外不可见,因此为空。改为首字母大写就OK 48 }
(12). (匿名字段)结构体中字段可以没有名字,即匿名字段
type Car struct {
Name string
Age int
}
type Train struct {
Car
Start time.Time
int
}
1 package main 2 3 import ( 4 "fmt" 5 "time" 6 ) 7 8 type Car struct { 9 Name string 10 Age int 11 } 12 13 type Train struct { 14 Car 15 Start time.Time 16 int 17 } 18 19 func main() { 20 var t Train 21 22 //如果没有命名冲突可以直接这样访问 23 //t.Name = "demo" 24 //t.Age = 20 25 26 t.Car.Name = "demo" 27 t.Car.Age = 20 28 t.int = 100 29 30 fmt.Println(t) //{{demo 20} 0001-01-01 00:00:00 +0000 UTC 100} 31 }
(13). 匿名字段冲突处理
1 package main 2 3 import ( 4 "fmt" 5 ) 6 7 type Cart1 struct { 8 name string 9 age int 10 } 11 12 type Cart2 struct { 13 name string 14 age int 15 } 16 17 type Train struct { 18 Cart1 19 Cart2 20 } 21 22 func main() { 23 var t Train 24 25 // t.name = "train" 26 // t.age = 100 27 28 // fmt.Println(t) //ambiguous selector t.name 29 30 t.Cart1.name = "train1" 31 t.Cart1.age = 100 32 33 t.Cart2.name = "train2" 34 t.Cart2.age = 200 35 36 fmt.Println(t) //{{train1 100} {train2 200}} 37 }
(14). 方法
a. 方法定义
方法其实就是一个函数,在 func 这个关键字和方法名中间加入了一个特殊的接收器类型。接收器可以是结构体类型或者是非结构体类型。接收器是可以在方法的内部访问的。
Golang中的方法是作用在特定类型的变量上,因此自定义类型,都可以有方法,而不仅仅是struct。
定义:func (recevier type) methodName(参数列表)(返回值列表) {}
1 package main 2 3 import "fmt" 4 5 type Student struct { 6 Name string 7 Age int 8 } 9 10 //为结构体Student定义init方法 11 func (p *Student) init(name string, age int) { 12 p.Name = name 13 p.Age = age 14 } 15 16 func main() { 17 var stu Student 18 stu.init("zhansan", 20) 19 fmt.Printf("name = %s, age = %d ", stu.Name, stu.Age) //name = zhansan, age = 20 20 }
"类的"方法:
Go 语言不像其它面相对象语言一样可以写个类,然后在类里面写一堆方法,但其实Go语言的方法很巧妙的实现了这种效果:我们只需要在普通函数前面加个接受者(receiver,写在函数名前面的括号里面),这样编译器就知道这个函数(方法)属于哪个struct了。
1). 在 Go 中,(接收者)类型关联的方法不写在类型结构里面,就像类那样;耦合更加宽松;类型和方法之间的关联由接收者来建立。
2). 方法没有和数据定义(结构体)混在一起:它们是正交的类型;表示(数据)和行为(方法)是独立的。
注意:Go语言不允许为简单的内置类型添加方法,所以下面定义的方法是非法的。
1 package main 2 3 //cannot define new methods on non-local type int 4 func (a int) add(b int) { 5 } 6 7 func main() { 8 9 }
1 package main 2 3 import( 4 "fmt" 5 ) 6 7 //将int定义别名myInt 8 type myInt int 9 10 func Add(a ,b int) int { //函数 11 return a + b 12 } 13 14 //cannot define new methods on non-local type int 15 // func (a int) Add(b int) { 16 // } 17 18 //对myInt类型定义Add方法 19 func (a myInt) Add (b myInt) myInt { //方法 20 return a + b 21 } 22 23 func main() { 24 a, b := 3,4 25 var aa, bb myInt = 3, 4 26 fmt.Println(Add(a, b)) //7 27 fmt.Println(aa.Add(bb)) //7 28 }
b. 方法的调用
1 package main 2 3 import "fmt" 4 5 type A struct { 6 a int 7 } 8 9 func (this A) test() { 10 fmt.Println(this.a) 11 } 12 13 func main() { 14 var t A 15 t.a = 100 16 t.test() //100 17 }
c. 方法和函数的区别
函数调用: function(variable, 参数列表)
方法:variable.function(参数列表)
为什么我们已经有函数了还需要方法呢?
I). Go 不是纯粹的面向对象编程语言,而且Go不支持类。因此,基于类型的方法是一种实现和类相似行为的途径。
II). 相同的名字的方法可以定义在不同的类型上,而相同名字的函数是不被允许的。
1 package main 2 3 import "fmt" 4 5 type People struct { 6 Age int 7 } 8 9 type Animal struct { 10 Age int 11 } 12 13 func (p People) Eat() { 14 fmt.Println("People age is ", p.Age) 15 } 16 17 func (a Animal) Eat() { 18 fmt.Println("Animal age is ", a.Age) 19 } 20 21 func main() { 22 var p People = People { 23 Age:20, 24 } 25 26 var a Animal = Animal { 27 Age:2, 28 } 29 30 p.Eat() 31 a.Eat() 32 }
d. 指针接收器与值接收器
本质上和函数的值传递和地址传递是一样的。
在上面的例子中,我们只使用值接收器的方法。还可以创建使用指针接收器的方法。值接收器和指针接收器之间的区别在于,在指针接收器的方法内部的改变对于调用者是可见的,然而值接收器的情况不是这样的。
1 #include<stdio.h> 2 3 void set(int *s, int newValue) 4 { 5 *s = newValue; 6 } 7 8 int main() 9 { 10 int num = 1; 11 printf("before num = %d ", num); //before num = 1 12 set(&num, 10); 13 printf("after num = %d ", num); //after num = 10 14 }
1 package main 2 3 import "fmt" 4 5 type People struct { 6 Name string 7 Age int 8 } 9 10 func (p People) ChangeAge(age int) { 11 p.Age = age 12 } 13 14 func (p *People) ChangeName(name string) { 15 p.Name = name 16 } 17 18 func main() { 19 var p People = People { 20 Name:"zhangsan", 21 Age:20, 22 } 23 24 fmt.Printf("before name = %s, age = %d ", p.Name, p.Age) //before name = zhangsan, age = 20 25 // (&p).ChangeName("lisi") //OK 26 p.ChangeName("lisi") //p.ChangeName("lisi") 自动被Go语言解释为 (&p).ChangeName("lisi") 27 p.ChangeAge(10) 28 fmt.Printf("after name = %s, age = %d ", p.Name, p.Age) //after name = lisi, age = 20 29 }
那么什么时候使用指针接收器,什么时候使用值接收器?
一般来说,指针接收器可以使用在:对方法内部的接收器所做的改变应该对调用者可见时。
指针接收器也可以被使用在如下场景:
1. 当拷贝一个结构体的代价过于昂贵时。
考虑下一个结构体有很多的字段。在方法内使用这个结构体做为值接收器需要拷贝整个结构体,这是很昂贵的。在这种情况下使用指针接收器,结构体不会被拷贝,只会传递一个指针到方法内部使用。
2. 在其他的所有情况,值接收器都可以被使用。
e. 在方法中使用值接收器 与 在函数中使用值参数
i) 当一个函数有一个值参数,它只能接受一个值参数。
ii) 当一个方法有一个值接收器,它可以接受值接收器和指针接收器。
iii) 当一个方法有一个指针接收器,它可以接受值接收器和指针接收器。
1 package main 2 3 import "fmt" 4 5 type Car struct { 6 weight int 7 name string 8 } 9 10 func InitChange(p Car) { 11 p.name = "func" 12 p.weight = 200 13 } 14 15 //值接收器 16 func (p Car) InitChange() { 17 p.name = "receiver" 18 p.weight = 600 19 20 } 21 22 //指针接收器 23 func (p *Car) InitChange2() { 24 p.name = "receiver2" 25 p.weight = 800 26 27 } 28 29 func main() { 30 var c Car = Car{ 31 weight:100, 32 name:"bike", 33 } 34 35 p := &c 36 37 // Run(&c) // cannot use p (type *Car) as type Car in argument to Run 38 InitChange(c) //传值 39 fmt.Println(c, " running in the func") //{200 bike} running in the func 40 41 // c.Run() 42 // 为了方便Go语言把 p.Run() 解释为 (*p).Run(),因此在Run中改变值不起作用 43 p.InitChange() //{100 receiver} running int the receiver 44 fmt.Println(c, " running in the receiver") //{100 bike} running in the receiver 45 46 // 为了方便Go语言把 c.Run() 解释为 (&c).Run(),因此在Change中改变值起作用 47 // c.InitChange2() //传值 48 p.InitChange2() //传指针 49 fmt.Println(c, " running in the receiver2") //{800 receiver2} running in the Change 50 }
f. 匿名字段的方法
属于结构体的匿名字段的方法可以被直接调用,就好像这些方法是属于定义了匿名字段的结构体一样。
1 package main 2 3 import "fmt" 4 5 type Car struct { 6 weight int 7 name string 8 } 9 10 func (p Car) Run() { 11 fmt.Println("running") 12 } 13 14 //Bike不仅继承了Car的成员变量weight和name,同时继承了Run方法 15 type Bike struct { 16 Car //匿名字段 17 wheel int 18 } 19 20 func main() { 21 var b Bike = Bike { 22 Car: Car{ 23 weight:100, 24 name:"bike", 25 }, 26 wheel:2, 27 } 28 29 30 fmt.Println(b) //{{100 bike} 2} 31 b.Run() //running 匿名字段方法 Run 32 }
g. 方法的访问控制,通过大小写控制
在不同的包之间,方法要对外可见需要首字母大写。
h. 继承
如果一个struct嵌套了另一个匿名结构体,那么这个结构可以直接访问匿名结构体的方法,从而实现了继承。
1 package main 2 3 import "fmt" 4 5 type Car struct { 6 weight int 7 name string 8 } 9 10 func (p Car) Run() { 11 fmt.Println("running") 12 } 13 14 //Bike不仅继承了Car的成员变量weight和name,同时继承了Run方法 15 type Bike struct { 16 Car 17 wheel int 18 } 19 20 func main() { 21 var a Bike 22 a.weight = 100 23 a.name = "bike" 24 a.wheel = 2 25 26 fmt.Println(a) //{{100 bike} 2} 27 a.Run() //running 28 }
i. 组合和匿名字段
如果一个struct嵌套了另一个有名结构体,那么这个模式就叫组合。
go里面的继承是通过组合来实现的。
匿名字段是一个特殊的组合。
1 package main 2 3 import "fmt" 4 5 type Car struct { 6 weight int 7 name string 8 } 9 10 func (p Car) Run() { 11 fmt.Println("running") 12 } 13 14 type Bike struct { 15 Car 16 lunzi int 17 } 18 19 type Train struct { 20 c Car //组合 21 } 22 23 func main() { 24 var a Bike 25 a.weight = 100 26 a.name = "bike" 27 a.lunzi = 2 28 29 fmt.Println(a) 30 a.Run() 31 32 var b Train 33 //注意访问方式 34 b.c.weight = 100 35 b.c.name = "train" 36 b.c.Run() 37 }
j. 多重继承
如果一个struct嵌套了多个匿名结构体,那么这个结构可以直接访问多个匿名结构体的方法,从而实现了多重继承。
1 package main 2 3 import "fmt" 4 5 type People struct { 6 Name string 7 Age int 8 } 9 10 type Animal struct { 11 Place string 12 Weight int 13 } 14 15 func (p People) Eat() { 16 fmt.Println("People eat food") 17 } 18 19 func (p People) Sleep() { 20 fmt.Println("People sleep") 21 } 22 23 func (p Animal) Eat() { 24 fmt.Println("Animal sleep") 25 } 26 27 func (p Animal) Run() { 28 fmt.Println("Animal running") 29 } 30 31 func (p Animal) Cry() { 32 fmt.Println("Animal cry") 33 } 34 35 //Test继承了People和Animal里面的成员变量和方法 36 type Test struct { 37 People 38 Animal 39 } 40 41 func main() { 42 var t Test 43 t.Name = "sara" 44 t.Age = 20 45 46 t.Place = "xian" 47 t.Weight = 200 48 49 // t.Eat() //ambiguous selector t.Eat 50 t.People.Eat() 51 t.Animal.Eat() 52 53 t.Sleep() //t.People.Sleep() 54 t.Run() //t.Animal.Run() 55 t.Cry() //t.Animal.Cry() 56 }
2. 接口
什么是接口?
在面向对象的领域里,接口一般这样定义:接口定义一个对象的行为。接口只指定了对象应该做什么,至于如何实现这个行为(即实现细节),则由对象本身去确定。
在 Go 语言中,接口就是方法签名(Method Signature)的集合。当一个类型定义了接口中的所有方法,我们称它实现了该接口。这与面向对象编程(OOP)的说法很类似。接口指定了一个类型应该具有的方法,并由该类型决定如何实现这些方法。
(1). 定义
Interface类型可以定义一组方法,但是这些不需要实现。并且interface不能包含任何变量。
type example interface{
Method1(参数列表) 返回值列表
Method2(参数列表) 返回值列表
…
}
1 package main 2 3 import "fmt" 4 5 type People struct { 6 name string 7 age int 8 } 9 10 type Test interface { 11 Eat() 12 Sleep() 13 } 14 15 func (p People) Eat() { 16 fmt.Println("people eat") 17 } 18 19 func (p People) Sleep() { 20 fmt.Println("people sleep") 21 } 22 23 func main() { 24 25 var t Test 26 fmt.Println(t) //<nil> 27 28 var people People = People { 29 name: "people", 30 age: 100, 31 } 32 33 t = people 34 t.Eat() 35 t.Sleep() 36 37 fmt.Println("t:", t) //t: {people 100} 38 }
(2). interface类型默认是一个指针
如(1)中的例子var t Test fmt.Println(t) //<nil>
(3). 接口的内部表示
我们可以把接口看作内部的一个元组 (type, value)。 type 是接口底层的具体类型(Concrete Type),而 value 是具体类型的值。
1 package main 2 3 import ( 4 "fmt" 5 ) 6 7 type Test interface { 8 Tester() 9 } 10 11 type MyFloat float64 12 13 func (m MyFloat) Tester() { 14 fmt.Println(m) 15 } 16 17 func describe(t Test) { 18 fmt.Printf("Interface type %T value %v ", t, t) 19 } 20 21 func main() { 22 var t Test 23 f := MyFloat(89.7) 24 t = f 25 describe(t) //Interface type main.MyFloat value 89.7 26 t.Tester() //89.7 27 }
(4). 接口实现
a. Golang中的接口,不需要显示的实现。只要一个变量,含有接口类型中的所有方法,那么这个变量就实现这个接口。因此,golang中没有implement
类似的关键字。
b. 如果一个变量含有了多个interface类型的方法,那么这个变量就实现了多个接口。
1 package main 2 3 import "fmt" 4 5 type People struct { 6 name string 7 age int 8 } 9 10 type EatInter interface { 11 Eat() 12 } 13 14 type SleepInter interface { 15 Sleep() 16 } 17 18 func (p People) Eat() { 19 fmt.Println("people eat") 20 } 21 22 func (p People) Sleep() { 23 fmt.Println("people sleep") 24 } 25 26 func main() { 27 var e EatInter 28 var s SleepInter 29 30 var people People = People { 31 name: "people", 32 age: 100, 33 } 34 35 //people实现了EatInter和SleepInter接口 36 e = people 37 s = people 38 e.Eat() 39 s.Sleep() 40 41 fmt.Println("e:", e) //e: {people 100} 42 fmt.Println("s:", s) //s: {people 100} 43 }
c. 如果一个变量只含有了1个interface的部分方法,那么这个变量没有实现这个接口。
(5). 多态
一种事物的多种形态,都可以按照统一的接口进行操作。
1 package main 2 3 import "fmt" 4 5 //一个接口Test,方法Eat()和Sleep()多种实现(People和Animal),这就是多态 6 type Test interface { 7 Eat() 8 Sleep() 9 } 10 11 type People struct { 12 Name string 13 } 14 15 type Animal struct { 16 Name string 17 } 18 19 func (p People) Eat() { 20 fmt.Printf("People %s eat ", p.Name) 21 } 22 23 func (p People) Sleep() { 24 fmt.Printf("People %s sleep ", p.Name) 25 } 26 27 func (p Animal) Eat() { 28 fmt.Printf("Animal %s eat ", p.Name) 29 } 30 31 func (p Animal) Sleep() { 32 fmt.Printf("Animal %s sleep ", p.Name) 33 } 34 35 func main() { 36 37 var t Test 38 39 var a Animal = Animal { 40 Name: "Cat", 41 } 42 43 t = a 44 t.Eat() 45 t.Sleep() 46 fmt.Println("t:", t) 47 48 var p People = People { 49 Name: "people", 50 } 51 52 t = p 53 t.Eat() 54 t.Sleep() 55 fmt.Println("t:", t) 56 }
练习:调用Sort系统函数实现对自定义数组的排序
1 package main 2 3 import ( 4 "fmt" 5 "math/rand" 6 "sort" 7 ) 8 9 type Student struct { 10 Name string 11 Id string 12 Age int 13 sortType int 14 } 15 16 type Book struct { 17 Name string 18 Author string 19 } 20 21 //官网的Sort没有实现对任意类型的排序,为了实现对StudentArray数组的排序, 22 //查询官网发现Sort的定义,参数的是一个接口,该接口中只要实现Len,Less,Swap三个方法就可以调用Sort函数 23 // func Sort(data Interface) 24 // type Interface interface { 25 // Len() int 26 // Less(i, j int) bool 27 // Swap(i, j int) 28 // } 29 30 type StudentArray []Student 31 32 func (p StudentArray) Len() int { 33 return len(p) 34 } 35 36 func (p StudentArray) Less(i, j int) bool { 37 return p[i].Name < p[j].Name //对名字桉升序排列 38 } 39 40 func (p StudentArray) Swap(i, j int) { 41 p[i], p[j] = p[j], p[i] 42 } 43 44 func main() { 45 var stus StudentArray 46 for i := 0; i < 10; i++ { 47 stu := Student{ 48 Name: fmt.Sprintf("stu%d", rand.Intn(100)), 49 Id: fmt.Sprintf("110%d", rand.Int()), 50 Age: rand.Intn(100), 51 } 52 53 stus = append(stus, stu) 54 } 55 56 for _, v := range stus { 57 fmt.Println(v) 58 } 59 60 fmt.Println(" ") 61 62 sort.Sort(stus) 63 for _, v := range stus { 64 fmt.Println(v) 65 } 66 }
(6). 接口嵌套
一个接口可以嵌套在另外的接口,如下所示:
type ReadWrite interface {
Read(b Buffer) bool
Write(b Buffer) bool
}
type Lock interface {
Lock()
Unlock()
}
type Close interface {
Close()
}
type File interface {
ReadWrite
Lock
Close
}
1 package main 2 3 import "fmt" 4 5 type Reader interface { 6 Read() 7 } 8 9 type Writer interface { 10 Write() 11 } 12 13 //接口嵌套 14 type ReadWriter interface { 15 Reader 16 Writer 17 } 18 19 type File struct { 20 } 21 22 func (f *File) Read() { 23 fmt.Println("read data") 24 } 25 26 func (f *File) Write() { 27 fmt.Println("write data") 28 } 29 30 func Test(rw ReadWriter) { 31 rw.Read() 32 rw.Write() 33 } 34 35 func main() { 36 var f *File 37 var b interface{} 38 b = f 39 // Test(f) 40 v, ok := b.(ReadWriter) //f中实现了Reader和Writer接口,因此ok为true 41 fmt.Println(v, ok) //<nil> true 42 }
(7). 类型断言,由于接口是一般类型,不知道具体类型,如果要转成具体类型,可以采用以下方法进行转换:
var t int
var x interface{}
x = t
y = x.(int) //转成int
var t int
var x interface{}
x = t
y, ok = x.(int) //转成int,带检查。y为x的值
类型断言用于提取接口的底层值(Underlying Value)。
在语法 i.(T) 中,接口 i 的具体类型是 T,该语法用于获得接口的底层值。
1 package main 2 3 import ( 4 "fmt" 5 ) 6 7 func assert(i interface{}) { 8 // s := i.(int) 9 if v, ok := i.(int); ok { //此时当传入assert(s)时程序不会panic 10 fmt.Println(v) 11 } 12 } 13 func main() { 14 var s interface{} = 56 15 assert(s) 16 17 s = "hello" 18 assert(s) //panic: interface conversion: interface {} is string, not int 19 }
注意:v, ok := i.(T)
如果 i 的具体类型是 T,那么 v 赋值为 i 的底层值,而 ok 赋值为 true。
如果 i 的具体类型不是 T,那么 ok 赋值为 false,v 赋值为 T 类型的零值,此时程序不会报错。
(8). 类型断言,采用type switch方式
类型选择用于将接口的具体类型与很多 case 语句所指定的类型进行比较。它与一般的 switch 语句类似。唯一的区别在于类型选择指定的是类型,而一般的 switch 指定的是值。
类型选择的语法类似于类型断言。类型断言的语法是 i.(T),而对于类型选择,类型 T 由关键字 type 代替。
练习,写一个函数判断传入参数的类型
func classifier(items ...interface{}) { for i, x := range items { switch x.(type) { case bool: fmt.Printf("param #%d is a bool ", i) case float64: fmt.Printf("param #%d is a float64 ", i) case int, int64: fmt.Printf("param #%d is an int ", i) case nil: fmt.Printf("param #%d is nil ", i) case string: fmt.Printf("param #%d is a string ", i) default: fmt.Printf("param #%d’s type is unknown ", i) } }
1 package main 2 3 import "fmt" 4 5 type Student struct { 6 Name string 7 Sex string 8 } 9 10 func Test(a interface{}) { 11 b, ok := a.(Student) 12 if ok == false { 13 fmt.Println("convert failed") 14 return 15 } 16 //b += 3 17 fmt.Println(b) 18 } 19 20 func just(items ...interface{}) { 21 for index, v := range items { 22 switch v.(type) { 23 case bool: 24 fmt.Printf("%d params is bool, value is %v ", index, v) 25 case int, int64, int32: 26 fmt.Printf("%d params is int, value is %v ", index, v) 27 case float32, float64: 28 fmt.Printf("%d params is float, value is %v ", index, v) 29 case string: 30 fmt.Printf("%d params is string, value is %v ", index, v) 31 case Student: 32 fmt.Printf("%d params student, value is %v ", index, v) 33 case *Student: 34 fmt.Printf("%d params *student, value is %v ", index, v) 35 } 36 } 37 } 38 39 func main() { 40 var b Student = Student{ 41 Name: "stu01", 42 Sex: "female", 43 } 44 Test(b) 45 just(28, 8.2, "this is a test", b, &b) 46 }
还可以将一个类型和接口相比较。如果一个类型实现了接口,那么该类型与其实现的接口就可以互相比较。
1 package main 2 3 import "fmt" 4 5 type Describer interface { 6 Describe() 7 } 8 type Person struct { 9 name string 10 age int 11 } 12 13 func (p Person) Describe() { 14 fmt.Printf("%s is %d years old", p.name, p.age) 15 } 16 17 func findType(i interface{}) { 18 switch v := i.(type) { 19 case Describer: 20 v.Describe() 21 default: 22 fmt.Printf("unknown type ") 23 } 24 } 25 26 func main() { 27 findType("zhangsan") //unknown type 28 p := Person{ 29 name: "zhangsan", 30 age: 25, 31 } 32 findType(p) //zhangsan is 25 years old 33 }
(9). 空接口,Interface{}
空接口没有任何方法,所以所有类型都实现了空接口。
var a int
var b interface{} //空接口
b = a
1 package main 2 3 import ( 4 "fmt" 5 ) 6 7 func describe(i interface{}) { 8 fmt.Printf("Type = %T, value = %v ", i, i) 9 } 10 11 func main() { 12 s := "Hello World" 13 describe(s) //Type = string, value = Hello World 14 i := 20 15 describe(i) //Type = int, value = 20 16 strt := struct { 17 name string 18 }{ 19 name: "zhangsan", 20 } 21 describe(strt) //Type = struct { name string }, value = {zhangsan} 22 }
(10). 判断一个变量是否实现了指定接口
1 package main 2 3 import "fmt" 4 5 type Describer interface { 6 Describe() string 7 } 8 9 type Person struct { 10 Name string 11 Age int 12 } 13 14 func (p Person) Describe() string { 15 str := fmt.Sprintf("%s is %d years old", p.Name, p.Age) 16 return str 17 } 18 19 func findType(a interface{}) { 20 if v, ok := a.(Describer); ok { 21 fmt.Printf("v implements Describer(): %s ", v.Describe()) 22 } 23 } 24 25 func main() { 26 p := Person { 27 Name: "zhangsan", 28 Age: 25, 29 } 30 31 findType(p) //v implements Describer(): zhangsan is 25 years old 32 }
(11). 指针类型和值类型的区别
1 package main 2 3 import "fmt" 4 5 type Describer interface { 6 Describe() 7 } 8 type Person struct { 9 name string 10 age int 11 } 12 13 func (p Person) Describe() { // 使用值接受者实现 14 fmt.Printf("%s is %d years old ", p.name, p.age) 15 } 16 17 type Address struct { 18 state string 19 country string 20 } 21 22 func (a *Address) Describe() { // 使用指针接受者实现 23 fmt.Printf("State %s Country %s", a.state, a.country) 24 } 25 26 // func (a Address) Describe() { // 使用指针接受者实现 27 // fmt.Printf("State %s Country %s", a.state, a.country) 28 // } 29 30 func main() { 31 var d1 Describer 32 p1 := Person{"Sam", 25} 33 d1 = p1 34 d1.Describe() 35 p2 := Person{"James", 32} 36 d1 = &p2 37 d1.Describe() 38 39 var d2 Describer 40 a := Address{"Washington", "USA"} 41 42 /* cannot use a (type Address) as type Describer 43 in assignment: Address does not implement 44 Describer (Describe method has pointer 45 receiver) 46 */ 47 48 //出错原因其原因是:对于使用指针接受者的方法,用一个指针或者一个可取得地址的值来调用 49 //都是合法的。但接口中存储的具体值(Concrete Value)并不能取到地址,因此在第 47 行, 50 //对于编译器无法自动获取 a 的地址,于是程序报错。 51 // d2 = a //error 但是如果将22-24替换为26-28,则d2 = a和d2 = &a都可以 52 53 d2 = &a // OK 54 55 d2.Describe() 56 57 }
(12). 变量slice和接口slice之间赋值操作,for range
var a []int var b []interface{} b = a
(13). 接口的零值
接口的零值是 nil。对于值为 nil 的接口,其底层值(Underlying Value)和具体类型(Concrete Type)都为 nil。
对于值为 nil 的接口,由于没有底层值和具体类型,当我们试图调用它的方法时,程序会产生 panic 异常。
1 package main 2 3 import "fmt" 4 5 type Describer interface { 6 Describe() 7 } 8 9 func main() { 10 var d1 Describer 11 if d1 == nil { 12 fmt.Printf("d1 is nil and has type %T value %v ", d1, d1) 13 } 14 15 //d1.Describe() //panic: runtime error: invalid memory address or nil pointer dereference 16 }
练习:实现一个通用的链表类(待完善)
1 package main 2 3 import "fmt" 4 5 type LinkNode struct { 6 data interface{} 7 next *LinkNode 8 } 9 10 type Link struct { 11 head *LinkNode 12 tail *LinkNode 13 } 14 15 func (p *Link) InsertHead(data interface{}) { 16 node := &LinkNode{ 17 data: data, 18 next: nil, 19 } 20 21 if p.tail == nil && p.head == nil { 22 p.tail = node 23 p.head = node 24 return 25 } 26 27 node.next = p.head 28 p.head = node 29 } 30 31 func (p *Link) InsertTail(data interface{}) { 32 node := &LinkNode{ 33 data: data, 34 next: nil, 35 } 36 37 if p.tail == nil && p.head == nil { 38 p.tail = node 39 p.head = node 40 return 41 } 42 43 p.tail.next = node 44 p.tail = node 45 } 46 47 func (p *Link) Trans() { 48 q := p.head 49 for q != nil { 50 fmt.Println(q.data) 51 q = q.next 52 } 53 }
1 package main 2 3 import "fmt" 4 5 func main() { 6 7 var link Link 8 for i := 0; i < 10; i++ { 9 //intLink.InsertHead(i) 10 link.InsertTail(fmt.Sprintf("str %d", i)) 11 } 12 13 link.Trans() 14 }
通过下面的例子体会接口的作用:
1 package main 2 3 import ( 4 "fmt" 5 ) 6 7 type SalaryCalculator interface { 8 CalculateSalary() int 9 } 10 11 type Permanent struct { 12 empId int 13 basicpay int 14 pf int 15 } 16 17 type Contract struct { 18 empId int 19 basicpay int 20 } 21 22 //salary of permanent employee is sum of basic pay and pf 23 func (p Permanent) CalculateSalary() int { 24 return p.basicpay + p.pf 25 } 26 27 //salary of contract employee is the basic pay alone 28 func (c Contract) CalculateSalary() int { 29 return c.basicpay 30 } 31 32 /* 33 total expense is calculated by iterating though the SalaryCalculator slice and summing 34 the salaries of the individual employees 35 */ 36 func totalExpense(s []SalaryCalculator) { 37 expense := 0 38 for _, v := range s { 39 expense = expense + v.CalculateSalary() 40 } 41 fmt.Printf("Total Expense Per Month $%d", expense) 42 } 43 44 func main() { 45 pemp1 := Permanent{1, 5000, 20} 46 pemp2 := Permanent{2, 6000, 30} 47 cemp1 := Contract{3, 3000} 48 employees := []SalaryCalculator{pemp1, pemp2, cemp1} 49 totalExpense(employees) 50 51 } 52 53 //假如公司增加了一种新的员工类型 Freelancer,它有着不同的薪资结构。Freelancer只需传递到 totalExpense 的切片参数中,无需 totalExpense 方法本身进行修改。只要 Freelancer 也实现了 SalaryCalculator 接口,totalExpense 就能够实现其功能。
用go实现一个图书管理系统:
1. 实现一个图书管理系统,具有以下功能:
a. 书籍录入功能,书籍信息包括书名、副本数、作者、出版日期
b. 书籍查询功能,按照书名、作者、出版日期等条件检索
c. 学生信息管理功能,管理每个学生的姓名、年级、身份证、性别、借了什么书等信息
d. 借书功能,学生可以查询想要的书籍,进行借出
e. 书籍管理功能,可以看到每种书被哪些人借出了
参考文献:
- https://blog.csdn.net/zyc88888/article/details/80307008 (Go 方法与函数区别)
- https://studygolang.com/articles/12266 (Go 系列教程 - 接口)
- https://studygolang.com/articles/12264 (Go 系列教程)