Go 语言快速入门——基础语法

Go语言准备工作

1、Go安装

2、Go命令

go build
  1. 在项目目录下执行go build

  2. 在其他路径下执行go build, 需要在后面加上项目的路径(项目路径从GOPATH/src后开始写起,编译之后的可执行文件就保存在当前目录下)

  3. go build -o hello.exe

go run

像执行脚本文件一样执行Go代码

go install

go install分为两步:

  1. 先编译得到一个可执行文件

  2. 将可执行文件拷贝到GOPATH/bin

交叉编译

Go支持跨平台编译

例如:在windows平台编译一个能在linux平台执行的可执行文件

1
2
3
4
5
SET CGO_ENABLED=0  // 禁用CGO

SET GOOS=linux // 目标平台是linux

SET GOARCH=amd64 // 目标处理器架构是amd64

执行go build

Mac平台交叉编译:

1
2
3
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build

CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build

3、GO语言文件基础语法

存放Go源代码的文件后缀名是.go

文件第一行:package关键字声明包名

如果要编译可执行文件,必须要有main包和main函数(入口函数)

1
2
3
4
5
// 单行注释

/*
多行注释
*/

Go语言函数外的语句必须以关键字开头

函数内部定义的变量必须使用

基本语法

1、HelloWorld 程序

1
2
3
4
5
6
7
8
9
10
11
12
package main

// 导入语句
import "fmt"

// 函数外只能放置标识符(变量\常量\函数\类型)的声明
// fmt.Println("Hello") // 语句是非法

// 程序的入口函数
func main() {
fmt.Println("Hello world!")
}

2、变量与常量

  • 标识符:字母与下划线开头
  • 关键字(25个):
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
  • 保留字(37个):
1
2
3
4
5
6
7
8
9
10
Constants:    true  false  iota  nil

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

Functions: make len cap new append copy close delete
complex real imag
panic recover
  • 变量声明
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var 变量名 变量类型 (= 表达式)

var name string
var age int = 18
var isOk bool

// 批量声明
var (
a string
b int
c bool
d float32
)

// 类型推导
var name = "Q1mi"
var age = 18

m := 200 // 短变量声明

// 匿名变量,用_表示
x, _ := foo()
_, y := foo()

【特性】

  1. Go语言推荐使用驼峰命名法;
  2. Go语言中变量声明了必须使用;
  3. 函数外的每个语句都必须以关键字开始(var、const、func等);
  4. :=不能使用在函数外;
  5. _多用于占位,表示忽略值;匿名变量不占用命名空间,不会分配内存,所以匿名变量之间不存在重复声明。
  • 常量声明
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
const pi = 3.1415
const e = 2.7182

const (
pi = 3.1415
e = 2.7182
)

// 批量声明常量时,如果某一行声明后没有赋值,默认就和上一行一致
const (
n1 = 100
n2
n3
)

// iota
const (
a1 = iota // 0
a2 // 1
a3 // 2
)

const (
b1 = iota // 0
b2 = iota // 1
_ = iota // 2
b3 = iota // 3
)

// 插队,每新增一行iota+1
const (
c1 = iota // 0
c2 = 100 // 100
c3 = iota // 2
c4
)

// 多个常量声明在一行
const (
d1, d2 = iota + 1, iota + 2 // d1:1 d2:2

d3, d4 = iota + 1, iota + 2 // d3:2 d4:3
)

// 定义数量级
const (
_ = iota
KB = 1 << (10 * iota)
MB = 1 << (10 * iota)
GB = 1 << (10 * iota)
TB = 1 << (10 * iota)
PB = 1 << (10 * iota)
)

【特性】

  1. 常量在定义的时候必须赋值;
  • 整型
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
package main

import "fmt"

// 整型
func main() {
// 十进制
var i1 = 101
fmt.Printf("%d\n", i1)
fmt.Printf("%b\n", i1) // 把十进制数转换成二进制
fmt.Printf("%o\n", i1) // 把十进制数转换成八进制
fmt.Printf("%x\n", i1) // 把十进制数转换成十六进制

// 八进制
i2 := 077
fmt.Printf("%d\n", i2)
// 十六进制
i3 := 0x1234567
fmt.Printf("%d\n", i3)
// 查看变量的类型
fmt.Printf("%T\n", i3)

// 声明int8类型的变量
i4 := int8(9) // 明确指定int8类型,否则就是默认为int类型
fmt.Printf("%T\n", i4)
}
  • 浮点型
1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import "fmt"

// 浮点数
func main() {
//math.MaxFloat32 // float32最大值
f1 := 1.23456
fmt.Printf("%T\n", f1) // 默认Go语言中的小数都是float64类型
f2 := float32(1.23456)
fmt.Printf("%T\n", f2) // 显示声明float32类型
// f1 = f2 // float32类型的值不能直接赋值给float64类型的变量
}
  • 布尔型
1
2
3
4
5
6
7
8
9
10
11
package main

import "fmt"

// 布尔值
func main() {
b1 := true
var b2 bool // 默认是false
fmt.Printf("%T\n", b1)
fmt.Printf("%T value:%v\n", b2, b2)
}
  • fmt 占位符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import "fmt"

// fmt占位符
func main() {
var n = 100
// 查看类型
fmt.Printf("%T\n", n)
fmt.Printf("%v\n", n)
fmt.Printf("%b\n", n)
fmt.Printf("%d\n", n)
fmt.Printf("%o\n", n)
fmt.Printf("%x\n", n)
var s = "Hello 沙河!"
fmt.Printf("字符串:%s\n", s)
fmt.Printf("字符串:%v\n", s)
fmt.Printf("字符串:%#v\n", s)
}
  • 字符串
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// 字符串
s := "Hello 沙河"
// 单独的字母、汉字、符号表示一个字符
c1 := 'h'
c2 := '1'
c3 := '沙'

