首页 > 技术文章 > 【go基础】

gokublog 2021-04-21 04:11 原文

Golang环境

1.Golang环境搭建

1.1 linux

1.2 macos

1.3 windows

2.Golang项目的目录结构

--pkg
--bin
--src
	--crm
		--app.go

3.Golang项目运行方式

3.1 第一种运行方式

// 直接运行
go run main.go

3.2 第二种运行方式

// 编译为可执行文件
go build
// 执行可执行文件
app.exe

3.2 第三种运行方式

// 生产可执行文件默认放在bin目录
go install

Golang基础

1.Golang包管理

关于包的总结:

  • 一个文件夹可以成为一个包
  • 早文件夹(包)中可以创建多个文件
  • 在用一个包文件夹(包)下的每一个文件中必须指定包名称,且包名称要相同

关于包的分类:

  • main包
    • main包里必须有main函数
    • main函数就是项目的入口
  • 非main包'
    • 用来拆分代码

2.Golang输出发fmt.Println

控制台输出推荐使用fmt.Println(),因为go本身的输出语句后面可能不更新了

package main

import (
	"fmt"
)
func main(){
	fmt.Println("今天真开心") //控制台输出
	fmt.Printf("今天真%s","开心呢")//格式化字符串
}

3.Golang注释

3.1 单行注释

//表示单行注释

3.2 多行注释

/*
多行注释
*/

4.Golang数据类型

  • 整型-整数
  • 字符串-文本
  • 布尔型-真假
package main

import "fmt"

func main(){
	//整型
	fmt.Println(666)
	fmt.Println(1+1)
	fmt.Println(2/2)
	fmt.Println(2*2)
	fmt.Println(1-1)
	fmt.Println(3%2)
	//字符串 双引号包裹起来
	fmt.Println("字符串类型")
	fmt.Println("钓鱼要钓刀鱼,刀鱼要到岛上钓")
	fmt.Println("zyt"+"dpl")//输出没有空格
	//布尔
	fmt.Println(1>2) //false
	fmt.Println(1<2) //true
	fmt.Println("ALEX"== "SB") //true
	if 2>1 {
		fmt.Println("那你可真牛啊")
	} else {
		fmt.Println("你个小垃圾")
	}
}

5.Golang变量

5.1 变量声明的意义

  • 变量可以存储数据
  • 变量可以重复使用
package main

import "fmt"

func main(){
	var name string = "tom"
	fmt.Println("name:",name)

	var age int = 18
	fmt.Println("age:", age)

	var flag bool = true
	fmt.Println("bool:", flag)
}

5.2 变量名要求

  • 变量名只能包括字母、数字、下划线
  • 数字不能开头
  • 不能使用go内置关键字
    • break、default、select、interface、case、map、const、if、range、type

image-20210411023626816

//变量名只能由字母数字下划线
var add? string = "1" 错误
// 数字不能开头
var 1add int = 1 错误的
// 不使用go关键字
var var string = "1"
  • 建议
    • 变量名见名知意
    • 驼峰命名
      • myBossName

5.3 变量名简写

  • 声明+赋值
var name string = "abc"
var name = "abc"
var := "abc"
  • 先声明再赋值
    • string默认值空串
    • int默认值0
    • bool默认值false
var name string
var age string
var phone string

var name,age,phone string
name = "abc"
age = "18"
phone = "123123123"
···
var (
    name = "abc"
    age = "18"
    phone = "123123123"    
)

5.4 变量的作用域

  • 变量的子作用域大于父级作用域

5.5 作用域和内存

示例1:

//引用
name := "武沛齐"

image-20210411045006681

示例2:

// golang会copy一份给新的nickname
name := "武沛齐"
nickname := name

image-20210411044956019

示例3:

// 变量重新赋值后会覆盖原来的值
name := "武沛齐"
nickname := name
name = "alex"

image-20210411045125499

注意事项:使用int、string、bool这三种数据类型,如果变量赋值会拷贝一份【值类型】

6.Golang常量

  • 常量是不能修改的变量
  • 关键字是const
  • 一般情况常量定义在全局 【因为常量不能被修改】
package main

import "fmt"
//全局常量
const (
    v3 = 1123
    v4 = 4156
    //v3 = int 错误的 不能修改
)
func main(){
	//定义常量
	//const age int = 89
	const age = 98 //简写
	fmt.Println(age)
    const (
        v1 = 123
        v2 = 456
        //v3 = int 错误的 不能修改
    )
}
  • iota
    • 可有可无 常量的计数器
package main

import "fmt"

// 全局iota
const (
	mon = iota + 1
	tue
	wed
	thu
	fir
	sat
	sun
)
func main() {
	//示例1
	//const (
	//	v1 = iota
	//	v2 = iota
	//	v3 = iota
	//	v4 = iota
	//	v5 = iota
	//)

	//示例2
	//fmt.Println(v1,v2,v3,v4,v5)
	//const (
	//	v1 = iota
	//	v2
	//	v3
	//	v4
	//	v5
	//	)
	//fmt.Println(v1,v2,v3,v4,v5)

	//示例3 从非0开始计数
	//const (
	//	v1 = iota + 2
	//	v2
	//	v3
	//	v4
	//	v5
	//)
	//fmt.Println(v1,v2,v3,v4,v5)

	//示例3 从非0开始计数
	const (
		v1 = iota
		_	//占用一个位置
		v2
		v3
		v4
		v5
	)
	fmt.Println(v1,v2,v3,v4,v5)
}

7.Golang输入

让用户输入,完成信息的校验 。

  • fmt.scan
    • fmt.scan要求输入两个值 必须输入两个 输入一个会一直等待
package main

import "fmt"

func main(){
	//示例1:
	//var name string	//声明变量
	//fmt.Println("请输入用户名")
	//fmt.Scan(&name)//将输入的内容的内存放在name对应的内存地址
	//fmt.Println(name)

	//示例2:
	var name string	//声明变量
	var age int
	fmt.Println("请输入用户名")
	//count 声明了不用会报错 用_下划线代替
	//count,err := fmt.Scan(&name,&age)//将输入的内容的内存放在name对应的内存地址
	_,err := fmt.Scan(&name,&age)//将输入的内容的内存放在name对应的内存地址
	if err == nil {
		fmt.Println(name,age)
	}else {
		fmt.Println("用户输入的信息错误",err)
	}
	//fmt.scan要求输入两个值 必须输入两个 输入一个会一直等待
}
  • fmt.scanln
    • fmt.scan要求输入两个值 如果输入一个 再回车会直接跳过
