Go语言 函数 切片 map

10

Go语言函数 切片 map

author: fengclchn@outlook.com

date: 2020/08/04


1.函数

Go语言拥有三种类型的函数:

  • 普通命名函数

  • 匿名函数(闭包)或lambda函数

  • 方法(类成员函数)

Go语言函数特色

  • 多返回值与comma-ok模式

  • 函数可以作为其他函数的参数传递,然后在其他函数中调用(回调)

  • defer语句延迟执行

  • 不需要前向声明

普通函数

 //辗转相除法求最大公约数和最小公倍数
 //divide(左操作数,右操作数)(最大公约数,最小公倍数)
 func divide(x, y int) (int, int) {
     s1, s2 := x, y
     for t := x % y; t != 0; t = x % y {
         x = y
         y = t
     }
     return y, s1 * s2 / y
 }
  • 函数声明和定义一般形式如下

 func Name_of_Function(/*形参列表*/)(/*返回值类型列表*/){
 /*函数体*/
 }

注意 函数也是块元素,前花括号需要跟在函数声明后

  • Go语言支持对返回值进行命名,此时在return语句后的返回值列表可以省略

    以下两段代码是等价的

 //整除及取余
 func divide(a, b int) (int, int) {//纯类型返回值
     quotient := a / b
     remainder := a % b
     return quotient, remainder//返回值要和返回值类型列表一一对应
 }
 //整除及取余
 func divide(a, b int) (quotient , remainder int) {//带有变量名的返回值
     quotient = a / b
     remainder = a % b
     return                         //返回值列表省略
     //return quotient, remainder    //也可以不省略
 }
  • 函数的调用

    Go语言函数的调用格式如下

 返回值变量列表 = 函数名(参数列表)//多返回值使用逗号分隔
  • 来看一个例子

 /*求两个数的最大公约数和最小公倍数*/
 // GCD project main.go
 package main
 ​
 import (
     "fmt"
 )
 ​
 func main() {
     var greatestCommonDivisor int                               //最大公约数
     var leastCommonMultiple int                                 //最小公倍数
     //求12和15的最大公约数最小公倍数
     greatestCommonDivisor, leastCommonMultiple = divide(12, 15) //调用函数divide 多返回值用逗号分隔
     fmt.Println(greatestCommonDivisor, leastCommonMultiple)
 }
 ​
 //Go语言函数不需要前向声明
 //辗转相除法
 func divide(x, y int) (int, int) {//纯类型返回值
     s1, s2 := x, y
     for t := x % y; t != 0; t = x % y {
         x = y
         y = t
     }
     return y, s1 * s2 / y//返回值要和返回值类型列表一一对应
 }
 ​

funcGCD

  • 多返回值类型促进了comma-ok模式

 // comma-ok project main.go
 package main
 ​
 import (
     "fmt"
 )
 ​
 func main() {
     a := [3]int{1, 2, 3}
     if value, ok := get(2, a); ok { //此处是获取数组下标为2的项
         fmt.Println(value, ok)
     }
 }
 ​
 // comma-ok模式
 //获取数组下标为i的项
 func get(i int, arr [3]int) (int, bool) { //获取数组下标为i的项
     if i < 0 || i > len(arr)-1 { //len获取集合长度(对切片来说是有效数据长度)
         return 0, false //如果下标参数在数组之外,则返回false,同时返回值0
     } else {
         return arr[i], true //如果下标参数合法,则返回true,同时返回该项值
     }
 }
 ​

comma-ok

  • comma-ok断言:有可能失败的函数可以返回第二个布尔结果来表示成功与否。错误时,作为替代,也可以返回一个错误对象。

  • comma-ok断言的一般语法如下

 /*comma-ok*/
 if result, ok:=someFunc(); ok { //断言正确(ok为true)则执行
     /*Do something with result*/ 
 }

