1 接口基础
# 接口约束结构体的行为(方法),接口是一系列方法的集合,接口也是一种类型(特殊类型)
# 接口变量.(Dog)--->类型断言
# 接口变量.(type)--->类型选择,配合switch使用
package main
import "fmt"
// 1 接口的实际用途
// Animal 接口定义
type Animal interface {
eat()
sleep()
}
// 只要结构体实现了接口中的所有方法,就叫实现该接口
// 如果没有实现接口中的所有方法,就不叫实现该接口
// go 语言也是鸭子类型
// java中:实现接口,必须显示的声明,并且必须实现所有方法
// python,go中,实现接口,不需要显示声明,只要实现了所有方法,就叫实现接口,这种操作叫鸭子类型
// Dog 定义结构体与绑定对应方法
type Dog struct {
name string
age int
}
func (dog Dog) WatchHouse() { fmt.Println("狗看家功能") }
func (dog Dog) eat() { fmt.Println("狗吃东西") }
func (dog Dog) sleep() { fmt.Println("狗睡觉") }
// Cat 定义结构体和绑定方法
type Cat struct {
name string
age int
}
func (cat Cat) CatchMouse() { fmt.Println("猫抓老鼠") }
func (cat Cat) eat() { fmt.Println("猫吃东西") }
func (cat Cat) sleep() { fmt.Println("猫睡觉") }
// Cat和Dog都实现了Animal接口,所以Cat和Dog都属于Animal这个类型
// goToSleep 定义一个普通函数
func goToSleep(a Animal) {
a.sleep() // 只要是Animal类型就都有eat、sleep功能
}
func main() {
var cat1 = Cat{"cat1", 1}
var dog1 = Dog{"dog1", 1}
var animal1 Animal
// 因为Cat和Dog都属于Animal这个类型(实现了Animal接口),所以给animal1赋值可以赋cat1或者dog1
animal1 = cat1
fmt.Println(animal1) // {cat1 1}
animal1 = dog1
fmt.Println(animal1) // {dog1 1}
goToSleep(cat1) // 参数为Animal类型、cat1也是Animal类型 打印猫睡觉
goToSleep(dog1) // 参数为Animal类型、dog1也是Animal类型 打印狗睡觉
goToSleep(animal1) // 参数为Animal类型、animal1也是Animal类型 打印狗睡觉
}
package main
import "fmt"
// 1 接口的实际用途
// Animal 接口定义
type Animal interface {
eat()
sleep()
}
// 只要结构体实现了接口中的所有方法,就叫实现该接口
// 如果没有实现接口中的所有方法,就不叫实现该接口
// go 语言也是鸭子类型
// java中:实现接口,必须显示的声明,并且必须实现所有方法
// python,go中,实现接口,不需要显示声明,只要实现了所有方法,就叫实现接口,这种操作叫鸭子类型
// Dog 定义结构体与绑定对应方法
type Dog struct {
name string
age int
}
func (dog Dog) WatchHouse() { fmt.Println("狗看家功能") }
func (dog Dog) eat() { fmt.Println("狗吃东西") }
func (dog Dog) sleep() { fmt.Println("狗睡觉") }
// Cat 定义结构体和绑定方法
type Cat struct {
name string
age int
}
func (cat Cat) CatchMouse() { fmt.Println("猫抓老鼠") }
func (cat Cat) eat() { fmt.Println("猫吃东西") }
func (cat Cat) sleep() { fmt.Println("猫睡觉") }
// Cat和Dog都实现了Animal接口,所以Cat和Dog都属于Animal这个类型
// goToSleep 定义一个普通函数
func goToSleep(a Animal) {
a.sleep() // 只要是Animal类型就都有eat、sleep功能
}
// 2 空接口:即不要求满足任何功能就实现了空接口,所以所有类型都属于空接口
// Empty 定义空接口
type Empty interface{}
func myPrint(a Empty) {fmt.Println(a)} // 定义一个普通函数
// 3 匿名空接口
func myPrint2(a interface{}) {fmt.Println(a)} // 简写myPrint函数
// 4 类型断言
func testDog (a Animal) {
dog := a.(Dog) // 类型断言 如果a不是Dog类型就断言失败
fmt.Println(dog)
}
func testDog2 (a Animal) {
dog,ok := a.(Dog) // 类型断言 两个变量接收,断言成功返回调用的具体实参和true,否则返回具体实参的零值和false
fmt.Println(dog,ok)
}
func testDog3(a Animal) {
dog,ok := a.(Dog)
if ok{
dog.WatchHouse() // 断言成功返回的dog能调用Dog类型绑定的的所有方法
}
}
func testDog4(a Animal) {
if dog,ok:=a.(Dog);ok{ // testDog3的简写,if判断后的条件前可以加执行语句用分号隔开
dog.WatchHouse()
}
}
// 5 类型选择
func testDog5(i interface{}) {
switch obj:=i.(type) {
case Dog:
obj.WatchHouse()
case Cat:
obj.CatchMouse()
case int:
fmt.Println("这是int类型")
case string:
fmt.Println("这是string类型")
default:
fmt.Println("其他类型")
}
}
func main() {
// 1 接口的实际应用
var cat1 = Cat{"cat1", 1}
var dog1 = Dog{"dog1", 1}
var animal1 Animal
// 因为Cat和Dog都属于Animal这个类型(实现了Animal接口),所以给animal1赋值可以赋cat1或者dog1
animal1 = cat1
fmt.Println(animal1) // {cat1 1}
animal1 = dog1
fmt.Println(animal1) // {dog1 1}
goToSleep(cat1) // 参数为Animal类型、cat1也是Animal类型 打印猫睡觉
goToSleep(dog1) // 参数为Animal类型、dog1也是Animal类型 打印狗睡觉
goToSleep(animal1) // 参数为Animal类型、animal1也是Animal类型 打印狗睡觉
// 2 空接口的应用
myPrint(10) // 10
myPrint("cx") // cx
var a2 [3]int
myPrint(a2) // [0 0 0]
// 总结:函数的参数类型为空接口类型时,可以传任意类型,因为所有类型都实现了空接口,属于空接口类型
var i2 interface{}
i2 = 0
fmt.Println(i2) // 0
i2 = "cx"
fmt.Println(i2) // cx
// 总结: 变量为空接口类型时,可以给该变量赋任意类型值
// 4 类型断言
testDog(dog1) // {dog1 1}
//testDog(cat1) // 报错 因为断言失败
testDog2(dog1) // {dog1 1} true dog1和true
testDog2(cat1) // { 0} false cat1的零值和false
testDog3(dog1) // 狗看家功能
testDog3(cat1) // if判断失败,后面代码没执行
// 5 类型选择: 用switch case做类型断言
testDog5(cat1) // 猫抓老鼠
testDog5(dog1) // 狗看家功能
testDog5(100) // 这是int类型
testDog5("cx") // 这是string类型
testDog5([3]int{1,2,3}) // 其他类型
// 6 接口的零值
var animal2 Animal1
fmt.Println(animal2)
// 7 接口的底层实现:引用: {具体类型(Dog), 指针(指向具体的类型}
}
2 接口高级
package main
import "fmt"
// 1 实现接口:也就是实现接口对应方法,有两种:值接收者、指针接收者
// Animal1 定义接口
type Animal1 interface {
eat()
}
// Duck 结构体定义与绑定方法
type Duck struct {
name string
}
func (duck Duck) eat() {
fmt.Println("鸭子吃", duck)
}
// Chicken 结构体定义与绑定方法
type Chicken struct {
name string
}
func (chicken *Chicken) eat() {
fmt.Println("鸡吃", chicken)
}
// 2 接口嵌套
// Animal2 接口定义
type Animal2 interface {
eat()
}
// Duck2 接口嵌套的定义
type Duck2 interface { // 需要实现所有方法才算实现接口、包括Animal2方法
run()
speak()
Animal2 // 等同于把Animal2的所有方法放在了这
}
// 3 同时实现多个接口
// Animal3 接口定义
type Animal3 interface {
eat()
}
// Duck3 接口嵌套的定义
type Duck3 interface { // 需要实现所有方法才算实现接口、包括Animal2方法
run()
speak()
Animal3 // 等同于把Animal2的所有方法放在了这
}
// tDuck 结构体定义与方法绑定 同时实现Duck3、Animal3接口
type tDuck struct {
name string
age int
}
func (duck tDuck) eat() {fmt.Println("duck eat")}
func (duck tDuck) speak() {fmt.Println("duck speak")}
func (duck tDuck) run() {fmt.Println("duck run")}
func main() {
// 1 值接收者、指针接收者
duck := Duck{"鸭子"}
chicken := Chicken{"鸡"}
duck.eat() // 值接收者,值类型调用时将duck复制传入 鸭子吃 {鸭子}
chicken.eat() // 指针接收者,值类型调用时将chicken的地址传入 鸡吃 &{鸡}
duck1 := &Duck{"鸭子"}
chicken1 := &Chicken{"鸡"}
duck1.eat() // 值接收者,指针类型调用时将指针对应值复制传入 鸭子吃 {鸭子}
chicken1.eat() // 指针接收者,指针类型调用时将指针复制传入 鸡吃 &{鸡}
var animal1 Animal1 = duck
animal1.eat() // 调用的是duck的值接收者 鸭子吃 {鸭子}
var animal11 Animal1 = &duck
animal11.eat() // 调用的duck的值接收者 鸭子吃 {鸭子}
//var animal2 Animal1 = chicken // 报错 如果是用指针接收者实现的Animal1接口,那么只能传指针给Animal1接口
var animal2 Animal1 = &chicken
animal2.eat() // 鸡吃 &{鸡}
// 总结:
// 1、如果用值接收者实现接口,可以将指针或者值赋值给接口类型
// 2、如果用指针接收者实现接口,只能把指针赋值给接口类型
// 3 实现多接口
var duck3 = tDuck{"t_duck",1}
var animal3 Animal3 = duck3
var duckInt Duck3 = duck3 // duck3 既可以赋值给Animal3也可以赋值给Duck3类型
fmt.Println(animal3, duckInt) // {t_duck 1} {t_duck 1}
// 注意:
// duck3可以使用eat run speak
// animal3可以使用eat(如果要使用其他两种方式、可以使用类型断言)
// duckInt可以使用ear run speak
}
3 并发和并行
# 并发:并发是指立即处理多个任务的能力,在一个时间段内,可以进行多个任务(干多个活),单核cpu,多个任务执行,只能每个任务执行一会,这就是并发
# 并行:并行指的是同时处理多个任务,在同一时刻,可以进行多个任务,必须有多个cpu的支持
# 只针对于Python语言,cpython 解释器,GIL锁,同一时刻,只能有一个线程执行
# Python如果是IO密集型,开启线程
# Python如果是计算密集型,开启进程,多个进程中的线程可以运行在多个cpu上
# 但是到了go语言,不存在这个问题,go中开多个线程,能够运行在多个cpu上,能够利用多核优势
# 并且go没有开启线程和进程的代码,咱们开启的统统叫做协程,可以实现单线程下的并发---》go开启的协程本质是:线程池+协程
# 开启多个线程,如果遇到io操作,线程会切换,但是是操作系统层面切换,比较消耗资源
# 如果一个线程下,开启多个协程,协程的切换,是程序(代码)控制的,不消耗过多资源
# 同种情况下,协程更节约资源(针对的是IO密集型)
4 go协程
package main
import (
"fmt"
"time"
)
// Go协程(线程池+协程) goroutine
func printHello() {
fmt.Println("hello")
}
------------------------------------------
func main() {
fmt.Println("程序开始了")
printHello()
fmt.Println("程序结束了")
// 1 开启goroutine
fmt.Println("程序开始了")
go printHello() // 开启协程
go printHello() // 开启协程
time.Sleep(1*time.Second) // 只传1是1纳秒,等一秒是为了让协程执行打印hello完毕,否则执行主程序完毕后可能协程还没有执行完毕。此时开启的协程将会被关闭
fmt.Println("程序结束了")
}
------------------------------------------
// 2 同时开启多个goroutine,尽管用,开上万个都没问题
func main() {
fmt.Println("程序开始了")
go printHello()
go printHello()
go printHello()
...
...
time.Sleep(1*time.Second)
fmt.Println("程序结束了")
}
------------------------------------------
// 开启多个goroutine,可以利用多核优势
func loop_dead(name string) { // 死循环函数
for {
fmt.Println(name)
}
}
func main() {
// goroutine 能够利用多核优势
// python 如果想利用多核优势,必须开多进程
// 8核cpu,开一个死循环,只能占住1核,cpu的使用率,只占八分之一
// 如果使用python 开启多个线程,无论开多少个,如果是8核cpu,都只占八分之一
go loop_dead("cx1")
go loop_dead("cx2")
go loop_dead("cx3")
go loop_dead("cx4")
go loop_dead("cx5")
go loop_dead("cx6")
}
----------------------------------------
/*
补充:
在main或其他函数中开协程,主程序结束了,协程还继续执行吗?
结论:main函数中的协程,如果main结束了,协程也会结束
其他函数里的协程,函数结束了,只要main没结束,协程就会执行。
*/
5 信道
package main
import (
"fmt"
"time"
)
// 1 信道的快速使用
// 信道:信道可以想象成Go协程之间通信的管道。如同管道中的水会从一端流到另一端
// 通过信道,数据也可以从一端发送,在另一端接收
// 信道也是一个变量
func go1(a chan bool) {
fmt.Println("其他goroutine开始")
time.Sleep(time.Second * 2)
a <- true // 向a信道放true
fmt.Println("其他goroutine结束")
}
func main() {
// 1 信道的快速使用
var c chan bool = make(chan bool) // 简写:var c = make(chan bool)
fmt.Println("主的goroutine开始")
go go1(c) // 将信道c传入协程
// 阻塞,等接收到值后再向下运行。通过信道 可以实现使go协程完成后主协程再继续运行
b := <-c // 从c信道取值(信道在<-后面就是取值,在<-前面就是放值),没有值接收不会报错,代表不要了
fmt.Println("主的goroutine结束,接收了b为:", b)
// 1.2 定义信道
var c2 chan interface{}
fmt.Println(c2) // <nil>
var c21 = make(chan interface{})
fmt.Println(c21) // 0xc00008c000 是一个内存地址
// 1.3 信道的取值和赋值、默认都是阻塞的
// - 有go协程发送值,就必须有go协程接受值,否则报死锁
//<- c21 // - deadlock! 报错 因为信道没放值,取不到,报死锁错误
}
package main
import "fmt"
// 2 案例: 有一个数字,计算这个数字每一位的平方和和立方和
//同步写法
func calcSquares(number int,a chan int) { // 345
total:=0
for number!=0{
digit := number % 10 // 对10取余数 5 4 3
total+=digit*digit //5*5+4*4+3*3
number=number/10 // 34 3 简写:number/=10
}
a<-total
}
func calcCubes(number int,b chan int) { // 345
total:=0
for number!=0{
digit := number % 10 // 对10取余数 5 4 3
total+=digit*digit*digit //5*5+4*4+3*3
number=number/10 // 34 3 简写:number/=10
}
b<-total
}
func main() {
number:=889 //1962 同步计算
var a =make(chan int)
var b =make(chan int)
go calcSquares(number,a) // 算完平方和假如: 5s
go calcCubes(number,b) // 算完立方和假如: 7s
// 阻塞,直到两个信道的值都取到
res1, res2 := <-a, <-b // 因为goroutine可以利用多核优势,所以两个都接收到得时间接近算完立方和的时间 7s
fmt.Println(res1+res2) // 1962
}
package main
import "fmt"
// 3 定义一个信道,要么只能写,要么只能放
func task1(c chan<- int) {
// 在这个任务中,信道只能放值,不能取值
c <- 9
}
func main() {
c := make(chan int)
go task1(c)
<-c
}
package main
import "fmt"
// 4 信道的循环
func producer(chnl chan int) {
for i := 0; i < 10; i++ {
chnl <- i
fmt.Println("放入", i)
}
close(chnl) //关闭信道
}
func main() {
// 1 使用死循环
ch := make(chan int)
go producer(ch)
for {
v, ok := <-ch // 信道取值,如果取不到,且没有关闭,ok就是true,如果关闭了,ok就是false
if ok == false {
break
}
fmt.Println("Received ", v, ok)
}
// 2 使用range循环信道,如果信道关闭,range循环也就结束了
go producer(ch)
for v := range ch { // 一旦信道被关闭,for循环就结束了
fmt.Println("取出: ", v)
}
}
4 缓冲信道
package main
import "fmt"
// 有缓冲信道
func main() {
//var a =make(chan int,0)
//a<-999 // 无缓冲信道,放不进去,必须有另一个goroutine接收,才不会报错
// 1 定义有缓冲信道,缓冲信道为3
var a1 =make(chan int,3) // 信道最多能放3个int值
a1<-999
a1<-888
a1<-777
//a1<-666 // 报死锁
fmt.Println(a1) // 0xc000108080 信道类似管道,先进先出
<-a1 // 999
<-a1 //888
fmt.Println(<-a1) //777
//fmt.Println(<-a1) // 也会死锁
// 最开始学的是的无缓冲信道,相当于有缓冲信道数字是0
// 2 长度和容量
// 长度:目前信道有几个值
// 容量:信道最大能够放多少,初始化的时候,数字决定,没有扩容这一说
var c =make(chan int)
fmt.Println(len(c)) // 0
fmt.Println(cap(c)) // 0
// 无缓冲信道,长度容量都是0
var c2 =make(chan int,3)
fmt.Println(len(c2)) // 0
fmt.Println(cap(c2)) //3
c2<-999
c2<-888
fmt.Println(len(c2)) // 2
fmt.Println(cap(c2)) //3
c2<-888 // 长度和容量一样
//c2<-888 // 报错
// 实际生产中运用:信道中可以传各种类型数据
var c3 =make(chan entity.Person,3)
c3<-entity.NewPerson("lqz",19,"lyf")
var p entity.Person=<-c3
fmt.Println(p)
}
5 WaitGroup
package main
import (
"fmt"
"sync"
)
// WaitGroup 是一个类型,开了很多goroutine,主的goroutine,等待所有goroutine执行完,主的再继续执行
// 1 使用信道实现多个goroutine执行完成,主goroutine再继续执行
func task3(s string,c chan int) {
fmt.Println("打印",s)
c<-999
}
func main() {
fmt.Println("主开始了")
var c =make(chan int,3)
go task3("lqz",c)
go task3("111",c)
go task3("222",c)
for i:=0;i<cap(c);i++{
<-c
}
fmt.Println("主结束了")
}
// 2 使用WaitGroup 实现等待多个任务完成
func task3(s string, wg *sync.WaitGroup) {
fmt.Println(s)
wg.Done() // 其中一个任务完成
}
func main() {
// 1 简单版
fmt.Println("主开始了")
var wg sync.WaitGroup // 值类型,不用初始化就有默认零值;当参数传递,如果改原值,需要取地址
fmt.Println(wg) // {{} [0 0 0]}
go task3("lqz", &wg)
go task3("111", &wg)
go task3("222", &wg)
wg.Add(3) // 表明有3个任务
wg.Wait() // 等待3个任务完成
fmt.Println("主结束了")
// 2 实用版
fmt.Println("主开始了")
var wg2 sync.WaitGroup
for i := 0; i < 5; i++ { // 每开一个goroutine add一个
go task3("ssss", &wg2) // 数字和字符串的相互转换 string(1) int("1") 都不行
//go task3(i,&wg2)
wg2.Add(1)
}
wg2.Wait()
fmt.Println("主结束了")
}
7 defer的使用
package main
import "fmt"
// defer的使用---》注册代码,延迟调用,等函数执行完后,按注册顺序倒序执行defer,先注册的,最后执行
// 如果打开一个文件,最后要关闭,一打开文件,立马用defer注册关闭
--------------------------------------------
func main() { // 顺序:开始 结束 3 2 1
fmt.Println("我开始执行了")
defer fmt.Println("我是defer注册的代码1")
defer fmt.Println("我是defer注册的代码2")
defer fmt.Println("我是defer注册的代码3")
fmt.Println("我执行结束了")
}
--------------------------------------------
func main() { // 顺序:开始 结束 10
fmt.Println("我开始执行了")
var a int=10
defer func(i int) {
fmt.Println("我是匿名函数的执行")
fmt.Println(i)
}(a) // 已经传入,但是没执行,copy传递,复制了一份a=10传入了
a++ // a 自增1
fmt.Println("我执行结束了")
}
--------------------------------------------
func main() { // 顺序:开始 结束 11
fmt.Println("我开始执行了")
var a int=10
defer func() {
fmt.Println("我是匿名函数的执行")
fmt.Println(a) // 对外部的引用 , 打印出11
}()
a++ // a 自增1
fmt.Println("我执行结束了")
}
--------------------------------------------
func main() { // 顺序:开始 结束 [999,7,8]
fmt.Println("我开始执行了")
var a =[]int{6,7,8}
defer func() {
fmt.Println("我是匿名函数的执行")
fmt.Println(a) // 对外部的引用 , [999,7,8]
}()
a[0]=999
fmt.Println("我执行结束了")
}
--------------------------------------------
func main() { 顺序:开始 结束 [999,7,8]
fmt.Println("我开始执行了")
var a =[]int{6,7,8}
defer func(s []int) {
fmt.Println("我是匿名函数的执行")
fmt.Println(s) // 虽然是copy传递,但是s是引用,改了会影响原来的[999,7,8]
}(a)
a[0]=999
fmt.Println("我执行结束了")
}
--------------------------------------------
func main() { // 顺序:开始 结束 循环第0123456789 10 0
fmt.Println("我开始执行了")
i:=0
defer fmt.Println("普通目前i的值是:",i) // 0
defer func() { // 对外部的引用
fmt.Println("函数内目前i的值是:",i) //10
}()
defer func() { // 对外部的引用
for ;i<10;i++{
fmt.Println("循环第:",i)
}
}()
fmt.Println("我执行结束了")
}
8 panic和recover
package main
import "fmt"
// defer : 先注册,后执行,即便程序报了panic的错误,也会执行
// panic : 跟python中raise,java中throw是一样的,主动抛出异常
// recover: 恢复程序,继续执行
func main() {
fmt.Println("主开始了")
panic("我出错了")
fmt.Println("主结束了") // 永远不会被执行到
}
----------------------------------
// panic 主动抛出异常
func test8() {
fmt.Println("我是test8")
panic("我出错了")
fmt.Println("我是test8,我执行完了")
}
func main() {
fmt.Println("主开始了")
test8()
fmt.Println("主结束了") // 永远不会被执行到
}
----------------------------------
func test8(s []int) {
fmt.Println(s)
fmt.Println(s[9]) // 越界,报错,抛异常,程序抛出的
}
func main() {
var s=[]int{7,8,9}
test8(s)
fmt.Println("我结束了") // 不会被执行
}
----------------------------------
// 程序出异常,捕获异常,处理异常,程序继续执行
func test8(s []int) {
defer func() { // 即使报错后也会执行,所以报错后可以恢复、但报错后代码不会运行
recover()
}()
fmt.Println(s)
fmt.Println(s[9]) // 越界,报错,抛异常,程序抛出的
}
func main() {
var s=[]int{7,8,9}
test8(s)
fmt.Println("我结束了") // 会运行
}
----------------------------------
/*
Python异常处理:
try:
f2()
except Exception as e:
print(e)
finally:
print(无论是否出错,都会执行)
*/
// go 语言中异常处理
func main() {
fmt.Println("我开始了")
f1()
f2() // 知道f2会异常,我想后面的继续执行
f3()
fmt.Println("我结束了")
}
func f1() {
fmt.Println("我是f1")
}
func f2() {
defer func() {
//recover() // 恢复程序,如果有错误,会返回错误对象,如果没有错误,会返回nil
if err:=recover();err!=nil{ // 说明出错了
fmt.Println(err) // 打印错误
}
fmt.Println("这是其他语言finally的代码")
}()
//panic("我主动抛出")
//var a =[]int{9,8,7}
//fmt.Println(a[9])
fmt.Println("我是f2")
}
func f3() {
fmt.Println("我是f3")
}
----------------------------------
// 总结:go 中异常捕获:把这段代码,放到可能会出错代码的上方
defer func() {
if err:=recover();err!=nil{
// except的代码
}
//finally的代码
}()