Go 编程 | 连载 16 - 结构体 Struct

简介: Go 编程 | 连载 16 - 结构体 Struct

一、type 关键字的作用

type 定义别名

在基本数据类型中的 byterune 其实就是 uint8int32 的别名,在源码中这些别名就是使用 type 关键字定义的,当然我们也可以自己定义别名。

// 定义别名,将原名字使用 = 赋值给别名
type alias = oldName
复制代码
func main(){
   type bigInt = int64
   var balance bigInt = 100000000
   fmt.Printf("%T", balance) // int64
}
复制代码

基于已有类型定义新的类型

定义新的类型要在已有类型的基础上进行定义,注意与定义类型别名作区分。

func main(){
   type tinyInt int8
   var age tinyInt = 8
   fmt.Printf("%T", age) // main.tinyInt
}
复制代码

type 关键字还可以为函数定义别名、定义结构体以及定义接口。

先来看看什么是结构体以及如何使用 type 关键字定义结构体。

二、结构体 struct

在 Go 中没有类和对象的概念,但是并不代表 Go 无法实现面向对象的三大特征。Go 中通过结构体来实现。

Go 结构体要解决的问题就是如何实现面向对象的三个基本特征 封装、继承、多态 以及 方法重载抽象基类 等特征。

结构体的定义

定义结构体除了使用 type 关键字外,还需要用到结构的标识符 struct

type StructName struct{
    attr1 attrType
    attr2 attrType
    ...
}
复制代码

注意结构体的属性之间是没有逗号的,不要与 Map 混淆。

func main() {
   // 实例化结构体
   var tom Male = Male{
      name:    "tom",
      age:     30,
      address: "tomcat",
   }
   fmt.Println(tom.name, tom.age, tom.address) // tom 30 tomcat
}
// 定义结构体
type Male struct {
   name string
   age int
   address string
}
复制代码

Go 是区分大小写的,大写的变量、函数名、接口名或者结构体名等表示可以被外界访问,如果是下小写的则表示私有的,无法被外界(其他包)访问到。Go 的大小写影响其可见性。

一般情况下都会将结构体的属性名大写,使其对外可见。

结构体的实例化

上面的代码中演示过了一种结构体实例化的方式,起始结构体实例化时还可以将属性名省略,但是要保证赋值的顺序与定义的顺序是一一对应的。

func main() {
   // 第二种实例化方式,省略属性名
   allen := Male{"allen", 18, "NYC"}
   fmt.Println(allen.name, allen.age, allen.address) // allen 18 NYC
}
复制代码

结构体实例的属性值不仅可以通过结构体实例来获取,也可以通过结构体指针来直接获取

func main() {
   // 定义一个结构体指针变量
   pennyPoi := &Female{"penny", 18, "Miramar, Florida"}
   fmt.Printf("%T\n", pennyPoi)
   // 通过结构体指针获取结构体的属性值
   fmt.Println(pennyPoi.Name, pennyPoi.Age, pennyPoi.Address)
   // 通过指针获取到结构体实例,再通过结构体实例获取属性值
   penny := *pennyPoi
   fmt.Println(penny.Name, penny.Age, penny.Address)
}
type Female struct {
   Name string
   Age int
   Address string
}
复制代码

执行上述代码,输出结果如下:

*main.Female
penny 18 Miramar, Florida
penny 18 Miramar, Florida
复制代码

注意通过结构体指针获取结构体实例的属性值时不要通过 *pennyPoi.Name 这种方式来获取,因为这样会被误认为 pennyPoi.Name 是一个指针,要么添加括号 (*pennyPoi).Name,要么先将通过结构体指针获取到的结构体实例赋值到一个变量中,通过变量再去获取属性值。

直接通过结构体指针获取结构体的属性值其实是 Go 的一个语法糖,Go 内部会将 *pennyPoi.Name 转换为 *pennyPoi.Name

结构体实例化时如果为空值,那么结构体属性的值会赋值指定类型的默认值

func main() {
   // 零值
   nancy := Female{}
   fmt.Println(nancy.Name)
   fmt.Println(nancy.Age)
   fmt.Println(nancy.Address)
}
复制代码

执行上述代码,输出结果如下:

0
复制代码

除了上述方式外,还有其他方式可以零值初始化

func main() {
   var nancy2 Female
   fmt.Println(nancy2.Age) // 0
   var nancy3Poi *Female = new(Female)
   fmt.Println(nancy3Poi.Age) // 0
   // 这种方式会引起报错
   // panic: runtime error: invalid memory address or nil pointer dereference
   //var nancy4Poi *Female
   //fmt.Println(nancy4Poi.Age)
}
复制代码

指针如果只声明不赋值默认是 nil,会报错,需要通过 new 函数申请内存。而结构体则可以直接通过 var 关键字初始化结构体并自动分配内存。

