Golang学习笔记

Golang学习笔记

文章目录

  • Golang学习笔记
    • Go是啥?
    • Go语言的用途
    • Go语言的特点
    • Go基本语法
      • 基本数据类型
      • Go变量的定义
      • 查看数据类型
      • 查看字符的码值
      • 字符串
      • 数值类型转换
        • 注意事项
        • 基本类型转 string 类型
        • string 类型转基本数据类型
      • 指针
      • 数组
        • 数组的定义
        • 数组在内存布局
        • 四种初始化数组的方式
      • 切片
        • 切片是什么?
        • 切片的定义方式
        • 切片在内存中形式
        • 切片的遍历
      • Map
        • Map集合定义
        • Map集合的使用
        • Map操作
          • map 增加和更新
          • map 删除:
          • map 查找
          • map 遍历
        • 总结
      • 结构体
        • 结构体定义
        • 创建结构体变量
      • 方法
        • 方法的声明和调用
        • 方法与函数的区别
      • 继承
        • 属性继承
        • 方法继承和重写
      • 接口、多态
        • 接口的定义
        • 接口的继承与实现
        • 多态
        • 类型断言
      • 异常
        • 处理异常的方法

Go是啥?

Golang 简称 Go,是一个开源的编程语言。

Go语言的用途

Go 语言被设计成一门应用于搭载 Web 服务器,存储集群或类似用途的巨型中央服务器的系统编程语言。
Go 语言在用于高性能分布式系统开发中,无疑比大多数其它语言有着更高的开发效率。此外,它提供了海量并行的支持,这对于游戏服务端的开发而言也是不错的选择。

Go语言的特点

  • 优点
    • 简洁、快速、安全
    • 支持并发编程
    • 自动垃圾回收
    • 编译迅速
    • 开源
    • 便捷部署::go最终生成的是一个 可执行文件,不管你的程序依赖多少库,都会被打包进行,生成一个可执行文件,所以相比java庞大的jar库来说,他的部署非常方便,执行运行这个可执行文件就好了。
  • 缺点
    • 错误处理比较繁琐,需要写很多 err 判断

Go基本语法

基本数据类型

bool

String

整数

int8(-128 -> 127) int16(-32768 -> 32767) int32(-2,147,483,648 -> 2,147,483,647) int64(-9,223,372,036,854,775,808 -> 9,223,372,036,854,775,807)

无符号整数

uint8(0 -> 255) uint16(0 -> 65,535) uint32(0 -> 4,294,967,295) uint64(0 -> 18,446,744,073,709,551,615)

浮点型(IEEE-754 标准):

float32(± 1e-45 -> ± 3.4 * 1e38) float64(± 5 * 1e-324 -> 107 * 1e308)

uintptr 同指针,32位平台为4字节,64位八字节

byte 等价于uint8

rune 等价于uint32,单个unicode字符

复数类型:complex64,complex128

int 型是计算最快的一种类型。

整型的零值为 0,浮点型的零值为 0.0

结构化的(复合的),如:struct、array、slice、map、channel;

只描述类型的行为的,如:interface

Go变量的定义

package main
import(
	"fmt"
)
func main() {
	//1.指定变量类型,若不赋值,使用默认值
	var num int
	var num1 int = 1
	fmt.Println(num,num1)
	//2.根据值自行判定变量类型(类型推导),其实就是不写类型,根据你赋的值判断
	var num2  = 2
	fmt.Println(num2)
	//3.省略 var, 注意 :=左侧的变量不应该是已经声明过的,否则会导致编译错误
	num3 := 3
	fmt.Println(num3)
	//4.批量定义
	var n1, n2, n3 int
	fmt.Println(n1,n2,n3)
	fmt.Println("=======")
	var n4, name, n5 = 100, "tom",888
	fmt.Println("n4=",n4,"name=",name,"n5=",n5)
	fmt.Println("======")
	n6, name1, n7 := 6, "name1",7
	fmt.Println("n6=",n6,"name1=",name1,"n7=", n7)

}

查看数据类型

