本文省去基本定义
文章目录
- 1. 函数
- 1.1 闭包
- 1.2 延迟执行语句(defer)
- 1.3 处理运行时发生的错误
- 1.4 宕机(panic)——程序终止运行
- 1.5 宕机恢复(recover)——防止程序崩溃
- 2. 结构体
- 2.1 结构体的实例化
- 2.2 方法
- 2.3 结构体的继承
- 2.4 实现接口
- 2.5 函数和方法的区别
1. 函数
1.1 闭包
闭包是一个函数和其相关环境组合的一个整体
函数+引用环境=闭包
能通过指针调用匿名函数或者函数是实现闭包的关键
func main() {
accumulate := Accumulate(1) //返回一个指向返回函数的指针
fmt.Printf("%T", accumulate)
fmt.Println(accumulate())//通过指针调用Accumulate内的匿名函数
fmt.Println(accumulate())
}
func Accumulate(i int) func() int {
return func() int {
i++
return i
}
}
1.2 延迟执行语句(defer)
先被defer的语句最后被执行,最后被defer的语句,最先被执行。
用法: 使用延迟执行语句在函数退出时释放资源
func main() {
defer fmt.Println("first")
fmt.Println("second")
defer fmt.Println("third")
}
1.3 处理运行时发生的错误
Go语言的设计者认为其他语言的异常机制已被过度使用,上层逻辑需要为函数发生的异常付出太多的资源。
error是Go系统声明的接口类型,代码如下:
type error interface{
Error() string
}
所有符合Error() string格式的方法,都能实现错误接口。Error()方法返回错误的具体描述,使用者可以通过这个字符串知道发生了什么错误。
func main() {
fmt.Println(div(0, 1))
fmt.Println(div(0, 0))
}
func div(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("made,除了个0!")
}
return a / b, nil
}
1.4 宕机(panic)——程序终止运行
手动触发panic
func main() {
panic("触发异常。。。")
}
其次:
func main() {
defer fmt.Println("beforepanic")
panic("触发异常。。。")
defer fmt.Println("afterpanic")
}
当panic()触发的宕机发生时,panic()后面的代码将不会被运行,但是在panic()函数前面已经运行过的defer语句依然会在宕机发生时发生作用。
1.5 宕机恢复(recover)——防止程序崩溃
这玩意真难用:
例1:
func main() {
defer func() {
//recover() //可以打印panic的错误信息
//fmt.Println(recover())
if err := recover(); err != nil { //产生了panic异常
fmt.Println(err)
}
}() //别忘了(), 调用此匿名函数
panic("0000")
}
例2:
package main
import "fmt"
func testa() {
fmt.Println("aaaaaaaaaaaaaaaaa")
}
func testb(x int) {
//设置recover,recover只能放在defer后面使用
defer func() {
//recover() //可以打印panic的错误信息
//fmt.Println(recover())
if err := recover(); err != nil { //产生了panic异常
fmt.Println(err)
}
}() //别忘了(), 调用此匿名函数
var a [10]int
a[x] = 111 //当x为20时候,导致数组越界,产生一个panic,导致程序崩溃
}
func testc() {
fmt.Println("cccccccccccccccccc")
}
func main() {
testa()
testb(20) //当值是1的时候,就不会越界,值是20的时候,就会越界报错。
testc()
}
2. 结构体
2.1 结构体的实例化
- 结构体本身是一种类型,可以像整型、字符串等类型一样,以var的方式声明结构体即可完成实例化。
type Student struct {
name string
age int
}
func main() {
var student Student
fmt.Printf("%T\n",student)
fmt.Printf("%T\n",&student)
student.name="lisi"
student.age=11
fmt.Println(student)
}
- Go语言中,还可以使用new关键字对类型(包括结构体、整型、浮点数、字符串等)进行实例化,结构体在实例化后会形成指针类型的结构体。
func main() {
student := new(Student)
fmt.Printf("%T\n",student)
fmt.Printf("%T\n",&student)
student.name="lisi"
student.age=11
fmt.Println(student)
}
在Go语言中,访问结构体指针的成员变量时可以继续使用“.”。这是因为Go语言为了方便开发者访问结构体指针的成员变量,使用了语法糖(Syntactic sugar)技术,将ins.Name形式转换为(*ins).Name。
- 在Go语言中,对结构体进行“&”取地址操作时,视为对该类型进行一次new的实例化操作。
func main() {
//student是一个指针
student := &Student{
name: "zhansan",
age: 0,
}
fmt.Println(student)
fmt.Printf("%T\n", student)
fmt.Printf("%T\n", &student)
fmt.Printf("%T\n", *student)
student.name = "lisi"
student.age = 11
fmt.Println(student)
}
2.2 方法
Go语言中的方法(Method)是一种作用于特定类型变量的函数。这种特定类型变量叫做接收器(Receiver)。
这里多了一个接收器的概念:
接收器——方法作用的目标
重点区别一下:(student *Student)与(student Student)的区别
- 理解指针类型的接收器
指针类型的接收器由一个结构体的指针组成,更接近于面向对象中的this或者self。
由于指针的特性,调用方法时,修改接收器指针的任意成员变量,在方法结束后,修改都是有效的。
func (this *Student) init() {
this.name="init"
this.age=21
}
func main() {
var stu Student
fmt.Println(stu)
stu.init()
fmt.Println(stu)
}
func (this Student) initNoPointer() {
this.name="noPointerInit"
this.age=21
}
func main() {
var stu Student
fmt.Println(stu)
stu.initNoPointer()
fmt.Println(stu)
}
- 理解非指针类型的接收器
当方法作用于非指针接收器时,Go语言会在代码运行时将接收器的值复制一份。在非指针接收器的方法中可以获取接收器的成员值,但修改后无效。
func (this Student) initNoPointerReturn() Student {
this.name = "noPointerInit"
this.age = 21 + this.age
return this
}
func main() {
var stu Student
stu.age = 11
fmt.Println(stu)
initNoPointerReturn := stu.initNoPointerReturn()
fmt.Println(initNoPointerReturn)
fmt.Println(stu)
}
在计算机中,小对象由于值复制时的速度较快,所以适合使用非指针接收器。大对象因为复制性能较低,适合使用指针接收器,在接收器和参数间传递时不进行复制,只是传递指针。
2.3 结构体的继承
组合思想
2.4 实现接口
想要实现接口只需要方法格式与接口内的方法结构一致即可
但是 Go 语言里有非常灵活的 接口 概念,通过它可以实现很多面向对象的特性。接口提供了一种方式来 说明 对象的行为:如果谁能搞定这件事,它就可以用在这儿。
接口定义了一组方法(方法集),但是这些方法不包含(实现)代码:它们没有被实现(它们是抽象的)。接口里也不能包含变量。
通过如下格式定义接口:
type Namer interface {
Method1(param_list) return_type
Method2(param_list) return_type
...
}
类型不需要显式声明它实现了某个接口:接口被隐式地实现。多个类型可以实现同一个接口。
实现某个接口的类型(除了实现接口方法外)可以有其他的方法。
一个类型可以实现多个接口。
接口类型可以包含一个实例的引用, 该实例的类型实现了此接口(接口是动态类型)。
Go 语言规范定义了接口方法集的调用规则:
类型 T 的可调用方法集包含接受者为 *T 或 T 的所有方法集
类型 *T 的可调用方法集包含接受者为 *T 的所有方法
类型 *T 的可调用方法集不包含接受者为 T 的方法
2.5 函数和方法的区别
函数将变量作为参数:Function1(recv)
方法在变量上被调用:recv.Method1()
在接收者是指针时,方法可以改变接收者的值(或状态),这点函数也可以做到(当参数作为指针传递,即通过引用调用时,函数也可以改变参数的状态)。
不要忘记 Method1 后边的括号 (),否则会引发编译器错误:method recv.Method1 is not an expression, must be called
接收者必须有一个显式的名字,这个名字必须在方法中被使用。
receiver_type 叫做 (接收者)基本类型,这个类型必须在和方法同样的包中被声明。
在 Go 中,(接收者)类型关联的方法不写在类型结构里面,就像类那样;耦合更加宽松;类型和方法之间的关联由接收者来建立。
方法没有和数据定义(结构体)混在一起:它们是正交的类型;表示(数据)和行为(方法)是独立的。