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)
}

运行结果如下:

image-20230321224351750

可以看到,最后调用的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))
}

image-20230321224709980

打印可以看到,函数也有其类型。

匿名函数和回调函数

理所应当的,既然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)

可以看出,定义时只是把长度去掉而已。但不同的是,类型却发生了变化:

image-20230326214547323

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)

总结

这次博客主要学习了函数,数组与切片。总的来说难度还不是很高,学习时很轻松。

posted @ 2023-03-26 21:55  武神酱丶  阅读(34)  评论(0编辑  收藏  举报