# 数据类型

## 值类型和引用类型

在golang中，同其他语言相同，可以将类型大致分为两类：**值类型**与**引用类型**。

***

其中，**值类型**存储在栈中，他们在编译期就可以确定变量所占用的空间，并且值类型变量直接指向栈空间的值。使用等号`=`将一个变量的值赋给另一个变量时，如`j = i`,实际上是在内存中将 `i` 的值进行了拷贝。我们可以通过 `&i` 获取变量 `i` 的内存地址。在golang中，属于值类型的数据类型包含：

* 数字类型
* 布尔类型
* 字符串类型
* 数组：数组也存放在栈空间中，这个与Java不同
* 结构体：因为是值传递，所以在方法之间传递会进行值复制，如果结构体较大，需要使用指针

***

而**引用类型**拥有更复杂的存储结构，推崇存储在堆内存中，编译时一般无法确定其所占空间：

1. 需要通过make创建并分配内存
2. 初始化一系列属性：指针、长度、哈希分布、数据队列等。一个引用类型的变量`r1`存储的是`r1`的值所在的内存地址（数字），或内存地址中第一个元素所在的位置，这个内存地址被称之为指针，这个指针实际上也被存在另外的某一个变量中。

在golang中，是引用类型的有：

* 指针
* 管道 channel
* 接口 interface
* 映射 map
* 函数 function

***

> \*\*值类型在传递参数时，进行值拷贝，引用类型则是引用拷贝。\*\*主要是赋值的区别。

## 类型零值

零值也就是默认值，当一个类型声明之后没有被初始化，那么他的值就是零值，每种类型都有对应的零值，其中，引用类型对应的零值都为`nil`：

| 数据类型                                                           | 零值            |
| -------------------------------------------------------------- | ------------- |
| 数字类型，包含整型、浮点型、复数                                               | 0             |
| 布尔型                                                            | false         |
| 字符串                                                            | ""            |
| 结构体                                                            | 结构体中的每个字段都是零值 |
| 引用类型：`slice`， `pointer`，`map`，`channel`，`function`，`interface` | `nil`         |

### nil详解

在Go语言中，布尔类型的零值（初始值）为 `false`，数值类型的零值为 `0`，字符串类型的零值为空字符串`""`，而指针、切片、映射、通道、函数和接口的零值则是 `nil`。

> 注意，nil是一个值，像0一样，代表不存在

注意，使用`nil`，要注意下面几点：

* 不是关键字和保留字

  因为不是关键字和保留字，只是一个标识符，类似int等，所以可以定义一个名称为`nil`的变量：

  ```go
  var nil = errors.New("my god") 
  ```
* 没有默认类型

  ```go
  package main
  import (
      "fmt"
  )
  func main() {
      fmt.Printf("%T", nil) // .\main.go:9:10: use of untyped nil
      print(nil)
  }
  ```
* 引用类型的零值(初始值)都是nil

  ```go
  package main

  import (
      "fmt"
  )

  func main() {
      var m map[int]string
      var ptr *int
      var c chan int
      var sl []int
      var f func()
      var i interface{}
      fmt.Printf("%#v\n", m) // map[int]string(nil)
      fmt.Printf("%#v\n", ptr) // (*int)(nil)
      fmt.Printf("%#v\n", c) // (chan int)(nil)
      fmt.Printf("%#v\n", sl) // []int(nil)
      fmt.Printf("%#v\n", f) // (func())(nil)
      fmt.Printf("%#v\n", i) // <nil>
  }
  ```
* 不同引用类型的nil值，指针是相同的

  ```go
  package main
  import (
      "fmt"
  )
  func main() {
      var arr []int
      var num *int
      fmt.Printf("%p\n", arr) // 0x0
      fmt.Printf("%p", num) // 0x0
  }
  ```
* 不同引用类型的nil值，占用大小不同

  ```go
  package main
  import (
      "fmt"
      "unsafe"
  )
  func main() {
      var p *struct{}
      fmt.Println( unsafe.Sizeof( p ) ) // 8
      var s []int
      fmt.Println( unsafe.Sizeof( s ) ) // 24
      var m map[int]bool
      fmt.Println( unsafe.Sizeof( m ) ) // 8
      var c chan string
      fmt.Println( unsafe.Sizeof( c ) ) // 8
      var f func()
      fmt.Println( unsafe.Sizeof( f ) ) // 8
      var i interface{}
      fmt.Println( unsafe.Sizeof( i ) ) // 16
  }
  ```
