本节主要内容:

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. }

example

(2). struct 中字段访问:和其他语言一样,使用点(.)

  1. var stu Student
  2.  
  3. stu.Name = "tony"
  4. stu.Age =
  5. stu.Score=
  6.  
  7. fmt.Printf("name=%s age=%d score=%d", stu.Name, stu.Age, stu.Score)

example

(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 : ,
  16. score : 99.99,
  17. }
  18.  
  19. //struct定义的形式1
  20. var stu2 Student
  21. stu2.Name = "zhangsan2"
  22. stu2.Age =
  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 = //(*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:,
  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. }

example

(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 =
  15. stu.Name = "hua"
  16. stu.score =
  17.  
  18. var stu1 *Student = &Student{
  19. Age: ,
  20. Name: "hua",
  21. }
  22.  
  23. var stu3 = Student{
  24. Age: ,
  25. Name: "hua",
  26. }
  27.  
  28. fmt.Println(stu1.Name)
  29. fmt.Println(stu3)
  30. fmt.Printf("Name:%p\n", &stu.Name) //Name:0xc042002720
  31. fmt.Printf("Age: %p\n", &stu.Age) //Age: 0xc042002730
  32. fmt.Printf("score:%p\n", &stu.score) //score:0xc042002738
  33. }

example

(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\n", 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\n", 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\n", id)
  73. }
  74.  
  75. func main() {
  76. var stu1 Student = Student {
  77. Name:"name1",
  78. Age:rand.Intn(),
  79. Score:rand.Float32()*,
  80. Id:"",
  81. }
  82. trans(&stu1)
  83.  
  84. var head *Student = &stu1
  85.  
  86. var stu2 Student = Student {
  87. Name:"name2",
  88. Age:rand.Intn(),
  89. Score:rand.Float32()*,
  90. Id:"",
  91. }
  92. insertHead(&head, &stu2) //头部插入
  93. trans(head)
  94.  
  95. var stu3 Student = Student {
  96. Name:"name3",
  97. Age:rand.Intn(),
  98. Score:rand.Float32()*,
  99. Id:"",
  100. }
  101.  
  102. insertTail(head, &stu3) //尾部插入
  103. trans(head)
  104.  
  105. for i := ; i < ; i++ {
  106. stu := Student {
  107. Name:fmt.Sprintf("name%d", i),
  108. Age:rand.Intn(),
  109. Score:rand.Float32()*,
  110. Id:fmt.Sprintf("00000%d", i),
  111. }
  112.  
  113. addNode(head, "", &stu) //增加节点
  114. }
  115. trans(head)
  116.  
  117. delNode(head, "") //删除节点
  118. trans(head)
  119.  
  120. checkNode(head, "") //查
  121. checkNode(head, "")
  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 =
  29. root.Score =
  30.  
  31. var left1 *Student = new(Student)
  32. left1.Name = "stu02"
  33. left1.Age =
  34. left1.Score =
  35.  
  36. root.left = left1
  37.  
  38. var right1 *Student = new(Student)
  39. right1.Name = "stu04"
  40. right1.Age =
  41. right1.Score =
  42.  
  43. root.right = right1
  44.  
  45. var left2 *Student = new(Student)
  46. left2.Name = "stu03"
  47. left2.Age =
  48. left2.Score =
  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 =
  12.  
  13. var b Stu
  14. a = b // cannot use b (type Stu) as type Student in assignment
  15. }

example

  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 =
  16. var j int =
  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{}
  24.  
  25. var b Stu
  26. a = Student(b) //进行强制转换 ok
  27. fmt.Println(a) //{0}
  28. }

example2

(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. fmt.Println(s) //&{tony 20}
  21. }

example

(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: ,
  24. Score: ,
  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: ,
  38. score: ,
  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. }

tag

(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 =
  28. t.int =
  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 =
  32.  
  33. t.Cart2.name = "train2"
  34. t.Cart2.age =
  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", )
  19. fmt.Printf("name = %s, age = %d\n", stu.Name, stu.Age) //name = zhansan, age = 20
  20. }

example

"类的"方法:
        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. }

error example

  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 := ,
  25. var aa, bb myInt = ,
  26. fmt.Println(Add(a, b)) //
  27. fmt.Println(aa.Add(bb)) //
  28. }

right example

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 =
  16. t.test() //
  17. }

example

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:,
  24. }
  25.  
  26. var a Animal = Animal {
  27. Age:,
  28. }
  29.  
  30. p.Eat()
  31. a.Eat()
  32. }

example

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 = ;
  11. printf("before num = %d\n", num); //before num = 1
  12. set(&num, );
  13. printf("after num = %d\n", num); //after num = 10
  14. }

