目录
  1. 1. 程序结构
    1. 1.1. 命名
    2. 1.2. 声明
    3. 1.3. 变量
      1. 1.3.1. 简短变量声明
      2. 1.3.2. 指针
      3. 1.3.3. new函数
    4. 1.4. 赋值
    5. 1.5. 类型
    6. 1.6. 包和文件
      1. 1.6.1. 导入包
      2. 1.6.2. 包初始化
    7. 1.7. 作用域
  2. 2. 数据类型
    1. 2.1. 基础类型
      1. 2.1.1. Integer
      2. 2.1.2. Float
      3. 2.1.3. Complex
      4. 2.1.4. Bool
      5. 2.1.5. String
      6. 2.1.6. Const
    2. 2.2. 复合类型
      1. 2.2.1. Array
      2. 2.2.2. Slice
      3. 2.2.3. Map
      4. 2.2.4. Struct
      5. 2.2.5. Json
        1. 2.2.5.1. struct to json
        2. 2.2.5.2. json to struct
        3. 2.2.5.3. map to json
        4. 2.2.5.4. json to map

程序结构

命名

  • 命名规则:

    • 以字母或下划线开头,接任意字母、数字或下划线(区分大小写)。保留关键字及预定义名不可用
    • 名字首字母大小写决定该名在包外可见性
  • 保留关键字

    1
    2
    3
    4
    5
    break      default       func     interface   select
    case defer go map struct
    chan else goto package switch
    const fallthrough if range type
    continue for import return var
  • 预定义名

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    内建常量: true false iota nil

    内建类型: int int8 int16 int32 int64
    uint uint8 uint16 uint32 uint64 uintptr
    float32 float64 complex128 complex64
    bool byte rune string error

    内建函数: make len cap new append copy close delete
    complex real imag
    panic recover
  • 命名规范

    • 使用驼峰式方式

    • 包(package)名用小写,使用短命名

    • 接口(interface)名,单方法接口以函数名+er后缀,两个方法接口综合两个函数名+er后缀,三个以上取能精准描述接口目的的名字

      1
      2
      3
      4
      5
      6
      7
      8
      type Reader interface {
      Read(p []byte) (n int, err error)
      }

      type WriteFlusher interface {
      Write([]byte) (int, error)
      Flush() error
      }
    • 局部变量/函数参数,使用小写字母,尽量短小

    • 方法接收者(receiver)应该缩写,一般使用一个或者两个字符作为receiver的名称

    • 包(package)级别导出名不要把包名的意义再写一遍,bytes.Bufferbytes.ByteBuffer

声明

声明语句,包(package)级声明语句顺序无关紧要

  • var: 变量
  • const: 常量
  • type: 类型
  • func: 函数

声明示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var a int
var a, b, c int
var (
name string
age int
)
var mapper map[int]string // mapper = make(map[int]string) map是声明后必须使用make初始化才能使用
var names []string // 声明并初始化: var sliceName []type = make([]type, len, capacity),简写slice1 := make([]type, len, capacity)

const Pi float32 = 3.14159 // const Pi = 3.14

type Student struct {
id int
name string
age int
}
type WriteFlusher interface {
Write([]byte) (int, error)
Flush() error
}

func add(a, b int) int {
return a + b
}

变量

变量声明

1
2
3
var name type = expression	// var age int = 18
var name type // var age int // 自动赋予零值
var name = expression // var age = 18

type= expression可二选一,省略type会根据expression推导变量类型(var name = expression),省略expression会用零值初始化变量(var name type),各类型零值:

  • number: 0
  • bool: false
  • string: “” (空字符串)
  • slice/pointer/map/chan/func/interface: nil
  • array/struct: 每个元素/字段对于类型的零值

简短变量声明

函数内部可用简短变量声明语句声明初始化局部变量(只能用于函数内部),变量类型根据表达式自动推导

1
name := expression	// age := 18

相同词法域中,简短变量声明过的变量只能赋值

1
2
3
in, err := os.Open(infile)
// ...
out, err := os.Create(outfile) // err变量已简短声明过,此处是对err变量进行了赋值

简短变量声明语句中必须至少要声明一个新变量,否则编译不通过

