开发工具:Visual Studio Code或者 goland(推荐)
goland开发工具中常用命令:
1、配置库代理,用来加载第三方库:go env -w GOPROXY=https://goproxy.cn
2、加载第三方库资源:go mod tidy
1、导包
import "fmt"
import "time"
两个包以上时,建议用一下方式导包
import(
"fmt"
"time"
)
2、四种变量声明方式
(1)、声明一个变量,默认的值为0
var a int
(2)、声明一个变量,初始化一个值
var b int =100
(3)、在初始化的时候,可以省去数据类型,通过值自动匹配当前的变量的数据类型
var c=100
(4)、省去var关键字,直接自动匹配(常用方法)
e :=100
fmt.Println("e=",e)
fmt.Printf("type of e=%T\n",e) //打印类型
注意:方法四,不能用于声明全局变量,方法一、二、三可以
声明多个变量
var xx,yy int=100,200
fmt.Println("xx =",xx,", yy=",yy)
var kk,ll=100,"Aceld"
fmt.Println("kk=",kk,", ll=",ll)
var (
vv int=100
jj bool =true
)
fmt.Println("vv=",vv,",jj=",jj)
3、每行代码结尾,可以加;或者不加,建议不加
4、导入的包或者声明的变量如果没有用到,会编译失败
5、代码注释
单行注释 :Ctrl + /
多行注释:shift+alt+a
6、代码缩进
Tab 向右
shift+Tab 向左取消缩进
7、数据类型
(1)、基本数据类型
[1]、 数值型
a、整数类型:int,int8(1个字节,表示范围:-2^7~2^7-1 (-128~127))
,int16(2个字节,表示范围:-2^15~2^15-1 (-32768~32767)),
int32(4个字节,表示范围:-2^31~2^31-1 (-2147483648~2147483647)),
int64(8个字节,表示范围:-2^63~2^63-1 (-9223372036854775808~9223372036854775807)),
uint8(1个字节,表示范围:0~255),
uint16(2个字节,表示范围:0~2^16-1 即0~65536),
uint32(4个字节,表示范围:0~2^32-1 即0~4294967295),
uint64(8个字节,表示范围:0~2^64-1 即18446744073709551615),
byte(1个字节,表示范围:0~ 255 等价于uint8)
注意:尽量选择合适的类型声明变量,不然会出现浪费内存或者损失精度问题
例如:声明一个年龄变量,用int8就行了,没必要使用int64浪费内存
整数类型使用时,遵守保小不保大的原则,即在保证程序运行下,尽量使用占用空间小的数据类型
b、浮点类型:float32,float64
浮点数可能会有精度的损失,所以通常情况下,建议使用:float64
例如: var num1 float32=3.1415925632543656 //会精度损失
var num2 float64=3.141592563254366
var num3=3.14159 //默认的浮点数类型为:float64
[2]、字符型(没有单独的字符型,使用byte来保存单个字母字符)
[3]、布尔型(bool 一个字节)
[4]、字符串
(2)、派生数据类型/复杂数据类型
[1]、指针
[2]、数组
[3]、结构体
[4]、管道
[5]、函数
[6]、切片
[7]、接口
[8]、map
8、进制问题
几进制:就是逢几进1的问题
十进制:0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
二进制:0 1 10 11 100 101 110 111 1000 1001 1010 1011 1100 1101 1110 1111
八进制:0 1 2 3 4 5 6 7 10 11 12 13 14 15 16 17
十六进制:0 1 2 3 4 5 6 7 8 9 A B C D E F
[1]、二进制和十进制的转换
(1)、二进制转换成十进制
二进制:1101
十进制:1*2^3 + 1*2^2 + 0*2^1 +1*2^0=13
(2)、十进制转换成二进制
十进制的值除以2,余数排列在一块
[2]、八进制和十进制的转换
(1)、八进制转换成十进制
八进制:16
十进制:1*8^1 +6*8^0=14
(2)、十进制转换成八进制
十进制的值除以8,余数排列在一块
[3]、十六进制转换成八进制
思路:十六进制转换成十进制,然后十进制转换成八进制
9、基本数据类型转换
语法:
表达式T(v)将值v转换为类型T
T:就是数据类型
v:就是需要转换的变量
var n1 int =100
var n2 float32=n1 //这里会报错,需要转换,和其他语言不太一样,大转小,小转大,都需要类型转换,即没有隐式转换
var n2 float32=float32(n1)
10、基本数据类型和string的转换介绍
(1)、基本类型转string类型
方式1:fmt.Sprintf("%参数",表达式)
var n1 int = 19
var s1 string = fmt.Sprintf("%d", n1)
方式2:使用strconv包的函数
[1]、int 转string
var n1 int =10
var s1 string = strconv.FormatInt(int64(n1), 10) //参数,第一个参数必须转为int64类型,第二个参数指定字面值的进制形式为十进制
fmt.Println("s1 的值:", s1)
fmt.Printf("s1对应的类型是:%T,s1=%q", s1, s1)
[2]、float 转 string
var n2 float64 = 4.29
var s2 string = strconv.FormatFloat(n2, 'f', 5, 64) //参数,'f'表示十进制,第三个参数,表示保留小数5位,第三个参数,表示这个小数是float64类型
fmt.Printf("s2对应的类型是:%T,s2=%v\n", s2, s2)
备注:方式2用法比较繁琐,推荐使用方式1
(2)、string 转基本类型
[1]、string 转bool
var s1 string = "true"
var b bool
b, _ = strconv.ParseBool(s1) //注意:这里返回两个值,err值不用的话,可以用 _ 忽略
fmt.Printf("b的类型是:%T,b=%v", b, b)
[2]、string 转 int
var s1 string = "19"
var num1 int64
num1, _ = strconv.ParseInt(s1, 10, 64) //参数2,代表10进制,参数3,代表转换的num1是64位的
fmt.Printf("num1 的类型是:%T,num1=%v", num1, num1)
[3]、string 转 float
var s2 string = "3.14"
var f1 float64
f1, _ = strconv.ParseFloat(s2, 64) //,参数2,代表转换的f1是64位的
fmt.Printf("f1 的类型是:%T,f1=%v", f1, f1)
11、指针
(1)、可以通过指针改变指向值
var num int =10
fmt.Println(num)
var ptr *int=&num
*ptr=20
fmt.Println(num)
(2)、指针变量接收的一定是地址值
(3)、指针变量的地址不可以不匹配
var num int =10
fmt.Println(num)
var ptr *float32=&num //这是错误的
(4)、基本数据类型(又叫值类型),都有对应的指针类型,形式为*数据类型
比如:int 对应的指针就是*int,float32对应的指针类型是*float.
12、如果变量名、函数名、常量名首字母大写,则可以被其他的包访问;
如果首字母小写,则只能在本包中使用(利用首字母大写小写完成权限控制)
13、运算符
(1)、算术运算符:+、-、*、/、%、++、--
(2)、赋值运算符:=、+=、-=、*=、/=、%=
(3)、关系运算符:==、!=、>、<、>=、<=
(4)、逻辑运算符:&&、||、!
(5)、位运算符:&、|、^
(6)、其他运算符:&、*
14、流程控制
(1)、if 单分支
if 后面一定要有空格,和条件表达式分隔开来,
条件表达式左右的()是建议省略
var count int=100
if count <30 {
fmt.Println("条件成立,执行了")
}
(2)、if 双分支
var count int=100
if count <30 {
fmt.Println("库存不足")
}else{
fmt.Println("库存充足")
}
(3)、if多分支
(4)、switch
var score int = 87
switch score / 10 {
case 10:
fmt.Println("您的等级为A级别")
case 9:
fmt.Println("您的等级为B级别")
case 8:
fmt.Println("您的等级为C级别")
case 7:
fmt.Println("您的等级为D级别")
default:
fmt.Println("你的成绩有误")
}
//switch 后面是一个表达式,这个表达式的结果依次跟case进行比较,满足结果的话执行冒号后面的代码。
//default 是用来"兜底"的一个分支,其他case分支都不走的情况下就会走default分支
//default 分支可以放在任意位置上,不一定非要放在最后
//case 后面不需要带break
//case 后面可以写多个值,用,隔开,例如:case 10,11,12:
//switch穿透,利用fallthrough关键字,如果在case语句块后增加fallthrough,则会继续执行下一个case(相当于java语言,case中没写break)
15、for 循环
结构组成: for 初始表达式;布尔表达式;迭代因子{
循环体
}
var sum int = 0
for i := 1; i <= 5; i++ {
sum += i
}
fmt.Println("sum=", sum)
注意:for 的初始表达式,不能用var定义变量的形式,要用:=,否则会报错
例如:下面是错的
var sum int = 0
for var i int= 1; i <= 5; i++ {
sum += i
}
fmt.Println("sum=", sum)
死循环写法:
for {
fmt.Println("你好 go")
}
for ;; {
fmt.Println("你好 Golang")
}
在控制台结束死循环:Ctrl + C
遍历字符串例子:
var str string = "hello golang"
for i := 0; i < len(str); i++ {
fmt.Printf("%c\n", str[i])
}
16、for range 键值循环
var str string = "hello golang你好"
for i, value := range str {
fmt.Printf("i=%d,value=%c\n", i, value)
}
17、关键字 break
//双重循环
for i := 1; i <= 5; i++ {
for j := 2; j <= 4; j++ {
fmt.Printf("i=%v,j=%v\n", i, j)
if i == 2 && j == 2 {
break
}
}
}
总结:break的作用是结束离它最近的循环
标签的使用:
lable2:
for i := 1; i <= 5; i++ {
//lable1:
for j := 2; j <= 4; j++ {
fmt.Printf("i=%v,j=%v\n", i, j)
if i == 2 && j == 2 {
break lable2
}
}
}
注意:如果那个标签没有使用到,不用加,上面的break lable2 会直接结束外面的循环
18、continue
for i := 1; i <= 100; i++ {
if i%6 != 0 {
continue //结束本次循环,继续下一次循环
}
fmt.Println(i)
}
标签的使用:
lable2:
for i := 1; i <= 5; i++ {
//lable1:
for j := 2; j <= 4; j++ {
if i == 2 && j == 2 {
continue lable2 //结束外面i=2的循环,但是i=3那些,会继续,这就是和break的区别
}
fmt.Printf("i=%v,j=%v\n", i, j)
}
}
}
19、goto 字节跳转到某一行
fmt.Println("hello golang1")
fmt.Println("hello golang2")
if true {
goto lable1
}
fmt.Println("hello golang3")
fmt.Println("hello golang4")
fmt.Println("hello golang5")
lable1:
fmt.Println("hello golang6")
fmt.Println("hello golang7")
20、return
作用:结束当前函数
21、函数
基本语法
func 函数名(形参列表) (返回值类型列表){
执行语句
return + 返回值列表
}
func cal(num1 int, num2 int) int {
return num1 + num2
}
相关细节:
(1)、函数名
遵循标识命名规范:见名知意 addNum,驼峰命名
首字母不能是数字
首字母大写该函数,可以被本包文件和其他包文件使用(类似public)
首字母小写该函数,只能被本包文件使用,其他包文件不能使用(类似private)
(2)、形参列表
(3)、返回值
第一种:一个返回值
如果返回值类型就一个的话,那么()是可以省略不写的
func cal(num1 int, num2 int) int {
return num1 + num2
}
第二种:两个或者多个返回值,返回值需要用()括起来
func cal2(num1 int, num2 int) (int, int) {
return num1 + num2, num1 - num2
}
result1, resulte2 := cal2(20, 10) //接收两个返回值
result1, _ := cal2(20, 10) //注意:如果返回值不需要,可以用"_"符号忽略返回值
(4)、函数不支持重载
(5)、支持可变参数
func test(args ...int) {
for i := 0; i < len(args); i++ {
fmt.Println(args[i])
}
}
(6)、以值传递方式的数据类型,如果希望在函数内的变量能修改函数外的变量,可以传入变量的地址&,函数内以指针的方式操作变量,从效果来看,类似引用传递
func exchangeNum(num1 int, num2 int) {
var t int
t = num1
num1 = num2
num2 = t
fmt.Println("exchangeNum num1=", num1)
fmt.Println("exchangeNum num2=", num2)
}
var num1 int = 10
var num2 int = 20
fmt.Printf("交换前 num1=%v,num2=%v\n", num1, num2)
exchangeNum(num1, num2)
fmt.Printf("交换后 num1=%v,num2=%v\n", num1, num2)
(7)、支持函数返回值命令
//传统写法要求:返回值和返回值的类型对应,顺序不能差
func test(num1 int,num2 int)(int,int){
restult1 :=num1+num2
result2 :==num1-num2
return restult1,result2
}
//升级写法:对函数返回值命名,里面顺序就无所谓了,不用对应
func test(num1 int,num2 int)(sum int,sub int){
sum :=num1+num2
sub :=num1-num2
return
}
22、init函数:初始化函数,可以用来进行一些初始化的操作
每一个源文件都可以包含一个init函数,该函数会在main函数执行前,被Go运行框架调用
23、匿名函数
//定义匿名函数,定义的同时调用
results := func(num1 int, num2 int) int {
return num1 + num2
}(10, 20)
fmt.Println(results)
//将匿名函数赋给一个变量,这个变量实际就是函数函数类型的变量
//直接调用sub,就是调用这个匿名函数了
sub := func(num1 int, num2 int) int {
return num1 - num2
}
result1 := sub(30, 70)
fmt.Println(result1)
24、闭包
返回的匿名函数+匿名函数以外的变量
闭包的使用场景:闭包可以保留上次引用的某个值,我们传入一次就可以反复使用了
// getSum函数返回值为一个函数,这个函数的参数是一个int类型的参数,返回值也是int类型
func getSum() func(int) int {
var sum int = 0
return func(num int) int {
sum = sum + num
return sum
}
}
func main(){
f := getSum()
fmt.Println(f(1)) //1
fmt.Println(f(2)) //3
fmt.Println(f(3)) //6
}
25、defer 关键字
程序中遇到defer 关键字,不会立即执行defer后的语句,而是将defer 后的语句压入一个栈中,然后继续执行后面的语句
func add(num1 int, num2 int) int {
//程序中遇到defer 关键字,不会立即执行defer后的语句,而是将defer 后的语句压入一个栈中,然后继续执行后面的语句
defer fmt.Println("num1=", num1)
defer fmt.Println("num2=", num2)
//栈的特点:先进后出
//在函数执行完毕后,从栈中取出语句开始执行,按照先进后出的规则执行语句
var sum int = num1 + num2
fmt.Println("sum=", sum)
return sum
}
func main(){
fmt.Println(add(30, 60))
}
26、数组
(1)、声明数组
语言数组声明需要指定元素类型和元素个数
var variable_name [SIZE] variable_type
var balance [10] float32
(2)、初始化数组
var balance = [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
我们也可以通过字面量在声明数组的同时快速初始化数组:
balance := [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
如果数组长度不确定,可以使用 ... 代替数组的长度,编译器会根据元素个数自行推断数组的长度:
var balance = [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
或
balance := [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
(3)、访问数组元素
数组元素可以通过索引(位置)来读取。格式为数组名后加中括号,中括号中为索引的值
也可以用for循环输出每个数组元素的值
var a = [...]int{1, 2, 3}
for i := 0; i < len(a); i++ {
fmt.Printf("数组第%d个值=%d\n", i, a[i])
}
(4)、向函数传递数组
方式一
形参设定数组大小:
void myFunction(param [10]int)
{
.
.
.
}
方式二
形参未设定数组大小:
void myFunction(param []int)
{
.
.
.
}
27、结构体
(1)、定义结构体
结构体定义需要使用 type 和 struct 语句。struct 语句定义一个新的数据类型,结构体中有一个或多个成员。
type 语句设定了结构体的名称。结构体的格式如下:
type struct_variable_type struct {
member definition
member definition
...
member definition
}
例如:
type Books struct {
title string
author string
subject string
book_id int
}
(2)、结构体赋值
一旦定义了结构体类型,它就能用于变量的声明,语法格式如下:
variable_name := structure_variable_type {value1, value2...valuen}
或
variable_name := structure_variable_type { key1: value1, key2: value2..., keyn: valuen}
[1]、方式一
var books Books = Books{"标题", "作者", "科目", 1006}
fmt.Println(books)
[2]、方式二
var books Books = Books{author: "作者", title: "标题1", subject: "科目", book_id: 1006}
fmt.Println(books)
[3]、方式三
var book1 Books
book1.title = "标题"
book1.author = "作者"
book1.subject = "科目"
book1.book_id = 1008
fmt.Println("book1 title", book1.title)
fmt.Println("book1 author", book1.author)
fmt.Println("book1 subject", book1.subject)
fmt.Println("book1 book_id", book1.book_id)
(3)、访问结构体成员
fmt.Println("book1 title", book1.title)
fmt.Println("book1 author", book1.author)
fmt.Println("book1 subject", book1.subject)
fmt.Println("book1 book_id", book1.book_id)
(4)、结构体作为函数参数
func printBook(book Books) {
fmt.Println("printBook book title", book.title)
fmt.Println("printBook book author", book.author)
fmt.Println("printBook book subject", book.subject)
fmt.Println("printBook book book_id", book.book_id)
}
type Books struct {
title string
author string
subject string
book_id int
}
func main(){
var book1 Books
book1.title = "标题"
book1.author = "作者"
book1.subject = "科目"
book1.book_id = 1008
printBook(book1)
}
(5)、结构体指针
使用结构体指针访问结构体成员,使用 "." 操作符
func printBook(book *Books) {
fmt.Println("printBook book title", book.title)
fmt.Println("printBook book author", book.author)
fmt.Println("printBook book subject", book.subject)
fmt.Println("printBook book book_id", book.book_id)
book.author = "作者1"
}
type Books struct {
title string
author string
subject string
book_id int
}
func main(){
var book1 Books
book1.title = "标题"
book1.author = "作者"
book1.subject = "科目"
book1.book_id = 1008
printBook(&book1)
}
28、切片
切片是对数组的抽象
数组的长度不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,
功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,
在追加时可能使切片的容量增大
(1)、定义切片
[1]、可以声明一个未指定大小的数组来定义切片,切片不需要说明长度
var identifier []type
[2]、或使用 make() 函数来创建切片:
var slice1 []type=make([]type,len)
也可以简写为:
slice1 :=make([]type,len)
注意:也可以指定容量,其中 capacity 为可选参数
make([]T, length, capacity)
这里 len 是数组的长度并且也是切片的初始长度
(2)、切片初始化
s:=[]int{1,2,3}
(3)、len()和cap()函数
func main(){
var numbers = make([]int, 3, 5)
printSlice(numbers)
}
func printSlice(x []int) {
fmt.Printf("len=%d,,,cap=%d,,,slice=%v\n", len(x), cap(x), x)
}
(3)、空(nil)切片
一个切片在未初始化之前默认为 nil,长度为 0
var numbers []int
if(numbers == nil){
fmt.Printf("切片是空的")
}
(4)、切片截取
可以通过设置下限及上限来设置截取切片
func printSlice(x []int) {
fmt.Printf("len=%d,,,cap=%d,,,slice=%v\n", len(x), cap(x), x)
}
func main(){
//创建切片
numbers := []int{0, 1, 2, 3, 4, 5, 6, 7, 8}
printSlice(numbers)
//打印原始切片
fmt.Println("numbers==", numbers)
//打印子切片从索引1(包含)到索引4(不包含)
fmt.Println("numbers[1:4]==", numbers[1:4])
//默认下限为0
fmt.Println("numbers[:3]==", numbers[:3])
//默认上限为len(s)
fmt.Println("numbers[4:]==", numbers[4:])
numbers1 := make([]int, 0, 5)
printSlice(numbers1)
//打印子切片从索引 0(包含)到索引2(不包含)
numbers2 := numbers[:2]
printSlice(numbers2)
//打印子切片从索引2(包含)到索引5(不包含)
numbers3 := numbers[2:5]
printSlice(numbers3)
}
打印结果:
len=9,,,cap=9,,,slice=[0 1 2 3 4 5 6 7 8]
numbers== [0 1 2 3 4 5 6 7 8]
numbers[1:4]== [1 2 3]
numbers[:3]== [0 1 2]
numbers[4:]== [4 5 6 7 8]
len=0,,,cap=5,,,slice=[]
len=2,,,cap=9,,,slice=[0 1] //注意:截取的子切片,容量cap=9还是和之前的切片一样大
len=3,,,cap=7,,,slice=[2 3 4]
(5)、append() 和 copy() 函数
func printSlice(x []int) {
fmt.Printf("len=%d,,,cap=%d,,,slice=%v\n", len(x), cap(x), x)
}
func main(){
var numbers5 []int
printSlice(numbers5)
/* 允许追加空切片 */
numbers5 = append(numbers5, 0)
printSlice(numbers5)
/* 向切片添加一个元素 */
numbers5 = append(numbers5, 1)
printSlice(numbers5)
/* 同时添加多个元素 */
numbers5 = append(numbers5, 2, 3, 4)
printSlice(numbers5)
/* 拷贝 numbers 的内容到 numbers1 */
numbers6 := make([]int, len(numbers5), (cap(numbers5))*2)
copy(numbers6, numbers5)
printSlice(numbers6)
}
代码执行输出结果为:
len=0,,,cap=0,,,slice=[]
len=1,,,cap=1,,,slice=[0]
len=2,,,cap=2,,,slice=[0 1]
len=5,,,cap=6,,,slice=[0 1 2 3 4]
len=5,,,cap=12,,,slice=[0 1 2 3 4]
(6)、使用append进行切片拼接
func main(){
s := []int{1, 2, 3}
i := append(s, 100)//切片中新增元素
fmt.Printf("i: %v\n", i)
s1 := []int{4, 5, 6}
i2 := append(s, s1...)//切片中新增切片
fmt.Printf("i2: %v\n", i2)
}
注意:使用make创建的切片会赋默认值,例如:[]int类型的切片,默认值为0;
var number[]int;声明的切片,不会赋默认值
29、语言返回 Range
range 关键字用于 for 循环中迭代数组(array)、切片(slice)、通道(channel)或集合(map)的元素。
在数组和切片中它返回元素的索引和索引对应的值,在集合中返回 key-value 对。
for 循环的 range 格式可以对 slice、map、数组、字符串等进行迭代循环。格式如下:
for key, value := range oldMap {
newMap[key] = value
}
以上代码中的 key 和 value 是可以省略。
如果只想读取 key,格式如下:
for key := range oldMap
或者这样:
for key, _ := range oldMap
如果只想读取 value,格式如下:
for _, value := range oldMap
(1)、遍历数组
var pow = []int{1, 2, 4, 8, 16, 32, 64}
for i, v := range pow {
fmt.Printf("2**%d =%d\n", i, v)
}
(2)、遍历map
map1 := make(map[int]float32)
map1[1] = 1.0
map1[2] = 2.0
map1[3] = 3.0
map1[4] = 4.0
//读取key和value
for key, value := range map1 {
fmt.Printf("key=%d value=%f\n", key, value)
}
//读取key
for key := range map1 {
fmt.Printf("key=%d\n", key)
}
//读取值
for _, value := range map1 {
fmt.Printf("value=%f\n", value)
}
30、map集合
Map 是一种无序的键值对的集合。Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。=
Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。不过,Map 是无序的,我们无法决定它的返回顺序,
这是因为 Map 是使用 hash 表来实现的
(1)、定义map
方式一:
/* 声明变量,默认 map 是 nil */
var map_variable map[key_data_type]value_data_type
注意:这里的map是空的,没办法赋值
推荐使用方式二 一步到位
方式二:
/* 使用 make 函数 */
map_variable := make(map[key_data_type]value_data_type)
(2)、创建map
var map1 = make(map[string]string)
map1["France"] = "巴黎"
map1["Italy"] = "罗马"
map1["Japan"] = "东京"
for key, value := range map1 {
fmt.Printf("key=%s value=%s\n", key, value)
}
(3)、查看元素在集合中是否存在
capital, ok := map1["Japan666"]
fmt.Println(capital)//capital为返回的value,不存在则返回空
fmt.Println(ok)//返回的bool值
(4)、delete()函数
var map1 = make(map[string]string)
map1["France"] = "巴黎"
map1["Italy"] = "罗马"
map1["Japan"] = "东京"
delete(map1, "France")
for key, value := range map1 {
fmt.Printf("key=%s value=%s\n", key, value)
}
31、类型转换
类型转换用于将一种数据类型的变量转换为另外一种类型的变量
type_name(expression)
type_name 为类型,expression 为表达式。
package main
import "fmt"
func main() {
var sum int = 17
var count int = 5
var mean float32
mean = float32(sum)/float32(count)
fmt.Printf("mean 的值为: %f\n",mean)
}
32、接口
把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口
(1)、/* 定义接口 */
type interface_name interface {
method_name1 [return_type]
method_name2 [return_type]
method_name3 [return_type]
...
method_namen [return_type]
}
(2)、/* 定义结构体 */
type struct_name struct {
/* variables */
}
(3)、/* 实现接口方法 */
func (struct_name_variable struct_name) method_name1() [return_type] {
/* 方法实现 */
}
package main
import (
"fmt"
)
type Phone interface {
call()
}
type NokiaPhone struct {
}
func (nokiaPhone NokiaPhone) call() {
fmt.Println("I am Nokia, I can call you!")
}
type IPhone struct {
}
func (iPhone IPhone) call() {
fmt.Println("I am iPhone, I can call you!")
}
func main() {
var phone Phone
phone = new(NokiaPhone)
phone.call()
phone = new(IPhone)
phone.call()
}
33、线程
(1)、Go 语言支持并发,我们只需要通过 go 关键字来开启 goroutine 即可。
goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的。
goroutine 语法格式:
go 函数名( 参数列表 )
package main
import (
"fmt"
"time"
)
func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
go say("world")
say("hello")
}
执行以上代码,你会看到输出的 hello 和 world 是没有固定先后顺序。因为它们是两个 goroutine 在执行:
world
hello
hello
world
world
hello
hello
world
world
hello
(2)、通道(channel)
通道(channel)是用来传递数据的一个数据结构
通道可用于两个线程(goroutine)之间通过传递一个指定类型的值来同步运行和通讯。
操作符 <- 用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道。
【1】、格式
ch <- v // 把 v 发送到通道 ch
v := <-ch // 从 ch 接收数据
// 并把值赋给 v
【2】、声明通道
声明一个通道很简单,我们使用chan关键字即可,通道在使用前必须先创建:
ch := make(chan int)
注意:默认情况下,通道是不带缓冲区的。发送端发送数据,同时必须有接收端相应的接收数据
【3】、示例
以下实例通过两个 goroutine 来计算数字之和,在 goroutine 完成计算后,
它会计算两个结果的和:
package main
import "fmt"
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // 把 sum 发送到通道 c
}
func main() {
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(s[:len(s)/2], c)//17
go sum(s[len(s)/2:], c)//-5
x, y := <-c, <-c // 从通道 c 中接收
//x := <-c
//y := <-c
fmt.Println(x, y, x+y)
}
输出结果为:
-5 17 12
注意:通道值先进先出
(3)、通道缓存区
通道可以设置缓冲区,通过 make 的第二个参数指定缓冲区大小:
ch := make(chan int, 100)
带缓冲区的通道允许发送端的数据发送和接收端的数据获取处于异步状态,就是说发送端发送的数据可以放在缓冲区里面,可以等待接收端去获取数据,
而不是立刻需要接收端去获取数据。
不过由于缓冲区的大小是有限的,所以还是必须有接收端来接收数据的,否则缓冲区一满,数据发送端就无法再发送数据了。
注意:如果通道不带缓冲,发送方会阻塞直到接收方从通道中接收了值。如果通道带缓冲,发送方则会阻塞直到发送的值被拷贝到缓冲区内;
如果缓冲区已满,则意味着需要等待直到某个接收方获取到一个值。接收方在有值可以接收之前会一直阻塞。
package main
import "fmt"
func main() {
// 这里我们定义了一个可以存储整数类型的带缓冲通道
// 缓冲区大小为2
ch := make(chan int, 2)
// 因为 ch 是带缓冲的通道,我们可以同时发送两个数据
// 而不用立刻需要去同步读取数据
ch <- 1
ch <- 2
// 获取这两个数据
fmt.Println(<-ch)
fmt.Println(<-ch)
}
执行输出结果为:
1
2
(4)、遍历通道和关闭通道
Go 通过 range 关键字来实现遍历读取到的数据,类似于与数组或切片。格式如下:
v, ok := <-ch
如果通道接收不到数据后 ok 就为 false,这时通道就可以使用 close() 函数来关闭。
实例
package main
import (
"fmt"
)
func fibonacci(n int, c chan int) {
x, y := 0, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x+y
}
close(c)
}
func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
// range 函数遍历每个从通道接收到的数据,因为 c 在发送完 10 个
// 数据之后就关闭了通道,所以这里我们 range 函数在接收到 10 个数据
// 之后就结束了。如果上面的 c 通道不关闭,那么 range 函数就不
// 会结束,从而在接收第 11 个数据的时候就阻塞了。
for i := range c {
fmt.Println(i)
}
}
34、json库的使用
核心的两个函数
[1]、将struct 生成json,可以接收任意类型
func Marshal(v any) ([]byte, error)
type Person struct {
Name string
Age int
Email string
}
func main(){
p := Person{
Name: "tom",
Age: 20,
Email: "tomagmail.com",
}
b, _ := json.Marshal(p)
fmt.Printf("b:%v\n", string(b))
}
[2]、将json转码成struct 结构体
func Unmarshal(data []byte, v any) error
func main(){
b := []byte(`{"Name":"tom","Age":20,"Email":"tomagmail.com"}`)
var p Person
//b := []byte(`{"Name":"tom","Age":20,"Email":"tomagmail.com","Parents":["big tom","kite"]}`)
//var p interface{} //注意:interface{}表示任意类型
json.Unmarshal(b, &p)
fmt.Printf("p:%v\n", p)
}
type Person struct {
Name string
Age int
Email string
}
两个核心的结构体
[3]、从输入流读取并解析json
// A Decoder reads and decodes JSON values from an input stream.
type Decoder struct {
r io.Reader
buf []byte
d decodeState
scanp int // start of unread data in buf
scanned int64 // amount of data already scanned
scan scanner
err error
tokenState int
tokenStack []int
}
示例:
a.json 的内容:{"Name":"tom","Age":20,"Email":"tomagmail.com","Parents":["big tom","kite"]}
func test1() {
f, _ := os.Open("a.json")
defer f.Close()
d := json.NewDecoder(f)
var v map[string]interface{}
d.Decode(&v)
fmt.Printf("v:%v\n", v)
}
func main(){
test1()
}
[4]、写json到输出流
type Encoder struct {
w io.Writer
err error
escapeHTML bool
indentBuf *bytes.Buffer
indentPrefix string
indentValue string
}
示例:
func test2() {
type Person struct {
Name string
Age int
Email string
Parent []string
}
p := Person{
Name: "tom",
Age: 20,
Email: "tom@mail.com",
Parent: []string{"big tom", "big kite"},
}
f, _ := os.OpenFile("a.json", os.O_WRONLY, 0777)
defer f.Close()
e := json.NewEncoder(f)
e.Encode(p)
}
func main(){
test2()
}
35、类型断言
类型断言就是将接口类型的值(x),转换成类型(T)。格式为:x.(T)
(1)、类型断言的必要条件就x是接口类型,非接口类型的x不能做类型断言;
(2)、T可以是非接口类型,如果想断言合法,则T必须实现x的接口;
(3)、T也可以是接口,则x的动态类型也应该是接口T;
(4)、类型断言如果非法,运行时会导致错误,为了避免这种错误,应该总是使用下面的方式来进行类型断言
(5)、x值和要转换的类型必须对应,不然会报错
示例:
package main
import (
"fmt"
)
func main() {
var x interface{}
x =100
value1,ok :=x.(int)
if ok {
fmt.Println(value1)
}
//value2:=x.(string) //这样也行,直接拿结果
value2,ok :=x.(string) //断言x 为int 类型,转换为string时,会转换失败,ok=false
if ok {
fmt.Println(value2)
}
}
36、标准库 errors
(1)、error的使用
func check(s string) (string, error) {
if s == "" {
err := errors.New("字符串不能为空")
return "", err
} else {
return s, nil
}
}
package main
import (
"fmt"
"errors"
)
func main() {
s, err := check("")
if err != nil {
fmt.Printf("err:%v\n", err.Error())
} else {
fmt.Printf("s:%v\n", s)
}
}
(2)、自定义错误
type MyError struct {
When time.Time
What string
}
func (e MyError) Error() string {
return fmt.Sprintf("%v:%v", e.When, e.What)
}
func oops() error {
return MyError{
time.Date(1989, 3, 15, 22, 30, 0, 0, time.UTC),
"the file system has gone away",
}
}
func main() {
err := oops()
if err != nil {
fmt.Println(err)
}
}
38、定时器Ticker
func main() {
ticker := time.NewTicker(time.Second) //一秒执行一次,5秒的话可以time.Second*5
counter := 1
for range ticker.C {
fmt.Println("ticker...")
counter++
if counter >= 5 {
ticker.Stop()
break
}
}
}
//通过定时器,往通道里写数据,然后读出来
import (
"fmt"
"time"
)
func main() {
ticker := time.NewTicker(time.Second)
chanInt := make(chan int)
go func() {
for range ticker.C {
select {
case chanInt <- 1:
case chanInt <- 2:
case chanInt <- 3:
}
}
}()
sum := 0
for v := range chanInt {
fmt.Println("收到:", v)
sum += v
if sum >= 10 {
break
}
}
}
39、select
(1)、select 是Go中的一个控制结构,类似于switch语句,用于处理异步IO操作。select会监听case语句中
channel的读写操作,当case中channel读写操作为非阻塞状态(即能读写)时,将会触发相应的动作
select中的case语句必须是一个channel操作
select中的default字句总是可运行的
(2)、如果有多个case都可以运行,select会随机公平地选出一个执行,其他不会执行
(3)、如果没有可运行的case语句,且有default语句,那么就会执行default的动作
(4)、如果没有可运行的case语句,且没有default语句,select将阻塞,直到某个case通信可以运行
func main() {
var chanInt = make(chan int)
var chanStr = make(chan string)
go func() {
chanInt <- 100
chanStr <- "hello"
defer close(chanInt)
defer close(chanStr)
}()
for {
select {
case r := <-chanInt:
fmt.Printf("chanInt:%v\n", r)
case r := <-chanStr:
fmt.Printf("chanStr:%v\n", r)
default:
fmt.Println("default...")
}
time.Sleep(time.Second)
}
}
40、func (c *Context) HandlerName() string {}
func括号表示Copy() 由*Context调用,即指定函数的调用者,类似C++ 中的::
41、抛异常 panic
func main() {
defer fmt.Println("panic 后我还会执行")
panic("抛出异常,程序终止...")
fmt.Println("end...")
结果:panic 后我还会执行
panic: 抛出异常,程序终止...
}
注意:不要随便使用panic,调用panic后,程序会终止,panic后的语句将不会执行
42、new 和make 区别
(1)、make 只能用来分配及初始化类型为slice(切片)、map、chan(通道)的数据,new 可以分配任意类型的数据
(2)、new 分配返回的是指针,即类型*T;make返回引用,即T
(3)、new 分配的空间被清零,make分配后,会进行初始化
43、panic和recover结合使用,捕捉异常
panic和recover使用原则:
defer 需要放在 panic 之前定义,另外 recover 只有在 defer 调用的函数中才有效。
recover 处理异常后,逻辑并不会恢复到 panic 那个点去,函数跑到 defer 之后的那个点。
多个 defer 会形成 defer 栈,后定义的 defer 语句会被最先调用
panic与recover的关系:
如果有 panic 但没有 recover,那么程序会宕机。如果有 panic 也有 recover,程序不会宕机,
执行完对应的 defer 后,从宕机点退出当前函数后继续执行。虽然 panic/recover 能模拟其他语言的异常机制,
但并不建议在编写普通函数时也经常性使用这种特性。在 panic 触发的 defer 函数内,
可以继续调用 panic,进一步将错误外抛,直到程序整体崩溃
44、格式化打印占位符:
%v,原样输出(即打印原始值)
%T,打印类型
%t,bool类型
%s,字符串
%f,浮点
%d,10进制的整数
%b,2进制的整数
%o,8进制
%x,%X,16进制
%x:0-9,a-f
%X:0-9,A-F
%c,打印字符
%p,打印地址
45、omitempty作用
omitempty作用是在json数据结构转换时,当该字段的值为该字段类型的零值时,忽略该字段
type Student struct {
Name string `json:"name"`
Age int `json:"age"`
Grade string `json:"grade,omitempty"`
}
func main() {
stu1 := Student{
Name:"Tom",
Age:18,
Grade:"middle school",
}
stu2 := Student{
Name:"LiLy",
Age:19,
}
stuByts1,_ := json.Marshal(&stu1)
stuByts2 ,_ := json.Marshal(&stu2)
fmt.Println("stu1:",string(stuByts1))
fmt.Println("stu2:",string(stuByts2))
}
打印结果如下:
stu1: {"name":"Tom","age":18,"grade":"middle school"}
stu2: {"name":"LiLy","age":19}
45、nil 表示为空