* 两个`nil`字面量比较

  ```go
  package main
  import (
      "fmt"
  )
  func main() {
      fmt.Println(nil==nil)  // 报错
  }
  ```
* 两个值都为`nil`的变量不可比较

  ```go
  package main
  import (
      "fmt"
  )
  func main() {
      // 不同类型变量
      var m map[int]string
      var ptr *int
      fmt.Printf(m == ptr) // invalid operation: arr == ptr (mismatched types []int and *int)
  }
  ```

  ```go
  package main
  import (
      "fmt"
  )
  func main() {
      // 相同类型变量
      var s1 []int
      var s2 []int
      fmt.Printf(s1 == s2) // invalid operation: s1 == s2 (slice can only be compared to nil)
  }
  ```
* 变量(有可能是nil 或者 非nil)可以与nil值比较：

  ```go
  package main
  import (
      "fmt"
  )
  func main() {
      var s1 []int
      fmt.Println(s1 == nil) // true
  }
  ```

## 基本数据类型

### 数值型

#### 整型

**无符号整型**

| 数据类型     | 存储空间(字节)   | 值范围                         | 数据级别   | 默认值 |
| -------- | ---------- | --------------------------- | ------ | --- |
| `uint8`  | 1          | 0 \~ 255                    | 百      | 0   |
| `uint16` | 2          | 0 \~65535                   | 6万多    | 0   |
| `uint32` | 3          | 0 \~ 4294967295             | 40多亿   | 0   |
| `uint64` | 4          | 0 \~ 18446744073709551615   | 大到没有概念 | 0   |
| `uint`   | 系统决定，无符号整型 | 32位系统位`int32`，64位系统位`int64` |        | 0   |

```go
var ui2 uint8 = 1
fmt.Printf("类型 uint8, 长度=%d字节 \n", unsafe.Sizeof(ui2))
var ui3 uint16 = 1
fmt.Printf("类型 uint16, 长度=%d字节 \n", unsafe.Sizeof(ui3))
var ui4 uint32 = 1
fmt.Printf("类型 uint32, 长度=%d字节 \n", unsafe.Sizeof(ui4))
var ui5 uint64 = 1
fmt.Printf("类型 uint64, 长度=%d字节 \n", unsafe.Sizeof(ui5))
```

**有符号整型**

| 数据类型    | 存储空间(字节)   | 值范围                                         | 数据级别     | 默认值 |
| ------- | ---------- | ------------------------------------------- | -------- | --- |
| `int8`  | 1          | -128 \~ 127                                 | 正负百      | 0   |
| `int16` | 2          | -32768 \~ 32767                             | 正负3万多    | 0   |
| `int32` | 3          | -2147483648 \~ 2147483647                   | 正负大20多亿  | 0   |
| `int64` | 4          | -9223372036854775808 \~ 9223372036854775807 | 正负大到没有概念 | 0   |
| `int`   | 系统决定，有符号整型 | 32位系统位`int32`，64位系统位`int64`                 |          | 0   |

```go
var i2 int8 = 1
fmt.Printf("类型 int8, 长度=%d字节 \n", unsafe.Sizeof(i2))
var i3 int16 = 1
fmt.Printf("类型 int16, 长度=%d字节 \n", unsafe.Sizeof(i3))
var i4 int32 = 1
fmt.Printf("类型 int32, 长度=%d字节 \n", unsafe.Sizeof(i4))
var i5 int64 = 1
fmt.Printf("类型 int64, 长度=%d字节 \n", unsafe.Sizeof(i5))
```

**字符整型**

| 数据类型   | 存储空间(字节)   | 作用               |
| ------ | ---------- | ---------------- |
| `byte` | `uint8`的别名 | 用与表示一个ASCII      |
| `rune` | `int32`的别名 | 用于表示一个Unicode代码点 |

```go
// 获取对应字符的ASCII码值
var a byte = 'a'
fmt.Println(a)

// 将字符串转换为Unicode码点
first := "Hello, 世界"
fmt.Println([]rune(first))
```

**表示一个ASCII：**

```go
var ch byte = 65
var ch byte = '\x41' // 16进制
var ch byte = 'A'
```

**表示一个Unicode代码点：**