匿名函数

  • 匿名函数:在需要使用函数时再定义函数,没有函数名只有函数体

  • 可以作为一种类型被赋给函数类型的变量

  • 匿名函数往往以变量方式传递(类似C语言的回调函数)

  • 注意 匿名函数不能作为顶级函数,而只能放在其他函数的函数体中,也就是说它必须有一个外层函数

  • 定义方式如下:

 func(/*参数列表*/)(/*返回参数列表*/){
     /*函数体*/
 }
  • 来看一个例子

 // anonymousFunc project main.go
 package main
 ​
 import (
     "fmt"
 )
 ​
 func main() {
     //声明一个匿名函数,并直接赋给函数类型变量f
     f := func(a, b int) int {
         return a + b
     } //f为匿名函数,是数据类型
     result := f(1, 3) //对函数类型变量f进行调用
     fmt.Println(result)
 ​
     r := func(a, b int) int {
         return a + b
     }(2, 6) //声明的同时直接调用匿名函数 r此时为整型
     fmt.Println(r)
 }
 ​

anonymousFunc

闭包
  • 匿名函数被称为闭包(Closure),原因在于匿名函数通过某种方式使其可见范围超出了其定义的范围

 // closure project main.go
 package main
 ​
 import (
     "fmt"
 )
 ​
 // 提供一个值, 每次调用函数会指定对值进行累加
 func Accumulate(value int) func() int {
     return func() int { // 返回一个闭包
         value++      // 累加
         return value // 返回一个累加值
     }
 }
 ​
 func main() {
     accumulator := Accumulate(1)      // 创建一个累加器, 初始值为1
     fmt.Println(accumulator())        // 累加1并打印
     fmt.Println(accumulator())        // 再次累加
     fmt.Printf("%p\n", &accumulator)  // 打印累加器的函数地址
     accumulator2 := Accumulate(10)    // 创建一个累加器, 初始值为10
     fmt.Println(accumulator2())       // 累加1并打印
     fmt.Printf("%p\n", &accumulator2) // 打印累加器的函数地址
     // accumulator 与 accumulator2 输出的函数地址不同,因此它们是两个不同的闭包实例
 }
 ​

closure

  • 再看一个例子

 // closure project main.go
 package main
 ​
 import (
     "fmt"
 )
 ​
 func main() {
     fmt.Println(makeAdder(makeAdder(5)(1))(36))
     //makeAdder(5)与makeAdder(makeAdder(5)(1))也是函数
     //makeAdder(5)()初始值为5的一个函数,此处加1
     //makeAdder(makeAdder(5)(1))()初始值为5+1的一个函数,此处加36
 }
 ​
 func makeAdder(x int) func(int) int {
     return func(y int) int {
         return x + y //x的可用范围扩展了
     }
 }
 ​

closure2

回调
  • 函数可以作为其它函数的参数进行传递,然后在其它函数内调用执行,一般称之为回调

 // callback project main.go
 package main
 ​
 import (
     "fmt"
 )
 ​
 func main() {
     callback(1, add) //在其他函数内调用执行
     callback(3, multi)
 }
 ​
 func add(a, b int) {
     fmt.Printf("%d+%d=%d\n", a, b, a+b)
 }
 ​
 func multi(a, b int) {
     fmt.Printf("%d*%d=%d\n", a, b, a*b)
 }
 ​
 func callback(y int, f func(int, int)) { //f为参数是(int,int)的函数
     f(y, 2)
 }
 ​

callback

defer语句
  • 延迟调用语句,无论函数执行是否出错,都确保结束前被调用

  • 用于数据清理工作,保证代码结构清晰,避免遗忘

  • 先顺序执行普通语句,再倒序执行defer语句

 // defer project main.go
 package main
 ​
 import (
     "fmt"
 )
 ​
 func main() {
     fmt.Println("Hello Go!")
     defer fmt.Println("1") //defer语句是从最后的执行至最前的
     defer fmt.Println("2")
     defer fmt.Println("3")
     defer fmt.Println("4")
     fmt.Println("Hello World!") //先顺序执行普通语句,再倒序执行defer语句
 }
 ​

defer

方法(类成员函数)

在面向对象章节里

2.数组

 // array project main.go
 package main
 ​
 import (
     "fmt"
 )
 ​
 func main() {
     var a [4]int                      //元素自动初始化为0
     var b [4]int = [4]int{1, 2, 3, 4} //定义并初始化数组b
     var c = [4]int{5, 6, 7, 8}        //类型自动推断,注意后面不可省略
     //var c [4]int = {5,6,7,8}//错误 注意后面不可省略
     d := [4]int{2, 5}         //未初始化的元素自动初始化为0
     e := [4]int{5, 3: 10}     //可指定索引位置初始化
     f := [...]int{1, 2, 3}    //按初始化值数量确定数组长度
     g := [...]int{10, 3: 100} //上两个结合,支持索引初始化,但数组长度与此有关
     fmt.Println(a)            //可直接输出数组各元素,不必遍历
     fmt.Println(b)
     fmt.Println(c)
     fmt.Println(d)
     fmt.Println(e)
     fmt.Println(f)
     fmt.Println(g)
 }
 ​

