【GoWeb编程笔记】第1章 Go基础入门 -4

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是一个stringslice,可通过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)用匿名函数作为回调函数。

回调函数简称“回调”(Callbackcall 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. deferreturn的执行顺序

代码 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错误时,在程序出现宕机之前被执行,依次来恢复程序。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值