4 异常机制–数组、切片、算法【Go语言教程】

有时候,不是因为你没有能力,也不是因为你缺少勇气,只是因为你付出的努力还太少,所以,成功便不会走向你。而你所需要做的,就是坚定你的梦想,你的目标,你的未来,然后以不达目的誓不罢休的那股劲,去付出你的努力,成功就会慢慢向你靠近。

导读:本篇文章讲解 4 异常机制–数组、切片、算法【Go语言教程】,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

4 异常机制–数组、切片、算法【Go语言教程】

1 异常机制

1.1 处理错误

  1. Go 语言追求简洁优雅,所以,Go 语言不支持传统的 try…catch…finally 这种处理。
  2. Go 中引入的处理方式为:defer, panic, recover
  3. 这几个异常的使用场景可以这么简单描述:Go 中可以抛出一个 panic 的异常,然后在 defer 中通过 recover 捕获这个异常,然后正常处理
package main
import (
	"fmt"
	_ "time"
)

func test(){
	//使用defer + recover 来捕获和处理异常
	defer func(){
		err := recover() //recover()内置函数,可以捕获到异常
		if err != nil { //说明捕获到异常
			fmt.Println("err=", err)
			//这里就可以将错误信息发送给管理员
			fmt.Println("发送邮件给admin@sohu.com~")
		}
	}()
	num1 := 10
	num2 := 0
	res := num1 / num2
	fmt.Println("res=", res)
}

func main(){
	test()
}

在这里插入图片描述

1.2 自定义错误

Go 程序中,也支持自定义错误, 使用 errors.New 和 panic 内置函数。

  1. errors.New(“错误说明”) , 会返回一个 error 类型的值,表示一个错误
  2. panic 内置函数 ,接收一个 interface{}类型的值(也就是任何值了)作为参数。可以接收 error 类型的变量,输出错误信息,并退出程序.
package main
import (
	"fmt"
	"errors"
)

//函数去读取配置文件init.conf的信息
//如果文件名传入不正确,我们就返回一个自定义的错误

func readConf(name string) (err error){
	if name == "config.ini" {
		//读取...
		return nil
	}else {
		//返回一个自定义的错误
		return errors.New("读取文件错误")
	}
}

func test02(){
	err := readConf("config2.ini")
	if err != nil {
		//如果读取文件发送错误,就输出这个错误,并终止程序
		panic(err)
	}
	fmt.Println("test02()继续执行")
}

func main(){
	//测试自定义错误的使用
	test02()
}

在这里插入图片描述

2 数组与切片

2.1 数组

2.1.1 概念

数组可以存放多个同一类型数据。数组也是一种数据类型,在 Go 中,数组是值类型。
注意:数组的地址就是数组第一个元素的地址;我们可以通过地址(根据数据类型推断,如:int64,数组地址为0x428170,则数组第二个元素地址为0x428178,int64占8字节)来更加快速的访问到数组中元素的值

在这里插入图片描述

  1. 数组的地址可以通过数组名来获取 &intArr
  2. 数组的第一个元素的地址,就是数组的首地址
  3. 数组的各个元素的地址间隔是依据数组的类型决定,比如 int64 -> 8 int32->4…

在这里插入图片描述

2.1.2 初始化方式及使用

  • 初始化方式:
package main
import (
	"fmt"
)


func main(){
	//四种初始化数组的方式
	var numArr01 [3]int = [3]int{1,2,3}
	fmt.Println("numArr01=", numArr01)

	var numArr02 = [3]int{5,6,7}
	fmt.Println("numArr02=", numArr02)

	var numArr03 = [...]int{8,9,10}
	fmt.Println("numArr03=", numArr03)

	//指定对应下标的元素
	var numArr04 = [...]int{1:800, 0: 900, 2:999}
	fmt.Println("numArr04=", numArr04)

	//类型推导
	strArr05 := [...]string{1:"tom", 0:"jack", 2:"mary"}
	fmt.Println("strArr05=", strArr05)
}

在这里插入图片描述

  • 数组的使用:

从终端循环输入 5 个成绩,保存到 float64 数组,并输出.

package main
import (
	"fmt"
)


func main(){
	//从终端输入5个成绩,并保存到float64数组
	var scores [5]float64
	for i := 0; i < len(scores); i++ {
		fmt.Printf("请输入第%d个元素的值\n", i+1)
		fmt.Scanln(&scores[i])
	}
	//打印结果
	for i := 0; i < len(scores); i++ {
		fmt.Printf("scores[%d]=%v\n", i, scores[i])
	}
}

