学习一门新语言,其实大多数东西都是类似的,我们只需要关注这么语言特殊的地方。本文就是在学习过程中记录go特殊的地方
b站
git_book
搭建环境
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()
}
这个是数组
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)
}
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作为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
go build -race xxx.go
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)
}
本质是一个队列
使用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:这个原理是啥?
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的原理,这个后面可以细读。
使用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,一切都是空接口
啥地方用了反射?
反射可以做什么