```go
var ch byte ='\u0041' // ASCII码值 六十进制41 十进制65 对应大写字母A
fmt.Printf("%c", ch) // ASCII对应单个字节，故可以使用byte类型接收

var ch2 rune = '\u266b' // 对应ASCII字符 🎵
fmt.Printf("%c", ch2) // 占用四个字节，故使用rune接收

var ch3 int64 = '\U0001F4B8' // 部分ASCII码值（比如生僻汉字），会占用四个字节以上，💸
fmt.Printf("%c", ch3)  // 所以使用`\U`大写U字母来表示一个Unicode字符，占用8个字节，不够前面补0
```

#### 浮点型

| 数据类型           | 存储空间(字节) | 值范围                                                                                                       | 数据级别    | 默认值 |
| -------------- | -------- | --------------------------------------------------------------------------------------------------------- | ------- | --- |
| `float32`（不常用） | 3        | IEEE-754 1.401298464324817070923729583289916131280e-45 \~ 3.402823466385288598117041834516925440e+38      | 精度6位小数  | 0   |
| `float64`      | 4        | IEEE-754 4.940656458412465441765687928682213723651e-324 \~ 1.797693134862315708145274237317043567981e+308 | 精度15位小数 | 0   |

```go
fmt.Println("---------------- 浮点型 ------------------")
f0 := 3.14
fmt.Printf("类型推断： %T \n", f0)
var f1 float32 = 1
fmt.Printf("类型 float32, 长度=%d字节 \n", unsafe.Sizeof(f1))
var f2 float64 = 1
fmt.Printf("类型 float64, 长度=%d字节 \n", unsafe.Sizeof(f2))
// float32可能会有精度丢失，所以通常使用float64
var f3 float32 = 314e-2 // 314 / 10^2
fmt.Printf("指数方式表示: %f \n", f3)
var f4 float32 = 0.0314e2 // 0.0314 * 10^2
fmt.Printf("指数方式表示: %f \n", f4)
```

#### 复数

> 我们把形如 z=a+bi（a、b均为实数）的数称为复数。其中，a 称为实部，b 称为虚部，i 称为虚数单位。当 z 的虚部 b＝0 时，则 z 为实数；当 z 的虚部 b≠0 时，实部 a＝0 时，常称 z 为纯虚数。复数域是实数域的代数闭包，即任何复系数多项式在复数域中总有根。
>
> **复数的运算：**
>
> * 加法法则：(a+bi)+(c+di)=(a+c)+(b+d)i\`
> * 减法法则：(a+bi)-(c+di)=(a-c)+(b-d)i\`
> * 乘法法则：`(a+bi)(c+di)=(ac-bd)+(bc+ad)i`
> * 除法法则：![img](https://2351062869-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7b2CdwBN9liniVJpfEAc%2Fuploads%2Fgit-blob-f8211dbbfcb0af0350c79da987da36770610f7fc%2F150fac16efdf26fc71fc5c617adfe151.svg?alt=media)

| 数据类型         | 存储空间(字节) | 默认值      |
| ------------ | -------- | -------- |
| `complex64`  | 8        | `(0+0i)` |
| `complex128` | 16       | `(0+0i)` |

```go
var name complex128 = complex(x, y)
```

复数的值由三部分组成 `RE + IMi`，其中 `RE` 是实数部分，`IM` 是虚数部分，`RE` 和 `IM` 均为 `float` 类型，而最后的 `i` 是虚数单位。

**复数的定义和运算：**

```go
var x complex128 = complex(1, 2) // 1. 入门+2i
var y complex128 = complex(3, 4) // 3+4i
fmt.Println(x + y)               // "(4+6i)" 加法运算 
fmt.Println(x * y)               // "(-5+10i)"  惩罚运算
fmt.Println(real(x * y))         // "-5"  获取实部
fmt.Println(imag(x * y))         // "10"  获取虚部
```

### 布尔型

| 数据类型   | 存储空间(字节) | 值范围            | 默认值   |
| ------ | -------- | -------------- | ----- |
| `bool` | 1        | `true`,`false` | false |

```go
var a bool = true
fmt.Printf("bool的类型为： %T, 长度为 %d \n", a, unsafe.Sizeof(a))
```

### 字符串

* 字符串是一个不可变的UTF-8字符序列，并且每个属于ASCII码的字符占用单个字节，其他字符则是占用2-4个字节
* 不同于C、C++、Java，他们的UTF-8字符长度至少会占用两个字节
* Go语言这样做不仅减少了内存和硬盘空间占用，同时也不用像其它语言那样需要对使用 UTF-8 字符集的文本进行编码和解码
* 双引号字符串支持转义字符

#### 访问字符

```go
var a string = "Hello 世界"
fmt.Printf("%c \n", []byte(a)[0]) // H
fmt.Printf("%c \n", []rune(a)[7]) // 界
```

#### 字符拼接 "+"

```go
fmt.Println("a" + "b") // 注意，其他类型不会进行字符串转换
```

#### 多行字符串

多行字符串中的内容都会原样输出，转义字符不会生效。通常用语内嵌代码或者数据

```go
const str = `第一行
第二\n行
第三行`
fmt.Println(str)
```

输出结果为：

```
第一行
第二\n行
第三行
```

### 类型转换

#### 数值类型相互转换

```go
// GO语言数据类型转换，只有显示转换（强制类型转换），没有隐式转换
fmt.Println("----------- 基本数据类型互相转换 -------------")
var i int = 100
var f float32 = float32(i)
fmt.Println(f)