1
2
3
f, err := os.Open(infile)
// ...
f, err := os.Create(outfile) // 编译报错,f和err都已声明过,只能赋值 // f, err = os.Create(outfile)

简短变量声明只对同级词法域声明过的变量进行赋值操作,变量在外部词法域则是重新声明一个新的变量

1
2
3
4
5
6
7
8
func main() {
test := 0
if true {
test := 1 // 此test变量是一个新变量, 无论怎么修改都不会影响if外的test变量
fmt.Println("main if", test) // 1
}
fmt.Println("main", test) // 0
}

指针

一个指针类型变量的值是另一个变量的地址(指针存储变量在内存中的位置),通过指针可直接读或更新对应变量的值,不需要知道变量名

1
2
3
4
5
6
7
x := 1
p := &x // p为指向x变量的指针,p的类型为 *int // & 为取地址操作
fmt.Println(*p) // 1 // *p表示读取指针指向变量的值
*p = 2 // 等价于 x = 2 // 通过指针修改变量值
fmt.Println(x) // 2
*p++ // 只是增加p指向的变量的值,并不改变p指针!!!
fmt.Println(x) // 3

任何类型的指针的零值都是nil

p指向某个有效变量,那么p != nil测试为true

指针可进行相等测试,只有当它们指向同一个变量或全部是nil时才相等

new函数

new(T)

  1. 创建一个T类型匿名变量
  2. 初始化匿名变量为T类型零值
  3. 返回匿名变量地址,返回的指针类型为*T

new(T)得到的是一个*T类型,值为T类型零值的匿名变量

1
2
3
4
p := new(int)	// p 类型为*int,值为0
fmt.Println(*p) // 0
*p = 2
fmt.Println(*p) // 2

赋值

1
2
3
4
5
6
x = 1                       // 命名变量的赋值
*p = true // 通过指针间接赋值
person.name = "bob" // struct结构体字段赋值
count[x] = count[x] * scale // array、slice或map的元素赋值
count[x] *= scale // 二元算术运算符和赋值语句的复合操作
v++;v-- // 数值变量支持递增/递减语句,自增/自减是语句,非表达式,x = v++ 是错误的!!!

元组赋值,允许同时更新多个变量的值

当函数调用出现在元组赋值右边的表达式中时,左边变量的数目必须和右边一致

1
2
3
x, y = y, x					// 交换两个变量
i, j, k = 2, 3, 5 // 多变量同时赋值
f, err = os.Open("foo.txt") // os.Open()返回2个值,进行元组赋值时左边也必须有2个变量

如果map查找、类型断言或channel接收出现在赋值语句的右边,它们都可能会产生两个结果,有一个额外的布尔结果表示操作是否成功

1
2
3
4
v, ok = m[key]             // map查找
v, ok = x.(T) // 类型断言
v, ok = <-ch // channel接收
_, ok = m[key] // 可使用_丢弃不需要的值

隐式赋值行为:

  • 函数调用隐式地将调用参数的值赋值给函数的参数变量
  • 返回语句会隐式地将返回操作的值赋值给结果变量
  • 复合类型的字面量所产生的赋值行为 medals := []string{"gold", "silver", "bronze"},赋值行为:medals[0] = "gold"

类型

类型声明语句一般在包级,<类型名>首字母大写则包外可见。类型声明语句如下:

1
type <类型名> <底层类型>	// type Celsius float64

类型转换操作,每一类型T,都有对应的类型转换操作T(x),用于将x转为T类型。只有当两个类型的底层基础类型相同时,才允许这种转型操作

命名类型可为该类型的值定义新行为,行为表现为一组关联到该类型的函数集合,称之为类型方法集

1
2
3
4
5
type Celsius float64	// 创建命令类型
func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) } // 为命名类型创建String()方法
var c Celsius
c = 100.0
fmt.Println(c.String()) // 调用String()方法

包和文件

Go语言中的包(package),目的是为了支持模块化、封装、单独编译和代码重用

一个包中保存在一个或多个以.go为文件后缀名的源文件,包级别的名字在同一个包的其他源文件可以直接访问,逻辑上所有代码都在一个文件一样

