1. 自定义类型和类型别名

1.1. 自定义类型

Go语言中除了基本的数据类型之外,还可以自定义数据类型,最常用的是就是结构体,除此之外还可以基于基本数据类型来实现自定义类型,如: type newType type 。基于基本数据类型创造自定义类型的目的在于,部分场景中,需要对已有类型添加新的方法,此时需要使用到自定义类型。

  1. package main
  2. import "fmt"
  3. type MyInt int32
  4. func main() {
  5. var a MyInt = 133
  6. fmt.Printf("%T %#v %d\n", a, a, a) // main.MyInt 133 133, main包中MyInt类型
  7. }

1.2. 类型别名

在Go中最常见的类型别名就是 rune 和 byte ,分别为 int32 和 uint8 的别名。别名只是在代码中用于更好的区分不同数据类型,一旦编译完毕后,就直接映射为原始的数据类型。

  1. package main
  2. import "fmt"
  3. type testInt = uint64
  4. func main() {
  5. var (
  6. a testInt = 65539
  7. b rune = '渡'
  8. c rune = '#'
  9. )
  10. fmt.Printf("a --> Type:%T Value:%v\n", a, a)
  11. fmt.Printf("b --> Type:%T Value:%v\n", b, b)
  12. fmt.Printf("c --> Type:%T Value:%v\n", c, c)
  13. }
  1. [root@heyingsheng 01-type]# go run 01.go
  2. a --> Type:uint64 Value:65539
  3. b --> Type:int32 Value:28193
  4. c --> Type:int32 Value:35
  1. # rune 和 byte 类型
  2. [root@heyingsheng 01-type]# grep -E "rune|byte" /mnt/c/Program\ Files/Go/src/builtin/builtin.go | grep -v '//'
  3. type byte = uint8
  4. type rune = int32

1.3. 类型别名和自定义类型的区别

  • 自定义类型编译完毕后,仍然是自定义类型;类型别名编译完毕后,编译完毕后变成原始的数据类型
  • 类型别名是方便写代码时区分,自定义类型是方便扩展功能,如方法

2. 定义结构体

GoLang中支持面向对象编程,但是并不是通过Class方式来实现的,而是通过结构体(struct)来实现的,GoLang的面向对象思维的编程方式比较简单,去掉了传统OOP语言中的继承、方法重载、构造函数、析构函数以及this指针等特性。GoLang中仍然有面向对象编程的继承、封装和多态的特性,只是实现的方式不同而言。

2.1. 结构体的定义

2.1.1. 定义结构体

结构体的名称就是一种数据类型,其它内部的字段可以具有不同的数据类型,这样就实现了一个实例的不同属性。注意:

  • 同一个包中结构体数据类型不能重名
  • 同一个结构体中字段不能重名
  • 不能指定字段的默认值
  • 结构体默认是值类型,指针类型的结构体需要单独创建
    1. type Person struct {
    2. name string
    3. gender string
    4. age uint8
    5. hobby []string
    6. }

    2.1.2. 字段简写

    多个同类型的字段可以写在一行,格式: field1, field2, field3 Type
    1. type service struct {
    2. name, logDir, dataDir string
    3. port []uint16
    4. }

    2.1.3. 匿名结构体

    上述两种方式定义的结构体都有一个类型名称,可以被不同的实例所引用,参考 2.2. 结构体的初始化 。如果一个结构体只是使用一次,那么可以不定义结构体名称,直接完成定义。 ```go package main

func main() { var redis struct{ name string port uint16 dataDir string } redis.name = “Redis” redis.port = 6379 // 直接赋值 var nginx = struct{ name string port []uint16 }{“nginx”, []uint16{80,8080,8081}} }

  1. <a name="WbAa5"></a>
  2. #### 2.1.4. 结构体的匿名字段
  3. 匿名字段指没有声明字段名称,指声明了字段类型的结构体,这种结构体的字段名称与字段类型一致。因此同一个类型只能定义一个,否则就出现了结构体中字段名称冲突。 了解即可,**不建议使用!**
  4. ```go
  5. type student struct {
  6. string
  7. uint8
  8. }
  9. var stu01 student
  10. stu01.string = "张三"