// 注意，将大范围值转换为小范围值，可能会造成数据损失
var i1 int64 = 8888
var i2 int8 = int8(i1)
fmt.Println(i2)

// 赋值左右数据类型一致，否则会报错
var n1 int32 = 12
var n2 int64 = int64(n1) + 31
fmt.Println(n2)
```

#### 基本类型 -> `string`

```go
fmt.Println("--------------基本数据类型转换为string------------")
// 使用fmt.Sprintf(), S代表返回string，printf代表格式化打印
var s = fmt.Sprintf("%d", 100)
fmt.Println(s)

r1 := strconv.FormatInt(678, 10) // 将int数字678转换为10进制字符串
r2 := strconv.FormatInt(678, 16) // 将int数字678转换为16进制字符串
fmt.Println(r1)
fmt.Println(r2)

// 参数1 要进行format的浮点数
// 参数2 格式化格式，参见API文档
// 参数3 保留小数点为3位
// 参数4 代表该浮点数的bit大小，如果是float64则为64
f1 := strconv.FormatFloat(10.02, 'f', 3, 64)
fmt.Println(f1)

r3 := strconv.FormatBool(true)
fmt.Println(r3)
```

#### `string`->基本类型

```go
fmt.Println("--------------string转换为基本类型------------")
b, _ := strconv.ParseBool("true")
fmt.Println(b)
i, _ := strconv.ParseInt("A", 16,64) // 将字符串转换, 第二个参数代表该字符串是16进制，第三个值是值数据的类型int64
fmt.Println(i)
f, _ := strconv.ParseFloat("12.00865", 64)
fmt.Println(f)

// 无效类型转换，将会返回对应类型的默认值
b2, _ := strconv.ParseBool("e")
fmt.Println(b2)
```

## 派生数据类型

派生数据类型又称为复合数据类型，是由基本类型复合而来。

### 指针

#### 基本类型也有对应的指针类型

```go
*int
*float32
*bool
...
```

#### 通过&创建指针变量

```go
var i = 100
// 通过取地址符&定义一个指针变量，指针变量通过 *type表示
var ptr *int = &i
// 指针变量是存放指定变量值得内存地址的引用
fmt.Println(ptr) // 0xc00001a0b8

// 初始化指针变量值的时候，初始值一定是地址
// 下面代码会报错
//var b = 89
//var ptrb *int = b
```

#### 通过\*获取指针变量对应的值

```go
fmt.Println(*ptr) // 100，会根据指针变量的地址0xc00001a0b8，找到这个地址对应的内存空间中的值
```

#### 改变指针变量指向的值

```go
*ptr = 200
fmt.Println(i) // 200
fmt.Println(&i) // 再次查询地址将会发现地址已经发生变化

// 什么类型的指针，接收的一定是相应类型的值，如果不是会报错
// 下面代码会报错
//var c int32 =16
//var ptrc *float32 = &c
```

#### 通过new()函数创建指针变量

```go
str := new(string)
*str = "你好"
fmt.Println(*str)
```

### 数组

#### 定义数组

```go
// 定义数组变量，不初始化
var balance [10]float32
// 初始化数组，没有赋值的元素将会赋予类型默认值
balance = [10]float32{0, 1, 2, 3, 4, 5}
fmt.Println(balance)