// \ 本来是具有特殊含义的,我应该告诉程序我写的\就是一个单纯的\
path := "'D:\\Go\\src\\code.oldboyedu.com\\studygo\\day01'"
fmt.Println(path)

s := "I'm ok"
fmt.Println(s)
// 多行的字符串
s2 := `
世情薄
人情恶
雨送黄昏花易落
`
fmt.Println(s2)
s3 := `D:\Go\src\code.oldboyedu.com\studygo\day01`
fmt.Println(s3)

// 字符串相关操作
fmt.Println(len(s3)) // ?

// 字符串拼接
name := "理想"
world := "大帅比"

ss := name + world
fmt.Println(ss)
ss1 := fmt.Sprintf("%s%s", name, world)
// fmt.Printf("%s%s", name, world)
fmt.Println(ss1)
// 分隔
ret := strings.Split(s3, "\\")
fmt.Println(ret)
// 包含
fmt.Println(strings.Contains(ss, "理性"))
fmt.Println(strings.Contains(ss, "理想"))
// 前缀
fmt.Println(strings.HasPrefix(ss, "理想"))
// 后缀
fmt.Println(strings.HasSuffix(ss, "理想"))

s4 := "abcdeb"
fmt.Println(strings.Index(s4, "c"))
fmt.Println(strings.LastIndex(s4, "b"))
// 拼接
fmt.Println(strings.Join(ret, "+"))

【注意】

  1. 字符串必须用""包裹;
  2. Go语言中''包裹的是字符!!!
  • byte与rune类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import "fmt"

// byte和rune类型

// Go语言中为了处理非ASCII码类型的字符 定义了新的rune类型
func main() {
s := "Hello沙河사샤"
// len()求得是byte字节的数量
n := len(s) // 求字符串s的长度,把长度保存到变量n中
fmt.Println(n)

/for i := 0; i < len(s); i++ {
// fmt.Println(s[i])
fmt.Printf("%c\n", s[i]) // %c:字符
}

for _, c := range s { // 从字符串中拿出具体的字符
fmt.Printf("%c\n", c) // %c:字符
}

// "Hello" => 'H' 'e' 'l' 'l' 'o'
}

3、字符串修改与类型转换

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
package main

import "fmt"

func main() {
// 字符串修改
s2 := "白萝卜" // => '白' '萝' '卜'
s3 := []rune(s2) // 把字符串强制转换成了一个rune切片
s3[0] = '红'
fmt.Println(string(s3)) // 把rune切片强制转换成字符串

c1 := "红"
c2 := '红' // rune(int32)
fmt.Printf("c1:%T c2:%T\n", c1, c2)
c3 := "H" // string
c4 := byte('H') // byte(uint8)
fmt.Printf("c3:%T c4:%T\n", c3, c4)
fmt.Printf("%d\n", c4)

// 类型转换
n1 := 10 // int
var f float64
f = float64(n1)
fmt.Println(f)
fmt.Printf("%T\n", f)
}

4、if 判断与 for 循环

  • if
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
30
31
32
33
package main

import "fmt"

// if条件判断
func main() {
age := 19

if age > 18 { // 如果 age > 18 就执行这个{}中的代码
fmt.Println("澳门首家线上赌场开业啦!")
} else { // 否则就执行这个{}中的代码
fmt.Println("改写暑假作业啦!")
}

// 多个判断条件
if age > 35 {
fmt.Println("人到中年")
} else if age > 18 {
fmt.Println("青年")
} else {
fmt.Println("好好学习!")
}

// 作用域
// age变量此时只在if条件判断语句中生效
if age := 19; age > 18 { // 如果 age > 18 就执行这个{}中的代码
fmt.Println("澳门首家线上赌场开业啦!")
} else { // 否则就执行这个{}中的代码
fmt.Println("改写暑假作业啦!")
}

fmt.Println(age) // 在这里是找不到age
}
  • for
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package main

import "fmt"

// for循环

func main() {
// 基本格式
for i := 0; i < 10; i++ {
fmt.Println(i)
}

// 变种1
var i = 5
for ; i < 10; i++ {
fmt.Println(i)
}
// 变种2
var i = 5
for i < 10 {
fmt.Println(i)
i++
}

// 无限循环
for {
fmt.Println("123")
}

// for range循环
s := "Hello沙河"
for i, v := range s {
fmt.Printf("%d %c\n", i, v)
}

// 当i=5时就跳出for循环
for i := 0; i < 10; i++ {
if i == 5 {
break // 跳出for循环
}
fmt.Println(i)
}
fmt.Println("over")

// 当i=5时,跳过此次for循环(不执行for循环内部的打印语句),继续下一次循环
for i := 0; i < 10; i++ {
if i == 5 {
continue // 继续下一次循环
}
fmt.Println(i)
}
}

5、switch 与 goto

  • switch:
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
30
31
32
33
34
35
package main

import "fmt"

// switch
// 简化大量的判断(一个变量和具体的值作比较)