2.2. 结构体的实例化

将结构体作为数据类型赋值给一个变量,称为结构体的实例化,实例化后的变量被称为(结构体的)实例、(结构体的)对象:

  • 实例化有两大类:以定义变量的形式实例化结构体,使用构造函数来实例化
  • 通过变量形式实例化有三种:
    • 使用 var.field = value 格式直接对字段赋值,常用
    • 使用 key/value 方式来赋值,与map 类型类似,常用
    • 使用 value 直接赋值,这种方式要求按顺序对所有字段赋值
  • 字段的默认值为对应数据类型的零值
    • 字符串、数字、数组字段为默认的零,可以直接拿来赋值
    • 切片、map和指针需要分配内存之后才能使用

      2.2.1. 以定义变量的形式实例化

      ```go package main

import ( “fmt” “strings” )

type Person struct { name string gender string age uint8 hobby []string }

func main() { // 结构体的默认值 var p0 Person fmt.Printf(“p0: %v %#v\n”, p0, p0) fmt.Println(strings.Repeat(“-“,30)) // 先实例化,再赋值,未赋值的使用字段零值 var p1 Person p1.name = “张三” p1.age = 18 p1.hobby = []string{“篮球”,”乒乓球”,”足够”} fmt.Printf(“p1: %v\n”, p1) fmt.Println(strings.Repeat(“-“,30)) // 使用key/value来实例化 p2 := Person{ name: “李四”, gender: “女”, age: 24, hobby: []string{“旅游”,”购物”}, } fmt.Printf(“p2: %v\n”, p2) fmt.Println(strings.Repeat(“-“,30)) // 直接使用value来实例化 p3 := Person{ “王五”, “男”, 35, []string{“游泳”,”钓鱼”}, } fmt.Printf(“p3: %v\n”, p3) }

[root@heyingsheng 02-struct]# go run 03.go

p0: { 0 []} main.Person{name:””, gender:””, age:0x0, hobby:[]string(nil)}

p1: {张三 18 [篮球 乒乓球 足够]}

p2: {李四 女 24 [旅游 购物]}

p3: {王五 男 35 [游泳 钓鱼]}

  1. <a name="th3AP"></a>
  2. #### 2.2.2. 结构体的指针
  3. ```go
  4. package main
  5. import "fmt"
  6. type Service struct {
  7. Name string
  8. Port []uint16
  9. }
  10. func main() {
  11. // 使用new定义结构体的指针类型的变量
  12. sshd := new(Service)
  13. sshd.Port = []uint16{22} //语法糖,是 (*sshd).port 的简写
  14. sshd.Name = "SSHD"
  15. fmt.Printf("sshd 类型:%T; 值:%v; 16进制:%p; 结构体的值:%v\n", sshd, sshd, sshd, *sshd)
  16. // 直接使用结构体指针进行定义
  17. nginx := &Service{}
  18. nginx.Name = "Nginx"
  19. nginx.Port = []uint16{80, 8080}
  20. fmt.Printf("nginx 类型:%T; 值:%v; 16进制:%p; 结构体的值:%v\n", nginx, nginx, nginx, *nginx)
  21. }
  1. [root@heyingsheng 02-struct]# go run 05.go
  2. sshd 类型:*main.Service; 值:&{SSHD [22]}; 16进制:0xc000066330; 结构体的值:{SSHD [22]}
  3. nginx 类型:*main.Service; 值:&{Nginx [80 8080]}; 16进制:0xc0000663c0; 结构体的值:{Nginx [80 8080]}

2.2.3. 使用构造函数来实例化

