首页 > 编程笔记 > Go语言笔记 阅读:1

Go语言结构体的用法(非常详细)

数组只能存储同一类型的数据,如果想要为不同的成员定义不同的数据类型,那么就轮到结构体出场了。

结构体就是用户自定义的数据类型。初始化结构体,指的是创建一个结构体的值,而不是创建一个结构体对象。

结构体是由一系列数据集合组成的,这些数据集合由相同类型或不同类型的成员构成,我们可使用“.”操作符访问结构体中的成员。

Go 语言中没有类的概念,它只有结构体。方法不是结构体的成员,方法与结构体只是绑定关系。

Go语言结构体的定义

结构体中成员的类型可以是基本数据类型、自定义类型和接口类型。类型前可以加上成员的名字,也可以采用匿名方式。结构体指针是指向结构体的指针,类似其他指针的变量。

结构体的定义方法如下:
type Sturct_name struct{
    成员名字 类型
    成员名字 自定义类型
    结构体 //匿名方式
    成员名字 结构体
    成员名字 接口类型
    ...
}

结构体支持嵌套,示例代码如下:
type Database struct {
    name         string
    dbType       string
    isOpenSource bool
}
 
type Rdbms struct {
    Database
    name    string
    version string
}
 
type NewSQL struct {
    Rdbms
    cap  string
    name string
}
 
type NoSQL struct {
    db  Database
    cap  string
    name string
}
上面代码中的结构体 Database 只包含了基本数据类型的成员,而其他 3 个结构体则嵌套了别的结构体。可以看到,Rdbms 和 NoSQL 结构体嵌套了 Database 结构体,NewSQL 结构体嵌套了 Rdbms 结构体,它们的嵌套关系如下图所示:

 
图 1 结构体的嵌套关系

结构体 Rdbms 和 NoSQL 虽然都嵌套了 Database 结构体,但它们并不相同,不同之处在于 Rdbms 结构体中嵌套的 Database 结构体没有名字,属于匿名嵌套。

也正因如此,我们可以直接访问此 Database 结构体的属性 isOpenSource,但访问 NoSQL 结构体嵌套的 Database 结构体时就需要加上 db 前缀了。

同样,NewSQL 结构体匿名嵌套了 Rdbms 结构体,所以它可以直接访问 Rdbms 结构体和 Database 结构体的所有属性。

下表列出了访问 Database 结构体的成员 isOpenSource 的方法。

表:访问Database结构体的成员isOpenSource的方法
结构体 访问Database结构体的成员isOpenSource的方法
Rdbms .isOpenSource
NoSQL .db.isOpenSource
NewSQL .isOpenSource

请注意,如果结构体的成员与嵌套结构体的成员同名,那么访问此成员时应遵循最近优先原则。

结构体的嵌套常应用在并发控制上。例如,后面提到的 sync.Mutex 内嵌到结构体中以后,就可以直接调用成员 sync.Mutex 的 Lock/Unlock 方法了,示例代码如下:
//自定义内嵌sync.Mutex的计数器
type Counter struct {
    sync.Mutex
    Count uint64
}
 
var counter Counter
counter.Lock()
counter.Count++
counter.Unlock()

Go语言结构体的初始化

初始化结构体有两种方法,具体如下:
前面的代码中定义了四个结构体:Database、Rdbms、NewSQL 和 NoSQL,接下来演示如何初始化它们。

1) 使用“var 变量名 结构体”的方式声明,然后使用操作符“.”对成员进行赋值。
//变量mysql的初始化及使用
var mysql Rdbms
mysql.Database.name = "MySQL" //等价于 mysql.name ="MySQL"
mysql.dbType = "rdbms"
mysql.isOpenSource = true
mysql.version = "5.27"
fmt.Println(mysql) //输出:{{MySQL rdbms true}  5.27}

2) 使用显式的方法对所有的成员进行赋值初始化。如果当前的结构体与嵌套的结构体有相同的成员名,则遵循最近优先原则,这时输出的 oracle.name 的值是 Oracle,而不是 oracle-db。
//变量oracle的初始化及使用
oracle := Rdbms{
    Database: Database{
            name:         "oracle-db",
            dbType:       "rdbms",
            isOpenSource: true,
    },
    name:    "Oracle",
    version: "18C", //后面的“,”是必不可少的!!!
}
fmt.Println(oracle) //输出:{{oracle-db rdbms true} Oracle 18C}
fmt.Println(oracle.name) //输出:Oracle