package main
import(
	"fmt"
	"unsafe"
)
func main() {
	//如何在程序查看某个变量的字节大小和数据类型 (使用较多)
	var i uint16 = 100
	fmt.Printf("i 的类型%T,占用的字节数:%d\n",i,unsafe.Sizeof(i))
	var i1 uint = 100
	fmt.Printf("i 的类型%T,占用的字节数:%d",i1,unsafe.Sizeof(i1))
}
i 的类型uint16,占用的字节数:2
i 的类型uint,占用的字节数:8

查看字符的码值

package main
import(
	"fmt"
)
func main() {
	var i byte = 'a'
	fmt.Printf("i对应的类型:%T,对应的字符:%c,对应的码值:%d\n", i, i, i)
	var i1= ','
	fmt.Printf("i对应的类型:%T,对应的字符:%c,对应的码值:%d\n", i1, i1, i1)
	var i2= '南'
	fmt.Printf("i对应的类型:%T,对应的字符:%c,对应的码值:%d\n", i2, i2, i2)
	//var i3 byte= '经' //报错:constant 32463 overflows byte
}
i对应的类型:uint8,对应的字符:a,对应的码值:97
i对应的类型:int32,对应的字符:,,对应的码值:44
i对应的类型:int32,对应的字符:南,对应的码值:21335

字符串

package main

import "fmt"

func main() {
	//字符串golang
	//双引号
	var str1  = "hello"
	//单引号将字符串完整格式都打出
	str2 := `
		pachage
		it is time;
		lets go;
	`
	fmt.Println(str1)
	fmt.Println(str2)
}
hello

		pachage
		it is time;
		lets go;

数值类型转换

表达式 T(v) 将值 v 转换为类型 T
T: 就是数据类型,比如 int32,int64,float32 等等
v: 就是需要转换的变量

注意事项

  1. Go 中,数据类型的转换可以是从 表示范围小–>表示范围大,也可以 范围大—>范围小
  2. 被转换的是变量存储的数据(即值),变量本身的数据类型并没有变化!

在转换中,比如将 int64 转成 int8 【-128—127】 ,编译时不会报错,只是转换的结果是按
溢出处理,和我们希望的结果不一样。 因此在转换时,需要考虑范围

基本类型转 string 类型

方式一:fmt.Sprintf("%参数", 表达式)

package main

import "fmt"

func main() {
	var i  = true
	var i1  = 1
	var i2  = 1.3
	var s1 = fmt.Sprintf("%v",i)
	fmt.Printf("i对应的类型:%T,对应的值%v\n",s1,s1)
	var s2 = fmt.Sprintf("%v",i1)
	fmt.Printf("i对应的类型:%T,对应的值%v\n",s2,s2)
	var s3 = fmt.Sprintf("%v",i2)
	fmt.Printf("i对应的类型:%T,对应的值%v\n",s3,s3)
	fmt.Println(s1+s2+s3)
}
i对应的类型:string,对应的值true
i对应的类型:string,对应的值1
i对应的类型:string,对应的值1.3
true11.3

方式二:使用 strconv 包的函数

package main

import (
	"fmt"
	"strconv"
)

func main() {
	var i  = true
	var i1  = 10
	var i2  = 1.3
	var s1 = strconv.FormatBool(i)
	fmt.Printf("i对应的类型:%T,对应的值%v\n",s1,s1)
	//整型需要转换成int64,还需指定对应的进制
	var s2 = strconv.FormatInt(int64(i1),10)
	fmt.Printf("i对应的类型:%T,对应的值%v\n",s2,s2)
	//f表示格式; 10表示小数位保留10位; 64表示float64
	var s3 = strconv.FormatFloat(i2,'f',10,64)
	fmt.Printf("i对应的类型:%T,对应的值%v\n",s3,s3)
	fmt.Println(s1+s2+s3)
}

i对应的类型:string,对应的值true
i对应的类型:string,对应的值10
i对应的类型:string,对应的值1.3000000000
true101.3000000000

string 类型转基本数据类型

使用时 strconv 包的函数

Golang学习笔记_第1张图片

注:返回的都是int64和float64,如不满足需求需要自己转换

注意事项

