Golang 语言基础之七: struct, method

Xiaohui Wang · · 2856 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

Golang 语言基础之七: struct, method

Golang 语言基础系列:

很多现代语言都支持面向对象编程模式,C++,Python,Ruby,C#,F# 等,其中 Python,Ruby,F# 甚至加入了很多对 函数式编程 的支持。Golang 作为一门相对来说很年轻的语言,通过对闭包的支持,也可以用于实践函数式编程模式,这有便于我们写出抽象程度很高的代码,而且语言本身的灵活性很高。

至于对面向对象编程模式的支持,Golang 中没有 class 的概念,虽然有 structinterface 类型,但是不支持像 C/C++ 语言中传统方式的 继承,实际上 Golang 对 OOP 特性 的直接支持就只有 封装。我们可以使用 struct 通过一些语法来模拟 继承关系。下面关于 struct 的用法实例中会给出具体的例子。而 多态,虽然 Golang 是静态类型的语言,但是 structinterface 配合 Golang 中 method 的概念,使用时却很类似于动态语言的 鸭子类型多态 的实现, Golang 通过 structinterface 的方法集及其规则来保证可行性。

官方文档的 FAQ 中有一个问题:Is Go an object-oriented language?,Google 是这么回答的:

Yes and no. Although Go has types and methods and allows an object-oriented style of programming, there is no type hierarchy. The concept of “interface” in Go provides a different approach that we believe is easy to use and in some ways more general. There are also ways to embed types in other types to provide something analogous—but not identical—to subclassing. Moreover, methods in Go are more general than in C++ or Java: they can be defined for any sort of data, even built-in types such as plain, “unboxed” integers. They are not restricted to structs (classes).

Struct, Method 和 Method Sets

先看看 官方定义

A struct is a sequence of named elements, called fields, each of which has a name and a type. Field names may be specified explicitly (IdentifierList) or implicitly (AnonymousField). Within a struct, non-blank field names must be unique.

也就是说:一个 struct 类型由一系列被称为 属性 的命名元素组成。每个 属性属性名属性类型 组成。属性名 可以被显式地指定,也可以是匿名的。struct 类型中 非补位 的所有属性(属性名非下划线 _)的 属性名 不能重复。

匿名属性匿名属性是只包含 属性类型 的 属性,也被称为 嵌入属性 或者 一个类型在这个 struct 中的嵌入。这里的 类型 可以是指针类型,但不能是指针的指针,而且一个 struct 类型不能同时包含一个类型本身以及指向该类型的指针,因为他们在这个 struct 类型中的 属性名 是相同的,都是不包含 package name 的类型名。看一下官网的说明

// A struct with four anonymous fields of type T1, *T2, P.T3 and *P.T4
struct {
	T1        // field name is T1
	*T2       // field name is T2
	P.T3      // field name is T3
	*P.T4     // field name is T4
	x, y int  // field names are x and y
}

一个 struct 类型的 匿名属性 的方法可以通过该 struct 对象直接调用。这个特性可以让我们实现类似于传统 OOP 中的 继承 特性。调用方法时 Golang 会从外层向内层寻找,所以如果想 override 一个 匿名属性 的方法,可以给这个 struct 类型定义一个具有和其具有相同函数签名的方法,这样调用时 Golang 在最外层就能找到符合条件的方法并调用。

方法集:对于一个 struct 类型 S 和 一个类型 T 而言,如果 T 会作为 S 的一个 匿名属性,S 的方法集(method sets)遵循下面的规则

  • If S contains an anonymous field T, the method sets of S and *S both include promoted methods with receiver T. The method set of *S also includes promoted methods with receiver *T.
  • If S contains an anonymous field *T, the method sets of S and *S both include promoted methods with receiver T or *T.

我们还是用用法实例来理解一下,使用实例如下:

package main

import "fmt"

// 定义空 `struct` 类型
type EmptyStruct struct{}

// 定义含有 `tag` 的 `struct` 类型,之后可以用 reflection 对有 `tag` 的属性进行访问。
type Job struct {
	title string "JD"
	rank  int
}

// 定义不含有 `tag`、其他属性和 Job `struct` 类型完全相同的另一个 `struct` 类型
type JobWithoutTag struct {
	title string
	rank  int
}

// 定义 `struct` 类型 Job 的方法,由于方法体中无需用到 Job 对象,那么对象名可以省略
func (Job) Help() {
	fmt.Println("Use \"j.What()\" to get job detail.")
}

// 定义 `struct` 类型 Job 的方法,由于方法体中用到 Job 对象,那么对象名不可以省略
func (job Job) What() {
	fmt.Printf("Job Detail:\n\ttitle: %s\n\trank: %d\n", job.title, job.rank)
}

// 定义指向 `struct` 类型 Job 的指针的方法
func (jobP *Job) SetRank(newRank int) {
	jobP.rank = newRank
	fmt.Println("Rank updated, new value is: ", jobP.rank)
}

// 定义 Employee 结构类型,其含有 `补位属性`、匿名 `struct` 类型属性、匿名 `struct` 类型而显式命名的属性
// 同一类型的属性可以放在一行
// 从效果看,Employee `继承` 了 Job,这和传统 `继承` 区别很大,注意上面提到的 `匿名属性` 的方法集规则
// 在其他 OOP 语言比如 C++ 中,我们不会做这样的抽象设计,因为两者没有什么所谓的 `继承关系`
type Employee struct {
	_                   int
	age                 int
	firstName, lastName string
	pack                struct {
		salary int
		stock  int
	}
	*Job
}