func main() {
// switch 简化上面的代码
switch n := 3; n {
case 1:
fmt.Println("大拇指")
case 2:
fmt.Println("食指")
fallthrough // 可以执行满足条件的case的下一个case,是为了兼容C语言中的case设计的
case 3:
fmt.Println("中指")
case 4:
fmt.Println("无名指")
case 5:
fmt.Println("小拇指")
default:
fmt.Println("无效的数字")
}
// fmt.Println(n)

switch n := 7; n {
case 1, 3, 5, 7, 9:
fmt.Println("奇数")
case 2, 4, 6, 8:
fmt.Println("偶数")
default:
fmt.Println(n)
}
}
  • goto:
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
30
31
32
33
34
package main

import "fmt"

// goto

func main() {
// 跳出多层for循环
var flag = false
for i := 0; i < 10; i++ {
for j := 'A'; j < 'Z'; j++ {
if j == 'C' {
flag = true
break // 跳出内层的for循环
}
fmt.Printf("%v-%c\n", i, j)
}
if flag {
break // 跳出for循环(外层的for循环)
}
}

// goto+label实现跳出多层for循环
for i := 0; i < 10; i++ {
for j := 'A'; j < 'Z'; j++ {
if j == 'C' {
goto XX // 跳到我指定的那个标签
}
fmt.Printf("%v-%c\n", i, j)
}
}
XX: // label标签
fmt.Println("over")
}

6、运算符

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
package main

import "fmt"

// 运算符

func main() {
var (
a = 5
b = 2
)

// 算术运算符
fmt.Println(a + b)
fmt.Println(a - b)
fmt.Println(a * b)
fmt.Println(a / b)
fmt.Println(a % b)
a++ // 单独的语句,不能放在=的右边赋值 ==> a = a + 1
b-- // 单独的语句,不能放在=的右边赋值 ==> b = b - 1
fmt.Println(a)

// 关系运算符
fmt.Println(a == b) // Go语言是强类型,相同类型的变量才能比较
fmt.Println(a != b) // 不等于
fmt.Println(a >= b) // 大于等于
fmt.Println(a > b) // 大于
fmt.Println(a <= b) // 小于等于
fmt.Println(a < b) // 小于

// 逻辑运算符
// 如果年龄大于18岁 并且 年龄小于60岁
age := 22
if age > 18 && age < 60 {
fmt.Println("苦逼上班的!")
} else {
fmt.Println("不用上班!")
}
// 如果年龄小于18岁 或者 年龄大于60岁
if age < 18 || age > 60 {
fmt.Println("不用上班!")
} else {
fmt.Println("苦逼上班的!")
}

// not取反,原来为真就为假,原来为假就为真
isMarried := false
fmt.Println(isMarried) // false
fmt.Println(!isMarried) // true

// 位运算:针对的是二进制数
// 5的二进制表示:101
// 2的二进制表示: 10

// &:按位与 (两位均为1才为1)
fmt.Println(5 & 2) // 000
// |:按位或(两位有1个为1就为1)
fmt.Println(5 | 2) // 111
// ^:按位异或(两位不一样则为1)
fmt.Println(5 ^ 2) // 111
// <<:将二进制位左移指定位数
fmt.Println(5 << 1) // 1010 => 10
fmt.Println(1 << 10) // 10000000000 => 1024
// >>:将二进制位右移指定的位数
fmt.Println(5 >> 2) // 1 =>1
var m = int8(1) // 只能存8位
fmt.Println(m << 10) // 10000000000

// 192.168.1.1
// 权限 文件操作会讲位运算实际的应用
// 0644
// 赋值运算符, 用来给变量赋值的

var x int
x = 10
x += 1 // x = x + 1
x -= 1 // x = x - 1
x *= 2 // x = x * 2
x /= 2 // x = x / 2
x %= 2 // x = x % 2

x <<= 2 // x = x << 2
x &= 2 // x = x & 2
x |= 3 // x = x | 3
x ^= 4 // x = x ^ 4
x >>= 2 // x = x >> 2

fmt.Println(x)
}

7、数组(array)

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
package main

import "fmt"

// 数组

// 存放元素的容器
// 必须指定存放的元素的类型和容量(长度)
// 数组的长度是数组类型的一部分
func main() {
var a1 [3]bool // [true false true]
var a2 [4]bool // [true true false false]

fmt.Printf("a1:%T a2:%T\n", a1, a2)

// 数组的初始化
// 如果不初始化:默认元素都是零值(布尔值:false, 整型和浮点型都是0, 字符串:"")
fmt.Println(a1, a2)
// 1. 初始化方式1
a1 = [3]bool{true, true, true}
fmt.Println(a1)
// 2. 初始化方式2:根据初始值自动推断数组的长度是多少
// a10 := [9]int{0, 1, 2, 3, 4, 4, 5, 6, 7}
a10 := [...]int{0, 1, 2, 3, 4, 4, 5, 6, 7}
fmt.Println(a10)
// 3. 初始化方式3:根据索引来初始化
a3 := [5]int{0: 1, 4: 2}
fmt.Println(a3)

// 数组的遍历
citys := [...]string{"北京", "上海", "深圳"} // 索引:0~2 citys[0],citys[1],citys[2]
// 1. 根据索引遍历
for i := 0; i < len(citys); i++ {
fmt.Println(citys[i])
}
// 2. for range遍历
for i, v := range citys {
fmt.Println(i, v)
}

// 多维数组
// [[1 2] [3 4] [5 6]]
var a11 [3][2]int
a11 = [3][2]int{
[2]int{1, 2},
[2]int{3, 4},
[2]int{5, 6},
}
fmt.Println(a11)
var a1 = [...][2]int{ // 多维数组只有最外层能省略
[2]int{1, 2},
[2]int{3, 4},
[2]int{5, 6},
}

// 多维数组的遍历
for _, v1 := range a11 {
fmt.Println(v1)
for _, v2 := range v1 {
fmt.Println(v2)
}
}

// 数组是值类型
b1 := [3]int{1, 2, 3} // [1 2 3]
b2 := b1 // [1 2 3] Ctrl+C Ctrl+V => 把world文档从文件夹A拷贝到文件夹B
b2[0] = 100 // b2:[100 2 3]
fmt.Println(b1, b2) // b1:[1 2 3]
}

