golang结构体与接口笔记

引言

本篇主要想总结一下关于结构体和接口的一些原理与说明。

结构体

在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()
}

参考文献:

  1. go语言实战
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

submarineas

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值