包所在目录路径是包的导入路径,e.g., 包导入路径: gopl.io/ch1/helloworld,目录路径: $GOPATH/src/gopl.io/ch1/helloworld

每个包对应一个独立的名字空间,通过首字母大小写控制包外可见性

导入包

每个包都有一个全局唯一的导入路径

每个包还有一个包名,包名命名以短小原则为主。一般而言,包名和包的导入路径的最后一个字段相同。e.g., gopl.io/ch2/tempconv,包名为tempconv

包被导入却没有使用会引发编译错误

包初始化

包的变量初始化:按变量声明顺序初始化,但变量间有依赖优先初始化被依赖变量

1
2
3
4
5
var a = b + c // a 第三个初始化, 为 3
var b = f() // b 第二个初始化, 为 2, 通过调用 f (依赖c)
var c = 1 // c 第一个初始化, 为 1

func f() int { return c + 1 }

包内有多个.go源文件,按照发给编译器顺序初始化,Go语言构建工具首先按.go文件的文件名字典顺序排序,然后依次传给编译器

初始化工作可交由init()初始化函数完成,init()初始化函数有以下特点:

  • 每个.go文件可包含多个init()初始化函数

  • init()初始化函数不能被调用无传参

  • 多个init()初始化函数,在程序执行时,按声明顺序被自动调用

包的初始化顺序如下图所示:

Go 包初始化

图片出处

  1. 初始化依赖包:初始化一个包前,必须完全初始化其所依赖的包,按包出现顺序初始化并且每个包只会初始化一次(即使多次引用也一次)
  2. 初始化const常量:初始化完依赖包后,以下的都属于包内初始化。首先是const常量的初始化
  3. 初始化var全局变量:按全局变量声明顺序初始化,但全局变量间有依赖优先初始化被依赖变量
  4. 初始化init()函数:按声明顺序初始化init()函数,若包内有多个.go文件,按文件名字典顺序初始化.go文件
  5. main()函数

作用域

  • 包级作用域:包内函数外定义,必须以var方式声明(不能简短声明),包内多个文件皆可使用,包外可见性以首字母大小写决定。包级作用域的变量,声明顺序不影响作用域范围

    1
    2
    3
    4
    var age int	// 包级作用域
    func main() {
    ...
    }
  • 函数作用域:传入参数(非外部引用型)、返回值声明参数、函数内部声明参数,以上都只能在函数内部使用。若外部有同名变量则内部覆盖外部

    1
    2
    3
    4
    5
    // str/name/age 都是函数作用域
    func test(str string) (name string) {
    age := 18
    ...
    }
    1
    2
    3
    4
    5
    6
    7
    8
    var cwd string

    func init() {
    cwd, err := os.Getwd() // 此处cwd为重新声明定义的变量,与包级cwd无丝毫关系,为init()函数作用域变量
    if err != nil {
    log.Fatalf("os.Getwd failed: %v", err)
    }
    }
  • for/if/switch局部作用域

    1
    2
    3
    4
    5
    if f, err := os.Open(fname); err != nil { // if局部作用域,只在if词法域中有效
    return err
    }
    f.ReadByte() // f 为if局部作用域变量,if外无法使用,f未定义
    f.Close() // compile error: undefined f

数据类型

Go语言中数据类型分为4类:

  • 基础类型:numeric、string、bool
  • 复合类型:array、struct
  • 引用类型:point、slicemapchannal、function
  • 接口类型:interface

基础类型

Integer

  • 有符号整型:int8int16int32(rune)、int64 (-2^(N-1) ~ 2^(N-1)-1)
  • 无符号整型:uint8(byte)、uint16uint32uint64 (0 ~ 2^N - 1)
  • 依赖系统类型:intuint (32位系统为int32uint32,64位系统为int64uint64)

二元运算优先级:上 > 下、左 > 右、可使用()提升优先级、%的符号和被取模数符号一致、/结果精度根据操作数定

1
2
3
4
5
*      /      %      <<       >>     &       &^
+ - | ^
== != < <= > >=
&&
||

Float

  • 单精度浮点型:float32
  • 双精度浮点型:float64

Complex

  • complex64:对应float32
  • complex128:对应float64

