Go允许用户自定义类型,当你需要用代码抽象描述一个事物或者对象的时候,可以声明一个 struct 类型来进行描述。
当然,Go语言中,用户还可以基于已有的类型来定义其他类型。
简单来说,Go语言中用户可以有两种方法定义类型,第一种是使用 struct 关键字来创造一个结构类型;第二种是基于已有的类型,将其作为新类型的类型说明。
01. 自定义类型的基本使用基于已有的类型的这种方式比较简单,但需要注意的是,虽然是基于已有类型来定义新类型,但是基础类型和新类型是完全不同的两种类型,不能相互赋值,因为Go语言中,编译器不会对不同类型的值做隐式转换。
当需要使用一个比较明确的名字类描述一种类型时,使用这种自定义类型就比较合适,比如定义一个表示年龄的类型可以基于整形来定义一个 Age 类型,特指年龄类型。
下面是基于已有类型的方式定义类型的示例
1 2 3 4 5 6 7 8 9 10 type Duration int var d Durationi := 50 d = i
使用关键字 struct 来声明一个结构类型时,要求字段是固定并且唯一的,并且字段的类型也是已知的,但是字段类型可以是内置类型(比如 string, bool, int 等等),也可以是用户自定义的类型(比如,本文中介绍的 struct 类型)。
声明struct 结构体的公式:type 结构体名称 struct {}
。
在任何时候,创建一个变量并初始化其零值时,我们习惯是使用关键字 var,这种用法是为了更明确的表示变量被设置为零值。
而如果是变量被初始化为非零值时,则使用短变量操作符 :=
和结构字面量 结构类型{ 字段: 字段值, } 或者 结构类型{ 字段1值, 字段2值 } 来创建变量。
两种字面量初始化方式的差异与限制:
结构类型{ 字段1值, 字段2值 } 这种初始化方式时:
在最后一个字段值的结尾可以不用加逗号 ,
必须严格按照声明时的字段顺序来进行初始化,不然会得不到预期的结果;如果字段类型不一致,还会导致初始化失败
必须要初始化所有的字段,不然会报错 Too few values
结构类型{ 字段: 字段值, } 这种初始化方式时:
每一个字段值的结尾必须要加一个逗号 ,
初始化时,不要考虑字段声明的顺序
允许只初始化部分字段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package main import "log" type animal struct {} type cat struct { name string age int } func main () { var c1 cat log.Println(c1) c2 := cat{"kitten" , 1 } log.Println(c2) c3 := cat{age: 2 } log.Println(c3, c3.age) c3.name = "kk" log.Println(c3.name) }
以上是 struct 结构类型的基本使用,但是在项目开发中会遇到其他的用法,比如解析 json 或者 xml 文件到结构体类型变量中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 [ { "site" : "npr" , "link" : "http://www.npr.org/rss/rss.php?id=1001" , "type" : "rss" }, { "site" : "npr" , "link" : "http://www.npr.org/rss/rss.php?id=1008" , "type" : "rss" }, { "site" : "npr" , "link" : "http://www.npr.org/rss/rss.php?id=1006" , "type" : "rss" } ] package main import ( "encoding/json" "log" "os" ) type Feed struct { Site string `json:"site"` Link string `json:"link"` Type string `json:"type"` } func ParseJSON (path string ) ([]*Feed, error ) { file, err := os.Open(path) if err != nil { return nil , err } defer file.Close() var files []*Feed json.NewDecoder(file).Decode(&files) return files, nil } func main () { var path = "./data.json" feeds, err := ParseJSON(path) if err != nil { log.Println("error: " , err) } for i, val := range feeds { log.Printf("%d - site:%s, link:%s, type:%s" , i, val.Site, val.Link, val.Type) } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 <?xml version="1.0" encoding="utf-8" ?> <content> <item> <site>npr</site> <link>http: <type >rss</type > </item> <item> <site>npr</site> <link>http: <type >rss</type > </item> <item> <site>npr</site> <link>http: <type >rss</type > </item> </content> package main import ( "encoding/xml" "io/ioutil" "log" "os" ) type Content struct { XMLName xml.Name `xml:"content"` Item []item `xml:"item"` } type item struct { XMLName xml.Name `xml:"item"` Site string `xml:"site"` Link string `xml:"link"` Type string `xml:"type"` } func ParseXML (path string ) (*Content, error ) { data, err := ioutil.ReadFile(path) if err != nil { return nil , err } var con Content xml.Unmarshal(data, &con) return &con, nil } func main () { var xmlpath = "./data.xml" content, err := ParseXML(xmlpath) if err != nil { log.Println("error: " , err) } for i, val := range content.Item { log.Printf("%d - site:%s, link:%s, type:%s" , i, val.Site, val.Link, val.Type) } }
02. 公开或未公开的标识符在Go语言中,声明类型、函数、方法、变量等标识符时,使用大小写字母开头来区分该标识符是否公开(即是否能在包外访问)。
大写字母开头表示公开,小写字母开头表示非公开。所以如果某个结构类型以及结构类型的字段,函数,方法,变量等标识符,想要被外部访问到,那必须以大写字母开头。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 package user type duration int type Duration int type user struct { name string } type User struct { Name string phone string address } type address struct { City string position position } type position struct { Longitude string Latitude string } func New (num int ) duration { return duration(num) }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 package main import ( "go-demo/user" "log" ) func main () { var d2 user.Duration = 10 log.Println(d2) d3 := user.New(100 ) log.Printf("type: %T, value:%d" , d3, d3) var u user.User log.Printf("%#v" , u) u3 := user.User{ Name: "Jack" , } log.Println(u3.Name) var u5 user.User u5.City = "Beijing" log.Println(u5.City) }
03. 给自定义类型增加方法在Go语言中,编译器只允许为命名的用户定义的类型声明方法。方法跟函数类似,只是方法不会单独存在,一般是绑定到某个结构类型中,给类型增加方法的方式很简单,就是在方法名和 func 之间增加一个参数即可, 这个参数称为方法的接收者。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 type User struct { Name string } func (u User) Read() { log.Println(u.Name, "is Reading..." ) } func main () { u := User{ Name: "Jack" , } u.Read() }
方法的接收者,可以是值接收者,也可以是指针接收者。
而应该使用值接收者还是指针接收者,那要看给这个类型增加或删除某个值时,是创建一个新值,还是要更改当前值?如果是要创建一个新值,该类型的方法就使用值接收者;如果是要修改当前值,就使用指针接收者。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 package main import "log" type Age int func (age Age) ChangeAge() { age = 18 } func (age *Age) ChangeAgeByPointer() { *age = 18 } type IP []byte func (ip IP) ChangeIP() { ip = []byte ("456" ) } func (ip *IP) ChangeIPByPointer() { *ip = []byte ("456" ) } type Pet struct { Name string Hobby []string } func (pet Pet) ChangePetValue(name string , hobby []string ) { pet.Name = name pet.Hobby = hobby } func (pet *Pet) ChangePetValueByPointer(name string , hobby []string ) { pet.Name = name pet.Hobby = hobby } func main () { var age Age = 38 log.Println("前age=" , age) age.ChangeAge() log.Println("后age=" , age) var age2 Age = 38 log.Println("前age2=" , age2) age2.ChangeAgeByPointer() log.Println("后age2=" , age2) var ip IP = []byte ("123" ) log.Printf("前ip=%s" , ip) ip.ChangeIP() log.Printf("后ip=%s" , ip) var ip2 IP = []byte ("123" ) log.Printf("前ip2=%s" , ip2) ip2.ChangeIPByPointer() log.Printf("后ip2=%s" , ip2) cat := Pet{ Name: "kk" , Hobby: []string {"cookies" , "fishes" }, } log.Printf("前:%#v" , cat) cat.ChangePetValue("kitten" , []string {"meat" }) log.Printf("后:%#v" , cat) log.Printf("指针前:%#v" , cat) cat.ChangePetValueByPointer("kitten" , []string {"meat" }) log.Printf("指针后:%#v" , cat) }
04. 嵌入类型Go语言通过类型嵌套的方式来复用代码,当多个结构类型相互嵌套时,外部类型会复用内部类型的代码。
由于内部类型的标识符会提升到外部类型中,所以内部类型实现的字段,方法和接口在外部类型中也能直接访问到。
当外部类型需要实现一个和内部类型一样的方法或接口时,只需要给外部类型重新绑定方法或实现接口即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 package main import "log" type user struct { name string phone string } func (u *user) Call() { log.Printf("Call user %s<%s>" , u.name, u.phone) } type Admin struct { user level string } func (ad *Admin) Call() { log.Printf("Call admin %s<%s>" , ad.name, ad.phone) } type notifier interface { notify() } func (u *user) notify() { log.Printf("Sending a message to user %s<%s>" , u.name, u.phone) } func sendNotification (n notifier) { n.notify() } func (ad *Admin) notify() { log.Printf("Sending a message to ADMIN %s<%s>" , ad.name, ad.phone) } func main () { ad := Admin{ user: user{ name: "Jack" , phone: "17688888888" , }, level: "super" , } ad.user.Call() ad.Call() log.Println(ad.name, ad.phone) ad.Call() ad.user.Call() sendNotification(&ad) ad.notify() ad.user.notify() sendNotification(&ad) ad.notify() ad.user.notify() }
**05.**类型实现接口 Go语言中,接口是用来定义行为的类型,这些被定义的行为不由接口直接实现,而是通过方法由用户定义的类型实现。
如果用户定义的类型实现了某个接口里的一组方法,那么用户定义的这个类型值,就可以赋值给该接口值,此时用户定义的类型称为实体类型。
而用户定义的类型想要实现一个接口,需要遵循一些规则,这些规则使用方法集来进行定义。
从类型实现方法的接收者角度来看,可以描述为以下表格。
方法接收者
类型值或类型值的指针
(t T)
T and *T
(t *T)
*T
表示当类型的方法为指针接收者时,只有类型值的指针,才能实现接口。
如果类型的方法为值接收者,那么类型值还是类型值的指针都能够实现对应的接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 package mainimport "log" type notifier interface { notify() } type user struct { name string phone string } func (u *user) notify() { log.Println("Send user a text" ) } type Admin struct { user level string } func (ad Admin) notify() { log.Println("Send admin a message" ) } func sendNotification (n notifier) { n.notify() } func main () { u := user{ name: "Jack" , phone: "17688888888" , } var n notifier = &u n.notify() ad := Admin{ user: user{"Jack" , "17688888888" }, level: "super" , } var n2 notifier = ad n2.notify() var n3 notifier = &ad n3.notify() sendNotification(n) sendNotification(n3) }