文章目录
背景
学习一门新语言,其实大多数东西都是类似的,我们只需要关注这么语言特殊的地方。本文就是在学习过程中记录go特殊的地方
特殊之处
闭包
vscode
defer
- 把语句放到栈中,函数结束的时候再执行
- 一般用于打开文件/数据库后立马defer 关闭,有点像把finally提前
package main
import (
_ "encoding/json"
"fmt"
)
func sum(n1 int, n2 int) int {
defer fmt.Println("ok1 n1 = ", n1)
defer fmt.Println("ok2 n2 = ", n2)
n1++
n2++
res := n1 + n2
fmt.Println("ok3 res=", res)
return res
}
func main() {
res := sum(1, 2)
fmt.Println("Res=", res)
}
错误处理机制
不支持try…catch…finlly
使用 defer panic recover
package main
import (
"errors"
"fmt"
)
func test() {
defer func() {
err := recover()
if err != nil {
fmt.Println("err = ", err)
}
}()
num1 := 10
num2 := 0
res := num1 / num2
fmt.Println("Res=", res)
}
func readConf(name string) (err error) {
if name == "config.ini" {
return nil
} else {
return errors.New("读取文件error")
}
}
func test02() {
err := readConf("config.iniaaa")
if err != nil {
panic(err)
}
fmt.Println("test02ok")
}
func main() {
test()
fmt.Println("成功执行test")
test02()
fmt.Println("成功执行test02")
test02()
}
for-range遍历
数组和切片
这个是数组
func test(arr [3]int) {
}
这个是切片
func test(arr []int) {
}
传入函数中之后是值拷贝,函数内修改的数组,是深拷贝后的数组,不会影响函数外的数组。
如果需要函数内部修改,需要传入引用。
fucn test(arr *[3]int) {
(*arr)[0] = 1
}
长度是数组的一部分,传递参数的时候需要考虑数组的长度
切片的数据结构
可以理解为是一个结构体,保存了首地址、len、cap
如果初始化从一个数组中来,由于使用的是同一个地址,数组和切片的修改都是指向同一个地方,所以会相互影响。
func test03() {
var arr [5]int = [...]int{1, 2, 3, 4, 5}
slice := arr[1:4]
fmt.Println("arr", arr)
fmt.Println("slice", slice)
arr[2] = 30
slice[0] = 20
fmt.Println("arr", arr)
fmt.Println("slice", slice)
}
字符串和切片
字符串可以转为[]byte,这是按照byte切分的
中文字符可以转为[]rune,这是按照字符切分的
func testStr() {
str := "hello@123"
slice := str[3:]
fmt.Println("slice=", slice)
arr2 := []byte(str)
arr2[1] = 'z'
str = string(arr2)
fmt.Println("str=", str)
arr1 := []rune(str)
arr1[0] = '啊'
str = string(arr1)
fmt.Println("str=", str)
}
map slice
slice中每个元素都是一个map
func testMap() {
var monsters []map[string]string
monsters = make([]map[string]string, 2)
monsters[0] = make(map[string]string, 2)
monsters[0]["name"] = "牛魔王"
monsters[0]["age"] = "500"
fmt.Println(monsters)
}
如何对map的key按顺序遍历?
取出map的key作为slice,slice排序、根据排好序的key取值
func testOrderMap() {
map1 := make(map[int]int, 10)
map1[10] = 100
map1[1] = 13
map1[4] = 56
map1[8] = 99
fmt.Println(map1)
var keys []int
for k := range map1 {
keys = append(keys, k)
}
sort.Ints(keys)
fmt.Println(keys)
}
没有面向对象
但是可以通过接口和结构体开搞
结构体
完全相同的结构体(顺序、变量名、类型)可以相互强转,相当于取别名
可以加上tag,然后根据反射机制获取,常见使用场景:序列化和反序列化
type Stu struct {
Name string `json:"name"`
Age int `json:"age"`
}
func testStruct() {
students := make(map[string]Stu)
stu1 := Stu{"tom", 10}
stu2 := Stu{"marry", 20}
students["no1"] = stu1
students["no2"] = stu2
fmt.Println(students)
jsonStr, err := json.Marshal(stu1)
if err != nil {
fmt.Println("json 处理出错")
}
fmt.Println("jsonStr", string(jsonStr))
}
没构造函数
需要用工厂模式:返回一个包里的私有的结构体:
type privateStudent struct {
Name string `json:"name"`
Age int `json:"age"`
}
func NewStudent(name string, age int) *privateStudent {
return &privateStudent{
Name: name,
Age: age,
}
}
方法
方法是绑定在类型type
上的,而不仅仅是struct
如int float都可以有方法
如果一个类型实现了String()方法,fmt.Println() 会调用这个String()方法
面向对象-继承
嵌套匿名结构体
编译器会先找当前,然后找匿名的嵌套结构体
接口设计
有个比较好理解的例子来说明接口的用法
如何实现结构体的排序?
1、定义一个struct Stu
2、再定义一个struct列表,Students
3、实现Sort接口所需的三个方法:Len,Less,Swap
type Stu struct {
Name string `json:"name"`
Age int `json:"age"`
}
type StudentList []Stu
func (sl StudentList) Len() int {
return len(sl)
}
func (sl StudentList) Less(i, j int) bool {
return sl[i].Age < sl[j].Age
}
func (sl StudentList) Swap(i, j int) {
sl[i], sl[j] = sl[j], sl[i]
}
// https://www.bilibili.com/e748289c-09fd-4314-80ab-13e3003c0656
func testSortStruct() {
var students StudentList
students = append(students, Stu{"tom", 20})
students = append(students, Stu{"marry", 10})
students = append(students, Stu{"john", 30})
fmt.Println(students)
sort.Sort(students)
fmt.Println(students)
}
以上便是实现struct自定义排序所需做的
这里面就涉及了接口的设计。我们从Sort源码的角度出发,源码中规定,只要实现了Interface接口,就可以实现对自定义struct的排序,方便了调用者的使用。
具体到我们的例子里,我们的StudentList就是一个自定义的type,这个type实现了Interface的三个方法,于是,他就可以传进去Sort(data Interface)中进行排序了
我们可以想想,如果是冒泡排序的话,是不是总体上就是需要抽象的几个操作实际上就是Len、Less、Swap。
上述就是接口的作用。
类型断言
判断某个变量是啥类型
https://www.bilibili.com/7725bd8f-98c5-4214-b964-01f13e95b058
空接口可以接受任何类型。
最佳实践:判断传入参数类型:
func TypeJudge(items ...interface{}) {
// 入参是任意个数的任意类型的参数
for i, x := range items {
switch x.(type) {
case bool:
fmt.Printf("param #%d is bool, val = %v\n", i, x)
case float64:
fmt.Printf("param #%d is float64, val = %v\n", i, x)
case int, int64:
fmt.Printf("param #%d is int, val = %v\n", i, x)
case nil:
fmt.Printf("param #%d is nil, val = %v\n", i, x)
case string:
fmt.Printf("param #%d is string, val = %v\n", i, x)
default:
fmt.Printf("param #%d is unknown, val = %v\n", i, x)
}
}
}
测试
测试框架:
原来的功能函数:
package main
func addUpper(n int) int {
res := 0
for i := 1; i < n; i++ {
res += i
}
return res
}
配套的测试函数 xxx_test.go
package main
import (
"testing"
)
func TestAddUpper(t *testing.T) {
res := addUpper(10)
if res != 55 {
t.Fatalf("testAddUpper failed, should be 55, got %v\n", res)
} else {
t.Logf("testAddUpper ok")
}
}
命令行运行 go test -v
goroutine
查看是否存在并发
go build -race xxx.go
加锁+sleep的蠢方法
package main
import (
"fmt"
"sync"
"time"
)
var (
myMap = make(map[int]int, 10)
lock sync.Mutex
)
func test(n int) {
res := 1
for i := 1; i <= n; i++ {
res += i
}
lock.Lock()
myMap[n] = res
lock.Unlock()
}
func main() {
for i := 1; i <= 200; i++ {
go test(i)
}
time.Sleep(time.Second)
fmt.Println(myMap)
}
channel
本质是一个队列
使用for range遍历
如果管道不关闭,遍历取出的时候,最后会有deadlock错误。如果管道关闭了,则不会有这个问题
func writeData(intChan chan int) {
for i := 1; i <= 50; i++ {
fmt.Printf("write data: %v\n", i)
intChan <- i
}
// 写完了close
close(intChan)
}
func readData(intChan chan int, exitChan chan bool) {
for {
v, ok := <-intChan
if !ok {
break
}
fmt.Printf("read data: %v\n", v)
}
// 读完了close
close(exitChan)
}
func main() {
intChan := make(chan int, 50)
exitChan := make(chan bool, 1)
go writeData(intChan)
go readData(intChan, exitChan)
for {
_, ok := <-exitChan
if !ok {
// close了exitChan说明读完了
break
}
}
}
再来看一个协程算素数的例子:
package main
import (
"fmt"
"sort"
"time"
)
func putNum(intChan chan int) {
for i := 2; i <= 8000; i++ {
intChan <- i
}
close(intChan)
}
func primeNum(intChan chan int, primeChan chan int, exitChan chan bool) {
var flag bool
for {
num, ok := <-intChan
if !ok {
break
}
flag = true
for i := 2; i < num; i++ {
if num%i == 0 {
flag = false
break
}
}
if flag {
primeChan <- num
}
}
fmt.Println("one goroutine end")
exitChan <- true
}
func main() {
intChan := make(chan int, 1000)
primeChan := make(chan int, 2000)
exitChan := make(chan bool, 4)
// 生产者
go putNum(intChan)
// 消费者
for i := 0; i < 4; i++ {
go primeNum(intChan, primeChan, exitChan)
}
for {
if len(exitChan) == 4 {
break
}
time.Sleep(time.Second)
}
close(primeChan)
close(exitChan)
nums := make([]int, 0)
for num := range primeChan {
nums = append(nums, num)
}
sort.Ints(nums)
fmt.Println("intChan: ", intChan)
fmt.Println("finish: ", nums)
}
todo:这个原理是啥?
select
package main
import (
"fmt"
"time"
)
func main() {
intChan := make(chan int, 10)
for i := 0; i < 10; i++ {
intChan <- i
}
stringChan := make(chan string, 5)
for i := 0; i < 5; i++ {
stringChan <- "hello" + fmt.Sprintf("%d", i)
}
time.Sleep(time.Second * 5)
readNothing := false
for {
if readNothing {
break
}
select {
case v := <-intChan:
// 如果管道没关闭,但又取不到,自动会到下一个case
fmt.Printf("read intChan:%d \n", v)
break
case v := <-stringChan:
fmt.Printf("read stringChan:%s \n", v)
break
default:
fmt.Println("read nothing")
readNothing = true
}
}
}
我们会发现,尽管sleep了一会,取的时候还是会有取不到intChan的时候,这是为啥呢?我们来看看select的原理,这个后面可以细读。
某一个goroutine panic了,怎样别影响别的goroutine?
使用defer+recover
package main
import (
"fmt"
"time"
)
func sayHello() {
for i := 0; i < 10; i++ {
time.Sleep(time.Second)
fmt.Println("hello world")
}
}
func test() {
defer func() {
if err := recover(); err != nil {
fmt.Println("test() error", err)
}
}()
var myMap map[int]string
myMap[0] = "golang"
}
func main() {
go sayHello()
go test()
for i := 0; i < 10; i++ {
fmt.Println("main() ok= ", i)
time.Sleep(time.Second)
}
}
空接口
类似于Python的object,一切都是空接口
反射
啥地方用了反射?
- 结构体序列化反序列化
- 适配器
- go框架
反射可以做什么
- 获取变量的类型(type)、类别(kind)
- 获取结构体的信息(结构体的字段、方法)
- 修改变量值
.Elem
类似于把指针对应的真正的结构体取出来
可以做结构体的创建
最佳实践
package main
import (
"fmt"
"reflect"
)
func reflectTest01(b interface{}) {
rType := reflect.TypeOf(b)
fmt.Println("rType =", rType)
rValue := reflect.ValueOf(b)
fmt.Println("rValue =", rValue)
r2 := rValue.Int() + 10
fmt.Println("r2 =", r2)
//转回接口
iV := rValue.Interface()
num2 := iV.(int)
fmt.Println("num2 =", num2)
}
type Student struct {
Name string
Age int
}
func reflectStruct(b interface{}) {
rType := reflect.TypeOf(b)
fmt.Println("rType =", rType)
rValue := reflect.ValueOf(b)
fmt.Println("rValue =", rValue)
//转回接口
iV := rValue.Interface()
//可以通过switch做各种类型
stu2, ok := iV.(Student)
if ok {
fmt.Println("stu2 =", stu2)
}
}
// 使用反射遍历结构体的字段,调用结构体的方法
type Monster struct {
Name string `json:"name"`
Age int `json:"monster_age"`
Score float32
Sex string
}
func (m Monster) Print() {
fmt.Println("now print monster: ", m)
}
func (m Monster) GetSum(n1, n2 int) int {
return n1 + n2
}
func (m Monster) Set(name string, age int, score float32, sex string) {
m.Name = name
m.Age = age
m.Score = score
m.Sex = sex
}
func TestStruct(a interface{}) {
typ := reflect.TypeOf(a)
val := reflect.ValueOf(a)
kd := val.Kind()
if kd != reflect.Struct {
fmt.Println("struct expexted")
return
}
num := val.NumField()
fmt.Printf("fields: %d\n", num)
for i := 0; i < num; i++ {
fmt.Printf("field %d: val = %v\n", i, val.Field(i))
// 获取标签
tagVal := typ.Field(i).Tag.Get("json")
if tagVal != "" {
fmt.Printf("Field %d: tag = %v\n", i, tagVal)
}
}
numOfMethod := val.NumMethod()
fmt.Printf("struct has %d methods\n", numOfMethod)
// 按照函数名排序
for i := 0; i < numOfMethod; i++ {
fmt.Println("method:", i, val.Method(i))
}
fmt.Println("call by index:")
val.Method(1).Call(nil)
fmt.Println("call by name:")
var params []reflect.Value
params = append(params, reflect.ValueOf(10))
params = append(params, reflect.ValueOf(40))
method := val.MethodByName("GetSum")
if !method.IsNil() {
res := method.Call(params)
fmt.Println("Res = ", res[0].Int())
}
}
func main() {
var num int = 100
reflectTest01(num)
stu := Student{
Name: "tom",
Age: 20,
}
reflectStruct(stu)
fmt.Println("##BestPratice")
monster := Monster{
Name: "xx精",
Age: 300,
Score: 30.8,
Sex: "南",
}
TestStruct(monster)
}