Go的第10课——类型定义和结构体

类型定义

定义语法跟之前的函数类型一样,使用 type 关键字

1
type newtype oldtype

这里我们是给 oldtype 定义了一个别名,相当于是 typedef 只是 go 里面相对于 C 是反着写的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import "fmt"

type pid_t int

func main() {
var pid pid_t
pid = 1
if pid == 1 {
fmt.Println("SYSTEM PROCESS")
} else {
fmt.Println("COMMON PROCESS")
}
}
/*
SYSTEM PROCESS
*/

结构体

基本介绍

go语言没有面向对象的概念了,但是可以使用结构体来实现,面向对象编程的一些特性,例如:继承、组合等特性。

在这里我们结合上面的定义去创建一个结构体类型。

1
2
3
4
5
6
type struct_identifier struct{
member1 type1
member2 type2
...
membern typen
}

demo

随便定义一个结构体玩玩

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import "fmt"

type Person struct {
age int
name string
height int
}

func main() {
var person Person
person.name = "xia0ji233"
person.age = 22
person.height = 185
fmt.Println(person)
}
/*
{22 xia0ji233 185}
*/

结构体成员在没有赋值之前都是默认值。

结构体初始化

第一个就是按照上面的 demo 给成员一个一个赋值。

第二个就是使用键值对的方式初始化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import (
"fmt"
)

type Person struct {
age int
name string
height int
}

func main() {
var person = Person{age: 18, name: "xia0ji233", height: 185}
fmt.Println(person)
}
/*
{18 xia0ji233 185}
*/

还有一个应该是可以使用成员方法的,这个不叫构造方法,因为它没有面向对象的概念,所以说也只能是定义一个成员方法去给它初始化。

结构体指针

基本用法

就和普通类型的指针差不多的用法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import "fmt"

type Person struct {
age int
name string
height int
}

func main() {
var person = Person{age: 18, name: "xia0ji233", height: 185}
var p *Person
p = &person
fmt.Printf("name:%v\n", (*p).name)
fmt.Printf("age:%v\n", (*p).age)
fmt.Printf("height:%v\n", (*p).height)
}
/*
name:xia0ji233
age:18
height:185
*/

new关键字创建结构体指针

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import "fmt"

type Person struct {
age int
name string
height int
}

func main() {
var p *Person
p = new(Person)
(*p).name = "xia0ji233"
(*p).age = 18
(*p).height = 185
fmt.Printf("name:%v\n", (*p).name)
fmt.Printf("age:%v\n", (*p).age)
fmt.Printf("height:%v\n", (*p).height)
}
/*
name:xia0ji233
age:18
height:185
*/

这里还发现一点,就是 C 语言对于指针访问结构体变量是只能 (*ptr).member 去访问,或者是 ptr->member 这样子的,而 go 就爽了,指针和结构体类型都是可以直接用 . 来访问成员变量的,可能是因为对指针做了限制,导致 ptr.member 写法就没什么歧义,因此可以。

方法

基本用法

go 语言没有面向对象的特性,也没有类对象的概念。但是,可以使用结构体来模拟这些特性,我们都知道面向对象里面有类方法等概念。我们也可以声明一些方法,属于某个结构体。

Go中的方法,是一种特殊的函数定义于struct之上(与struct关联、绑定),被称为struct的接受者(receiver)。

通俗的讲,方法就是有接收者的函数。

语法格式如下:

1
2
3
type mytype struct{}
func (recv mytype) my_method(para) return_type {}
func (recv *mytype) my_method(para) return_type {}

和一般的函数对比起来,多了一个接受类型(recv) 其实这也可以看成一个参数,只不过因为是结构体主动调用的,所以这个参数可以不用我们主动传。

如果我们需要修改成员那就使用指针传参,如果不需要那么可以直接结构体传参,下面实现一个 init 的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main

import "fmt"

type Person struct {
age int
name string
height int
}

func (recv *Person) init(age int, name string, height int) {
*recv = Person{
age: age,
name: name,
height: height,
}
}

func main() {
var p *Person
p = new(Person)
p.init(18, "xiaoji233", 185)
fmt.Printf("name:%v\n", (*p).name)
fmt.Printf("age:%v\n", (*p).age)
fmt.Printf("height:%v\n", (*p).height)
}
/*
name:xiaoji233
age:18
height:185
*/

注意事项

  • 方法的receiver type并非一定要是struct类型,type定义的类型别名、slice,map,channel,func类型等都可以。
  • struct结合它的方法就等价于面向对象中的类。只不过struct可以和它的方法分开,并非一定要属于同一个文件,但必须属于同一个包。
  • 方法有两种接收类型:(T Type)和(T*Type),它们之间有区别。
  • 方法就是函数,所以Go中没有方法重载(overload)的说法,也就是说同一个类型中的所有方法名必须都唯一。
  • 如果receiver是一个指针类型,则会自动解除引用。
  • 方法和type是分开的,意味着实例的行为(behavior)和数据存储(field)是分开的,但是它们通过receiver建立起关联关系。

经过自己实验呢,发现基础数据类型确实不能,但是自己往基础类型创建一个别名就可以使用,弹幕里面有一句话叫 “扩展方法”,姑且认为是这样吧,方法和函数差不多,也不支持重载。

这边再写一个小例子巩固一下,弥补一下之前 int 转 string 不能强转的缺陷,为它写一个方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import "fmt"

type pid_t int

func (recv pid_t) tostring() string {
return fmt.Sprintf("%d", recv)
}

func main() {
var s pid_t
s = 2147483647
var q = "value:" + s.tostring()
fmt.Println(q)
}
/*
value:2147483647
*/