// 定义并初始化数组
var balance2 = [10]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
fmt.Println(balance2)

var balance3 [10]float32 = [10]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
fmt.Println(balance3)

balance4 := [10]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
fmt.Println(balance4)

// 定义数组，不显式指定数组长度
balance5 := [...]float32 {1000.0, 2.0, 3.4, 7.0, 50.0}
fmt.Println(balance5)
```

#### 下标访问

```go
// 访问数组元素：下标直接访问
fmt.Println(balance5[0])
```

#### 遍历数组：`for`

```go
// 访问数组元素：遍历数组
for i := 0; i < len(balance5); i++ {
  fmt.Printf("%f ", balance5[i])
  if i == len(balance5) - 1 {
    fmt.Println()
  }
}
```

#### 遍历数组：`for range`

```go
// for range 遍历
for index, value := range balance5 {
  fmt.Printf("[%d]=%f ", index, value)
  if index == len(balance5) - 1 {
    fmt.Println()
  }
}
```

#### 数组比较

必须保证数组类型长度相同，才能进行数组比较：

```go
a := [2]int{1, 2}
b := [...]int{1, 2}
c := [2]int{1, 3}
fmt.Println(a == b, a == c, b == c) // "true false false"
d := [3]int{1, 2}
fmt.Println(a == d) // 编译错误：无法比较 [2]int == [3]int
```

#### 多维数组

```go
// 声明一个二维整型数组，两个维度的长度分别是 4 和 2
var array [4][2]int
// 使用数组字面量来声明并初始化一个二维整型数组
array = [4][2]int{{10, 11}, {20, 21}, {30, 31}, {40, 41}}
// 声明并初始化数组中索引为 1. 入门 和 3 的元素
array = [4][2]int{1: {20, 21}, 3: {40, 41}}
// 声明并初始化数组中指定的元素
array = [4][2]int{1: {0: 20}, 3: {1: 41}}
```

### 切片

![image-20210910171851698](https://2351062869-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7b2CdwBN9liniVJpfEAc%2Fuploads%2Fgit-blob-f17e84c52e35c2e773ceb95d8905a897a7422e4e%2Fimage-20210910171851698.png?alt=media)

* 切片是对数组连续片段的引用
* 这个片段可以是整个数组，也可以是指定起始位置的子集
* 切片内部主要包括三个部分
  * data，指向切片所在数组的开始元素位置的指针
  * len，切片的长度
  * cap，切片的容量（底层数组的长度）

#### 定义切片：从已有数组生成

```go
package main
import "fmt"

func main() {
	// 切片建立在一个数组中，需要先定义一个数组
	var intarr [6]int = [6]int{3,4,4,1,2,7}
	// 根据上面的数组，构建一个切片
	// 切片的长度是不固定的，所以不需要写切片长度
	var slice0 []int = intarr[1:3] // 这个切面是针对数组intarr的切片，其切取元素位置范围为[1. 入门, 3)
	// 输出数组
	fmt.Println("数组为：", intarr) // [3 4 4 1. 入门 2 7]
	fmt.Println("切片为：", slice0) // [4 4]
	fmt.Println("切片的容量为：", cap(slice0)) // 5
}
```

**从开始切到结尾**

```go
var slice []int = intarr[:]
```

**从指定位置切到结尾**

```go
var slice []int intarr[2:]
```

**从开始切刀指定位置**

```go
var slice []int intarr[:5]
```

**重置切片：空切片**

```go
var slice []int intarr[0:0]
```

#### 定义切片：直接定义

切片声明与数组声明很像，只不过切片的声明没有长度；这种方式声明切片与`make()`函数效果一致，底层都会创建一个不可访问的数组

```go
var strList []string = []string{} // 声明一个空的切片
```

#### 定义切片：使用make()函数构造切片

```go
make( []Type, size, cap ) // 创建一个切片，指定类型、长度、初始容量

a := make([]int, 2)
b := make([]int, 2, 10)
fmt.Println(a, b)
fmt.Println(len(a), len(b))
```

#### 切片容量：`cap()`

```go
cap(切片)
```

#### 切片长度：`len()`

```go
len(切片)
```

#### 添加元素：append()

* 在使用 append() 函数为切片动态添加元素时，如果空间不足以容纳足够多的元素，切片就会进行“扩容”，此时新切片的长度会发生改变
* 切片在扩容时，容量的扩展规律是按容量的 2 倍数进行扩充，也就是扩容因子为2

```go
package main

