# 数据类型

## 值类型和引用类型

在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](/files/lBgT2nfWexJsK66mOjkv)

| 数据类型         | 存储空间(字节) | 默认值      |
| ------------ | -------- | -------- |
| `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](/files/kS4Gb4Z79pPi8xNqAenE)

* 切片是对数组连续片段的引用
* 这个片段可以是整个数组，也可以是指定起始位置的子集
* 切片内部主要包括三个部分
  * 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
```

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


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://yangsx95.gitbook.io/notes/programming-language/golang/shu-ju-lei-xing.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
