变量定义 Go中有很多种定义变量的方式,集百家之长,最标准的应该是先定义,后赋值。
注意:在Go中,局部变量定义了就必须使用,否则编译不成功
标准定义方式,关键字 变量名称 类型
1 2 3 4 5 6 7 8 9 10 11 12 13 package mainimport ( "fmt" ) func main () { var name string name = "xpctf" fmt.Println(name) }
定义并赋值, 关键字 变量名 类型 = 值
1 2 3 4 5 6 7 8 9 10 package mainimport ( "fmt" ) func main () { var name string = "xpctf" fmt.Println(name) }
省略类型,Go会自动根据值来推断类型 关键字 变量名 = 值
这种定义方式是最常用,项目中使用最多的
1 2 3 4 5 6 7 8 9 10 package mainimport ( "fmt" ) func main () { var name = "xpctf" fmt.Println(name) }
最简单的定义方式(只能在方法里面使用 )
1 2 3 4 5 6 7 8 9 10 package mainimport ( "fmt" ) func main () { name := "xpctf" fmt.Println(name) }
全局变量,在函数外部的就是全局变量,全局变量必须使用var或const关键字,不能使用:=
1 2 3 4 5 6 7 8 9 10 11 12 package mainimport ( "fmt" ) var age = 18 func main () { name := "xpctf" fmt.Println(name, age) }
一次定义多个变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package mainimport ( "fmt" ) func main () { var name1, name2 = "xpctf" , "xpctf1" fmt.Println(name1, name2) var ( name3 string = "xpctf3" name4 = "xpctf4" ) fmt.Println(name3, name4) }
定义常量,使用const关键字定义
1 2 3 4 5 6 7 8 9 10 package mainimport "fmt" const version string = "1.1.1" func main () { const version1 = "1.1.2" fmt.Println(version, version1) }
命名规范
核心思想:首字母大写的变量、函数、方法、属性可在包外(跨包)进行访问
类似java的公开和私密,首字母大写就是公开,首字母小写就私密
package概念
Go中是以package(包)为单位的,在同一个包内可以直接使用其他文件定义的变量和方法
在package user的user/user.go中定义了Name和age变量,在user/info.go中可以直接使用,不需要导包
前提是user.go和info.go都在同一目录下,并且都写了package user
总结:Go 是以包为单位控制可见性的:同包共享所有内容,跨包只能访问导出的(首字母大写)成员。
输入输出 输出
在go中,常用的输出就fmt.Println和fmt.Printf
Println是换行输出,可以一次性输出多个值,使用空格分隔,和python的print一样
fmt.Printf是格式化输出,用的最多,但是不会自动换行, 可以在末尾加上\n
常用格式有: %s %d %f %.2f %T %v %#v 分别对应:字符串 整数 浮点数 保留2位小数点 类型 任意类型 空文本输出双引号
Sprintf和Printf一样,但是返回格式化后的文本, 可以使用变量接收
输入
Go中使用fmt.Scan来接收标准输入,和python的input一样,但是没有输入提示,要自己输出
如果输入的值不能转化为对应类型,那么就会报错,但是会输出变量的默认值,比如age输入a会输出0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport ( "fmt" ) func main () { var ( name string age int ) fmt.Print("你叫什么名字:" ) fmt.Scan(&name) fmt.Print("你多大了:" ) fmt.Scan(&age) fmt.Printf("你好, %s,%d岁的帅哥\n" , name, age) }
基础数据类型 基础数据类型有:整数类型 浮点数类型 复数类型 字符串类型 布尔类型
整数类型
int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64
int后面的数字就是能存储的最大数据长度,int8是8个字节(8个二进制)
int取决于平台,32位操作系统就是int32 64位操作系统就是int64
uint和int同理,但它是无符号整数,只能用来存正整数,不能存负数
什么时候用int8或者int32? 在内存很匮乏并且长度短的项目中使用
浮点数类型
就一个float32和float64, 没什么好说的,用的也少
数组和切片 Go的数组需要定义长度,并且长度不能改变, 所以在Go中不常用,常用的是切片,和python中的列表类似
1 2 3 4 5 6 7 func main () { nameList := [3 ]string {"张三" , "李四" , "王五" } fmt.Println(nameList) nameList[0 ] = "张三改名了" fmt.Println(nameList[0 ]) fmt.Println(len (nameList)) }
切片可以动态增加和删除元素, 不用定义长度,也是由数组演变而来
1 2 3 4 5 6 7 func main () { nameList := []string {"张三" , "李四" } fmt.Println(nameList) nameList = append (nameList, "王二" , "麻子" ) nameList = append (nameList, []string {"猴子" , "八戒" }...) fmt.Println(nameList) }
为了更像python中的列表,能存储任意类型,可以使用any类型定义
1 2 3 4 5 6 7 func main () { nameList := []any{"张三" , "李四" } fmt.Println(nameList) nameList = append (nameList, 18 , 20 ) fmt.Println(nameList) fmt.Println(len (nameList)) }
创建全0切片, 默认长度为10,元素值都为0, 这样在可预估的长度条件下就更高效,不用一直扩充
它还有一个容量参数,不写为长度,容量是底层数组容量, 只要不超过容量都不会进行底层数组扩充
假设有一个场景: 一个班级最多有100个学员,就可以像下面这样定义, 不要太大也不要太小
1 2 indexList := make ([]int , 100 ) fmt.Println(indexList)
map map在每种语言都存在,只是叫法不一样,js叫对象,python叫字典,就是有一个键和一个值(key-value)组成的集合
1 2 3 4 5 6 7 8 9 10 func main () { userMap := map [string ]string { "张三" : "张三" , "李四" : "李四" , } fmt.Println(userMap) delete (userMap, "李四" ) userMap["王二" ] = "王二" fmt.Println(userMap) }
还有其他定义方式,喜欢那种用那种, 但make用的多一点
1 2 3 4 5 6 7 8 9 10 func main () { userMap := map [string ]string {} var ageMap = make (map [string ]int , 100 ) fmt.Println(userMap) delete (userMap, "李四" ) userMap["王二" ] = "王二" fmt.Println(userMap) ageMap["王二" ] = 25 fmt.Println(ageMap) }
判断语句 使用if写一个成绩等级判断,大于90为优, 80-90为良,60-70为中,小于60为差
在go中且运算符为&&,或运算符为||,还有一个取反运算符,!(表达式);对应python的and、or、一样
1 2 3 4 5 6 7 8 9 10 11 12 13 func main () { var score int score = 79 if score > 90 { fmt.Println("优" ) } else if score >= 80 && score <= 90 { fmt.Println("良" ) } else if score >= 70 && score <= 80 { fmt.Println("中" ) } else { fmt.Println("差" ) } }
go中有switch语句,python中没有,所以它不常用,基本上if…else if…else就够用了
go和java有一点不同,go的switch有一个条件满足就跳出,不会进入自动下一层判断
如果要进入下层判断,可以使用fallthrough关键字, 和java恰恰相反
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package mainimport "fmt" func main () { var score int fmt.Printf("请输入你的成绩:" ) fmt.Scan(&score) switch { case score > 90 : fmt.Println("优" ) case score >= 80 && score <= 90 : fmt.Println("良" ) case score >= 70 && score <= 80 : fmt.Println("中" ) case score >= 0 && score <= 70 : fmt.Println("差" ) fallthrough default : fmt.Println("输入错误" ) } }
switch还有一种写法,表达式写在switch后面,case是表达式的运算结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 func main () { var score int fmt.Printf("请输入你的成绩:" ) fmt.Scan(&score) switch score { case 90 : fmt.Println("优" ) case 80 : fmt.Println("良" ) case 70 : fmt.Println("中" ) case 60 : fmt.Println("差" ) default : fmt.Println("输入错误" ) } }
for循环 Go只有for循环,没有while循环和do…while循环
for循环很简单,和其他语言一样,也有i–
1 2 3 4 5 6 7 func main () { var count int for i := 1 ; i <= 100 ; i++ { count += i } fmt.Println(count) }
老样子,99乘法表练练嵌套for循环, 在学校上学就是这么写的
1 2 3 4 5 6 7 8 func main () { for i := 1 ; i <= 9 ; i++ { for j := 1 ; j < i+1 ; j++ { fmt.Printf("%d*%d=%d\t" , j, i, i*j) } fmt.Println() } }
死循环
1 2 3 4 5 6 func main () { for { fmt.Println(time.Now()) time.Sleep(1 * time.Second) } }
模拟while循环, 没人会这么写
1 2 3 4 5 6 7 func main () { i := 1 for i <= 100 { fmt.Println(i) i++ } }
遍历切片 基础写法和js一样,但go中也有range的写法
1 2 3 4 5 6 func main () { list := []string {"张三" , "李四" } for i := 0 ; i < len (list); i++ { fmt.Println(list[i]) } }
range写法, 可以迭代全部可以迭代数组、切片、字符串、map、channel
对于map来说,range返回的是key和value, 如果不需要其中一个,可以使用_占位符来忽略
1 2 3 4 5 6 func main () { list := []string {"张三" , "李四" } for idx, item := range list { fmt.Println(idx, item) } }
跳过循环 break是跳出整个for循环体
continue是跳过本次循环中没有执行的部分
和python一模一样的
函数参数 函数是一段封装了特定功能的可重用代码块,用于执行重复的任务或者计算,函数接收一个或多个参数并返回一个或多个结果
Go使用func关键字定义函数,下面是一个最简单的函数;main函数是入口函数,由Go调用
1 2 3 func main () { fmt.Println("Hello World" ) }
go中定义函数参数由很多中方式,基础是参数名称 参数类型, 如果类型相同可以省略前面的参数类型
1 2 3 func login (username, password string ) { fmt.Println(username, password) }
接收不确定参数个数的参数,可以使用...类型的方式,就像Println函数一样
1 2 3 func Println (a ...any) (n int , err error ) { return Fprintln(os.Stdout, a...) }
函数返回值 函数的返回值类型在参数后面定义,可以返回一个或多个返回值
1 2 3 func add (a, b int ) int { return a + b }
多个返回值使用小括号包裹,指定类型
1 2 3 4 5 6 func div (a, b int ) (int , bool ) { if b == 0 { return 0 , false } return a / b, true }
定义返回名称,可以使用直接使用return关键字,Go会自动返回返回名称的当前值
1 2 3 4 5 6 7 func div (a, b int ) (r int , ok bool ) { if b == 0 { return 0 , false } r = a / b return }
匿名函数 Go不支持函数内再定义函数,但是可以使用匿名函数来实现相同的作用
1 2 3 4 5 6 7 8 9 10 func userInfo () (name string , age int ) { GetName := func () (string , int ) { GetAge := func () int { return 24 } return "枫枫" , GetAge() } name, age = GetName() return }
匿名函数只能在定义的函数体内使用,不能在其他函数内使用;同一个文件不行,首字母大写也不行
并且匿名函数只能在函数体内定义,不能在外部定义, 匿名函数的上级必须是有个func, 匿名函数内可以定义匿名函数
高阶函数 在Go中,可以把一个函数当成参数传递到另一个函数中,函数也可以返回另外一个函数
下面是一个计数器,实现调用一次id就加1的效果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 func makeGetId () func () { var id = 1 return func () { fmt.Println(time.Now(), id) id++ } } func main () { getId := makeGetId() getId() getId() getId() }
值传递和引用传递 值传递就是把变量拷贝一份传递给变量,函数内修改不会影响外部变量的值
引用传递就是传递变量的内存地址(指针)给函数,函数内修改会影响外部变量的值
在go中使用&变量名可以获取到变量的内存地址,使用*指针获取内存地址的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 func value (a int ) { a = 100 fmt.Println(a, &a) } func pointer (a *int ) { *a = 100 fmt.Println(*a, a) } func main () { a := 1 fmt.Println(a, &a) value(a) fmt.Println(a, &a) pointer(&a) fmt.Println(a, &a) }
四个特殊函数 init函数是一个特殊的函数,存在以下特征:
在main函数之前执行,自动调用, 并且不能被其他函数调用
不能作为其他函数的参数传入
不能有传入参数和返回值
一个go文件可以有多个init函数,谁在前面谁先执行
总结: init函数主要是用于非main包的初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 func init () { fmt.Println("init-1" ) } func init () { fmt.Println("init-2" ) } func init () { fmt.Println("init-3" ) } func main () { fmt.Println("main" ) }
defer函数也是一个特殊的函数,存在以下特性:
使用defer定义,用于注册延迟调用
defer 函数会在 函数“结束”之前 调用
多个defer函数,遵循先进后出,先定义的后调用
defer函数中的变量,在defer声明时就赋值了
总结:defer函数用于资源释放和异常处理,保证资源在return前被释放
1 2 3 4 5 6 7 8 9 10 11 12 13 func main () { defer func () { fmt.Println("defer1" ) }() defer func () { fmt.Println("defer2" ) }() fmt.Println(time.Now()) }
recover函数也是一个特殊的函数,存在以下特征:
recover函数只能在defer函数中使用
recover() 返回 panic 的传入值(异常信息),没有异常时返回 nil。
总结:用来捕获(恢复)panic引发的异常, 如果程序中发生了 panic,而且用 recover() 捕获了它,程序就不会崩溃,可以继续执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func safeDivide (a, b int ) { defer func () { if r := recover (); r != nil { fmt.Println("捕获到异常:" , r) } }() fmt.Println("结果:" , a/b) } func main () { safeDivide(10 , 2 ) safeDivide(10 , 0 ) fmt.Println("程序继续运行" ) }
panic函数也是一个特殊的函数,存在以下特征:
抛出运行时错误,导致程序崩溃(除非被 recover 捕获)
程序立即停止当前函数,逐层向上展开调用栈
总结:panic抛出一个致命错误,让程序停止执行;类似python中的raise,但是go不鼓励使用panic来做常规错误处理,而是使用 error 类型返回错误;Go 标准库里,函数通常返回一个 error 类型作为最后一个返回值,调用方通过判断这个错误值是否为 nil 来判断是否有错。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func testRecover () { defer func () { if r := recover (); r != nil { fmt.Println("捕获到panic:" , r) } }() panic ("发生了致命错误!" ) fmt.Println("这行代码不会执行" ) } func main () { testRecover() fmt.Println("程序继续执行" ) }