package main

import "fmt"

//scan
func main(){
	//示例1:
	//var name string	//声明变量
	//fmt.Print("请输入用户名")
	//fmt.Scanln(&name)//将输入的内容的内存放在name对应的内存地址
	//fmt.Println(name)

	//示例2:
	var name string	//声明变量
	var age int
	fmt.Println("请输入用户名")
	//count 声明了不用会报错 用_下划线代替
	//count,err := fmt.Scan(&name,&age)//将输入的内容的内存放在name对应的内存地址
	_,err := fmt.Scanln(&name,&age)//将输入的内容的内存放在name对应的内存地址
	if err == nil {
		fmt.Println(name,age)
	}else {
		fmt.Println("用户输入的信息错误",err)
	}
	//fmt.scan要求输入两个值 如果输入一个 再回车会直接跳过
}
  • fmt.scanf
    • 格式化字符串
package main

import "fmt"

func main() {
	var name string

	fmt.Print("请输入用户名")
	count,err :=  fmt.Scanf("我叫%s 今年18岁",&name)// 自动提取name 后面还有文本要加空格
	fmt.Println(name)
	fmt.Println(count,err)
}
  • 无法解决的问题

    • 如果输入有空格 只会获取空格之前的
  • 解决方法

    • 标准输入
package main

import (
   "bufio"
   "fmt"
   "os"
)

func main(){
   //问题 长文本有空格只能获取空格之前的
   //var message string
   //fmt.Println("请输入个人信息:")
   //fmt.Scanln(&message)
   //fmt.Println(message)

   //解决方法 标准输入
   reader:= bufio.NewReader(os.Stdin)
   // line 从stdin中读取一行的数据 (字节集合 转化为字符串)
   // reader默认一次读取4096个字节
   // isPrefix 表示是否读取完了
   // err 表示错误
   //line,isPrefix,err := reader.ReadLine()
   line,_,_ := reader.ReadLine()
   data := string(line)
   fmt.Println(data)
}

8.Golang条件语句

8.1 基本条件语句

if true {
    ...
} else{
    ...
}

8.2 多条件

if 条件1 {
    ...
} else if 条件2{
    ...
} else if 条件3{
    ...
} else{
    ...
}

8.3 嵌套条件语句

image-20210411141140942

9.Golang switch case语句

  • 注意事项:
    • 数据的比较只能相同数据类型的比较
switch num{
    case 条件1:
    fmt.Println("等于1")
    case 条件2:
    fmt.Println("等于2")
    case 条件3:
    fmt.Println("等于3")
    case 条件4:
    fmt.Println("等于4")
    default:
    fmt.Println=("都不满足")
}
package main

import (
	"fmt"
)

func main(){
	var num int
	fmt.Println("请输入一个数字")
	fmt.Scan(&num)//将输入的内容的内存放在name对应的内存地址
	switch num{
	case 1:
		fmt.Println("等于1")
	case 2:
		fmt.Println("等于2")
	case 3:
		fmt.Println("等于3")
	case 4:
		fmt.Println("等于4")
	default:
		fmt.Println("都不满足")
	}
}

10.Golang的for循环

10.1 for循环各种情况

for 循环条件 {
    循环体
}
package main

import (
	"fmt"
	"time"
)

func main(){
	//示例1:死循环
	//fmt.Println("循环开始")
	//for{
	//	fmt.Println("红鲤鱼绿鲤鱼与驴")
	//	time.Sleep(time.Second+1)//等1秒执行一次
	//}
	//fmt.Println("循环开结束")

	//示例2:for 加条件语句
	//fmt.Println("循环开始")
	//for 2>1{
	//	fmt.Println("红鲤鱼绿鲤鱼与驴")
	//	time.Sleep(time.Second+1)//等1秒执行一次
	//}
	//fmt.Println("循环开结束")

	//示例3:for 加条件语句+结束
	//fmt.Println("循环开始")
	//var num=1
	//for num<5{
	//	fmt.Println("红鲤鱼绿鲤鱼与驴")
	//	time.Sleep(time.Second+1)//等1秒执行一次
	//	num+=1
	//}
	//fmt.Println("循环开结束")

	//示例4:for 布尔类型示例
	fmt.Println("循环开始")
	var flag=true
	for flag{
		fmt.Println("红鲤鱼绿鲤鱼与驴")1
		time.Sleep(time.Second+1)//等1秒执行一次
		flag=false
	}
	fmt.Println("循环开结束")
}

10.2 for循环中有变量和条件11

////示例5:for 循环体内声明变量
for i:=1;i<10;i+=1{
   fmt.Println("zyt是我儿子")
}

11.Golang continue和break关键字

11.1 continue

在for循环中,当遇到continue后会终止当前的循环

11.2 break

在for循环中,当遇到break后会终止所有循环

11.3 对for循环打标签

对for循环进行打标签,然后通过break和continue可以实现多层循环的终止和跳出

f1:
for i:=1;i<3;i++{
    for j:=1;j<5;j++{
        if j == 3 {
            continue f1	//跳出f1的当前循环
        }
        fmt.Println(i,j)
    }
}
f2:
for i:=1;i<3;i++{
    for j:=1;j<5;j++{
        if j == 3 {
            break f2	//跳出f2的当前循环
        }
        fmt.Println(i,j)
    }
}

12. Golang goto关键字

goto关键字可以跳转到指定的行

image-20210411163332832

13. 字符串格式化

将数据格式化为特定格式的字符串。

package main

import "fmt"

func main(){
	var name,address,action string

	fmt.Println("请输入姓名:")
	fmt.Scanln(&name)

	fmt.Println("请输入位置:")
	fmt.Scanln(&address)

	fmt.Println("请输入行为:")
	fmt.Scanln(&action)

	result := fmt.Sprintf("我叫%s,在%s上%s",name,address,action)
	fmt.Println(result)
}

14.运算符

14.1 关系运算符

+
-
*
/
%
++ 自增
-- 自己

14.2 关系运算符

>=
<=
>
<
==
!=

14.3 逻辑运算符

&& and
|| or
!  not

14.4 位运算符

&
|
^
<<
>>

必知必会概念

  • 计算机中的存储、运算、网络传输的行为都是二进制的

  • 二进制的表示形式

    • 二进制
    • 十进制
  • 十进制和二进制 的转换

image-20210411190122993

14.5 赋值运算符

