4. map类型
(1)map定义。
Go语言中map是一种特殊的数据类型——一种“元素对”(pair)的无序集合。元素对包含一个key(索引)和一个value(值),所以这个结构也被成为“关联数组”或“字典”。这是一种能够快速寻找值的理想结构:给定了key,就可以训练找到对应的value。
map是引用类型,可以使用如下方式声明:
var name map[key_type] value_type
在声明时不需要知道map的长度,因为map是可以动态增长的。未初始化的map的值是nil。使用函数len()可以获取map中元素对的数目。
var literalMap map[string]string
var assignedMap map[string]string
literalMap = map[string]string{"first":"go", "second":"web"}
createMap := make(map[string]float32)
assignedMap = literalMap
createMap["k1"] = 99
createMap["k2"] = 199
assignedMap["second"] = "program"
fmt.Printf("Map literal at \"first\" is: %s\n", literalMap["first"])
fmt.Printf("Map created at \"k2\" is: %f\n", createdMap["k2"])
fmt.Printf("Map assigned at \"second\" is: %s\n", literalMap["second"])
fmt.Printf("Map literal at \"third\" is: %s\n", literalMap["third"])
运行以上代码,输出如下:
Map literal at "first" is: go
Map created at "k2" is: 199.000000
Map assigned at "second" is: program
Map literal at "third" is:
(2)map容量。
和数组不同,map可以根据新增的元素对来动态地伸缩,因此它不存在固定长度或最大限制。但也可以选择标明map的初始容量capacity,格式如下:
make(map[key_type]value_type, cap)
例如:
make(map[string]float32, 100)
对于大的map或者会快速扩张的map,即使只是大概知道容量,也最好先表明。
achievement := map[string]float32{
"zhangsan":99.5, "xiaoli":88,
"wangwu":96, "lidong":100,
}
(3)用切片作为map的值。
map1 := make(map[int] []int)
map2 := make(map[int] *[]int)
1.5 函数
1.5.1 声明函数
在Go语言中,声明函数的格式如下:
func functiong_name([parameter list]) [return_types] {
// 函数体
}
func:函数声明关键字。function_name:函数名称。函数名和参数列表一起构成函数签名。parameter list:参数列表,是可选项。参数就像一个占位符。当函数被调用时,可以将值传递给参数,这个值被称为“实际参数”。参数列表指定的参数类型、顺序及参数个数。return_types:包含返回类型的返回值,是可选项。如果函数需要返回一列值,则该项值的数据类型是返回值。如果有些功能不需要返回值,则return_types可以为空。- 函数体:函数定义的代码集合。
以下为min()函数的示例。向该函数传入整型数组参数arr,返回数组参数的最小值:
// 获取整型数组中的最小值
func min(arr []int) (m int) {
m = arr[0]
for _, v := range arr {
if v < m{
m = v
}
}
return
}
在创建函数时,定义了函数有什么功能。通过调用函数向函数传递参数,可以获取函数的返回值。函数的使用示例如下:
代码 chapter1/1.5-func2.go 函数返回值示例
package main
import "fmt"
func main(){
array := []int{6, 7, 8} // 定义局部变量
var ret int
ret = min(array) // 调用函数并返回最小值
fmt.Printf("最小值是:%d\n", ret)
}
// 获取整型数组中的最小值
func min(arr []int) (m int) {
m = arr[0]
for _, v := range arr {
if v < m{
m = v
}
}
return
}
Go语言函数还可以返回多个值,例如下面示例:
代码 chapter1/1.5-func3.go 函数返回多个值的示例
package main
import "fmt"
func compute(x, y int) (int, int){
return x+y, x*y
}
func main(){
a, b := computer(6, 8)
fmt.Println(a, b)
}
以上示例的执行结果为:
14 48
如果要按函数头声明的顺序返回值,则return语句后面的表达式可以为空。如果return语句后面不为空,则按return语句后面的表达式的顺序返回值,而非按函数头声明的顺序,见下面示例。
代码 chapter1/1.5-func-return.go return语句的返回示例
package main
func change(a, b, int) (x, y, int) {
x = a + 100
y = b + 100
return // 返回:101, 102
// return x, y // 返回:101, 102
// return y, x // 返回:102, 101
}
func main(){
a := 1
b := 2
c, d := change(a, b)
println(c, d)
}
1.5.2 函数参数
1. 参数的使用
函数可以有1个或多个参数。如果函数使用参数,则该参数被称为函数的形参。形参就像定义在函数体内的局部变量。
- 形参:在定义函数时,用于接收外部传入的数据被称为形式参数,简称形参。
- 实参:在调用函数时,传给形参的实际的数据被称为实际参数,简称实参。
函数参数调用需遵守如下形式:
- 函数名称必须匹配;
- 实参与形参必须一一对应:顺序、个数、类型。
2. 可变参数
Go函数支持可变参数(简称“变参”)。接受变参的函数有着不定数量的参数。定义可接收变参的函数形式如下:
func myFunc(arg ...string){
// ...
}
“arg ...string”告诉Go这个函数可接受不定数量的参数。注意,这些参数的类型全部是string。在相应的函数体中,变量arg是一个string的slice,可通过for-range语句遍历:
for _, v := range arg {
fmt.Printf("And the string is: %s \n", v)
}
3. 参数传递
调用函数,可以通过如下两种方式来传递参数。
(1)值传递。
值传递是指,在调用函数时将实际参数复制一份传递到函数中。在这样函数中,对参数进行修改,不会影响实际参数的值。
默认情况下,Go语言使用的是值传递,即在调用过程中不会影响实际参数的值。
以下代码定义了exchange()函数:
/* 定义相互交换之的函数 */
func exchange(x, y int) int {
var tmp int
tmp = x // 将x值赋给tmp
x = y // 将y值赋给x
y = tmp // 将tmp值赋值给y
return tmp
}
接下来用值传递来调用exchange()函数。
代码 chapter1/1.5-func5.go 值传递示例
package main
import "fmt"
func main(){
// 定义局部变量
num1 := 6
num2 := 8
fmt.Printf("交换前num1的值为:%d\n", num1)
fmt.Printf("交换前num2的值为:%d\n", num2)
// 通过调用函数来交换值
exchange(num1, num2)
fmt.Printf("交换前num1的值为:%d\n", num1)
fmt.Printf("交换前num2的值为:%d\n", num2)
}
/* 定义相互交换之的函数 */
func exchange(x, y int) int {
var tmp int
tmp = x // 将x值赋给tmp
x = y // 将y值赋给x
y = tmp // 将tmp值赋值给y
return tmp
}
以上代码的运行结果如下:
交换前num1的值为:6
交换前num2的值为:8
交换后num1的值为:6
交换后num2的值为:8
因为上述程序中使用的是值传递,所以两个值并没有实现交换,可以使用引用传递来实现交换。
(2)引用传递。
引用传递是指,在调用函数时,将参数的地址传递到函数中。那么,在函数中对参数所进行的修改,将修改实际参数的值。
以下是交换函数exchange()使用了引用传递:
/* 定义相互交换之的函数 */
func exchange(x *int, y *int) int {
var tmp int
tmp = *x // 将*x值赋给tmp
*x = *y // 将*y值赋给*x
*y = tmp // 将tmp值赋值给*y
return tmp
}
下面通过使用引用传递来调用exchange()函数。
代码 chapter1/1.5-func5-pointer.go 用引用传递来调用exchange()函数的示例
package main
import "fmt"
func main(){
// 定义局部变量
num1 := 6
num2 := 8
fmt.Printf("交换前num1的值为:%d\n", num1)
fmt.Printf("交换前num2的值为:%d\n", num2)
// 通过调用函数来交换值
exchange(&num1, &num2)
fmt.Printf("交换前num1的值为:%d\n", num1)
fmt.Printf("交换前num2的值为:%d\n", num2)
}
/* 定义相互交换之的函数 */
func exchange(x *int, y *int) int {
var tmp int
tmp = *x // 将*x值赋给tmp
*x = *y // 将*y值赋给*x
*y = tmp // 将tmp值赋值给*y
return tmp
}
以上代码的运行结果如下:
交换前num1的值为:6
交换前num2的值为:8
交换后num1的值为:8
交换后num2的值为:6
默认情况下,Go语言使用的是值传递,即在调用过程中不会影响实际参数的值。
1.5.3 匿名函数
匿名函数也被称为“闭包”,是指一类无须定义标识符(函数名)的函数或子程序。匿名函数没有函数名,只有函数体。函数可以作为一种被赋值给函数类型的变量;匿名函数往往以变量方式被传递。
1. 匿名函数的定义
匿名函数可以被理解为没有名字的普通函数,其定义如下:
func (参数列表) (返回值列表) {
// 函数体
}
匿名函数是一个“内联”语句或表达式。匿名函数的优越性在于:可以直接使用函数内的变量,不必声明。
在以下示例中创建了匿名函数func(a int)。
代码 chapter1/1.5-func7.go 匿名函数的使用示例
package main
import "fmt"
func main() {
x, y := 6, 8
defer func(a int) {
fmt.Println("defer x, y = ", a, y) // y为闭包引用
}(x)
x += 10
y += 100
fmt.Println(x, y)
}
以上代码的执行结果为:
16 108
defer x, y = 6 108
2. 匿名函数的调用
(1)在定义时调用匿名函数。
匿名函数可以在声明后直接调用,也可以直接声明并调用,见下方示例。
代码 chapter1/1.5-func-closeure1.go 匿名函数的使用示例
package main
import "fmt"
func main() {
// 定义匿名函数并赋值给f变量
f := func(data int) {
fmt.Println("hi, this is a closure", data)
}
// 此时f变量的类型是func(),可以直接调用
f(6)
// 直接声明并调用
func(data int) {
fmt.Println("hi, this is a closure, directly", data)
}(8)
}
// hi, this is a closure 6
// hi, this is a closure, directly 8
匿名函数的用途非常广泛。匿名函数本身是一种值,可以方便地在各种容器中实现回调函数和操作封装。
(2)用匿名函数作为回调函数。
回调函数简称“回调”(Callback即call then back,被主函数调用运算后会返回主函数),是指通过函数参数传递到其他代码的某一块可执行代码的引用。
匿名函数作为回调函数来使用,在Go语言的系统包中是很常见的。在strings包中就有这种实现:
func TrimFunc(s string, f func(rune) bool) string{
return TrimRightFunc(TrimLeftFunc(s, f), f)
}
可以使用匿名函数体作为参数,来实现对切片中的元素的遍历操作。示例如下:
代码 chapter1/1.5-func-closure2.go 用匿名函数作为回调函数的示例
package main
import "fmt"
// 遍历切片中每个元素,通过给定的函数访问元素
func visitPrint(list []int, func(int)) {
for _, value := range list{
f(value)
}
}
func main(){
sli := []int{1, 6. 8}
// 使用匿名函数打印切片的内容
vistPrint(sli, func(value int){
fmt.Println(value)
})
}
以上代码的运行结果如下:
1
6
8
1.5.4 defer延迟语句
1. 什么是defer语句
defer语句主要用在函数当中,用来在函数结束(return或者panic异常导致结束)之前执行某个动作,是一个函数结束前最后执行的动作。
在Go语言一个函数中,defer语句的执行逻辑如下:
(1)当程序执行到一个defer时,不会立即执行defer后的语句,而是将defer后的语句压入一个专门存储defer语句的栈中,然后继续执行函数下一个语句。
(2)当函数执行完毕后,在从defer栈中依次从栈顶取出语句执行(注:先进去的最后执行,最后进去的最先执行)。
(3)在defer将语句放入栈时,也会将相关的值复制进入栈中。
使用示例代码如下:
代码 chapter1/1.5-func-defer.go 多个defer反序的示例
package main
import "fmt"
func main() {
deferCall()
}
func deferCall() {
defer func1()
defer func2()
defer func3()
}
func func1(){
fmt.Println("A")
}
func func2(){
fmt.Println("B")
}
func func3(){
fmt.Println("C")
}
以上代码的运行结果如下:
C
B
A
2. defer与return的执行顺序
代码 chapter1/1.5-func-defer-return.go defer与return的执行顺序示例
package main
import "fmt"
var name string = "go"
func myfunc() string {
defer func() {
name = "python"
}()
fmt.Printf("myfunc()函数里的name:%s\n", name)
return name
}
func main() {
myname := myfunc()
fmt.Printf("main()函数里的name:%s\n", name)
fmt.Println("main()函数里的myname: ", myname)
}
以上代码的运行结果如下:
myfunc()函数里的name:go
main()函数里的name:python
main()函数里的myname: go
3. defer常用应用场景。
(1)关闭资源。
在创建资源(比如数据库连接、文件句柄、锁等)后,需要释放掉资源内存,避免占用内存、系统资源。可以在打开资源语句的下一行,直接用defer语句提前把关闭资源的操作注册了,这样就会减少程序员忘写关闭资源的请况。
(2)和recover()函数一起使用。
当程序出现宕机或者遇到panic错误时,recover()函数可以恢复执行,而且不会报告宕机错误。之前说过,defer不但可以在return返回前调用,也可以在程序宕机显示panic错误时,在程序出现宕机之前被执行,依次来恢复程序。

1444

被折叠的 条评论
为什么被折叠?