在将 String 类型转成 基本数据类型时,要确保 String 类型能够转成有效的数据,比如 我们可以把 “123” , 转成一个整数,但是不能把 “hello” 转成一个整数,如果这样做,Golang 直接将其转成 0 ,其它类型也是一样的道理. float => 0 bool => false

指针

Golang学习笔记_第2张图片

  1. 基本数据类型,变量存的就是值,也叫值类型;
  2. 获取变量的地址,用&,比如: var num int, 获取 num 的地址:#
  3. 指针类型,指针变量存的是一个地址,这个地址指向的空间存的才是值
    比如:var ptr *int = #
  4. 获取指针类型所指向的值,使用:*,比如:var ptr int, 使用ptr 获取 ptr 指向的值;

数组

数组可以存放多个同一类型数据。数组也是一种数据类型,在 Go 中,数组是值类型。

数组的定义

var 数组名 [数组大小]数据类型
var a [5]int
赋初值 a[0] = 1 a[1] = 30 …

数组在内存布局

Golang学习笔记_第3张图片

对上图的总结:

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

四种初始化数组的方式

func main() {
	var arr [2]int = [2]int{1,2}
	fmt.Println(arr)
	
	//这里[...]是固定写法,省去了指定数组长度
	var arr1 [2]int = [...]int{1,2}
	fmt.Println(arr1)
	var arr2  = [...]int{1,2}
	fmt.Println(arr2)
	
	//指定数组元素的顺序
	var arr3  = [...]int{1:1,0:2}
	fmt.Println(arr3)
	
	//通过类型推导
	 arr4  := [...]string{1:"tom",0:"susan"}
	fmt.Println(arr4)
}


切片

切片是什么?

  1. 切片是可变数组
  2. 切片的英文是 slice
  3. 切片是数组的一个引用,因此切片是引用类型,在进行传递时,遵守引用传递的机制。
  4. 切片的使用和数组类似,遍历切片、访问切片的元素和求切片长度 len(slice)都一样。
  5. 切片的长度是可以变化的,因此切片是一个可以动态变化数组。

切片的定义方式

方式一:

定义一个切片,然后让切片去引用一个已经创建好的数组。

slice :=arr[a:b:c]

a:起始位置
b:截取数据的结束位置 默认到末端)
c:截取后的容量位置 默认到末端)
slice的len = b - a
slice的cap = c - a
底层数组: 为原数组的下标a到下标b(不包括)的所有元素

func main() {
    //定义一个数组
	var arr = [...]string{"张学友","刘德华","黎明","郭富城","成龙","周星驰"}
    //定义切片,参数分别为:起始位置,终止位置,切片的容量
	slice :=arr[1:3:5]
	fmt.Printf("slice容量:%d,长度:%d,值:%v\n",cap(slice),len(slice),slice)
	fmt.Printf("slice地址:%p",slice)
}

slice容量:4,长度:2,值:[刘德华 黎明]
slice地址:0xc000084130

方式二:

通过 make 来创建切片
基本语法:var 切片名 []type = make([]type, len, [cap])

参数说明:
type: 就是数据类型
len : 大小
cap :指定切片容量,可选, 如果你分配了 cap,则要求 cap>=len.

func main() {
	slice :=make([]int,3,5)
	slice[0] = 100
	slice[1] = 200
	slice[2] = 300
	fmt.Printf("slice容量:%d,长度:%d,值:%v\n",cap(slice),len(slice),slice)
	fmt.Printf("slice地址:%p\n",slice)
	//slice[3] = 400 //runtime error: index out of range 报错
	slice = append(slice, 400)
	slice = append(slice, 500)
	fmt.Printf("slice容量:%d,长度:%d,值:%v\n",cap(slice),len(slice),slice)
	fmt.Printf("slice地址:%p\n",slice)
	slice = append(slice, 600)
	fmt.Printf("slice容量:%d,长度:%d,值:%v\n",cap(slice),len(slice),slice)
	fmt.Printf("slice地址:%p",slice)
}


slice容量:5,长度:3,值:[100 200 300]
slice地址:0xc000016120
slice容量:5,长度:5,值:[100 200 300 400 500]
slice地址:0xc000016120
slice容量:10,长度:6,值:[100 200 300 400 500 600]
slice地址:0xc000074000


与第一种方式的区别:

Golang学习笔记_第4张图片

方式三:

定义一个切片,直接指定具体数组,相当于直接把切片当数组用。

func main() {
	slice :=[]string{"张学友","刘德华","黎明","郭富城","成龙","周星驰"}
	fmt.Printf("slice容量:%d,长度:%d,值:%v\n",cap(slice),len(slice),slice)
	fmt.Printf("slice地址:%p",slice)
}

slice容量:6,长度:6,值:[张学友 刘德华 黎明 郭富城 成龙 周星驰]
slice地址:0xc000052180

切片在内存中形式

Golang学习笔记_第5张图片

切片的遍历

  • for 循环常规方式
  • 遍历for-range 结构遍历
func main() {
	slice :=[]string{"张学友","刘德华","黎明","郭富城","成龙","周星驰"}
	fmt.Println("常规方式")
	for i:=0; i<len(slice) ; i++  {
		fmt.Printf("%d ===> %v\n",i,slice[i])
	}
	fmt.Println("结构遍历")
	for index,value := range slice{
		fmt.Printf("%d ===> %v\n",index,value)
	}
}

Map

Go语言中的Map集合几乎和Java里面的差不多,但是Go语言里面,定义Map集合是不能使用的,需要手动用make方法分配内存。

Map集合定义

var a map[string]string
var a map[string]int
var a map[int]string
var a map[string]map[string]string

分配空间

func main() {
	var m = make(map[int]interface{},1)
	m[1]="A"
	m[2]="B"
	fmt.Printf("m地址:%p;m的值:%v",m,m)
}

Map集合的使用

方式一:

用make函数分配内存,并且指定长度。

func main() {
	var m = make(map[int]interface{},1)
	m[1]="A"
	m[2]="B"
	fmt.Printf("m地址:%p;m的值:%v",m,m)
}

m地址:0xc00005c150;m的值:map[1:A 2:B]

方式二:

不指定长度。

func main() {
	var m = make(map[int]interface{})
	m[1]="A"
	m[2]="B"
	fmt.Printf("m地址:%p;m的值:%v",m,m)
}

m地址:0xc00005c150;m的值:map[1:A 2:B]

方式三:

直接声明并赋值,注意最后要加上,(逗号)。

func main() {
	var m = map[int]interface{}{
		1:"A",
		2:"B",
	}
	fmt.Printf("m地址:%p;m的值:%v",m,m)
}

m地址:0xc000070150;m的值:map[2:B 1:A]

Map操作

map 增加和更新

map[“key”] = value //如果 key 还没有,就是增加,如果 key 存在就是修改。

map 删除:

delete(map,“key”) ,delete 是一个内置函数,如果 key 存在,就删除该 key-value,如果 key 不存在,
不操作,但是也不会报错。

如果我们要删除 map 的所有 key ,没有一个专门的方法一次删除,可以遍历一下 key, 逐个删除
或者 map = make(…),make 一个新的,让原来的成为垃圾,被 gc 回收

map 查找

value := m[key]

如果 m这个 map 中存在 key , 那么 返回 对应的value,否则返回 对应value类型的默认值

map 遍历
func main() {
	var m = map[int]interface{}{
		1:"A",
		2:"B",
	}

	fmt.Printf("m地址:%p;m的值:%v\n",m,m)
	//添加
	m[3]="C"
	fmt.Printf("m地址:%p;m的值:%v\n",m,m)
	//更新
	m[2]= "H"
	//删除
	delete(m,3)
	//遍历
	for key,value:= range m{
		fmt.Printf("key:%v ; value:%v\n",key,value)
	}
	fmt.Printf("m地址:%p;m的值:%v\n",m,m)
}

m地址:0xc00005c150;m的值:map[1:A 2:B]
m地址:0xc00005c150;m的值:map[1:A 2:B 3:C]
key:1 ; value:A
key:2 ; value:H
m地址:0xc00005c150;m的值:map[2:H 1:A]

