结构体 - Go 语言学习笔记

241 阅读4分钟

概述

结构体是一种聚合的自定义数据类型,是由零个或任意多个相同或不同类型的变量聚合而成的数据集合,每个变量称为结构体成员(也可称之为成员变量,字段,属性)。

定义结构体

使用 type 和 struct 语句定义结构体,type 语句设定了结构体的类型名称,struct 语句定义一个新的数据类型,结构体中有一个或多个成员。

格式如下:

/* 
type 类型名 struct {
    字段1  字段1类型
    字段2  字段2类型
    …
    字段3  字段3类型
}
*/
type struct_variable_type struct {
    member definition;
    member definition;
    ...
    member definition;
}

// 或者 同一类型的字段,可以定义在一行
type Color struct {
    R, G, B byte
}

注意:

  1. 如果一个结构体的成员变量名称是首字母大些的,那么这个变量是可导出的(即在其它包可以访问到),反之不可导出。如下:
type A struct {
    Hour int    // 可导出
    minute int  // 不可导出
}
  1. 结构体的定义只是一种内存布局的描述,只有当结构体实例化时,才会真正地分配内存。

结构体实例化

  1. 通过 var 声明结构体完成实例化

结构体本身是一种类型,可以像整型、字符串等类型一样,以 var 的方式声明结构体即可完成实例化。

structure_variable_type 为结构体类型,variable_name 为结构体的实例。

// 给 variable_name 分配内存,并零值化内存,但是这个时候的 variable_name 的类型是 structure_variable_type
var variable_name structure_variable_type
  1. 使用 new() 实例化

使用 new 函数给一个新的结构体变量分配内存,它返回指向已分配内存的指针:var variable_name *structure_variable_type = new(structure_variable_type)

variable_name := new(structure_variable_type)
  1. 使用字面量初始化
variable_name := structure_variable_type {value1, value2, valuen3}
或
variable_name := structure_variable_type {key1: value1, key2: value2, key3: value3}

variable_name := &structure_variable_type {value1, value2, valuen3}  // 等效于 new(structure_variable_type)

使用结构体

1. 访问结构体成员

要访问结构体成员,需要使用点号 . 操作符,格式为:

// 结构体.成员名
type Point struct {
    X int
    Y int
}
var p Point
p.X = 10
p.Y = 20

2. 比较结构体

如果结构体的所有成员变量都可以比较,那么这个结构体是可以比较的。两个结构体的比较可以使用 == 或者 !=。

例如:

type C struct {
    A int
    B string
}
c1 := C{A:1, B:"abc"}
c2 := C{A:1, B:"abc"}
c3 := C{A:2, B:"abc"}
fmt.Println(c1.A == c2.A && c1.B == c2.B)   // true
fmt.Println(c1 == c2)                       // true 与上等价
fmt.Println(c1.A == c3.A && c1.B == c3.B)   // false
fmt.Println(c1 == c3)                       // false 与上等价

和其他可比较的类型一样,可比较的结构体类型都可以作为map的键类型。

例如:

type C struct {
    A int
    B string
}
mp := make(map[C]int)
key := C{A:1, B:"abc"}
mp[key] = 9
fmt.Println(mp[C{A:1, B:"abc"}])    // 9

3. 结构体嵌套和匿名成员

Go语言允许我们定义不带(省略)字段名只写类型的结构体成员,这种结构体成员称作匿名成员。因为结构体中字段名必须是唯一的,所以匿名成员的类型也必须是唯一的;

结构体成员的类型必须是一个命名类型或者指向命名类型的指针。正是因为有了这种结构体嵌套的功能,我们才能直接访问到我们需要的变量而不是指定一大串中间变量。

type Point struct {
    X int
    Y int
}
type Circle struct {
    Point
}
var c Circle
c.X = 10    // 等价于 c.Point.X = 10
c.Y = 10    // 等价于 c.Point.Y = 10

type Wheel struct {
    *Point
}

结构体字面量初始化没有快捷方式,必须遵循形状类型的定义。

type Point struct {
    X int
    Y int
}
type Circle struct {
    Point
}
var c Circle
c = Circle{1,1}                 // 错误
c = Circle{Point{1,1}}          // 正确
c = Circle{Point: Point{1,1}}   // 正确

因为“匿名成员”拥有隐式的名字,所以你不能在一个结构体里面定义两个相同类型的匿名成员,否则会引起冲突。由于匿名成员的名字是由它们的类型决定的,因此它们的可导出性也是由他们的类型决定。在下面的例子中,point和circle这两个匿名成员是可导出的,即使这两个结构体是不可导出的(point和circle)。

type point struct {
    X int
    Y int
}
type circle struct {
    point
}
type Wheel struct {
    circle
}
var w Wheel
w.X = 8     // 等价于 w.circle.point.X = 8, w.X是可导出的,w.circle.point.X是不可导出的

4. 结构体作为函数参数

可以像其他数据类型一样将结构体类型作为参数传递给函数。并以实例的方式访问结构体变量。 例如:

package main

import "fmt"

type Books struct {
    title string
    author string
    subject string
    book_id int
}

func changeBook(book Books) {
    book.title = "book1_change"
    fmt.Println(book)
}

func main() {
    var book1 Books;
    book1.title = "book1"
    book1.author = "zuozhe"
    book1.book_id = 1
    changeBook(book1)
    fmt.Println(book1)
}

函数中结构体作为参数,如果不是用结构指针,函数内参数属性的改变不影响原来结构体数据。 结果为:

{book1_change zuozhe  1}
{book1 zuozhe  1}

5. 结构体指针

可以定义指向结构体的指针类似于其他指针变量,格式如下:

var struct_pointer *Books

以上定义的指针变量可以存储结构体变量的地址。查看结构体变量地址,可以将 & 符号放置于结构体变量前:

struct_pointer = &Book1

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

struct_pointer.title