构造函数类似于Python中 __init__() 函数,是为了更方便的完成结构体的实例化。需要注意的是:

  • 构造函数名称约定俗成用new开头
  • 构造函数分两类,返回结构体的构造函数和返回结构体指针的构造函数
    • 返回结构体的构造函数,会将函数内生成的结构体赋值给接收变量,内存的开销较大
    • 返回结构体指针的构造函数,只需要对返回的指针进行拷贝,内存的开销较小,推荐使用 ```go package main

import “fmt”

type service struct { name string port []uint16 dataDir string logDir string man string }

func NewService(name string, port []uint16, dataDir string, logDir string, man string) service { return service{ name: name, port: port, dataDir: dataDir, logDir: logDir, man: man, } }

func NewServicePointer(name string, port []uint16, dataDir string, logDir string, man string) *service { return &service{ name: name, port: port, dataDir: dataDir, logDir: logDir, man: man, } }

func main() { nginx := NewService(“Nginx”,[]uint16{80,8080},”/data/nginx/html”,”/data/nginx/logs”,””) sshd := NewServicePointer(“sshd”,[]uint16{22},””,”/var/log/“,””) fmt.Printf(“nginx —> value:%v; type:%T\n”, nginx, nginx) fmt.Printf(“sshd —> value:%v; type:%T ;dstvalue:%v\n”, sshd, sshd, *sshd) }

[root@heyingsheng day04]# go run 05-structfunc/main.go nginx —> value:{Nginx [80 8080] /data/nginx/html /data/nginx/logs }; type:main.service sshd —> value:&{sshd [22] /var/log/ }; type:*main.service ;dstvalue:{sshd [22] /var/log/ }

  1. <a name="ycM2o"></a>
  2. ### 2.3. 结构体值的访问和修改
  3. <a name="3pmqv"></a>
  4. #### 2.3.1. 结构体的访问
  5. ```go
  6. package main
  7. import "fmt"
  8. type service struct {
  9. name string
  10. port []uint16
  11. dataDir string
  12. logDir string
  13. man string
  14. }
  15. func main() {
  16. mysql := service{
  17. name: "MySQL",
  18. port: []uint16{3306},
  19. dataDir: "/data/mysql/db",
  20. logDir: "/data/mysql/logs",
  21. man: "",
  22. }
  23. fmt.Println(mysql.name,mysql.dataDir)
  24. }

2.3.2. 结构体数据修改

