九、错误处理
1.defer+recover机制处理异常错误
展示错误:
![image-20230907111853394](/i/l/?n=23&i=blog/2319323/202309/2319323-20230907233734931-697607634.png)
发现:程序中出现错误/恐慌以后,程序被中断,无法继续执行。
错误处理/捕获机制:
内置函数recover:
![image-20230907111929849](/i/l/?n=23&i=blog/2319323/202309/2319323-20230907233734910-2022130242.png)
![image-20230907111939200](/i/l/?n=23&i=blog/2319323/202309/2319323-20230907233735208-822894550.png)
2.自定义错误
需要调用errors包下的New函数:函数返回error类型
![image-20230907112050176](/i/l/?n=23&i=blog/2319323/202309/2319323-20230907233735054-337967038.png)
![image-20230907112101527](/i/l/?n=23&i=blog/2319323/202309/2319323-20230907233735592-1130484412.png)
3.panic
有一种情况:程序出现错误以后,后续代码就没有必要执行,想让程序中断,退出程序:
借助:builtin包下内置函数:panic
![image-20230907112123210](/i/l/?n=23&i=blog/2319323/202309/2319323-20230907233734950-1781834691.png)
![image-20230907112131779](/i/l/?n=23&i=blog/2319323/202309/2319323-20230907233735595-343529575.png)
十、数组
1.使用
数组定义格式:
var 数组名 [数组大小]数据类型
例如:
var scores [5]int
package main
import "fmt"
func main(){
//实现的功能:给出五个学生的成绩,求出成绩的总和,平均数:
//给出五个学生的成绩:--->数组存储:
//定义一个数组:
var scores [5]int
//将成绩存入数组:
scores[0] = 95
scores[1] = 91
scores[2] = 39
scores[3] = 60
scores[4] = 21
//求和:
//定义一个变量专门接收成绩的和:
sum := 0
for i := 0;i < len(scores);i++ {//i: 0,1,2,3,4
sum += scores[i]
}
//平均数:
avg := sum / len(scores)
//输出
fmt.Printf("成绩的总和为:%v,成绩的平均数为:%v",sum,avg)
}
2.内存分析
![](/i/l/?n=23&i=blog/2319323/202309/2319323-20230907233735370-593882382.png)
赋值内存(数组是值类型,在栈中开辟内存)
![image-20230907114627864](/i/l/?n=23&i=blog/2319323/202309/2319323-20230907233735391-921636106.png)
3.数组的遍历
【1】普通for循环
【2】键值循环
(键值循环) for range结构是Go语言特有的一种的迭代结构,在许多情况下都非常有用,for range 可以遍历数组、切片、字符串、map 及通道,for range 语法上类似于其它语言中的 foreach 语句,一般形式为:
for key, val := range coll {
...
}
注意:
(1)coll就是你要的数组
(2)每次遍历得到的索引用key接收,每次遍历得到的索引位置上的值用val
(3)key、value的名字随便起名 k、v key、value
(4)key、value属于在这个循环中的局部变量
(5)你想忽略某个值:用_就可以了:
![image-20230907114756788](/i/l/?n=23&i=blog/2319323/202309/2319323-20230907233735321-2107501213.png)
package main
import "fmt"
func main(){
//实现的功能:给出五个学生的成绩,求出成绩的总和,平均数:
//给出五个学生的成绩:--->数组存储:
//定义一个数组:
var scores [5]int
//将成绩存入数组:(循环 + 终端输入)
for i := 0; i < len(scores);i++ {//i:数组的下标
fmt.Printf("请录入第个%d学生的成绩",i + 1)
fmt.Scanln(&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,value := range scores {
fmt.Printf("第%d个学生的成绩为:%d\n",key + 1,value)
}
}
结果:
![image-20230907114916858](/i/l/?n=23&i=blog/2319323/202309/2319323-20230907233735597-470195411.png)
4.数组的初始化操作
package main
import "fmt"
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)
}
5.注意事项
【1】长度属于类型的一部分 :
![image-20230907114958806](/i/l/?n=23&i=blog/2319323/202309/2319323-20230907233735415-1673253130.png)
【2】Go中数组属值类型,在默认情况下是值传递,因此会进行值拷贝。
![image-20230907115006602](/i/l/?n=23&i=blog/2319323/202309/2319323-20230907233735469-361555717.png)
【3】如想在其它函数中,去修改原来的数组,可以使用引用传递(指针方式)。
![image-20230907115012762](/i/l/?n=23&i=blog/2319323/202309/2319323-20230907233735439-2054368579.png)
6.二维数组
二维数组有初始值,
![image-20230907170553154](/i/l/?n=23&i=blog/2319323/202309/2319323-20230907233735410-1372057408.png)
初始化:
![image-20230907170712976](/i/l/?n=23&i=blog/2319323/202309/2319323-20230907233735360-1631857302.png)
遍历
package main
import "fmt"
func main(){
//定义二维数组:
var arr [3][3]int = [3][3]int{{1,4,7},{2,5,8},{3,6,9}}
fmt.Println(arr)
fmt.Println("------------------------")
//方式1:普通for循环:
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("------------------------")
//方式2:for range循环:
for key,value := range arr {
for k,v := range value {
fmt.Printf("arr[%v][%v]=%v\t",key,k,v)
}
fmt.Println()
}
}
十一、切片
【1】切片(slice)是golang中一种特有的数据类型
【2】数组有特定的用处,但是却有一些呆板(数组长度固定不可变),所以在 Go 语言的代码里并不是特别常见。相对的切片却是随处可见的,切片是一种建立在数组类型之上的抽象,它构建在数组之上并且提供更强大的能力和便捷。
【3】切片(slice)是对数组一个连续片段的引用,所以切片是一个引用类型。这个片段可以是整个数组,或者是由起始和终止索引标识的一些项的子集。需要注意的是,终止索引标识的项不包括在切片内。切片提供了一个相关数组的动态窗口。
1.语法
var 切片名 []类型 = 数组的一个片段引用
![image-20230907213036099](/i/l/?n=23&i=blog/2319323/202309/2319323-20230907233735707-1704396510.png)
2.内存分析
切片有3个字段的数据结构:一个是指向底层数组的指针,一个是切片的长度,一个是切片的容量。
![image-20230907213225615](/i/l/?n=23&i=blog/2319323/202309/2319323-20230907233735457-1392663811.png)
3.定义
【1】方式1:定义一个切片,然后让切片去引用一个已经创建好的数组。
![image-20230907213250992](/i/l/?n=23&i=blog/2319323/202309/2319323-20230907233735464-776432898.png)
【2】方式2:通过make内置函数来创建切片。基本语法: var切片名[type = make([], len,[cap])
![image-20230907213257916](/i/l/?n=23&i=blog/2319323/202309/2319323-20230907233735595-313733691.png)
make底层创建一个数组,对外不可见,所以不可以直接操作这个数组,要通过slice去间接的访问各个元素,不可以直接对数组进行维护/操作
【3】方式3:定一个切片,直接就指定具体数组,使用原理类似make的方式。
4.遍历
【1】方式1:for循环常规方式遍历
【2】方式2:for-range 结构遍历切片
package main
import "fmt"
func main(){
//定义切片:
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)
}
}
5.注意事项
【1】切片定义后不可以直接使用,需要让其引用到一个数组,或者make一个空间供切片来使用
![image-20230907213432846](/i/l/?n=23&i=blog/2319323/202309/2319323-20230907233735599-1470814385.png)
【2】切片使用不能越界。
![image-20230907213437814](/i/l/?n=23&i=blog/2319323/202309/2319323-20230907233735593-1979904088.png)
【3】简写方式:
- 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[:]
【4】切片可以继续切片
【5】切片可以动态增长
package main
import "fmt"
func main(){
//定义数组:
var intarr [6]int = [6]int{1,4,7,3,6,9}
//定义切片:
var slice []int = intarr[1:4] //4,7,3
fmt.Println(len(slice))
slice2 := append(slice,88,50)
fmt.Println(slice2) //[4 7 3 88 50]
fmt.Println(slice)
//底层原理:
//1.底层追加元素的时候对数组进行扩容,老数组扩容为新数组:
//2.创建一个新数组,将老数组中的4,7,3复制到新数组中,在新数组中追加88,50
//3.slice2 底层数组的指向 指向的是新数组
//4.往往我们在使用追加的时候其实想要做的效果给slice追加:
slice = append(slice,88,50)
fmt.Println(slice)
//5.底层的新数组 不能直接维护,需要通过切片间接维护操作。
}
可以通过append函数将切片追加给切片:
【6】切片的拷贝:
package main
import "fmt"
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)
}
十二、映射
1.map的引入
【1】映射(map), Go语言中内置的一种类型,它将键值对相关联,我们可以通过键 key来获取对应的值 value。 类似其它语言的集合
【2】基本语法
var map变量名 map[keytype]valuetype
key、value的类型:bool、数字、string、指针、channel 、还可以是只包含前面几个类型的接口、结构体、数组
key通常为int 、string类型,value通常为数字(整数、浮点数)、string、map、结构体
key:slice、map、function不可以
【3】代码:
map的特点:
(1)map集合在使用前一定要make
(2)map的key-value是无序的
(3)key是不可以重复的,如果遇到重复,后一个value会替换前一个value
(4)value可以重复的
package main
import "fmt"
func main(){
//定义map变量:
var a map[int]string
//只声明map内存是没有分配空间
//必须通过make函数进行初始化,才会分配空间:
a = make(map[int]string,10) //map可以存放10个键值对
//将键值对存入map中:
a[20095452] = "张三"
a[20095387] = "李四"
a[20097291] = "王五"
a[20095387] = "朱六"
a[20096699] = "张三"
//输出集合
fmt.Println(a)
}
2.创建
package main
import "fmt"
func main(){
//方式1:
//定义map变量:
var a map[int]string
//只声明map内存是没有分配空间
//必须通过make函数进行初始化,才会分配空间:
a = make(map[int]string,10) //map可以存放10个键值对
//将键值对存入map中:
a[20095452] = "张三"
a[20095387] = "李四"
//输出集合
fmt.Println(a)
//方式2:
b := make(map[int]string)
b[20095452] = "张三"
b[20095387] = "李四"
fmt.Println(b)
//方式3:
c := map[int]string{
20095452 : "张三",
20098765 : "李四",
}
c[20095387] = "王五"
fmt.Println(c)
}
3.操作
1】增加和更新操作:
map["key"]= value ——》 如果key还没有,就是增加,如果key存在就是修改。
【2】删除操作:
delete(map,"key") , delete是一个内置函数,如果key存在,就删除该key-value,如果k的y不存在,不操作,但是也不会报错
【3】清空操作:
(1)如果我们要删除map的所有key ,没有一个专门的方法一次删除,可以遍历一下key,逐个删除
(2)或者map = make(...),make一个新的,让原来的成为垃圾,被gc回收
【4】查找操作:
value ,bool = map[key]
value为返回的value,bool为是否返回 ,要么true 要么false
package main
import "fmt"
func main(){
//定义map
b := make(map[int]string)
//增加:
b[20095452] = "张三"
b[20095387] = "李四"
//修改:
b[20095452] = "王五"
//删除:
delete(b,20095387)
delete(b,20089546)
fmt.Println(b)
//查找:
value,flag := b[200]
fmt.Println(value)
fmt.Println(flag)
}
【5】获取长度:len函数
【6】遍历:for-range
package main
import "fmt"
func main(){
//定义map
b := make(map[int]string)
//增加:
b[20095452] = "张三"
b[20095387] = "李四"
b[20098833] = "王五"
//获取长度:
fmt.Println(len(b))
//遍历:
for k,v := range b {
fmt.Printf("key为:%v value为%v \t",k,v)
}
fmt.Println("---------------------------")
//加深难度:
a := make(map[string]map[int]string)
//赋值:
a["班级1"] = make(map[int]string,3)
a["班级1"][20096677] = "露露"
a["班级1"][20098833] = "丽丽"
a["班级1"][20097722] = "菲菲"
a["班级2"] = make(map[int]string,3)
a["班级2"][20089911] = "小明"
a["班级2"][20085533] = "小龙"
a["班级2"][20087244] = "小飞"
for k1,v1:= range a {
fmt.Println(k1)
for k2,v2:= range v1{
fmt.Printf("学生学号为:%v 学生姓名为%v \t",k2,v2)
}
fmt.Println()
}
}
十三、对象
1.对象的引入
【1】Golang语言面向对象编程说明:
(1)Golang也支持面向对象编程(OOP),但是和传统的面向对象编程有区别,并不是纯粹的面向对象语言。所以我们说Golang支持面向对象编程特性是比较准确的。
(2)Golang没有类(class),Go语言的结构体(struct)和其它编程语言的类(class)有同等的地位,你可以理解Gelang是基于struct来实现OOP特性的。
(3)Golang面向对象编程非常简洁,去掉了传统OOP语言的方法重载、构造函数和析构函数、隐藏的this指针等等
(4)Golang仍然有面向对象编程的继承,封装和多态的特性,只是实现的方式和其它OOP语言不一样,比如继承:Golang没有extends 关键字,继承是通过匿名字段来实现。
【2】结构体的引入:
具体的对象:
一位老师:郜宇博老师: 姓名:郜宇博 年龄:22岁 性别 :男 ......
可以使用变量来处理:
package main
import "fmt"
func main(){
//郜宇博老师: 姓名:郜宇博 年龄:22岁 性别 :男 ......
var name string = "郜宇博"
var age int = 22
var sex string = "男"
//马士兵老师:
var name2 string = "马士兵"
var age2 int = 45
var sex2 string = "男"
}
缺点:
(1)不利于数据的管理、维护
(2)老师的很多属性属于一个对象,用变量管理太分散了
2.结构体
代码:
package main
import "fmt"
//定义老师结构体,将老师中的各个属性 统一放入结构体中管理:
type Teacher struct{
//变量名字大写外界可以访问这个属性
Name string
Age int
School string
}
func main(){
//创建老师结构体的实例、对象、变量:
var t1 Teacher // var a int
fmt.Println(t1) //在未赋值时默认值:{ 0 }
t1.Name = "马士兵"
t1.Age = 45
t1.School = "清华大学"
fmt.Println(t1)
fmt.Println(t1.Age + 10)
}
3.结构体的创建
1.直接创建
![image-20230907214202449](/i/l/?n=23&i=blog/2319323/202309/2319323-20230907233735595-1305916094.png)
2.附带初始值
![image-20230907214222933](/i/l/?n=23&i=blog/2319323/202309/2319323-20230907233735751-337257948.png)
3.结构体指针创建
![image-20230907214243724](/i/l/?n=23&i=blog/2319323/202309/2319323-20230907233735584-1055352415.png)
![image-20230907214249171](/i/l/?n=23&i=blog/2319323/202309/2319323-20230907233735951-143298109.png)
4.结构体之间转换
【1】结构体是用户单独定义的类型,和其它类型进行转换时需要有完全相同的字段(名字、个数和类型)
package main
import "fmt"
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)
fmt.Println(p)
}
【2】结构体进行type重新定义(相当于取别名),Golang认为是新的数据类型,但是相互间可以强转
package main
import "fmt"
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)
}
5.方法的引入
【1】方法是作用在指定的数据类型上、和指定的数据类型绑定,因此自定义类型,都可以有方法,而不仅仅是struct
【2】方法的声明和调用格式:
声明:
type A struct {
Num int
}
func (a A) test() {
fmt.Println(a.Num)
}
调用:
var a A
a.test()
(1)func (a A) test()相当于A结构体有一个方法叫test
(2)(a A)体现方法test和结构体A绑定关系
(3)代码层面:
![image-20230907233032350](/i/l/?n=23&i=blog/2319323/202309/2319323-20230907233735616-1035753655.png)
注意:
(1)test方法中参数名字随意起
(2)结构体Person和test方法绑定,调用test方法必须靠指定的类型:Person
(3)如果其他类型变量调用test方法一定会报错。
(4)结构体对象传入test方法中,值传递,和函数参数传递一致。
![image-20230907233044542](/i/l/?n=23&i=blog/2319323/202309/2319323-20230907233735780-56717182.png)
6.方法的注意事项
(1)结构体类型是值类型,在方法调用中,遵守值类型的传递机制,是值拷贝传递方式
(2)如程序员希望在方法中,改变结构体变量的值,可以通过结构体指针的方式来处理
![image-20230907233123227](/i/l/?n=23&i=blog/2319323/202309/2319323-20230907233735739-484037772.png)
![image-20230907233131792](/i/l/?n=23&i=blog/2319323/202309/2319323-20230907233735418-1600103395.png)
我们写程序的时候,可以直接简化:
![image-20230907233136349](/i/l/?n=23&i=blog/2319323/202309/2319323-20230907233735637-1697131946.png)
底层编译器做了优化,底层会自动帮我们加上 & *
(3)Golang中的方法作用在指定的数据类型上的,和指定的数据类型绑定,因此自定义类型,都可以有方法,而不仅仅是struct,比如int , float32等都可以有方法
package main
import "fmt"
type integer int
func (i integer) print(){
i = 30
fmt.Println("i = ",i)
}
func (i *integer) change(){
*i = 30
fmt.Println("i = ",*i)
}
func main(){
var i integer = 20
i.print()
i.change()
fmt.Println(i)
}
(4)方法的访问范围控制的规则,和函数一样。方法名首字母小写,只能在本包访问,方法首字母大写,可以在本包和其它包访问。
(5)如果一个类型实现了String()这个方法,那么fmt.Println默认会调用这个变量的String()进行输出
以后定义结构体的话,常定义String()作为输出结构体信息的方法,在fmt.Println会自动调用
package main
import "fmt"
type Student struct{
Name string
Age int
}
func (s *Student) String() string{
str := fmt.Sprintf("Name = %v , Age = %v",s.Name,s.Age)
return str
}
func main(){
stu := Student{
Name : "丽丽",
Age : 20,
}
//传入地址,如果绑定了String方法就会自动调用
fmt.Println(&stu)
}
7.方法和函数的区别
【1】绑定指定类型:
方法:需要绑定指定数据类型
函数:不需要绑定数据类型
【2】调用方式不一样:
函数的调用方式:
函数名(实参列表)
方法的调用方式:变量.方法名(实参列表)
package main
import "fmt"
type Student struct{
Name string
}
//定义方法:
func (s Student) test01(){
fmt.Println(s.Name)
}
//定义函数:
func method01(s Student){
fmt.Println(s.Name)
}
func main(){
//调用函数:
var s Student = Student{"丽丽"}
method01(s)
//方法调用:
s.test01()
}
【3】对于函数来说,参数类型对应是什么就要传入什么。
package main
import "fmt"
type Student struct{
Name string
}
//定义函数:
func method01(s Student){
fmt.Println(s.Name)
}
func method02(s *Student){
fmt.Println((*s).Name)
}
func main(){
var s Student = Student{"丽丽"}
method01(s)
//method01(&s)错误
method02(&s)
//method02(s)错误
}
【4】对于方法来说,接收者为值类型,可以传入指针类型,接受者为指针类型,可以传入值类型。
package main
import "fmt"
type Student struct{
Name string
}
//定义方法:
func (s Student) test01(){
fmt.Println(s.Name)
}
func (s *Student) test02(){
fmt.Println((*s).Name)
}
func main(){
var s Student = Student{"丽丽"}
s.test01()
(&s).test01()//虽然用指针类型调用,但是传递还是按照值传递的形式
(&s).test02()
s.test02()//等价
}
8.创建结构体时初始化
【1】方式1:按照顺序赋值操作
缺点:必须按照顺序有局限性
【2】方式2:按照指定类型
【3】方式3:想要返回结构体的指针类型
![image-20230907233304075](/i/l/?n=23&i=blog/2319323/202309/2319323-20230907233735772-61801027.png)