GoGo
1. 结构体的定义
typestruct
typestruct

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

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

结构体的标准格式如下:

type structName struct {
   member type;
   member type;
   ...
   member type;
}
2. 结构体实例化

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

package main

import (
	"fmt"
)

type Student struct {
	Id int
	Name string
	Age int
	Mobile int
	Major 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}
{}newnew{}
{}&new
.
s1.Name = "hi hzwy23"
s3.Name = "hello hzwy23"
fmt.Println(s3.Name)
fmt.Println(s1.Name)
.

2.1 普通实例化

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

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

type Point struct {
    X int
    Y int
}

var p Point
p.X = 10
p.Y = 20
.p.Xp.Y
nilPointPoint{}

2.2 new 实例化

Gonew
new
ins := new(T)

其中:

TinsTinsins*T
Go.
type Player struct{
    Name string
    HealthPoint int
    MagicPoint int
}

tank := new(Player)
tank.Name = "Canon"
tank.HealthPoint = 300
new
new->
Go.Goins.Name(*ins).Name

2.3 取地址实例化

Go& new
ins := &T{}

其中:

Tins*T

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

type Command struct {
    Name    string    // 指令名称
    Var     *int      // 指令绑定的变量
    Comment string    // 指令的注释
}

var version int = 1

cmd := &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)对应字段需要初始化的值。

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

0falsenil

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

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

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

type People struct {
    name  string
    child *People
}

relation := &People{
    name: "爷爷",
    child: &People{
        name: "爸爸",
        child: &People{
                name: "我",
        },
    },
}

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

3.2 多值列表初始化

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

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

  • 必须初始化结构体的所有字段
  • 每一个初始值的填充顺序必须与字段在结构体中的声明顺序一致
  • 键值对与值列表的初始化形式不能混用
type Address struct {
    Province    string
    City        string
    ZipCode     int
    PhoneNumber string
}

addr := Address{
    "陕西",
    "西安",
    710000,
    "12345",
}

fmt.Println(addr)	// {陕西 西安 710000 12345}

3.3 初始化注意事项

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

package main

func main() {
	var a struct { x int } = { 100 }  // syntax error,  syntax error: unexpected {, expecting expression
	var b []int = { 1, 2, 3 }  // syntax error
	c := 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
Goroutine
var c = make(chan Empty) // 声明一个元素类型为Empty的channel
c<-Empty{}               // 向channel写入一个“事件”
channelGoroutine
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 main

import (
    "fmt"
)

// 定义 printMsgType() 函数,参数为 msg,类型为 *struct{id int data string},
// 因为类型没有使用 type 定义,所以需要在每次用到的地方进行定义。
func printMsgType(msg *struct {
    id   int
    data string
}) {

    // 使用动词%T打印msg的类型
    fmt.Printf("%T\n", msg)	// *struct { id int; data string }
}

func main() {

    // 实例化一个匿名结构体
    msg := &struct {  // 定义部分
        id   int
        data string
    }{  // 值初始化部分
        1024,
        "hello",
    }

    printMsgType(msg)
}
5. 结构体访问
.
结构体.成员名
struct
package main

import "fmt"

type Phone struct {
	model string
	color string
	price int
}

func main() {

	var phone1 Phone
	var phone2 Phone
	var phone4 Phone

	fmt.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 = 8000
	phone4.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 main

import "fmt"

type Phone struct {
	model string
	color string
	price int
}

func main() {

	var phone Phone

	// 访问结构体成员
	phone.price = 8000
	phone.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 main

import "fmt"

type InnerData struct {
	a int
}

type Data struct {
	complax  []int
	instance InnerData
	ptr      *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 main

import "fmt"

type Phone struct {
	model string
	color string
	price int
}

func main() {

	var p *Phone
	var phone Phone

	p = &phone
	// 访问结构体成员
	p.price = 8000
	p.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
    ... ...
}
Goinvalid recursive type T

但是下面的是可以的

type T struct {
    t  *T           // ok
    st []T          // ok
    m  map[string]T // ok
}     

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

mapintmap
valuemap
9. 零值初始化
GoGo
var book Book // book为零值结构体变量
GosyncMutex
GoCC
pthread_mutex_t mutex; 
pthread_mutex_init(&mutex, NULL);

pthread_mutex_lock(&mutex); 
... ...
pthread_mutex_unlock(&mutex); 
Cmutexpthread_mutex_initlockunlock
Go
var mu sync.Mutex
mu.Lock()
mu.Unlock()
Gosync.MutexMutexlockunlock
10. 结构体可比较性

结构体可比较性