3) 声明与初始化变量 tidb。将已经初始化的 mysql 赋给 tidb.Rdbms。
//变量tidb的初始化及使用
var tidb NewSQL
tidb = NewSQL{
    Rdbms: mysql,
    cap:   "CA",
    name:  "tidb",
}
tidb.dbType = "newsql"
fmt.Println(tidb) //输出:{{{MySQL newsql true}  5.27} CA tidb}

除了使用字面量初始化结构体,还可以使用关键字 new 初始化。需要注意的是,new 返回的是一个指针变量 &{}。由于 NoSQL 结构体嵌套了 Database 结构体且不是匿名嵌套,因此在访问结构体 Database 的成员时,要带上前面的变量名,如访问 Database 的 isOpenSource 成员时,要使用 nosql.db.isOpenSource。
// new返回的是一个指针变量 &{}
var nosql *NoSQL = new(NoSQL)
//因为定义nosql结构体时,Database前面有一个变量名db,
//所以此时不再是匿名嵌套Database结构体,
//也就是在访问其成员时需要加上变量名
nosql.db.isOpenSource = true
fmt.Println(nosql) //输出:&{{  true}  }
注意,结构体中的每个成员后面都有一个逗号,就算是最后一个成员,后面也必须跟一个逗号。

Go语言结构体的类型转换

Go语言不会隐式转换有具体名称的类型,如果是有具体名称的类型,进行类型转换时必须写出类型名称,但如果遇到的是没有具体名称的字面类型,则可以把字面类型的值赋给有具体名称的类型变量。

示例代码如下:
type GoInt struct{
        i int
}
 
type JavaInt struct{
        i int
}
 
var a GoInt
var b JavaInt
 
b=a //变量a和b的类型都有具体的名称,所以进行类型转换时必须写出类型名称
b=JavaInt(a) //类型转换
 
c :=struct{
        i int
}{
        i: 10,
}
b=c //变量b的类型有具体的名称(JavaInt),变量c没有具体的类型名称,所以可以将c直接赋给b

Go语言结构体比较

1) 结构体类型相同才可以比较

只有结构体的类型相同才可以进行比较,判断结构体是否相同的依据是成员变量的类型、个数和顺序是否一致。示例代码如下:
s1 := struct {
    age  int
    name string
}{age: 18, name: "student"}
 
s2 := struct {
    name string
    age  int
}{age: 30, name: "employee"}
 
if s1 == s2 {
    fmt.Println("s1 == s2")
}
以上代码在编辑阶段会报错,因为两个结构体成员变量的定义顺序不一样。报错信息如下:
invalid operation: s1 == s2 (mismatched types struct{age int; name string} and struct{name string; age int})

2) 并非所有的结构体成员变量都能用“==”比较

如果结构体中两个成员变量的类型和定义顺序都相同,但它们的类型(如切片、映射和函数等)不可以比较,那么不能使用“= =”进行比较。

示例代码如下:
s1 := struct {
    age int
    m   map[string]string
}{age: 18, m: map[string]string{"occupation": "student"}}
 
s2 := struct {
    age int
    m   map[string]string
}{age: 30, m: map[string]string{"occupation": "employee"}}
 
if s1 == s2 {
    fmt.Println("s1 == s2")
}
以上代码在编辑阶段会报错,因为此结构体中的两个实例不能使用“= =”进行比较。报错信息如下:
invalid operation: s1 == s2 (struct containing map[string]string cannot be compared)

如果结构体包含的是不可比较类型的成员,那么只能比较其结构体指针。例如,将上面的代码 s1==s2 换成 &s1 == &s2 则可以通过编译。此外,想要比较结构体的两个字面量,还可以使用与反射有关的函数 reflect.DeepEqual,如果所有字面量的值都相等,那么返回 true。

下表总结了同一结构体中两个不同实例的比较方式:

表:同一结构体中两个不同实例的比较方式
比较方式 如果结构体包含不可比较的成员变量 如果结构体不包含不可比较的成员变量
使用“==” 只能比较结构体指针 指针和实例均可比较
使用reflect.DeepEqual函数 指针和实例均可比较 指针和实例均可比较

Go语言结构体的值

我们可以通过字面量来给结构体赋值,对于使用 var 定义后不通过字面量来赋值的结构体,Go 语言会为其赋零值。

除了使用 var,还可以通过 struct{} 构建零值的结构体。如果仅仅想赋零值,建议采用 var。采用 struct{} 构建的结构体,通常用于返回给调用者,而不是赋给变量。

示例代码如下:
msg := model.Message{} //采用struct{}构建零值的结构体
err = json.Unmarshal(message, msg)
结构体的值 msg 被传递给了 json.Unmarshal 函数。另外,因为 Go 语言中只有值传递,所以结构体的体量越大,值传递造成的性能损失也就越大。

相关文章