import "fmt"

func main() {
	var a []int

	// 在切片尾部追加元素
	a = append(a, 1) // 追加1个元素
	fmt.Println(a)
	a = append(a, 2, 3, 4) // 追加多个元素, 手写解包方式
	fmt.Println(a)
	a = append(a, []int{5, 6}...) // 追加一个切片, 切片需要解包，三个点代表对数组/切片解包
	fmt.Println(a)

	// 在切片头部追加元素
	a = append([]int{0}, a...) // 在开头添加1个元素，
	fmt.Println(a)
	a = append([]int{-3, -2, -1}, a...) // 在开头添加1个切片
	fmt.Println(a)

	// 在切片指定位置插入元素
	// 在切片第四个位置插入数字100
	a = append(a[:4], append([]int{100}, a[4:]...)...)
	fmt.Println(a)
}
// 切片可以进行链式调用
```

#### 复制：copy()

复制需要保证切片类型一致

```go
copy( destSlice, srcSlice []T) int

slice1 := []int{1, 2, 3, 4, 5}
slice2 := []int{5, 4, 3}
copy(slice2, slice1) // 只会复制slice1的前3个元素到slice2中
copy(slice1, slice2) // 只会复制slice2的3个元素到slice1的前3个位置
```

#### 删除元素

切片的删除实际上仍然是根据当前切片切割，比如**删除头部元素：**

```go
a = []int{1, 2, 3}
a = a[1:] // 删除开头1个元素
a = a[N:] // 删除开头N个元素
```

**删除中间位置：**

```go
a = []int{1, 2, 3, ...}
a = append(a[:i], a[i+1:]...) // 删除中间1个元素
a = append(a[:i], a[i+N:]...) // 删除中间N个元素
a = a[:i+copy(a[i:], a[i+1:])] // 删除中间1个元素
a = a[:i+copy(a[i:], a[i+N:])] // 删除中间N个元素
```

**删除尾部元素：**

```go
a = []int{1, 2, 3, ...}
a = append(a[:i], a[i+1:]...) // 删除中间1个元素
a = append(a[:i], a[i+N:]...) // 删除中间N个元素
a = a[:i+copy(a[i:], a[i+1:])] // 删除中间1个元素
a = a[:i+copy(a[i:], a[i+N:])] // 删除中间N个元素
```

#### 遍历切片：range

```go
// 创建一个整型切片，并赋值
slice := []int{10, 20, 30, 40}
// 迭代每个元素，并显示其值
for _, value := range slice {
    fmt.Printf("Value: %d\n", value)
}
```

#### 多维切片

```go
var sliceName [][]...[]sliceType

//声明一个二维切片
var slice [][]int
//为二维切片赋值
slice = [][]int{{10}, {100, 200}}
```

### 映射

map，键值对映射结构，一种元素对（pair）的无序集合，pair 对应一个 key（索引）和一个 value（值），所以这个结构也称为关联数组或字典，这是一种能够快速寻找值的理想结构，给定 key，就可以迅速找到对应的 value

* key不可以重复
* key的类型不能是：`slice`、`map`、`function`
* value的不做限制

#### 定义map

```go
package main

import "fmt"

func main() {
	var m1 map[string]int = make(map[string]int, 10) // map的容量不受限制，该参数10代表此map创建时的初始容量
	m1["语文"] = 88
	m1["数学"] = 98
	fmt.Println(m1) // map[数学:98 语文:88]

	m2 := map[string]int{
		"语文": 88,
		"数学": 98,
	}
	fmt.Println(m2) // map[数学:98 语文:88]
}
```

#### 遍历map: range

```go
scene := make(map[string]int)
scene["route"] = 66
scene["brazil"] = 4
scene["china"] = 960
for k, v := range scene {
    fmt.Println(k, v)
}
```

#### 添加/更新键值对

```go
scene["china"] = 960
```

#### 删除键值对: delete()

```go
scene := make(map[string]int)
// 准备map数据
scene["route"] = 66
scene["brazil"] = 4
scene["china"] = 960
delete(scene, "brazil")
for k, v := range scene {
    fmt.Println(k, v)
}
```

#### 清空map

go没有提供清空map的方法，有两种方法可以清空map：

* 遍历调用`delete()`
* 给变量重新赋予一个新创建的map

### 结构体

**结构体的定义只是一种内存布局的描述**，只有当结构体实例化时，才会真正地分配内存。

#### 定义结构体

```go
type 类型名 struct {
    字段1 字段1类型
    字段2 字段2类型
    …
}
```

> type用于自定义类型，定义type可以对结构体进行复用

```go
type Point struct {
    X int
    Y int
}
```

**同类型变量可将字段放在一行**

```go
type Color struct {
    R, G, B byte
}
```

**空结构体，没有意义**

```go
type Ept struct {}
```

**字段类型不受限制**

结构体的字段类型不受限制，可以是基本类型，也可以是复合类型，比如函数等：

```go
// Go program to illustrate the function
// as a field in Go structure
package main

