4 异常机制–数组、切片、算法【Go语言教程】
1 异常机制
1.1 处理错误
- Go 语言追求简洁优雅,所以,Go 语言不支持传统的 try…catch…finally 这种处理。
- Go 中引入的处理方式为:defer, panic, recover
- 这几个异常的使用场景可以这么简单描述: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 内置函数。
- errors.New(“错误说明”) , 会返回一个 error 类型的值,表示一个错误
- 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字节)来更加快速的访问到数组中元素的值
- 数组的地址可以通过数组名来获取 &intArr
- 数组的第一个元素的地址,就是数组的首地址
- 数组的各个元素的地址间隔是依据数组的类型决定,比如 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)
}
}
②使用细节
- 数组是多个相同类型数据的组合,一个数组一旦声明/定义了,其长度是固定的, 不能动态变化
- var arr []int 这时 arr 就是一个 slice 切片
- 数组中的元素可以是任何数据类型,包括值类型和引用类型,但是不能混用。
- 数组创建后,如果没有赋值,有默认值(零值)
数值类型数组:默认值为 0
字符串数组: 默认值为 “”
bool 数组: 默认值为 false- 数组下标必须在指定范围内使用,否则报 panic:数组越界
- Go 的数组属值类型, 在默认情况下是值传递, 因此会进行值拷贝。数组间不会相互影响
- 如想在其它函数中,去修改原来的数组,可以使用引用传递(指针方式)
2.2 切片
2.2.1 概念
先看一个需求:我们需要一个数组用于保存学生的成绩,但是学生的个数是不确定的,请问怎么办?解决方案:-》使用切片。【类比于java中的list集合】
- 切片的英文是 slice
- 切片是数组的一个引用,因此切片是引用类型,在进行传递时,遵守引用传递的机制。
- 切片的使用和数组类似,遍历切片、访问切片的元素和求切片长度 len(slice)都一样。
- 切片的长度是可以变化的,因此切片是一个可以动态变化数组。
- 切片定义的基本语法: 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.
对上面代码的小结:
- 通过 make 方式创建切片可以指定切片的大小和容量
- 如果没有给切片的各个元素赋值,那么就会使用默认值[int , float=> 0 string =>”” bool => false]
- 通过 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 切片在内存中的形式(重要)
对上面的分析图总结
- slice 的确是一个引用类型
- slice 从底层来说,其实就是一个数据结构(struct 结构体)
- 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 切片的使用细节
- 切片初始化时 var slice = arr[startIndex:endIndex]
说明:从 arr 数组下标为 startIndex,取到 下标为 endIndex 的元素(不含 arr[endIndex])。- 切片初始化时,仍然不能越界。范围在 [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[:]
- cap 是一个内置函数,用于统计切片的容量,即最大可以存放多少个元素。
- 切片定义完后,还不能使用,因为本身是一个空的,需要让其引用到一个数组,或者 make 一个空间供切片来使用
- 切片可以继续切片
- 用 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 是在底层来维护的,程序员不可见.
- 切片的拷贝操作
切片使用 copy 内置函数完成拷贝
(1) copy(para1, para2) 参数的数据类型是切片
(2) 按照上面的代码来看, slice4 和 slice5 的数据空间是独立,相互不影响,也就是说 slice4[0]= 999, slice5[0] 仍然是 1- 关于拷贝的注意事项
说明: 上面的代码没有问题,可以运行, 最后输出的是 [1]
func main(){
slice1 := []int {1,2,5,6,7}
var slice2 = make([]int, 1)
copy(slice2, slice1)
//slice2= [1]
fmt.Println("slice2=", slice2)
}
- 切片是引用类型,所以在传递时,遵守引用传递机制。看两段代码,并分析底层原理
2.2.6 string与slice
- string 底层是一个 byte 数组,因此 string 也可以进行切片处理
- string 和切片在内存的形式,以 “abcd” 画出内存示意图
- string 是不可变的,也就说不能通过 str[0] = ‘z’ 方式来修改字符串
- 如果需要修改字符串,可以先将 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) ,要求完成
- 可以接收一个 n int
- 能够将斐波那契的数列放到切片中
- 提示, 斐波那契的数列形式:
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)
- 使用演示
说明:二维数组在声明/定义时也对应有四种写法[和一维数组类似]
- var 数组名 [大小][大小]类型 = [大小][大小]类型{{初值…},{初值…}}
- var 数组名 [大小][大小]类型 = […][大小]类型{{初值…},{初值…}}
- var 数组名 = [大小][大小]类型{{初值…},{初值…}}
- 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