=
+=
-=
*=
/=
%=
<<=
>>=
&=
^=
|=

image-20210411190321814

14.6 运算符的优先级

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

Golang进制、单位、编码

1.进制

  • 计算机的本质底层都是二进制
  • 八进制、十进制、十六进制之间的对应关系
  • 满N进1

image-20210411191217188

2.单位

  • 计算机的底层都是二进制的

    1100101010101010101
    

由于计算机中本质上所有的东西以为二进制存储和操作的,为了方便对于二进制值大小的表示,所以就搞了一些单位,例如:流量还有多少M、硬盘容量有1T、计算机8G内存等、宽带是200M、千兆网络等。

计算机中表示对于二进制大小的常见单位有:

  • b(bit),位

      表示二进制有多少位,例如:      01101     就是 5位 = 5b      011011010 就是 9位 = 9b
    
  • B(byte),字节

      8位就是1个字节,例如:  10100101                就是 8位 = 8b = 1B= 1个字节  1010010110100101        就是 16位 = 16b = 2B= 2个字节
    
  • KB(Kilobyte),千字节

      1024个字节就是1千字节(1KB),即:  1 KB = 1024 B = 1024*8 b
    
  • M(Megabyte),兆

      1024个千字节就是1兆(1M),即:  1M = 1024 KB = 1024 * 1024 B = 1024 * 1024 * 8 b
    
  • G(Gigabyte),千兆

      1024个兆就是1千兆(1G),即:  1G = 1024 M = 1024 * 1024 KB = 1024 * 1024 * 1024 B = 1024 * 1024 * 1024 * 8 b
    
  • T(Terabyte),万亿字节

      1024个G就是1T
    
  • …其他更大单位 PB/EB/ZB/YB/BB/NB/DB 不再赘述。

学完上面的这些单位之后,是不是就了解了平时大家说的自己有1G流量是啥意思了。

3.编码

3.1 ascii编码

最多用二进制的八位来表示所有的情况。

00000000
00000001
00000010
........
11111111
2**8=256个

ascii编码有256个

3.2 unicode(万国码)

  • us2,用16为来表示所有的情况。2**16=65536 0-65545
00000000 00000000
...
...
11111111 11111111
  • us4,用32为来表示所有的情况。
00000000 00000000 00000000 00000000 
...
...
11111111 11111111 11111111 11111111

注意:us2和us4根据情况来选

3.3 utf-8编码

  • 第二步:码位以二进制展示,再根据模板进行转换找模板

      码位范围(十六进制)                转换模板
       0000 ~ 007F              0XXXXXXX
       0080 ~ 07FF              110XXXXX 10XXXXXX
       0800 ~ FFFF              1110XXXX 10XXXXXX 10XXXXXX
      10000 ~ 10FFFF            11110XXX 10XXXXXX 10XXXXXX 10XXXXXX
      例如:
          "B"  对应的unicode码位为 0042,那么他应该选择的一个模板。
          "ǣ"  对应的unicode码位为 01E3,则应该选择第二个模板。
          "武" 对应的unicode码位为 6B66,则应该选择第三个模板。
          "沛" 对应的unicode码位为 6C9B,则应该选择第三个模板。
          "齐" 对应的unicode码位为 9F50,则应该选择第三个模板。
          注意:一般中文都使用第三个模板(3个字节),这也就是平时大家说中文在utf-8中会占3个字节的原因了
    
  • 第二步:码位以二进制展示,再根据模板进行转换

    码位拆分: "武"的码位为6B66,则二进制为 0110101101100110
    根据模板转换:
    6    B    6    6
    0110 1011 0110 0110
    ----------------------------
    1110XXXX 10XXXXXX 10XXXXXX    使用第三个模板
    11100110 10XXXXXX 10XXXXXX    第一步:取二进制前四位0110填充到模板的第一个字节的xxxx位置
    11100110 10101101 10XXXXXX    第二步:挨着向后取6位101101填充到模板的第二个字节的xxxxxx位置
    11100110 10101101 10100110    第二步:再挨着向后取6位100110填充到模板的第三个字节的xxxxxx位置
    最终,"武"对应的utf-8编码为 11100110 10101101 10100110
    

总结:

本章的知识点属于理解为主,了解这些基础之后有利于后面知识点的学习,接下来对本节所有的知识点进行归纳总结:

  1. 计算机上所有的东西最终都会转换成为二进制再去运行。
  2. ascii编码、unicode字符集、utf-8编码本质上都是字符与二进制的关系。
    • ascii,字符和二进制的对照表。
    • unicode,字符和二进制(码位)的对照表。
    • utf-8,对unicode字符集的码位进行压缩处理,间接也维护了字符和二进制的对照表。
  3. ucs2和ucs4指的是使用多少个字节来表示unicode字符集的码位。
  4. 目前最广泛的编码为:utf-8,他可以表示所有的字符且存储或网络传输也不会浪费资源(对码位进行压缩了)。
  5. 二进制、八进制、十进制、十六进制其实就是进位的时机不同。
  6. 一个字节8位
  7. b/B/KB/M/G的关系。

image-20210411223105386

Golang数据类型

数据类型,其实就是各种各样的数据。

Go语言中常见的数据类型有很多例如

  • 整形
  • 浮点型
  • 布尔型
  • 字符串
  • 数组
  • 指针
  • 切片
  • 字典
  • 结构体
  • 接口

1.整形

序号 类型和描述
1 uint8 无符号 8 位整型 (0 到 255)
2 uint16 无符号 16 位整型 (0 到 65535)
3 uint32 无符号 32 位整型 (0 到 4294967295)
4 uint64 无符号 64 位整型 (0 到 18446744073709551615)
5 int8 有符号 8 位整型 (-128 到 127)
6 int16 有符号 16 位整型 (-32768 到 32767)
7 int32 有符号 32 位整型 (-2147483648 到 2147483647)
8 int64 有符号 64 位整型 (-9223372036854775808 到 9223372036854775807)
  • int
    • 在32为操作系统中(-2147483648 到 2147483647) 2**32
    • 在64为操作系统中( -9223372036854775808 到 9223372036854775807) 2*64

1.1 整形的相互转换

要转换成声明就用什么函数包裹起来

package main
import "fmt"
func main(){
   var v1 int8 = 1
   var v2 int16 = 2
   var v3 = int16(v1)+v2) //将v1转换为int16
   fmt.Println(v3)
}