Bool

  • true
  • false

String

字符串是数个8位字节(8-bit bytes)的集合,通常是UTF-8编码的文本(可为其他编码)

字符串是不可改变的字节序列 (意味若两个字符串共享相同的底层数据是安全的),由于字符串不可改变,所以每次更改字符串需要重新分配一块新内存空间
Go的字符串有2种形式:

  • 解析性字符串:带“”的字节序列

    1
    2
    s := "hello world"
    fmt.Println(s) // hello world
  • 原生字符串:带反引号的字符序列

    1
    2
    s := `hello world\n`
    fmt.Println(s) // hello world\n // \n会被原样输出

len()函数返回字符串中的字节数;str[i]索引操作返回第i个字节值(0<= i <len(str)),超出索引范围会引发panic

str[i:j]子字符串操作返回第i个字节到第j-1个字节(并非第j个字节)

+操作拼接两个字符串

1
2
3
4
5
6
7
8
9
s := "hello, world"		// len(s) == 12
fmt.Println(s[0:5]) // "hello"
fmt.Println(s[:]) // "hello, world"
fmt.Println("goodbye" + " " + s[7:]) // "goodbye world"

s[0] = "H" // cannot assign to s[0] 字符串不可改变
Usage := `test // ``表示原生字符串字面值

Uage: xxx`

由于字符串不可改变,所以若想单独修改字符串中的字符,则需要将string转换为[]byte,修改完后再转换回string ([]byte既是[]uint8)

1
2
3
4
s := "hello world"
b := []byte(s)
b[0] = 'H' // 是''而非""
fmt.Println(string(b))

string转为runeruneint32别名(byteuint8别名),代表字符的Unicode编码,使用4个字节存储,将string转成rune就意味着任何一个字符都用4个字节来存储其Unicode值

1
2
3
4
5
6
s2 := "Go语言"
fmt.Pringln(len(s2)) // 8 // len()输出字节个数而非字符个数,一个UTF-8编码的中文等于3字节

r := []rune(s2)
fmt.Println(r) // [71 111 35821 35328] // 输出的是4个Unicode值
fmt.Pringln(len(r)) // 4

string转为numeric需要使用到strconv包:

  • strconv.Itoaint to string

    1
    2
    i := 123
    s := strconf.Itoa(i) // s 为 string类型,值为"123"
  • strconv.Atoistring to int

    1
    2
    s3 := "123"
    i, err := strconv.Atoi(s3) // i 为 int类型,值为123

Const

常量的值在编译阶段确定而非运行时,常量的底层类型都是基础类型(bool/numeric/string),常量的值不可修改,常量的二元运算结果也是常量

批量声明常量,除第一个必须有初始化表达式外,其余的可省略,省略则默认使用前面的初始化表达式

1
2
3
4
5
6
7
8
const pi float64 = 3.14		// const pi = 3.14 省略type也可,自动根据表达式确认type
const (
a = 1
b // 省略初始化表达式,默认使用前一个变量声明的初始化表达式
c = 2
d
)
fmt.Println(a, b, c, d) // 1 1 2 2

iota常量生成器,用于以相似规则生成常量。const声明语句中,第一个变量声明所在行iota将置0,然后每个有常量声明的行都+1iota按行递增

iota生成器被打断后,需要显示用iota恢复

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const (
a = iota
b // iota + 1
c = "test"
d // iota被打断,d 省略初始化表达式,默认使用前一个初始化表达式
e // 同 d
f = iota // 显示使用iota恢复,iota后每有一行变量声明都会+1,不管是否被打断
)
fmt.Pringln(a, b, c, d, e, f) // 0 1 test test test 5

const (
a, b = iota, iota + 1 // iota == 0
_, _ // iota == 1
c, d // iota == 2
e = iota + 10 // iota == 3
)
fmt.Pringln(a, b, c, d, e) // 0 1 2 3 13

const (
_ = 1 << (10 * iota)
KiB // 1024
MiB // 1048576
GiB // 1073741824
TiB // 1099511627776 (exceeds 1 << 32)
PiB // 1125899906842624
EiB // 1152921504606846976
ZiB // 1180591620717411303424 (exceeds 1 << 64)
YiB // 1208925819614629174706176
)