总结

  1. map 在使用前一定要 make

  2. map 的 key 是不能重复,如果重复了,则以最后这个 key-value 为准;

  3. map 的 value 是可以相同的, map 的 key-value 是无序;

  4. map 是引用类型,遵守引用类型传递的机制,在一个函数接收 map,修改后,会直接修改原来
    的 map;

  5. map 的容量达到后,再想 map 增加元素,会自动扩容,并不会发生 panic,也就是说 map 能动

    态的增长 键值对(key-value);

  6. map 的 value 也经常使用 struct 类型,更适合管理复杂的数据(比前面 value 是一个 map 更好),比如 value 为 Student 结构体。

结构体

Golang语言面向对象编程的说明:

  1. Golang支持面向对象编程,但是不是纯粹面向对象编程;
  2. Golang 没有类(class),Go 语言的==结构体(struct)==和其它编程语言的类(class)有同等的地位,你可
    以理解 Golang 是基于 struct 来实现 OOP 特性的;
  3. Golang 面向对象编程非常简洁,去掉了传统 OOP 语言的继承、方法重载、构造函数和析构函
    数、隐藏的 this 指针等等;
  4. Golang 仍然有面向对象编程的继承,封装和多态的特性,只是实现的方式和其它 OOP 语言不
    一样,比如继承 :Golang 没有 extends 关键字,继承是通过匿名字段来实现;
  5. Golang也是面向接口编程的。

结构体定义

type 结构体名称 struct {
field1 type
field2 type
}

示例:

type Student struct {
Name string //字段
Age int //字段
Score float32
}

在创建一个结构体变量后,如果没有给字段赋值,都对应一个零值(默认值),规则同前面讲的
一样:

  1. 布尔类型是 false ,数值是 0 ,字符串是 “”;
  2. 数组类型的默认值和它的元素类型相关,比如 score [3]int 则为[0, 0, 0];
  3. 指针,slice,和 map 的零值都是 nil ,即还没有分配空间。

创建结构体变量

方式一

var person Person

方式二

var person Person = Person{}

方式三

var person *Person = new (Person)

方式四

var person *Person = &Person{}

示例:

type Person struct {
	Name string
	Age int
}
func main() {
	var p1 Person
	p1.Name="jerry"
	p1.Age=55
	fmt.Printf("p1的值:%v\n",p1)
	var p2 Person= Person{Name:"susan",Age:10}
	fmt.Printf("p2的值:%v\n",p2)
	var p3 *Person = new(Person)
	p3.Name="jeck"
	p3.Age=66
	fmt.Printf("p3的值:%v\n",p3)
	var p4 *Person= &Person{Name:"john",Age:88}
	fmt.Printf("p4的值:%v\n",p4)
}

p1的值:{jerry 55}
p2的值:{susan 10}
p3的值:&{jeck 66}
p4的值:&{john 88}

总结:

  1. 第 3 种和第 4 种方式返回的是 结构体指针

  2. 结构体指针访问字段的标准方式应该是:(*结构体指针).字段名 ,比如 (*person).Name = “tom” 3) 但 go 做了一个简化,也支持 结构体指针.字段名, 比如 person.Name = “tom”。更加符合程序员使用的习惯,go 编译器底层 对 person.Name 做了转化 (*person).Name。

  3. 结构体的所有字段在内存中是连续的

  4. 结构体是用户单独定义的类型,和其它类型进行转换时需要有完全相同的字段(名字、个数和类
    型)

  5. 结构体进行 type 重新定义(相当于取别名),Golang 认为是新的数据类型,但是相互间可以强转

    type Person struct {
    	Name string
    	Age int
    }
    type ps Person
    func main() {
    	var p1 ps
    	p1.Name="jerry"
    	p1.Age=55
    	fmt.Printf("p1的值:%v\n",p1)
    	var p2 Person= Person(p1)
    	fmt.Printf("p2的值:%v\n",p2)
    }
    
    
  6. struct 的每个字段上,可以写上一个 tag, 该 tag 可以通过反射机制获取,常见的使用场景就是序
    列化和反序列化。

方法

Go语言里面也和java一样,对应的结构体可以有自己的方法。

方法的声明和调用

type Person struct {
	Name string
	Age int
}

