引言
本篇主要想总结一下关于结构体和接口的一些原理与说明。
结构体
在golang中,结构体属于一种自定义的类型。当用户声明一个新类型时,这个声明就给编译器提供了一个框架,告知必要的内存大小和表示信息。声明后的类型与内置类型运作上类似,并且能继承内置类型,直接完成定义。
结构体构建
针对上面的意思,我们可以定义两个结构体调用:
package main
import "fmt"
type user struct {
name string
email string
age int
privileged bool
}
type person_age int64
func main() {
user1 := person_age(22)
user2 := user{
name: "Tim",
email: "Tim@163.com",
age: 10,
privileged: true,
}
// user3 := user{"Lisa","lisa@163.com",123, true}
fmt.Println("user1:",user1) // user1: 22
fmt.Println("user2:",user2) // user2: {Tim Tim@163.com 10 true}
}
上述代码中,我们初始化了两个结构体,第一个声明了四个字段对应的类型,后续的调用要按照这种规则进行,当然也能不对其做赋值,那么打印出的结果,就是字段类型对应的0值。而第二个结构体,是直接使用了int64来做person_age,同样,如果我们不给它值,那么它的打印结果为整形0,而结构体的调用可以用{} 以key-value的形式赋值,也能用第二种方式写到一行。
结构体的组成并不是只能用基本的内置类型,还可以写得复杂一点,使用结构体嵌套:
type user struct {
name string
email string
age int
privileged bool
}
type admin struct {
person user
level string
}
func main() {
fred := admin{
person : user{
name: "Tim",
email: "Tim@163.com",
age: 10,
privileged: true,
},
level:"super",
}
fmt.Println("fred:",fred) // fred: {{Tim Tim@163.com 10 true} super}
}
上述即用结构体嵌套给fred进行了赋值,但没有对person初始化,因为结构体算一种单独的类型,我们可以看如下实例:
type foo int
func main() {
var dur foo
dur = int(0)
fmt.Println(dur)
}
上述代码会报错为cannot use int(0) (type int) as type foo in assignment,因为foo已经是继承自int的一种foo类型,如果要初始化,需要将int(0)改成foo(0)。
那么依照上面的内容可以写一个结构体转json或者json转结构体的例子:
type person struct {
Name string
Age int
Sex string
}
//struct to json
func structToJson()string{
pet := person{
Name:"qqq",
Age :18,
Sex:"male",
}
data,err := json.Marshal(pet)
if err !=nil{
fmt.Println("json.marshal failed.error=",err)
}
return string(data)
}
//json to struct
func jsonToStruct(str string){
var pet person
err := json.Unmarshal([]byte(str),&pet)
if err != nil{
panic(err)
}
fmt.Println(pet)
}
func main() {
// p1 := person{"James", 18, "male"}
// p2 := person{"Miss", 18, "famale"}
str := structToJson()
jsonToStruct(str) // {qqq 18 male}
}
方法
方法能给用户定义的类型添加新的行为。方法实际上也是函数,只是在声明时,在关键字func和方法名之间增加了一个参数,这个叫做接收者。如果一个函数有接收者,这个函数就被称为方法。可以看下面的例子:
type person struct {
first string
last string
age int
}
func (p person) fullName() string {
return p.first + p.last
}
func main() {
p1 := person{"James", "Bond", 20}
p2 := person{"Miss", "Moneypenny", 18}
fmt.Println(p1.fullName()) // JamesBond
fmt.Println(p2.fullName()) // MissMoneypenny
}
一般方法的接收者分为值接收者和指针接收者,前者就是上述代码中使用的(p person),但这样会存在一个问题,当我们在方法中调用这个person类型的时候,内部会为其copy一个副本。可如果传递的数据很大或者我们想对立面的值进行修改,那我们就可以定义指针接收者,下面为一个案例:
type user struct {
name string
email string
}
// notify 使用值接收者实现了一个方法
func (u user) notify() {
fmt.Printf("Sending User Email To %s<%s>\n",u.name,u.email)
}
// changeEmail 使用指针接收者实现了一个方法
func (u *user) changeEmail(email string) {
u.email = email
}
func main() {
user1 := user{"小白","xiaobai@email.com"}
user1.notify()
user2 := &user{"小黑","xiaohei@email.com"}
user2.notify()
user1.changeEmail("xiaobai@email.com")
user1.notify()
user2.changeEmail("xiaohei@email.com")
user2.notify()
/*
Sending User Email To 小白<xiaobai@email.com>
Sending User Email To 小黑<xiaohei@email.com>
Sending User Email To 小白<xiaobai@email.com>
Sending User Email To 小黑<xiaohei@email.com>
*/
}
user2初始化了一个指针变量,这里存在一个语法糖,golang编译器会将user2.notify()转换成(*user2).notify(),这也将不再是复制副本后的值的调用。而changeEmail使用指针接收者实现了一个方法,顺序是先得到这个指针,这样这个指针就能够匹配方法的接收者类型,再进行调用。
//
//
接口
接口是一组仅包含方法名、参数、返回值的未具体实现的方法的集合。在golang里,如果一个类型实现了某个接口,所有使用这个接口的地方,都可以支持这种类型的值。而多态是指代码可以根据类型的具体实现采取不同行为的能力。
接口构建
来看如下例子:
type notifier interface {
notify()
}
type user struct {
name string
email string
}
// notify 使用值接收者实现了一个方法
func (u *user) notify() {
fmt.Printf("Sending User Email To %s<%s>\n",u.name,u.email)
}
// changeEmail 使用指针接收者实现了一个方法
func (u *user) changeEmail(email string) {
u.email = email
}
func main() {
u := user{"xiaobai","xiaobai@email.com"}
sendNotification(u)
// cannot use u (type user) as type notifier in argument to sendNotification:
// user does not implement notifier (notify method has pointer receiver)
}
func sendNotification(n notifier) {
n.notify()
}
notifier加关键字interface即为实现接口,但是它是通过user类型的指针接收者实现的。所以,就会出现cannot use u (type user) as type notifier in argument to sendNotification:的错误,这和上面方法中的指针接收者以及值接收者的调用不同,如果接口是指针实现的,那么它接收的值也应该是指针,这叫做方法集。
方法集
方法集定义了接口的接受规则,具体的规则我们可以从两个角度去看,分别是方法实现和方法接收者,如下:
Values Methods Receivers
-----------------------------------------------
T (t T)
*T (t T) and (t *T)
Methods Receivers Values
-----------------------------------------------
(t T) T and *T
(t *T) *T
上述第一种方式以方法实现角度来看,T 类型的值的方法集只包含值接收者声明的方法。而指向 T 类型的指针的方法集既包含值接收者声明的方法,也包含指针接收者声明的方法。
而以方法接收者来看,如果使用指针接收者来实现一个接口,那么只有指向那个类型的指针才能够实现对应的接口。如果使用值接收者来实现一个接口,那么那个类型的值和指针都能够实现对应的接口。
那么开始的例子就可以修改为:
func main() {
u := &user{"xiaobai","xiaobai@email.com"}
sendNotification(u) // Sending User Email To xiaobai<xiaobai@email.com>
// sendNotification(&u)
}
多态
我们声明了user和admin两种类型,用同样的形式实现了 notifier 接口,现在,有两个实体类型实现了 notifier接口。
type notifier interface {
notify()
}
type user struct {
name string
email string
}
// notify 使用值接收者实现了一个方法
func (u *user) notify() {
fmt.Printf("Sending User Email To %s<%s>\n",u.name,u.email)
}
type admin struct {
name string
email string
}
// notify使用指针接收者实现了notifier接口
func (a *admin) notify() {
fmt.Printf("Sending User Email To %s<%s>\n",a.name,a.email)
}
func main() {
user1 := &user{"xiaobai","xiaobai@email.com"}
sendNotification(user1) // Sending User Email To xiaobai<xiaobai@email.com>
// sendNotification(&u)
user2 := admin{"xiaohei","xiaohei@email.com"}
sendNotification(&user2) // Sending User Email To xiaohei<xiaohei@email.com>
}
func sendNotification(n notifier) {
n.notify()
}
参考文献:
- go语言实战