Go语言 函数 切片 map
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//返回值要和返回值类型列表一一对应
}
多返回值类型促进了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*/
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)
}
闭包
匿名函数被称为闭包(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 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的可用范围扩展了
}
}
回调
函数可以作为其它函数的参数进行传递,然后在其它函数内调用执行,一般称之为回调
// 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)
}
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语句
}
方法(类成员函数)
在面向对象章节里
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 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)
}
%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均计算的是第一维长度
}
"..."仅可用于第一维
不同一行时,最后一个元素都加逗号
内置函数 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)
}
数组名不代表数组的首地址,而代表整个数组
%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
//数组名不代表数组的首地址,赋值和传参都会复制全部元素,形参变化不影响实参
}
数组名不代表数组的首地址,赋值和传参都会复制全部元素,形参变化不影响实参
数组、指针与函数
// 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)
}
指向数组的指针作为函数参数,形参的变化,能够改变数组
此操作类似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) //修改后原数组也被修改,即共享同一空间
}
内置函数 具体用法见上例
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:
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 }