失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > Go 学习笔记(14)— 结构体定义 实例化 初始化 匿名结构体 结构体访问 结构体

Go 学习笔记(14)— 结构体定义 实例化 初始化 匿名结构体 结构体访问 结构体

时间:2019-08-01 07:10:16

相关推荐

Go 学习笔记(14)— 结构体定义 实例化 初始化 匿名结构体 结构体访问 结构体

Go语言中没有 “类” 的概念,也不支持 “类” 的继承等面向对象的概念。Go语言不仅认为结构体能拥有方法,且每种自定义类型也可以拥有自己的方法。

1. 结构体的定义

结构体是由一系列具有相同或者不同类型数据组成的集合。结构体的定义需要使用typestruct关键字。含义如下:

type关键字定义了结构体的名称;

struct关键字定义新的数据类型,结构体中可以有一个或者多个成员;

结构体成员是由一系列的成员变量构成,这些成员变量也被称为“字段”。字段有以下特性:

字段拥有自己的类型和值。字段名必须唯一。字段的类型也可以是结构体,甚至是字段所在结构体的类型。

type是自定义类型的关键字,不仅支持struct类型的创建,还支持任意其它子定义类型(整型、字符串、布尔等)的创建。

结构体的标准格式如下:

type structName struct {member type;member type;...member type;}

2. 结构体实例化

结构体的定义只是一种内存布局的描述,只有当结构体实例化时,才会真正地分配内存,因此必须在定义结构体并实例化后才能使用结构体的字段。