func (p Person) hello() {
	fmt.Printf("hello ! my name is %v",p.Name)
}
func main() {
	var p Person
	p.Name="jerry"
	p.Age=55
	p.hello()
}

  1. func (p Person) hello() {} 表示 Person 结构体有一方法,方法名为 hello;
  2. (p Person) 体现 hello方法是和 Person 类型绑定的;
  3. hello方法和 Person 类型绑定;
  4. hello方法只能通过 Person 类型的变量来调用,而不能直接调用,也不能使用其它类型变量来调
    用;
  5. func (p Person) hello() {}… p 表示哪个 Person 变量调用,这个 p 就是它的副本, 这点和函数传参非
    常相似;
  6. p 这个名字,有程序员指定,不是固定, 比如修改成 person 也是可以。

方法是可以有参数、返回值,比如:

func (p Person) getSum(a int, b int) (c int) {
	c = a + b
	return 
}

也可以写成:

func (p Person) getSum(a int, b int) int {
	return a + b
}

注意 不能返回==局部变量地址值;因为函数调用结束,栈帧释放,局部变量的地址,不再受系统保护,随时可能分配给其他程序,可以返回局部变量==。

方法与函数的区别

type Circle struct {
	R float64
}

func (c Circle)getArea() float64 {
	return math.Pi * math.Pow(c.R,2)
}
func main() {
	var c = Circle{R: 2}
	area := c.getArea()
	fmt.Printf("面积:%v",area)
}

  1. 调用方式不一样
    函数的调用方式: 函数名(实参列表)
    方法的调用方式: 变量.方法名(实参列表)
  2. 对于普通函数,接收者为值类型时,不能将指针类型的数据直接传递,反之亦然;
  3. 对于方法(如 struct 的方法),接收者为值类型时,可以直接用指针类型的变量调用方法,反
    过来同样也可以。

继承

属性继承

在Go中使用的是匿名属性,来实现继承的;即将父类作为子类的匿名属性。

package main

import "fmt"

type Person struct {
	id int
	name string
	age  int
}

type Student struct {
	Person
	id int
	score     int
	className string
}

func main() {
	var (
		s Student
	)
	s = Student{
		Person{name: "susan", age: 23,id:123},
		456,
		58,
		"302班"}

	fmt.Printf("ID:%d\n姓名:%s\n年龄:%d\n班级:%s\n分数:%d",
		s.id,s.name,s.age,s.className,s.score)
}

注意:在Student对象中可以直接使用父类Person的属性,如果父类和子类中有重复字段,则优先使用子类自身的属性

方法继承和重写

方法的继承和属性的继承是类似的,当一个类是另一个类的匿名属性时即可直接使用该类的方法

package main

import "fmt"

type Person struct {
	id int
	name string
	age  int
}
//父类say方法
func (p Person)say(msg string)  {
	fmt.Printf("[person] %s say: '%s'",p.name,msg)
}
//student子类say方法
func (s Student)say(msg string){
	fmt.Printf("[student] %s say: '%s'",s.name,msg)
}

type Student struct {
	Person
	id int
	score     int
	className string
}

func main() {
	var (
		s Student
	)
	s = Student{
		Person{name: "susan", age: 23,id:123},
		456,
		58,
		"302班"}

	fmt.Printf("ID:%d\n姓名:%s\n年龄:%d\n班级:%s\n分数:%d\n",
		s.id,s.name,s.age,s.className,s.score)
	s.say("hello world")
}

ID:456
姓名:susan
年龄:23
班级:302班
分数:58
[student] susan say: 'hello world'

方法的重写,在语法格式上有一定的要求,即方法名,参数,返回值类型都必须一样,但绑定的对象不再是父类而是子类本身。

接口、多态

接口的定义

type annimal interface {
	eat()
	sleep()
	run()
}

接口的继承与实现

接口的继承与实现也是将被继承和实现的接口以匿名属性传入即可。

package main

import "fmt"

type annimal interface {
	eat()
	sleep()
	run()
}

type cat interface {
	annimal
	Climb()
}

type HelloKitty struct {
	cat
}

func (h HelloKitty)eat(){
	fmt.Println("eat cake!!")
}

func main() {
	var a annimal
	a = HelloKitty{}
	a.eat()
}

多态

package main

import "fmt"

type annimal interface {
	eat()
	sleep()
	run()
}

