Go(二)结构体

682 阅读10分钟

这是我参与11月更文挑战的第7天,活动详情查看:2021最后一次更文挑战

作者:lomtom

个人网站:lomtom.top

个人公众号:博思奥园

你的支持就是我最大的动力。

Go系列:

  1. Go(一)基础入门
  2. Go(二)结构体
  3. Go(三)Go配置文件

在Go中没有类的概念,取而代之,我觉得Go中的结构体却在充当着类的角色。

但是在Go中通过结构体的内嵌再配合接口比面向对象具有更高的扩展性和灵活性。

比面向对象具有更高的扩展性和灵活性。

1 类型别名和自定义类型

1.1 自定义类型

和java一样,在Go语言中有一些基本的数据类型,如string、整型、浮点型、布尔等数据类型

在Go语言中可以使用type关键字来定义自定义类型。

自定义类型是定义了一个全新的类型。我们可以基于内置的基本类型定义,也可以通过struct定义。例如:

//将MyInt定义为int类型
type MyInt int

通过Type关键字的定义,MyInt就是一种新的类型,它具有int的特性。

1.2 类型别名

类型别名是Go1.9版本添加的新功能。

类型别名规定:TypeAlias只是Type的别名,本质上TypeAlias与Type是同一个类型。就像一个孩子小时候有小名、乳名,上学后用学名,英语老师又会给他起英文名,但这些名字都指的是他本人。

type TypeAlias = Type

我们之前见过的rune和byte就是类型别名,他们的定义如下:

type byte = uint8
type rune = int32

1.3类型定义和类型别名的区别

类型别名与类型定义表面上看只有一个等号的差异,我们通过下面的这段代码来理解它们之间的区别。

//类型定义
type NewInt int

//类型别名
type MyInt = int

func main() {
    var a NewInt
    var b MyInt

    fmt.Printf("type of a:%T\n", a) //type of a:main.NewInt
    fmt.Printf("type of b:%T\n", b) //type of b:int
}

结果显示a的类型是main.NewInt,表示main包下定义的NewInt类型。b的类型是int。MyInt类型只会在代码中存在,编译完成时并不会有MyInt类型。

2 结构体

Go语言中的基础数据类型可以表示一些事物的基本属性,但是当我们想表达一个事物的全部或部分属性时,这时候再用单一的基本数据类型明显就无法满足需求了,

Go语言提供了一种自定义数据类型,可以封装多个基本数据类型,这种数据类型叫结构体,英文名称struct。 也就是我们可以通过struct来定义自己的类型了。

Go语言中通过struct来实现面向对象。

2.1 结构体的定义

同样,使用type和struct关键字也可以用来定义结构体,具体代码格式如下:

type 类型名 struct {
    字段名 字段类型
    字段名 字段类型
    …
}

其中:

  1. 类型名:标识自定义结构体的名称,在同一个包内不能重复。
  2. 字段名:表示结构体字段名。结构体中的字段名必须唯一。
  3. 字段类型:表示结构体字段的具体类型。

举个例子,我们定义一个Article (文章)结构体,代码如下:

type Article struct {
    author string
    title string
    content string
    articleId int
}

同样类型的字段也可以写在一行,

type Article struct {
    author,title,content string
    articleId int
}

这样我们就拥有了一个Article 的自定义类型,它有author,title,content,articleId四个字段,分别表示作者、标题、内容和文章编号。

这样我们使用这个Article 结构体就能够很方便的在程序中表示和存储文章信息了。

语言内置的基础数据类型是用来描述一个值的,而结构体是用来描述一组值的。 比如一篇文章有作者、标题、内容和文章编号等,本质上是一种聚合型的数据类型

2.2 结构体实例化

只有当结构体实例化时,才会真正地分配内存。也就是必须实例化后才能使用结构体的字段。

结构体本身也是一种类型,我们可以像声明内置类型一样使用var关键字声明结构体类型。

var 结构体实例 结构体类型

如果需要访问结构体的字段就可以使用结构体名.属性名来访问该结构体的属性

