Go语言笔记(四)

错误处理

错误处理/捕获机制:

  • 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)
}
# Go入门  

评论

公众号:mumuser

企鹅群:932154986

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×