8、切片(slice)

定义:拥有相同类型元素的可变长度的序列

实质:切片就是一个框,框住了一块连续的内存;切片属于引用类型,真正的数据都是保存在底层数组里的。

切片之间是不能比较的,我们不能使用==操作符来判断两个切片是否含有相等的元素。

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package main

import "fmt"

// 切片slice

func main() {
// 切片的定义
var s1 []int // 定义一个存放int类型元素的切片
var s2 []string // 定义一个存放string类型元素的切片
fmt.Println(s1, s2)
fmt.Println(s1 == nil) // true
fmt.Println(s2 == nil) // true
// 初始化
s1 = []int{1, 2, 3}
s2 = []string{"沙河", "张江", "平山村"}
fmt.Println(s1, s2)
fmt.Println(s1 == nil) // false
fmt.Println(s2 == nil) // false
// 长度和容量
fmt.Printf("len(s1):%d cap(s1):%d\n", len(s1), cap(s1))
fmt.Printf("len(s2):%d cap(s2):%d\n", len(s2), cap(s2))

// 2. 由数组得到切片
a1 := [...]int{1, 3, 5, 7, 9, 11, 13}
s3 := a1[0:4] // 基于一个数组切割,左包含右不包含,(左闭右开)
fmt.Println(s3)
s4 := a1[1:6]
fmt.Println(s4)
s5 := a1[:4] // => [0:4] [1 3 5 7]
s6 := a1[3:] // => [3:len(a1)] [7 9 11 13]
s7 := a1[:] // => [0:len(a1)]
fmt.Println(s5, s6, s7)
// 切片的容量是指底层数组的容量
fmt.Printf("len(s5):%d cap(s5):%d\n", len(s5), cap(s5))
// 底层数组从切片的第一个元素到最后的元素数量
fmt.Printf("len(s6):%d cap(s6):%d\n", len(s6), cap(s6))
// 切片再切割
s8 := s6[3:] // [13]
fmt.Printf("len(s8):%d cap(s8):%d\n", len(s8), cap(s8))
// 切片是引用类型,都指向了底层的一个数组。
fmt.Println("s6:", s6)
a1[6] = 1300 // 修改了底层数组的值
fmt.Println("s6:", s6)
fmt.Println("s8:", s8)
}

【注意】

  1. 底层数组从切片的第一个元素到最后的元素数量

9、使用make()函数创造切片

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
30
package main

import "fmt"

// make()函数创造切片

func main() {
s1 := make([]int, 5, 10)
fmt.Printf("s1=%v len(s1)=%d cap(s1)=%d\n", s1, len(s1), cap(s1))

s2 := make([]int, 0, 10)
fmt.Printf("s1=%v len(s1)=%d cap(s1)=%d\n", s2, len(s2), cap(s2))

// 切片的赋值
s3 := []int{1, 3, 5}
s4 := s3 // s3和s4都指向了同一个底层数组
fmt.Println(s3, s4)
s3[0] = 1000
fmt.Println(s3, s4)

// 切片的遍历
// 1. 索引遍历
for i := 0; i < len(s3); i++ {
fmt.Println(s3[i])
}
// 2. for range循环
for i, v := range s3 {
fmt.Println(i, v)
}
}

10、切片进阶

  • append()方法为切片添加元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import "fmt"

// append() 为切片追加元素

func main() {
s1 := []string{"北京", "上海", "深圳"}
fmt.Printf("s1=%v len(s1)=%d cap(s1)=%d\n", s1, len(s1), cap(s1))
// s1[3] = "广州" // 错误的写法 会导致编译错误:索引越界
// fmt.Println(s1)

// 调用append函数必须用原来的切片变量接收返回值
// append追加元素,原来的底层数组放不下的时候,Go底层就会把底层数组换一个
// 必须用变量接收append的返回值
s1 = append(s1, "广州")
fmt.Printf("s1=%v len(s1)=%d cap(s1)=%d\n", s1, len(s1), cap(s1))
s1 = append(s1, "杭州", "成都")
fmt.Printf("s1=%v len(s1)=%d cap(s1)=%d\n", s1, len(s1), cap(s1))
ss := []string{"武汉", "西安", "苏州"}
s1 = append(s1, ss...) // ...表示拆开
fmt.Printf("s1=%v len(s1)=%d cap(s1)=%d\n", s1, len(s1), cap(s1))
}

【注意】

  1. 切片的扩容策略:
    • 如果申请的容量大于原来的2倍,那就直接扩容至新申请的容量
    • 如果小于1024, 那么就直接两倍
    • 如果大于1024,就按照1.25倍去扩容
    • 具体存储的值类型不同,扩容策略也有一定的不同。
  • 切片复制
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
30
31
32
33
34
35
36
37
38
39
40
41
package main

import "fmt"

// copy