2.3 基本实例化

type Article struct {
	author, title, content string
	articleId              int
}

func main() {
	var article Article
	article.author = "lomtom"
	article.title = "Go(二)结构体"
	article.content = "这是内容"
	article.articleId = 119995581
	fmt.Printf("%v", article)  // {lomtom Go(二)结构体 这是内容 119995581}
	fmt.Printf("%#v", article) // {author:"lomtom", title:"Go(二)结构体", content:"这是内容", articleId:119995581}
}

2.4 创建指针类型结构体

我们还可以通过使用new关键字对结构体进行实例化,得到的是结构体的地址。

实例化格式

var article = new(Article)
或者
var article *Article= new(Article)

获取属性值\设置属性值

article.author = "lomtom"
或者
(*article).author = "lomtom"

在这里就可以看得出go设计的巧妙,也同样是为了程序员使用方便,底层会对article.author = "lomtom"进行处理,会给 article加上 取值运算 (*article).author = "lomtom"

具体代码如下:

type Article struct {
	author, title, content string
	articleId              int
}

func main() {
	var article = new(Article)
	article.author = "lomtom"
	article.title = "Go(二)结构体"
	article.content = "这是内容"
	article.articleId = 119995581
	fmt.Printf("%v", article)  // &{lomtom Go(二)结构体 这是内容 119995581}
	fmt.Printf("%#v", article) // &{author:"lomtom", title:"Go(二)结构体", content:"这是内容", articleId:119995581}
}

从打印的结果中我们可以看出p2是一个结构体指针。

实际上,他等同于

type Article struct {
	author, title, content string
	articleId              int
}

func main() {
	var article *Article= new(Article)
	(*article).author = "lomtom"
	(*article).title = "Go(二)结构体"
	(*article).content = "这是内容"
	(*article).articleId = 119995581
	fmt.Printf("%v", article)  // &{lomtom Go(二)结构体 这是内容 119995581}
	fmt.Printf("%#v", article) // &{author:"lomtom", title:"Go(二)结构体", content:"这是内容", articleId:119995581}
}

2.5 取结构体的地址实例化

使用&对结构体进行取地址操作相当于对该结构体类型进行了一次new实例化操作。

type Article struct {
	author, title, content string
	articleId              int
}

func main() {
	var article *Article= &Article{}
	(*article).author = "lomtom"
	(*article).title = "Go(二)结构体"
	(*article).content = "这是内容"
	(*article).articleId = 119995581
	fmt.Printf("%v", article)  // &{lomtom Go(二)结构体 这是内容 119995581}
	fmt.Printf("%#v", article) // &{author:"lomtom", title:"Go(二)结构体", content:"这是内容", articleId:119995581}
}

对于结构体的赋值,可以在初始化时进行赋值:

var article =  &Article{
		"lomtom",
		"Go(二)结构体",
		"这是内容",
		119995581,
	}

var article =  &Article{
		author: "lomtom",
		title: "Go(二)结构体",
		content: "这是内容",
		articleId: 119995581,
	}

值得注意的是:

1.必须初始化结构体的所有字段。 2.初始值的填充顺序必须与字段在结构体中的声明顺序一致。 3.该方式不能和键值初始化方式混用。 4.属性名要么全部省略,要么全部保留

在使用上来说,使用new创建指针类型结构体和使用&是一样的

3 结构体特殊用法

3.1 构造函数

Go语言的结构体没有构造函数,我们可以自己实现。

例如,下方的代码就实现了一个Article的构造函数。 因为struct是值类型,如果结构体比较复杂的话,值拷贝性能开销会比较大,所以该构造函数返回的是结构体指针类型。

func newArticle(author, title, content string, articleId int) *Article {
	return &Article{
		author,title,content,articleId,
	}
}

调用构造函数

	article := newArticle("lomtom","Go(二)结构体","这是内容",119995581)
	fmt.Printf("%v", article)  // &{lomtom Go(二)结构体 这是内容 119995581}
	fmt.Printf("%#v", article) // &{author:"lomtom", title:"Go(二)结构体", content:"这是内容", articleId:119995581}