对于结构体的数据修改有两种方式:一个是通过 name.field = value ,一种是通过函数修改。

  • 通过函数修改时,函数传参传的是实参的副本,因此在函数想要修改结构体的值,必须要要采用指针的方式。
  • go语言中通过结构体指针操作结构体数据时,可以省略 * ,这是go的语法糖 ```go package main

import ( “fmt” “strings” )

type service struct { name string port []uint16 dataDir string logDir string man string }

func f0(s service) { fmt.Printf(“函数内结构体内存地址:%p\n”, &s) s.dataDir = “/data/mysql/data” }

func f1(s service) { //(s).dataDir = “/data/mysql/data” s.dataDir = “/data/mysql/data” // 这种写法是go的语法糖,本质就是上一行的写法 }

func main() { mysql := service{ name: “MySQL”, port: []uint16{3306}, dataDir: “/data/mysql/db”, logDir: “/data/mysql/logs”, man: “”, } mysql.dataDir = “/data/mysql/database” fmt.Println(mysql.name,mysql.dataDir) fmt.Println(strings.Repeat(“-“,30)) fmt.Printf(“实例化的mysql内存地址:%p\n”, &mysql) f0(mysql) fmt.Println(mysql.name,mysql.dataDir) fmt.Println(strings.Repeat(“-“,30)) f1(&mysql) fmt.Println(mysql.name,mysql.dataDir) }

[root@heyingsheng day04]# go run 03-accessstruct/main.go

MySQL /data/mysql/database

实例化的mysql内存地址:0xc00001a0c0 函数内结构体内存地址:0xc00001a120

MySQL /data/mysql/database

MySQL /data/mysql/data

  1. <a name="dDoAj"></a>
  2. #### 2.4. 嵌套结构体
  3. <a name="VyNbU"></a>
  4. #### 2.4.1. 嵌套结构体的定义
  5. 嵌套结构体类似于Json,用于存储复杂数据类型,比如存储Kubernetes中的资源配置清单,如下案例:
  6. ```go
  7. package main
  8. import "fmt"
  9. // 定义k8s资源清单metadata字段
  10. type metadata struct {
  11. name string
  12. namespace string
  13. }
  14. // 定义k8s资源清单顶层字段
  15. type topInfo struct {
  16. aipVersion string
  17. kind string
  18. metadata
  19. }
  20. // 定义pod
  21. type pod struct {
  22. info topInfo
  23. spec struct{
  24. containers []string // 仅做一个示范而已,不操作更加复杂结构
  25. }
  26. }
  27. // 定义service
  28. type service struct {
  29. topInfo
  30. spec struct{
  31. clusterIP string
  32. }
  33. }
  34. func main() {
  35. var nginxPod = pod{
  36. info: topInfo{
  37. aipVersion: "v1",
  38. kind: "Pod",
  39. metadata: metadata{
  40. name: "nginx",
  41. namespace: "default",
  42. },
  43. },
  44. spec: struct {
  45. containers []string
  46. }{[]string{"nginx"}},
  47. }
  48. fmt.Println(nginxPod.info.aipVersion, nginxPod.info.namespace,nginxPod.spec.containers)
  49. nginxSVC := service{
  50. topInfo: topInfo{
  51. aipVersion:"v1",
  52. kind:"Service",
  53. metadata: metadata{
  54. name: "nginx",
  55. namespace: "default",
  56. },
  57. },
  58. spec: struct {
  59. clusterIP string
  60. }{"192.168.0.112"},
  61. }
  62. fmt.Println(nginxSVC.aipVersion, nginxSVC.namespace,nginxSVC.spec.clusterIP)
  63. }
  1. [root@heyingsheng day04]# go run 08-nested/main.go
  2. v1 default [nginx]
  3. v1 default 192.168.0.112
  1. 通过上面的案例可以分析得到:
  2. 1. 实现结构体嵌套有两种方式:
  3. (1) 将每一层的结构拆开定义(line5-14)。这种方式定义繁琐,赋值时方便(line32-39)
  4. (2) 直接在一个结构体中,使用struct来定义嵌套的结构体(line18-20),定义方便,赋值繁琐(line54-56)
  5. 2. 嵌套结构体访问
  6. (1) 如果嵌套的为匿名结构体(line13,line24),在查询值的时候可以省略中间字段,如nginxSVC.namespace
  7. (2) 如果嵌套的为命名结构体(line17),在查询的时候就得写全路径,如nginxPod.info.aipVersion
  8. (3) 当多个嵌套结构体中存在相同名称字段,则不适合使用匿名结构体

2.4.2. 结构体转json

使用 json.Marshal(name) 可以将结构体转换为字符切片,转为string后就是json字符串。需要注意的是结构体字段名称首字母需要大写,以下为简单演示。

  1. package main
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. )
  6. type service struct {
  7. // 只有字段名称首字母大写才能被其它包读取到,此时json字符串中的字段名都为大写字母开头
  8. // 如果需要指定在json中该字段显示的名称,则需要使用tag指定
  9. Name string `json:"name"`
  10. Port []uint16 `json:"port"`
  11. DataDir string `json:"data_dir"`
  12. LogDir string `json:"log_dir"`
  13. Man string `json:"man"`
  14. }
  15. func main() {
  16. nginx := service{
  17. Name: "Nginx",
  18. Port: []uint16{80, 8080, 8081},
  19. DataDir: "/data/nginx/www",
  20. LogDir: "/data/nginx/logs",
  21. Man: "",
  22. }
  23. ret, err := json.Marshal(nginx)
  24. if err == nil {
  25. fmt.Printf("type: %T; value:%v\n", string(ret), string(ret))
  26. }
  27. }
  1. [root@heyingsheng day04]# go run 10-json/main.go
  2. type: string; value:{"name":"Nginx","port":[80,8080,8081],"data_dir":"/data/nginx/www","log_dir":"/data/nginx/logs","man":""}
  3. [root@heyingsheng day04]# echo '{"name":"Nginx","port":[80,8080,8081],"data_dir":"/data/nginx/www","log_dir":"/data/nginx/logs","man":""}'|python3 -m json.tool
  4. {
  5. "name": "Nginx",
  6. "port": [
  7. 80,
  8. 8080,
  9. 8081
  10. ],
  11. "data_dir": "/data/nginx/www",
  12. "log_dir": "/data/nginx/logs",
  13. "man": ""
  14. }