注意:

  • 低位转高位不会出问题
  • 高位转为低位置可能会有问题

1.2 整形和字符串的转换

  • 整形 转换 字符串
    • strconv.Itoa() 整数转为字符串
    • 转换的之后只能是int类型 不能是int8,int16,int32
// 整形和字符串的转换
// strconv.Itoa() 整数转为字符串
v6 := 19
result := strconv.Itoa(v6)
fmt.Println(result,reflect.TypeOf(result))

// 转换的之后只能是int类型 不能是int8,int16,int32
var v7 int8 = 123
result2 := strconv.Itoa(int(v7))
fmt.Println(result2,reflect.TypeOf(result2))
  • 字符串 转为 整形
    • 转换失败后result3还是int的初始值
// strconv.Atoi() 字符串转为数字
v8 := "000i"
result,err := strconv.Atoi(v8)
fmt.Println(result,reflect.TypeOf(result))
fmt.Println(err,reflect.TypeOf(err))

1.3 进制转换

  • Go代码中:
    • 十进制中,整形的方式存在
    • 其他进制,字符串方式存在
  • 整形,10进制
// 字符串转换
//data:要转换的文本
//2:   将字符串按照2进制处理转换为10进制
//16:   转换过程中的约束不能超过int16的范围
//result: 永远是int64()
data := "1010101001"
result,err := strconv.ParseInt(data,2,16)
fmt.Println(result,err)
  • 通过Parseint转换的时候 ,本质上和Atoi是相同的

image-20210412003521025

1.4 数学运算

image-20210412004239477

1.5 指针/nil/变量声明/new

  • 声明变量

    var v1 int
    v2 := 999
    

    image-20210412004454680

  • 指针

    • 创建一个内存,内存中存储了真正的数据存放的地址
    • 指针是为了节省内存
    var v3 *int //声明一个int类型的指针
    v4 := new(int) //声明一个int类型的指针
    

    image-20210412004954504

  • new

    new用于创建内存并进行内部数据的初始化,并返回一个指针
    
  • nil

    nil是空值
    
    • 指针默认为nil

1.6 超大整型

//超大整形
//创建一个超大整形
var v1 big.Int
//var v2 *big.Int  //一般用不到(直接赋值的时候用)
v3:=new(big.Int)

//第二步 在超大整形中写入值
v1.SetInt64(1990)
v1.SetString("92358203984",10) //将字符串已10进制写在v1中
v3.SetInt64(1990)
v3.SetString("92358203984",10) //将字符串已10进制写在v1中

第一步:创建超大整形对象

//第一步:床i教案一个超大整形的对象
var v1 big.Int
var v2 big.Int

//第二步:在超大整形对象中写入值
v1.SetInt64(93492342394234)
fmt.Println(v1)

v2.SetString("237498237489023042809428093408240394",10)
fmt.Println(V2)
//第一步:床i教案一个超大整形的对象
v3 := new(big.Int)
v4 := new(big.Int)

//第二步:在超大整形对象中写入值
v3.SetInt64(2348920384923840923490)
fmt.SetString(v3)

v4.SetInt64("237498237489023042809428093408240394",10)
fmt.Println(v4)

推荐:使用指针的方式:即使用new来创建和初始化对象

第二步:基本的加减乘除

  • 加法
//v1 := new(big.Int)
//v1.SetInt64(89)
//v2 := new(big.Int)
//v2.SetInt64(89)
v1 := big.NewInt(69)
v2 := big.NewInt(89)
res := new(big.Int)
res.Add(v1,v2)
fmt.Println(res)
  • 其他
v1 := big.NewInt(69)
v2 := big.NewInt(68)
res := new(big.Int)

//加
res.Add(v1,v2)
fmt.Println(result)

//减
res.Sub(v1,v2)
fmt.Println(result)

//乘
res.Mul(v1,v2)
fmt.Println(result)

//除 得到商
res.Div(v1,v2)
fmt.Println(result)

//除 得商和余数
minder := new(big.Int)
result.DivMod(v1,v2,minder)
fmt.Println(result,minder)
  • 结果
v1 := new(big.Int)
v1.SetString("692354345324523453245345",10)
v2 := new(big.Int)
v2.SetString("68234523452352353525234523452345",10)
result := new(big.Int)
result.Add(v1,v2)
fmt.Println(result.String())

建议:

  • 尽量new创建一个指针
  • int和*int是两种类型

2.浮点型

浮点数,算计中表示小数的类型,如3.14

Go语言中有两种浮点型:

  • float32,用32位(4个字节)来存储浮点数

  • float64,用64位(8个字节)来存储浮点数

package main

import "fmt"

func main() {
	var v1 float32
	v1 = 3.14
	v2 := 3.15 // 默认是float64
	v3 := float64(v1) + v2
	fmt.Println(v3)
}
>>>6.290000104904175

2.1 非精确的结果

浮点数处理的时候是非精确的计算

浮点数的底层存储原理导致的

2.2 浮点数的存储原理

var price flaot64 = 39.29

第一步:浮点数转型为二进制

  • 整数部分,直接转换为二进制
  • 小数部分,让小数部分乘2,结果小于1则乘2,结果小于1则将结果减1乘2,一直循环,直到结果等于一。
    • 浮点数的小数部分就是将运算结果的整数部分拼接起来

image-20210412234101481

第二步:科学计数法表示

image-20210412234205992

第三步:存储

以float32来进行存储,用32为来存储浮点型

image-20210412234308939

  • sign 用1位来表示浮点数的正负
    • 1 正数
    • 0 负数
  • exponent 用8位来表示256中可能(0255),含正负值(-127128)。例如:5要存储到exponent位的话,需要让5+127=132,再将132转为二进制,存储在exponent。(132的二进制是01000010)
  • fraction存储小数点后的所有数据。超过23为不要了

float64和32类似只是sign=1位 exponent=11位 fraction=52位也就意味范围更大的

2.3 decimal精确小数

Go语言内部没有decimal

第三方包,则需要在Go语言的环境中安装再使用

http://github.com//shopspring/decimal

使用步骤

第一步:安装第三包

go get github.com//shopspring/decimal
  • 会放在SRC目录下,编译之后会放在pkg目录

第二步:使用decimal包

image-20210412235426338

3.布尔型

表示真假。一般配合条件使用,用于满足一些操作

package main

import (
   "fmt"
   "strconv"
)