func main() {
a1 := []int{1, 3, 5}
a2 := a1 // 赋值
var a3 = make([]int, 3, 3)
copy(a3, a1) // copy
fmt.Println(a1, a2, a3)
a1[0] = 100
fmt.Println(a1, a2, a3) //a1=a2{100, 3, 5} a3={1, 3, 5}

// 将a1中的索引为1的3这个元素删掉
a1 = append(a1[:1], a1[2:]...)
fmt.Println(a1)
fmt.Println(cap(a1))

x1 := [...]int{1, 3, 5} // 数组
s1 := x1[:] // 切片
fmt.Println(s1, len(s1), cap(s1))
// 1. 切片不保存具体的值
// 2. 切片对应一个底层数组
// 3. 底层数组都是占用一块连续的内存
fmt.Printf("%p\n", &s1[0])
s1 = append(s1[:1], s1[2:]...) // 修改了底层数组!
fmt.Printf("%p\n", &s1[0])
fmt.Println(s1, len(s1), cap(s1))
// ?
s1[0] = 100 // 修改底层数组
fmt.Println(x1) // [1 5 5 ]?

a1 := [...]int{1, 3, 5, 7, 9, 11, 13, 15, 17}
s1 := a1[:]
// 删掉索引为1的那个3
s1 = append(s1[0:1], s1[2:]...)
fmt.Println(s1) // [1 5 7 9 11 13 15 17]
fmt.Println(a1) // [1 5 7 9 11 13 15 17 17]
}

【注意】

  1. 切片不保存具体的值;

  2. 切片对应一个底层数组;

  3. 底层数组都是占用一块连续的内存;

  • 简单的实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
"fmt"
"sort"
)

// 切片的练习题

func main() {
var a = make([]int, 5, 10) // 创建切片,长度为5,容量为10
fmt.Println(a)
for i := 0; i < 10; i++ {
a = append(a, i)
}

fmt.Println(a) // [0 0 0 0 0 0 1 2 3 4 5 6 7 8 9]
fmt.Println(cap(a))

var a1 = [...]int{3, 7, 8, 9, 1}
sort.Ints(a1[:]) // 对切片进行排序
fmt.Println(a1)
}

11、指针

  1. &取地址;
  2. *根据地址取值;
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
package main

import "fmt"

// vscode 不支持go module

// pointer

func main() {
// 1. &:取地址
n := 18
p := &n
fmt.Println(p)
fmt.Printf("%T\n", p) // *int:int类型的指针
// 2. *:根据地址取值
m := *p
fmt.Println(m)
fmt.Printf("%T\n", m) // int

var a1 *int // nil pointer
fmt.Println(a1)
var a2 = new(int) // new函数申请一个内存地址
fmt.Println(a2)
fmt.Println(*a2)
*a2 = 100
fmt.Println(*a2)
}

【make与new的区别】

  1. make和new都是用来申请内存的;
  2. new很少用,一般用来给基本数据类型申请内存你,stringint,返回的是对应类型的指针;
  3. make是用来给slicemapchan申请内存的,make函数返回的是对应的这三个类型本身。

12、map

map是一种无序的基于key-value的数据结构,Go语言中的map是引用类型,必须初始化才能使用。

  • 基本使用
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
30
31
32
33
34
35
36
37
38
39
40
41
package main

import "fmt"

// map

func main() {
var m1 map[string]int // key:string value:int
fmt.Println(m1 == nil) // 还没有初始化(没有在内存中开辟空间)
m1 = make(map[string]int, 10) // 要估算好该map容量,避免在程序运行期间再动态扩容
m1["理想"] = 18
m1["jiwuming"] = 35

fmt.Println(m1)
fmt.Println(m1["理想"])
// 约定成俗用ok接收返回的布尔值
fmt.Println(m1["娜扎"]) // 如果不存在这个key拿到对应值类型的零值(0)
value, ok := m1["娜扎"]
if !ok {
fmt.Println("查无此key")
} else {
fmt.Println(value)
}

// map的遍历
for k, v := range m1 {
fmt.Println(k, v)
}
// 只遍历key
for k := range m1 {
fmt.Println(k)
}
// 只遍历value
for _, v := range m1 {
fmt.Println(v)
}
// 删除
delete(m1, "jiwuming")
fmt.Println(m1)
delete(m1, "沙河") // 删除不存在的key
}
  • 排序与遍历
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
30
31
32
33
package main

import (
"fmt"
"math/rand"
"sort"
"time"
)

func main() {
rand.Seed(time.Now().UnixNano()) //初始化随机数种子

var scoreMap = make(map[string]int, 200)

for i := 0; i < 100; i++ {
key := fmt.Sprintf("stu%02d", i) //生成stu开头的字符串
value := rand.Intn(100) //生成0~99的随机整数
scoreMap[key] = value
}

fmt.Println(scoreMap)
//取出map中的所有key存入切片keys
var keys = make([]string, 0, 200)
for key := range scoreMap {
keys = append(keys, key)
}
// //对切片进行排序
sort.Strings(keys)
//按照排序后的key遍历map
for _, key := range keys {
fmt.Println(key, scoreMap[key])
}
}
  • map与slice:元素为map类型的切片、值为切片类型的map
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import "fmt"

// map和slice组合

func main() {
// 元素类型为map的切片
var s1 = make([]map[int]string, 10, 10)
// 没有对内部的map做初始化
s1[0] = make(map[int]string, 1)
s1[0][10] = "沙河"
fmt.Println(s1) // [map[10:沙河] map[] map[] map[] map[] map[] map[] map[] map[] map[]]
// 值为切片类型的map
var m1 = make(map[string][]int, 10)
m1["北京"] = []int{10, 20, 30}
fmt.Println(m1) // map[北京:[10 20 30]]
}

