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:     20210901,Name:   "wohu",Age:    20,Mobile: 123456,Major:  "move brick",}// 创建Student类型变量,并根据字段在结构体中的顺序附初始值s5 := Student{20170901,"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: {20210901 wohu 20 123456 move brick}
s5: {20170901 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 Point
p.X = 10
p.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 = &version
cmd.Comment = "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        stringZipCode     intPhoneNumber 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 Empty
println(unsafe.Sizeof(s)) // 0

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

var c = make(chan Empty) // 声明一个元素类型为Empty的channel
c<-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 : 8000
phone color : blue
phone 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: 0xc000082150
inFunc value: {complax:[1 2 3] instance:{a:5} ptr:0xc0000160e8}
inFunc ptr: 0xc0000821e0
out 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 : 0xc00000e028
phone is : {苹果 blue 8000}
phone price : 8000
phone color : blue
phone 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.Mutex
mu.Lock()
mu.Unlock()

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

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

  1. 华为HCIA-datacom 学习笔记14——WLAN概述

    华为HCIA-datacom 学习笔记14--WLAN概述 1.WLAN(无线局域网) 通过无线技术构造的无线局域网络.WLAN广义上是指以无线电波.激光.红外线等无线信号代替有线局域网中的部分或全部 ...

  2. Linux学习笔记14

    Linux学习笔记14 Linux学习笔记14 DNS服务搭建 基本介绍 使用bind搭建dns服务 增加一个域名zone 配置DNS转发 配置主从 测试主从同步 后续课程 DNS服务搭建 基本介绍 ...

  3. 台大李宏毅Machine Learning 2017Fall学习笔记 (14)Unsupervised Learning:Linear Dimension Reduction

    台大李宏毅Machine Learning 2017Fall学习笔记 (14)Unsupervised Learning:Linear Dimension Reduction 本博客整理自: http ...

  4. node.js学习笔记14—微型社交网站

    node.js学习笔记14-微型社交网站 1.功能分析 微博是以用户为中心,因此需要有注册和登录功能. 微博最核心的功能是信息的发表,这个功能包括许多方面,包括:数据库访问,前端显示等. 一个完整的微 ...

  5. 【转】 C#学习笔记14——Trace、Debug和TraceSource的使用以及日志设计

    [转] C#学习笔记14--Trace.Debug和TraceSource的使用以及日志设计 Trace.Debug和TraceSource的使用以及日志设计   .NET Framework 命名空 ...

  6. 【白帽子学习笔记14】SQL注入常用语句

    [白帽子学习笔记14]SQL注入常用语句 目前网站中使用的最多的数据库要算是 ACCESS.SQL Server(MSSQL).MySQL 这三个了,所以这里的手工注入,我就以他们三个数据库来分成三 ...

  7. 运动控制学习 学习笔记(八)——定义和评价学习

    运动控制学习 学习笔记(八)--定义和评价学习 目标 (1)能够区分Performance(表现)与Learning(学习) (2)掌握"出现"了学习时,典型的六种Performa ...

  8. 泛型学习笔记:泛型使用的注意点、泛型在继承方面的体现、自定义泛型结构、泛型应用举例、通配符

    泛型学习笔记 集合是为了解决数组某些存储限制的特点出现的,但是数组有一个优点就是存放的数据的类型是确定的,但是集合容器在声明阶段存入的对象是什么类型是不确定的,所以在JDK1.5之前只能把元素的类型设 ...

  9. NuttX的学习笔记 14

    下面来分析timer这个例子吧. int timer_main(int argc, char *argv[]) {struct timer_notify_s notify;struct sigacti ...

  10. 影像组学视频学习笔记(14)-特征权重做图及美化、Li‘s have a solution and plan.

    本笔记来源于B站Up主: 有Li 的影像组学系列教学视频 本节(14)主要介绍: 特征权重做图及美化 import matplotlib.pyplot as plt %matplotlib inlin ...

最新文章

  1. php-fpm 参数及配置详解
  2. 内核分析PE获取DLL导出函数地址
  3. date new 转换时区_Pandas 时间序列 时区控制
  4. 解决MySQL删除外键时报错Error Code: 1091. Can‘t DROP ‘XXX‘; check that column/key exists
  5. POJ - 2187 Beauty Contest(最远点对)
  6. 家长又放心了一些!教育类App不能再干这些事了
  7. 韩国NF功放芯片在音频音响领域的应用
  8. js网状特效源代码下载
  9. gltf中的bufferViews,accessors和buffers
  10. 本科毕业、硕士毕业和博士毕业PPT制作和预答辩、答辩注意事项
  11. 什么是Richard Feynman 积分技巧
  12. python中新式类和经典类的区别
  13. (深搜)蒜厂有一间长方形的房子,地上铺了红色、黑色两种颜色的正方形瓷砖。你站在其中一块黑色的瓷砖上,只能向相邻的黑色瓷砖移动。(深搜)
  14. RSA加密算法(公钥+私钥加密)
  15. SDU软件17级大一下离散期末真题(回忆版)
  16. vue+elementui Table组件splice删除表格总是删除最后一行
  17. 5. PostCSS
  18. 查询数据库的表(字段)名和对应的注释
  19. Fuse.js模糊搜索引擎
  20. 【物联网】物联网学习学科主要课程

热门文章

  1. 2022-2028年中国老年旅游市场深度调研及开发战略研究报告
  2. 用乐观的心态去面对生活,能让你的生活过得更加快乐
  3. 【Spring】spring基于纯注解的声明式事务控制
  4. 站在巨人的肩膀上“思考”问题,重在思考而不是拿来主义
  5. tvm模型部署c++ 分析
  6. 至强® 平台配备先进遥测技术让您的数据中心更智能
  7. 2021年大数据ZooKeeper(一):ZooKeeper基本知识
  8. 如何写出安全的API接口(参数加密+超时处理+私钥验证+Https)
  9. Python 如何查看内存地址
  10. Android AlertDialog设置宽度显示不正常的问题