Golang数据类型(结构体)

结构体重要概念

在之前学过的数据类型中,数组与切片,只能存储同一类型的变量。若要存储多个类型的变量,就需要用到结构体,它是将多个任意类型的变量组合在一起的聚合数据类型。

每个变量都成为该结构体的成员变量。

可以理解为 Go语言 的结构体struct和其他语言的class有相等的地位,但是Go语言放弃大量面向对象的特性,所有的Go语言类型除了指针类型外,都可以有自己的方法,提高了可扩展性。

在 Go 语言中没有没有 class 类的概念,只有 struct 结构体的概念,因此也没有继承

定义结构体

type 结构体名 struct {
    属性名   属性类型
    属性名   属性类型
    ...
}

比如我要定义一个可以存储个人资料名为 Profile 的结构体,可以这么写

func main() {
	type Profile struct {
		name   string
		age    int
		gender string
		mother *Profile // 指针
		father *Profile // 指针
	}
}

若相邻的属性(字段)是相同类型,可以合并写在一起

func main() {
	type Profile struct {
		name,gender   string
		age    int
		mother *Profile // 指针
		father *Profile // 指针
	}
}

通过结构体可以定义一个组合字面量

不过结构体有几个细节需要注意:

规则一:当最后一个字段和结果不在同一行时,, 不可省略。

xm := Profile{
    name: "小明",
    age: 18,
    gender: "male",
}

反之,不在同一行,就可以省略。

xm := Profile{
    name: "小明",
    age: 18,
    gender: "male"}

规则二:字段名要不全写,要不全不写,不能有的写,有的不写。

例如下面这种写法是会报 mixture of field:value and value initializers 错误的

xm := Profile{
    name: "小明",
    18,
    "male",
}

规则三:初始化结构体,并不一定要所有字段都赋值,未被赋值的字段,会自动赋值为其类型的零值。

xm := Profile{name: "小明"}
fmt.Println(xm.age)
// output: 0

但要注意的是,只有通过指定字段名才可以赋值部分字段。

若你没有指定字段名,像这样

xm := Profile{"小明"}

在编译的时候,是会直接报错的

$ go run demo.go
# command-line-arguments
./demo.go:12:16: too few values in Profile literal

绑定方法

在 Go 语言中,我们无法在结构体内定义方法,那如何给一个结构体定义方法呢,答案是可以使用组合函数的方式来定义结构体方法。它和普通函数的定义方式有些不一样,比如下面这个方法

func (profile Profile) FmtProfile(addAge int) {
	profile.age = profile.age + addAge
	fmt.Printf("name:%v,age:%d\n", profile.name, profile.age)
}

其中FmtProfile 是方法名,而(person Profile) :表示将 FmtProfile 方法与 Profile 的实例绑定。我们把 Profile 称为方法的接收者,而 person 表示实例本身,它相当于 Python 中的 self或PHP中的$this,在方法内可以使用 person.属性名 的方法来访问实例属性。

完整代码:

import (
"example.com/m/pagkage/linux"
)
func main() {
	linux.ProcessStructDemo1()
}
type Profile struct {
	name   string
	age    int
	mother *Profile
	father *Profile
}

func (profile Profile) FmtProfile(addAge int) {
	profile.age = profile.age + addAge
	fmt.Printf("name:%v,age:%d\n", profile.name, profile.age)
}
func ProcessStructDemo1() {
	myself := Profile{
		name: "Linrux",
		age:  10,
	}
	myself.FmtProfile(5)
}

输出:

方法的参数传递方式

当你想要在方法内改变实例的属性的时候,必须使用指针做为方法的接收者。

代码:

type Profile struct {
	name   string
	age    int
	mother *Profile
	father *Profile
}

// FmtProfile 重点在这个* 使用地址传递
func (profile *Profile) FmtProfile(addAge int) {
	profile.age = profile.age + addAge
	fmt.Printf("name:%v,age:%d\n", profile.name, profile.age)
}
func ProcessStructDemo1() {
	myself := Profile{
		name: "Linrux",
		age:  10,
	}
	myself.FmtProfile(5)
	fmt.Println(myself.age)
}