C语言通过传递指针改变变量的值

  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:,
  22. }
  23.  
  24. fmt.Printf("before name = %s, age = %d\n", 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()
  28. fmt.Printf("after name = %s, age = %d\n", 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 =
  13. }
  14.  
  15. //值接收器
  16. func (p Car) InitChange() {
  17. p.name = "receiver"
  18. p.weight =
  19.  
  20. }
  21.  
  22. //指针接收器
  23. func (p *Car) InitChange2() {
  24. p.name = "receiver2"
  25. p.weight =
  26.  
  27. }
  28.  
  29. func main() {
  30. var c Car = Car{
  31. weight:,
  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. }

example

      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:,
  24. name:"bike",
  25. },
  26. wheel:,
  27. }
  28.  
  29. fmt.Println(b) //{{100 bike} 2}
  30. b.Run() //running 匿名字段方法 Run
  31. }

调用匿名字段方法

      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 =
  23. a.name = "bike"
  24. a.wheel =
  25.  
  26. fmt.Println(a) //{{100 bike} 2}
  27. a.Run() //running
  28. }

example

      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 =
  26. a.name = "bike"
  27. a.lunzi =
  28.  
  29. fmt.Println(a)
  30. a.Run()
  31.  
  32. var b Train
  33. //注意访问方式
  34. b.c.weight =
  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 =
  45.  
  46. t.Place = "xian"
  47. t.Weight =
  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. }

example

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: ,
  31. }
  32.  
  33. t = people
  34. t.Eat()
  35. t.Sleep()
  36.  
  37. fmt.Println("t:", t) //t: {people 100}
  38. }

example

(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\n", 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. }

example

(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: ,
  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. }

example

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\n", p.Name)
  21. }
  22.  
  23. func (p People) Sleep() {
  24. fmt.Printf("People %s sleep\n", p.Name)
  25. }
  26.  
  27. func (p Animal) Eat() {
  28. fmt.Printf("Animal %s eat\n", p.Name)
  29. }
  30.  
  31. func (p Animal) Sleep() {
  32. fmt.Printf("Animal %s sleep\n", 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. }

example

练习:调用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 := ; i < ; i++ {
  47. stu := Student{
  48. Name: fmt.Sprintf("stu%d", rand.Intn()),
  49. Id: fmt.Sprintf("110%d", rand.Int()),
  50. Age: rand.Intn(),
  51. }
  52.  
  53. stus = append(stus, stu)
  54. }
  55.  
  56. for _, v := range stus {
  57. fmt.Println(v)
  58. }
  59.  
  60. fmt.Println("\n\n")
  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. }

example

(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{} =
  15. assert(s)
  16.  
  17. s = "hello"
  18. assert(s) //panic: interface conversion: interface {} is string, not int
  19. }

example

注意: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 代替。

练习,写一个函数判断传入参数的类型

  1. func classifier(items ...interface{}) {
  2. for i, x := range items {
  3. switch x.(type) {
  4. case bool: fmt.Printf("param #%d is a bool\n", i)
  5. case float64: fmt.Printf("param #%d is a float64\n", i)
  6. case int, int64: fmt.Printf("param #%d is an int\n", i)
  7. case nil: fmt.Printf("param #%d is nil\n", i)
  8. case string: fmt.Printf("param #%d is a string\n", i)
  9. default: fmt.Printf("param #%d’s type is unknown\n", i)
  10. }
  11. }
  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\n", index, v)
  25. case int, int64, int32:
  26. fmt.Printf("%d params is int, value is %v\n", index, v)
  27. case float32, float64:
  28. fmt.Printf("%d params is float, value is %v\n", index, v)
  29. case string:
  30. fmt.Printf("%d params is string, value is %v\n", index, v)
  31. case Student:
  32. fmt.Printf("%d params student, value is %v\n", index, v)
  33. case *Student:
  34. fmt.Printf("%d params *student, value is %v\n", 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(, 8.2, "this is a test", b, &b)
  46. }

example

还可以将一个类型和接口相比较。如果一个类型实现了接口,那么该类型与其实现的接口就可以互相比较。

  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\n")
  23. }
  24. }
  25.  
  26. func main() {
  27. findType("zhangsan") //unknown type
  28. p := Person{
  29. name: "zhangsan",
  30. age: ,
  31. }
  32. findType(p) //zhangsan is 25 years old
  33. }

example

(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\n", i, i)
  9. }
  10.  
  11. func main() {
  12. s := "Hello World"
  13. describe(s) //Type = string, value = Hello World
  14. i :=
  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. }

example

(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\n", v.Describe())
  22. }
  23. }
  24.  
  25. func main() {
  26. p := Person {
  27. Name: "zhangsan",
  28. Age: ,
  29. }
  30.  
  31. findType(p) //v implements Describer(): zhangsan is 25 years old
  32. }

example

(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\n", 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", }
  33. d1 = p1
  34. d1.Describe()
  35. p2 := Person{"James", }
  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. }

example

(12). 变量slice和接口slice之间赋值操作,for range

  1. var a []int
  2. var b []interface{}
  3. 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\n", d1, d1)
  13. }
  14.  
  15. //d1.Describe() //panic: runtime error: invalid memory address or nil pointer dereference
  16. }

example

练习:实现一个通用的链表类(待完善)

  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. }