复合类型

Array

数组是由特定元素组成的固定长度序列([length]type),通过索引下标访问元素(0 ~ len(array) - 1)

数组长度是数组类型的组成部分,数组长度在编译阶段确定,故[3]int[4]int是两个不同的数组类型

[length]type中length用...代替,表示数组长度根据初始化值的个数决定

相同类型的数组(lengthtype都相同)可进行比较,不同类型的数组进行比较会编译出错panic

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var array [3]int
array[2] = 2
fmt.Println(array[0]) // 0 // 无初始化值赋值则默认为对应类型的零值

// var array1 [3]string = [3]string{"one", "two", "three"}
array1 := [3]string{"one", "two", "three"} // 声明并初始化赋值

for i, v := range array1 {
fmt.Println(i, v)
}

array2 := [...]int{1, 2, 3} // [3]int

r := [...]int{99: -1} // 含有100个元素,最后一个元素初始化为-1

Slice

切片是由特定元素组成的可变长序列([]type),通过索引下标访问元素(0 ~ cap(slice))

slice和array不同,slice间不能进行比较,slice唯一能进行比较操作的是和nil,零值slice等于nil(nil值的slice无底层array),但判断slice是否为空不能用s == nil,使用len(s) == 0

slice由3部分组成:

  • 指针pointer:指向第一个slice元素对应的底层array元素的地址 (slice的第一个元素不一定就是array的第一个元素)
  • 长度length:slice的元素个数 (length <= capacity),可使用len(slice)获得
  • 容量capacity:slice元素的最大个数,可使用cap(slice)获得
1
2
3
4
5
6
// src/runtime/sort.go
type slice struct {
array unsafe.Pointer // 指向所引用的数组指针 // unsafe.Pointer 表示任何可寻址的值的指针
len int
cap int
}

slice切片操作s[i:j](0<=i<=j<=cap(s)),获取第i个元素到第j-1个元素,len(s[i:j])j-icap(s[i:j])cap(s)-i

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func main() {
s := []int{1, 2, 3, 4, 5}
fmt.Println(s, len(s), cap(s)) // [1 2 3 4 5] 5 5

s1 := s[0:3]
fmt.Println(s1, len(s1), cap(s1)) // [1 2 3] 3 5

s2 := s[2:]
fmt.Println(s2, len(s2), cap(s2)) // [3 4 5] 3 3

for i, v := range s2 {
fmt.Println(i, v)
}
}

可使用内置的make()函数创建slice,make()创建一个匿名的数组变量并初始化赋值对应类型的零值,然后返回一个slice

1
2
3
4
5
6
// make([]T, len)
// make([]T, len, cap)

s4 := make([]int, 3)
s4[1] = 222
fmt.Println(s4) // [0 222 0]

使用append()函数向slice追加元素,使用append()函数必须注意一点:调用append()函数是,会先检测slice是否有足够的capacity保存新元素,如果有足够容量则直接扩展slice,保持底层数组不变;如果没有足够容量会先分配一个capacity * 2的新slice,然后将原slice内容复制到新slice,最后再添加新元素,由于新分配了内存空间,所以底层数组改变

1
2
3
4
5
6
7
8
9
s := []int{1, 2, 3}
fmt.Println(s) // [1 2 3]

s = append(s, 4)
fmt.Println(s) // [1 2 3 4]

s1 := []int{5, 6}
s = append(s, s1...) // 将一个slice追加到另一个slice
fmt.Println(s) // [1 2 3 4 5 6]
1
2
3
4
a := make([]int, 0, 10)		// a ---> 0x1234  无(data)  0(length)  10(capacity)
b := append(a, 1) // b ---> 0x1234 1(data) 1(length) 10(capacity) // a和b指向的底层数组是一致的,但a长度为0,b长度为1
_ = append(a, 2) // a和b指向的底层数组是相同的,此行修改了底层数组,a长度为0,append 2直接将array[0] = 2 (之前array[0] == 1 被覆盖)
println(b[0]) // 2
1
2
3
4
5
6
s := []int{5}					// 0x1234(array add)  5(data)      1(length)  1(capacity)
s = append(s, 7) // 0x5678(array add) 5,7(data) 2(length) 2(capacity) // 由于容量不够,capacity*2生成新slice(地址改变)
s = append(s, 9) // 0x9abc(array add) 5,7,9,[0](data) 3(length) 4(capacity) // 由于容量不够,capacity*2生成新slice(地址改变)
x := append(s, 11) // 0x9abc(array add) 5,7,9,11(data) 4(length) 4(capacity) // 由于上次的扩容,容量足够,无生成新slice
y := append(s, 12) // 0x9abc(array add) 5,7,9,12(data) 4(length) 4(capacity) // 由于上次的扩容,容量足够,无生成新slice导致数据覆盖
fmt.Println(s, x, y) // [5 7 9] [5 7 9 12] [5 7 9 12]