除了指针之外,还有 Slice Map 初始化时不会自动分配内存的,要使用 new 函数来分配内存。

结构体是值类型

从上面的代码可以确定结构体类型是可以直接通过 var 关键字直接初始化并自动分配内存的,类似的还有 数组 Array,整型 Int,浮点型 Float 以及字符串 String 多可以直接初始化并自动分配内存。

数组 Array,整型 Int,浮点型 Float 以及字符串 String 都是值类型,而 结构体 Struct 是不是也是值类型?

func main() {
   // 结构体是值类型
   p1 := Female{"Penny", 23, "NYC"}
   p2 := p1
   fmt.Printf("%v\n", &(p1.Name))
   fmt.Printf("%v\n", &(p2.Name))
   // 修改 p2
   p2.Name = "PENNY"
   fmt.Println(p2.Name)
   fmt.Println(p1.Name)
}
复制代码

执行上述代码,输出结果如下:

0xc000098180
0xc0000981b0
PENNY
Penny
复制代码

根据输出的 Name 属性的内存地址不同就可以确定结构体是值类型,并且修改 p2 对 p1 没有影响。

因此结构体作为函数参数传递的时候也是,值传递,既复制一个给函数作为参数使用,与原结构体互不影响

结构体占用内存大小

结构体占用内存大小可以使用 unsafe.Sizeof 函数来获取,结构体占用内存大小是固定的,不会应为存的内容的大小而改变

func main() {
   // 结构体是值类型
   p1 := Female{"Penny", 23, "NYC"}
   p2 := Female{"PennyPennyPennyPennyPennyPenny", 23, "NYC"}
   fmt.Println(unsafe.Sizeof(p1), unsafe.Sizeof(p2)) // 40 40
}
复制代码

string 和 slice 底层都是结构体。


相关文章
|
23天前
|
程序员 Go
go语言中结构体(Struct)
go语言中结构体(Struct)
97 71
|
1月前
|
存储 Rust Go
Go nil 空结构体 空接口有什么区别?
本文介绍了Go语言中的`nil`、空结构体和空接口的区别。`nil`是预定义的零值变量,适用于指针、管道等类型;空结构体大小为0,多个空结构体实例指向同一地址;空接口由`_type`和`data`字段组成,仅当两者均为`nil`时,空接口才为`nil`。
Go nil 空结构体 空接口有什么区别?
|
1月前
|
编译器 Go
探索 Go 语言中的内存对齐:为什么结构体大小会有所不同?
在 Go 语言中,内存对齐是优化内存访问速度的重要概念。通过调整数据在内存中的位置,编译器确保不同类型的数据能够高效访问。本文通过示例代码展示了两个结构体 `A` 和 `B`,尽管字段相同但排列不同,导致内存占用分别为 40 字节和 48 字节。通过分析内存布局,解释了内存对齐的原因,并提供了优化结构体字段顺序的方法,以减少内存填充,提高性能。
42 3
|
1月前
|
数据库连接 Go 数据库
Go语言中的错误注入与防御编程。错误注入通过模拟网络故障、数据库错误等,测试系统稳定性
本文探讨了Go语言中的错误注入与防御编程。错误注入通过模拟网络故障、数据库错误等,测试系统稳定性;防御编程则强调在编码时考虑各种错误情况,确保程序健壮性。文章详细介绍了这两种技术在Go语言中的实现方法及其重要性,旨在提升软件质量和可靠性。
38 1
|
1月前
|
数据采集 监控 Java
go语言编程学习
【11月更文挑战第3天】
42 7
|
1月前
|
Unix Linux Go
go进阶编程:Golang中的文件与文件夹操作指南
本文详细介绍了Golang中文件与文件夹的基本操作,包括读取、写入、创建、删除和遍历等。通过示例代码展示了如何使用`os`和`io/ioutil`包进行文件操作,并强调了错误处理、权限控制和路径问题的重要性。适合初学者和有经验的开发者参考。
|
2月前
|
Java 大数据 Go
Go语言:高效并发的编程新星
【10月更文挑战第21】Go语言:高效并发的编程新星
54 7
|
2月前
|
Go 数据处理 调度
Go语言中的并发模型:解锁高效并行编程的秘诀
本文将探讨Go语言中独特的并发模型及其在现代软件开发中的应用。通过深入分析 Goroutines 和 Channels,我们将揭示这一模型如何简化并行编程,提升应用性能,并改变开发者处理并发任务的方式。不同于传统多线程编程,Go的并发方法以其简洁性和高效性脱颖而出,为开发者提供了一种全新的编程范式。
|
3月前
|
Go
Go to Learn Go之结构体
Go to Learn Go之结构体
43 5
|
3月前
|
存储 JSON 安全
go语言编程系列(七)
go语言编程系列(七)