百无一用程序员

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

结构体

结构体

定义

结构体定义的是数据,与C语言的结构体相同。

结构体定义如下:

type structTypeName struct {
   member definition;
   member definition;
   ...
   member definition;
}

结构体变量可以有两种声明方式(使用KV方式时,可以仅初始化部分成员):

varName := structTypeName {value1, value2...valuen}
varName := structTypeName { key1: value1, key2: value2..., keyn: valuen}

还有另外一种,通过new来创建特定对象:

varName := new (structTypeName)	// 使用默认值初始化

代码范例:

type student struct {
	name	string
	age		int
	grage	int
}

func main() {
	s1 := student{"wang", 10, 2}
	s2 := new(student)
	// s3 := make(student)  	// cannot make type student

	s2.name = "li";
	s2.age = 11;

	fmt.Println(s1)
	fmt.Println(s2)
}

内嵌结构体

内嵌结构体是指将某个结构体,潜入到其他的结构体中,类似于C语言中的struct内部定义struct。
Go语言中不能在struct内部嵌套,而是需要将内嵌的结构体独立声明。

范例,我们需要在student结构体中内嵌other结构体:

type other struct {		// 声明other结构体
	addr string
}

type student struct {
	name	string
	age		int
	grage	int
	other				// 内嵌other结构体
}

如果名字没有冲突,可以直接使用内嵌结构体的字段

s1 := student{"wang", 10, 2, other{"JS"}}	// 初始化时需要带着内嵌结构体一起
s1.addr = "JJ"			// 直接使用内嵌结构体的成员变量名
s1.other.addr = "AA"	// 访问特定类型的内嵌结构体成员变量名,名字冲突时使用

这里顺便将内嵌组合区分下,如果使用组合方式,则student的定义将会类似如下:

type student struct {
	name	string
	age		int
	grage	int
	etc		other	// 组合other对象
}

方法

概念

在 Go 语言中,结构体与C语言的结构体等价,表示纯数据,那么Go是否支持面向对象呢?如何实现在结构体上实现方法呢?

实际上,Go的方法是作用在接收者上的一个函数,接收者是某种特定类型的变量,所以我们可以人为方法是一种特殊类型的函数,Go的这种绑定与Lisp语言非常相似。

Go中方法接收者类型几乎可以是任何类型,任何类型都可以有方法,甚至可以是函数类型。接收者不能是以下类型:

  1. 接口类型,接口是一个抽象定义,而方法却是具体实现,实现无法绑定在抽象定义上;
  2. 指针类型。

类型+方法构成了传统面向对象语言中的一个,在 Go 中,类型的代码和绑定在它上面的方法的代码必须位于同一个包中,但可以存在于不同的源文件中(这个也就限制了您不能给int类型增加方法)。

Go语言中不支持方法重载,即对于一个类型只能有一个给定名称的方法,但是具有同样名字的方法可以在 2 个或多个不同的接收者类型上存在。

方法定义:

func (recv receiverType) methodName(paraList) (returnValueList) { ... }

范例:

func (a *denseMatrix) Add(b Matrix) Matrix
func (a *sparseMatrix) Add(b Matrix) Matrix

方法和函数的区别

上文提及到,方法是一种特殊的函数,实际上我们可以人为仅仅是格式有差异。

例如对于变量rcv

  • 使用函数时,将变量作为参数传递给函数,例如FuncCall(recv)
  • 使用方法时,就像是调用变量的方法,例如rcv.Method()

Go的这种方法与结构独立的方式,很好的解决了耦合问题。

以指针或者值作为接收者

以值为接收者时,调用方法时会将接收者的值传递进去,所以在方法内部修改接收者时,实际上修改的时传递进来的接收者副本,并不会影响接收者本身。

以指针为接收者时,在方法内部修改接收者数据时,直接作用在接收者上。

以前面的student为例,追加2个方法:

func (s *student) funcPtr() {
	s.name = "ptrName"
	fmt.Printf("funcPtr: Change student name to ptrName\n")
}

func (s student) funcObj() {
	s.name = "objName"
	fmt.Printf("funcObj: Change student name to objName\n")
}

测试代码:

s1 := student{"wang", 10, 2, other{"JS"}}

s1.funcObj()
fmt.Println(s1)

s1.funcPtr()
fmt.Println(s1)

输出结果:

funcObj: Change student name to objName
{wang 10 2 {JS}}
funcPtr: Change student name to ptrName
{ptrName 10 2 {JS}}

结论:使用指针方式传递时,可以修改接收者的数据。

内嵌类型的方法

内嵌类型的绑定方法后,外层结构相当于自动拥有了该方法。

范例,内嵌类型Point绑定了Abs方法,此时NamedPoint类型将自动拥有该方法:

type Point struct {
	x, y float64
}

func (p *Point) Abs() float64 {
	return math.Sqrt(p.x*p.x + p.y*p.y)
}

type NamedPoint struct {
	Point
	name string
}

测试代码:

n := NamedPoint{Point{3, 4}, "Pythagoras"}
fmt.Println(n.Abs()) // 输出“5”

如果您在外层类中实现了同名方法,那么外层类型的方法将覆盖内层类型的方法

如果一个类型包含了多个子类型,就相当于该类型继承了多个子类型的方法,等同于C++中的多重继承。

方法调用规则

Go语言约定如下方法调用规则:

  • 类型 T 的可调用方法集包含接受者为 *T 或 T 的所有方法集
  • 类型 *T 的可调用方法集包含接受者为 *T 的所有方法
  • 类型 *T 的可调用方法集不包含接受者为 T 的方法

也就是,方法应绑定到*T的类型上

参考

参考书籍:https://github.com/unknwon/the-way-to-go_ZH_CN/blob/master/eBook/directory.md

posted on 2021-02-28 20:17  psbec  阅读(163)  评论(0)    收藏  举报