13、函数

  • 函数定义
1
2
3
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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
package main

import "fmt"

// 函数

// 函数存在的意义?
// 函数是一段代码的封装
// 把一段逻辑抽象出来封装到一个函数中,给它起个名字,每次用到它的时候直接用函数名调用就可以了
// 使用函数能够让代码结构更清晰、更简洁。

// 函数的定义
func sum(x int, y int) (ret int) {
return x + y
}

// 没有返回值
func f1(x int, y int) {
fmt.Println(x + y)
}

// 没有参数没有返回值
func f2() {
fmt.Println("f2")
}

// 没有参数但有返回值的
func f3() int {
ret := 3
return ret
}

// 返回值可以命名也可以不命名

// 命名的返回值就相当于在函数中声明一个变量
func f4(x int, y int) (ret int) {
ret = x + y
return // 使用命名返回值可以return后省略
}

// 多个返回值
func f5() (int, string) {
return 1, "沙河"
}

// 参数的类型简写:
// 当参数中连续多个参数的类型一致时,我们可以将非最后一个参数的类型省略
func f6(x, y, z int, m, n string, i, j bool) int {
return x + y
}

// 可变长参数
// 可变长参数必须放在函数参数的最后
func f7(x string, y ...int) {
fmt.Println(x)
fmt.Println(y) // y的类型是切片 []int
}

// Go语言中函数没有默认参数这个概念

func main() {
r := sum(1, 2)
fmt.Println(r)

_, n := f5()
fmt.Println(n)

f7("下雨了")
f7("下雨了", 1, 2, 3, 4, 5, 6, 7)
}
  • defer语句:会将其后面跟随的语句进行延迟处理,在defer归属的函数即将返回时,将延迟处理的语句按defer定义的逆序进行执行,也就是说,先被defer的语句最后被执行,最后被defer的语句最先被执行。
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
package main

import "fmt"

// defer

// defer多用于函数结束之前释放资源(文件句柄、数据库连接、socket连接)
func deferDemo() {
fmt.Println("start")
defer fmt.Println("嘿嘿嘿") // defer把它后面的语句延迟到函数即将返回的时候再执行
defer fmt.Println("呵呵呵") // 一个函数中可以有多个defer语句
defer fmt.Println("哈哈哈") // 多个defer语言按照先进后出(后进先出)的顺序延迟执行
fmt.Println("end")
}

func main() {
deferDemo()
}
/*
start
end
哈哈哈
呵呵呵
嘿嘿嘿
*/

【defer的执行时机】

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package main

import "fmt"

// Go语言中函数的return不是原子操作,在底层是分为两步来执行
// 第一步:返回值赋值
// defer
// 第二步:真正的RET返回
// 函数中如果存在defer,那么defer执行的时机是在第一步和第二步之间

func f1() int {
x := 5
defer func() {
x++ // 修改的是x不是返回值
}()
return x // 1. 返回值赋值 2. defer 3. 真正的RET指令
}

func f2() (x int) {
defer func() {
x++
}()
return 5 // 返回值=x 6
}

func f3() (y int) {
x := 5
defer func() {
x++ // 修改的是x
}()
return x // 1. 返回值 = y = x = 5 2. defer修改的是x 3. 真正的返回
}

func f4() (x int) {
defer func(x int) {
x++ // 改变的是函数中x的副本
}(x)
return 5 // 返回值 = x = 5
}

func f5() (x int) {
defer func(x int) int {
x++
return x
}(x)
return 5
}

// 传一个x的指针到匿名函数中
func f6() (x int) {
defer func(x *int) {
(*x)++
}(&x)
return 5 // 1. 返回值=x=5 2. defer x=6 3. RET返回
}

func main() {
fmt.Println(f1()) // 5
fmt.Println(f2()) // 6
fmt.Println(f3()) // 5
fmt.Println(f4()) // 5
fmt.Println(f5()) // 5
fmt.Println(f6()) // 6
}
  • 变量作用域

函数中查找变量的顺序

  1. 先在函数内部查找
  2. 找不到就往函数的外面查找,一直找到全局
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
30
31
32
package main

import "fmt"

// 变量的作用域

var x = 100 // 定义一个全局变量

// 定义一个函数
func f1() {
// x := 10
name := "理想"
// 函数中查找变量的顺序
// 1. 先在函数内部查找
// 2. 找不到就往函数的外面查找,一直找到全局
fmt.Println(x, name)
}

func main() {
f1()
// fmt.Println(name) // 函数内部定义的变脸只能在该函数内部使用

// 语句块作用域
if i := 10; i < 18 {
fmt.Println("乖乖上学")
}
// fmt.Println(i) // 不存在i
for j := 0; j < 5; j++ {
fmt.Println(j)
}
// fmt.Println(j) // 不存在j
}
  • 函数类型与变量
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package main

import "fmt"

// 函数类型

func f1() {
fmt.Println("Hello 沙河")
}

func f2() int {
return 10
}

func f4(x, y int) int {
return x + y
}

// 函数也可以作为参数的类型
func f3(x func() int) {
ret := x()
fmt.Println(ret)
}

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

// 函数还可以作为返回值
func f5(x func() int) func(int, int) int {
return ff
}

func main() {
a := f1
fmt.Printf("%T\n", a)
b := f2
fmt.Printf("%T\n", b)

f3(f2)
f3(b)
fmt.Printf("%T\n", f4)
// f3(f4)
f7 := f5(f2)
fmt.Printf("%T\n", f7)
}