2.4.3. json转结构体

json转结构体,需要先定义好接收json准换后的结构体变量,通过 json.Unmarshal([]byte, &name) 可以将字符切片转为结构体。需要注意的是:

  • json.Unmarshal()接收的是字符切片和结构体变量的指针
  • 必须要先定义好结构体和结构体变量 ```go package main

import ( “encoding/json” “fmt” )

type service struct { // 只有字段名称首字母大写才能被其它包读取到,此时json字符串中的字段名都为大写字母开头 // 如果需要指定在json中该字段显示的名称,则需要使用tag指定 Name string json:"name" Port []uint16 json:"port" DataDir string json:"data_dir" LogDir string json:"log_dir" Man string json:"man" }

func main() { var ret service jsonStr := {"name":"MySQL","port":[3306],"data_dir":"/data/mysql/data","log_dir":"/data/mysql/logs","man":"db"} err := json.Unmarshal([]byte(jsonStr), &ret) if err == nil { fmt.Printf(“type: %T; value:%v\n”, ret, ret) } }

[root@heyingsheng day04]# go run 10-json/main.go type: main.service; value:{MySQL [3306] /data/mysql/data /data/mysql/logs db}

  1. <a name="ff5KI"></a>
  2. ### 2.5. 注意事项
  3. <a name="pJCJI"></a>
  4. #### 2.5.1. 结构体的修改
  5. ```go
  6. package main
  7. import "fmt"
  8. type Person struct {
  9. Name string
  10. Age uint8
  11. }
  12. func f0() {
  13. p1 := Person{Name: "Tom", Age: 18}
  14. p2 := p1
  15. p2.Name = "Stone" // 结构体是指针类型,因此p2是p1的副本,因此修改p2的属性不会影响p1
  16. fmt.Println("f0:", p1)
  17. fmt.Println("f0:", p2)
  18. }
  19. func f1() {
  20. p1 := &Person{Name: "Tom~", Age: 18}
  21. p2 := p1
  22. p2.Name = "Stone~" // p1是结构体指针,因此p2中存储的是p1的地址,而p1的地址又指向了结构体实例
  23. fmt.Println("f1:", *p1)
  24. fmt.Println("f1:", *p2)
  25. }
  26. func main() {
  27. f0()
  28. f1()
  29. }
  1. [root@heyingsheng 13-notice]# go run 01.go
  2. f0: {Tom 18}
  3. f0: {Stone 18}
  4. f1: {Stone~ 18}
  5. f1: {Stone~ 18}

2.5.2. 内存地址的连续性

  1. package main
  2. import "fmt"
  3. type S21 struct {
  4. N1, N2 int8
  5. N3, N4 int64
  6. }
  7. type S22 struct {
  8. x, y int8
  9. }
  10. type S23 struct {
  11. m, n S22
  12. }
  13. type S24 struct {
  14. m, n *S22
  15. }
  16. func f21() {
  17. // N1和N2是连续的,地址相差为1。N3和N4是连续的。N2和N3之所以中间有中断是为了方便寻址,而做的优化
  18. s := S21{N1: 100, N2: 120, N3: 0, N4: 1}
  19. fmt.Printf("s地址:%p, s.N1地址:%p, s.N2地址:%p, s.N3地址:%p, s.N4地址:%p\n", &s, &s.N1, &s.N2, &s.N3, &s.N4)
  20. // s23中所有属性的地址都是连续的
  21. e := S23{m:S22{1,2}, n:S22{3,4}}
  22. fmt.Printf("e地址:%p, e.m.x地址:%p, e.m.y地址:%p, e.n.x地址:%p, e.n.y地址:%p\n",
  23. &e, &e.m.x, &e.m.y, &e.n.x, &e.n.y)
  24. // s24中m和n的地址连续,但是m和n指针对应的s22的地址就不一定连续了
  25. f := S24{m:&S22{x:1,y:2},n:&S22{x:3,y:4}}
  26. fmt.Printf("f地址:%p, f.m地址:%p, f.n地址:%p\n",&f, &f.m, &f.n)
  27. fmt.Printf("f地址:%p, f.m.x地址:%p, f.m.y地址:%p, f.n.x地址:%p, f.n.y地址:%p\n",
  28. &f, &f.m.x, &f.m.y, &f.n.x, &f.n.y)
  29. }
  30. func main() {
  31. f21()
  32. }
  1. [root@heyingsheng 13-notice]# go run 02.go
  2. s地址:0xc00000c380, s.N1地址:0xc00000c380, s.N2地址:0xc00000c381, s.N3地址:0xc00000c388, s.N4地址:0xc00000c390
  3. e地址:0xc0000100a8, e.m.x地址:0xc0000100a8, e.m.y地址:0xc0000100a9, e.n.x地址:0xc0000100aa, e.n.y地址:0xc0000100ab
  4. f地址:0xc0000461f0, f.m地址:0xc0000461f0, f.n地址:0xc0000461f8
  5. f地址:0xc0000461f0, f.m.x地址:0xc0000100ac, f.m.y地址:0xc0000100ad, f.n.x地址:0xc0000100ae, f.n.y地址:0xc0000100af