import "fmt"

// Finalsalary of function type
type Finalsalary func(int, int) int

// Creating structure
type Author struct {
	name	 string
	language string
	Marticles int
	Pay	 int

	// Function as a field
	salary Finalsalary
}

// Main method
func main() {

	// Initializing the fields
	// of the structure
	result := Author{
		name:	 "Sonia",
		language: "Java",
		Marticles: 120,
		Pay:	 500,
		salary: func(Ma int, pay int) int {
			return Ma * pay
		},
	}

	// Display values
	fmt.Println("Author's Name: ", result.name)
	fmt.Println("Language: ", result.language)
	fmt.Println("Total number of articles published in May: ", result.Marticles)
	fmt.Println("Per article pay: ", result.Pay)
	fmt.Println("Total salary: ", result.salary(result.Marticles, result.Pay))
}
```

#### 实例化结构体

**基本实例化形式**

```go
type Point struct {
    X int
    Y int
}
var p Point
p.X = 10
p.Y = 20
```

**实例化指针类型结构体(常用)**

```go
type Player struct{
    Name string
    HealthPoint int
    MagicPoint int
}
tank := new(Player)
tank.Name = "Canon"
tank.HealthPoint = 300
```

**取地址符的实例化(常用)**

对结构体进行`&`取地址操作时，视为对该类型进行一次 new 的实例化操作

```go
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"
```

#### 初始化结构体

**键值类型初始化**

```go
type People struct {
    name  string
    child *People
}
relation := &People{
    name: "爷爷",
    child: &People{
        name: "爸爸",
        child: &People{
                name: "我",
        },
    },
}
```

**多值列表初始化**

1. 字段顺序要保持一致
2. 所有字段必须要初始化

```go
package main

import _ "fmt"

func main() {
	type People struct {
		name  string
		child *People
	}
	relation := &People{
		"爷爷",
		&People{
			"爸爸",
			&People{
				name: "我",
			},
		},
	}
}
```

**匿名结构体的初始化**

```go
package main
import (
    "fmt"
)
// 打印消息类型, 传入匿名结构体
func printMsgType(msg *struct {
    id   int
    data string
}) {
    // 使用动词%T打印msg的类型
    fmt.Printf("%T\n", msg)
}
func main() {
    // 实例化一个匿名结构体
    msg := &struct {  // 定义部分
        id   int
        data string
    }{  // 值初始化部分
        1024,
        "hello",
    }
    printMsgType(msg)
}

```

### 管道

参见：`并发编程/channel`

### 函数

## 接口

参照：`面向对象/接口`

### 类型定义

```go
// 定义了一个person类型，
type Person struct {
  Name string
  Age  int
}
```

> 无论是类型还是类型别名，都可以为他们指定方法

### 类型别名

类型别名是Go1.9版本添加的新功能，主要用于解决代码升级、迁移中存在的类型兼容性问题。再重构、升级代码时，经常会遇到变量名称变更，C/C++选择使用宏快速定义一段新的代码，而Golang则使用类型别名：

```go
// Go1.9之前，类型byte、rune通过自定义类型定义
type byte uint8
type rune int32

// Go1.9 之后，类型byte、rune则通过类型别名定义
type byte = uint8
type rune = int32
```

**定义类型别名：**

```go
// 类型别名定义的格式：
type TypeAlias = Type

// 根据内置类型创建类型别名
type myInt = int64

// 根据自定义类型创建类型别名
type myDuration = time.Duration

// 根据其他类型别名创建类型别名
type myInt2 = myInt
```

> 无论是类型还是类型别名，都可以为他们指定方法