array

  • 注意 "…"表示不指定长度,长度由初始化列表决定,这种情况"…"不能省略 ,否则就成了切片

结构体数组

 // array project main.go
 package main
 ​
 import (
     "fmt"
 )
 ​
 //结构体数组
 type user struct {
     name string
     age  byte //同char,8bit
 }
 ​
 func main() {
     d := [2]user{{"Tim", 19}, {"Tom", 20}}
     e := [2]user{
         {"Bob", 17},
         {"Bill", 21}, //如此定义需要加“,”,若是在一行定义不需要
     }
     fmt.Println(d)
     fmt.Printf("%v\n%+v\n%#v\n", d, d, d) //普通输出 键值输出 go语法输出
     fmt.Println(e)
 }
 ​

array2

  • %v 直接输出,即普通输出

  • %+v 键值输出,key : value

  • %#v Go语法输出,包含数据类型

  • byte类型 相当于C语言中的char类型,8bit(0-255)

多维数组

 // array project main.go
 package main
 ​
 import (
     "fmt"
 )
 ​
 //多维数组
 func main() {
     a := [2][2]int{{1, 2}, {3, 4}}
     b := [...][2]int{ //若干行两列,"..."仅可用于第一维
         {10, 20},
         3: {30, 40}, //直接定义第三行
     }
     c := [...][2][2]int{ //三维数组
         {
             {1, 2},
             {3, 4},
         },
         {
             {10, 20},
             {30, 40},
         }, //不同一行时,最后一个元素都加逗号
     }
     fmt.Println(a)
     fmt.Println(b)
     fmt.Println(c)
     fmt.Println(len(a), cap(a))
     fmt.Println(len(b), cap(b))
     fmt.Println(len(c), cap(c)) //len&cap均计算的是第一维长度
 }
 ​

array3

  • "..."仅可用于第一维

  • 不同一行时,最后一个元素都加逗号

  • 内置函数 len & cap 均计算的是第一维长度

数组与指针

 // array project main.go
 package main
 ​
 import (
     "fmt"
 )
 ​
 //array&pointer
 func main() {
     x, y := 10, 20
     a := [...]*int{&x, &y}
     fmt.Println(a)          //数组名不代表数组的首地址,而代表整个数组
     fmt.Println(a[0], a[1]) //a[0]与数组名内容不同
     p := &a
     fmt.Println(p)                  //p是a的地址
     fmt.Println(*p)                 //*p=a
     fmt.Printf("a:%T p:%T\n", a, p) //%T为类型 a是二维指针数组 p是指向二维指针数组的指针
     *p[1] += 10                     //数组指针可直接用来操作元素 []的优先级大于*
     fmt.Println(p[1], a[1])
     fmt.Println(y)
 }
 ​

array4

  • 数组名不代表数组的首地址,而代表整个数组

  • %T 为输出类型格式控制符

  • %p 为输出地址格式控制符

  • 数组指针可直接用来操作元素

  • p[1]中[]的优先级大于

数组与函数

 // array project main.go
 package main
 ​
 import (
     "fmt"
 )
 ​
 //array&function
 func test(x [2]int) {
     fmt.Printf("x:%p,%v\n", &x, x)
 }
 func main() {
     a := [2]int{10, 20}
     var b [2]int
     b = a //数组整体copy
     fmt.Printf("a:%p,%v\n", &a, a)
     fmt.Printf("b:%p,%v\n", &b, b)
     test(a) //&x!=&a
     //数组名不代表数组的首地址,赋值和传参都会复制全部元素,形参变化不影响实参
 }
 ​

array5

  • 数组名不代表数组的首地址,赋值和传参都会复制全部元素,形参变化不影响实参