2.5.3. 结构体之间类型转换

两个定义完全相同的结构体仍然是不同的数据类型,不能直接做类型转换。当且仅当在两个结构体字段完全相同(字段名称、类型、个数相同)的情况下实现类型强制转换!

  1. package main
  2. import "fmt"
  3. type S31 struct {
  4. Name string
  5. Age int
  6. }
  7. type S32 struct {
  8. Name string
  9. Age int
  10. }
  11. func main() {
  12. s1 := S31{Name:"Tom", Age:18}
  13. s2 := S32{}
  14. //s2 = s1 // 直接直接转换报错: cannot use s1 (type S31) as type S32 in assignment
  15. s2 = S32(s1)
  16. fmt.Printf("s1类型:%T, s2类型:%T\n", s1, s2)
  17. fmt.Printf("s1的值:%v, s2的值:%v\n", s1, s2)
  18. }
  1. [root@heyingsheng 13-notice]# go run 03.go
  2. s1类型:main.S31, s2类型:main.S32
  3. s1的值:{Tom 18}, s2的值:{Tom 18}

3. 方法

方法是作用于特定数据类型变量的一种函数,本质是函数,只是相对于普通函数多了接收者信息。方法相当于Python中实例的方法,结构体的方法在编程中使用的非常多。格式如下:

  1. func (接收者 接收者类型)函数名(参数)(返回值){
  2. 函数代码块
  3. }
  4. # 接收者类型:当前方法作用的数据类型,比如 service 结构体。也可以是数据类型的指针
  5. # 接收者: 接收者相当于Py中方法的self参数,表示调用这个方法的实例。在go中使用类型名称的小写,如service类型使用s
  6. # 函数名、参数、返回值与普通函数定义的规则一致
  7. 在方法中,采用 接收者.字段 可以实现调用实例的属性
  1. # 指针类型接收者和值类型接收者区别
  2. 1. 指针类型接收者方法会用在较为复杂的数据类中,降低内存拷贝带来的资源消耗
  3. 2. 当需要修改实例的属性时,需要通过指针类型的方法
  4. 3. 一般非特殊场景中,建议使用指针类型的方法
  5. # 语法糖
  6. 如果指针类型的变量调用值类型接收者方法时,方法会自动对指针求值!

3.1. 值类型接收者

3.1.1. 代码展示

  1. package main
  2. import "fmt"
  3. type service struct {
  4. name string
  5. port []uint16
  6. man string
  7. }
  8. func newService(name string,port []uint16,man string) *service {
  9. return &service{
  10. name: name,
  11. port: port,
  12. man: man,
  13. }
  14. }
  15. func (s service)startService() {
  16. s.port = []uint16{3366}
  17. fmt.Printf("startService: %T %p\n", s, &s)
  18. fmt.Printf("startService: service %s is started!Listen port %v\n", s.name, s.port)
  19. }
  20. func main() {
  21. mysql := newService("MySQL", []uint16{3306},"")
  22. fmt.Printf("%T %p %v\n", mysql, mysql,mysql)
  23. mysql.startService()
  24. fmt.Printf("%T %p %v\n", mysql, mysql,mysql)
  25. }
  1. [root@heyingsheng day04]# go run 06-method/main.go // 端口没有发生变化
  2. *main.service 0xc00004e040 &{MySQL [3306] }
  3. startService: main.service 0xc00004e0c0
  4. startService: service MySQL is started!Listen port [3366]
  5. *main.service 0xc00004e040 &{MySQL [3306] }