func main() {
   //字符串转换布尔、
   //true:"1", "t", "T", "true", "TRUE", "True"
   //false: "0", "f", "F", "false", "FALSE", "False"
   result,err := strconv.ParseBool("true")
   fmt.Println(result,err)
}

4.字符串

4.1 字符串的本质

计算机中的所有数据本质都是二进制

Go语言中的字符串是utf-8编码的序列。

package main

import (
	"fmt"
	"strconv"
	"unicode/utf8"
)

func main() {
	// 1.字符串的本质是utf-8序列
	var name string = "武沛齐"
	// 武 11100110 10101101 10100110
	fmt.Println(name[0],strconv.FormatInt(int64(name[0]),2))
	fmt.Println(name[1],strconv.FormatInt(int64(name[1]),2))
	fmt.Println(name[2],strconv.FormatInt(int64(name[2]),2))
	// 沛 11100110 10110010 10011011
	fmt.Println(name[3],strconv.FormatInt(int64(name[3]),2))
	fmt.Println(name[4],strconv.FormatInt(int64(name[4]),2))
	fmt.Println(name[5],strconv.FormatInt(int64(name[5]),2))
	// 齐 11101001 10111101 10010000
	fmt.Println(name[6],strconv.FormatInt(int64(name[6]),2))
	fmt.Println(name[7],strconv.FormatInt(int64(name[7]),2))
	fmt.Println(name[8],strconv.FormatInt(int64(name[8]),2))

	// 2.获取字符串的长度
	fmt.Println(len(name))

	// 3.字符串转为字节的集合
	byteSet := []byte(name)
	fmt.Println(byteSet)	//[230 173 166 230 178 155 233 189 144]

	// 4.字节的集合转换位字符串
	byteList := []byte{230, 173, 166, 230, 178, 155, 233, 189, 144}
	targetString := string(byteList)
	fmt.Println(targetString)	//武沛齐

	// 5.将字符串转unicode字符码点的集合-rune	ucs4
	tempSet := []rune(name)
	fmt.Println(tempSet)	//[27494 27803 40784]
	fmt.Println(tempSet[0],strconv.FormatInt(int64(tempSet[0]),16))	//27494 6b66 unicode编码
	fmt.Println(tempSet[1],strconv.FormatInt(int64(tempSet[1]),16))	//27803 6c9b unicode编码
	fmt.Println(tempSet[2],strconv.FormatInt(int64(tempSet[2]),16))	//40784 9f50 unicode编码

	// 6.rune集合转换位字符串
	runeList := []rune{27494, 27803, 40784}
	targetName := string(runeList)
	fmt.Println(targetName)	//武沛齐

	// 7.长度的处理-字符串的长度
	length := utf8.RuneCountInString(name)
	fmt.Println(length)	// 3
}

4.2 字符串的常见功能

4.2.1 获取字符串的长度

image-20210413005145142

4.2.2 是否以xxx开头

image-20210413005152036

4.2.3 是否以xxx结尾

image-20210413005229259

4.2.4 是否包含

image-20210413005214847

4.2.5 转大写

image-20210413005323633

4.2.6 转小写

image-20210413005256506

4.2.7 去除两边

image-20210413005356477

4.2.8 替换

image-20210413005458539

4.2.9 分割

image-20210413005526564

4.2.10 拼接

image-20210413004415242

4.2.11 string转为int

image-20210413004531784

4.2.12 int转为string

image-20210413004556553

4.2.13 字符串和字符结合

image-20210413004629215

4.2.14 字符串和rune集合

image-20210413004701836

4.2.15 string和字符

image-20210413005046553

  • 应用场景:生成一个随机数,带入得到随机字符。

4.3 字符串的索引、切片、循环

image-20210413010110528

5.数组

数组是定长且元素类型一致的集合。

// 方式一:先声明再赋值 (内存中已开辟了空间 内存初始化的值为0)
var numbers [3]int
numbers[0] = 333
numbers[1] = 666
numbers[2] = 999
fmt.Println(numbers)  // [333 666 999]

// 方式二:先声明 后赋值
var names = [2]string{"胡皓","ALEX"}
fmt.Println(names) // [胡皓 ALEX]

// 方式三: 声明+赋值+指定位置
var ages = [3]int{0:87,1:73,2:34}
fmt.Println(ages) //[87 73 34]

// 方式四:省略个数
var ids = [...]string{"1","2","3"}	//[1 2 3]
fmt.Println(ids)
var genders = [...]string{0:"nan",2:"other"}//[nan  other]
fmt.Println(genders)
//var genders = [...]string{0:"nan",1:"nv",2:"other"}//[nan  other]
//fmt.Println(genders)// [nan nv other]
// 指针数组
// 声明 指针类型的数组(指针类型) 不会开辟内存初始化数组中的值 numbers = nil
var numbers *[3]int
fmt.Println(numbers)//<nil>
// 声明数组并初始化 返回的是指针类型的数组
genders := new([3]int)
fmt.Println(genders)// &[0 0 0]

5.1 数组的内存管理

image-20210414000601363

数组是定长且元素类型一致的集合。

必备知识点:

  • 数组的内存是连续的
  • 数组的内存地址是数组的第一个元素的内存地址
  • 创建字符串的时候,字符串内部存了:len+str

示例1:

image-20210414000656422

示例2:

image-20210414001125983

示例3:

image-20210414001213054

5.2 数组的可变和拷贝

可变:数组的元素可以被更改(长度和类型不能修改)

names := [3]string{"alex","huhao","lufei"}
names[0] = "yuanhao"

字符串不可以被修改

拷贝:变量赋值的时候重新拷贝

names1 := [3]string{"alex","huhao","lufei"}
names2 = names1
names[1] = "afei"
fmt.Println(names1,names2)

5.3 索引、切片、循环、长度

  • 长度
names1 := [3]string{"alex","huhao","lufei"}
fmt.Println(len(names1)) // 3
  • 索引
//数组索引
names2 := [3]string{"alex","huhao","lufei"}
fmt.Println(names2[0]) //alex
fmt.Println(names2[1]) //huhao
fmt.Println(names2[2]) //lufei
  • 切片
//数组切片 --包头不包尾
names3 := [3]string{"alex","huhao","lufei"}
data := names3[0:1]	//包头不包尾
fmt.Println(data) // [alex]
  • 循环
//循环
names4 := [3]string{"alex", "huhao", "lufei"}
for i := 0; i < len(names4); i++ {
    fmt.Println(names4[i]) //alex huhao lufei
}

