Title

go

目录

day01 环境搭建

Go 和 C语言、C++、Python、Java 等一样都是编程语言。学习任何一门编程语言本质上都分3步走:

  • 第一步:安装 解释器 或 编译器
  • 第二步:学相关编程语言语法,然后写代码。
  • 第三步:用已安装解释器 或 编译器 去运行自己写的代码,这样代码就会去完成我们编写的功能了。

Go是编译型语言,所以我们一般称Go安装是编译器

Go是开源且跨平台的一门编程语言,所以他支持window、linux、mac操作系统,同时也意味着我们可以在各种系统中安装Go的编译器。

1. Mac系统

1.1 下载Go编译器

https://golang.google.cn/

1.2 点击安装

image-20200530225723360

默认的安装目录:/usr/local/go/

编译器启动文件:/usr/local/go/bin/go

1.3 配置环境PATH

export PATH=/usr/local/go/bin:$PATH

1.4 其他配置

1.4.1 创建一个任意目录

此目录以后放你写的所有go代码。

/Users/wupeiqi/GolangProjects/
- bin,通过go install编译时候,生成的可执行文件。
- pkg,通过go install编译时候,生成的包文件。
- src,放我们以后编写的所有go代码和依赖。
	- crm
		- app.go
	- luffcity
		- xx.go

1.4.2 配置环境变量

// Go安装目录
export GOROOT=/usr/local/go
// 代码和编译之后的文件相关代码
export GOPATH=/Users/wupeiqi/GolangProjects
// 存放编译之后的文件
export GOBIN=/Users/wupeiqi/GolangProjects/bin

1.5 环境变量“持久化”

vim ~/.bash_profile

export PATH=/usr/local/go/bin:$PATH
export GOROOT=/usr/local/go
export GOPATH=/Users/wupeiqi/GolangProjects
export GOBIN=/Users/wupeiqi/GolangProjects/bin

1.6 编写go代码

$GOPATH
├── bin
├── pkg
└── src
    └── crm
        └── app.go
package main

import "fmt"

func main() {
    fmt.Println("叫爸爸")
}

1.7 运行

本质上就是让Go编译器去运行咱们编写的代码。

方式1:

// 先进入项目目录
go run app.go

方式2(推荐):

// 先进入项目目录

// 编译代码
go build
// 运行
./crm

方式3:

// 先进入项目目录
go install 

// 去bin运行
./crm
$GOPATH
├── bin
│   └── crm
├── pkg
└── src
    └── crm
        └── app.go

2. Linux系统

2.1 下载Go编译器

https://golang.google.cn/

2.2 安装

安装目录
/opt/go

启动Go编译器文件:/opt/go/bin/go

2.3 配置环境变量PATH

export PATH=/opt/go/bin:$PATH

2.4 其他配置

2.4.1 创建一个任意目录

存放咱们编写的所有项目代码,编译文件之后存放编译后的文件。

/home/wupeiqi/GolangProjects/
- bin,在执行go install 命令,生成的可执行文件的目录。
- pkg,在执行go install 命令,存放生成的包文件。
- src,以后编写的所有Go代码都会放在这个目录。
	- crm
		- app.go
	- luffy
		- lk.go

2.4.2 环境变量的配置

export GOROOT=/opt/go
export GOPATH=/home/wupeiqi/GolangProjects
export GOBIN=/home/wupeiqi/GolangProjects/bin

2.5 环境变量的“持久化”

vim ~/.bash_profile

export PATH=/opt/go/bin:$PATH
export GOROOT=/opt/go
export GOPATH=/home/wupeiqi/GolangProjects
export GOBIN=/home/wupeiqi/GolangProjects/bin

2.6 编写go代码

/home/wupeiqi/GolangProjects(简写$GOPATH)
├── bin
├── pkg
└── src
    └── crm
        └── app.go
package main
import "fmt"
func main() {
    // 调用Println函数在屏幕输出:叫爸爸
    fmt.Println("叫爸爸")
}

2.7 运行代码

本质上将写好的代码交给Go编译器去执行。

方式1:

// 进入项目目录
go run app.go

方式2(推荐):

// 进入项目目录

// 编译代码并生成一个可执行文件
go build  

// 运行
./crm

方式3:

// 进入项目目录

// 编译代码,把编译之后的结果会放在 bin/pkg目录
go install 

// 运行
./crm
├── bin
│   └── crm
├── pkg
└── src
    └── crm
        └── app.go

Go程序员的项目:

  • 产出一个可执行文件。
  • 产出一个包文件。

3. Windows系统

3.1 下载Go编译器

https://golang.google.cn/

3.2 点击安装

建议安装:C:\Go

3.3 环境变量PATH

以便于以后运行GO编译器时,无需再写路径。

3.4 其他配置

3.4.1 创建一个任意目录

以后咱们的go项目都要按照要求放在这个目录。

Y:\GolangProjects
 - bin,go install在编译项目时,生成的可执行文件会放到此目录。
 - pkg,go install在编译项目时,生成的包文件会放到此目录。
 - src,以后所有的项目都要放在这个目录。
 	- crm
 		- app.go
	- luffy
		- xx.go

3.4.2 环境变量配置

![image-20200531115559729](C:/Users/LYX/Desktop/go_course-master/day01 环境搭建/assets/image-20200531115559729.png)

3.5 编写代码

Y:\GolangProjects
 - bin
 - pkg
 - src,以后所有的项目都要放在这个目录。
 	- crm
 		- app.go
package main

import "fmt"

func main() {
    fmt.Println("叫爸爸")
}

3.6 运行

本质上就是把Go代码交给Go编译器去执行。

方式1:

// 进入项目目录
go run app.go

方式2(推荐):

// 进入项目目录
go build

crm.exe

方式3:

// 进入项目目录
go install

执行 crm.exe
Y:\GolangProjects
 - bin
 	- crm.exe
 - pkg
 	- windows_amd64
 		- utils.a
 - src,以后所有的项目都要放在这个目录。
 	- crm
 		- app.go
 	- utils
 		- page.go

平时开发:

  • 开发可执行文件,用来让用户使用。
  • 开发一个包文件,其他项目来进行调用。

总结

首先要去下载Go编译器,然后进行安装,在安装目录下就是GO编译器相关的所有内容。

mac:     /etc/local/go/
linux:   /opt/go/
windows: C:\go\

在安装目录下有 bin目录中有一个go可执行文件,基于他来启动编译器。

  • 直接通过路径找到可执行文件去运行(麻烦)
  • /etc/local/go/bin 目录添加PATH环境变量中。

那么在终端就可以执行执行go version,调用咱们安装的编译器。

如果想要正确的使用go编译器,还需做一些相关的配置(其他语言)。

  • 创建目录,用于存放项目代码、编译后的可执行文件、编译后的包文件。

    xxxx
    - bin
    - pkg
    - src
    	- crm
    		app.go
    
  • 环境变量

    GOROOT,GO编译器安装目录。
    GOPATH,用于存放项目代码、编译后的可执行文件、编译后的包文件(go 1.11版本后,go mod)。
    GOBIN,编译后的可执行文件存放的目录。
    

编写代码,然后进行。

写了两个项目:

  • crm,编译后生成一个可执行文件。
  • utils,编译后生成一个包文件。

运行项目

  • go run,运行项目代码,内部会先编译并将编译后的文件放在系统的临时目录,然后再自动执行。
  • go build,运行项目代码,手动编译并生成一个可执行文件,然后再自动执行。
  • go install ,生成可执行文件 + 包文件,并且会将编译后的文件放在bin/pkg目录。

4.开发工具

  • Goland,IDE(继承开发环境)【收费】
  • vscode,编辑器 + 第三发组件 【免费】

4.1 下载Goland

https://www.jetbrains.com/go/

4.2 配置

  • 字体
  • 参数提示

4.3 项目开发

  • 新项目
  • 打开老项目

注意:项目放在 $GOPATH/src目录。

参考博客

https://pythonav.com/wiki/detail/4/42/

day02 快速上手

今日概要

  • 初识包管理,知道项目中文件和文件、文件和文件夹之间关系。

  • 输出,写代码,在go编译器运行时会在屏幕显示内容。

  • 初识数据类型

    • 整型,数字。例如:1、2、3、4
    • 字符串,表示文本信息。例如:“如家” "锦江之星"
    • 布尔类型,真假。例如: 1>2 、 "如家" == “家”
  • 变量 & 常量,当做是昵称。

  • 输入,让咱们用户输入内容。

  • 条件语句,开发一个猜数次程序,用户输入数字与咱们定义的数字进行比较。

1.初识包管理

关于包管理的总结:

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

重点:关于包的分类

  • main包,如果是main包,则必须写一个main函数,此函数就是项目的入口(main主函数)。 编译生成的就是一个可执行文件。
  • 非main包,用来将代码进行分类,分别放在不同的包和文件中。

![image-20200601001233922](C:/Users/LYX/Desktop/go_course-master/day02 快速上手/笔记/assets/image-20200601001233922.png)

![image-20200601001735200](C:/Users/LYX/Desktop/go_course-master/day02 快速上手/笔记/assets/image-20200601001735200.png)

注意:

  • 调用其他包的功能,需要先 import 导入,然后再使用;调用自己包中的功能时,直接调用即可(无需导入)

  • 文件中的函数首字母是小写,表示此函数只能被当前包内部文件调用。首字母是大写,则意味着任何包都可以调用。

2.输出

在终端将想要展示的数据显示出来,例如:欢迎登录、请输入用户名等。。。

  • 内置函数
    • print
    • println
  • fmt包(推荐)
    • fmt.Print
    • fmt.Println

扩展:进程里有 stdin/stdout/stderr 。

package main

import "fmt"

func main() {
	// 基于内置函数(不推荐)
	//print("好吃不过饺子 \n")
	//print("好玩不过嫂子 \n")
	//println("好吃不过饺子")
	//println("好玩不过嫂子")

	// fmt包(推荐)
	//fmt.Print("好吃不过饺子 \n")
	//fmt.Print("好玩不过嫂子 \n")
	//fmt.Println("好玩不过嫂子")
	//fmt.Println("好玩不过嫂子")
	//fmt.Println("我叫", "Alex", "我媳妇", "是个", "....")
	//fmt.Println("我叫Alex我媳妇是个....")

	// fmt包 扩展:格式化输出
	// %s,占位符 "文本"
	// %d,占位符 整数
	// %f,占位符 小数(浮点数)
	// %.2f,占位符 小数(保留小数点后2位,四舍五入)
	// 百分比
	fmt.Printf("老汉开着%s,去接%s的媳妇,多少钱一次?%d块。嫂子给打折吧,%.2f怎么样?小哥哥包你100%%满意", "车", "老男孩", 100, 3.889)
}

3.注释

  • 单行注释, //
  • 多行注释, /* */

快捷:contrl + ?

4.初识数据类型

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

import "fmt"

func main() {
	// 整型
	fmt.Println(666)
	fmt.Println(6 + 9)
	fmt.Println(6 - 9)
	fmt.Println(6 * 9)
	fmt.Println(16 / 9) // 商
	fmt.Println(16 % 9) // 余数

	// 字符串类型,特点:通过双引号
	fmt.Println("武沛齐")
	fmt.Println("钓鱼要掉刀鱼,刀鱼到岛上钓")
	fmt.Println("alex" + "SB")
	//fmt.Println("alex" + 666)
	fmt.Println("alex" + "666")
	// 对比
	fmt.Println("1" + "2") // 结果:"12"
	fmt.Println(1 + 2)     // 结果:3

	// 布尔类型,真假
	fmt.Println(1 > 2) // false  假
	fmt.Println(1 < 2) // true   真
	fmt.Println(1 == 2)
	fmt.Println(1 >= 2)
	fmt.Println("Alex" == "sb")

	// 超前
	if 2 > 1 {
		fmt.Println("叫爸爸")
	} else {
		fmt.Println("孙子")
	}

}

5.变量

可以理解为昵称。

  • 声明 + 赋值

    var sd string = "老男孩alex"
    fmt.Println(sd)
    
    var age int = 73
    fmt.Println(age)
    
    var flag bool = true
    fmt.Println(flag)
    
  • 先声明后赋值

    // 声明了一个字符类型变量 sd
    var sd string
    // 给sd变量赋值
    sd = "老男孩alex"
    fmt.Println(sd)
    

5.1 声明变量的意义?

  • 编写代码省事

    // 文本,请将文本输出3次:"伤情最是晚凉天,憔悴斯人不堪怜。"
    var message string = "伤情最是晚凉天,憔悴斯人不堪怜。"
    fmt.Println(message)
    fmt.Println(message)
    fmt.Println(message)
    
  • 存储结果,方便之后使用

    // 存储结果,方便之后使用
    var v1 int = 99
    var v2 int = 33
    var v3 int = v1 + v2
    var v4 int = v1 * v2
    var v5 int = v1 + v2 + v3 + v4
    fmt.Println(v1, v2, v3, v4, v5)
    
  • 存储用户输入的值

    var name string
    fmt.Scanf("%s", &name) // 用户输入字符串并赋值给name变量
    if name == "alex" {
        fmt.Println("用户名输入正确")
    } else {
        fmt.Println("用户名输入失败")
    }
    

5.2 变量名要求

  • 【要求】变量名必须只包含:字母、数字、下划线

    var %1 string,错误
    var $ string,错误
    
  • 【要求】数字不能开头

    var 1 string  错误
    var 1name string  错误
    var _ string 正确
    
  • 【要求】不能使用go语言内置的关键字

    var var string = "南通州北通州南北通州通南北"  错误
    
    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
    
  • 建议

    • 变量名见名知意:name/age/num ; v1、v2、v3
    • 驼峰式命名:myBossName / startDate

练习题:

var n1 int 
var data bool
var _9 string

5.3 变量简写

  • 声明+赋值

    var name string = "武沛齐"
    
    var name = "武沛齐"
    
    name := "武沛齐"  // 推荐
    
  • 先声明再赋值

    var name string
    var message string
    var data string
    
    var name,message,data string
    name = "武沛齐"
    message = "中奖了"
    data = "中了5000w"
    

因式分解,例如:声明5个变量,分别有字符串、整型

var (
    name   = "武沛齐"
    age    = 18
    hobby  = "大保健"
    salary = 1000000
    gender string  // 只声明但不赋值,有一个默认: ""
    length int     // 只声明但不赋值,有一个默认: 0
    sb bool     // 只声明但不赋值,有一个默认: false
)
fmt.Println(name, age, hobby, salary, gender,length,sb)

扩展:go编译器会认为声明变量不使用 就是耍流氓。

5.4 作用域

如果我们定义了大括号,那么在大括号中定义的变量。

  • 不能被上级使用。
  • 可以在同级使用。
  • 可以再子级使用。
package main

import "fmt"

func main() {
	name := "武沛齐"
	fmt.Println(name)
	if true {
		age := 18
		name := "alex"
		fmt.Println(age)
		fmt.Println(name)
	}
	fmt.Println(name)
}

全局变量和局部变量

  • 全局变量,未写在函数中的变量称为全局变量;不可以使用v1:=xx方式进行简化;可以基于因式分解方式声明多个变量;项目中寻找变量时最后一环。
  • 局部变量,编写在{}里面的变量;可以使用任意方式简化;可以基于因式分解方式声明多个变量;
package main

import "fmt"

// 全局变量(不能以省略的方式)
var school string = "老男孩IT教育" // 可以
//var school = "老男孩IT教育" 	 // 可以
//school := "老男孩IT教育"  		 // 不可以

var (
	v1 = 123
	v2 = "你好"
	v3 int
)

func main() {

	name := "武沛齐" // 局部变量
	fmt.Println(name)
	if true {
		age := 18      // 局部变量
		name := "alex" // 局部变量
		fmt.Println(age)
		fmt.Println(name)
	}
	fmt.Println(name)
	fmt.Println(school)
	fmt.Println(v1, v2, v3)
}

5.5 赋值及内存相关

示例1:

name := "武沛齐"

![image-20200601222221725](C:/Users/LYX/Desktop/go_course-master/day02 快速上手/笔记/assets/image-20200601222221725.png)

示例2:

name := "武沛齐"
nickname := name

![image-20200601222404306](C:/Users/LYX/Desktop/go_course-master/day02 快速上手/笔记/assets/image-20200601222404306.png)

注意:这一点与python不同。

package main

import "fmt"

func main() {
	name := "武沛齐"
	nickname := name

	fmt.Println(name, &name)
	fmt.Println(nickname, &nickname)
}

示例3:

name := "武沛齐"
nickname := name

name = "alex"

![image-20200601222800118](C:/Users/LYX/Desktop/go_course-master/day02 快速上手/笔记/assets/image-20200601222800118.png)

package main

import "fmt"

func main() {
	name := "武沛齐"
	nickname := name
	fmt.Println(name, &name)
	fmt.Println(nickname, &nickname)

	name = "alex"
	fmt.Println(name, &name)
	fmt.Println(nickname, &nickname)
}

注意事项

使用int、string、bool这三种数据类型时,如果遇到变量的赋值则会拷贝一份。【值类型】

name := "武沛齐"
nickname := name

提示:后续在数据类型中会详细再讲解。

阶段练习题

  1. package main的作用?

  2. 单行注释和多行注释分别是什么?

  3. 下面声明变量的方式错误的有哪些?

    var name = "武沛齐"
    var age int = 18
    hobby = "嫂子"
    nickName string = "一米八大高个"
    webSite := "秃头统治地球"
    
    var age int = 18
    age = 19
    
  4. 下面声明变量的方式正确的有哪些?

    var v1,v2,v3 int
    var (
    	v6 = 123
        v7 string
    )
    var v4,v5 = 11,22
    v4,v5 = 11,22
    v4,v5 := 11,22
    
  5. 变量名规范。

  6. 看代码写结果

    package main
    
    import "fmt"
    
    var number = 9999
    
    func main() {
    	number := 666
    	fmt.Println(number)
    }
    
  7. 看代码写结果

    package main
    
    import "fmt"
    
    var number = 99
    
    func main() {
    	number := 66
    	if true {
    		number := 33
    		fmt.Println(number)
    	}
    	fmt.Println(number)
    }
    
  8. 看代码写结果

    package main
    
    import "fmt"
    
    var number = 99
    
    func main() {
    	number := 66
    	if true {
    		number := 33
    		fmt.Println(number)
    	}
    	number = 88
    	fmt.Println(number)
    }
    
  9. 看代码写结果

    package main
    
    import "fmt"
    
    var number = 99
    
    func main() {
    	if true {
    		fmt.Println(number)
    	}
    	number = 88
    	fmt.Println(number)
    }
    
  10. 看代码写结果

    package main
    
    import "fmt"
    
    var number = 99
    
    func main() {
    	number := 88
    	if true {
    		number = 123
    		fmt.Println(number)
    	}
    	fmt.Println(number)
    }
    
    
  11. 看代码写结果

    package main
    
    import "fmt"
    
    var number = 99
    
    func main() {
    	fmt.Println(number)
    	number := 88
    	if true {
    		number = 123
    		fmt.Println(number)
    	}
    	fmt.Println(number)
    }
    