在这里插入图片描述

2.1.3 遍历方式及使用细节

①遍历方式:

package main
import (
	"fmt"
)


func main(){
	arr := [4]int {1, 2, 3, 4}
	//1. 常规遍历方式
	fmt.Println("==========第一种遍历方式============")
	for i := 0; i < len(arr); i++ {
		fmt.Println(arr[i])
	}
	//2. for-range遍历
	fmt.Println("==========for-range============")
	for _, v := range arr {
		fmt.Println(v)
	}
}

在这里插入图片描述
②使用细节

  1. 数组是多个相同类型数据的组合,一个数组一旦声明/定义了,其长度是固定的, 不能动态变化
  2. var arr []int 这时 arr 就是一个 slice 切片
  3. 数组中的元素可以是任何数据类型,包括值类型和引用类型,但是不能混用。
  4. 数组创建后,如果没有赋值,有默认值(零值)
    数值类型数组:默认值为 0
    字符串数组: 默认值为 “”
    bool 数组: 默认值为 false
  5. 数组下标必须在指定范围内使用,否则报 panic:数组越界
  6. Go 的数组属值类型, 在默认情况下是值传递, 因此会进行值拷贝。数组间不会相互影响
    在这里插入图片描述
  7. 如想在其它函数中,去修改原来的数组,可以使用引用传递(指针方式)
    在这里插入图片描述

2.2 切片

2.2.1 概念

先看一个需求:我们需要一个数组用于保存学生的成绩,但是学生的个数是不确定的,请问怎么办?解决方案:-》使用切片。【类比于java中的list集合】

  1. 切片的英文是 slice
  2. 切片是数组的一个引用,因此切片是引用类型,在进行传递时,遵守引用传递的机制。
  3. 切片的使用和数组类似,遍历切片、访问切片的元素和求切片长度 len(slice)都一样。
  4. 切片的长度是可以变化的,因此切片是一个可以动态变化数组。
  5. 切片定义的基本语法: var 切片名 []类型 比如:var a [] int

2.2.2 使用

方式一:

定义一个切片,然后让切片去引用一个已经创建好的数组,比如下面的案例就是这样的。

package main
import (
	"fmt"
)


func main(){
	//演示切片的基本使用
	var intArr [5]int = [...]int{1,2,44,55,99}
	//声明/定义一个切片
	//slice := intArr[1:3]
	//1. slice就是切片名
	//2. intArr[1:3] 表示slice引用到intArr这个数组
	//3. 引用intArr数组的起始下标为1, 最后的下标为3(但是不包含3)
	slice := intArr[1:3]
	fmt.Println("intArr=", intArr)
	fmt.Println("slice的元素是=", slice)
	fmt.Println("slice的元素个数是=", len(slice))
	fmt.Println("slice的容量是=", cap(slice))//切片的容量是可以动态变化的
}

在这里插入图片描述
方式二:

通过 make 来创建切片.

  • 语法:var 切片名 []type = make([]type, len, [cap])
    参数说明: type: 就是数据类型 len : 大小 cap :指定切片容量,可选, 如果你分配了 cap,则要求 cap>=len.
    在这里插入图片描述

对上面代码的小结:

  1. 通过 make 方式创建切片可以指定切片的大小和容量
  2. 如果没有给切片的各个元素赋值,那么就会使用默认值[int , float=> 0 string =>”” bool => false]
  3. 通过 make 方式创建的切片对应的数组是由 make 底层维护,对外不可见,即只能通过 slice 去访问各个元素.

方式三:

定义一个切片,直接就指定具体数组,使用原理类似 make 的方式
在这里插入图片描述

面试:方式一与方式二的区别:
在这里插入图片描述

三种方式创建全部代码:

package main
import (
	"fmt"
)


func main(){
	arr1 := [...]int {1,4,5,2,7}
	//方式1:直接引用数组(切片的变化会影响原来数组的变化)
	slice1 := arr1[:3]//0:3
	fmt.Println("slice1=", slice1)

	//方式2:通过make
	//对于切片来说如果不是通过数组直接引用,那么必须make后(开辟内存空间)使用
	var slice2 []float64 = make([]float64, 5, 10)
	slice2[1] = 10
	slice2[3] = 20
	fmt.Println("slice2=", slice2)

	//方式3:直接指定具体数组【原理类似于make的方式】
	var slice3 []string = []string{"tom", "jack", "mary"}
	fmt.Println("slice3=", slice3)
	fmt.Println("slice3 size=", len(slice3))
	fmt.Println("slice3 cap=", cap(slice3))	
}