14、匿名函数

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
package main

import "fmt"

// 匿名函数

// var f1 = func(x, y int) {
// fmt.Println(x + y)
// }

func main() {

// 函数内部没有办法声明带名字的函数
// 匿名函数
f1 := func(x, y int) {
fmt.Println(x + y)
}
f1(10, 20)

// 如果只是调用一次的函数,还可以简写成立即执行函数
func(x, y int) {
fmt.Println(x + y)
fmt.Println("Hello world!")
}(100, 200)
}

15、fmt

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package main

import "fmt"

// fmt

func main() {
// fmt.Print("沙河")
// fmt.Print("娜扎")
// fmt.Println("--------")
// fmt.Println("沙河")
// fmt.Println("娜扎")
// Printf("格式化字符串", 值)
// %T :查看类型
// %d :十进制数
// %b :二进制数
// %o :八进制数
// %x :十六进制数
// %c : 字符
// %s :字符串
// %p: 指针
// %v: 值
// %f:浮点数
// %t :布尔值

// var m1 = make(map[string]int, 1)
// m1["理想"] = 100
// fmt.Printf("%v\n", m1)
// fmt.Printf("%#v\n", m1)

// printBaifenbi(90)

// fmt.Printf("%v\n", 100)
// // 整数->字符
// fmt.Printf("%q\n", 65)
// // 浮点数和复数
// fmt.Printf("%b\n", 3.14159265354697)
// // 字符串
// fmt.Printf("%q\n", "李想有理想")
// fmt.Printf("%7.3s\n", "李想有理想")

// 获取用户输入
// var s string
// fmt.Scan(&s)
// fmt.Println("用户输入的内容是:", s)

// var (
// name string
// age int
// class string
// )
// // fmt.Scanf("%s %d %s\n", &name, &age, &class)
// // fmt.Println(name, age, class)

// fmt.Scanln(&name, &age, &class)
// fmt.Println(name, age, class)

fmt.Printf("%b\n", 1024)
}

func printBaifenbi(num int) {
fmt.Printf("%d%%\n", num)
}

语法补充

1、time 包

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
package main

import (
"fmt"
"time"
)

// 时间

func f1() {
now := time.Now()
fmt.Println(now)
fmt.Println(now.Year())
fmt.Println(now.Month())
fmt.Println(now.Day())
fmt.Println(now.Date())
fmt.Println(now.Hour())
fmt.Println(now.Minute())
fmt.Println(now.Second())
// 时间戳
fmt.Println(now.Unix())
fmt.Println(now.UnixNano())
// time.Unix()
ret := time.Unix(1564803667, 0)
fmt.Println(ret)
fmt.Println(ret.Year())
fmt.Println(ret.Day())
// 时间间隔
fmt.Println(time.Second)
// now + 24小时
fmt.Println(now.Add(24 * time.Hour))
// Sub 两个时间相减
nextYear, err := time.Parse("2006-01-02 15:04:05", "2019-08-04 12:25:00")
if err != nil {
fmt.Printf("parse time failed, err:%v\n", err)
return
}
now = now.UTC()
d := nextYear.Sub(now)
fmt.Println(d)
fmt.Println("------------------------")
// 定时器
timer := time.Tick(time.Second)
for t := range timer {
fmt.Println(t) // 1秒钟执行一次
}

// 格式化时间 把语言中时间对象 转换成字符串类型的时间
// 2019-08-03
fmt.Println(now.Format("2006-01-02"))
// 2019/02/03 11:55:02
fmt.Println(now.Format("2006/01/02 15:04:05"))
// 2019/02/03 11:55:02 AM
fmt.Println(now.Format("2006/01/02 03:04:05 PM"))
// // 2019/02/03 11:55:02.342
fmt.Println(now.Format("2006/01/02 15:04:05.000"))
// 按照对应的格式解析字符串类型的时间
timeObj, err := time.Parse("2006-01-02", "2019-08-03")
if err != nil {
fmt.Printf("parse time failed, err:%v\n", err)
return
}
fmt.Println(timeObj)
fmt.Println(timeObj.Unix())

// Sleep
n := 5 // int
fmt.Println("开始sleep了")
time.Sleep(time.Duration(n) * time.Second)
fmt.Println("5秒钟过去了")
// time.Sleep(5 * time.Second)
}

// 时区
func f2() {
now := time.Now() // 本地的时间
fmt.Println(now)
// 明天的这个时间
// 按照指定格式取解析一个字符串格式的时间
time.Parse("2006-01-02 15:04:05", "2019-08-04 14:41:50")
// 按照东八区的时区和格式取解析一个字符串格式的时间
// 根据字符串加载时区
loc, err := time.LoadLocation("Asia/Shanghai")
if err != nil {
fmt.Printf("load loc failed, err:%v\n", err)
return
}
// 按照指定时区解析时间
timeObj, err := time.ParseInLocation("2006-01-02 15:04:05", "2019-08-04 14:41:50", loc)
if err != nil {
fmt.Printf("parse time failed, err:%v\n", err)
return
}
fmt.Println(timeObj)
// 时间对象相减
td := timeObj.Sub(now)
fmt.Println(td)
}

func main() {
// f1()
f2()
}

【注意】

  • 格式化输出的不同,2006.01.02 03:04:05 pm

2、日志库

  • 简单示例(往文件中写入)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import (
"fmt"
"log"
"os"
"time"
)

// log demo

