Go语言基础(二)
写在前面
上次的博客主要介绍了Go语言中的变量和if,for循环等。见Go语言基础(一)。
这次主要来学习一下Go语言中的函数,数组与切片。
函数的具体定义
基本定义
直接上例子:
func add() {
fmt.Println("Hello World")
}
func max(num1 int, num2 int) int {
var result int
if num2 > num1 {
result = num2
} else {
result = num1
}
return result
}
多个返回值(*)
基本的函数定义就不再赘述了,和其他语言没什么不同。重点来学习一下Go中较为特殊的情况,多个返回值。最典型的一个情况,我们一个函数同时计算周长和面积,这在java中是做不到的,但Go是可以的:
// 案例 周长和面积
// 返回值也可以命名
// return的结果值和定义函数返回值的命名无关
func fun1(len, wid float64) (zc float64, area float64) {
area = len * wid
zc = (len + wid) * 2
// return 可以不写返回值 按照形参顺序返回
return
}
当然 我们的返回值也可以不定义名字:
// 交换两个string
// 多个返回值 返回值要用括号括起来
func swap(x, y string) (string, string) {
return y, x
}
可变参数
类似于JS,Go可以在传入参时传入不定长的多个入参:
// 可变参数只能放在最后一个
func getSum(nums ...int) int {
sum := 0
for i := 0; i < len(nums); i++ {
sum += nums[i]
}
return sum
}
defer关键字
当我们操作IO流,网络流时,我们都需要在操作的最后进行关闭。Go为了优化这类操作,推出了defer
关键字,使用defer关键字修饰的函数会在最后调用,如果有多个那么就倒序调用:
// defer 作用:处理一些善后的问题 如错误 文件 网络流关闭等
// 多个defer 倒序执行
func main() {
f("1")
fmt.Println("2")
defer f("3")
fmt.Println("4")
defer f("5")
fmt.Println("6")
defer f("7")
}
func f(s string) {
fmt.Println(s)
}
运行结果如下:
可以看到,最后调用的f(7)在3和5的前面被调用了。
当我们调用defer的时候,该函数就已经被调用了,但是没有被执行。也就是说当我们传入一个参数n,就算后续对n进行了操作,函数内的n还是停留在执行defer的状态。
函数类型
不同于Java这种纯面向对象的语言,在Go语言中函数也是有类型的。这一点有些类似函数式编程的语言。
func main() {
a := 10
fmt.Printf("%T\n", a)
b := [4]int{1, 2, 3, 4}
fmt.Printf("%T\n", b)
fmt.Printf("%T\n", func1)
fmt.Printf("%T\n", func2)
// 函数也是一个数据类型
func3 := func2
fmt.Println(func3(3))
}
打印可以看到,函数也有其类型。
匿名函数和回调函数
理所应当的,既然Go也具有函数式编程语言的特性,那么自然也可以定义匿名函数:
// 带参数的
r1 := func(a, b int) int {
return a + b
}(1, 2)
fmt.Println(r1)
既然有了匿名函数,那么当我们让一个函数可以接收函数的时候,这个函数就称为高阶函数,被传入的函数即为回调函数。比如下面这种:
package main
import "fmt"
// 回调函数
func main() {
r1 := add(1, 2)
fmt.Println(r1)
// 函数调用
r2 := oper(2, 3, add)
fmt.Println(r2)
r3 := oper(2, 3, sub)
fmt.Println(r3)
r4 := oper(2, 3, func(a int, b int) int {
if b == 0 {
fmt.Printf("除数不能为0")
return 0
}
return a / b
})
fmt.Println(r4)
}
// 运算
func oper(a, b int, fun func(int, int) int) int {
r := fun(a, b)
return r
}
func add(a, b int) int {
return a + b
}
func sub(a, b int) int {
return a - b
}
闭包
有了高阶函数和回调函数,自然也会产生闭包。这里就不详细写了。
数组
定义
定义数组很简单。类似于其他语言:
// 数组定义
var arr1 [5]int
// 给数组赋值
arr1[0] = 1
arr1[1] = 2
arr1[2] = 3
arr1[3] = 4
arr1[4] = 5
// 常用方法 len() 长度 cap() 容量
fmt.Println("数组的长度:", len(arr1))
fmt.Println("数组的容量:", cap(arr1))
不太一样的是,数组里有一个长度和容量的概念。这个概念如果学过Java的ArrayList,会更好理解。容量指的是当前最大的长度,而长度为数组目前容纳的长度。
当然,Go语言还允许我们不输入长度,自动推导,虽然似乎没啥用吧(:
var arr3 = [...]int{1, 2, 3, 4, 4, 5, 5}
我们还可以为特定下标赋值:
var arr4 = [10]int{5: 500}
遍历
遍历数组,首先是最简单的fori循环:
var arr1 = [5]int{1, 2, 3, 4, 5}
for i := 0; i < len(arr1); i++ {
fmt.Println(arr1[i])
}
Go语言还提供了一种更加万能的for range遍历:
for index := range arr1 {
fmt.Println(arr1[index])
}
这种循环方式在后面其他的数据结构中会出现的很多。
值类型的数组
与其他大部分语言不一样,Go语言中的数组是值类型,我们修改值不会影响原对象。
// 数组是值类型 所有赋值后的对象修改值后不影响原来的对象
func main() {
//
arr1 := [4]int{1, 2, 3, 4}
arr2 := [5]string{"aaa"}
fmt.Printf("%T\n", arr1)
fmt.Printf("%T\n", arr2)
// 数组的值传递和int等基本类型一致
arr3 := arr1
arr3[0] = 12
fmt.Println(arr1)
fmt.Println(arr3)
}
多维数组
当然,我们也可以定义二维,三维数组。用forrange循环可以很方便地遍历:
// 多维数组
arr := [3][4]int{
{1, 2, 3, 4},
{0, 1, 2, 3},
{3, 4, 5, 6},
}
for i, v := range arr {
fmt.Println(i, v)
}
切片
定义
上一小节说到数组是值类型,这让我一个老Javaer很是疑惑。因为我学过的所有语言里,数组都是引用类型。类似于Java中的List,Go语言中也有不定长度的数组——切片。
// 定义切片
var s1 []int
fmt.Println(s1)
if s1 == nil {
fmt.Println("切片是空的")
}
s2 := []int{1, 2, 3, 4}
fmt.Println(s2)
fmt.Printf("%T\n", s2)
可以看出,定义时只是把长度去掉而已。但不同的是,类型却发生了变化:
make方式定义
上一小节的定义方式是不太推荐的方式。正确的方式应是使用make函数,我们可以指定切片的长度与容量:
s1 := make([]int, 5, 10)
fmt.Println(s1)
fmt.Println(cap(s1))
虽然我们的容量定为了10,但我们还是无法直接给第6个元素赋值的。想要赋值需要进行扩容:
s1 = append(s1, 10, 10, 10, 10, 10, 10)
虽然我们的容量只有10,但这里会自动进行扩容。
除此之外,我们还可以使用解构运算符来进行扩容:
s2 := []int{100, 200, 300, 400}
s1 = append(s1, s2...)
遍历
同样的,我们也可以使用forrange循环来遍历:
for i := range s1 {
fmt.Println(s1[i])
}
深拷贝
刚才提到,数组是值类型,那么切片即为引用类型。既然是引用类型,那么我们对浅拷贝的对象进行的修改会反馈到原对象上,因此我们需要进行深拷贝。Go语言提供了一个方便的深拷贝函数:
s1 := []int{1, 2, 3, 4}
s2 := make([]int, 0)
// for
for i := 0; i < len(s1); i++ {
s2 = append(s2, s1[i])
}
// copy
s3 := []int{5, 6, 7}
fmt.Println(s3)
// 将s3拷贝到s2
copy(s2, s3)
总结
这次博客主要学习了函数,数组与切片。总的来说难度还不是很高,学习时很轻松。