for key, item := range names4 {
    fmt.Println(key, item) //0 alex	1 huhao	2 lufei
}

for key, _ := range names4 {
    fmt.Println(key) //0 1 2
}

for _, item := range names4 {
    fmt.Println(item) //ale huhao lufei
}

5.4 数组嵌套

//嵌套数组
//声明后赋值
var data [2][3]int
data[0] = [3]int{11,22,33}
data[1] = [3]int{44,55,66}
fmt.Println(data) //[[11 22 33] [44 55 66]]
data[0][0] = 12
fmt.Println(data) //[[12 22 33] [44 55 66]]

//声明+赋值
data2 := [2][3]int{[3]int{11,22,33},[3]int{44,55,66}} //[[11 22 33] [44 55 66]]
fmt.Println(data2)

6.切片

切片:动态数组

切片是Golang中重要的数据类型,每个切片对象内部都维护着:数组指针、切片长度、切片容量三个数据

type slice struct{
    
}

image-20210414003829900

在向切片中追加数据个数大于容量的时候,容量会变为原来的两倍(当容量超过1024时候每次增加1/4)

image-20210414004016528

6.1 创建切片

  • 切片的数据类型
//创建切片
var nums []int

//创建切片 推荐
var data = []int{11,22,33}
data := []int{11,22,33}

//创建切片 推荐
//make只能用于切片、字典、channel
var users = make([]int,2,5)
  • 切片的指针类型
//创建一个切片类型的指针
var v1 = new([]int)

//创建一个切片类型的指针 指向 nil
var v2 *[]int

6.2 自动扩容

6.2.1 没有扩容

如果没有扩容,原来的切片会多一个引用容量、地址相同,长度不同

v1 := make([]int,1,3)
fmt.Println(len(v1),cap(v1))	//获取长度和容量

// 其他
v2 := make([]int,3)

image-20210414004845921

  • append函数扩容切片

    v1 := make([]int,1,3)
    
    v2: = append(v1,66)
    
    fmt.Println(v1) // [0 ]
    fmt.Println(v2)	// [0 66]
    v1[0] = 99
    fmt.Println(v1) // [99 ]
    fmt.Println(v2)	// [99 66]
    

image-20210414005202524

//需求:有一个切片,往切片中追加一个数据
v1 := make([]int,1,3)
v1: = append(v1,66)

6.2 扩容了

如果扩容了会创建一个新的切片容量、长度、地址都先同

v1 := []int{11,22,33}
v2 := append(v1,44)

image-20210414005649014

6.3 常见操作

6.3.1 长度和容量相关

  • 长度:len()
  • 容量:cap()
v1 := make([]int,1,3)
fmt.Println(len(v1),cap(v1))	//获取长度和容量

6.3.2 索引

  • 根据长度去索引取值
  • 不要根据容量索引去取值
v1 := []string{"alex","lijie","oldboy"}
v1[0]
v1[1]
v1[2]

v2 := make([]int,2,5)
v2[0]
v2[1]
v2[2]	//报错

//根据索引修改值
v2[0] = 999

6.3.3 切片

  • 切片的切片不会创建新的切片,还是引用原来的切片,只是切片的内存地址起始位置变了
v1 := make([]int{0,1,2,3,4,5})
v2 = v1[1:3]
v3 = v1[1:]
v4 = v1[:4]

6.3.4 追加

  • 追加:append()
v1 := make([]int{0,1,2})

v2 = append(v1,3)

v3 = append(v1,3,4,5) 

v4 = append(v1, []int{100,200,300}...)

6.3.5 删除(不建议)

  • 删除是通过append()构造出来的
v1 := make([]int{11,22,33,44,55,66})
delete_index = 2
result := append(v1[:delete_index],v1[delete_index+1:]...)
fmt.Println(result) // [11 22 44 55 66]
fmt.Println(v1) // [11 22 44 55 66 66]

使用切片的时候一般不适用删除

  • 效率低
  • 修改了远切片的内容并且覆盖了
  • 删除的时候用链表

6.3.6 插入(不建议)

v1 := int[]{11,22,33,44,55,66}
insertIndex = 3 //在3地方插入99
result = make([]int,len(v1)+1)
result = append(result,v1[:insertIndex])
result = append(99)
result = append(result,v1[insertIndex:])
fmt.Println(result)

使用切片的时候一般不适用插入

  • 效率低
  • 出错
  • 删除的时候用链表

6.3.7 循环

  • range循环
  • index循环
func main(){
    v1 := []int{11,22,33,44,55,66}
    
    for i:=0;i<len(v1);i++{
        fmt.Println(i,v1[i])
    }
    
    for index,value := range v1{
        fmt.Println(index, value)
    }
}

6.4 切片的嵌套

package main

import "fmt"

func main() {
	// slice
	v1 := []int{11,22,33}
	// slice + slice
	v2 := [][]int{[]int{11,22,33,44},[]int{55,66}}
	// slice + array
	v3 := [][2]int{[2]int{11,22},[2]int{33,44}}

	fmt.Println(v1)	// [11 22 33]
	fmt.Println(v2)	// [[11 22 33 44] [55 66]]
	fmt.Println(v3) // [[1122] [33 44]]
}

6.5 变量赋值

  • 整形

    v1 := 1
    v2 = v1
    

    image-20210414013259925

  • 布尔

    image-20210414013321123

  • 浮点

    image-20210414013404723

  • 字符串

    image-20210414013510667

    image-20210414013519614

注意:字符串内部不能被修改

  • 数组

    image-20210414013722281

  • 切片

    • 如果不扩容:内部真正存储数据的地址相同

    image-20210414013908624

    image-20210414013917612

    • 如果扩容:内部存储数据的地址会不相同

    image-20210414014126954

扩展:值类型和引用类型

7.字典

在学习任何编程语言的时候,一般都会有这种数据类型:字典(dict)或者映射(map),以键值对来存储数据的集合。例如:

{
    "name":"huhao"
    "age":18,
    "email":"abc@123.com"
}

这种该数据结构查找数据非常快,其原因是取模+拉链法原理如图:

image-20210414233102803

Map的特点:

  • key不能重复
  • key必须可哈希(int,floot,bool,string,array)
  • 无序

7.1 声明&初始化

//第一种方式
//userInfo := map[string]string{}
userInfo := map[string]string{"name":"huhao","age":"18"}