在这里插入图片描述

2.2.3 切片在内存中的形式(重要)

在这里插入图片描述

对上面的分析图总结

  1. slice 的确是一个引用类型
  2. slice 从底层来说,其实就是一个数据结构(struct 结构体)
  3. type slice struct {
    ptr *[2]int
    len int
    cap int

}

2.2.4 切片的遍历

切片的遍历和数组一样,也有两种方式

  • for 循环常规方式遍历
  • for-range 结构遍历切片
func main(){
	slice := [3]int {1, 2, 4}
	//常规for
	for i := 0; i < len(slice); i++ {
		fmt.Printf("slice[%v]=%v", i, slice[i])
	}

	fmt.Println()
	//使用for-range 方式遍历 i:index, v:value
	for i, v := range slice {
		fmt.Printf("i=%v v=%v\n", i, v)
	}
}

2.2.5 切片的使用细节

  1. 切片初始化时 var slice = arr[startIndex:endIndex]
    说明:从 arr 数组下标为 startIndex,取到 下标为 endIndex 的元素(不含 arr[endIndex])。
  2. 切片初始化时,仍然不能越界。范围在 [0-len(arr)] 之间,但是可以动态增长.
  • var slice = arr[0:end] 可以简写 var slice = arr[:end]
  • var slice = arr[start:len(arr)] 可以简写: var slice = arr[start:]
  • var slice = arr[0:len(arr)] 可以简写: var slice = arr[:]
  1. cap 是一个内置函数,用于统计切片的容量,即最大可以存放多少个元素。
  2. 切片定义完后,还不能使用,因为本身是一个空的,需要让其引用到一个数组,或者 make 一个空间供切片来使用
  3. 切片可以继续切片
    在这里插入图片描述
  4. 用 append 内置函数,可以对切片进行动态追加
    在这里插入图片描述
package main
import (
	"fmt"
)


func main(){
	slice1 := [...]int {1, 2, 4, 6, 9, 10, 57, 28}
	slice2 := slice1[2:5]
	fmt.Println("slice2=", slice2)
	slice2[0] = -39
	fmt.Println("slice1=", slice1)
	fmt.Println("slice2=", slice2)
	//append()追加
	slice2 = append(slice2, -100, -200)
	fmt.Println("追加后的slice2=", slice2)
}

在这里插入图片描述
在这里插入图片描述

切片 append 操作的底层原理分析:

  • 切片 append 操作的本质就是对数组扩容
  • go 底层会创建一下新的数组 newArr(安装扩容后大小)
  • 将 slice 原来包含的元素拷贝到新的数组 newArr slice 重新引用到 newArr
    注意 :newArr 是在底层来维护的,程序员不可见.
  1. 切片的拷贝操作
    切片使用 copy 内置函数完成拷贝
    在这里插入图片描述
    (1) copy(para1, para2) 参数的数据类型是切片
    (2) 按照上面的代码来看, slice4 和 slice5 的数据空间是独立,相互不影响,也就是说 slice4[0]= 999, slice5[0] 仍然是 1
  2. 关于拷贝的注意事项
    在这里插入图片描述
    说明: 上面的代码没有问题,可以运行, 最后输出的是 [1]
func main(){
	slice1 := []int {1,2,5,6,7}
	var slice2 = make([]int, 1)
	copy(slice2, slice1)
	//slice2= [1]
	fmt.Println("slice2=", slice2)
}
  1. 切片是引用类型,所以在传递时,遵守引用传递机制。看两段代码,并分析底层原理
    在这里插入图片描述

2.2.6 string与slice

  1. string 底层是一个 byte 数组,因此 string 也可以进行切片处理
  2. string 和切片在内存的形式,以 “abcd” 画出内存示意图
  3. string 是不可变的,也就说不能通过 str[0] = ‘z’ 方式来修改字符串
  4. 如果需要修改字符串,可以先将 string -> []byte / 或者 []rune -> 修改 -> 重写转成 string
func main(){
	//1.不含有中文的字符串修改
	str := "hello, china"
	arr1 := []byte(str)
	arr1[0] = 'z'
	str = string(arr1)
	fmt.Println("str=", str)

	//2.含中文的字符串修改
	str2 := "你好, 你在哪里a"
	arr2 := []rune(str2)
	arr2[0] = '北'
	str2 = string(arr2)
	fmt.Println("str2=", str2)
	// str= zello, china
	// str2= 北好, 你在哪里a

}