func main() {
fileObj, err := os.OpenFile("./xx.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
fmt.Printf("open file failed, err:%v\n", err)
return
}
log.SetOutput(fileObj)

for {
log.Println("这是一条测试的日志")
time.Sleep(time.Second * 3)
}
}
  • 日志需求(一般情况)
    1. 支持往不同的地方输出
    2. 日志分级别
      1. debug
      2. Trace
      3. Info
      4. Warning
      5. Error
      6. Fatal
    3. 日志要支持开关控制
    4. 完整的日志记录要包含有时间、行号、文件名、日志级别、日志信息
    5. 日志文件要切割

3、反射

在Go语言的反射机制中,任何接口值都由是一个具体类型具体类型的值两部分组成的。 在Go语言中反射的相关功能由内置的reflect包提供,任意接口值在反射中都可以理解为由reflect.Typereflect.Value两部分组成,并且reflect包提供了reflect.TypeOfreflect.ValueOf两个函数来获取任意对象的Value和Type。

  • 通过反射获取对象的类型和值
  • 通过反射设置对象的值
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package main

import (
"fmt"
"reflect"
)

type Cat struct {
}

func reflectType(x interface{}) {
v := reflect.TypeOf(x)
fmt.Printf("type:%v\n", v)
fmt.Printf("type name:%v type kind:%v\n", v.Name(), v.Kind())
}

func reflectValue(x interface{}) {
v := reflect.ValueOf(x)
k := v.Kind() // 值的类型种类
switch k {
case reflect.Int64:
// v.Int()从反射中获取整型的原始值,然后通过int64()强制类型转换
fmt.Printf("type is int64, value is %d\n", int64(v.Int()))
case reflect.Float32:
// v.Float()从反射中获取整型的原始值,然后通过float32()强制类型转换
fmt.Printf("type is float32, value is %f\n", float32(v.Float()))
case reflect.Float64:
// v.Float()从反射中获取整型的原始值,然后通过float64()强制类型转换
fmt.Printf("type is float64, value is %f\n", float64(v.Float()))
}
}

// 通过反射设置变量的值
func reflectSetValue1(x interface{}) {
v := reflect.ValueOf(x)
if v.Kind() == reflect.Int64 {
v.SetInt(200) //修改的是副本,reflect包会引发panic
}
}

func reflectSetValue2(x interface{}) {
v := reflect.ValueOf(x)
if v.Elem().Kind() == reflect.Int64 {
v.Elem().SetInt(200) //修改的是副本,reflect包会引发panic
}
}

func main() {
var a float32 = 3.14
reflectType(a) // type:float32
var b int64 = 100
reflectType(b) // type:int64
var c = Cat{}
reflectType(c)
// ValueOf
reflectValue(a)
// 设置值
// reflectSetValue1(&b)
reflectSetValue2(&b)
fmt.Println(b)
}
  • 结构体中的反射应用
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
30
31
32
33
34
package main

import (
"fmt"
"reflect"
)

type student struct {
Name string `json:"name" zhoulin:"嘿嘿嘿"`
Score int `json:"score" zhoulin:"哈哈哈"`
}

func main() {
stu1 := student{
Name: "小王子",
Score: 90,
}

t := reflect.TypeOf(stu1)

fmt.Println(t.Name(), t.Kind()) // student struct

// 通过for循环遍历结构体的所有字段信息
fmt.Println(t.NumField()) // 2
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fmt.Printf("name:%s index:%d type:%v json tag:%v\n", field.Name, field.Index, field.Type, field.Tag.Get("zhoulin"))
}

// 通过字段名获取指定结构体字段信息
if scoreField, ok := t.FieldByName("Score"); ok {
fmt.Printf("name:%s index:%d type:%v json tag:%v\n", scoreField.Name, scoreField.Index, scoreField.Type, scoreField.Tag.Get("json"))
}
}

【反射是把双刃剑】

反射是一个强大并富有表现力的工具,能让我们写出更灵活的代码。但是反射不应该被滥用,原因有以下三个。

  1. 基于反射的代码是极其脆弱的,反射中的类型错误会在真正运行的时候才会引发panic,那很可能是在代码写完的很长时间之后。
  2. 大量使用反射的代码通常难以理解。
  3. 反射的性能低下,基于反射实现的代码通常比正常代码运行速度慢一到两个数量级。

4、strconv 包

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
30
31
32
33
34
35
36
37
38
39
40
41
42
package main

import (
"fmt"
"strconv"
)

// strconv

func main() {
// 从字符串中解析出对应的数据
str := "10000"
// ret1 := int64(str)
ret1, err := strconv.ParseInt(str, 10, 64)
if err != nil {
fmt.Println("parseint failed, err:", err)
return
}
fmt.Printf("%#v %T\n", ret1, int(ret1))

// Atoi:字符串转换成int
retInt, _ := strconv.Atoi(str)
fmt.Printf("%#v %T\n", retInt, retInt)

// 从字符串中解析出布尔值
boolStr := "true"
boolValue, _ := strconv.ParseBool(boolStr)
fmt.Printf("%#v %T\n", boolValue, boolValue)
// 从字符串中解析出浮点数
floatStr := "1.234"
floatValue, _ := strconv.ParseFloat(floatStr, 64)
fmt.Printf("%#v %T\n", floatValue, floatValue)

// 把数字转换成字符串类型
i := 97
// ret2 := string(i) // "a"

ret2 := fmt.Sprintf("%d", i) // "97"
fmt.Printf("%#v", ret2)
ret3 := strconv.Itoa(i)
fmt.Printf("%#v", ret3)
}