userInfo["name"]
userInfo["age"] = "20"
userInfo["email"] = "abc@123.com"
//第二种方式
//data := make(map[int]int,10)  //10十个键值对的位置
data := make(map[int]int)  
data[100] = 111
data[200] = 222
//第三种 整体赋值 不常用
data := make(map[int]int)  
data[100] = 111
data[200] = 222

// 声明 nil 整体赋值
var row map[string]int
//row["name"] = 666//报错 不允许
row = data
//第四种 整体赋值 不常用
data := make(map[int]int)  
data[100] = 111
data[200] = 222

// 指针 nil 整体赋值
value := new(map[string])
//value["K1"] = "123"//报错 不允许

//data转为指针 赋值
value = &data

注意:键不可重复,值可哈希

8.指针

  • 指针是一种数据类型,用来表示数据的内存地址

    // 声明一个字符串类型数据
    var v1 string
    
    // 声明了一个字符串类型的指针
    var v1 *string
    
    // 声明一个字符串类型数据,值是"abc"
    var name string =  "abc"
    
    
    // 声明一个字符串类型的指针,值为abc的内存地址
    // &name 获取name变量的内存地址,他的类型是一个指针
    var pointer *string = &name
    

    image-20210416225604030

8.1 指针的意义

  • 指针的意义相当于创建了一个值的引用,以后可以根据这个引用去获取值

image-20210416225730797

v1 := "武沛齐"
v2 := &v1
// *v2:因为v2存的是一个内存地址,*v2表示取出这个内存地址代表的值
fmt.Println(v1,v2,*v2) //武沛齐 0xc00004a240 武沛齐

8.2 指针的应用场景

场景一:

  • 如果希望v2的值和v1无关就直接赋值
v1 := "武沛齐"
v2 := v1
v1 = "alex"

fmt.Prinln(v1,v2) // alex 武沛齐
  • 如果希望v2的值随v1的 变化而变化就用指针
v1 := "武沛齐"
v2 := &v1
v1 = "alex"

fmt.Prinln(v1,v2) // alex alex

场景二:

func changeData(data string)  {
	data = "二哈"
}

func main() {
	name := "武沛齐"
    // 本质上会吧 name := "武沛齐" 复制一份给函数
	changeData(&name)
	fmt.Println(name)	//武沛齐
}
func changeData(ptr *string)  {
	*ptr = "二哈"
}

func main() {
	name := "武沛齐"
    // 吧name的内存地址传给了*ptr 函数中会把对应的内存中存的值改为二哈
	changeData(&name)
	fmt.Println(name)	//二哈

}

场景三:

func main() {
	var username string
	fmt.Println("请输入用户名")
	fmt.Scanf("%s", &username)
	if username == "武沛齐" {
		fmt.Println("登陆成功")
	} else {
		fmt.Println("登陆失败")
	}
}

8.3 指针的指针

// 声明了一个变量
name := "武沛齐"

// 声明了一个指针p1指向的是name的内存地址
var p1 *string = &name

// 声明了一个指针p2指向的是指针p1的内存地址
var p2 *string = &p1

// 声明了一个指针p3指向的是指针p2的内存地址
var p3 *string = &p2

image-20210416234740528

8.4 指针的高级操作

  • 数组的地址==数组的第一个元素的地址

image-20210416234929013

dataList := [3]int8{11,22,33}
fmt.Println("数组的地址:%p;数组的第一个元素的地址:%p \n",&*dataList,&dataList[0])
  • 指针的计算

    
    

    image-20210416235206405

9.结构体

什么是结构体?

  • 结构体是一个复合类型,用于表示一组数据。
  • 结构体一般由一系列属性组成,每个组成都有自己的类型和值
//结构体的结构
type 结构体名称 struct{
    字段 类型
    字段 类型
    字段 类型
    ......
}
type Person struct{
    name string
    age int
    email string
}

// 初始化
var p1 =  Person("武沛齐",18,"ABC@123.com")

// 取值
fmt.Println(p1.name,p1.age,p1.email)

// 修改
p1.age = 20

// 取值
fmt.Println(p1.name,p1.age,p1.email)

9.1 定义

  • 方式1:基本方法定义
type Person struct{
    name string
    age int
    email string
}
  • 方式2:相同类型合并
type Person struct{
    name,email string
    age int
}
  • 方式3:结构体的嵌套
type Adress struct{
	city,state string
}
type Person struct{
    name string
    age int
    ad Adress
}
  • 方式4: 匿名字段
type Adress struct{
	city,state string
}
type Person struct{
    name string
    age int
    Adress //匿名字段 Adress Adress
}

9.2 初始化

  • 根据结构体创建对象
func main() {
	type Person struct{
		name string
		age int
		email string
	}
	//1.按照先后顺序
	var p1 = Person{"武沛齐",18,"asd@123.com"}
	fmt.Println(p1.name,p1.age,p1.email)//武沛齐 18 asd@123.com
	//2.按照关键字
	var p2 = Person{name:"武沛齐",age:18,email:"asd@123.com"}
	fmt.Println(p2.name,p2.age,p2.email)//武沛齐 18 asd@123.com

	//3.先声明再赋值
	var p3 Person
	p3.name = "武沛齐"
	p3.age = 18
	p3.email = "asd@123.com"
	fmt.Println(p3.name,p3.age,p3.email)//武沛齐 18 asd@123.com
}

9.3 结构体指针

type Person struct{
    name string
    age int
}

// 初始化结构体
var p1 = Person{"武沛齐",18}
fmt.Println(p1.name,p1.age)	//武沛齐 18	

// 初始化结构体指针
var p2 = &Person{"武沛齐",18}
fmt.Println(p2.name,p2.age)	//武沛齐 18	//这里的数据是根据指针得到的

9.4 赋值

9.4.1 赋值时拷贝

  • 结构体
type Person struct{
    name string
    age int
}
var p1 = Person{"武沛齐",18}
p2 := p1 //内部将p1重新拷贝一份

fmt.println(p1)	//{"武沛齐",18}
fmt.println(p2) //{"武沛齐",18}

p1.name = "alex"

fmt.println(p1)	//{"alex",18}
fmt.println(p2) //{"武沛齐",18}

9.4.2 结构体指针

type Person struct{
    name string
    age int
}
var p1 = &Person{"武沛齐",18}
p2 := p1 //内部将p1重新拷贝一份

fmt.println(p1)	//{"武沛齐",18}
fmt.println(p2) //{"武沛齐",18}

p1.name = "alex"

fmt.println(p1)	//{"alex",18}
fmt.println(p2) //{"武沛齐",18}