数组、指针与函数

 // array project main.go
 package main
 ​
 import (
     "fmt"
 )
 ​
 // array&function&pointer
 func test(x *[2]int) {
     x[1] += 100 //形参的变化,能够改变数组
     fmt.Printf("x:%p,%v\n", x, *x)
 }
 func main() {
     a := [2]int{10, 20}
     test(&a) //指向数组的指针作为函数参数
     fmt.Printf("a:%p,%v\n", &a, a)
 }
 ​

array6

  • 指向数组的指针作为函数参数,形参的变化,能够改变数组

  • 此操作类似C语言中的指针做形参

3.切片(动态数组)

 // slice project main.go
 package main
 ​
 import (
     "fmt"
 )
 ​
 //切片 动态数组
 func main() {
     x := make([]int, 3, 5)                   //创建初始有效长度为0,容量为5的切片
     fmt.Println(x, len(x), cap(x), "origin") //len为实际数据长度,cap为容量长度
     for i := 0; i < 8; i++ {
         x = append(x, i) //追加数据,当超出容量限制的时候,容量自动翻倍
         //一定要把新地址赋给x,如果容量够用,地址可能不变,如果容量不够用地址可能变,为了安全
         fmt.Println(x, len(x), cap(x)) //len为实际数据长度,cap为容量长度
     }
     fmt.Println(x, len(x), cap(x), "final") //len为实际数据长度,cap为容量长度
 ​
     y := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
     s := y[2:5] //创建切片,指向数组的2,3,4元素,容量为len(x)-2,包左不包右
     for i, v := range s {
         fmt.Printf("%v:%v\n", i, v)
     }
     fmt.Println("有效数据长度,即可操控元素个数:", len(s))
     fmt.Println("总容量:", cap(s))
 ​
     fmt.Println(y)
     s[0] = 5
     fmt.Println(y) //修改后原数组也被修改,即共享同一空间
 }
 ​

slice

  • 内置函数 具体用法见上例

    • make函数 用来创建指定类型的对象

    • len函数 求有效数据长度,即可操作的数据长度

      • 对数组等来说,len()cap()结果相同

      • 对切片来说,len()cap()可能不同,len()为有效长度,不包括容量翻倍后未初始化的长度

    • cap函数 求集合容量长度(总内存占用长度)

    • append函数 为切片添加新元素,且只能在切片的最后添加,当超出容量限制的时候,容量自动翻倍

  • 注意 append时一定要把新地址赋给x,如果容量够用,地址可能不变,如果容量不够用地址可能变,为了安全

  • 切片创建时,包左不包右

    eg. s := y[2:5]中,创建的切片s指向数组的2,3,4元素,容量为len(x)-2(容量至数组结束,此处缺少下表为0、1两个元素,故 减2),包左不包右

  • 指向数组的切片被修改后原数组也被修改,即二者共享同一空间

4.map

 // map project main.go
 package main
 ​
 import (
     "fmt"
 )
 ​
 func main() {
     //建立商品价格字典
     gm := make(map[string]int)
 ​
     //map赋值
     gm["bed"] = 1000
     gm["desk"] = 500
     gm["chair"] = 300
     gm["computer"] = 5000
     gm["iphone"] = 6800
 ​
     //遍历map
     for good, price := range gm {
         fmt.Println(good, price)
     }
     fmt.Println()
     for good, _ := range gm { //下划线表示忽略第二个返回值,如果不使用某返回值可以用
         fmt.Println(good)
     }
     fmt.Println()
 ​
     fmt.Println("before del", gm)
     delete(gm, "bed") //map删除
     fmt.Println("after del", gm)
 ​
     //map查询
     if price, ok := gm["bed"]; ok {
         fmt.Println("bed is", price)
     } else {
         fmt.Println("bed is not exist")
     }
     if price, ok := gm["desk"]; ok {
         fmt.Println("desk is", price)
     } else {
         fmt.Println("desk is not exist")
     }
 ​
     fmt.Println(gm) //一次性输出map
 }
 ​

map

  • 建立map:mapName:=make(map[key]value)

  • 遍历map:for key,value:=range mapName{/\*do something of key&value\*/}

  • 删除map元素:delete(mapName,key)

  • 查询map元素:使用comma-ok断言

     if value, ok := mapName[key]; ok {
         /*do something of value*/
     } else {
         fmt.Println("the key is not exist") //do something to handle the exception
     }