slice小技巧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 合并两个slice
a = append(a, b...)

// 删除[i, j)之间的元素
a = append(a[:i], a[j:]...)

// 删除位置i的元素
a = append(a[:i], a[i+1:]...)

// 位置i扩展长度为j的slice
a = append(a[:i], append(make([]T, j), a[i:]...)...)

// 在位置i插入元素j
a = append(a[:i], append([]T{j}, a[i:]...)...)

Map

map是无序的key/value对集合,map[K]V。map中所有key为同一类似,所有value为同一类型

map的key类型必须为支持比较操作==的数据类型,所以slicemapfunction类型无法做为map的key。map的key类型无任何限制

1
2
3
4
5
6
7
8
9
10
func echo() string {
return "testMap"
}

func main() {
ages := map[string]int{"alice": 31}
ages[echo()]++ // 使用echo()返回的string作为key
fmt.Println(ages) // map[alice:31 testMap:1]
ages[echo]++ // panic!!! cannot use echo (type func() string) as type string in map index echo为函数类型,不能作为key
}

创建map可直接使用内置make()函数,也可用map字面值初始化方式

1
2
3
4
5
6
7
8
9
ages := make(map[string]int)		// make()方式创建
ages[alice] = 31

ages := map[string]int { // map字面值初始化方式创建
"alice": 31,
"charlie": 34,
}

ages := map[string]int{} // map字面值创建空map

map通过key下标访问value;通过delete()删除元素;通过for/range遍历map,遍历输出的顺序是随机的;通过if/ok判断是否存在value

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
fmt.Println(ages['alice'])		// 31
delete(ages, "alice") // 删除元素

for key, value := range ages { // map遍历,随机输出
fmt.Println(key, value)
}

// 通过sort.Strings实现顺序输出
names := []stirng
for name := range ages {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
fmt.Println(name, ages[name])
}

// 判断是否存在key/value
if age, ok := ages["bob"]; !ok {
// not exists
}

声明map后必须要创建map后才可进行赋值操作

1
2
3
var testMap map[string]int
testMap = make(map[string]int) // 声明后必须使用make()创建才可使用
testMap["alice"] = 31

map中的value是不可寻址的,对map的value进行取地址操作或让value出现在赋值语句=左边都会引起panic

1
_ := &ages["alice"]		// panic!!! cannot take the address of ages["alice"]  map中value不可寻址
1
2
3
4
5
6
7
8
9
10
11
type Student struct {
name string
}

func main() {
m := map[string]Student{"people": {"test"}}
//m := map[string]*Student{"people": {"test"}} // 解决办法
m["people"].name = "test1" // panic!!! 赋值语句=的左边对象必须是可寻址的,或者是map的index操作,或者是_,m["people"].name 是不可寻址的,不能在赋值语句左边

fmt.Println(m["people"].name)
}

Struct

struct结构体是由零个或多个任意类型的值组合而成,值称为结构体的成员,成员通过.点操作符访问,如:struct.member,不包含任何成员的结构体为空结构体struct{}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type Employee struct {		// 声明struct
ID int
Name string
Address string
DoB time.Time
Position string
Salary int
ManagerID int
}

var dilbert Employee // 声明Employee类型变量
dilbert.Salary += 500 // 通过点符号操作成员