image-20210417002014531

9.4.3 基于结构体实现数据同步

type Person struct{
    name string
    age int
}
var p1 = &Person{"武沛齐",18}

p2 := &p1 //内部将p1重新拷贝一份

fmt.println(p1)	// {"武沛齐",18}
fmt.println(p2) // &{"武沛齐",18}

p1.name = "alex"

fmt.println(p1)	// {"alex",18}
fmt.println(p2) // &{"alex",18}

9.4.4 嵌套赋值时拷贝

  • 嵌套结构体也会拷贝所有数据
type Person struct{
    name string
    age int
    Adress Adress
}
type Adress struct{
    staet string
}
p1 = Person{name:"二狗",age:16,Adress{"BEIJING"}}

9.4.5 嵌套时不拷贝的处理

  • 如果嵌套结构体的时候有一些默认不拷贝的想让它拷贝可以定义为指针类型

9.5 结构体标签

type Person struct{
    name string "姓名"
    age int	"年龄"
    blog string "博客"
}
p1 := Person{name:"武沛齐",age:18,blog:"http://www.123.com"}
p1Type := reflect.TypeOf(p1)

// 方式1
field1 := p1Type.field(0)
fmt.Println(field1.Tag) //"姓名"

// 方式2
field2,_:=p1Type.FieldByName("blog")
fmt.Println(field2.Tag)

//循环获取
fieldNum := p1Type.NumField("blog")
for index := 0;index<fieldNum;index++{
    field:= p1Type.Field(index)
    fmt.Println(field.Name,field.Tag)//field.Name--字段名 ,field.Tag--标签名称
}

10.函数

把函数当作一部分代码块,用于实现某个功能方便掉用

func 函数名称(参数)返回值类型{
    函数体
    return 返回值
}
func SendMail(data string)(bool){
    fmt.Println("发邮件成功")
    returm true
}

10.1 函数的参数

10.1.1 多个参数

package main

import "fmt"

func add(num1 int, num2 int) (int,bool){
result := num2 + num1
return result, true
}

func main() {
	data, flag := add(1, 2)
	fmt.Println(data, flag)
}
  • 函数传递的参数如果是数组,则内部不会改变数组的值,如果想要修改数组的值,需要传递数组的指针

  • 函数的参数会重新拷贝一份

  • map个slice不会拷贝

10.1.2 指针参数

  • 如果会拷贝的数据类型不想让他拷贝可以基于指针来做
package main

import "fmt"

// 数组类型
func test(arg [2]int){
	arg[0] = 666
}

// 指针类型
func test2(arg *[2]int){
	arg[0] = 666
}

func main() {

	//指针类型的参数
	data_list := [2]int{11,22}
	test(data_list)
	fmt.Println(data_list) //[11 22]

	//指针类型的参数
	data_list2 := [2]int{11,22}
	test2(&data_list2)
	fmt.Println(data_list2) //[666 22]

}

10.1.3 函数做参数

  • 函数作为参数
package main

import "fmt"

func add100(arg int) (int,bool){
    return arg+100,true
}

func proxy(data int, exec func(int) (int,bool)) int {
    data,flag := exec(data)
    if flag {
        return data
    }else{
        return 999
    }
}
func main(){
    result := proxy(123,add100)
    fmt.Println(result)
}
  • 简便写法
package main

import "fmt"

func add100(arg int) (int,bool){
	return arg+100,true
}

type f1 func(arg int)(int,bool)	// 简便写法

func proxy(data int, exec f1) int {	// 简便写法
	data,flag := exec(data)
	if flag {
		return data
	}else{
		return 999
	}
}
func main(){
	result := proxy(123,add100)
	fmt.Println(result)
}

10.1.4 变长参数

  • 在参数中通过...来传递变长参数
package main

import "fmt"

// 变长参数
func do(num ...int) int {
	fmt.Println(num)
	sum := 0
	for _, value := range num {
		sum += value
	}
	return sum
}

func main() {
	// 变长参数
	r1 := do(1,2,3,4)
	r2 := do(1,1,1)
	fmt.Println(r1,r2)	// 10 3

}

注意事项:只能放在最后且只能有一个

10.2 函数的返回值

10.2.1 返回函数

package main

import "fmt"

func exec(num1 int,num2 int) string {
	fmt.Println("执行函数了")
	return "成功"
}

type f1 func(int,int) string

func getFunction()f1{
	return exec
}

func main(){
	function := getFunction()
	result := function(111,222)
	fmt.Println(result)
}

10.2.2 匿名函数

package main

import "fmt"
// 方式3:函数作为返回值的时候 用匿名函数
func F1(n1 int,n2 int)func(int)string{
	return func(n1 int) string {
		fmt.Println("匿名函数")
		return "匿名函数"
	}
}

func main(){
	// 匿名函数
	// 方式1
	// 定义一个匿名函数并将函数名保存给v1
	v1 := func(v1 int,v2 int) int {
		return v1+v2
	}
	// v1()调用函数
	data := v1(11,22)
	fmt.Println(data)	//33

	// 方式2
	// 定义一个函数直接去执行
	value := func(v1 int,v2 int) int {
		return v1+v2
	}(11,22)
	fmt.Println(value)

}

10.3 闭包

案例1:

package main

import "fmt"

func main() {
	//存储五个函数
	var funcList []func()

	for i:=0;i<5;i++{
		function := func(){
			fmt.Println(i)
		}
		funcList = append(funcList, function)

	}
	funcList[0]()	//5
	funcList[1]()	//5
	funcList[2]()	//5
	funcList[3]()	//5
	funcList[4]()	//5
}

案例2:

package main

import "fmt"

func main() {
	// 情况2
	//存储五个函数
	var funcList []func()

	for i:=0;i<5;i++{
		function := func(arg int) func(){
			return func(){
				fmt.Println(arg)
			}
		}(i)
		funcList = append(funcList, function)
	}
	funcList[0]()	//0
	funcList[1]()	//1
	funcList[2]()	//2
	funcList[3]()	//3
	funcList[4]()	//4
}

10.4 defer延迟执行

  • defer用于函数执行后销毁数据或者关闭资源
  • 多个defer倒序执行
package main

import "fmt"

func do()int{
	defer fmt.Println("我最后执行")
	fmt.Println("天气真好")
	fmt.Println("风吹屁屁凉")
	return 666
}

func main() {
	ret := do()
	fmt.Println(ret)
}

推荐阅读