3.2 set和get方法

同样的,我们也可以实现结构体自己的set、get方法

type article struct {
	author, title, content string
	articleId              int
}

func (article *article) GetAuthor()  string {
	return article.author
}

func (article *article) SetAuthor(author string)  {
	article.author = author
}

使用

func main() {
	article := entity.NewArticle("lomtom","Go(二)结构体","这是内容",119995581)
	fmt.Printf("%v", article.GetAuthor()) // lomtom
	article.SetAuthor("小布丁")
	fmt.Printf("%v", article.GetAuthor()) // 小布丁
}

3.3 结构体嵌套使用

一个结构体中可以嵌套包含另一个结构体或结构体指针。

定义两个结构体,在Article 结构体中加入Type 属性

type Type struct {
	name string
	icon string
}

type Article struct {
	author, title, content string
	articleId              int
	myType                 Type
}

当然,在使用中我们可以省略属性名,而只需指定类型即可,例如

type Type struct {
	name string
	string
}

type Article struct {
	author, title, content string
	articleId              int
	Type
}

使用

func main() {
	article := &Article{
		"lomtom",
		"Go(二)结构体",
		"这是内容",
		119995581,
		Type{
			"Go",
			"icon-go",
		},
	}
	fmt.Printf("%v", article) // &{lomtom Go(二)结构体 这是内容 119995581 {Go icon-go}}
}

3.4 实现“继承”

Go语言中使用结构体也可以实现其他编程语言中面向对象的继承。

首先,定义一个结构体动物,有一个属性(feet),所有动物都由腿,并且定义一个动物的方法(getFeet)

type Animal  struct {
	feet int
}


func (a *Animal) getFeet() {
	fmt.Printf("有%v条腿" , a.feet)
}

定义一个结构体:狗,拥有一个名字,并且属性加上动物,通过嵌套匿名结构体实现继承,那么狗也可以使用动物的方法

type Dog struct {
	name string
	*Animal
}

使用

func main() {
	dog := &Dog{
		"旺财",
		&Animal{
			4,
		},
	}
	fmt.Println(dog.name) // 旺财
	dog.getFeet() // 有4条腿
}

另外定义另一个结构体,人类(高级动物),同样拥有一个名字,并且会说话

type Person struct {
	name string
	*Animal
}

func (a *Person) say() {
	fmt.Printf("我叫%v,我厉害吧,我会说话",a.name )
}

使用,那么人类也可以使用getFeet方法,除此之外,还能使用自己的方法say

func main() {
	person := &Person{"道科",&Animal{3}}
	fmt.Println(person.name) // 道科
	person.say() // 我叫道科,我厉害吧,我会说话
	person.getFeet() // 有3条腿
}

3.5 值传递/地址传递

在之前的实例化中,有通过一般方法进行实例化的。也有通过指针\地址进行实例化的,那么在Go中值传递和地址传递有什么区别呢?

同样的Article结构体

type Article struct {
	author, title, content string
	articleId              int
}

拥有两个方法,一个通过值进行参数传递,一个通过地址进行参数传递,起作用都是修改标题,那么结果会是怎么样呢?

func (a Article) setTitle1() {
	a.title = "1"
}

func (a *Article) setTitle2() {
	a.title = "2"
}

执行方法,并且进行打印

func main() {

	article := &Article{
		"lomtom",
		"Go(二)结构体",
		"这是内容",
		119995581,
	}
	fmt.Printf("%v", article) // &{lomtom Go(二)结构体 这是内容 119995581}
	article.setTitle1()
	fmt.Printf("%v", article) // &{lomtom Go(二)结构体 这是内容 119995581}
	article.setTitle2()
	fmt.Printf("%v", article) // &{lomtom 2 这是内容 119995581}
}

可以看到通过值传递的,并且实现真正的修改,这是为什么呢?

因为在Go中通过值传递,实质上是拷贝了一份新的内容,在setTitle1中的a实际上与article指向的并不是指的同一个地址了。