6.常量

不可被修改的变量。

package main

import "fmt"

func main() {
	// 定义变量
	//var name string = "武沛齐"
	//var name = "武沛齐"
	name := "武沛齐"
	name = "alex"
	fmt.Println(name)

	// 定义常量
	//const age int = 98
	const age = 98
	fmt.Println(age)
}

6.1 因式分解

package main

import "fmt"


func main() {
	// 定义变量
	//var name string = "武沛齐"
	//var name = "武沛齐"
	name := "武沛齐"
	name = "alex"
	fmt.Println(name)

	// 定义常量
	//const age int = 98
	const age = 98
	fmt.Println(age)

	// 常量因式分解
	const (
		v1 = 123
		v2 = 456
		pi = 9.9
	)
	fmt.Println(v1, v2, pi, gender)
}

6.2 全局

package main

import "fmt"

const Data = 999
const (
	pi     = 3.1415926
	gender = "男"
)

func main() {
	// 定义变量
	//var name string = "武沛齐"
	//var name = "武沛齐"
	name := "武沛齐"
	name = "alex"
	fmt.Println(name)

	// 定义常量
	//const age int = 98
	const age = 98
	fmt.Println(age)

	// 常量因式分解
	const (
		v1 = 123
		v2 = 456
		//pi = 9.9
	)
	fmt.Println(v1, v2, pi, gender)
}

6.3 iota

可有可无,当做一个在声明常量时的一个计数器。

package main

const (
	monday = iota + 1
	tuesday
	wednesday
	thursday
	friday
	saturday
	sunday
)

const (
	n1 = iota
	n2
	n3
)

func main() {

	// iota
	// 示例1
	/*
		const (
			v1 = 1
			v2 = 2
			v3 = 3
			v4 = 4
			v5 = 5
		)
		fmt.Println(v1, v2, v3, v4, v5)
	*/

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

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

	// 示例4:
	/*
		const (
			v1 = iota + 2
			_
			v2
			v3
			v4
			v5
		)
		fmt.Println(v1, v2, v3, v4, v5)
	*/

}

7.输入

让用户输入数据,完成项目交互。

  • fmt.Scan
  • fmt.Scanln
  • fmt.Scanf
package main

import "fmt"

func main() {
	// 示例1:fmt.Scan
	/*
		var name string
		fmt.Println("请输入用户名:")
		fmt.Scan(&name)
		fmt.Printf(name)
	*/

	// 示例2:fmt.Scan
	var name string
	var age int

	fmt.Println("请输入用户名:")
	// 当使用Scan时,会提示用户输入
	// 用户输入完成之后,会得到两个值:count,用户输入了几个值;err,用输入错误则是错误信息
	_, err := fmt.Scan(&name, &age)
	if err == nil {
		fmt.Println(name, age)
	} else {
		fmt.Println("用户输入数据错误", err)
	}
	// 特别说明:fmt.Scan 要求输入两个值,必须输入两个,否则他会一直等待。
}

package main

import "fmt"

func main() {
	// 示例1:fmt.Scanln
	/*
		var name string
		fmt.Print("请输入用户名:")
		fmt.Scanln(&name)
		fmt.Printf(name)
	*/

	// 示例2:fmt.Scanln

	var name string
	var age int
	fmt.Print("请输入用户名:")
	// 当使用Scanln时,会提示用户输入
	// 用户输入完成之后,会得到两个值:count,用户输入了几个值;err,用输入错误则是错误信息
	count, err := fmt.Scanln(&name, &age)
	fmt.Println(count, err)
	fmt.Println(name, age)

	// 特别说明:fmt.Scanln 等待回车。
}
package main

import "fmt"

func main() {
	var name string
	var age int

	fmt.Print("请输入用户名:")
	_, _ = fmt.Scanf("我叫%s 今年%d 岁", &name, &age)
	fmt.Println(name, age)
}

无法解决的难题?

image-20200602112623611
package main

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

func main() {
	reader := bufio.NewReader(os.Stdin)
	// line,从stdin中读取一行的数据(字节集合 -> 转化成为字符串)
	// reader默认一次能4096个字节(4096/3)
	//    1. 一次性读完,isPrefix=false
	// 	  2. 先读一部分,isPrefix=true,再去读取isPrefix=false
	line, _, _ := reader.ReadLine()
	data := string(line)
	fmt.Println(data)
}

8.条件语句

8.1 最基本

if 条件 {
    成立后,此代码块执行
}else{
    不成立,此代码块执行
}
if 条件 {
    成立后,此代码块执行
}

示例:

package main

func main() {
	/*
		if true {
			fmt.Println("666")
		}else{
			fmt.Println("999")
		}
	*/

	/*
		if 1 > 2 {
			fmt.Println("666")
		} else {
			fmt.Println("999")
		}
	*/

	/*
		flag := false
		if flag {
			fmt.Println("条件成立")
		}else{
			fmt.Println("条件不成立")
		}
	*/

	// 练习题1:用户输入姓名,判断是否正确
	/*
		var name string
		fmt.Print("请输入姓名:")
		fmt.Scanln(&name)
		if name == "alex" {
			fmt.Println("用户名输入正确")
		} else {
			fmt.Println("用户名输入错误")
		}
	*/
	// 练习题2:用户输入数字,判断奇数、偶数
	/*
		var number int
		fmt.Print("请输入数字:")
		fmt.Scanln(&number)
		if number % 2 == 0{
			fmt.Println("您输入的是偶数")
		}else{
			fmt.Println("您输入的是奇数")
		}
	*/
	// 练习题3:用户和密码,判断用户名密码是否正确。
	/*


		var username, password string
		fmt.Print("请输入用户名:")
		fmt.Scanln(&username)

		fmt.Print("请输入密码:")
		fmt.Scanln(&password)

		if username == "alex" && password == "sb" {
			fmt.Println("欢迎登录pornhub")
		} else {
			fmt.Println("用户名或密码错误")
		}
	*/
	// 练习题4:请输入用户名校验是否是VIP
	/*
		var username string
		fmt.Print("请输入用户名:")
		fmt.Scanln(&username)

		if username == "alex" || username == "eric" {
			fmt.Println("天上人间大VIP")
		} else {
			fmt.Println("屌丝")
		}
	*/
}

8.2 多条件判断

if 条件A{
    ...
}else if 条件B{
    ...
}else if 条件C{
    ...
}else{
    ...
}

示例:

package main

import "fmt"

func main() {
	var length int
	fmt.Print("请输入你的长度:")
	fmt.Scanln(&length)

	if length < 1 {
		fmt.Println("没用的东西,还特么是坑")
	} else if length < 6 {
		fmt.Println("刚刚能用")
	} else if length < 18 {
		fmt.Println("生活和谐")
	} else {
		fmt.Println("太特么大了")
	}
}

8.3 嵌套

package main

import "fmt"

func main() {
	fmt.Println("欢迎致电10086,1.话费相关;2.业务办理;3.人工服务。")

	var number int
	fmt.Scanln(&number)

	if number == 1 {
		fmt.Println("话费服务,1.交话费;2.查询。")
		var n1 int
		fmt.Scanln(&n1)
		if n1 == 1 {
			fmt.Println("缴话费啦")
		} else if n1 == 2 {
			fmt.Println("查话费了")
		} else {
			fmt.Println("输入错误")
		}
	} else if number == 2 {
		fmt.Println("业务办理")
	} else if number == 3 {
		fmt.Println("人工服务")
	} else {
		fmt.Println("输入错误")
	}
	
	// 建议:条件的嵌套不要太多
}