3.1.2. 执行步骤分析

image.png

3.2. 指针类型接收者

3.2.1. 代码展示

  1. package main
  2. import "fmt"
  3. type service struct {
  4. name string
  5. port []uint16
  6. man string
  7. }
  8. func newService(name string,port []uint16,man string) *service {
  9. return &service{
  10. name: name,
  11. port: port,
  12. man: man,
  13. }
  14. }
  15. func (s *service)stopService() {
  16. s.port = []uint16{3377}
  17. fmt.Printf("stopService: %T %p\n", s, s)
  18. fmt.Printf("stopService: service %s is stop!Release port %v\n", s.name, s.port)
  19. }
  20. func main() {
  21. mysql := newService("MySQL", []uint16{3306},"")
  22. fmt.Printf("%T %p %v\n", mysql, mysql,mysql)
  23. mysql.stopService()
  24. fmt.Printf("%T %p %v\n", mysql, mysql,mysql)
  25. }
  1. [root@heyingsheng day04]# go run 06-method/main.go
  2. *main.service 0xc00004e040 &{MySQL [3306] }
  3. stopService: *main.service 0xc00004e040
  4. stopService: service MySQL is stop!Release port [3377]
  5. *main.service 0xc00004e040 &{MySQL [3377] }

3.2.2. 执行步骤分析

image.png

3.3. 特殊方法

3.3.1. String()

当一个数据类型实现了 String() 方法后,在使用 fmt.Println() 时,会自动调用该方法,类似于 Python 中的 __str__()

  1. package main
  2. import "fmt"
  3. type Student struct {
  4. Name string
  5. Age int
  6. }
  7. func (s *Student)String() string {
  8. ret := fmt.Sprintf("value:(Name:[%v], Age:[%v]); pointer:[%p]", s.Name, s.Age, s)
  9. return ret
  10. }
  11. func main() {
  12. s1 := Student{Name:"Tom", Age:20}
  13. fmt.Println(&s1)
  14. }
  1. [root@heyingsheng 13-notice]# go run 04.go
  2. value:(Name:[Tom], Age:[20]); pointer:[0xc0000044a0]

3.4. 给内置类型添加方法

自定义内置类型的用处之一在于为内置类型添加方法,如基于 string 类型自定义一个myStr类型,目的在于添加一些自定义方法,因为在其它包中是无法直接给内置类型添加方法的。

  1. package main
  2. import "fmt"
  3. type myStr string
  4. func (m myStr)repeat(n int) myStr {
  5. var tmp myStr
  6. for i:=1;i<=n;i++{
  7. tmp += m
  8. }
  9. return tmp
  10. }
  11. func main() {
  12. s0 := myStr("s")
  13. s0 = s0.repeat(30)
  14. fmt.Printf("tye:%T value:%v\n",s0,s0)
  15. }
  1. [root@heyingsheng day04]# go run 07-method/main.go
  2. type:main.myStr value:ssssssssssssssssssssssssssssss

3.5. 方法和函数调用时的区别

3.5.1. 调用方式的区别

  1. 函数调用方式: 函数名(实参)
  2. 方法调用方式: 变量名.方法名(实参)

3.5.2. 方法中的语法糖

  1. // 定义方法和结构体如下:
  2. type Student struct { }
  3. func (s Student)f1() { }
  4. func (s *Student)f2() { }
  5. s1 := Student{}
  6. // 结构体实例 s1 可以直接调用 f1 和 f2,结构体指针 &s1 可以调用 f1 和 f2
  7. s1.f1()
  8. s1.f2()
  9. (&s1).f1()
  10. (&s1).f2()