// 使用结构体字面值构建结构体
albert := Empolyee{1, "albert"} // 不指定成员名,但顺序比较和声明struct的成员顺序一致
lily := Empolyee{ID: 2, Name: "lily", Position: "china"} // 使用成员名构建struct,被忽略的其他成员使用对应类型的零值

相同类型的结构体是可以进行比较的,结构体是否是相同类型,取决于以下2点:

  1. 成员类型、成员个数相同
  2. 成员顺序相同

成员名首字母是否大小写决定其能否导出,首字母大写的成员可导出(包外可访问)

复合类型(arraystruct)的值不能包含其自身,即S结构体类型将不能再有S类型的成员,但可有*S指针类型的成员

struct允许嵌套,即一个struct中嵌入另一个struct

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type Point struct {
X, Y int
}

type Circle struct {
Center Point
Radius int
}

type Wheel struct {
Circle Circle
Spokes int
}

var w Wheel
w.Circle.Center.X = 8
w.Circle.Center.Y = 8

struct还支持匿名成员,即只声明成员的类型,而不指名成员的名称,但匿名成员的类型必须是命名类型或指向命名类型的指针。其实匿名成员的名称既是其命名类型的名称(隐式名称),由于此,所以不能有两个类型相同的匿名成员,否则会导致成员名称冲突

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
type Point struct {
X, Y int
}

type Circle struct {
Point // 匿名成员
Radius int
}

type Wheel struct {
Circle // 匿名成员
Spokes int
}

var w Wheel
w.X = 8 // 相当于w.Circle.Point.X
w.Y = 8 // 相当于w.Circle.Point.Y

// 匿名类型的字面值初始化
w = Wheel{
Circle: Circle{ // 此处可看出匿名成员的名称既是其命名类型的名称
Point: Point{X: 8, Y: 8},
Radius: 5,
},
Spokes: 20,
}

Json

在需要对struct结构体成员添加额外元信息时,会使用到成员tag。通常以key:“value”形式存在,key为encoding/<key>包的名称,表示添加哪种格式的信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type Movie struct {
Title string
Year int `json:"released"` // 因为需要被enconding/json包使用,成员名首字母必须大写
Color bool `json:"color,omitempty"` // omitempty选项,表示当Go语言结构体成员为空或零值时不生成JSON对象
Actors []string
}

var movies = []Movie{
{Title: "Casablanca", Year: 1942, Color: false,
Actors: []string{"Humphrey Bogart", "Ingrid Bergman"}},
{Title: "Cool Hand Luke", Year: 1967, Color: true,
Actors: []string{"Paul Newman"}},
{Title: "Bullitt", Year: 1968, Color: true,
Actors: []string{"Steve McQueen", "Jacqueline Bisset"}},
}
struct to json
1
2
3
4
5
6
7
jsonBytes, err := json.Marshal(movies)
// jsonBytes, err := json.MarshalIndent(movies, "", " ") // 格式化输出
if err != nil {
panic(err)
}

fmt.Println(string(jsonBytes))
json to struct
1
2
3
4
5
6
7
8
9
10
11
jsonStr := `{
"title": "Casablanca",
"released": 1942,
"actors": ["Humphrey Bogart","Ingrid Bergman"]
}`


var movie Movie
if err := json.Unmarshal([]byte(jsonStr), &movie); err != nil {
panic(err)
}
fmt.Println(movie)
map to json
1
2
3
4
5
6
7
8
9
10
11
12
mapJson := map[string]interface{}{
"title": "Casablanca",
"released": 1942,
"actors": `["Humphrey Bogart","Ingrid Bergman"]`,
}

//jsonBytes, err := json.Marshal(mapJson)
jsonBytes, err := json.MarshalIndent(mapJson, "", " ")
if err != nil {
panic(err)
}
fmt.Println(string(jsonBytes))
json to map
1
2
3
4
5
6
7
8
9
10
11
jsonStr := `{
"title": "Casablanca",
"released": 1942,
"actors": ["Humphrey Bogart","Ingrid Bergman"]
}`


var movieMap map[string]interface{}
if err := json.Unmarshal([]byte(jsonStr), &movieMap); err != nil {
panic(err)
}
fmt.Println(movieMap)

Powered: Hexo, Theme: Nadya remastered from NadyMain