package mainimport ("fmt")type Student struct {Id intName stringAge intMobile intMajor string}func main() {// 创建Student指针类型变量s1 := new(Student)// 创建Student指针类型变量s2 := &Student{}// 创建Student类型变量s3 := Student{}// 创建Student指针类型变量,并根据字段名附初始值s4 := &Student{Id:0901,Name: "wohu",Age: 20,Mobile: 123456,Major: "move brick",}// 创建Student类型变量,并根据字段在结构体中的顺序附初始值s5 := Student{0901,"wohu",20,123456,"move brick",}fmt.Println("s1:", s1)fmt.Println("s2:", s2)fmt.Println("s3:", s3)fmt.Println("s4:", *s4)fmt.Println("s5:", s5)}

输出结果:

s1: &{0 0 0 }s2: &{0 0 0 }s3: {0 0 0 }s4: {0901 wohu 20 123456 move brick}s5: {0901 wohu 20 123456 move brick}

通过在结构体后边加大括号{}的方式创建变量与使用new关键字创建对象不同之处在于new创建出来的是指针类型,而结构体名加大括号{}创建出来的不是指针类型。

如果在使用结构体名加大括号{}创建变量时,在结构体名前边加上地址操作符&,那么这样创建出来的也是指针类型变量,效果就与new关键字一样了,如上边示例代码中,s1 与 s2 都是指针类型变量。

在程序中,可以通过结构体类型变量名加上点号.,再加上字段名的方式访问结构体中的字段。如访问学生姓名的操作是:

s1.Name = "hi hzwy23"s3.Name = "hello hzwy23"fmt.Println(s3.Name)fmt.Println(s1.Name)

不管变量是指针类型,还是非指针类型,操作结构体字段,都是一样的方法,如上边示例中 s1 是指针类型,s3 是非指针类型,在访问字段名为 Name 的字段时,都是采用点号.加上字段名的方式访问。这一点与 C/C++ 语言不同。

2.1 普通实例化

结构体本身是一种类型,可以像整型、字符串等类型一样,以var的方式声明结构体即可完成实例化。这种用法是为了更明确地表示一个变量被设置为零值。

var phone Phone// 其中,Phone 为结构体类型,phone 为结构体的实例

用结构体表示的点结构(Point)的实例化过程请参见下面的代码:

type Point struct {X intY int}var p Pointp.X = 10p.Y = 20

在例子中,使用.来访问结构体的成员变量,如p.Xp.Y等,结构体成员变量的赋值方法与普通变量一致。

与数组类型相同,结构体类型属于值类型,因此结构体类型的零值不是nil,上述Point的零值就是Point{}

2.2 new 实例化

Go语言中,还可以使用new关键字对类型(包括结构体、整型、浮点数、字符串等)进行实例化,结构体在实例化后会形成指针类型的结构体。

使用new的格式如下:

ins := new(T)

其中:

T为类型,可以是结构体、整型、字符串等。insT类型被实例化后保存到ins变量中,ins的类型为*T,属于指针。

Go语言让我们可以像访问普通结构体一样使用.来访问结构体指针的成员。

type Player struct{Name stringHealthPoint intMagicPoint int}tank := new(Player)tank.Name = "Canon"tank.HealthPoint = 300

经过new实例化的结构体实例在成员赋值上与普通实例化的写法一致。

在 C/C++ 语言中,使用new实例化类型后,访问其成员变量时必须使用->操作符。

Go语言中,访问结构体指针的成员交量时可以继续使用.。 这是因为Go语言为了方便开发者访问结构体指针的成员交量,使用了语法糖技术,将ins.Name形式转换为(*ins).Name

2.3 取地址实例化

Go语言中,对结构体进行&取地址操作时,视为对该类型进行一次new的实例化操作,取地址格式如下:

ins := &T{}

其中:

T: 表示结构体类型。ins:为结构体的实例,类型为*T,是指针类型。

下面使用结构体定义一个命令行指令(Command),指令中包含名称、变量关联和注释等,对 Command 进行指针地址的实例化,并完成赋值过程,代码如下:

type Command struct {Name string // 指令名称Var*int// 指令绑定的变量Comment string // 指令的注释}var version int = 1cmd := &Command{}cmd.Name = "version"cmd.Var = &ment = "show version"

取地址实例化是最广泛的一种结构体实例化方式,可以使用函数封装上面的初始化过程,代码如下:

func newCommand(name string, varref *int, comment string) *Command {return &Command{Name: name,Var:varref,Comment: comment,}}cmd = newCommand("version",&version,"show version",)

3. 结构体初始化

结构体在实例化时可以直接对成员变量进行初始化,初始化有两种形式分别是以字段“键值对”形式和多个值的列表形式,键值对形式的初始化适合选择性填充字段较多的结构体,多个值的列表形式适合填充字段较少的结构体

3.1 键值对初始化

结构体可以使用“键值对”(Key value pair)初始化字段,每个“键”(Key)对应结构体中的一个字段,键的“值”(Value)对应字段需要初始化的值。

键值对的填充是可选的,不需要初始化的字段可以不填入初始化列表中。

结构体实例化后字段的默认值是字段类型的默认值,例如 ,数值为0、字符串为 “”(空字符串)、布尔为false、指针为nil等。

键值对初始化的格式如下:

varName := structName{key1: value1, key2: value2..., keyn: valuen}// 键值之间以:分隔,键值对之间以,分隔。

下面示例中描述了家里的人物关联,正如儿歌里唱的:“爸爸的爸爸是爷爷”,人物之间可以使用多级的 child 来描述和建立关联,使用键值对形式填充结构体的代码如下:

type People struct {name stringchild *People}relation := &People{name: "爷爷",child: &People{name: "爸爸",child: &People{name: "我",},},}

注意:结构体成员中只能包含结构体的指针类型,包含非结构体指针类型会引起编译错误。

3.2 多值列表初始化

Go语言可以在“键值对”初始化的基础上忽略“键”,也就是说,可以使用多个值的列表初始化结构体的字段。多个值使用逗号分隔初始化结构体,例如:

varName := structName{value1, value2...valuen}

使用这种格式初始化时,需要注意:

必须初始化结构体的所有字段每一个初始值的填充顺序必须与字段在结构体中的声明顺序一致键值对与值列表的初始化形式不能混用

type Address struct {Province stringCity stringZipCodeintPhoneNumber string}addr := Address{"陕西","西安",710000,"12345",}fmt.Println(addr)// {陕西 西安 710000 12345}

3.3 初始化注意事项

初始化复合对象,必须使用类型标签,且左大括号必须在类型尾部。

package mainfunc main() {var a struct {x int } = {100 } // syntax error, syntax error: unexpected {, expecting expressionvar b []int = {1, 2, 3 } // syntax errorc := struct {x int; y string} // syntax error: unexpected semicolon or newline{}var a = struct{x int }{100}var b = []int{1, 2, 3}}

初始化值以 “,” 分隔。可以分多行,但最后一行必须以 “,” 或 “}” 结尾。

a := []int{1,2 // Error: need trailing comma before newline in composite literal}a := []int{1,2, // ok}b := []int{1,2 } // ok

3.4 空结构体

type Empty struct{} // Empty是一个不包含任何字段的空结构体类型

空结构体类型有什么用呢?

var s Emptyprintln(unsafe.Sizeof(s)) // 0

我们看到,输出的空结构体类型变量的大小为 0,也就是说,空结构体类型变量的内存占用为 0。基于空结构体类型内存零开销这样的特性,我们在日常 Go 开发中会经常使用空结构体类型元素,作为一种“事件”信息进行Goroutine之间的通信,就像下面示例代码这样:

var c = make(chan Empty) // 声明一个元素类型为Empty的channelc<-Empty{}// 向channel写入一个“事件”

这种以空结构体为元素类建立的channel,是目前能实现的、内存占用最小的Goroutine间通信方式。

4. 匿名结构体

匿名结构体没有类型名称,无须通过type关键字定义就可以直接使用。

4.1 匿名结构体定义和初始化

匿名结构体的初始化写法由结构体定义和键值对初始化两部分组成,结构体定义时没有结构体类型名,只有字段和类型定义,键值对初始化部分由可选的多个键值对组成,如下格式所示:

ins := struct {// 匿名结构体字段定义字段1 字段类型1字段2 字段类型2…}{// 字段值初始化初始化字段1: 字段1的值,初始化字段2: 字段2的值,…}

下面是对各个部分的说明:

字段1、字段2……:结构体定义的字段名。初始化字段1、初始化字段2……:结构体初始化时的字段名,可选择性地对字段初始化。字段类型1、字段类型2……:结构体定义字段的类型。字段1的值、字段2的值……:结构体初始化字段的初始值。

键值对初始化部分是可选的,不初始化成员时,匿名结构体的格式变为:

ins := struct {字段1 字段类型1字段2 字段类型2…}

4.2 匿名结构体示例

在本示例中,使用匿名结构体的方式定义和初始化一个消息结构,这个消息结构具有消息标示部分(ID)和数据部分(data),打印消息内容的 printMsg() 函数在接收匿名结构体时需要在参数上重新定义匿名结构体,代码如下:

package mainimport ("fmt")// 定义 printMsgType() 函数,参数为 msg,类型为 *struct{id int data string},// 因为类型没有使用 type 定义,所以需要在每次用到的地方进行定义。func printMsgType(msg *struct {id intdata string}) {// 使用动词%T打印msg的类型fmt.Printf("%T\n", msg)// *struct { id int; data string }}func main() {// 实例化一个匿名结构体msg := &struct {// 定义部分id intdata string}{// 值初始化部分1024,"hello",}printMsgType(msg)}

5. 结构体访问

在访问结构体变量成员时,使用点号.操作符,格式为:

结构体.成员名

结构体类型变量使用struct关键字定义,示例如下:

package mainimport "fmt"type Phone struct {model stringcolor stringprice int}func main() {var phone1 Phonevar phone2 Phonevar phone4 Phonefmt.Printf("phone1 init is %v\n", phone1)// 创建一个新的结构体phone1 = Phone{"华为", "black", 1000}fmt.Printf("phone1 is %v\n", phone1)// 也可以使用 key => value 格式phone2 = Phone{model: "华为", color: "black", price: 1000}fmt.Printf("phone2 is %v\n", phone2)// 忽略的字段为 0 或 空phone3 := Phone{model: "华为", price: 1000}fmt.Printf("phone3 is %v\n", phone3)// 访问结构体成员phone4.price = 8000phone4.color = "blue"phone4.model = "苹果"fmt.Printf("phone4 is %v\n", phone4)}

输出:

phone1 init is {0}phone1 is {华为 black 1000}phone2 is {华为 black 1000}phone3 is {华为 1000}phone4 is {苹果 blue 8000}

6. 结构体作为形参

可以将结构体类型作为参数传递给函数。

package mainimport "fmt"type Phone struct {model stringcolor stringprice int}func main() {var phone Phone// 访问结构体成员phone.price = 8000phone.color = "blue"phone.model = "苹果"printPhone(phone)}func printPhone(phone Phone) {fmt.Printf("phone price : %d\n", phone.price)fmt.Printf("phone color : %s\n", phone.color)fmt.Printf("phone model : %s\n", phone.model)}

输出:

phone price : 8000phone color : bluephone model : 苹果

指针结构体作为值传递。

package mainimport "fmt"type InnerData struct {a int}type Data struct {complax []intinstance InnerDataptr*InnerData}func passByValue(inFunc Data) Data {fmt.Printf("inFunc value: %+v\n", inFunc)fmt.Printf("inFunc ptr: %p\n", &inFunc)return inFunc}func main() {in := Data{complax: []int{1, 2, 3},instance: InnerData{5,},ptr: &InnerData{1},}fmt.Printf("in value: %+v\n", in)fmt.Printf("in ptr: %p\n", &in)out := passByValue(in)fmt.Printf("out value: %+v\n", out)fmt.Printf("out ptr: %p\n", &out)}

输出结果:

in value: {complax:[1 2 3] instance:{a:5} ptr:0xc0000160e8}in ptr: 0xc000082150inFunc value: {complax:[1 2 3] instance:{a:5} ptr:0xc0000160e8}inFunc ptr: 0xc0000821e0out value: {complax:[1 2 3] instance:{a:5} ptr:0xc0000160e8}out ptr: 0xc0000821b0

7. 结构体指针

指向结构体的指针类似于其它指针变量,格式如下:

var structPointer *Phone

上面定义的指针变量可以存储结构体变量的地址。如果需要查看结构体变量在内存中的地址,可以将&符号放置于结构体变量前:

structPointer = &phone1

使用结构体指针访问结构体成员,使用.操作符:

structPointer.title

使用示例

package mainimport "fmt"type Phone struct {model stringcolor stringprice int}func main() {var p *Phonevar phone Phonep = &phone// 访问结构体成员p.price = 8000p.color = "blue"p.model = "苹果"fmt.Printf("p is : %v\n", p)fmt.Printf("*p is : %v\n", *p)fmt.Printf("&p is : %v\n", &p)fmt.Printf("phone is : %v\n", phone)printPhone(p)}func printPhone(phone *Phone) {fmt.Printf("phone price : %d\n", phone.price)fmt.Printf("phone color : %s\n", phone.color)fmt.Printf("phone model : %s\n", phone.model)}

输出结果:

p is : &{苹果 blue 8000}*p is : {苹果 blue 8000}&p is : 0xc00000e028phone is : {苹果 blue 8000}phone price : 8000phone color : bluephone model : 苹果

var p *Phone// 就是说 p 这个指针是 Phone 类型的p = &phone//phone 是 Phone 的一个实例化的结构,&phone 就是把这个结构体的内存地址赋给了 p,*p //那么在使用的时候,只要在 p 的前面加个*号,就可以把 p 这个内存地址对应的值给取出来了&p // 就是取了 p 这个指针的内存地址,也就是 p 这个指针是放在内存空间的什么地方的。phone // 就是 phone 这个结构体,打印出来就是它自己。也就是指针 p 前面带了 * 号的效果。

8. 结构体拥有自身类型的指针类型、以自身类型为元素类型的切片类型

type T struct {t T // error... ...}

Go语言不支持这种在结构体类型定义中,递归地放入其自身类型字段的定义方式。面对上面的示例代码,编译器就会给出invalid recursive type T的错误信息。

但是下面的是可以的

type T struct {t *T // okst []T// okm map[string]T // ok}

一个类型,它所占用的大小是固定的,因此一个结构体定义好的时候,其大小是固定的。但是,如果结构体里面套结构体,那么在计算该结构体占用大小的时候,就会成死循环。

但如果是指针、切片、map等类型,其本质都是一个int大小(指针,4字节或者8字节,与操作系统有关),即因为指针、map、切片的变量元数据的内存占用大小是固定的,因此该结构体的大小是固定的,类型就能决定内存占用的大小。

因此,结构体是可以接受自身类型的指针类型、以自身类型为元素类型的切片类型,以及以自身类型作为value类型的map类型的字段,而自己本身不行。

9. 零值初始化

零值初始化说的是使用结构体的零值作为它的初始值。对于Go原生类型来说,这个默认值也称为零值。Go结构体类型由若干个字段组成,当这个结构体类型变量的各个字段的值都是零值时,我们就说这个结构体类型变量处于零值状态。

var book Book // book为零值结构体变量

Go语言标准库和运行时的代码中,有很多践行“零值可用”理念的好例子,最典型的莫过于sync包的Mutex类型了。

运用“零值可用”类型,给Go语言中的线程互斥锁带来了什么好处呢?我们横向对比一下C语言中的做法你就知道了。如果我们要在C语言中使用线程互斥锁,我们通常需要这么做:

pthread_mutex_t mutex; pthread_mutex_init(&mutex, NULL);pthread_mutex_lock(&mutex); ... ...pthread_mutex_unlock(&mutex);

我们可以看到,在C中使用互斥锁,我们需要首先声明一个mutex变量。但这个时候,我们不能直接使用声明过的变量,因为它的零值状态是不可用的,我们必须使用pthread_mutex_init函数对其进行专门的初始化操作后,它才能处于可用状态。再之后,我们才能进行lockunlock操作。

但是在Go语言中,我们只需要这几行代码就可以了:

var mu sync.Mutexmu.Lock()mu.Unlock()

Go标准库的设计者很贴心地将sync.Mutex结构体的零值状态,设计为可用状态,这样开发者便可直接基于零值状态下的Mutex进行lockunlock操作,而且不需要额外显式地对它进行初始化操作了。

Go 学习笔记(14)— 结构体定义 实例化 初始化 匿名结构体 结构体访问 结构体作为形参 结构体指针

如果觉得《Go 学习笔记(14)&mdash; 结构体定义 实例化 初始化 匿名结构体 结构体访问 结构体》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。