Go入门基础03
函数高级
给类型重命名:
函数的参数和返回值都是类型的一部分,函数可以赋值给一个变量
1. 例:type MyFunc func(int, int) int
MyFunc就是我们将func(int, int) int重新定义了一下类型,以后在定义函数类型的是时候直接使用MyFunc就是代替了func(int, int) int
2.例:type Myint =int
Myint只是我们将int赋值给了Myint,并不是重新定义
在之后的使用中,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
}
}
函数可变长参数
在go语言中函数时可以使用可变长参数来接收不定数量的参数。可变长参数由省略号 ...
和类型名组成,例如:‘ ...int
’表示一个接受任意数量的int
类型参数的可变长采纳数。
- 函数使用可变长参数时,可以像普通函数一样使用他们。可变长采纳数实际上就是一个切片,函数可以通过循环遍历切片来处理他们。
举个例子:
func sum(num ..int)int{
total :=0
for _,num :=range nums{
total +=num
}
return total
}
func mani(){
fmt.Println(sum(1,2,3)) // 输出:6
fmt.Println(sum(4,5,6,7)) //输出 22
fmt.Println("%T",num) // 类型是:int类型切片 []int
}
- 在上面的例子中,‘sum’函数使用可变长参数 ‘nums ...int’来接收任意数量的整数参数。在函数中,使用‘range’循环遍历‘nums’切片,计算他们的总和并返回,在‘main’函数中,我们使用‘sum’函数分别计算传递给他们的两个整数序列的总和。
defer关键字
-
defer
是 Go 语言中的一个关键字,用于注册一个函数调用,在函数返回时才被执行。 -
在 Go 语言中,函数可以通过
defer
关键字注册一个延迟执行的函数调用,这个函数会在包含它的函数返回时执行。使用defer
可以将一些必须要执行的代码(如释放资源、关闭文件等)放在函数结束前执行,以避免遗漏或者因为某些原因而忘记执行这些代码。defer
还可以被用来在出现异常时处理一些清理工作。 -
defer
关键字后面跟着要执行的函数或方法的调用。在函数执行到defer
语句时,被调用的函数并不会立即执行,而是会被放入一个栈中,等到函数执行完毕返回时再执行。如果函数在执行过程中出现了 panic,那么这些被延迟的函数也会在 panic 发生时按照后进先出的顺序执行。
延迟调用:;当前函数所有代码都执行完毕之后,在执行defer的内容,先注册,后调用,如果有两个defer,先写的defer后执行(类似是先进后出)
** 例子:**
func main(){
defer fmt.Println("Hello")
fmt.Println("World")
}
例子总结:
-
上面的代码会先输出 "World",然后再输出 "Hello"。因为
defer
的执行顺序是后进先出,所以 "Hello" 会在 "World" 之后输出。 -
需要注意的是,
defer
中注册的函数或方法的实参是在注册时就会被求值,而不是在函数返回时再被求值。这意味着,如果函数执行过程中发生了变量的改变,那么被注册的函数调用时使用的是当前变量的值,而不是注册时的值。
包的使用
在python中的 模块和包
- 模块是一个py文件
- 包是一个又__init__文件的文件夹
# 在go中 包是在一个文件夹下,这个文件夹下所有Go文件的第一行都需要声明包
--就是一堆Go文件的组合
使用步骤:
- 新建一个文件夹,写多个go文件,包名必须是一致的
PS:
1. 在包的内部,想要被其他的包下使用这个包中的变量,函数,就需要将函数,变量 的'首字母大写'
2.在包的内部的变量,函数名只能命名一次
3.包内部的所有东西只要是在这个包的内部都是可以直接使用的
4.包的名字可以和这个文件名不一样,但是一个文件夹下只能有一个包
5.导入包,是按照路径进行导入的,如果不重命名的话,就是文件夹必须和包名一样
如果文件夹跟包名不一样,就需要重命名,可以命名成任意的,但是一般是使用包名(包内部命名的包名)
import ff "go_03/utils"
也可以是再这个包下重新命名
import qqq "go_03/utils"
以后在使用包名调用即可
6.包内部中有init函数可以被定义多次,只要导入包,就会依次执行 包中的init函数
7.导入包,必须使用,不使用的话就会报错,现在不使用,只想执行init ,就可以
import _ "go_03/utils"
8.一个文件夹下可以再创建一个文件夹,可以在这个文件夹下再创建一个包,但是这两个文件夹是没有联系的,只是简单的文件的层级关系。
9.使用的Go mod模式,从1.11后都是使用这种模式,项目路径下会有一个go.mod文件,不要动它
// 早期有个go path 模式,已经被弃用了,它在导入包的时候,不是从项目路径下开始导入的,而是从go path 的src 路径下路径开始导入的
包的使用:
gin官网:https://pkg.go.dev/github.com/gin-gonic/gin#section-readme
go sdk 内置包,自定义包,第三方包
-gin : 可以开启一个web服务
- 安装第三方包
# 使用 gin
gin我们想要下载就是子啊GitHub上下载,会很慢,而且可能会出现下载不了的现象。所以我们可以使用配置代理: 使用七牛云代理
局部配置: Goland 中:
GOPROXY=https://goproxy.cn,direct
全局配置: 更改全局 go env
安装:go get github.com/gin-gonic/gin
代码例子:
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",
"meassge": "发送成功",
})
})
r.Run()
流程控制语句
if -else
# 语法格式
if 条件{
}else if 条件{
}else{
}
PS:{}必须挨着条件,否则就会报错
循环:
在go中只有for循环
在python中 有while for
java中 有while ,for ,do while
go中能是用for实现while循环功能
案例:
package main
import "fmt"
// 循环
func main() {
// 1.基本语法 循环打印0-9
/* for i := 0; i < 10; i++ {
fmt.Println(i)
}*/
// 2. i 的作用域范围只会在for 内部有效
//i := 0
//for i := 0; i < 10; i++ {
// fmt.Print(i) // 0123456789
//}
//fmt.Println(i) //0
// 3.省略第一部分 分号不能省
//i := 0
//for ; i < 10; i++ {
// fmt.Println(i) // 0,1,2,3,4,5,6,7,8,9
//}
//fmt.Println(i) // 10
// 4.省略第三部分 分号也不能省
//for i := 0; i < 10; {
// fmt.Println(i)
// i++
//}
// 5. 省略第一部分和第三部分,分号也不用写了
//i := 0
//for i < 10 {
// fmt.Println(i)
// i++
//}
// 6.for 条件{} 写成while循环 死循环
//for true {
// fmt.Println("aaaa")
//}
// 7. 死循环
//for {
// fmt.Println("bbbbbb")
//}
// 8.上面都是基于索引的循环,下面这个案例是基于迭代的
s := "lover中国"
//for i, v := range s {
// fmt.Println(i)
// fmt.Println(string(v))
//}
for i := 0; i < len(s); i++ {
fmt.Println(string(s[i])) // 打印的就是乱码
}
}
switch
- 在go语言中,
switch
语句用于根据不同的条件执行不同的代码块。它可以被认为是一个更灵活和可读性更高的if-else
语句。
switch的语句基本语法:
switch expression {
case value1:
// 执行代码块1
case value2:
// 执行代码块2
...
default:
// 执行默认代码块
}
详解:其中,expression
是要被检查的表达式,value1
、value2
等是与表达式进行比较的值。如果expression
的值等于其中一个case
后面跟随的值,那么与该case
匹配的代码块将被执行。如果没有任何一个case
匹配,则执行default
代码块(如果有)。
简单案例:
import "fmt"
func main() {
num := 2
switch num {
case 1:
fmt.Println("这是数字1")
case 2:
fmt.Println("这是数字2")
default:
fmt.Println("不知道是什么数字")
}
}
详解:在上面的示例中,如果num
等于1,那么输出"这是数字1";如果num
等于2,那么输出"这是数字2";如果num
不是1、2,那么输出"不知道是什么数字"。
Switch的多中使用方式:
package main
import "fmt"
func main() {
//num := 2
//switch num {
//case 1:
// fmt.Println("这是数字1")
//case 2:
// fmt.Println("这是数字2")
//default:
// fmt.Println("不知道是什么数字")
//
//}
// 多表达式判断
//num := 38
//switch num {
//case 3, 4, 5:
// fmt.Println("这是数字1")
//case 2, 8, 9, 20:
// fmt.Println("这是数字2")
//default:
// fmt.Println("不知道是什么数字")
//
//}
// 无表达式
//num := 48
//switch {
//case num > 40:
// fmt.Println("数字大于40")
//case num > 60 && num < 40:
// fmt.Println("数字大于60")
//default:
// fmt.Println("不知道是什么数字")
//}
// Fallthrough 在默认的情况下,每个条件之间执行完毕默认会加上break
// 但是也可以不用加,其他的语言是要加的,其他得语言去掉break就会执行
// 下一个 case
//在go 中要无条件执行下一个case ,及需要使用到 fallthrough
num := 38
switch {
case num < 20:
fmt.Println("数字小于20")
case num < 40 || num > 30:
fmt.Println("这是数字小于40大于30")
fallthrough
default:
fmt.Println("不知道是什么数字")
就会打印
// 这是数字小于40大于30
//不知道是什么数字
}
}
数组
- 在go语言中,数组是一种用于存储固定数量的数据结构。数组中的每一个元素都有一个唯一的索引,可以使用这个索引来访问特定的元素。
创建数组:
- 数组是值类型的
-数字,字符串,布尔,数组,都是值类型,是真正直接存数据的
-切片,map,指针 是引用类型的,他们都是指向一个地址,指向了一个人具体的值
- 想要创建一个数组,需要指定数组的长度和元素的类型如下:
var arr[3] int
这就是创建一个包含3个整数的数组
- 也可以使用字面量初始化数组
arr :=[3]int{1,2,3}
访问数组元素
- 想要访问数组元素,可以使用数组的索引。例如。想要访问arr数组的第一个元素,可以使用:
数组是值类型,go语言中函数传参是copy传递,复制一份采纳数,传入,当参数传递,在函数中修改,并不会影响原来的
fmt.Println(arr[0]) // 就会输出1
遍历数组
- 想要遍历数组,可以使用for循环和数组的长度。例如:下面就是遍历arr数组中所有的元素,
for i := 0; i<len(arr); i++{
fmt.Println(arr[i])
}
// 也可以使用range关键字来遍历数组,它会返回数组的索引和值:
for index,value := range arr{
fmt.Println(index,value)
}
数组的长度
- 想要获得数组的长度,可以使用len函数。例如:
fmt.Println(len(srr)) // 输出3
数组切片
- 数组可以使用切片来访问一部分的元素,切片是一个指向数组的指针,它包含一个开始索引和一个结束索引。
- 如下代码创建了一个包含arr数组的前两个元素的切片:
slice :=arr[0:2]
// 也可以使用省略来简化切片操作,如下
slice := arr[:2]
数组的修改
- 可以通过索引来修改数组的元素,如下将数组的第一个元素修改为10,
arr[0]=10 // 输出 [10,0,0]
多维数组
-
多维数组是指数组中包含其他的数组的数组。他们通常用于表示二维表格和矩阵等复杂数据结构,在go语言中,可以创建任意维度的数组,只需要指定每一维度的长度即可。
-
go语言可以创建多维数组,如下:
创建一个包含3行4列的二维数组:
var matrix[3][4]int
// 这个数组包含3个子数组,每个子数组包含4个整数。要访问数组中的元素,需要使用两个索引,一个表示行数,另一个表示示例数,如下:
value :=matrix[1][2]
//多维数组的初始化可以使用嵌套的花括号:如下:
matrix :=[3][4]int{
{1,2,3,4},
{5,6,7,8},
{9,10,11,12},
}
// 这个数组的每一个子数组包含4个整数,分别是初始化为
1、2、3、4,5、6、7、8和9、10、11、12。
//在go语言中,多维数组的每个维度都可以单独遍历,可以使用嵌套的for循环来遍历多维数组的每一个元素,如下遍历所有matrix数组的所有元素
for i := 0; i<len(matrix);i++{
for j:=0; j<len(matrix[i]);j++{
fmt.Println(matrix[i][j])
}
}
代码:
package main
import "fmt"
func main() {
// 2.数组长度
// 定义数组
//var a [3]int = [3]int{3, 4, 5}
//a:= [3]int{3,4,5}
// 初始化几位数,它的长度就是多少
//a := [...]int{1, 2, 3, 4, 5, 6}
//fmt.Println(len(a)) // 6
//fmt.Printf("%T\n", a) //[6]int
// 3. 循环打印数组
//a := [...]int{1, 2, 3, 4, 5, 6}
// 基于索引打印
//for i := 0; i < len(a); i++ {
// fmt.Println(a[i])
//}
// 基于迭代的 range
//for i, v := range a {
// fmt.Println(i) // 打印的是索引
// fmt.Println(v) // 输出的是值 不想要索引,就用_,来代替
//}
// 4.多维数组
//var a [3][4]int // 这是没有初始化的数组
//var matrix [3][4]int
//var a [3][4]int = [3][4]int{
// {1, 2, 3, 4},
// {5, 6, 7, 8},
// {9, 10, 11, 12},
//}
//fmt.Println(a)
// 循环多维数组
//for i := 0; i < len(a); i++ {
// for j := 0; j < len(a[i]); j++ {
// fmt.Println(a[i][j])
//
// }
//}
// range循环
//for _, vle := range a {
// for _, ve := range vle {
// fmt.Println(ve)
// }
//}
// 5.数组定义并赋初始值,将数组中第99赋值为1,其他都是0
var a [100]int8 = [100]int8{98: 1}
fmt.Println(a)
}
func test(a [3]int) {
a[0] = 9
fmt.Println(a) // [9 4 5]
}
ps:多维数组在一些算法和数据处理场景中非常有用,可以有效的表示和操作数据结构。
切片 (slice)
- 切片是由数组建立的一种方便,灵活且功能强大的包装。切片本身不拥有任何数据。他们只是对现在有数据的【引用】
-本身不存储数据,是对底层数组的引用
-切片的数据结构
{
len:2
cap:4
point:0x3456
}
package main
import (
"fmt"
)
func main() {
// 1.切片的定义 [] 里面不放入任何东西,就是切片类型,如果里面放了数字,就是数组类型
//var a []int // 这只是定义了一个切片,并没有初始化,是引用类型, 没有初始化打印的结果是nil类型
//fmt.Println(a[0]) // 没有初始化,使用就会报错
// 定义并初始化
//var b [10]int // 这是值类型 ,会有默认值
//var a []int = b[:] // 表示a这个切片,对数组进行了引用,引用了从0到最后
//fmt.Println(a)
//fmt.Println(a[0]) // 输出0
// 2. 切片取值
//fmt.Println(a[8]) //0
//切片的修改会影响到底层的数组,数组修改也会影响到切片
//b[0] = 11
//a[0] = 11
//fmt.Println(b)
//fmt.Println(a)
// 3.基于数组,获得切片
//var b [10]int = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
//var a []int = b[0:3] // 表示a这个切片,对数组进行引用,引用从0-3
//fmt.Println(a) // [1,2,3]
//a[0] = 99
//fmt.Println(a) //[99 2 3]
//fmt.Println(b) //[99 2 3 4 5 6 7 8 9 0]
//var a []int = b[6:9]
//fmt.Println(a) //[7 8 9]
//a[0] = 99
//fmt.Println(a) // [99 8 9]
//fmt.Println(b) // [1 2 3 4 5 6 99 8 9 0]
//b[0] = 88
//fmt.Println(b) //[88 2 3 4 5 6 99 8 9 0]
// 尾部追加
//a = append(a, 66)
//fmt.Println(a) // [7 8 9 66]
//fmt.Println(b) // [1 2 3 4 5 6 7 8 9 66]
// 切片的长度和容量
//fmt.Println(len(a)) // 3
//fmt.Println(cap(a)) // 4 容量就是能存的数是4,这个不是取决于底层数组的大小,而是取决于切片切数组的位置
// 切片如果追加值超过了底层数组的长度,就会自动扩容。(翻倍扩容)容量在1024内
//a = append(a, 11, 22, 33, 44, 55, 77)
//
//fmt.Println(len(a)) // 9
//fmt.Println(a) // [7 8 9 11 22 33 44 55 77]
//fmt.Println(cap(a)) // 10
//
//// 这时候该底层数组的值 数组和切片就没有联系了,不会影响到切片的值
//b[9] = 999
//fmt.Println(b) // [1 2 3 4 5 6 7 8 9 999]
//fmt.Println(a) // [7 8 9 11 22 33 44 55 77]
//
// 8.切片定义并初始化,使用make初始化
//var a[]int // 是nil 没有初始化
//var a []int = make([]int, 3, 4) //初始化,长度是3,容量是4
//fmt.Println(a) //[0 0 0]
//fmt.Println(len(a)) // 3
//fmt.Println(cap(a)) // 4
//var a []int = make([]int, 0, 4) // 长度是0,容量是4
//fmt.Println(a) //[]
//fmt.Println(len(a)) // 0
//fmt.Println(cap(a)) // 4
//a = append(a, 2)
//fmt.Println(a) // 2
//fmt.Println(len(a)) //1
//fmt.Println(cap(a)) // 4
//9.切片的参数传递,是引用类型,函数中修改值,会影响原来的
//var a []int = make([]int, 3, 5)
//a[1] = 99
//test1(a)
//fmt.Println(a) // [88 99 0]
// 10 多维切片 切片定义并初始化
//var a [][]int = [][]int{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}
//fmt.Println(a) // [[1 2 3] [4 5 6] [7 8 9]]
var a [][]string = make([][]string, 3, 3)
fmt.Println("-----", a[2]) // 会打印一个空字符
a[2] = make([]string, 3, 3) // []
fmt.Println(a[2][0])
}
func test1(a []int) {
a[0] = 99
fmt.Println(a, 33) //[99 99 0] 33
a[0] = 88
fmt.Println(a) // [88 99 0]
}
可变长参数
package main
import "fmt"
func main() {
var a []string = []string{"andy", "xxx", "hhh"}
test3(a...) // 相当于是打散传入进去
}
func test3(a ...string) {
fmt.Println(a) //[andy xxx]
fmt.Println(cap(a)) // 3
fmt.Printf("%T", a) // []string
}
maps
package main
import "fmt"
func main() {
// 1. 存储是以K:V的形式 定义map
//var usreInfo map[string]string // 没有初始化
//fmt.Println(usreInfo) // map[]
//
//if usreInfo == nil {
// fmt.Println("这是一个nil") // 会打印出来
//}
// 2 map 初始化: 一
//var userInfo map[int]string = map[int]string{1: "lqz", 3: "pyy"}
//fmt.Println(userInfo) // map[1:lqz 3:pyy]
//if userInfo == nil {
// fmt.Println("这是一个nil") // 不会打印
//}
// make 初始化
//var userInfo map[int]string = make(map[int]string)
//fmt.Println(userInfo) //map[]
// 3.初始化后才能取值赋值
//var userInfo map[int]string=make(map[int]string)
//var usreInfo map[int]string
//fmt.Println(usreInfo[1])
//usreInfo[1] = "PPP"
//fmt.Println(usreInfo)
// 以后所有的引用类型,都需要初始化才能用,值类型不需要,有默认值
// 4. 取值赋值
var userInfo map[string]string = make(map[string]string)
userInfo["age"] = "10"
userInfo["name"] = "小明"
//fmt.Println(userInfo) // map[age:10 name:小明]
//fmt.Println(userInfo["age"]) // 10
// 如果是取不存在的值,显示的就是value 值的默认值 按Key取值,能返回一个布尔值,根据布尔值来进行判断
//v, ok := userInfo["name"]
//fmt.Println(v) // 小明
//fmt.Println(ok) // true
//if v, ok := userInfo["name"]; ok {
// fmt.Println(v) // 小明
//}
// 删除map元素
//delete(userInfo, "name")
//fmt.Println(userInfo) //map[age:10]
// map 的长度
//fmt.Println(len(userInfo)) //2
// 引用类型
fmt.Println(len(userInfo)) // 2
test4(userInfo)
fmt.Println(userInfo) // map[age:10 name:pyy]
}
func test4(u map[string]string) {
u["name"] = "pyy"
fmt.Println(u) // map[age:10 name:pyy]
}