2.2.7 实现斐波那契数列

说明:编写一个函数 fbn(n int) ,要求完成

  1. 可以接收一个 n int
  2. 能够将斐波那契的数列放到切片中
  3. 提示, 斐波那契的数列形式:
    arr[0] = 1; arr[1] = 1; arr[2]=2; arr[3] = 3; arr[4]=5; arr[5]=8
//uint64范围更大
func fbn(n int)([] uint64){
	//声明一个切片,大小为n
	fbnSlice := make([]uint64, n)
	//初始化斐波那契,第一个、第二个数是1
	fbnSlice[0] = 1
	fbnSlice[1] = 1
	//进行for循环来存放斐波那契数列
	for i := 2; i < n; i++ {
		fbnSlice[i] = fbnSlice[i-1] + fbnSlice[i-2]
	}
	return fbnSlice
}

func main(){
	fbnSlice := fbn(10)
	//fbnSlice= [1 1 2 3 5 8 13 21 34 55]
	fmt.Println("fbnSlice=", fbnSlice)
}

3 算法

3.1 排序算法

冒泡排序:

//BubbleSort表示公开,其他包可以使用
//*[5]int 表示指针,可以修改数组的值
func BubbelSort(arr *[5]int){
	fmt.Println("排序前arr=", (*arr))
	tmp := 0 
	//冒泡排序【每一次找出最大的数来】
	for i := 0; i < len(*arr); i++ {
		for j := 0; j < len(*arr) - i - 1; j++ {
			if(*arr)[j] > (*arr)[j+1]{
				//交换
				tmp = (*arr)[j]
				(*arr)[j] = (*arr)[j+1]
				(*arr)[j+1] = tmp
			}
		}
	}
}

func main(){
	arr := [...]int {9,3,-1,0,87}
	BubbelSort(&arr)
	fmt.Println("排序后arr=", arr)
	//排序前arr= [9 3 -1 0 87]
	//排序后arr= [-1 0 3 9 87]
}

3.2 查找算法

二分查找

package main
import (
	"fmt"
)

//实现二分查找
func BinaryFind(arr *[6]int, target int) int {
	left := 0; right := len((*arr)) -1
	middle := left + (right - left) / 2
	for{
		if left <= right {
			middle = left + (right - left) / 2
			if (*arr)[middle] < target {
				left = middle + 1
			} else if (*arr)[middle] > target {
				right = middle - 1
			} else {
				return middle
			}
		}else {
			return -1
		}
	}
}


func main(){
	arr := [6]int{1,8,10,89,100,1234}
	res := BinaryFind(&arr, 1234)
	if res == -1 {
		fmt.Println("找不到")
	}else {
		fmt.Println("找到了,下标为:", res)
	}
}

3.3 二维数组

3.3.1 使用方式

方式一:

先声明/定义,再赋值

  • 语法: var 数组名 [大小][大小]类型
  • 比如: var arr [2][3]int , 再赋值。
  • 使用演示
  • 二维数组在内存的存在形式(重点)

在这里插入图片描述
在这里插入图片描述
方式二:

直接初始化

  • 声明:var 数组名 [大小][大小]类型 = [大小][大小]类型{{初值…},{初值…}}
  • 赋值(有默认值,比如 int 类型的就是 0)
  • 使用演示
    在这里插入图片描述说明:二维数组在声明/定义时也对应有四种写法[和一维数组类似]
  1. var 数组名 [大小][大小]类型 = [大小][大小]类型{{初值…},{初值…}}
  2. var 数组名 [大小][大小]类型 = […][大小]类型{{初值…},{初值…}}
  3. var 数组名 = [大小][大小]类型{{初值…},{初值…}}
  4. var 数组名 = […][大小]类型{{初值…},{初值…}}

3.3.2 遍历方式

  • 双层 for 循环完成遍历
  • for-range 方式完成遍历

在这里插入图片描述

package main
import (
	"fmt"
)

func main(){
	//演示二维数组的遍历
	var arr = [2][3]int{{1,2,3},{4,5,6}}
	//1. 双重for循环
	for i := 0; i < len(arr); i++ {
		for j := 0; j < len(arr[i]); j++ {
			fmt.Printf("%v\t", arr[i][j])
		}
		fmt.Println()
	}

	//2. for-range方式遍历
	for i, v := range arr {
		for j, v2 := range v {
			fmt.Printf("arr[%v][%v]=%v \t", i, j, v2)
		}
		fmt.Println()
	}
}

在这里插入图片描述

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/148511.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!