可以看到修改已生效:

至此,我们知道了两种定义方法的方式:

  • 以值做为方法接收者
  • 以指针做为方法接收者

那我们如何进行选择呢?以下几种情况,应当直接使用指针做为方法的接收者。

  1. 你需要在方法内部改变结构体内容的时候
  2. 出于性能的问题,当结构体过大的时候

有些情况下,以值或指针做为接收者都可以,但是考虑到代码一致性,建议都使用指针做为接收者。

结构体实现 “继承”

为什么标题的继承,加了双引号,因为Go 语言本身并不支持继承

但我们可以使用组合的方法,实现类似继承的效果。

在生活中,组合的例子非常多,比如一台电脑,是由机身外壳,主板,CPU,内存等零部件组合在一起,最后才有了我们用的电脑。

同样的,在 Go 语言中,把一个结构体嵌入到另一个结构体的方法,称之为组合。

现在这里有一个表示公司(addRes)的结构体,还有一个表示用户(Profile)的结构体。

type Profile struct {
	name   string
	age    int
	mother *Profile
}
type addRes struct {
	companyName string
	companyAddr string
}

若要将公司信息与用户关联起来,一般都会想到将 addRes 结构体的内容照抄到 Profile里。

type mergePro struct {
	name   string
	age    int
	mother *Profile
	companyName string
	companyAddr string
}

虽然在实现上并没有什么问题,但在你对同一公司的多个用户初始化的时候,都得重复初始化相同的公司信息,这做得并不好,借鉴继承的思想,我们可以将公司的属性都“继承”过来。

但是在 Go 中没有类的概念,只有组合,你可以将 addRes 这个 结构体嵌入到 Profile 中,做为 Profile 的一个匿名字段,Profile 就直接拥有了 addRes 的所有属性了。

type Profile struct {
	name   string
	age    int
	mother *Profile
	addRes //嵌入 匿名字段
}

完整代码:

type Profile struct {
	name   string
	age    int
	mother *Profile
	addRes
}
type addRes struct {
	companyName string
	companyAddr string
}
func (profile *Profile) FmtProfile(addAge int) {
	profile.age = profile.age + addAge
}
func ProcessStructDemo1() {
	addres := addRes{
		companyName: "Tencent",
		companyAddr: "深圳市南山区",
	}
	myself := Profile{
		name:   "Linrux",
		age:    10,
		addRes: addres,
	}
	myself.FmtProfile(0)
	//使用addRes去访问属性
	fmt.Printf("name:%s  age:%d  公司:%s 公司地址:%s", myself.name, myself.age, myself.addRes.companyName, myself.addRes.companyAddr)
	//跳过addRes去访问属性
	fmt.Printf("name:%s  age:%d  公司:%s 公司地址:%s", myself.name, myself.age, myself.companyName, myself.companyAddr)
}

输出结果如下:

可以看出

myself.addRes.companyName 和 myself.companyName的结果是一样的
不过一般都推荐使用myself.addRes.companyName这种 因为比较清晰明了是哪个结构体的属性

三种实例化方法

第一种:正常实例化

x := Profile{
		name: "linrux",
		age:  20,
	}

第二种:使用 new

x := new(Profile)
	x.name = "linrux"
	fmt.Println(x)
	(*x).name = "linrux1"
	fmt.Println(x)

第三种:使用 &

var x *Profile = &Profile{}
	x.name = "linrux"
	fmt.Println(x)
	(*x).name = "linrux1"
	fmt.Println(x)

 

版权声明:
作者:linrux
链接:https://www.tot7.cn/technology/go/540.html
来源:阿信博客
文章版权归作者所有,未经允许请勿转载。

THE END
分享
二维码
< <上一篇
下一篇>>
文章目录
关闭
目 录