link.go

  1. package main
  2.  
  3. import "fmt"
  4.  
  5. func main() {
  6.  
  7. var link Link
  8. for i := ; i < ; i++ {
  9. //intLink.InsertHead(i)
  10. link.InsertTail(fmt.Sprintf("str %d", i))
  11. }
  12.  
  13. link.Trans()
  14. }

main.go

通过下面的例子体会接口的作用:

  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 :=
  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{, , }
  46. pemp2 := Permanent{, , }
  47. cemp1 := Contract{, }
  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 系列教程)

Go语言学习之5 进阶-排序、链表、二叉树、接口的更多相关文章

  1. golang(5):struct & 链表 & 二叉树 & 接口

    struct : 结构体 // 1. 用来自定义复杂数据结构 // 2. struct里面可以包含多个字段(属性) // 3. struct类型可以定义方法,注意和函数的区分 // 4. struct ...

  2. C语言学习之交换(冒泡)排序

    在学习c语言的过程中,在数组内容中我们总是能学习到对一组数据进行排序,对于排序有许多的方法,像 (交换)冒泡排序.选择排序.(基数)桶排序.(插入)二分法排序等等. 我主要以我个人的理解去分析常见的交 ...

  3. c语言学习笔记(13)——链表

    链表 算法: 1.通俗定义: 解题的方法和步骤 2.狭义定义: 对存储数据的操作 3.广义定义: 广义的算法也叫泛型 无论数据是如何存储的,对数据的操作都是一样的 我们至少可以通过两种结构来存储数据 ...

  4. leetcood学习笔记-82-删除排序链表中的重复元素二

    题目描述: 方法一: class Solution(object): def deleteDuplicates(self, head): """ :type head: ...

  5. Leecode刷题之旅-C语言/python-83删除排序链表中的重复元素

    /* * @lc app=leetcode.cn id=83 lang=c * * [83] 删除排序链表中的重复元素 * * https://leetcode-cn.com/problems/rem ...

  6. 单链表 C语言 学习记录

    概念 链接方式存储 链接方式存储的线性表简称为链表(Linked List). 链表的具体存储表示为: 用一组任意的存储单元来存放线性表的结点(这组存储单元既可以是连续的,也可以是不连续的). 链表中 ...

  7. C语言 删除排序链表中的重复元素

    给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次. 示例 1: 输入: 1->1->2输出: 1->2示例 2: 输入: 1->1->2->3-> ...

  8. 足球运动训练心得及经验分析-c语言学习调查

    在准备预备作业02之前,我参考娄老师的提示,阅读了<[做中学(Learning By Doing)]之乒乓球刻意训练一年总结>一文. 在文章描述的字里行间,给予我的印象是系统.负责,娄老师 ...

  9. 5332盛照宗 如何获取新技能+c语言学习调查

    如何获取新技能+c语言学习调查 你有什么技能比大多人(超过90%以上)更好? 如果问我有没有什么技能比大多数人,并且是90%的人好,我还真不敢说有,因为世界上有70亿人,要比63亿人做的好才行啊.我也 ...

随机推荐

  1. 记录结果再利用的"动态规划"之背包问题

    参考<挑战程序设计竞赛>p51 https://www.cnblogs.com/Ymir-TaoMee/p/9419377.html 01背包问题 问题描述:有n个重量和价值分别为wi.v ...

  2. 牛客网数据库SQL实战(1-5)

    1.查找最晚入职员工的所有信息 CREATE TABLE `employees` ( `emp_no` int(11) NOT NULL, `birth_date` date NOT NULL, `f ...

  3. 【MonkeyRunner环境搭建】

    一.配置MonkeyRunner环境变量 1.首先下载一个AndroidSDK,在SDK的目录中的tools文件夹中,直接带有MonkeyRunner 2.打开MonkeyRunner的方式: |-- ...

  4. 使用svn导入项目

    打开eclipse工作平台点击“File”-->import如下图:   在项目导入框中找到SVN选择“从SVN检出项目”然后点击“下一步”,出现如下界面:   在上图界面中选择“创建新的资源库 ...

  5. 我的互联网30年。永远的8U8 永远的Y365

    我的互联网30年.永远的8U8 永远的Y365

  6. SpringBoot cookie工具类

    code: import java.io.IOException; import java.util.HashMap; import java.util.Map; import javax.annot ...

  7. 【resultType】Mybatis种insert或update的resultType问题

    Attribute "resultType" must be declared for element type "insert"或"update&q ...

  8. Tutorial: Generate BBox or Rectangle to locate the target obejct

    Tutorial: Generate BBox or Rectangle to locate the target obejct clc;close all;clear all; Img=imread ...

  9. .Net Core 本地化&全球化 实践

    介绍: 所有有关本地化的数据获取,都是从统一的一个资源文件中获取 1.创建虚拟类.资源文件,用于作为本地化数据的获取源 2.Configure localization:支持view.data ann ...

  10. docker 命令2

    docker build -t dvm.adsplatformproxy:v1.0.0 . #build images docker run -e WWNamespace=dev -e ZKServe ...