【Go】函数高级 包的使用 gin框架入门 if-else 循环 switch 数组
昨日回顾
# 1 go 基础数据类型
-数字:整数,正整数,浮点数,复数
-字符串: "" ``
-单引号引起来的,只能放一个字符 a 中
-实际上是数字表示 20013
-默认是int32 实际上是rune
-也可定义为 byte类型或uint8类型
-布尔类型 :true,false
# 2 常量 :后期不能修改
-作用域范围
-同时定义多个常量
-iota
# 3 函数
-1 没有参数没有返回值 fun 函数名(){}
-2 有参数没有返回值 fun 函数名(a int){}
-3 多个参数同一种类型,可以简写 fun 函数名(a,b int){}
-4 多个参数同一种类型,有返回值可以简写 fun 函数名(a,b int)int{ retrun a+b}
-5 多个返回值fun 函数名(a,b int)(int,string){ retrun a+b,'成功'}
# 4 函数高级
-1 匿名函数,定义在函数内部,没有名字
var f func()=func(){}
头等函数
一等公民
-2 闭包函数
-定义在函数内部,对外部作用域有引用
-3 函数也是一种类型,参数和返回值都是类型的一部分
-4 一个函数可以返回另一个函数
# 补充
go的值类型和引用类型。
装饰器是一种设计模式。go语言虽然没有装饰器语法糖,但还是可以实现装饰器。
设计模式还有装饰器模式、迭代器模式。
今日内容
1 函数高级
package main
import "fmt"
// 1 函数的参数和返回值都是类型的一部分,函数可以赋值给一个变量
// test3 函数,接收一个参,参数是函数类型:没有参数没有返回值
// test 有返回值,返回值是个函数:函数有两个参数,一个返回值
//func test3(a func()) func(int, int) int {
// a()
// return func(x, y int) int {
// return x + y
// }
//}
// 2 类型重命名
// 可以给类型重命名
// 如果 type Myint int 相当于我们新定义了一个类型,叫Myint
// 如果 type Myint = int 只是重命名,没有新定义类型
type MyFunc func(int, int) int
type Myint = int
func test3(a func()) MyFunc {
a()
return func(x, y int) int {
return x + y
}
}
// 我们可以定义自己的类型,简化重复的类型声明
// 3 函数可变长参数
// 可以传任意长度的int类型参数
func test4(a ...int) {
fmt.Println(a) // [1 2 3 4 5] 切片
fmt.Printf("%T", a) // 类型是 int类型切片 []int
}
// 4 defer 关键字
func main() {
//var a Myint = 9
//var b int = 19
//fmt.Println(a + b)
//fmt.Println(6,3,4,5,5,6,76,7,8)
// 完整定义
//var f MyFunc = test3(func() {
// fmt.Println("被传入的函数")
//})
//res := f(10, 19)
//fmt.Println(res)
// 3 可变长参数
//test4(1 2 3 4 5)
// 4 defer 延迟调用, 当前函数所有代码都执行完了,再执行defer的内容,先注册,后调用 ,先写的defer后来执行
//var a = 10
//defer func(i int) {
// fmt.Println(i)
// fmt.Println("我很帅")
//
//}(a)
defer fmt.Println("我很帅")
defer fmt.Println("我很帅222")
//a = 99
fmt.Println("我不觉得")
fmt.Println("我也不觉得")
}
go传入函数:
可见函数类型的完整定义比较麻烦,所以我们可以进行类型重命名,简化代码,这需要使用type关键字:
类型不匹配的情况,需要加等号,让两个类型之间可以计算:
即如果type Myint int 相当于新定义了一个类型,新类型比如Myint是不能和int做运算的。而使用等号的方式只相当于取别名。
println是有返回值的:
可变长参数:
结果a会返回一个切片类型:
defer关键字:
defer的意思是延迟调用。
使用defer:
对函数使用defer:
闭包函数使用defer:
值传递的情况:
闭包函数是多了一种传参的方式,也就是引用传参。
defer会等待当前函数的所有代码都执行完了,再执行defer的内容。
多个defer的执行顺序:
也就是先写的defer一定后来执行。
defer的作用:
defer可以用于文件相关操作(原理f.open 。defer f.close)。go语言没有try..except,go使用defer做异常捕获。
2 包的使用
# python 模块和包
-模块是一个py文件
-包是一个文件夹 有 __init__
# go 包 ---》包是在一个文件夹下,这个文件夹下所有go文件的第一行要声明包
一堆go文件的组合
# 使用步骤
-新建文件夹,写多个go文件,包名必须一致
# 注意点:
'''
// 1 包内部,大写开头,表示导出 变量,函数
// 创建utils,新建文件s1.go
import "fmt"
// 大写表示该函数导出,小写表示隐藏属性
func Add(a,b int)int{
return a+b
}
func test(){
res := Add(4,3)
fmt.Println(res)
}
这个包是执行不了的,需要有main函数。
// 在main包导出
package main
import (
"go_day03/utils"
)
func main(){
// 调用Add
res:= utils.Add(4,5)
fmt.Println(res)
}
// 2 包内部的变量函数,只能定义一次
// 新建s2.go
package utils
func Test1(){
// 同一个包下,使用变量和函数
Add()
}
// 3 包内部的所有东西,在包内部直接使用
在同一个包内,算作同一个作用域。
// 4 包名可以跟文件夹名不一样,但是一个文件夹下只能有一个包
// 修改utils/s1.go文件
package lqz
// 将 utils --> lqz,这样做就需要将文件夹下的所有文件第一行都改成 package lqz。
// 5 导入包,按路径导入,如果不重命名,就是文件夹必须跟包名一样
// 在别的地方导入,就不能这样使用了
import "go_day03/utils"
func main(){
res:= utils.Add(4,5)
fmt.Println(res)
}
// 这样可以使用
import "go_day03/utils"
func main(){
res:= lqz.Add(4,5)
fmt.Println(res)
}
// 推荐写成
import lqz "go_day03/utils"
func main(){
res:= lqz.Add(4,5)
fmt.Println(res)
}
// 或者将包的名字命名为文件夹的名字,就可以直接导入使用。
// 总结:
// 如果文件夹跟包名不一样,要重命名,可以命名成任意的,但是我们叫了包名
import lqz "go_day03/utils"
// 可以命名成任意的
import qqq "go_day03/utils"
// 以后使用包名. 调用即可
// 6 包内的init函数,可以定义多次,只要导入包,就会依次执行init
init可以写多个,不会报错。
只要导入包,所有的init函数都会执行。
同一目录下按照按照文件名排列顺序执行,比如先执行s1.go, 后执行s2.go.
同一文件内init按照从上往下的顺序,依次执行。
// 7 导入包,必须使用,不使用就报错。现在想导入但是不用,只想执行init
import _ "go_day03/utils"
// 8 一个文件夹下可以再建文件夹建新的包,各个文件夹直接没有必然联系,只是文件夹层级关系
在建立新的包。新的go文件:
/utils/lqz/s1.go
package lqz
import (
"fmt"
"go_day03/utils"
)
func Add(){
fmt.Println("add")
// 调用utils的add
utils.Add()
}
// 9 使用的go mod模式,从1.11后都是这种模式,项目根路径下会有一个go.mod
使用 go get 命令下载第三方包时,Go Modules 会根据 go.mod 文件中记录的依赖关系,从远程仓库下载并存储在 $GOPATH/pkg/mod 目录下。如果已经存在于该目录中,则直接使用已经下载的包。
'''
# 之前有个go path模式,已经弃用了,它的包导入,不是从项目路径下开始导入,而是从go path 的src路径下路径下开始导入
go env // 里面会有一个go path
比如我们之前创建的utils包,如果这个包在go path的src目录,我们在项目中就不需要写了,可以直接导入。
3 gin框架使用
# gosdk内置包,自定义包和第三方包
-内置包 fmt\time包
-gin:开一个web服务
-安装第三方包
# 使用gin
配置代理:七牛云
-局部:goland中:GOPROXY=https://goproxy.cn,direct
-全局:改全局go env
安装: go get github.com/gin-gonic/gin
使用fresh启动热部署:
在项目路径下:
go get github.com/pilu/fresh
go install github.com/pilu/fresh@latest
fresh
写代码:
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default()
r.LoadHTMLGlob("templates/*") // 加载模板文件夹
r.GET("/", func(c *gin.Context) {
c.JSON(200, gin.H{
"code": "100",
"message": "成功",
})
})
r.GET("/index", func(c *gin.Context) {
c.HTML(200, "index.html", gin.H{"name": "lqz", "age": 19}) // 使用模板,传递变量
})
r.Run() // listen and serve on 0.0.0.0:8080
}
// 在index.html文件内使用模板语法
{{.name}} {{.age}}
// 需要注意的是要加上这个点,这跟django模板语法不同。
go中只有模块的概念,没有包的概念。
包名建议叫文件夹的名字,但是可以不是。
函数、变量需要导出才能在外部使用,大写字母开头表示导出。java中就是共有和私有函数。
同一个包内,变量、函数不能重复定义。
同一个包内,使用变量和函数可以直接使用,无需导入。
一个包可能是有多个Go文件写的。
导入包,按照路径导入,如果不重命名,文件夹名必须和包名一致
如果文件夹和包名不一致,需要对包重命名。可以命名成任意的,但是我们叫了包名。
包内的init函数可以定义多次,只要导入包就会依次执行init.
我想导入包但是不使用,只想让包执行init函数;
第三方包:
go语言没有包仓库,大家的代码就放在github上面。
go设置代理:
当前项目局部设置代理:
也可以全局修改:
go env -w GOPROXY=https://goproxy.cn,direct
,
gin依赖了一些,go官网的一些模块,不仅仅是去github下载项目,所以需要一些代理,配置七牛云代理,让gin去七牛云下。
4 if-else
// 基本格式
if 条件{
}else if 条件{
}else{
}
// 条件代码块,必须放bool类型
package main
import (
"fmt"
)
// if-else
func main() {
score := 55
if score >= 90 {
fmt.Println("优秀")
} else if score >= 80 && score < 90 {
fmt.Println("良好")
} else if score >= 60 {
fmt.Println("及格")
} else {
fmt.Println("不及格")
}
}
5 循环
# 不同语言的循环方式
# python
while循环, for循环
# go
for循环
# java
while循环 , for循环 , do while循环
# go的for循环能实现while循环的功能
go的for循环实现while循环的功能:
package main
// 循环
func main() {
// 1 基本语法 for关键字 定义变量i=0;i<10;i++{} 三部分都可以省略,但是一般会保留第二部分,第二部分是条件
//// 2 循环打印0--9
//for i := 0; i < 10; i++ {
// fmt.Println(i)
//}
//fmt.Println(i) // i的作用域范围只在for内部有效
// 3 循环打印0--9 省略掉第一部分 分号不能省
//i := 0
//for ; i < 10; i++ {
// fmt.Println(i)
//}
//fmt.Println(i) // 10
// 4 循环打印0--9 第三部分 分号不能省
//for i := 0; i < 10; {
// fmt.Println(i)
// i++
//}
// 5 循环打印0--9 省略第一部分和第三部分 分号能省略
//i := 0
//for i < 10 {
// fmt.Println(i)
// i++
//}
// 6 for 条件 {} while 循环
//for true {
// fmt.Println("llll")
//}
// 死循环
//for {
// fmt.Println("llll")
//}
// 7 上面是基于索引的循环,这个案例是基于迭代的
s := "lqz国中"
//for i, v := range s {
// fmt.Println(i) // 字符串的索引
// fmt.Println(string(v)) // 字符串的值,输出的是该字符在Unicode编码对应的数字,比如汉字‘我’,对应的是25105
//}
// 请注意len获取的是字节长度,lqz占一个字节,每个汉字占三个字节。
//for i := 0; i < len(s); i++ {
// fmt.Println(string(s[i]))
// 汉字要三个字节才能正常解码,这里相当于将汉字的第一个字节拿来解码,依次解码,所以解码不出原汉字,乱码了。
//}
nums := [4]int{25105, 21916, 27426, 20320}
for _, v := range nums {
fmt.Print(string(v))
} // 运行...
}
// 8 break和continue:
for i := 0; i < 10; i++ {
if i == 5 {
break
}
if i == 2 {
fmt.Println("到达2")
continue
}
}
range是基于迭代的循环:
range循环字符串是按照字符进行循环、如果用len循环字符串是按照字节(有坑)。
6 switch
switch 是一个条件语句,用于将表达式的值与可能匹配的选项列表进行比较,并根据匹配情况执行相应的代码块,优雅的替换掉else-if
package main
import "fmt"
func main() {
// 1 switch 基本使用
//score := 90
//switch score { // 来判断score
//case 90:
// fmt.Println("我是90")
//case 80:
// fmt.Println("我是80")
//case 70:
// fmt.Println("我是70")
//}
//// 2 default 的使用
//score := 99
//switch score {
//case 90:
// fmt.Println("我是90")
//case 80:
// fmt.Println("我是80")
//case 70:
// fmt.Println("我是70")
//default:
// fmt.Println("不知道")
//}
// 3 多表达式判断
//score := 66
//switch score {
//case 90, 91, 92, 98, 99:
// fmt.Println("我是90")
//case 80, 88:
// fmt.Println("我是80")
//case 70:
// fmt.Println("我是70")
//default:
// fmt.Println("不知道")
//}
// 4 无表达式
//score := 66
//switch {
//case score > 90:
// fmt.Println("我是90")
//case score > 80 && score < 90:
// fmt.Println("我是80")
//case score >= 60:
// fmt.Println("大于60")
//default:
// fmt.Println("不知道")
//}
//5 Fallthrough 默认情况下,每个条件之间完,默认加break,但是也不用加,其他语言要加,其他语言去掉break,会无条件执行下一个case
// 要无条件执行下一个case,需要使用fallthrough
score := 99
switch {
case score > 90:
fmt.Println("我是90")
case score > 80 && score < 90:
fmt.Println("我是80")
fallthrough // 无条件执行下一个case
case score >= 60 && score <= 80:
fmt.Println("大于60")
fallthrough
default:
fmt.Println("不知道")
}
}
只要走到fallthrough,可以无条件执行下一个case。
7 数组
// 定义:
数组是同一类型元素的集合。例如,整数集合 5,8,9,79,76 形成一个数组。
// Go 语言中不允许混合不同类型的元素,例如包含字符串和整数的数组。而python可以。
// 数组是连续存储,存储同一个类型的数据结构
// python 写个单链表
// 定义一个数组:
var a [3]int // 长度为3,int类型
// 使用
a[0] = 99
如下是定义了长度为3,int类型的数组:每个格子,都只能放int类型。不允许存储不同类型元素。
格的大小不一样是不行的,上图这种是错误的。
python的列表,里面放的是地址,地址可以指向任何数据类型,数字,字符串,字典...
深浅拷贝:
Cpython源代码里面有列表字典底层存储。
需要给出类型,不然无法类型推导。
内存溢出,我的应用程序取出别的程序内存存储的东西。
这个数组是直接从内存地址取东西。
go数组一旦定义,大小是不能改变的,类型也不能改变。而列表是可以追加值的,python append会做扩容:
也就是产生新的数组,然后变量指向新的数组。
链表:
数组是连续存储,链表不是连续存储:
链表修改值的时间复杂度是On
数组删除值的时间复杂度是On
如何判断链表有没有环?
也就是链表中间打结。