type cat interface {
	annimal
	Climb()
}
type dog interface {
	annimal
}

type HelloKitty struct {
	cat
}
type husky struct {
	dog
}

func (h HelloKitty)eat(){
	fmt.Println("eat cake!!")
}

func (h husky)eat(){
	fmt.Println("eat bone!!")
}

func test(a annimal){
	a.eat()
}

func main() {
	var a annimal
	a = HelloKitty{}
	test(a)
	a = husky{}
	test(a)
}

test方法中传入了一个animal对象,在实际调用时会因传入对象的不同而得到不同的效果
结果:

eat cake!!
eat bone!!

类型断言

判断具体的类型,在go中可以使用 对象.(指定的类型) 判断改对象是否时指定的类型。

func main() {
	var a annimal
	a = HelloKitty{}
	var b annimal
	b = husky{}

	var animals [2]annimal =[...]annimal{a,b}

	for _,v :=range animals{
        //注意这里!================================================
		if data,ok :=v.(husky);ok{
			data.eat()
			fmt.Println("this is wangcai : ")
		}
		if data,ok :=v.(HelloKitty);ok{
			data.eat()
			fmt.Println("this is HelloKitty : ")
		}
	}

}

==注意:==if条件中的data,ok :=v.(husky);ok,v.(husky)返回了两个值——v和bool类型,如果v是husky类型,那么将v的对象传递给data,true传递给ok,这样弥补了一个问题:问题的链接。

异常

异常分为:

  1. 编译时异常:在编译时抛出的异常,编译不通过,语法使用错误,符号填写错误等等。。。
  2. 运行时异常:在程序运行时抛出的异常,这个才是我们将要说的,程序运行时,有很多状况发生,例如:让用户输入一个数字,可用户偏偏输入一个字符串,导致的异常,数组的下标越界,空指针等等。。。。

处理异常的方法

方法一:添加判断

这种是比较常见的作法,将每种异常都考虑到,然后做出响应的处理

package main

import (
	"errors"
	"fmt"
)

func test(a int,b int) (int,error){
	var err error
	if a == 0{
		return 0,errors.New("a 不可以为0!!")
	}
	a = b / a
	return a,err
}

func main() {
	i,err := test(0, 1)
	if err ==nil{
		fmt.Println("====main方法正常结束!!====",i)
	}else{
		fmt.Println(err)
	}
}

方法二:捕捉异常

异常的场景会有很多,会很难统计出来,或统计出来后出来很麻烦;此时我们就要对异常进行捕捉
这里将用到defer和recover
defer:延时执行,即在方法执行结束(出现异常而结束或正常结束)时执行
recover:恢复的意思,如果是异常结束程序不会中断,返回异常信息,可以根据异常来做出相应的处理
recover必须放在defer的函数中才能生效

package main

import (
	"errors"
	"fmt"
)

func test(a int,b int) (int,error){
	var err error
	if a == 0{
		return 0,errors.New("a 不可以为0!!")
	}
	a = b / a
	return a,err
}

func main() {
	i,err := test(0, 1)
	if err ==nil{
		fmt.Println("====main方法正常结束!!====",i)
	}else{
		fmt.Println(err)
	}
}

方法三:手动抛出异常

看到异常总觉得很讨厌,但有的时候它能帮助我们解决很多麻烦事,比如用户输入错误,就应该抛出异常,可以让这个异常一层层的返回给调用方的程序,使其不能继续执行,从而起到保护后面业务的目的,也简化了错误信息的传递,例如:

一个人取钱,账户只有100元,他输入了200元,从输入到账户扣款经过了20个方法调用,那总不能将这个err传递这么多次,传到起始的调用处吧?,后面的扣款业务也不能执行的

package main

import (
	"errors"
	"fmt"
)

func test(a int) int {
	i:=100 - a
	if i<0{
		panic(errors.New("账户金额不足!!!!"))
	}
	fmt.Println("=======账户扣款=====")
	return i
}

func main() {
	i := test(101)
	fmt.Println("====main方法正常结束!!====余额:",i)
}

panic: 账户金额不足!!!!

goroutine 1 [running]:

你可能感兴趣的:(Golang学习笔记)