// 定义 `struct` 类型 Employee 的方法,由于其匿名属性 Job 也有同名方法,根据上面提到的规则
// Employee 的 Help 方法会 override 其匿名属性 Job 的同名方法。
func (Employee) Help() {
	fmt.Println("Use \"e.Job.What()\" to get job detail of current employee.")
}

// 定义 `struct` 类型,这个类型唯一的属性为空接口类型,而任何类型都实现了空接口
// 所以 OnlyInterface 可以实例化属性为任何类型的 `struct` 对象
type OnlyInterface struct {
	f interface{}
}

func main() {

	// 检查空 `struct` 类型
	varEmptyStruct := EmptyStruct{}
	fmt.Println("varEmptyStruct: ", varEmptyStruct)

	// 检查 `struct` 类型对象的默认值
	var varJob0 Job
	fmt.Println("varJob0 = ", varJob0)

	// 对 `struct` 类型对象进行顺序初始化
	varJob1 := Job{
		"CEO",
		99,
	}
	fmt.Println("varJob1 = ", varJob1)

	// 对 `struct` 类型对象按照属性名依次初始化,可以和属性定义的顺序不一致
	varJob2 := Job{
		rank:  99,
		title: "CEO",
	}
	fmt.Println("varJob2 = ", varJob2)

	// 使用属性名进行初始化可以选择性初始化个别属性,其他属性初始化为它们的默认值
	varJob3 := Job{
		title: "COO",
	}
	fmt.Println("varJob2 = ", varJob3)

	// Employee  `struct` 类型对象的默认值
	varEmployee0 := Employee{}
	fmt.Println("varEmployee = ", varEmployee0)

	// 初始化 Employee `struct` 类型对象,匿名属性需要在初始化其他属性后使用对象名显式赋值
	varEmployee1 := Employee{
		age:       50,
		firstName: "Jack",
		lastName:  "Ma",
	}
	varEmployee1.pack.salary = 100000000
	varEmployee1.pack.stock = 1000000
	varJob4 := Job{
		"Founder",
		100,
	}
	varEmployee1.Job = &varJob4
	fmt.Println("varEmployee = ", varEmployee1)
	fmt.Println("varEmployee.Job = ", *(varEmployee1.Job))

	// 注意 Employee 的 Help 方法 `override` 了其属性 Job 的 Help 方法
	varEmployee1.Help()
	// 调用 Job 的 Help 方法需要显式调用
	varEmployee1.Job.Help()
	// 使用 Employee 对象可以直接调用其属性 Job 的方法 What
	varEmployee1.What()
	// Employee 对象的属性 Job 的类型是 `*Job`,所以可以修改 Job 的内容
	// 如果其类型是 Job 本身,由于获取到的 `struct` 对象是其原始值的拷贝,修改不会生效
	varEmployee1.Job.SetRank(99)
	fmt.Println("After job rank change, varEmployee.Job = ", *(varEmployee1.Job))

	// `struct` 类型对象是值类型,可以进行比较运算
	varEmployee2 := Employee{
		age:       50,
		firstName: "Jack",
		lastName:  "Ma",
	}
	varEmployee2.pack.salary = 100000000
	varEmployee2.pack.stock = 1000000
	varEmployee2.Job = &varJob4
	if fmt.Println("Cmpare 2 struct object."); varEmployee2 == varEmployee1 {
		fmt.Println("varEmployee2 equals varEmployee1.")
	}

	// Tag 是 `struct` 类型的一部分,下面 varJob5 无法赋值给 varJob4
	// varJob5 := JobWithoutTag{
	// 	"Founder",
	// 	100,
	// }
	// varJob4 = varJob5

	// 属性类型为空接口的 `struct` 类型的使用
	varOnlyInterface := OnlyInterface{
		f: 100,
	}
	fmt.Println("OnlyInterface with int: ", varOnlyInterface)
	varOnlyInterface = OnlyInterface{
		f: "I like Golang.",
	}
	fmt.Println("OnlyInterface with string: ", varOnlyInterface)

}

将上面的代码存入源文件 struct.go 并使用 go run struct.go 可以看到下面的输入:

varEmptyStruct:  {}
varJob0 =  { 0}
varJob1 =  {CEO 99}
varJob2 =  {CEO 99}
varJob2 =  {COO 0}
varEmployee =  {0 0   {0 0} <nil>}
varEmployee =  {0 50 Jack Ma {100000000 1000000} 0xc20801e0c0}
varEmployee.Job =  {Founder 100}
Use "e.Job.What()" to get job detail of current employee.
Use "j.What()" to get job detail.
Job Detail:
	title: Founder
	rank: 100
Rank updated, new value is:  99
After job rank change, varEmployee.Job =  {Founder 99}
Cmpare 2 struct object.
varEmployee2 equals varEmployee1.
OnlyInterface with int:  {100}
OnlyInterface with string:  {I like Golang.}

参考资料

-- EOF --

声明: 本文采用 BY-NC-SA 协议进行授权. 转载请注明转自: Golang 语言基础之七: struct, method


有疑问加站长微信联系(非本文作者)

本文来自:Ross's Page

感谢作者:Xiaohui Wang

查看原文:Golang 语言基础之七: struct, method

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

2856 次点击  
加入收藏 微博
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传