错误处理
错误处理/捕获机制:
- go中追求代码的优雅,不使用try/catch。引用defer+recover机制处理
- 内置函数
recover:允许程序管理恐慌panic
func main() {
test()
fmt.Println("上面的除法操作执行成功。。。")
fmt.Println("正常执行下面的逻辑")
}
func test(){
//利用defer+recover来捕获错误:
defer func() {
//调用recover内置函数,可以捕获错误:defer后加上匿名函数的调用
err := recover()
//如果没有捕获错误,返回值为零值,即:nil
if err != nil {
fmt.Println("错误已经捕获")
fmt.Println("err是:",err)
}
}()
num1 := 10
num2 := 0
result := num1/ num2
fmt.Println(result)
}
- 优点:提高程序健壮性
自定义错误
- 自定义错误:需要调用errors包下的New函数,函数返回error类型
- 示例:
func main() {
err := test()
if err != nil {
fmt.Println("自定义错误:", err)
}
fmt.Println("上面的除法操作执行成功。。。")
fmt.Println("正常执行下面的逻辑")
}
func test() (err error){
num1 := 10
num2 := 0
if num2 == 0{
//抛出自定义错误:
return errors.New("除数不能为0")
}else {
result := num1/ num2
fmt.Println(result)
// 如果没有错误,返回零值
return nil
}
}
- 有一种情况:程序出现错误以后,后续代码就没必要执行,想让程序中断,退出程序,需要借助buildin包下内置函数:
panic- 代码示例:
func main() {
err := test()
if err != nil {
fmt.Println("自定义错误:", err)
panic(err)
}
fmt.Println("上面的除法操作执行成功。。。")
fmt.Println("正常执行下面的逻辑")
}
func test() (err error){
num1 := 10
num2 := 0
if num2 == 0{
//抛出自定义错误:
return errors.New("除数不能为0")
}else {
result := num1/ num2
fmt.Println(result)
// 如果没有错误,返回零值
return nil
}
}
数组
数组定义格式
var 数组名 [数组大小]数组类型
数组内存分析
- 示例
func main() {
//声明数组
var arr [3]int16
fmt.Println(len(arr))
for i := 0; i < 3; i++ {
fmt.Println(arr[i])
}
//证明arr中存储的地址值
fmt.Printf("arr的地址为:%p\n",&arr)
//第一个空间的地址:
fmt.Printf("arr的地址为:%p\n",&arr[0])
//第二个空间的地址:
fmt.Printf("arr的地址为:%p\n",&arr[1])
//第三个空间的地址:
fmt.Printf("arr的地址为:%p\n",&arr[2])
}
运行结果:
3
0
0
0
arr的地址为:0xc000016092
arr的地址为:0xc000016092
arr的地址为:0xc000016094
arr的地址为:0xc000016096
- 声明数组时,会在内存分配空间,且会添加对应数据类型的默认值
- 数组优点:访问/查询/读取 速度快
- 每个空间占用的字节数取决于数组类型
数组遍历
实现方式
- 普通for循环
- 键值循环
for key,val := range coll {
...
}
* coll就是你要的数组
* 每次遍历得到的索引用key接受,每次遍历得到的索引位置上的值用val
* key,val的名字可随便起。
* key,val是当前循环的局部变量
* 如果想忽略某个值,使用`_`即可
- 示例
func main() {
// 给出5个学生的成绩,求出成绩的总和,平均数
var scores [5]int
for i := 0; i < len(scores) ; i++ {
fmt.Printf("请录入第%d学生的成绩:", i +1)
fmt.Scanln(&scores[i])
}
sum := 0
for i := 0; i < len(scores); i++ {
sum += scores[i]
}
//展示一下班级的每个学生的成绩:(数组进行遍历)
//方式1:普通for循环:
for i := 0; i < len(scores); i++ {
fmt.Printf("第%d个学生的成绩为:%d\n",i +1,scores[i])
}
fmt.Println("----------------------")
//方式2:for-range循环
for key, val := range scores {
fmt.Printf("第%d个学生的成绩为:%d\n",key +1,val)
}
}
运行结果:
请录入第1学生的成绩:94
请录入第2学生的成绩:43
请录入第3学生的成绩:86
请录入第4学生的成绩:68
请录入第5学生的成绩:96
第1个学生的成绩为:94
第2个学生的成绩为:43
第3个学生的成绩为:86
第4个学生的成绩为:68
第5个学生的成绩为:96
----------------------
第1个学生的成绩为:94
第2个学生的成绩为:43
第3个学生的成绩为:86
第4个学生的成绩为:68
第5个学生的成绩为:96
数组的初始化方式
func main() {
//第一种
var arr1 [3]int = [3]int{3, 6, 9}
fmt.Println(arr1)
//第二种
var arr2 = [3]int{1, 4, 7,}
fmt.Println(arr2)
//第三种
var arr3 = [...]int{4,5,6,7}
fmt.Println(arr3)
//第四种:冒号前指的是下标
var arr4 = [...]int{2: 66, 0: 33, 1: 99, 3: 88}
fmt.Println(arr4)
}
注意事项
- 长度属于类型的一部分
func main() {
// 定义一个数组
var arr1 = [3]int{3, 6, 9}
fmt.Printf("数组的类型为:%T\n",arr1)
var arr2 = [6]int{3, 6, 9, 1, 4, 7,}
fmt.Printf("数组的类型为:%T\n",arr2)
}
运行结果:
数组的类型为:[3]int
数组的类型为:[6]int
- Go中数组数值类型,在默认情况下是值传递,因此会进行值拷贝。
func main() {
var arr3 = [3]int{3, 6, 9}
test1(arr3)
fmt.Println(arr3)
}
func test1(arr [3]int) {
arr[0] = 7
}
运行结果:
[3 6 9]
- 如想在其他函数中,去修改原来的数组,可以使用引用传递(指针方式)
二维数组
数组定义格式
var 数组名 [数组大小][二维数组大小]数组类型- 二维数组定义,有默认初始值,与数组一致
二维数组内存
- 示例
func main() {
//定义二维数组
var arr [2][3]int16
fmt.Println(arr)
fmt.Printf("arr的地址是:%p\n",&arr)
fmt.Printf("arr的地址是:%p\n",&arr[0])
fmt.Printf("arr的地址是:%p\n",&arr[0][0])
fmt.Printf("arr的地址是:%p\n",&arr[1])
fmt.Printf("arr的地址是:%p\n",&arr[1][0])
}
返回结果:
func main() {
//定义二维数组
var arr [2][3]int16
fmt.Println(arr)
fmt.Printf("arr的地址是:%p\n",&arr)
fmt.Printf("arr的地址是:%p\n",&arr[0])
fmt.Printf("arr的地址是:%p\n",&arr[0][0])
fmt.Printf("arr的地址是:%p\n",&arr[1])
fmt.Printf("arr的地址是:%p\n",&arr[1][0])
}
二维数组遍历
- 二维数组遍历与一维数组一致即可以使用普通for循环,也可以键值循环
- 示例
func main() {
//定义二维数组
var arr [3][3]int = [3][3]int{{1,4,7},{2,5,8},{3,6,9} }
fmt.Println(arr)
fmt.Println("----------")
for i := 0; i < len(arr);i++ {
for j := 0; j < len(arr[i]); j++ {
fmt.Print(arr[i][j],"\t")
}
fmt.Println()
}
fmt.Println("----------")
for key,val := range arr {
for key2,val2 := range val {
fmt.Printf("arr[%v][%v]=%v\t",key,key2,val2)
}
fmt.Println()
}
}
运行结果:
[[1 4 7] [2 5 8] [3 6 9]]
----------
1 4 7
2 5 8
3 6 9
----------
arr[0][0]=1 arr[0][1]=4 arr[0][2]=7
arr[1][0]=2 arr[1][1]=5 arr[1][2]=8
arr[2][0]=3 arr[2][1]=6 arr[2][2]=9
切片
切片的引入
- 切片(
slice)是Golang中一种独有的数据类型 - 数组有特定的用处,但是却有一些呆板(数组长度固定不可变),所在Go语言的代码里并不是特别常见。相对的切片却是随处可见的。切片是一种建立在数组类型之上的抽象,它构建在数组之上并且提供更强大的能力和便捷。
- 切片是对数组一个连续片段的引用,所以切片是一个引用类型。这个片段可以是整个数组,或者是由起始和终止索引标识的一些项的子集。需要注意的是,终止索引标识的项不包括在切片内。切片提供了一个相关数组的动态窗口。
- 切片的语法:
var 切片名[]类型 = 数组的一个片段引用
- 示例:
func main() {
// 定义数组
var intarr [6]int = [6]int{3, 6, 9, 1, 4, 7}
//切片构建在数组之上
//定义一个切片名字为slice,[]动态变化的数组长度不写,int类型,intarr是原数组
//[1:3]切片 - 切出的一段片段 表示索引从1开始,到3结束,但不包含3
//var slice []int = intarr[1:3]
slice := intarr[1:3]
//输出数组:
fmt.Println("intarr:", intarr)
//输出切片:
fmt.Println("slice:", slice)
//切片元素个数:
fmt.Println("slice的元素个数为:",len(slice))
//获取切片的容量:容量可以动态变化
fmt.Println("slice的容量为:",cap(slice))
}
内存分析
- 切片有三个字段的数据结构:一个是指向底层数组的指针,一个是切片的长度,一个是切片的容量
- 代码示例
func main() {
// 定义数组
var intarr [6]int = [6]int{3, 6, 9, 1, 4, 7}
//切片构建在数组之上
//定义一个切片名字为slice,[]动态变化的数组长度不写,int类型,intarr是原数组
//[1:3]切片 - 切出的一段片段 表示索引从1开始,到3结束,但不包含3
//var slice []int = intarr[1:3]
slice := intarr[1:3]
//输出数组:
fmt.Println("intarr:", intarr)
//输出切片:
fmt.Println("slice:", slice)
//切片元素个数:
fmt.Println("slice的元素个数为:",len(slice))
//获取切片的容量:容量可以动态变化
fmt.Println("slice的容量为:",cap(slice))
fmt.Printf("数组中下标为1位置的地址:%p\n",&intarr[1])
fmt.Printf("切片中下标为1位置的地址:%p\n",&slice[0])
slice[1] = 16
fmt.Println("intarr:",intarr)
fmt.Println("slice:",slice)
}
运行结果:
func main() {
// 定义数组
var intarr [6]int = [6]int{3, 6, 9, 1, 4, 7}
//切片构建在数组之上
//定义一个切片名字为slice,[]动态变化的数组长度不写,int类型,intarr是原数组
//[1:3]切片 - 切出的一段片段 表示索引从1开始,到3结束,但不包含3
//var slice []int = intarr[1:3]
slice := intarr[1:3]
//输出数组:
fmt.Println("intarr:", intarr)
//输出切片:
fmt.Println("slice:", slice)
//切片元素个数:
fmt.Println("slice的元素个数为:",len(slice))
//获取切片的容量:容量可以动态变化
fmt.Println("slice的容量为:",cap(slice))
fmt.Printf("数组中下标为1位置的地址:%p\n",&intarr[1])
fmt.Printf("切片中下标为1位置的地址:%p\n",&slice[0])
slice[1] = 16
fmt.Println("intarr:",intarr)
fmt.Println("slice:",slice)
}
切片的定义
- 定义一个切片,然后让切片去引用一个已经建好的数组
var intarr [6]int = [6]int{3, 6, 9, 1, 4, 7}
slice := intarr[1:3]
- 通过make内置函数来创建切片。基本语法:
var 切片名[type= make([],len,[cap])
// 定义切片:make函数的三个参数:1.切片类型,2.切片长度,3.切片容量
slice := make([]int,4, 20)
fmt.Println(slice)
fmt.Println("切片的长度:",len(slice))
fmt.Println("切片的容量:",cap(slice))
slice[0] = 66
slice[1] = 88
fmt.Println(slice)
PS: make底层创建一个数组,对外不可见,所以不可以直接操作这个数组,要通过slice去间接的访问各个元素,不可以直接对数组进行操作/维护
- 定一个切片,直接就指定具体数组,使用原理类似make的方式。
slice2 := []int{1, 4, 7}
fmt.Println(slice2)
fmt.Println("切片的长度:",len(slice2))
fmt.Println("切片的容量:",cap(slice2))
切片的循环
func main() {
// 定义切片:make函数的三个参数:1.切片类型,2.切片长度,3.切片容量
slice := make([]int,4, 20)
slice[0] = 66
slice[1] = 88
slice[2] = 99
slice[3] = 100
//方式1:普通for循环
for i := 0; i < len(slice); i++ {
fmt.Printf("slice[%v]: %v\t", i, slice[i])
}
fmt.Println("\n---------------------")
//方式2:for-range循环
for i,v := range slice {
fmt.Printf("下标:%v,元素:%v\n",i, v)
}
}
切片的注意事项
- 切片定义后不可以直接使用,需要让其引用到一个数组,或者make一个空间供切片来使用
func main() {
var slice []int
fmt.Println(slice)
//这样写法毫无意义
}
- 切片使用不能越界
func main() {
var intarr [6]int = [6]int{1, 4, 7, 2, 5, 8}
var slice []int = intarr[1:4]
fmt.Println(slice[0])
fmt.Println(slice[1])
fmt.Println(slice[2])
fmt.Println(slice[3])
}
运行结果:
4
7
2
panic: runtime error: index out of range [3] with length 3
- 简写方式:
var slice = arr[0:end] ---> var slice = arr[:end]//从0开始切,则可以省略开始部分
var slice = arr[start:len(arr)] ---> var slice = arr[start:]//切到最后,则可省略结束部分
var slice = arr[0:len(arr)] ---> var slice = arr[:]//从头切到尾,则可都省略
- 切片可以继续切片
func main() {
//切片再次切片
var intarr [6]int = [6]int{1, 4, 7, 2, 5, 8}
var slice []int = intarr[1:4] //4,7,2
var slice2 []int = slice[1:2]
fmt.Println(slice2)
slice2[0] = 66
fmt.Println(slice)
fmt.Println(slice2)
fmt.Println(intarr)
}
- 切片可以动态增长
func main() {
//定义数组:
var intarr [6]int = [6]int{1, 4, 7, 3, 6, 9}
//定义切片:
var slice []int = intarr[1:4]
fmt.Println(len(slice))
slice2 := append(slice,88,50)
fmt.Println(slice2)
//底层原理:
//1.底层追加元素的时候对数组进行扩容,老数组扩容为新数组:
//2.创建一个新数组,将老数组中的4,7,3复制到新的数组中,在数组中增加88,50
//3.slice2底层数组的指向,指向的是新数组
//4.往往我们在使用追加的时候其实想做的效果给slice追加:
slice = append(slice,88,50)
fmt.Println(len(slice))
//5.底层的新数组不能直接维护,不能直接维护,需要通过切片简介维护操作
}
PS:可以通过append函数将切片追加给切片
slice3 := []int{99,44}
slice = append(slice,slice3...)
fmt.Println(len(slice))
- 切片的拷贝
func main() {
//定义切片:
var a []int = []int{1, 4, 7, 3, 6, 9}
//在定义一个切片:
var b []int = make([]int, 10)
copy(b, a) //将a中对应数组中元素内容复制到b中对应的数组中,对应的是两个数组
fmt.Println(b)
}
映射
映射说明
- 映射(
map):Go语言中内置的一种类型,它将键值对相关联,我们可以通过键key来获取对应的值value。类似其他语言的集合(如Java的HashMap)
基本语法
var map 变量名 map[keytype]valuetype
PS:key,value的类型:bool,数字,string,指针,channel、还可以包含前几个类型的接口、结构体、数组
PS:key通常为int,string类型,value通常为数字(整数,浮点数)、string、map、结构体
PS:key:slice、map、function不可以
代码
- map的特点:
- map集合在使用前一定要make
- make的key-value是无序的
- key是不可以重复的,如果遇到重复,后一个value会替换前一个value
- value是可以重复的
func main() {
//定义map变量:
var a map[int]string
//只声明map内存是没有分配空间的
//必须通过make函数进行初始化,才会分配空间:
a = make(map[int]string, 10)
a[1990] = "张三"
a[1991] = "李四"
a[1992] = "王五"
a[1993] = "王五"
a[1992] = "赵六"
//输出集合
fmt.Println(a)
}
运行结果:
map[1990:张三 1991:李四 1992:赵六 1993:王五]
map的创建方式
func main() {
//方式1:
//定义map变量:
var a map[int]string
//只声明map内存是没有分配空间的
//必须通过make函数进行初始化,才会分配空间:
a = make(map[int]string, 10)
a[1990] = "张三"
a[1991] = "李四"
//输出集合
fmt.Println(a)
//方式2:可不设置长度
b := make(map[int]string)
b[1990] = "张三"
b[1991] = "李四"
fmt.Println(b)
//方式3:
c := map[int]string{
1990: "张三",
1991: "李四",
}
c[1992] = "王五"
fmt.Println(c)
}
map的操作
- 增加和更新操作:
map["key"] = value --> 如果key还没有就是增加,如果key存在就是修改 - 删除操作
delete(map,"key"),delete是一个内置函数,如果key存在,就删除该key-value,如果key不存在,不操作,但也不会报错 - 清空操作
- 如果我们要删除map的所有key,没有一个专门的方法一次删除,可以遍历一下key,逐个删除
- 或者map = make(...),make一个新的,让原来的成为垃圾,被gc回收
- 查找操作
value,bool = map[key]
value为返回的value,bool为是否返回,要么true,要么false
代码
func main() {
b := make(map[int]string)
//增加:
b[1990] = "张三"
b[1991] = "李四"
//修改:
b[1990] = "王五"
//删除
delete(b,1990)
delete(b,1998) //如果key值不存在也不会报错
fmt.Println(b)
//查找:
value, flag := b[19900]
fmt.Println(value)
fmt.Println(flag)
fmt.Println(b)
}
func main() {
b := make(map[int]string)
//增加:
b[1990] = "张三"
b[1991] = "李四"
b[1992] = "王五"
//获取长度
fmt.Println(len(b))
//遍历:
for k,v := range b {
fmt.Printf("key为:%v,value为%v \t",k, v)
}
//加深难度:
a := make(map[string]map[int]string)
//赋值:
a["班级1"] = make(map[int]string, 3)
a["班级1"][1990] = "张三"
a["班级1"][1991] = "李四"
a["班级1"][1992] = "王五"
a["班级2"] = make(map[int]string, 3)
a["班级2"][2001] = "小明"
a["班级2"][2002] = "小方"
a["班级2"][2003] = "小红"
for s, m := range a {
fmt.Println(s)
for k, v := range m {
fmt.Printf("学号为: %v,学生姓名为:%v\t",k,v)
}
}
}
面向对象
面向对象的引入
Golang语言面向对象编程说明
- Golang也支持面向对象编程(OOP),但是和传统的面向对象编程有区别,并不是纯粹的面向对象语言。所以我们说Golang支持面向对象编程特性是比较准确的。
- Golang没有类(class),Go语言的结构体(struct)和其他编程语言的类有同等的地位,你可以理解Golang是基于struct来实现OOP特性的。
- Golang面向对象编程非常简洁,去掉了传统OOP语言的方法重载、构造函数和析构函数、隐藏的this指针等等
- Golang仍然有面向对象编程的继承,封装和多态的特性,只是实现的方式和其他OOP语言不一样,比如继承:Golang没有extends关键字,继承是通过匿名字段来实现。
结构体的引入
- 利用变量处理对象
- 缺点:1、不利于数据的管理、维护。2、用变量管理太分散
- 结构体
// Teacher 定义老师的结构体,将老师中的各个属性,统一放入结构体中管理
type Teacher struct{
//变量名大写可访问这个属性
Name string
Age int
School string
}
结构体实例创建
- 方式1
func main() {
var t1 Teacher
t1.Name = "张三"
t1.Age = 45
t1.School = "清华大学"
fmt.Println(t1)
- 方式2
func main() {
var t1 Teacher = Teacher{"张三",45,"江阴学院"}
fmt.Println(t1)
}
- 方式3
func main() {
//创建老师结构体的实例,对象,变量
var t *Teacher = new(Teacher)
// t是指针,t其实指向的就是地址,应该给这个地址的指向的对象的字段赋值;
(*t).Name = "张三"
(*t).Age = 31 //*的作用:根据地址取值
//为了符合程序员的变成习惯.go提供了简化的赋值方式:
t.School = "江阴学院" //go编译器底层对t.School转化(*t).School = "江阴学院"
fmt.Println(*t)
}
- 方式4
func main() {
//创建老师结构体的实例,对象,变量
var t *Teacher = &Teacher{"张三",31,"江阴学院"}
fmt.Println(*t)
}
结构体之间转换
- 结构体是用户单独定义的类型,和其他类型进行转换时需要有完全相同的字段(名字、个数和类型)
type Student struct{
Age int
}
type Person struct{
Age int
}
func main() {
var s Student = Student{10}
var p Person = Person{10}
s = Student(p)
fmt.Println(s)
}
- 结构体进行type重新定义(相当于取别名),Golang认为是新的数据类型,但是互相可以强转
type Student struct{
Age int
}
type Stu Student
func main() {
var s1 Student = Student{19}
var s2 Stu = Stu{19}
s1 = Student(s2)
fmt.Println(s1)
fmt.Println(s2)
}