今日作业

  1. 提⽰⽤户输入⿇花藤. 判断⽤户输入的对不对。如果对, 提⽰真聪明, 如果不对, 提⽰你 是傻逼么。

  2. 提示用户输入两个数字,计算两个数的和并输出。

  3. 提示用户输入姓名、位置、行为三个值,然后做字符串的拼接 并输出,例如:xx 在 xx 做 xx 。

  4. 设定一个理想数字比如:66,让用户输入数字,如果比66大,则显示猜测的结果大了;如果比66小,则显示猜测的结果小了;只有等于66,显示猜测结果正确。

  5. 写程序,输出成绩等级。成绩有ABCDE5个等级,与分数的对应关系如下.

    A    90-100
    B    80-89
    C    60-79
    D    40-59
    E    0-39
    

    要求用户输入0-100的数字后,正确输出他的对应成绩等级。

    day03 基础知识

    今日概要

    • switch case语句,条件判断。
    • for循环语句,循环。
    • goto语法,不太建议使用。
    • 字符串格式化,“拼接”数据。
    • 运算符

    1.switch语句

    package main
    
    func main() {
    
    	// 表达式
    	/*
    		switch 1 + 1 {
    		case 1:
    			fmt.Println("等于1")
    		case 2:
    			fmt.Println("等于2")
    		case 3:
    			fmt.Println("等于3")
    			fmt.Println("等于3")
    		default:
    			fmt.Println("都不满足")
    		}
    	*/
    	// 变量
    	/*
    		var age int
    		fmt.Scanln(&age)
    		switch age {
    		case "1":
    			fmt.Println("等于1")
    		case 2:
    			fmt.Println("等于2")
    		case 3:
    			fmt.Println("等于3")
    			fmt.Println("等于3")
    		default:
    			fmt.Println("都不满足")
    		}
    	*/
    	// 注意事项: 数据类型一致的情况。 正确:1>2   3+4    错误: 1>"3"   5+"6"
    }
    

    2.for循环

    2.1 死循环

    for {
        ...
    }
    

    2.2 布尔值条件

    for 1>2 {
        ...
    }
    
    flag := true
    for flag {
        
    }
    

    示例

    package main
    
    func main() {
    
    	// 示例1:死循环
    	/*
    		fmt.Println("开始")
    		for {
    			fmt.Println("红鲤鱼与绿鲤鱼与驴")
    			time.Sleep(time.Second * 1) // 等一秒再继续执行
    		}
    		fmt.Println("结束")
    	*/
    
    	// 示例2:
    	/*
    		fmt.Println("开始")
    		for 2 > 1 {
    			fmt.Println("红鲤鱼与绿鲤鱼与驴")
    			time.Sleep(time.Second * 1) // 等一秒再继续执行
    		}
    		fmt.Println("结束")
    	*/
    
    	// 示例3:
    	/*
    
    		fmt.Println("开始")
    		number := 1
    		for number < 5 {
    			fmt.Println("钓鱼要掉刀鱼,刀鱼要到岛上钓")
    			number = 10
    		}
    		fmt.Println("结束")
    	*/
    
    	// 示例4:
    	/*
    		fmt.Println("开始")
    		number := 1
    		for number < 5 {
    			fmt.Println("钓鱼要掉刀鱼,刀鱼要到岛上钓")
    			number = number + 1
    		}
    		fmt.Println("结束")
    
    	*/
    
    	// 示例5:布尔类型的变量
    	/*
    		fmt.Println("开始")
    		flag := true
    		for flag {
    			fmt.Println("钓鱼要掉刀鱼,刀鱼要到岛上钓")
    			flag = false
    		}
    		fmt.Println("结束")
    
    	*/
    
    }
    

    2.3 变量&条件

    for i:=1;i<10; {
        fmt.Println("钓鱼要掉刀鱼,刀鱼要到岛上钓")
        i = i + 1
    }
    

    2.4 变量&条件&变量赋值

    for i:=1;i<10;i=i+1 {
        fmt.Println("钓鱼要掉刀鱼,刀鱼要到岛上钓")
    }
    // 简化为:
    for i:=1;i<10;i++ {
        fmt.Println("钓鱼要掉刀鱼,刀鱼要到岛上钓")
    }
    
    for i := 1; i < 10; i = i + 2 {
        fmt.Println("钓鱼要掉刀鱼,刀鱼要到岛上钓")
    }
    

    扩展:对于 i=i+1简写 --> i++

    num := 10
    fmt.Println(num)
    num++ // 等价于 num = num + 1
    fmt.Println(num)
    
    num := 10
    fmt.Println(num)
    num-- // 等价于 num = num - 1
    fmt.Println(num)
    

    阶段练习题

    1. 猜数字,设定一个理想数字比如:66,一直提示让用户输入数字,如果比66大,则显示猜测的结果大了;如果比66小,则显示猜测的结果小了;只有输入等于66,显示猜测结果正确,然后退出循环。
    2. 使用循环输出1~100所有整数。
    3. 使用循环输出 1 2 3 4 5 6 8 9 10,即:10以内除7以外的整数。
    4. 输出 1~100 内的所有奇数。
    5. 输出 1~100 内的所有偶数。
    6. 求 1~100 的所有整数的和。
    7. 输出10~1 所有整数。

    2.5 continue

    在for循环中,当循环遇到continue关键字时,会停止当前循环,开始下一次循环。

    for{
        fmt.Println("Alex今天不在家,沛齐你来陪我呀!!!")
        continue
        fmt.Println("让苑昊也一起来")
    }
    

    案例1:使用循环输出 1 2 3 4 5 6 8 9 10,即:10以内除7以外的整数。

    for i:=1;i<=10;i++{
        if i == 7{
            continue
        }
        fmt.Println(i)
    }
    

    案例2:for循环嵌套

    for i:=1;i<3;i++{
        // i=1
        // i=2
        for j:=1;j<5;j++{
            // j=1/2/3/4
            fmt.Println(i,j)
        }
    }
    
    >>> 输出:
    1 1
    1 2
    1 3
    1 4
    2 1
    2 2
    2 3
    2 4
    

    案例3:for循环嵌套 + continue

    for i:=1;i<3;i++{
        // i=1
        // i=2
        for j:=1;j<5;j++{
            // j=1/2/3/4
            if j == 3{
                continue
            }
            fmt.Println(i,j)
        }
    }
    
    >>> 输出:
    1 1
    1 2
    1 4
    2 1
    2 2
    2 4
    

    2.6 break

    在for循环中时,循环中一旦遇到break,跳出循环。

    for{
        fmt.Println("王老汉、李老汉、张老汉")
        break
        fmt.Println("alex老婆满身大汗")
    }
    

    案例1:猜数字,设定一个理想数字比如:66,一直提示让用户输入数字,如果比66大,则显示猜测的结果大了;如果比66小,则显示猜测的结果小了;只有输入等于66,显示猜测结果正确,然后退出循环。

    fmt.Print("开始")
    data := 66
    for{
        var userInputNumber int
        fmt.Print("请输入数字:")
        fmt.Scanln(&userInputNumber)
        if userInputNumber > data {
            fmt.Println("大了")
        } else if userInputNumber < data {
            fmt.Println("小了")
        } else {
            fmt.Println("恭喜你猜对了")
            break
        }
    }
    fmt.Print("结束")
    

    案例2:

    for i:=1;i<3;i++{
        // i=1
        // i=2
        for j:=1;j<5;j++{
            // j=1/2/3/4
            if j == 3{
                break
            }
            fmt.Println(i,j)
        }
    }
    
    >>> 输出:
    1 1
    1 2
    2 1
    2 2
    
    for i:=1;i<3;i++{
        // i=1
        // i=2
        for j:=1;j<5;j++{
            // j=1/2/3/4
            if j == 3{
                break
            }
            fmt.Println(i,j)
        }
        break
    }
    
    >>> 输出:
    1 1
    1 2
    

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

    f1:
    	for i := 1; i < 3; i++ {
    		// i=1
    		// i=2
    		for j := 1; j < 5; j++ {
    			// j=1/2/3/4
    			if j == 3 {
    				continue f1
    			}
    			fmt.Println(i, j)
    		}
    	}
    
    >>> 输出:
    1 1
    1 2
    2 1
    2 2
    
    f1:
    	for i := 1; i < 3; i++ {
    		// i=1
    		// i=2
    		for j := 1; j < 5; j++ {
    			// j=1/2/3/4
    			if j == 3 {
    				break f1
    			}
    			fmt.Println(i, j)
    		}
    	}
    
    >>> 输出:
    1 1
    1 2
    

    3.goto语句

    跳跃到指定的行,然后向下执行代码。

    package main
    
    import "fmt"
    
    func main() {
    	var name string
    	fmt.Print("请输入姓名:")
    	fmt.Scanln(&name)
    
    	if name == "wupeiqi" {
    		// svip
    		goto SVIP
    	} else if name == "yuanhao" {
    		// vip
    		goto VIP
    	}
    	fmt.Println("预约...")
    VIP:
    	fmt.Println("等号...")
    SVIP:
    	fmt.Println("进入...")
    }
    

    4.字符串格式化

    将数据格式化成为特定格式的字符串时,可以使用字符串格式化。

    package main
    
    import "fmt"
    
    func main() {
    	var name, address, action string
    
    	fmt.Print("请输入姓名:")
    	fmt.Scanln(&name)
    
    	fmt.Print("请输入位置:")
    	fmt.Scanln(&address)
    
    	fmt.Print("请输入行为:")
    	fmt.Scanln(&action)
    
    	result := fmt.Sprintf("我叫%s,在%s正在干%s", name, address, action)
    	//result := "我叫" + name + "在" + address + "干" + action
    	fmt.Println(result)
    }
    
    

    5.运算符

    5.1 算数运算符

    image-20200602230315027

    5.2 关系运算符

    image-20200602230358895

    5.3 逻辑运算符

    image-20200602230437070
    if !true {
        fmt.Println("你是风儿我是沙,去你吗的风河沙")
    }
    

    5.4 位运算符

    image-20200602230522054

    必知必会概念:

    • 计算机中的 存储、运算、网络传输等任何的行为,本质上都是二进制的操作。例如:01010101。

        A				                  B
      hello   -> 0101010010101  ->    hello
      
    • 信息表现形式

      二进制表示:0101010010101   ->  hello
      十进制表示:1921            ->  hello
      
    • 十进制和二进制的转换关系

      十进制 二进制
      0 0
      1 1
      2 10
      3 11
      4 100
      5 101
      6 110
      7 111
      8 1000
      9 1001
      10 1010
      ... ...
      • 二进制转换为十进制

        10101         ->   2**4 + 2**2 + 2**0 => 16 + 4 + 1 => 21
        101010010101  ->   2**11 + 2**9 + ....
        
      • 十进制转换成二进制

        99            -> 64 + 32 + 2 + 1  -> 2**6 + 2**5 + 2**1 + 2*0  -> 1100011
        

    位运算指的是二进制之间的运算:

    // 1.按位进行与运算(全为1,才得1)
    r1 := 5 & 99
    5  -> 0000101
    99 -> 1100011
          0000001   -> 1
    
    // 2.按位进行或运算(只要有1,就得1)
    r2 := 5 | 99
    5  -> 0000101
    99 -> 1100011
          1100111   -> 2**6 + 2**5 + 2**2 + 2**1 + 2**0 = 64 + 32 + 4 + 2 + 1 = 103
    
    // 3.按位进行异或运算(上下不同,就得1)
    r3 := 5 ^ 99
    5  -> 0000101
    99 -> 1100011
          1100110   -> 2**6 + 2**5 + 2**2 + 2**1 = 64 + 32 + 4 + 2 = 102
    
    // 4.按位向左移动
    r4 := 5 << 2
            5  -> 101
    向左移动2位  -> 10100  -> 2**4 + 2**2 = 16 + 4 = 20
    
    // 5.按位向右移动
    r5 := 5 >> 1
            5  -> 101
    向右移动1位  -> 10  -> 2**1 = 2
    
    // 6.比较清除   // 以前面的值为基准,让前面的和后面的值的二进制位进行比较,如果两个位置都是1,则讲前面的值的那个位置置0
    r6 := 5 &^ 99
    5  -> 0000101
    99 -> 1100011
          0000100     -> 2**2 = 4
    

    5.5 赋值运算符

    image-20200602230552667
    age := 19
    age = 99
    
    age = age + 9  // age+=9
    age = age - 9  // age-=9
    age = age * 9  // age*=9
    ...
    

    运算符的优先级

    Precedence    Operator
        5             *  /  %  <<  >>  &  &^
        4             +  -  |  ^
        3             ==  !=  <  <=  >  >=
        2             &&
        1             ||
    
    v1 := 3 + 2 * 2
    v2 := 8 == 5 & 99
    

    注意:不要想办法去记住他,使用括号。

    参考地址:

    叮嘱:不要太过于用心去背、记,主要认识即可。优先级记不住就用括号。

    今日作业

    1. 猜年龄游戏,要求:允许用户最多尝试3次,3次都没猜对的话,就直接退出,如果猜对了,打印恭喜信息并退出。
    2. 实现用户登录系统,并且要支持连续3次输错之后直接退出,并且在每次输错误时显示剩余错误次数(提示:使⽤字符串格式化)。

    day04 必备基础知识

    今日概要

    • 进制
    • 单位
    • 编码

    1.进制

    1.1 烽火狼烟

    • 古代打仗用烽火狼烟来进行传递信号
    • 生活中灯泡的亮与暗用来指示信息(用电来抓耗子,灯亮表示抓到了)

    ![image-20200604182524790](C:/Users/LYX/Desktop/go_course-master/day04 进制和编码/课件/assets/image-20200604182524790.png)

    用亮灯用1表示,则上述的情景为:

    • 110010

    • 010100

    • 100001

    计算机本质上都有由晶体管和二极管组成(类比为灯泡),亮与灭都代表一定的信息,所以,本质计算机的所有操作:0101010。

    为这些“10101010”来进行一定的格式化:

    0

    1

    10

    11

    100

    所以,二进制,满2进1.

    小结

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

    2.单位

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

    • 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流量是啥意思了。

    生活中的场景:

    • 电脑的内存 4G
    • 硬盘是1T
    • 流量10G
    • 流量有30M

    3.编码

    计算机底层本质都是二进制。

    3.1 ascii编码

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

    0000000
    0000001
    0000010
    0000011
    0000100
    ....
    ...
    11111111
    
    2**8=256
    

    A --> 按照ascii码对照表找到他对应的二进制 01000001

    3.2 unicode(万国码)

    • ucs2,用16位来表示所有的情况。2**16=65535

                                   十六进制
      
      0000000 0000000 
      0000000 0000001
      0000000 0000010 
      0000000 1111111
      ...                           0025             %    
                                    6B66             武
      ...
      11111111 11111111
      
    • ucs4,用32位来表示所有的情况。2**32=4294967296

      0000000 0000000 0000000 0000000 
      ...
      0000000 0000000 0000000 11111111
      ...
      11111111 11111111 11111111 11111111
      

    忠告:ucs2和ucs4应该根据自己的业务场景来进行选择。

    3.3 utf-8编码

    对unicode进行压缩,到底是如何压缩?

    • 第一步:找模板

        码位范围(十六进制)                转换模板
         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,则应该选择第三个模板。
      

      注意: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
      

    除了utf-8之外,其实还有一些其他的 utf-7/utf-16/utf-32 等编码,他们跟utf-8类似,但没有utf-8应用广泛。

    编码答疑

    /*
     @Author:武沛齐  微信号:wupeiqi666
     @Description: 老男孩IT教育 & 路飞学城
     @Video:  https://space.bilibili.com/283478842
    */
    package main
    
    import (
    	"fmt"
    	"strconv"
    )
    
    func main() {
    	// 定义字符串,字符串是以什么形式存在于Go编译器(utf-8编码)
    	name := "武沛齐"
    
    	// 其他语言:
    	// 0 武
    	fmt.Println(name[0], strconv.FormatInt(int64(name[0]), 2)) // 230  11100110
    	fmt.Println(name[1], strconv.FormatInt(int64(name[1]), 2)) // 173  10101101
    	fmt.Println(name[2], strconv.FormatInt(int64(name[2]), 2)) // 166  10100110
    
    	// 1 沛
    	fmt.Println(name[3])
    	fmt.Println(name[4])
    	fmt.Println(name[5])
    
    	// 2 齐
    	fmt.Println(name[6])
    	fmt.Println(name[7])
    	fmt.Println(name[8])
    }
    
    

    总结

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

    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的关系。
    8. utf-8用3个字节表示中文。

    参考文档:https://pythonav.com/wiki/detail/1/80/

    day05 数据类型

    写程序 等价于 写作文

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

    ![image-20200607215200860](C:/Users/LYX/Desktop/go_course-master/day05 数据类型:数字、字符串、数组/笔记/assets/image-20200607215200860.png)

    Go语言中常见的数据类型有挺多,例如:

    • 整型,用于表示整数。
    • 浮点型,用于表示小数。
    • 布尔型,用于表示真/假。
    • 字符串,用于表示文本信息。
    • 数组,用于表示多个数据(数据集合)
    • 指针,用于表示内存地址的类型。
    • 切片,用于表示多个数据(数据集合)
    • 字典,用于表示键值对结合。
    • 结构体,用于自定义一些数据集合。
    • 接口,用于约束和泛指数据类型。

    注意:关于“值类型”和“引用类型” https://github.com/go101/go101/wiki/About-the-terminology-%22reference-type%22-in-Go

    今日概要

    • 整型,用于表示整数。
    • 浮点型,用于表示小数。
    • 布尔型,用于表示真/假。
    • 字符串,用于表示文本信息。
    • 数组,用于表示多个数据(数据集合)["长沙","东莞","惠州"]

    1.整型

    Go中的整型分为有符号和无符号两大类,有符号的包含负值,无符号不包含负值。

    有符号整型:

    • int8(-128 -> 127)
    • int16(-32768 -> 32767)
    • int32(-2,147,483,648 -> 2,147,483,647)
    • int64(-9,223,372,036,854,775,808 -> 9,223,372,036,854,775,807)
    • int
      • 在 32 位操作系统上使用 32 位(-2,147,483,648 -> 2,147,483,647) 2**32
      • 在 64 位操作系统上使用 64 位(-9,223,372,036,854,775,808 -> 9,223,372,036,854,775,80)2**64

    无符号整数:

    • uint8(0 -> 255)
    • uint16(0 -> 65,535)
    • uint32(0 -> 4,294,967,295)
    • uint64(0 -> 18,446,744,073,709,551,615)
    • uint
      • 在 32 位操作系统上使用32 位(0 -> 4,294,967,295) 2**32
      • 64 位操作系统上使用 64 位(0 -> 18,446,744,073,709,551,615) 2**64

    不同整型可表示的数据范围不同,我们需要根据自己的需求来选择适合的类型。

    1.1 整型之间的转换

    data := intXXX(其他整型)
    
    var v1 int8 = 10
    var v2 int16 = 18
    v3 := int16(v1) + v2
    fmt.Println(v3, reflect.TypeOf(v3))
    

    注意:

    • 地位转向高位,没问题。

    • 高位转向低位,可能有问题

      var v1 int16 = 130
      v2 := int8(v1)
      fmt.Println(v2)
      

    1.2 整型与字符串的转换

    /*
     @Author:武沛齐  微信号:wupeiqi666
     @Description: 老男孩IT教育 & 路飞学城
     @Video:  https://space.bilibili.com/283478842
    */
    package main
    
    func main() {
    
    	// 整型转换为字符串类型
    	/*
    		v1 := 19
    		result := strconv.Itoa(v1)
    		fmt.Println(result, reflect.TypeOf(result))
    		var v2 int8 = 17
    		data := strconv.Itoa(int(v2))
    		fmt.Println(data,reflect.TypeOf(data))
    	*/
    
    	// 字符串转换为整型:转换后是int类型;可能存在错误
    	/*
    		v1 := "666"
    		result, err := strconv.Atoi(v1)
    		if err == nil {
    			fmt.Println("转换成功", result,reflect.TypeOf(result))
    		} else {
    			fmt.Println("转换失败")
    		}
    	
    	*/
    }
    

    1.3 进制转换

    • Go代码中:
      • 十进制,整型的方式存在。
      • 其他进制,是以字符串的形式存在。
    • 整形,10进制数。

    十进制转换为其他进制:

    /*
     @Author:武沛齐  微信号:wupeiqi666
     @Description: 老男孩IT教育 & 路飞学城
     @Video:  https://space.bilibili.com/283478842
    */
    package main
    
    import (
    	"fmt"
    	"reflect"
    	"strconv"
    )
    
    func main() {
    	v1 := 99
    	// 整型(十进制)转换为:二进制、八进制、十六进制
    	r1 := strconv.FormatInt(int64(v1), 16)
    	fmt.Println(r1, reflect.TypeOf(r1))
    
    }
    
    

    其他进制转换为十进制:

    /*
     @Author:武沛齐  微信号:wupeiqi666
     @Description: 老男孩IT教育 & 路飞学城
     @Video:  https://space.bilibili.com/283478842
    */
    package main
    
    import (
    	"fmt"
    	"reflect"
    	"strconv"
    )
    
    func main() {
    	// data,要转换的文本
    	// 2,把文档当做二进制去转换成 十进制(整型)
    	// 16,转换的过程中对结果进行约束
    	// 结果:如果转换成功,则将err设置为nil,result则永远以int64的类型返回。
    	data := "1001000101"
    	result, err := strconv.ParseInt(data, 2, 0)
    	fmt.Print(result, err, reflect.TypeOf(result))
    }
    

    提醒:通过ParseInt将字符串转换为10进制时,本质上与Atoi是相同的。

    ![image-20200608113745070](C:/Users/LYX/Desktop/go_course-master/day05 数据类型:数字、字符串、数组/笔记/assets/image-20200608113745070.png)

    练习题:

    • 将十进制 14 用 转换成16进制的字符串。
    • 将 2进制 "10011" 转换成 10进制的整型。
    • 将 2进制 "10011" 转换成 16 进制的字符串。

    1.4 常见数学运算

    package main
    
    import (
    	"fmt"
    	"math"
    )
    
    func main() {
    	fmt.Println(math.Abs(-19))                // 取绝对值
    	fmt.Println(math.Floor(3.14))             // 向下取整
    	fmt.Println(math.Ceil(3.14))              // 向上取整
    	fmt.Println(math.Round(3.3478))           // 就近取整
    	fmt.Println(math.Round(3.5478*100) / 100) // 保留小数点后两位
    	fmt.Println(math.Mod(11, 3))              // 取余数,同11 % 3
    	fmt.Println(math.Pow(2, 5))               // 计算次方,如:2的5次方
    	fmt.Println(math.Pow10(2))                // 计算10次方,如:2的10次方
    	fmt.Println(math.Max(1, 2))               // 两个值,取较大值
    	fmt.Println(math.Min(1, 2))               // 两个值,取较小值
    	// ...
    }
    

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

    • 声明变量

      var v1 int 
      v2 := 999
      

      ![image-20200608195751136](C:/Users/LYX/Desktop/go_course-master/day05 数据类型:数字、字符串、数组/笔记/assets/image-20200608195751136.png)

    • 指针

      var v3 *int    
      v4 := new(int)   
      

      ![image-20200608200651520](C:/Users/LYX/Desktop/go_course-master/day05 数据类型:数字、字符串、数组/笔记/assets/image-20200608200651520.png)

    • new关键字

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

      nil指go语言中的空值。
      var v100 *int
      var v101 *int8
      

    问题:

    • 为什么要有指针?

      为了节省内存,不重复开辟空间去存储数据。
      
    • int和*int是两种不同的数据类型,不等。(之后专门讲指针细说)

    1.6 超大整型

    第一步:创建对象

    创建超大整型对象

    // 第一步:创建一个超大整型的一个对象
    var v1 big.Int
    var v2 big.Int
    
    // 第二步:在超大整型对象中写入值
    v1.SetInt64(9223372036854775807)
    fmt.Println(v1)
    
    v2.SetString("92233720368547758089223372036854775808", 10)
    fmt.Println(v2)
    
    // 第一步:创建一个超大整型的一个对象
    v3 := new(big.Int)
    v4 := new(big.Int)
    
    // 第二步:在超大整型对象中写入值
    v3.SetInt64(9223372036854775807)
    fmt.Println(v3)
    
    v4.SetString("92233720368547758089223372036854775808", 10)
    fmt.Println(v4)
    

    推荐:使用指针的方式,即:使用new来进行创建和初始化。

    第二步:加减乘除

    超大对象进行加减乘除

    n1 := new(big.Int)
    n1.SetInt64(89)
    
    n2 := new(big.Int)
    n2.SetInt64(99)
    
    result := new(big.Int)
    result.Add(n1, n2)
    
    fmt.Println(result)
    
    n1 := big.NewInt(89)
    
    n2 := big.NewInt(99)
    
    result := new(big.Int)
    result.Add(n1, n2)
    
    fmt.Println(result)
    

    其他:

    v1 := big.NewInt(11)
    v2 := big.NewInt(3)
    result := new(big.Int)
    
    // 加
    result.Add(v1, v2)
    fmt.Println(result)
    // 减
    result.Sub(v1, v2)
    fmt.Println(result)
    
    // 乘
    result.Mul(v1, v2)
    fmt.Println(result)
    
    // 除(地板除,只能得到商)
    result.Div(v1, v2)
    fmt.Println(result)
    
    // 除,得商和余数
    minder := new(big.Int)
    result.DivMod(v1, v2, minder)
    fmt.Println(result, minder)
    

    第三步:关于结果

    n1 := new(big.Int)
    n1.SetString("92233720368547758089223372036854775808", 10)
    
    n2 := new(big.Int)
    n2.SetString("11111192233720368547758089223372036854775808", 10)
    
    result := new(big.Int)
    result.Add(n1, n2)
    
    fmt.Println(result.String())
    

    最后建议

    • 尽量new方式去初始化并返回一个指针类型的方式。

    • 易错的点(int类型和*int类型)

      var v1 big.Int
      v1.SetString("92233720368547758089223372036854775808", 10)
      
      var v2 big.Int
      v2.SetString("2", 10)
      
      //result := new(big.Int)
      var result big.Int
      result.Add(&v1, &v2)
      fmt.Println(result.String())
      

    2.浮点型

    浮点数,计算机中小数的表示方式,如:3.14

    Go语言中提供了两种浮点型:

    • float32,用32位(4个字节)来存储浮点型。
    • float64,用64位(8个字节)来存储浮点型。
    /*
     @Author:武沛齐  微信号:wupeiqi666
     @Description: 老男孩IT教育 & 路飞学城
     @Video:  https://space.bilibili.com/283478842
    */
    package main
    
    import "fmt"
    
    func main() {
    	var v1 float32
    	v1 = 3.14
    	v2 := 99.9
    	v3 := float64(v1) + v2
    	fmt.Println(v1, v2, v3)
    }
    

    2.1 非精确

    float类型,计算机中小数的非精确的表示方式,如:3.14

    /*
     @Author:武沛齐  微信号:wupeiqi666
     @Description: 老男孩IT教育 & 路飞学城
     @Video:  https://space.bilibili.com/283478842
    */
    package main
    
    import "fmt"
    
    func main() {
    	var v1 float32
    	v1 = 3.14
    	v2 := 99.9
    	v3 := float64(v1) + v2
    	fmt.Println(v1, v2, v3)
    
    	v4 := 0.1
    	v5 := 0.2
    	result := v4 + v5
    	fmt.Println(result)
    
    	v6 := 0.3
    	v7 := 0.2
    	data := v6 + v7
    	fmt.Println(data)
    }
    
    3.14 99.9 103.04000010490418
    0.30000000000000004
    0.5
    

    2.2 float底层存储原理

    var price float32 = 0.29
    
    第一步:浮点型转换为二进制
    • 整数部分,直接转换为二进制(10进制转换为2进制),即:100111

    • 小数部分,让小数部分乘以2,结果小于1则将结果继续乘以2,结果大于1则讲结果-1继续乘以2,结果等于1则结束。

      0.29 * 2 = 0.58       // 小于1,则继续乘
      0.58 * 2 = 1.16		  // 大于1,则减1继续乘
      0.16 * 2 = 0.32		  // 小于1,则继续乘
      0.32 * 2 = 0.64       // 小于1,则继续乘
      0.64 * 2 = 1.28       // 大于1,则减1继续乘
      0.28 * 2 = 0.56       // 小于1,则继续乘
      0.56 * 2 = 1.12       // 大于1,则减1继续乘
      0.12 * 2 = 0.24       // 小于1,则继续乘    
      0.24 * 2 = 0.48       // 小于1,则继续乘
      0.48 * 2 = 0.96       // 小于1,则继续乘
      0.96 * 2 = 1.92       // 大于1,则减1继续乘
      0.92 * 2 = 1.84       // 大于1,则减1继续乘
      0.84 * 2 = 1.68       // 大于1,则减1继续乘
      0.68 * 2 = 1.36       // 大于1,则减1继续乘
      0.36 * 2 = 0.72       // 小于1,则继续乘
      0.72 * 2 = 1.44       // 大于1,则减1继续乘
      0.44 * 2 = 0.88       // 小于1,则继续乘
      0.88 * 2 = 1.76       // 大于1,则减1继续乘
      0.76 * 2 = 1.52       // 大于1,则减1继续乘
      0.52 * 2 = 1.04       // 大于1,则减1继续乘
      0.04 * 2 = 0.08       // 小于1,则继续乘
      0.08 * 2 = 0.16       // 小于1,则继续乘
      0.16 * 2 = 0.32       // 小于1,则继续乘(与第三行相同,这样会一直循环执行下去)
      ...
      
      将相乘之后等结果的整数部分拼接起来,所以 0.29的 二进制表示:010010100011110101110000101000111...
      

    所以,最终39.29的二进制表示为:100111.010010100011110101110000101000111...

    第二步:科学计数法表示

    100111.010010100011110101110000101000111...

    \[1.00111010010100011110101110000101000111... * 2^5 \]

    第三步:存储

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

    ![image-20200608235249835](C:/Users/LYX/Desktop/go_course-master/day05 数据类型:数字、字符串、数组/笔记/assets/image-20200608235249835.png)

    • sign,用1位来表示浮点数正负,0表示正数;1表示负数。
    • exponent,用8位来表示共有256种(0~255),含正负值(-127 ~ 128)。例如:5想要存储到exponent位的话,需要让 5 + 127 = 132,再讲132转换二进制,存储到exponent。(132的二进制是:01000010)
    • fraction,存储小数点后的所有数据。

    float64和float32类似,只是用于表示各部分的位数不同而已,其中:sign=1位exponent=11位fraction=52位,也就意味着可以表示的范围更大了。

    2.3 decimal

    Go语言内部没有decimal。

    第三方包,则需要在本地的Go环境中先安装再使用。第三方包源码地址:https://github.com/shopspring/decimal

    第一步:安装第三发的包
    go get github.com/shopspring/decimal 
    

    命令执行完成之后,在 $GOPATH/src的目录下就会出现 github/shopspring/decimal的目录,这就是第三方模块安排的位置。

    第二步:使用decimal包
    package main
    
    import (
    	"fmt"
    	"github.com/shopspring/decimal"
    )
    
    func main() {
    
    	var v1 = decimal.NewFromFloat(0.0000000000019)
    	var v2 = decimal.NewFromFloat(0.29)
    
    	var v3 = v1.Add(v2)
    
    	var v4 = v1.Sub(v2)
    
    	var v5 = v1.Mul(v2)
    
    	var v6 = v1.Div(v2)
    	
        fmt.Println(v3, v4, v5, v6) // 输出:0.2900000000019(也可以调用String方法)
        
        
    	var price = decimal.NewFromFloat(3.4626)
    	var data1 = price.Round(1) 		// 保留小数点后1位(四舍五入)
    	var data2 = price.Truncate(1)	// 保留小数点后1位
    	fmt.Println(data1, data2) // 输出:3.5  3.4
    }
    

    3.布尔类型

    表示真假,一般是和条件等配合使用,用于满足某个条件时,执行某个操作。

    /*
     @Author:武沛齐  微信号:wupeiqi666
     @Description: 老男孩IT教育 & 路飞学城
     @Video:  https://space.bilibili.com/283478842
    */
    package main
    
    import (
    	"fmt"
    	"strconv"
    )
    
    func main() {
    	// 字符串 转换 布尔类型
    	// true:"1", "t", "T", "true", "TRUE", "True"
    	// false:"0", "f", "F", "false", "FALSE", "False"
    	//false,err错误信息
    	v1, err := strconv.ParseBool("t")
    	fmt.Println(v1, err)
    
    	// 布尔类型 转换 字符串
    	v2 := strconv.FormatBool(false)
    	fmt.Println(v2)
    }
    
    

    4.字符串

    在编写程序时,使用字符串来进行文本的处理,例如:

    /*
     @Author:武沛齐  微信号:wupeiqi666
     @Description: 老男孩IT教育 & 路飞学城
     @Video:  https://space.bilibili.com/283478842
    */
    package main
    
    import "fmt"
    
    func main() {
    	var name string = "alex"
    	fmt.Printf(name)
    
    	title := "生活要想过得去,头上总得带点绿"
    	fmt.Printf(title)
    }
    

    4.1 字符串的底层存储

    计算机中所有的操作和数据最终都是二进制,即:1000100001011...

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

    /*
     @Author:武沛齐  微信号:wupeiqi666
     @Description: 老男孩IT教育 & 路飞学城
     @Video:  https://space.bilibili.com/283478842
    */
    package main
    
    import "fmt"
    
    func main() {
        // unicode字符集:文字 -> 码点(ucs4, 4个字节表示)
        // utf-8编码,对unicode字符集的码点进行编码最终得到:1000100001
    	var name string = "武沛齐"
        fmt.Printf(name)
    }
    

    课上代码示例:

    /*
     @Author:武沛齐  微信号:wupeiqi666
     @Description: 老男孩IT教育 & 路飞学城
     @Video:  https://space.bilibili.com/283478842
    */
    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. 获取字符串的长度:9(字节长度)
    	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字符集码点的集合    6b66 ->  武  6c9b->沛   9f50->齐
    	tempSet := []rune(name)
    	fmt.Println(tempSet) // [27494 27803 40784]
    	fmt.Println(tempSet[0], strconv.FormatInt(int64(tempSet[0]), 16))
    	fmt.Println(tempSet[1], strconv.FormatInt(int64(tempSet[1]), 16))
    	fmt.Println(tempSet[2], strconv.FormatInt(int64(tempSet[2]), 16))
    
    	// 6. "rune集合" 转换 为字符串
    	runeList := []rune{27494, 27803, 40784}
    	targetName := string(runeList)
    	fmt.Println(targetName)
    
    	// 7. 长度的处理(获取字符长度)
    	runeLength := utf8.RuneCountInString(name)
    	fmt.Println(runeLength)
    }
    

    4.2 字符串常见功能

    字符串属于在程序中最常见用的数据类型,所以Go中为字符串提供了很多常见的操作。

    4.2.1 获取长度

    package main
    
    import (
       "fmt"
       "unicode/utf8"
    )
    
    func main() {
       var name string = "武沛齐"
    
       fmt.Println( len(name) )                    // 获取 字节 长度,输出:8
       fmt.Println( utf8.RuneCountInString(name) ) // 获取字符长度,输出:3
    }
    

    4.2.2 是否以xx开头

    package main
    
    import (
       "fmt"
       "strings"
    )
    
    func main() {
       name := "武沛齐"
    
       result := strings.HasPrefix(name, "武")
    
       fmt.Println(result) // 输出:true
    }
    

    4.2.3 是否以xx结尾

    package main
    
    import (
    	"fmt"
    	"strings"
    )
    
    func main() {
    	name := "武沛齐"
    
    	result := strings.HasSuffix(name, "齐")
    
    	fmt.Println(result) // 输出:true
    }
    

    4.2.4 是否包含

    package main
    
    import (
       "fmt"
       "strings"
    )
    
    func main() {
       name := "抬老子的意大利炮来"
       result := strings.Contains(name, "老子")
    
       fmt.Println(result) // 输出:true
    }
    

    4.2.5 变大写

    package main
    
    import (
       "fmt"
       "strings"
    )
    
    func main() {
       name := "wupeiqi"
    
       result := strings.ToUpper(name)
    
       fmt.Println(result) // 输出:WUPEIQI
    }
    // 注意:result是大写;name依然是小写。
    

    4.2.6 变小写

    package main
    
    import (
    	"fmt"
    	"strings"
    )
    
    func main() {
    	name := "WUPEIQI"
    
    	result := strings.ToLower(name)
    
    	fmt.Println(result) // 输出:wupeiqi
    }
    
    

    4.2.7 去两边

    package main
    
    import (
       "fmt"
       "strings"
    )
    
    func main() {
       name := "wupeiqi"
    
       result1 := strings.TrimRight(name, "qi") // 去除右边的qi
       result2 := strings.TrimLeft(name, "w")   // 去除左边的w
       result3 := strings.Trim(name, "w")       // 去除两边的w
    
       fmt.Println(result1, result2, result3) // 输出:wupe upeiqi upeiqi
    }
    

    4.2.8 替换

    package main
    
    import (
       "fmt"
       "strings"
    )
    
    func main() {
       name := "wupeipeiqi"
    
       result1 := strings.Replace(name, "pei", "PE", 1)  // 找到pei替换为PE,从左到右找第一个替换
       result2 := strings.Replace(name, "pei", "PE", 2)  // 找到pei替换为PE,从左到右找前两个替换
       result3 := strings.Replace(name, "pei", "PE", -1) // 找到pei替换为PE,替换所有
    
       fmt.Println(result1, result2, result3)
    }
    

    4.2.9 分割

    package main
    
    import (
       "fmt"
       "strings"
    )
    
    func main() {
       name := "抬老子的意大利的炮来"
       result := strings.Split(name, "的")
    
       // 根据`的`进行切割,获取一个切片(类似于一个数组)
       fmt.Println(result) // [ 抬老子, 意大利, 炮来 ]
    }
    

    4.2.10 拼接

    可以使用 + 让两个字符串进行拼接,但这样的拼接效率会非常的低,不建议使用,建议大家使用以下的方式:

    package main
    
    import (
    	"bytes"
    	"fmt"
    	"strings"
    )
    
    func main() {
    	// 不建议
    	message := "我爱" + "北京天安门"
    	fmt.Println(message)
    
    	// 建议:效率高一些
    	dataList := []string{"我爱", "北京天安门"}
    	result := strings.Join(dataList, "")
    	fmt.Println(result) // 我爱北京天安门
    
    	// 建议:效率更高一些(go 1.10之前)
    	var buffer bytes.Buffer
    	buffer.WriteString("你想")
    	buffer.WriteString("我干")
    	buffer.WriteString("他")
    	data := buffer.String()
    	fmt.Print(data)
    
    	// 建议:效率更更更更高一些(go 1.10之后)
    	var builder strings.Builder
    	builder.WriteString("哈哈哈")
    	builder.WriteString("去你的吧")
    	value := builder.String()
    	fmt.Print(value)
    }
    

    4.2.11 string转换为int

    package main
    
    import (
    	"fmt"
    	"strconv"
    )
    
    func main() {
    	num := "666"
    
    	// 内部调用的就是 ParseInt
    	var data, _ = strconv.Atoi(num)
    	fmt.Println(data)
        
        // 整型转字符串(strconv.ParseInt 和 strconv.FormatInt 可用处理进制转换)
        // 十进制:整型; 其他进制:字符串形式 
        var result, err = strconv.ParseInt(num, 10, 32)
    	fmt.Println(result, err)
    }
    

    4.2.12 int转换为string

    package main
    
    import (
    	"fmt"
    	"strconv"
    )
    
    func main() {
    	var result = strconv.Itoa(888)
    	fmt.Println(result)
    }
    

    4.2.13 字符串 和 “字节集合”

    /*
     @Author:武沛齐  微信号:wupeiqi666
     @Description: 老男孩IT教育 & 路飞学城
     @Video:  https://space.bilibili.com/283478842
    */
    package main
    
    import (
    	"fmt"
    	"strconv"
    	"unicode/utf8"
    )
    
    func main() {
    
    	var name string = "武沛齐"
    
    	// 字符串转换为一个"字节集合"
    	byteSet := []byte(name)
    	fmt.Println(byteSet) // [230,173,166,230,178,155,233,189,144]
    
    	// 字节的集合转换为字符串
    	byteList := []byte{230, 173, 166, 230, 178, 155, 233, 189, 144}
    	targetString := string(byteList)
    	fmt.Println(targetString)
    
    }
    

    4.2.14 字符串 和 “rune集合”

    /*
     @Author:武沛齐  微信号:wupeiqi666
     @Description: 老男孩IT教育 & 路飞学城
     @Video:  https://space.bilibili.com/283478842
    */
    package main
    
    import (
    	"fmt"
    	"strconv"
    	"unicode/utf8"
    )
    
    func main() {
    
    	var name string = "武沛齐"
    
    	// 将字符串转换为 unicode字符集码点的集合    6b66 ->  武  6c9b->沛   9f50->齐
    	tempSet := []rune(name)
    	fmt.Println(tempSet) // [27494 27803 40784]
    	fmt.Println(tempSet[0], strconv.FormatInt(int64(tempSet[0]), 16))
    	fmt.Println(tempSet[1], strconv.FormatInt(int64(tempSet[1]), 16))
    	fmt.Println(tempSet[2], strconv.FormatInt(int64(tempSet[2]), 16))
    
    	// "rune集合" 转换 为字符串
    	runeList := []rune{27494, 27803, 40784}
    	targetName := string(runeList)
    	fmt.Println(targetName)
    }
    

    4.2.15 string 和 字符

    /*
     @Author:武沛齐  微信号:wupeiqi666
     @Description: 老男孩IT教育 & 路飞学城
     @Video:  https://space.bilibili.com/283478842
    */
    package main
    
    import (
    	"fmt"
    	"unicode/utf8"
    )
    
    func main() {
    	// 数字转字符串
    	v1 := string(65)
    	fmt.Println(v1) // A
        
        v2 := string(27494)
    	fmt.Println(v2) // 武
        
    	// 字符串转数字
    	v3, size := utf8.DecodeRuneInString("A")
    	fmt.Println(v3, size) // 65    1
        
        v4, size := utf8.DecodeRuneInString("武")
    	fmt.Println(v4, size) // 27494 3
    
    }
    

    应用场景:生成一个随机数,然后调用string得到一个随机的字符。

    4.3 索引切片和循环

    /*
     @Author:武沛齐  微信号:wupeiqi666
     @Description: 老男孩IT教育 & 路飞学城
     @Video:  https://space.bilibili.com/283478842
    */
    package main
    
    import "fmt"
    
    func main() {
    	var name string = "武沛齐"
    
    	// 1. 索引获取字节
    	v1 := name[0]
    	fmt.Println(v1)  // 230
    
    	// 2. 切片获取字节区间
    	v2 := name[0:3]
    	fmt.Println(v2)  // 武
    
    	// 3. 手动循环获取所有字节
        /*
            0 230
            1 173
            2 166
            3 230
            4 178
            5 155
            6 233
            7 189
            8 144
        */
    	for i := 0; i < len(name); i++ {
    		fmt.Println(i, name[i]) 
    	}
    
    	// 4. for range 循环获取所有字符
        /*
            0 27494 武
            3 27803 沛
            6 40784 齐
    	*/
    	for index, item := range name {
            fmt.Println(index, item, string(item))
    	}
    
    	// 5.转换成rune集合 [27494,27803,40784]
    	dataList := []rune(name)
    	fmt.Println(dataList[0], string(dataList[0]))
    
    }
    

    5.数组

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

    // 方式一:先声明再赋值(声明时内存中已开辟空间,内存初始化的值是0)
    var numbers [3]int
    numbers[0] = 999
    numbers[1] = 666
    numbers[2] = 333
    
    // 方式二:声明+赋值
    var names = [2]string{"武沛齐","alex"}
    
    // 方式三:声明+赋值 + 指定位置
    var ages = [3]int{0:87,1:73,2:99}
    
    // 方式四:省略个数
    var names = [...]string{"武沛齐","alex"}
    var ages = [...]int{  0:87,  2:99 }
    
    // 声明 指针类型的数组(指针类型),不会开辟内存初始化数组中的值,numbers = nil
    var numbers *[3]int
    
    // 声明数组并初始化,返回的是 指针类型的数组(指针类型)
    numbers := new([3]int)
    

    5.1 数组内存管理

    ![image-20200610112239589](C:/Users/LYX/Desktop/go_course-master/day05 数据类型:数字、字符串、数组/笔记/assets/image-20200610112239589.png)

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

    必备知识点:

    • 数组的内存是连续的。

    • 数组的内存地址实际上就是数组第一个元素的内存地址。

    • 每个字符串的内部存储:len + str

      type stringStruct struct {
      	str unsafe.Pointer
      	len int
      }
      

    示例1:

    /*
     @Author:武沛齐  微信号:wupeiqi666
     @Description: 老男孩IT教育 & 路飞学城
     @Video:  https://space.bilibili.com/283478842
    */
    package main
    
    import "fmt"
    
    func main() {
    	nums := [3]int8{11, 22, 33}
    
    	fmt.Printf("数组的内存地址:%p \n", &nums)
    	fmt.Printf("数组第1个元素的内存地址:%p \n", &nums[0])
    	fmt.Printf("数组第2个元素的内存地址:%p \n", &nums[1])
    	fmt.Printf("数组第3个元素的内存地址:%p \n", &nums[2])
    }
    
    >>> 输出
    数组的内存地址:0xc00001604a 
    数组第1个元素的内存地址:0xc00001604a 
    数组第2个元素的内存地址:0xc00001604b 
    数组第3个元素的内存地址:0xc00001604c 
    

    示例2:

    /*
     @Author:武沛齐  微信号:wupeiqi666
     @Description: 老男孩IT教育 & 路飞学城
     @Video:  https://space.bilibili.com/283478842
    */
    package main
    
    import "fmt"
    
    func main() {
    	nums := [3]int32{11, 22, 33}
    
    	fmt.Printf("数组的内存地址:%p \n", &nums)
    	fmt.Printf("数组第1个元素的内存地址:%p \n", &nums[0])
    	fmt.Printf("数组第2个元素的内存地址:%p \n", &nums[1])
    	fmt.Printf("数组第3个元素的内存地址:%p \n", &nums[2])
    }
    
    >>> 输出
    数组的内存地址:0xc0000b4004 
    数组第1个元素的内存地址:0xc0000b4004 
    数组第2个元素的内存地址:0xc0000b4008 
    数组第3个元素的内存地址:0xc0000b400c 
    

    示例3:

    /*
     @Author:武沛齐  微信号:wupeiqi666
     @Description: 老男孩IT教育 & 路飞学城
     @Video:  https://space.bilibili.com/283478842
    */
    package main
    
    import "fmt"
    
    func main() {
    	names := [2]string{"武沛齐", "alex"}
    	fmt.Printf("数组的内存地址:%p \n", &names)
    	fmt.Printf("数组第1个元素的内存地址:%p \n", &names[0])
    	fmt.Printf("数组第2个元素的内存地址:%p \n", &names[1])
    
    }
    
    >>> 输出:
    数组的内存地址:0xc000128020 
    数组第1个元素的内存地址:0xc000128020 
    数组第2个元素的内存地址:0xc000128030
    

    5.2 可变和拷贝

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

    names := [2]string{"武沛齐", "alex"}
    names[1] = "苑昊"
    

    注意:字符串不可以被修改。 "武沛齐" "武陪齐"

    拷贝,变量赋值时重新拷贝一份。

    name1 := [2]string{"武沛齐", "alex"}
    name2 := name1
    
    name1[1] = "苑昊"
    
    fmt.Println(name1,name2)   // [武沛齐 苑昊]   [武沛齐 alex]
    

    5.3 长度索引切片和循环

    /*
     @Author:武沛齐  微信号:wupeiqi666
     @Description: 老男孩IT教育 & 路飞学城
     @Video:  https://space.bilibili.com/283478842
    */
    package main
    
    import "fmt"
    
    func main() {
    	// 1. 长度
    	//name := [2]string{"武沛齐", "alex"}
    	//fmt.Println(len(name))
    
    	// 2. 索引
    	//name := [2]string{"武沛齐", "alex"}
    	//data := name[0]
    	//fmt.Println(data)
    	//name[0] = "eric"
    	//fmt.Println(name)
    
    	// 3. 切片
    	//nums := [3]int32{11, 22, 33}
    	//data := nums[0:2] // 获取   0 <= 下标 < 2
    	//fmt.Println(data)
    
    	// 4. 循环
    	//nums := [3]int32{11, 22, 33}
    	//for i:=0;i<len(nums);i++{
    	//	fmt.Println(i, nums[i] )
    	//}
    
    	// 5.for range 循环
    	nums := [3]int32{11, 22, 33}
    	for key, item := range nums {
    		fmt.Println(key, item)
    	}
    
    	for key := range nums {
    		fmt.Println(key)
    	}
    
    	for _,item := range nums {
    		fmt.Println(item)
    	}
    
    }
    

    5.4 数组嵌套

    // [0,0,0]
    //var nestData [3]int
    
    // [ [  [0,0,0],[0,0,0]  ],[  [0,0,0],[0,0,0]  ],[  [0,0,0],[0,0,0]  ], ]
    //var nestData [3][2][3]int
    
    // [  [0,0,0],[0,0,0]  ]
    //var nestData [2][3]int
    //nestData[0] = [3]int{11, 22, 33}
    //nestData[1][1] = 666
    //fmt.Println(nestData)
    
    //nestData := [2][3]int{[3]int{11, 22, 33}, [3]int{44, 55, 66}}
    //fmt.Println(nestData)
    

    作业题(21道)

    1. Go语言中int占多少字节?

    2. 整型中有符号和无符号是什么意思?

    3. 整型可以表示的最大范围是多少?超出怎么办?

    4. 什么是nil?

    5. 十进制是以整型方式存在,其他进制则是以字符串的形式存在?如何实现进制之间的转换?

    6. 简述如下代码的意义

      var v1 int
      var v2 *int
      var v3 = new(int)
      
    7. 浮点型为什么有时无法精确表示小数?

    8. 如何使用第三方包 decimal?

    9. 简述 ascii、unicode、utf-8的关系。

    10. 判断:Go语言中的字符串是utf-8编码的字节序列。

    11. 什么是rune?

    12. 判断:字符串是否可变?

    13. 列举你记得的字符串的常见功能?

    14. 字符串和 “字节集合” 、“rune集合” 如何实现相互转换?

    15. 字符串如何实现高效的拼接?

    16. 简述数组的存储原理。

    17. 根据要求写代码

      names := [3]string{"Alex","超级无敌小jj","傻儿子"}
      
      a. 请根据索引获取“傻儿子”
      b. 请根据索引获取“alex”
      c. 请根据索引获取“小jj”
      d. 请将name数组的最后一个元素修改为 “大烧饼”
      
    18. 看代码写输出结果

      var nestData [3][2]int
      fmt.Println(nestData)
      
    19. 请声明一个有3个元素的数组,元素的类型是有两个元素的数组,并在数组中初始化值。例如:

      [ 
          ["alex","qwe123"],
          ["eric","admin11"],
          ["tony","pp1111"] 
      ]
      
    20. 循环如下数组并使用字符串格式化输出如下内容:

      dataList := [ 
          ["alex","qwe123"],
          ["eric","admin11"],
          ["tony","pp1111"] 
      ]
      
      请补充代码...
      
      
      最终实现输出:
      我是alex,我的密码是qwe123。
      我是eric,我的密码是admin11。
      我是tony,我的密码是pp1111。
      
    21. 补充代码实现用户登录

      // userList表示有三个用户,每个用户有用户名和密码,例如:用户名是alex,密码是qwe213
      userList :=  [ 
          ["alex","qwe123"],
          ["eric","admin11"],
          ["tony","pp1111"] 
      ]
      
      // 需求:提示让用户输入用户名和密码,然后再userList中校验用户名和密码是否正确。
      

      day06 数据类型

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

      • 整型,用于表示整数。
      • 浮点型,用于表示小数。
      • 布尔型,用于表示真/假。
      • 字符串,用于表示文本信息。
      • 数组,用于表示多个数据(数据集合)
      • 指针,用于表示内存地址的类型。
      • 切片,用于表示多个数据(数据集合)
      • 字典,用于表示键值对结合。
      • 结构体,用于自定义一些数据集合。
      • 接口,用于约束和泛指数据类型。

      今日概要

      • 切片,用于表示多个数据(数据集合),可以理解为动态数组。 [alex,18,3]
      • 字典,键值对。例如:
      • 指针,用于表示内存地址的类型。

      1. 切片

      切片,动态数组。

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

      type slice struct {
      	array unsafe.Pointer
      	len   int
      	cap   int
      }
      

      ![image-20200617213213399](C:/Users/LYX/Desktop/go_course-master/day06 数据类型:指针、切片、字典/笔记/assets/image-20200617213213399.png)

      在向切片中追加的数据个数大于容量时,内部会自动扩容且每次扩容都当前容量的2倍(当容量超过1024时每次扩容则只增加 1/4容量)。

      1.1 创建切片

      // 创建切片
      var nums []int
      
      // 创建切片
      var data = []int{11,22,33}
      data:= []int{11,22,33}
      
      // 创建切片
      // make只用于 切片、字典、channel
      var users = make([]int,1,3)
      
      // 切片的指针类型
      var v1 = new([]int)
      
      // 指针类型(nil)
      var v2 *[]int
      

      1.2 自动扩容

      v1 := make([]int,1,3)
      
      fmt.Println(len(v1),cap(v1))
      
      // 其他
      data := make([]int,3)
      
      image-20200620113636908
      v1 := make([]int,1,3)
      
      v2 := append(v1,66)
      
      fmt.Println(v1)  // [0 ]
      fmt.Println(v2)  // [0,66]
      
      v1[0] = 999
      fmt.Println(v1)  // [999 ]
      fmt.Println(v2)  // [999,66]
      
      // 需求:有一个切片,请往一个切片中追加一个数据。
      v3 := make([]int,1,3)
      v3 = append(v3,999)
      
      image-20200620114104494
      v1 := []int{11,22,33}
      v2 := append(v1,44)
      
      v1[0] = 999
      
      fmt.Println(v1) // [999 22 33]
      fmt.Println(v2) // [11 22 33 44]
      

      ![image-20200620114904089](C:/Users/LYX/Desktop/go_course-master/day06 数据类型:指针、切片、字典/笔记/assets/image-20200620114904089.png)

      1.3 常见操作

      1.3.1 长度和容量

      v1 := []int{11,22,33}
      fmt.Println(len(v1), cap(v1))
      

      1.3.2 索引

      v1 := []string{"alex","李杰","老男孩"}
      v1[0]
      v1[1]
      v1[2]
      
      v2 := make([]int,2,5)
      v2[0]
      v2[1]
      v2[2]  // 报错
      
      
      v2[0] = 999
      

      1.3.3 切片

      v1 := []int{11,22,33,44,55,66}
      
      v2 := v1[1:3]
      v3 := v1[1:]
      v4 := v1[:3]
      
      // 注意:通过切片切出来的数据和原切片内部存储的数据地址相同
      

      1.3.4 追加

      /*
       @Author:武沛齐  微信号:wupeiqi666
       @Description: 老男孩IT教育 & 路飞学城
       @Video:  https://space.bilibili.com/283478842
      */
      package main
      
      import (
      	"fmt"
      )
      
      func main() {
      	v1 := []int{11,22,33}
      
      	v2 := append(v1,44)
      
      	v3 := append(v1,55,66,77,88)
      
      	v4 := append(v1, []int{100,200,300}...)
      
      	fmt.Println(v2)
      	fmt.Println(v3)
      	fmt.Println(v4)
      }
      
      >>> 输出
      [11 22 33 44]
      [11 22 33 55 66 77 88]
      [11 22 33 100 200 300]
      

      1.3.5 删除

      /*
       @Author:武沛齐  微信号:wupeiqi666
       @Description: 老男孩IT教育 & 路飞学城
       @Video:  https://space.bilibili.com/283478842
      */
      package main
      
      import "fmt"
      
      func main() {
      	v1 := []int{11, 22, 33, 44, 55, 66}
      	deleteIndex := 2
      
      	// 切片获取到 {11,22,44、55、66}
      	// 又获取到 {44, 55, 66},将44、55、66要追加到
      	result := append(v1[:deleteIndex], v1[deleteIndex+1:]...)
      	fmt.Println(result) // [11 22 44 55 66]
      	fmt.Println(v1)     //[11 22 44 55 66 66]
      }
      

      注意:使用切片时不太会使用删除。【链表】

      1.3.6 插入

      /*
       @Author:武沛齐  微信号:wupeiqi666
       @Description: 老男孩IT教育 & 路飞学城
       @Video:  https://space.bilibili.com/283478842
      */
      package main
      
      import "fmt"
      
      func main() {
      	v1 := []int{11, 22, 33, 44, 55, 66}
      	insertIndex := 3 // 在索引3的位置插入99
      
      	result := make([]int, 0, len(v1)+1)
      	result = append(result, v1[:insertIndex]...)
      	result = append(result,99)
      	result = append(result,v1[insertIndex:]...)
      	fmt.Println(result)
      }
      
      

      注意:效率低下。【链表】

      1.3.7 循环

      /*
       @Author:武沛齐  微信号:wupeiqi666
       @Description: 老男孩IT教育 & 路飞学城
       @Video:  https://space.bilibili.com/283478842
      */
      package main
      
      import "fmt"
      
      func main() {
      	v1 := []int{11, 22, 33, 99, 55, 66}
      
      	for i := 0; i < len(v1); i++ {
      		fmt.Println(i, v1[i])
      	}
      
      	for index, value := range v1 {
      		fmt.Println(index, value)
      	}
      }
      

      1.4 切片嵌套

      /*
       @Author:武沛齐  微信号:wupeiqi666
       @Description: 老男孩IT教育 & 路飞学城
       @Video:  https://space.bilibili.com/283478842
      */
      package main
      
      import "fmt"
      
      func main() {
      	v1 := []int{11, 22, 33, 99, 55, 66}
      	v2 := [][]int{[]int{11, 22, 33, 44}, []int{44, 55}}
      	v3 := [][2]int{[2]int{1, 2}, [2]int{4, 5}}
      
      	fmt.Println(v1)
      	fmt.Println(v2)
      	fmt.Println(v3)
      
      	v1[0] = 111111
      	v2[0][2] = 222222
      	v3[1][0] = 99999
      
      	fmt.Println(v1)
      	fmt.Println(v2)
      	fmt.Println(v3)
      }
      

      1.5 变量赋值

      • 整型

        /*
         @Author:武沛齐  微信号:wupeiqi666
         @Description: 老男孩IT教育 & 路飞学城
         @Video:  https://space.bilibili.com/283478842
        */
        package main
        
        import "fmt"
        
        func main() {
        	v1 := 1
        	v2 := v1
        
        	fmt.Printf("v1的内存地址:%p \n", &v1) // 0xc0000b4008
        	fmt.Printf("v2的内存地址:%p \n", &v2) // 0xc0000b4010
        }
        
      • 布尔类型

        /*
         @Author:武沛齐  微信号:wupeiqi666
         @Description: 老男孩IT教育 & 路飞学城
         @Video:  https://space.bilibili.com/283478842
        */
        package main
        
        import "fmt"
        
        func main() {
        	v1 := false
        	v2 := v1
        
        	fmt.Printf("v1的内存地址:%p \n", &v1) // 0xc00012a002
        	fmt.Printf("v2的内存地址:%p \n", &v2) // 0xc00012a003
        }
        
      • 浮点型

        /*
         @Author:武沛齐  微信号:wupeiqi666
         @Description: 老男孩IT教育 & 路飞学城
         @Video:  https://space.bilibili.com/283478842
        */
        package main
        
        import "fmt"
        
        func main() {
        	v1 := 3.14
        	v2 := v1
        
        	fmt.Printf("v1的内存地址:%p \n", &v1) // 0xc000016050
        	fmt.Printf("v2的内存地址:%p \n", &v2) // 0xc000016058
        }
        
        
      • 字符串

        /*
         @Author:武沛齐  微信号:wupeiqi666
         @Description: 老男孩IT教育 & 路飞学城
         @Video:  https://space.bilibili.com/283478842
        */
        package main
        
        import "fmt"
        
        func main() {
        	v1 := "武沛齐"
        	v2 := v1
        
        	fmt.Printf("v1的内存地址:%p \n", &v1) // 0xc000010200
        	fmt.Printf("v2的内存地址:%p \n", &v2) // 0xc000010210 
        }
        

        ![image-20200620234120353](C:/Users/LYX/Desktop/go_course-master/day06 数据类型:指针、切片、字典/笔记/assets/image-20200620234120353.png)
        注意:字符串内部元素不可被修改。

      • 数组

        /*
         @Author:武沛齐  微信号:wupeiqi666
         @Description: 老男孩IT教育 & 路飞学城
         @Video:  https://space.bilibili.com/283478842
        */
        package main
        
        import "fmt"
        
        func main() {
        	v1 := [2]int{6, 9}
        	v2 := v1
        	fmt.Println(v1, v2)
        	fmt.Printf("v1的内存地址:%p \n", &v1) // 0xc0000b4010
        	fmt.Printf("v2的内存地址:%p \n", &v2) // 0xc0000b4020
        
        	v1[0] = 11111
        	fmt.Println(v1, v2)
        }
        
        
      • 切片

        /*
         @Author:武沛齐  微信号:wupeiqi666
         @Description: 老男孩IT教育 & 路飞学城
         @Video:  https://space.bilibili.com/283478842
        */
        package main
        
        import "fmt"
        
        func main() {
        	v1 := []int{6, 9}
        	v2 := v1
        	fmt.Println(v1, v2)              // [6 9] [6 9]
        	fmt.Printf("v1的内存地址:%p \n", &v1) // 0xc0000a6020
        	fmt.Printf("v2的内存地址:%p \n", &v2) // 0xc0000a6040
        
        	v1[0] = 11111
        	fmt.Println(v1, v2) // [11111 9] [11111 9]
        }
        
        

        ![image-20200620234833936](C:/Users/LYX/Desktop/go_course-master/day06 数据类型:指针、切片、字典/笔记/assets/image-20200620234833936.png)
        如果扩容,那么内部存储数据的数组就会重新开辟区域。

        package main
        
        import "fmt"
        
        func main() {
        	v1 := []int{6, 9}
        	v2 := v1
        	fmt.Println(v1, v2)              // [6 9] [6 9]
        	fmt.Printf("v1的内存地址:%p \n", &v1) // 0xc0000a6020
        	fmt.Printf("v2的内存地址:%p \n", &v2) // 0xc0000a6040
        
        	v1 = append(v1, 999)
        	fmt.Println(v1, v2) // [11111 9] [11111 9]
        }
        
        

      总结,目前所学的所有的数据类型中,在修改切片的内部元素时,会造成所有的赋值的变量同时修改(不扩容)。

      扩展:引用类型和值类型

      1.6 练习题

      1. 简述切片和数组的区别?

      2. 简述new和make的区别?

      3. 看代码写结果

        v1 := make([]int,2,5)
        fmt.Println(v1[0],len(v1),cap(v1))
        
      4. 看代码写结果

        v1 := make([]int,2,5)
        v2 := append(v1,123)
        
        fmt.Println(len(v1),cap(v1)) // 2,5
        fmt.Println(len(v2),cap(v2)) // 3,5
        fmt.Println(v1) // [0 0]
        fmt.Println(v2) // [0 0 123]
        

        ![image-20200621002506511](C:/Users/LYX/Desktop/go_course-master/day06 数据类型:指针、切片、字典/笔记/assets/image-20200621002506511.png)

      5. 看代码写结果

        v1 := make([]int,2,5)
        v2 := append(v1,123)
        v1[0] = 999
        
        fmt.Println(v1) // [999 0]
        fmt.Println(v2) // [999, 0, 123]
        
      6. 看代码写结果

        v1 := make([]int,2,2)
        v2 := append(v1,123)
        v1[0] = 999
        
        fmt.Println(v1) // [999 0]
        fmt.Println(v2) // [0 0 123]
        

        ![image-20200621002905735](C:/Users/LYX/Desktop/go_course-master/day06 数据类型:指针、切片、字典/笔记/assets/image-20200621002905735.png)

      7. 看代码写结果

        v1 := make([]int,2,2)
        v2 := v1[0:2]
        
        v1[0] = 111
        
        fmt.Println(v1) // [111,0]
        fmt.Println(v2) // [111,0]
        
      8. 看代码写结果

        v1 := [][]int{ []int{11, 22, 33, 44},  []int{44, 55} }
        v1[0][2] = 999
        fmt.Println(v1) // [ [11 22 99 44]  [44,55] ]
        
      9. 看代码写结果

        v1 := [][]int{[]int{11, 22, 33, 44}, []int{44, 55}}
        v2 := v1[0]
        
        v2[2] = 69
        fmt.Println(v1)  // [[11 22 69 44] [44 55]]
        
      10. 看代码写结果

        v1 := [][]int{[]int{11, 22, 33, 44}, []int{44, 55}}
        v2 := append(v1[0],99)
        
        v2[2] = 69
        fmt.Println(v1) // [[11 22 33 44] [44 55]]
        
      11. 看代码写结果

        v1 := [][]int{ make([]int, 2, 5), make([]int, 2, 3) }
        v2 := append(v1[0], 99)
        
        fmt.Println(v1) // [ [0 0], [0 0 ] ]
        fmt.Println(v2) // [0 0 99]
        
        v2[0] = 69
        fmt.Println(v1) // [ [69 0], [0 0 ] ]
        fmt.Println(v2) // [69 0 99]
        

      2.字典类型(Map)

      在学习任何的编程语言时,一般都会一种数据类型称为:字典(dict)或映射(map),以键值对为元素的数据集合。例如:

      {
      	"age":"18",
      	"name":"武沛齐",
      	"email":"wupeiqi@live.com"
      }
      

      这种类型最大的特点就是查找速度非常快,因为他的底层存储是基于哈希表存储的(不同语言还会有一些差异)。

      取模+拉链法来快速了解下哈希表存储原理,如下图:

      ![image-20201006100506181](C:/Users/LYX/Desktop/go_course-master/day06 数据类型:指针、切片、字典/笔记/assets/image-20201006100506181.png)

      这种结构之所以快,是因为根据key可以直接找到数据存放的位置;而其他的数据类型是需要从前到后去逐一比对,相对来说比较耗时。

      以上只是基本的存储模型,而各个编程语言中的字典都会在此基础上进行相应的修改和优化(后续会深入讲解Golang中的map实现机制)。

      Map的特点:

      • 键不能重复
      • 键必须可哈希(目前我们已学的数据类型中,可哈希的有:int/bool/float/string/array)
      • 无序

      接下来关于map我会从两个维度来进行讲解:

      • 常见使用
      • 底层原理剖析(面试常问)

      2.1 声明&初始化

      // userInfo := map[string]string{}
      userInfo := map[string]string{"name":"武沛齐","age":"18"}
      
      userInfo["name"]  // 武沛齐
      userInfo["age"] = "20"
      userInfo["email"] = "wupeiqi@live.com"
      
      // data := make(map[int]int, 10)
      data := make(map[int]int)
      data[100] = 998
      data[200] = 999
      
      data := make(map[string]int)
      data["100"] = 998
      data["200"] = 999
      
      // 声明,nil
      var row map[string]int
      row = data
      
      data := make(map[string]int)
      data["100"] = 998
      data["200"] = 999
      
      // 声明,nil
      value := new(map[string]int)
      // value["k1"] = 123  # 报错
      value = &data
      

      注意:键不重复 & 键必须可哈希(int/bool/float/string/array)

      v1 := make(map[[2]int]float32)
      v1[[2]int{1,1}] = 1.6
      v1[[2]int{1,2}] = 3.4
      
      v2 := make(map[[2]int][3]string )
      v2[[2]int{1,1}] = [3]string{"武沛齐","alex","老妖"}
      

      2.2 常用操作

      2.2.1 长度和容量

      data := map[string]string{"n1":"武沛齐","n2":"alex"}
      value := len(data)  // 2
      
      // 根据参数值(10),计算出合适的容量。
      // 一个map 中会包含很多桶,每个桶中可以存放8个键值对。
      info := make(map[string]string, 10)
      
      info["n1"] = "武沛齐"
      info["n2"] = "alex"
      
      v1 := len(info)  // 2
      // v2 := cap(info)  // 报错
      

      2.2.2 添加

      data := map[string]string{"n1":"武沛齐","n2":"alex"}
      data["n3"] = "eric"
      

      2.2.3 修改

      data := map[string]string{"n1":"武沛齐","n2":"alex"}
      data["n1"] = "eric"
      

      2..2.4 删除

      data := map[string]string{"n1":"武沛齐","n2":"alex"}
      delete(data,"n2")
      

      2.2.5 查看

      data := map[string]string{"n1":"武沛齐","n2":"alex"}
      data["n1"]
      
      data := map[string]string{"n1":"武沛齐","n2":"alex"}
      for key,value := range data{
          fmt.Println(key,value)
      }
      
      data := map[string]string{"n1":"武沛齐","n2":"alex"}
      for key := range data{
          fmt.Println(key) 
      }
      
      data := map[string]string{"n1": "武沛齐", "n2": "alex"}
      for _, value := range data {
          fmt.Println(value)
      }
      

      2.2.6 嵌套

      v1 := make(map[string]int)
      v2 := make(map[string]string)
      v3 := make(map[string]...)
      v4 := make(map[string][2]int)
      v5 := make(map[string][]int)
      v6 := make(map[string]map[int]int)
      
      v7 := make(map[string][2]map[string]string)
      v7["n1"] = [2]map[string]string{ map[string]string{"name":"武沛齐","age":"18"},map[string]string{"name":"alex","age":"78"}}
      v7["n2"] = [2]map[string]string{ map[string]string{"name":"eric","age":"18"},map[string]string{"name":"seven","age":"78"}}
      
      // 伪代码
      v7 = {
          n1:[
              {"name":"武沛齐","age":"18"},
              {"name":"alex","age":"78"}
          ],
          n2:[
              {"name":"eric","age":"18"},
              {"name":"seven","age":"78"}
          ]
      }
      

      前提:键不重复 & 键必须可哈希

      v8 := make(map[int]int)
      v9 := make(map[string]int)
      v10 := make(map[float32]int)
      v11 := make(map[bool]int)
      v12 := make(map[ [2]int ]int)
      v13 := make(map[ []int ]int) // 错误,不可哈希
      v14 := make(map[ map[int]int ]int) // 错误,不可哈希
      v15 := make(map[ [2][]int ]int) // 报错
      v16 := make(map[ [2]map[string]string ]int) // 报错
      

      2.2.7 变量赋值

      v1 := map[string]string{"n1":"武沛齐","n2":"alex"}
      v2 := v1
      
      v1["n1"] = "wupeiqi"
      
      ftm.Println(v1) // {"n1":"wupeiqi","n2":"alex"}
      ftm.Println(v2) // {"n1":"wupeiqi","n2":"alex"}
      

      特别提醒:无论是否存在扩容都指向同一个地址。

      2.3 Map底层原理剖析

      Golang中的Map有自己的一套实现原理,其核心是由hmapbmap两个结构体实现。

      ![image-20200919191800912](C:/Users/LYX/Desktop/go_course-master/day06 数据类型:指针、切片、字典/笔记/assets/image-20200919191800912.png)

      2.3.1 初始化

      // 初始化一个可容纳10个元素的map
      info = make(map[string]string,10)
      
      • 第一步:创建一个hmap结构体对象。

      • 第二步:生成一个哈希因子hash0 并赋值到hmap对象中(用于后续为key创建哈希值)。

      • 第三步:根据hint=10,并根据算法规则来创建 B,当前B应该为1。

        hint            B
        0~8				0
        9~13            1
        14~26           2
        ...
        
      • 第四步:根据B去创建去创建桶(bmap对象)并存放在buckets数组中,当前bmap的数量应为2.

        • 当B<4时,根据B创建桶的个数的规则为:\(2^B\)(标准桶)
        • 当B>=4时,根据B创建桶的个数的规则为:\(2^B\) + \(2^{B-4}\)(标准桶+溢出桶)

        注意:每个bmap中可以存储8个键值对,当不够存储时需要使用溢出桶,并将当前bmap中的overflow字段指向溢出桶的位置。

      ![image-20200919191800912](C:/Users/LYX/Desktop/go_course-master/day06 数据类型:指针、切片、字典/笔记/assets/image-20200919191800912.png)

      2.3.2 写入数据

      info["name"] = "武沛齐"
      

      在map中写入数据时,内部的执行流程为:

      • 第一步:结合哈希因子和键 name生成哈希值 011011100011111110111011011

      • 第二步:获取哈希值的后B位,并根据后B为的值来决定将此键值对存放到那个桶中(bmap)。

        将哈希值和桶掩码(B个为1的二进制)进行 & 运算,最终得到哈希值的后B位的值。假设当B为1时,其结果为 0 :
        哈希值:011011100011111110111011010
        桶掩码:000000000000000000000000001
        结果:  000000000000000000000000000 = 0
        
        通过示例你会发现,找桶的原则实际上是根据后B为的位运算计算出 索引位置,然后再去buckets数组中根据索引找到目标桶(bmap)。
        
      • 第三步:在上一步确定桶之后,接下来就在桶中写入数据。

        获取哈希值的tophash(即:哈希值的`高8位`),将tophash、key、value分别写入到桶中的三个数组中。
        如果桶已满,则通过overflow找到溢出桶,并在溢出桶中继续写入。
        
        注意:以后在桶中查找数据时,会基于tophash来找(tophash相同则再去比较key)。
        
      • 第四步:hmap的个数count++(map中的元素个数+1)

      ![image-20200919191800912](C:/Users/LYX/Desktop/go_course-master/day06 数据类型:指针、切片、字典/笔记/assets/image-20200919191800912.png)

      2.3.3 读取数据

      value := info["name"]
      

      在map中读取数据时,内部的执行流程为:

      • 第一步:结合哈希引子和键 name生成哈希值。

      • 第二步:获取哈希值的后B位,并根据后B为的值来决定将此键值对存放到那个桶中(bmap)。

      • 第三步:确定桶之后,再根据key的哈希值计算出tophash(高8位),根据tophash和key去桶中查找数据。

        当前桶如果没找到,则根据overflow再去溢出桶中找,均未找到则表示key不存在。
        

      ![image-20200919191800912](C:/Users/LYX/Desktop/go_course-master/day06 数据类型:指针、切片、字典/笔记/assets/image-20200919191800912.png)

      2.3.4 扩容

      在向map中添加数据时,当达到某个条件,则会引发字典扩容。

      扩容条件:

      • map中数据总个数 / 桶个数 > 6.5 ,引发翻倍扩容。
      • 使用了太多的溢出桶时(溢出桶使用的太多会导致map处理速度降低)。
        • B <=15,已使用的溢出桶个数 >= \(2^B\) 时,引发等量扩容。
        • B > 15,已使用的溢出桶个数 >= \(2^{15}\) 时,引发等量扩容。
      func hashGrow(t *maptype, h *hmap) {
      	// If we've hit the load factor, get bigger.
      	// Otherwise, there are too many overflow buckets,
      	// so keep the same number of buckets and "grow" laterally.
      	bigger := uint8(1)
      	if !overLoadFactor(h.count+1, h.B) {
      		bigger = 0
      		h.flags |= sameSizeGrow
      	}
      	oldbuckets := h.buckets
      	newbuckets, nextOverflow := makeBucketArray(t, h.B+bigger, nil)
      	...
      }
      

      当扩容之后:

      • 第一步:B会根据扩容后新桶的个数进行增加(翻倍扩容新B=旧B+1,等量扩容 新B=旧B)。
      • 第二步:oldbuckets指向原来的桶(旧桶)。
      • 第三步:buckets指向新创建的桶(新桶中暂时还没有数据)。
      • 第四步:nevacuate设置为0,表示如果数据迁移的话,应该从原桶(旧桶)中的第0个位置开始迁移。
      • 第五步:noverflow设置为0,扩容后新桶中已使用的溢出桶为0。
      • 第六步:extra.oldoverflow设置为原桶(旧桶)已使用的所有溢出桶。即:h.extra.oldoverflow = h.extra.overflow
      • 第七步:extra.overflow设置为nil,因为新桶中还未使用溢出桶。
      • 第八步:extra.nextOverflow设置为新创建的桶中的第一个溢出桶的位置。

      ![image-20200920154253729](C:/Users/LYX/Desktop/go_course-master/day06 数据类型:指针、切片、字典/笔记/assets/image-20200920154253729.png)

      2.3.5 迁移

      扩容之后,必然要伴随着数据的迁移,即:将旧桶中的数据要迁移到新桶中。

      翻倍扩容

      如果是翻倍扩容,那么迁移规就是将旧桶中的数据分流至新的两个桶中(比例不定),并且桶编号的位置为:同编号位置 和 翻倍后对应编号位置。

      image-20201006081842270

      那么问题来了,如何实现的这种迁移呢?

      首先,我们要知道如果翻倍扩容(数据总个数 / 桶个数 > 6.5),则新桶个数是旧桶的2倍,即:map中的B的值要+1(因为桶的个数等于\(2^B\),而翻倍之后新桶的个数就是\(2^B\) * 2 ,也就是\(2^{B+1}\),所以 新桶的B的值=原桶B + 1 )。

      迁移时会遍历某个旧桶中所有的key(包括溢出桶),并根据key重新生成哈希值,根据哈希值的 底B位 来决定将此键值对分流道那个新桶中。

      ![image-20201006085640008](C:/Users/LYX/Desktop/go_course-master/day06 数据类型:指针、切片、字典/笔记/assets/image-20201006085640008.png)

      扩容后,B的值在原来的基础上已加1,也就意味着通过多1位来计算此键值对要分流到新桶位置,如上图:

      • 当新增的位(红色)的值为 0,则数据会迁移到与旧桶编号一致的位置。
      • 当新增的位(红色)的值为 1,则数据会迁移到翻倍后对应编号位置。

      例如:

      旧桶个数为32个,翻倍后新桶的个数为64。
      在重新计算旧桶中的所有key哈希值时,红色位只能是0或1,所以桶中的所有数据的后B位只能是以下两种情况:
      	- 000111【7】,意味着要迁移到与旧桶编号一致的位置。
      	- 100111【39】,意味着会迁移到翻倍后对应编号位置。
      	
      特别提醒:同一个桶中key的哈希值的低B位一定是相同的,不然不会放在同一个桶中,所以同一个桶中黄色标记的位都是相同的。
      
      等量扩容

      如果是等量扩容(溢出桶太多引发的扩容),那么数据迁移机制就会比较简单,就是将旧桶(含溢出桶)中的值迁移到新桶中。

      这种扩容和迁移的意义在于:当溢出桶比较多而每个桶中的数据又不多时,可以通过等量扩容和迁移让数据更紧凑,从而减少溢出桶。

      3.指针

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

      // 声明一个 字符串类型 的变量(默认初始化值为空字符串)。
      var v1 string
      
      // 声明一个 字符串的指针类型 的变量(默认初始化值为nil)。
      var v2 *string 
      
      var v3 int
      
      var v4 *int
      
      // 声明一个 字符串类型 的变量,值为 武沛齐。
      var name string = "武沛齐"
      
      // 声明一个 字符串的指针类型 的变量,值为 name 对应的内存地址。
      var pointer *string = &name
      
      var age int = 18
      var x1 *int = &age
      
      image-20201030144654619

      3.1 指针的意义

      相当于创建了一个地址的引用,以后根据这个引用再去获取他里面的值。

      ![image-20201030163932778](C:/Users/LYX/Desktop/go_course-master/day06 数据类型:指针、切片、字典/笔记/assets/image-20201030163932778.png)

      /*
       @Author:武沛齐  微信号:wupeiqi666
       @Description: 老男孩IT教育 & 路飞学城
       @Video:  https://space.bilibili.com/283478842
      */
      package main
      
      import "fmt"
      
      func main() {
      	v1 := "武沛齐"
      	v2 := &v1
      	fmt.Println(v1, v2, *v2) // 武沛齐 0xc00008e1e0 武沛齐
        
        v1 = "alex"
        fmt.Println(v1, v2, *v2) // alex 0xc00008e1e0 alex
      }
      
      

      3.2 指针的应用场景

      场景1:

      v1 := "武沛齐"
      v2 := v1
      v1 = "alex"
      
      fmt.Println(v1,v2) // alex 武沛齐
      
      v1 := "武沛齐"
      v2 := &v1
      v1 = "alex"
      fmt.Println(v1,*v2) // alex  alex
      
      image-20201030170159212

      场景2:

      package main
      
      import "fmt"
      
      func changeData(data string) {
      	data = "哈哈哈哈哈"
      }
      
      func main() {
      	name := "武沛齐"
        // 本质上会将name的值拷贝一份,并赋值给data
      	changeData(name)
        fmt.Println(name) // 武沛齐
      }
      
      package main
      
      import "fmt"
      
      func changeData(ptr *string) {
      	*ptr = "哈哈哈哈哈"
      }
      
      func main() {
      	name := "武沛齐"
      	changeData(&name)
        fmt.Println(name) // 哈哈哈哈哈
      }
      

      场景3:

      package main
      
      import "fmt"
      
      func main() {
      	var username string
      	fmt.Printf("请输入用户名:")
      	
        // 提示用户输入,当用户输入之后,将输入的值赋值给内存地址对应的区域中。
      	fmt.Scanf("%s", &username)
      
      	if username == "武沛齐" {
      		fmt.Println("登录成功")
      	} else {
      		fmt.Println("登录失败")
      	}
      }
      

      3.3 指针的指针

      name := "武沛齐"
      
      // 声明一个指针类型变量p1,内部存储name的内存地址
      var p1 *string = &name
      
      // 声明一个指针的指针类型变量p2,内部存储指针p1的内存地址
      var p2 **string = &p1
      
      // 声明一个指针的指针的指针类型变量p3,内部存储指针p2的内存地址
      var p3 ***string = &p2
      
      image-20201030172324426

      因为有指针的指针存在,所以在使用指针进行重置值时,也需要将相应的*号设置好,例如:

      package main
      
      import "fmt"
      
      func main() {
         name := "武沛齐"
      
         // 声明一个指针类型变量p1,内部存储name的内存地址
         var p1 *string = &name
        
         *p1 = "张三" // 将name的内存中的值由 武沛齐 改为 张三
      
         // 声明一个指针的指针类型变量p2,内部存储指针p1的内存地址
         var p2 **string = &p1
         **p2 = "啦啦啦" // 将name的内存中的值由 张三 改为 啦啦啦
      
         var p3 ***string = &p2
         ***p3 = "我靠" // 将name的内存中的值由 啦啦啦 改为 我靠
      }
      

      3.4 指针小高级操作

      • 数组的地址 == 数组的第一个元素的地址。
        image-20201030181305133

        dataList := [3]int8{11, 22, 33}
        
        fmt.Printf("数组的地址:%p;数组第一个元素的地址:%p \n", &dataList, &dataList[0])
        // &dataList 和 &dataList[0] 的内存中存储的数据虽然相同,但他们是两个不同类型的指针。
        // &dataList 是 *[3]int8 类型
        // &dataList[0] 是 *int8 类型
        
      • 指针的计算

        /*
         @Author:武沛齐  微信号:wupeiqi666
         @Description: 老男孩IT教育 & 路飞学城
         @Video:  https://space.bilibili.com/283478842
        */
        package main
        
        import (
        	"fmt"
        	"unsafe"
        )
        
        func main() {
        
        	dataList := [3]int8{11, 22, 33}
        
        	// 1.获取数组第一个元素的地址(指针)
        	var firstDataPtr *int8 = &dataList[0]
        
        	// 2.转换成Pointer类型
        	ptr := unsafe.Pointer(firstDataPtr)
        
        	// 3.转换成uintptr类型,然后进行内存地址的计算(即:地址加1个字节,意味着取第2个索引位置的值)。
        	targetAddress := uintptr(ptr) + 1
        
        	// 4.根据新地址,重新转换成Pointer类型
        	newPtr := unsafe.Pointer(targetAddress)
        
        	// 5.Pointer对象转换为 int8 指针类型
        	value := (*int8)(newPtr)
        
        	// 6.根据指针获取值
        	fmt.Println("最终结果为:", *value)
        }
        
        

    day07 数据类型

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

    • 整型,用于表示整数。
    • 浮点型,用于表示小数。
    • 布尔型,用于表示真/假。
    • 字符串,用于表示文本信息。
    • 数组,用于表示多个数据(数据集合)
    • 指针,用于表示内存地址的类型。
    • 切片,用于表示多个数据(数据集合)
    • 字典,用于表示键值对结合。
    • 结构体,用于自定义一些数据集合。
    • 接口,用于约束和泛指数据类型。

    今日概要

    • 结构体,用于自定义一些数据集合。
    • 接口,用于约束和泛指数据类型。

    1.结构体

    什么是结构体?

    结构体是一个复合类型,用于表示一组数据。

    结构体由一系列属性组成,每个属性都有自己的类型和值。

    // 定义
    type Person struct {
        name  string
        age   int
        email string
    }
    
    // 初始化
    var p1 = Person{"武沛齐", 19, "wupeiqi@live.com"}
    
    // 结构体中取值
    fmt.Println(p1.name, p1.age, p1.email)
    
    p1.age = 20
    
    fmt.Println(p1.name, p1.age, p1.email)
    
    type 结构体名称 struct {
        字段 类型
        ...
    }
    

    1.1 定义

    type Person struct {
        name  string
        age   int
        hobby []string
    }
    
    type Address struct {  
        city,state string
        age int
    }
    
    type Address struct {  
        city,state string
    }
    
    type Person struct {  
        name string
        age int
        ad Address
    }
    
    type Address struct {
        city, state string
    }
    
    type Person struct {
        name    string
        age     int
    	Address // 匿名字段 Address Address
    }
    

    1.2 初始化

    或称根据结构体创建一个对象。

    // 定义一个结构体(类型),每个结构体包含 name、age、hobby 三个元素
    type Person struct {
        name  string
        age   int
        hobby []string
    }
    
    //方式1:先后顺序
    var p1 = Person{"武沛齐", 19, []string{"篮球", "足球"}}
    fmt.Println(p1.name, p1.age, p1.hobby)
    
    //方式2:关键字
    var p2 = Person{name: "武沛齐", age: 19, hobby: []string{"饺子", "嫂子"}}
    fmt.Println(p2.name, p2.age, p2.hobby)
    
    //方式3:先声明再赋值
    var p3 Person
    p3.name = "武沛齐"
    p3.age = 18
    p3.hobby = []string{"女人", "篮球"}
    fmt.Println(p3.name, p3.age, p3.hobby)
    
    
    type Address struct {  
        city,state string
        age int
    }
    // 同上
    
    type Address struct {  
        city,state string
    }
    type Person struct {  
        name string
        age int
        address Address
    }
    
    //方式1:先后顺序
    var p1 = Person{"武沛齐", 19, Address{"北京", "中国"}}
    fmt.Println(p1.name, p1.age, p1.address.city, p1.address.state)
    
    //方式2:关键字
    var p1 = Person{name: "武沛齐",age: 19, address: Address{city: "北京", state: "中国"}}
    fmt.Println(p1.name, p1.age, p1.address.city, p1.address.state)
    
    //方式3:先声明再赋值
    var p3 Person
    p3.name = "武沛齐"
    p3.age = 50
    p3.address = Address{
        city:  "北京",
        state: "BJ",
    }
    fmt.Println(p3.name, p3.age, p3.address.city, p3.address.state)
    
    
    // 定义一个结构体(类型),每个结构体包含 name、age、hobby 三个元素
    type Address struct {
        city, state string
    }
    type Person struct {
        name    string
        age     int
        Address // 匿名字段,那么默认Person就包含了Address的所有字段
    }
    
    //方式1:先后顺序
    p1 := Person{"武沛齐", 19, Address{"北京", "中国"}}
    fmt.Println(p1.name, p1.age, p1.city, p1.state)
    
    
    //方式2:关键字
    p2 := Person{name: "武沛齐", age: 19, Address: Address{city: "北京", state: "中国"}}
    fmt.Println(p2.name, p2.age, p2.city, p2.state,p2.Address.city, p2.Address.state)
    
    //方式3:先声明再赋值
    var p3 Person
    p3.name = "武沛齐"
    p3.age = 50
    p3.Address = Address{
        city:  "北京",
        state: "BJ",
    }
    fmt.Println(p3.name, p3.age, p3.address.city, p3.address.state)
    // 或
    var p4 Person
    p4.name = "武沛齐"
    p4.age = 50
    p4.city = "北京"
    p4.state = "BJ"
    fmt.Println(p3.name, p3.age, p3.Address.city, p3.Address.state)
    

    1.3 结构体指针

    1.3.1 创建

    type Person struct {
        name  string
        age   int
    }
    
    // 初始化结构体(创建一个结构体对象)
    p1 := Person{"武沛齐", 18}
    fmt.Println(p1.name, p1.age)
    
    // 初始化结构体指针
    // var p2 *Person = &Person{"武沛齐", 18}
    p2 := &Person{"武沛齐", 18}
    fmt.Println(p2.name, p2.age)
    
    var p3 *Person = new(Person)
    p3.name = "武沛齐"
    p3.age = 18
    
    fmt.Println(p3.name, p3.age)
    

    1.3.2 内存管理

    type Person struct {
        name  string
        age   int
    }
    
    // 初始化结构体
    p1 := Person{"武沛齐", 18}
    fmt.Println(p1.name, p1.age)
    
    // 初始化结构体指针
    p2 := &Person{"武沛齐", 1}
    fmt.Println(p2.name, p2.age)
    
    image-20201115140009147

    1.4 赋值

    1.4.1 赋值拷贝

    type Person struct {
        name string
        age  int
    }
    
    p1 := Person{name: "武沛齐", age: 18}
    p2 := p1 // 内部将p1重新拷贝一份
    
    fmt.Println(p1) // {武沛齐 18}
    fmt.Println(p2) // {武沛齐 18}
    
    p1.name = "alex"
    
    fmt.Println(p1) // {alex 19}
    fmt.Println(p2) // {武沛齐 19}
    

    ![image-20201115142417232](C:/Users/LYX/Desktop/go_course-master/day07 数据类型:结构体、接口、函数/笔记/assets/image-20201115142417232.png)

    1.4.2 结构体指针赋值

    type Person struct {
        name string
        age  int
    }
    
    p1 := &Person{"武沛齐", 18}
    p2 := p1
    
    fmt.Println(p1) // &{武沛齐 18}
    fmt.Println(p2) // &{武沛齐 18}
    p1.name = "alex"
    fmt.Println(p1) // &{alex 18}
    fmt.Println(p2) // &{alex 18}
    

    ![image-20201115142508568](C:/Users/LYX/Desktop/go_course-master/day07 数据类型:结构体、接口、函数/笔记/assets/image-20201115142508568.png)

    基于结合结构体和结构体指针的特性,基于指针实现数据变化后同步遍布。

    type Person struct {
        name string
        age  int
    }
    
    p1 := Person{name: "二狗子", age: 19}
    p2 := &p1
    
    fmt.Println(p1) // {二狗子 19}
    fmt.Println(p2) // &{二狗子 19}
    
    p1.name = "alex"
    
    fmt.Println(p1) // {alex 19}
    fmt.Println(p2) // &{alex 19}
    

    ![image-20201115142542202](C:/Users/LYX/Desktop/go_course-master/day07 数据类型:结构体、接口、函数/笔记/assets/image-20201115142542202.png)

    1.4.3 嵌套赋值拷贝

    在存在结构体嵌套时,赋值会拷贝一份所有的数据。

    type Address struct {
    	city, state string
    }
    
    type Person struct {
    	name    string
    	age     int
    	address Address
    }
    
    p1 := Person{name:"二狗子",age:19,address: Address{"北京", "BJ"}}
    p2 := p1
    
    fmt.Println(p1.address) // {"北京" "BJ"}
    fmt.Println(p2.address) // {"北京" "BJ"}
    
    p1.address.city = "上海"
    
    fmt.Println(p1.address) // {"上海" "BJ"}
    fmt.Println(p2.address) // {"北京" "BJ"}
    

    1.4.4 谁不拷贝?

    其实本质上都拷贝了,只不过由于数据存储方式的不同,导致拷贝的有些是数据,有些是内存地址(指针)。

    • 感觉拷贝:字符串、数组、整型等。
    • 感觉不拷贝:map、切片。
    /*
     @Author:武沛齐  微信号:wupeiqi666
     @Description: 老男孩IT教育 & 路飞学城
     @Video:  https://space.bilibili.com/283478842
    */
    package main
    
    import "fmt"
    
    func main() {
    
    	type Person struct {
    		name    string
    		age     int
    		hobby   [2]string
    		num     []int
    		parent  map[string]string
    	}
    
    	p1 := Person{
    		name:   "二狗子",
    		age:    19,
    		hobby:  [2]string{"裸奔", "大保健"}, // 拷贝
    		num:    []int{69, 19, 99, 38}, // 未拷贝 (内部维护指针指向数据存储的地方)
    		parent: map[string]string{"father": "Alex", "mother": "Monika"}, // 未拷贝 (内部维护指针指向数据存储的地方)
    	}
    	p2 := p1
    
    	fmt.Println(p1)
    	fmt.Println(p2)
    	p1.parent["father"] = "武沛齐"
    	fmt.Println(p1)
    	fmt.Println(p2)
    }
    

    注意:对于那些默认拷贝的情况,可以改变为指针类型,让数据实现同步修改。

    type Address struct {
        city, state string
    }
    type Person struct {
        name    string
        age     int
        hobby   *[2]string
        num     []int
        parent  map[string]string
        address Address
    }
    
    p1 := Person{
        name:   "二狗子",
        age:    19,
        hobby:  &[2]string{"裸奔", "大保健"},
        num:    []int{69, 19, 99, 38},
        parent: map[string]string{"father": "Alex", "mother": "Monika"},
    }
    p2 := p1
    p1.hobby[0] = "洗澡"
    
    fmt.Println(p1.hobby) // &[洗澡 大保健]
    fmt.Println(p2.hobby) // &[洗澡 大保健]
    

    1.5 结构体标签

    /*
     @Author:武沛齐  微信号:wupeiqi666
     @Description: 老男孩IT教育 & 路飞学城
     @Video:  https://space.bilibili.com/283478842
    */
    package main
    
    import (
    	"fmt"
    	"reflect"
    )
    
    func main() {
    	type Person struct {
    		name string "姓名"
    		age  int32  "年龄"
    		blog string "博客"
    	}
    
    	p1 := Person{name: "武沛齐", age: 18, blog: "https://www.pythonav.com"}
    
    	p1Type := reflect.TypeOf(p1)
        
    	// 方式1
    	filed1 := p1Type.Field(0)
    	fmt.Println(filed1.Tag) // 姓名   filed1.Name -> name
    
    	// 方式2
    	filed2, _ := p1Type.FieldByName("blog")
    	fmt.Println(filed2.Tag) // 
    
    	// 循环获取
    	fieldNum := p1Type.NumField() // 总共有多少个字段 3
        // 循环:0 1 2 
    	for index := 0; index < fieldNum; index++ {
    		field := p1Type.Field(index)
    		fmt.Println(field.Name, field.Tag) //    name  姓名;    age 年龄  ;   blog 博客
    	}
    }
    

    1.6 练习题

    1. 创建学校结构体,并添加到切片中。

      创建一个学校的结构体,根据用户输入去创建学校(学校包含品牌 和 城市),然后将创建的学校信息动态添加到一个切片中。

      /*
       @Author:武沛齐  微信号:wupeiqi666
       @Description: 老男孩IT教育 & 路飞学城
       @Video:  https://space.bilibili.com/283478842
      */
      package main
      
      import "fmt"
      
      type School struct {
      	band string
      	city string
      }
      
      func main() {
      	var schoolList []School
      	for{
      		// 用户输入品牌和城市
      		var band,city string
      		fmt.Printf("请输入品牌:")
      		fmt.Scanf("%s",&band)
      		if band == "Q"{
      			break
      		}
      		fmt.Printf("请输入城市:")
      		fmt.Scanf("%s",&city)
      		// 创建一个结构体对象
      		sch := School{band: band,city: city}
      		// 把结构体对象放到切片中
      		schoolList = append(schoolList,sch)
      	}
      	fmt.Println(schoolList) // [{路飞学成 北京} {luffy 上海}]
      }
      
      
    2. 创建班级,并展示班级信息

      创建学校和班级的结构体,默认创建一个学校对象。

      根据用户输入去创建班级(包含班级名称和人数),然后将创建的班级信息动态添加到一个切片中。

      /*
       @Author:武沛齐  微信号:wupeiqi666
       @Description: 老男孩IT教育 & 路飞学城
       @Video:  https://space.bilibili.com/283478842
      */
      package main
      
      import "fmt"
      
      type School struct {
      	band string
      	city string
      }
      
      type Class struct {
      	title  string
      	count  int
      	school *School
      }
      
      func main() {
      	// 1.创建学校
      	sch := &School{"路飞学城", "北京"}
      
      	// 2.创建两个班级
      	c1 := Class{"Python1", 80, sch}
      	c2 := Class{"Python2", 70, sch}
      
      	c1.school.band = "老男孩"
      
      	fmt.Println(c1.school, c2.school)
      }
      
      
      /*
       @Author:武沛齐  微信号:wupeiqi666
       @Description: 老男孩IT教育 & 路飞学城
       @Video:  https://space.bilibili.com/283478842
      */
      package main
      
      import "fmt"
      
      type School struct {
      	band string
      	city string
      }
      
      type Class struct {
      	title  string
      	count  int
      	school *School
      }
      
      func main() {
      	// 1.创建学校
      	sch := &School{"路飞学城", "北京"}
      
      	var classList []Class
      	// 2.循环创建班级
      	for{
      		// 用户输入品牌和城市
      		//var title string
      		//var count int
      		var cls Class
      		fmt.Printf("请输入班级:")
      		fmt.Scanf("%s",&cls.title)
      		if cls.title == "Q"{
      			break
      		}
      		fmt.Printf("请输入人数:")
      		fmt.Scanf("%d",&cls.count)
      
      		// 创建班级对象
      		cls.school = sch
      
      		// 加入到班级的列表(切片)
      		classList = append(classList,cls)
      	}
      	fmt.Println(classList) // [{python1 10 0xc00000c060} {python2 90 0xc00000c060}]
      
      	for _,item := range  classList{
      		message := fmt.Sprintf("%s%s 校区,%s 班级有%d多学生。",item.school.band,item.school.city,item.title,item.count)
      		fmt.Println(message)
      	}
      }
      
      

    注意:稍后再补充

    结构体暂时先讲这么多。

    接下来学点 函数 的知识,然后再给大家补充结构体部分的内容(该部分内容需要大家先理解什么时候函数)。

    2.函数

    可以把函数当做一个代码块,用于实现某个功能。并且提高代码的重用性和可读性。

    /*
     @Author:武沛齐  微信号:wupeiqi666
     @Description: 老男孩IT教育 & 路飞学城
     @Video:  https://space.bilibili.com/283478842
    */
    package main
    
    import "fmt"
    
    // 定义函数
    func SendEmail()  {
    	fmt.Println("发送邮件了...")
    }
    
    func main() {
    	// 1000行 or 10000行
    	// ...
    	// 执行函数
    	SendEmail()
    	// ...
    	// 10行代码可以实现发邮件
    }
    
    func 函数名(参数) 返回值 {
        函数体
    }
    
    package main
    
    import "fmt"
    
    func SendEmail(email string) bool {
       fmt.Println(email, "你有新邮件来了")
       return true
    }
    
    func main() {
       result := SendEmail("wupeiqi@qq.com")
       if result {
          fmt.Println("发送成功")
       } else {
          fmt.Println("发送失败")
       }
    }
    

    关于函数名需要注意:函数名只能是字母数字下划线组合且数字不能开头(驼峰式命名 SendEmail sendEmail)。

    2.1 参数

    2.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, 8)
       fmt.Println(data, flag)
    }
    
    >>> 输出:
    9 true
    

    注意:传值时会拷贝一份数据(等同于赋值拷贝)。

    /*
     @Author:武沛齐  微信号:wupeiqi666
     @Description: 老男孩IT教育 & 路飞学城
     @Video:  https://space.bilibili.com/283478842
    */
    package main
    
    import "fmt"
    
    func SendEmail(arg [2]int)  {
    	arg[0] = 666
    }
    
    func main() {
    	dataList := [2]int{11,22}
    	SendEmail(dataList)
    	fmt.Println(dataList) // [11,22]
    }
    
    /*
     @Author:武沛齐  微信号:wupeiqi666
     @Description: 老男孩IT教育 & 路飞学城
     @Video:  https://space.bilibili.com/283478842
    */
    package main
    
    import "fmt"
    
    func SendEmail(arg *[2]int)  {
    	arg[0] = 666
    }
    
    func main() {
    	dataList := [2]int{11,22}
    	SendEmail(&dataList)
    	fmt.Println(dataList) // [666,22]
    }
    
    
    /*
     @Author:武沛齐  微信号:wupeiqi666
     @Description: 老男孩IT教育 & 路飞学城
     @Video:  https://space.bilibili.com/283478842
    */
    package main
    
    import "fmt"
    
    func SendEmail(arg []int)  {
    	arg[0] = 666
    }
    
    func main() {
    	dataList := []int{11,22}
    	SendEmail(dataList)
    	fmt.Println(dataList) // [666,22]
    }
    

    2.1.2 指针参数

    package main
    
    import "fmt"
    
    func changeData(dataList *[3]string) {
    	dataList[1] = "李杰"
    }
    
    func main() {
    	userList := [3]string{"武沛齐", "Alex", "老妖"}
    	changeData(&userList)
    
    	fmt.Println(userList)
    }
    
    >>> 输出:
    [武沛齐 李杰 老妖]
    

    数组是值类型,参数传递时会重新拷贝一份,为了避免拷贝可以使用指针类型做参数。

    2.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 9999
    	}
    }
    
    func main() {
    	result := proxy(123, add100)
    	fmt.Println(result) // 223
    
    }
    
    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 9999
       }
    }
    
    func main() {
       result := proxy(123, add100)
       fmt.Println(result)
    
    }
    

    2.1.4 变长参数

    package main
    
    import "fmt"
    
    func do(num ...int) int {
       sum := 0
       for _, value := range num {
          sum += value
       }
       return sum
    }
    
    func main() {
       r1 := do(1, 2, 3, 3)
       r2 := do(0, 1, 1)
       fmt.Println(result)
    }
    

    注意事项:边长参数只能放在最后且只能有一个。

    2.2 返回值

    2.2.1 多个返回值

    package main
    
    import "fmt"
    
    func add100(arg int) (int, bool, int) {
       return arg + 100, true, 888
    }
    
    func main() {
       v1, v2, v3 := add100(555)
       fmt.Println(v1, v2, v3)
    }
    

    2.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)
    }
    
    

    2.2.2 匿名函数&返回函数

    /*
     @Author:武沛齐  微信号:wupeiqi666
     @Description: 老男孩IT教育 & 路飞学城
     @Video:  https://space.bilibili.com/283478842
    */
    package main
    
    import "fmt"
    
    func F1(n1 int, n2 int) func(int) string {
    
    	return func(n1 int) string {
    		fmt.Println("匿名函数")
    		return "匿名"
    	}
    }
    
    func main() {
    	// 匿名函数
    	v1 := func(n1 int, n2 int) int {
    		return 123
    	}
    	data := v1(11, 22)
    	fmt.Println(data)
    
    	value := func(n1 int, n2 int) int {
    		return 123
    	}(11, 22)
    	fmt.Println(value)
    
    }
    
    package main
    
    import "fmt"
    
    func proxy() func() int {
    	v1 := func() int {
    		return 100
    	}
    	return v1
    }
    
    func main() {
    	function := proxy()
    	result := function()
    	fmt.Println(result)
    }
    

    2.3 闭包

    学函数,少不了闭包。下面用两个示例了解闭包的作用。

    /*
     @Author:武沛齐  微信号:wupeiqi666
     @Description: 老男孩IT教育 & 路飞学城
     @Video:  https://space.bilibili.com/283478842
    */
    package main
    
    import "fmt"
    
    func main() {
    	var functionList []func()
    
    	for i := 0; i < 5; i++ {
    		function := func() {
    			fmt.Println(i)
    		}
    		functionList = append(functionList, function)
    	}
    
    	functionList[0]()
    	functionList[1]()
    	functionList[2]()
    }
    
    
    /*
     @Author:武沛齐  微信号:wupeiqi666
     @Description: 老男孩IT教育 & 路飞学城
     @Video:  https://space.bilibili.com/283478842
    */
    package main
    
    import "fmt"
    
    func main() {
    	// 存储着5个函数
    	var functionList []func()
    
    	for i := 0; i < 5; i++ {
    		function := func(arg int) func() {
    			return func() {
    				fmt.Println(arg)
    			}
    		}(i)
    		functionList = append(functionList, function)
    	}
    
    	// 运行函数
    	functionList[0]()
    	functionList[1]()
    	functionList[2]()
    }
    

    2.4 defer

    用于在一个函数执行完成之后自动触发的语句,一般用于结束操作之后释放资源。

    示例1:

    package main
    
    import "fmt"
    
    func do() int {
    	fmt.Println("风吹")
    	defer fmt.Println("函数执行完毕了")  // 如果在这行之前执行return,那么defer就不再执行
    	fmt.Println("屁屁凉")
    	return 666
    }
    
    func main() {
    	ret := do()
    	fmt.Println(ret)
    }
    
    

    注意:在函数return之后/执行结束之前自动调用defer语句。

    示例2:

    package main
    
    import "fmt"
    
    func do() int {
       fmt.Println("风吹")
       defer fmt.Println("函数执行完毕了")
       defer fmt.Println("再来一个")
       fmt.Println("屁屁凉")
       return 666
    }
    
    func main() {
       ret := do()
       fmt.Println(ret)
    }
    

    当存在多个defer时,最终函数执行完毕后会按照倒序的方式去执行。

    示例3:

    package main
    
    import "fmt"
    
    func other(a1 int, a2 int) {
       fmt.Println("defer函数被执行了")
    }
    
    func do() int {
       fmt.Println("风吹")
       defer fmt.Println("函数执行完毕了")
       defer other(1, 22)
       fmt.Println("屁屁凉")
       return 666
    }
    
    func main() {
       ret := do()
       fmt.Println(ret)
    }
    

    2.5 自执行函数

    result := func(arg int) int {
        return arg + 100
    }(123)
    
    fmt.Println(result) // 223
    

    3.再看结构体

    3.1 结构体做参数和返回值

    结构体做参数和返回值时,在执行时候都会被重新拷贝一份,如果不想被拷贝,则可以通过指针的形式进行处理。

    /*
     @Author:武沛齐  微信号:wupeiqi666
     @Description: 老男孩IT教育 & 路飞学城
     @Video:  https://space.bilibili.com/283478842
    */
    package main
    
    import "fmt"
    
    type Person struct {
    	name string
    	age  int
    }
    
    var P Person = Person{"wupeiqi", 18}
    
    func doSomething() Person {
    	return P
    }
    
    func main() {
    	data := doSomething()
    	P.name = "alex"
        fmt.Println(data) // {wupeiqi 18}
    	fmt.Println(P)    // {alex 18}
    }
    
    
    /*
     @Author:武沛齐  微信号:wupeiqi666
     @Description: 老男孩IT教育 & 路飞学城
     @Video:  https://space.bilibili.com/283478842
    */
    package main
    
    import "fmt"
    
    type Person struct {
    	name string
    	age  int
    }
    
    var P Person = Person{"wupeiqi", 18}
    
    func doSomething() *Person {
    	return &P
    }
    
    func main() {
    	data := doSomething()
    	P.name = "alex"
    	fmt.Println(data) // &{alex 18}
    	fmt.Println(P)    // {alex 18}
    }
    

    3.2 类型方法

    项目开发中可以为type声明的类型编写一些方法,从而实现对象.方法的操作。

    package main
    
    import "fmt"
    
    // 声明类型
    type MyInt int
    
    // 为MyInt类型自定义一个指针方法
    // 可以是指针/可以是类型:*MyInt   MyInt
    // 不使用对象,可以用 _ 代替
    func (_ *MyInt) DoSomething(a1 int, a2 int) int {
    	return a1 + a2
    }
    
    func do(a1 int,a2 int) int{
        return a1 + a2
    }
    
    func main() {
    	var v1 MyInt = 1
    	result := v1.DoSomething(1, 2)
    	fmt.Println(result)
    }
    

    结构体也是基于type声明的类型,所以也可以使用此方式为结构体定义一些方法。

    package main
    
    import "fmt"
    
    type Person struct {
    	name string
    	age  int
    	blog string
    }
    
    // 为Person结构体类型自定义一个指针方法
    // 注意:此处如果不是指针类型的话,再执行方法时结构体对象就会被重复拷贝一份。
    func (p *Person) DoSomething(a1 int, a2 int) int {
    	return a1 + a2 + p.age
    }
    
    func main() {
    	p1 := Person{name: "武沛齐", age: 18, blog: "https://www.pythonav.com"}
    	// 调用Person的DoSomething方法
    	result := p1.DoSomething(1, 2)
    	fmt.Println(result)
    }
    

    注意:在方法名之前,func 关键字之后的括号中指定 receiver。如果方法不需要使用 recv 的值,可以用 _ 替换它。recv 就像是面向对象语言中的 thisself,但是 Go 中并没有这两个关键字。随个人喜好,你可以使用 thisself 作为 receiver 的名字。

    3.3 方法继承

    如果结构体之前存在匿名嵌套关系,则 子结构体可以继承父结构体中的方法。

    package main
    
    import "fmt"
    
    type Base struct {
       name string
    }
    type Son struct {
       Base    // 匿名的方式,如果改成 base Base 则无法继承Base的方法。
       age int
    }
    
    // Base结构体的方法
    func (b *Base) m1() int {
       return 666
    }
    
    // Son结构体的方法
    func (s *Son) m2() int {
       return 999
    }
    
    func main() {
        son := Son{age: 18, Base: Base{name: "武沛齐"}}
    	result1 := son.m1()
    	result2 := son.m2()
    	fmt.Println(result1, result2)
    }
    

    如果Son结构体中还有与其他结构体嵌套,那么他可以继承所有嵌套结构体中的方法。

    3.4 结构体工厂

    Go 语言不支持面向对象编程语言中那样的构造方法,但是可以很容易的在 Go 中实现 “构造工厂”方法。为了方便通常会为类型定义一个工厂,按惯例,工厂的名字以 new 或 New 开头。假设定义了如下的 File 结构体类型:

    type File struct {
        fd      int     
        name    string  
    }
    
    // 20...
    f := File{10, "xxxxxx"}
    
    type File struct {
        fd      int     
        name    string  
    }
    
    func NewFile(fd int, name string) *File {
    	// 20...
        return &File{fd, name}
    }
    
    func main() {
    	f1 := NewFile(10, "./test.txt")
        f2 := File{10, "xxxxxx"}
    }
    

    在 Go 语言中常常像上面这样在工厂方法里使用初始化来简便的实现构造函数。

    强制使用工厂方法,让结构体变为私有,工厂方法变为共有,这样强制所有代码在实例化结构体是都是用工厂方法。

    可以l用包导入时首字母大写共有的方式来实现。

    // 私有
    type matrix struct {
        ...
    }
    
    // 共有
    func NewMatrix(params) *matrix {
        m := new(matrix) // 初始化 m
        return m
    }
    
    package main
    import "matrix"
    ...
    // wrong := new(matrix.matrix)     // 编译失败(matrix 是私有的)
    right := matrix.NewMatrix(...)  // 实例化 matrix 的唯一方式
    

    4.接口

    Go语言中的接口是一种特殊的数据类型,定义格式如下:

    type 接口名称 interface{
        方法名称() 返回值
    }
    

    例如:

    type Base interface {
    	f1()                   // 定义方法,无返回值
    	f2() int               // 定义方法,返回值int类型
    	f3() (int, bool)       // 定义方法,2个返回值分别是 int、bool类型
    	f4(n1 int, n2 int) int // 定义方法,需要两个参数,1个返回值
    }
    
    type empty interface {}  // interface{} 
    

    接口中的方法只定义,不能编写具体的实现逻辑。

    4.1 接口的作用

    在程序开发中接口一般有两大作用:代指类型 & 约束。

    4.1.1 空接口,代指任意类型

    示例1:

    package main
    
    import (
    	"fmt"
    	"reflect"
    )
    
    // 定义空接口
    type Base interface {
    	
    }
    
    func main() {
        //定义一个切片,内部可以存放任意类型。
    	dataList := make([]Base, 0)  // 推荐简写为:dataList := make([]interface{}, 0)
        
        // 切片中添加 字符串类型
    	dataList = append(dataList, "武沛齐")
    	// 切片中添加 整型
    	dataList = append(dataList, 18)
        // 切片中添加 浮点型
    	dataList = append(dataList, 99.99)
    }
    

    示例2:

    /*
     @Author:武沛齐  微信号:wupeiqi666
     @Video:  https://space.bilibili.com/283478842
    */
    package main
    
    import (
    	"fmt"
    	"reflect"
    )
    
    type Person struct {
    	name string
    	age  int
    }
    
    func something(arg interface{}) {
    	fmt.Println(arg)
    }
    
    func main() {
    	something("武沛齐")
    	something(666)
    	something(4.15)
    	something(Person{name: "wupeiqi", age: 18})
    }
    

    由于接口只是代指这些数据类型(在内部其实是转换为了接口类型),想要再获取数据中的值时,需要再将接口转换为指定的数据类型。

    /*
     @Author:武沛齐  微信号:wupeiqi666
     @Video:  https://space.bilibili.com/283478842
    */
    package main
    
    import "fmt"
    
    type Person struct {
    	name string
    	age  int
    }
    
    func something(arg interface{}) {
    	// 接口转换为Person成功,ok=True;否则ok=True
    	tp, ok := arg.(Person)
    	if ok {
    		fmt.Println(tp.name, tp.age)
    	} else {
    		fmt.Println("转换失败")
    	}
    }
    
    func main() {
    	something(Person{name: "wupeiqi", age: 18})
        something("武沛齐")
    }
    
    package main
    
    import (
    	"fmt"
    )
    
    type Person struct {
    	name string
    	age  int
    }
    
    type Role struct {
    	title string
    	count int
    }
    
    func something(arg interface{}) {
    
    	// 多个类型转换,将arg接口对象转换为。(断言)
    	switch tp := arg.(type) {
    	case Person:
    		fmt.Println(tp.name)
    	case Role:
    		fmt.Println(tp.title)
    	case string:
    		fmt.Println(tp)
    	default:
    		fmt.Println(tp)
    	}
    }
    
    func main() {
    	something("武沛齐")
    	something(666)
    	something(4.15)
    	something(Person{name: "wupeiqi", age: 18})
    	something(Role{title: "管理员", count: 2})
    }
    

    4.1.2 非空接口,规范&约束

    一般定义非空接口,都是用于约束结构体中必须含有某个方法,例如:

    示例1:

    package main
    
    import "fmt"
    
    // 定义接口
    type IBase interface {
    	f1() int
    }
    
    // 定义结构体Person
    type Person struct {
    	name string
    }
    
    // 为结构体Person定义方法
    func (p Person) f1() int {
    	return 123
    }
    
    // 定义结构体User
    type User struct {
    	name string
    }
    
    // 为结构体User定义方法
    func (p User) f1() int {
    	return 666
    }
    
    // 基于接口的参数,可以实现传入多中类型(多态),也同时具有约束对象必须实现接口方法的功能
    func DoSomething(base IBase) {
        result := base.f1() //直接调用  接口.f1()  -> 找到其对应的类型并执行其方法
    	fmt.Println(result)
    }
    
    func main() {
    
    	per := Person{name: "武沛齐"}
    	user := User{name: "wupeiqi"}
    
    	DoSomething(per)
    	DoSomething(user)
    }
    

    示例2:

    package main
    
    import "fmt"
    
    // 定义接口
    type IBase interface {
       f1() int
    }
    
    // 定义结构体
    type Person struct {
       name string
    }
    
    // 为结构体定义方法
    func (p *Person) f1() int {
       return 123
    }
    
    // 定义结构体
    type User struct {
       name string
    }
    
    // 为结构体定义方法
    func (p *User) f1() int {
       return 666
    }
    
    // 基于接口的参数,可以实现传入多中类型(多态),也同时具有约束对象必须实现接口方法的功能
    func DoSomething(base IBase) {
       result := base.f1() // 由于base的约束,此处智能执行IBase中约束的接口。
       fmt.Println(result)
    }
    
    func main() {
    
       per := &Person{name: "武沛齐"} // 创建结构体对象并获取其指针对象
       user := &User{name: "wupeiqi"}
    
       DoSomething(per)
       DoSomething(user)
    }
    

    练习题

    在项目中实现注册成功之后向用户发送:邮件、微信的消息提醒。

    package main
    
    import (
    	"fmt"
    )
    
    type IMessage interface {
    	send() bool
    }
    
    type Email struct {
    	email   string
    	content string
    }
    
    func (p *Email) send() bool {
    	fmt.Println("发送邮件提醒", p.email, p.content)
    	return true
    }
    
    type Wechat struct {
    	wid     string
    	content string
    }
    
    func (p *Wechat) send() bool {
    	fmt.Println("发送微信提醒", p.wid, p.content)
    	return true
    }
    
    func something(objectList []IMessage) {
    	for _, item := range objectList {
    		result := item.send()
    		fmt.Println(result)
    	}
    }
    
    func main() {
    	// 用户注册...
    	messageObjectList := []IMessage{
    		&Email{"wupeiqi@live.com", "注册成功"},
    		&Wechat{"wupeiqi@live.com", "注册成功"},
    	}
    	something(messageObjectList)
    }
    

    4.2 底层实现

    4.2.1 空接口

    接口是Go的一种数据类型,在上文也已经了解到Go的空接口可以代指任意类型,从而实现参数、”容器“中可以处理多种数据类型。

    Go语言底层对空接口的实现:

    type eface struct {
       _type *_type			// 存储类型相关信息
       data  unsafe.Pointer // 存储数据
    }
    

    如果在代码中出现其他对象赋值给空接口,其实就是将其他对象相关的值存放到eface的 _typedata中,内部源码:

    // The conv and assert functions below do very similar things.
    // The convXXX functions are guaranteed by the compiler to succeed.
    // The assertXXX functions may fail (either panicking or returning false,
    // depending on whether they are 1-result or 2-result).
    // The convXXX functions succeed on a nil input, whereas the assertXXX
    // functions fail on a nil input.
    
    func convT2E(t *_type, elem unsafe.Pointer) (e eface) {
       if raceenabled {
          raceReadObjectPC(t, elem, getcallerpc(), funcPC(convT2E))
       }
       if msanenabled {
          msanread(elem, t.size)
       }
       x := mallocgc(t.size, t, true)
       // TODO: We allocate a zeroed object only to overwrite it with actual data.
       // Figure out how to avoid zeroing. Also below in convT2Eslice, convT2I, convT2Islice.
       typedmemmove(t, x, elem)
       e._type = t
       e.data = x
       return
    }
    

    ![image-20200511153726162](C:/Users/LYX/Desktop/go_course-master/day07 数据类型:结构体、接口、函数/笔记/assets/image-20200511153726162.png)

    注意:_type是一个结构体内部存储挺多的信息,这里统称为类型相关的信息。

    示例1:

    package main
    
    func main() {
    	num := 666
    	var object interface{}
    	// 将num的类型int存储到 _type 中;值8存储到data中
    	object = num
    }
    

    示例2:

    package main
    
    func DoSomething(arg interface{}) {
       // 将 num的类型int 存储到 _type 中;值8存储到data中;
    }
    
    func main() {
       num := 666
       DoSomething(num)
    }
    

    示例3:

    package main
    
    type Person struct {
       name string
       age  int
    }
    
    func DoSomething(arg interface{}) {
       // 将 info的类型Person 存储到 _type 中;值{name: "武沛齐", age: 18}存储到data中;
    }
    
    func main() {
       info := Person{name: "武沛齐", age: 18}
       DoSomething(info)
    }
    

    4.2.2 非空接口

    非空接口会定义一些方法来实现约束,所以在底层实现和空接口有些不同。

    type iface struct {
       tab  *itab           // 类型和方法相关
       data unsafe.Pointer  // 数据
    }
    
    type itab struct {
    	inter *interfacetype // 接口信息,如:接口中定义的方法。
    	_type *_type         // 类型
    	hash  uint32
    	_     [4]byte
    	fun   [1]uintptr
    }
    
    type interfacetype struct {
    	typ     _type
    	pkgpath name
    	mhdr    []imethod   // 接口的方法
    }
    

    ![image-20200511180244891](C:/Users/LYX/Desktop/go_course-master/day07 数据类型:结构体、接口、函数/笔记/assets/image-20200511180244891.png)

    总结

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

    • 整型,用于表示整数。
    • 浮点型,用于表示小数。
    • 布尔型,用于表示真/假。
    • 字符串,用于表示文本信息。
    • 数组,用于表示多个数据(数据集合)
    • 指针,用于表示内存地址的类型。
    • 切片,用于表示多个数据(数据集合)
    • 字典,用于表示键值对结合。
    • 结构体,用于自定义一些数据集合。
    • 接口,用于约束和泛指数据类型。

    除此之外,还学习 函数,以及为结构体定义方法。

posted @ 2023-05-07 17:55  哈哈哈哼  阅读(8)  评论(0编辑  收藏  举报