Go学习笔记
1. byte与rune 字符串
byte 占用1个字节,8个bit,范围为0->255(uint8)
rune 占用4个字节,32个bit,范围为0-2^32(int32)(字符集大,中文必须使用这个)
func main() {
var a byte = 'A'
var b rune = 'B'
fmt.Printf("a 占用 %d 个字节数\nb 占用 %d 个字节数", unsafe.Sizeof(a), unsafe.Sizeof(b))
}
//1 4
字符串
一个英文字符占1个字节,一个中文字符占3个字节
//表示 \r\n 这个字符串
//解释型表示法
var str string = "\\r\\n"
//原生型表示法(不会根据转义字符解析)
var str string = `\r\n`
//使用%q还原原生型表示法
fmt.Printf("的解释型字符串是: %q", str)
2. go指针
创建方法
- 定义变量取内存地址
a:=1
p=&a
- 创建指针分配内存后,再给指针指向的内存写入值
str:=new(string)
*str="test"
- 声明指针变量,从其他变量取地址并赋值
a:=1
var b *int
b=&a
& 在普通变量中取内存地址
* 该符号在赋值操作符的右边时,表示在指针变量在取得变量值,在左边是,是指指针指向的变量
打印内存地址
fmt.Printf("%p", p)
fmt.Println(p)
指针具有类型,比如string,int,*float64
指针的零值为nil
指针和切片都是引用类型,改变一个数组的值时,有两个方法:
- 传入数组切片(推荐使用)
func modify(nums []int) {
nums[0] = 90
}
func main() {
nums := [3]int{89, 90, 91}
modify(nums[:])
}
- 传入数组指针
func modify(nums *[3]int) {
(*nums)[0] = 90
}
func main() {
nums := [3]int{89, 90, 91}
modify(&nums)
}
3. select用法
func main() {
c1 := make(chan string, 1)
c2 := make(chan string, 1)
timeout := make(chan bool, 1)
go makeTimeout(timeout, 2)
select {
case msg1 := <-c1:
fmt.Println("c1 received: ", msg1)
case msg2 := <-c2:
fmt.Println("c2 received: ", msg2)
case <-timeout:
fmt.Println("Timeout, exit.")
}
}
//一直阻塞,导致超时
- select 只能用于 channel 的操作(写入/读出/关闭),而 switch 则更通用一些;
- select 的 case 是随机的,而 switch 里的 case 是顺序执行;
- select 要注意避免出现死锁,同时也可以自行实现超时机制;
- select 里没有类似 switch 里的 fallthrough 的用法;
- select 不能像 switch 一样接函数或其他表达式。
4. go异常机制
触发panic
panic("crash")
recover(),捕获panic信息并打印
defer func() {
// recover() 可以将捕获到的panic信息打印
if err := recover(); err != nil {
fmt.Println(err)
}
}()
即使 panic 会导致整个程序退出,但在退出前,若有 defer 延迟函数,还是得执行完 defer 。
defer 在多个协程之间是没有效果,在子协程里触发 panic,只能触发自己协程内的 defer,而不能调用 main 协程里的 defer 函数的。
panic:抛出异常,使程序崩溃
recover:捕获异常,恢复程序或做收尾工作
5. 结构体
结构体可以继承
type Person struct {
Name string
}
type Man struct {
person
age string
}
结构体中,属性名大写为Public,小写为Private
实例化方法:
- 正常实例化
p:=Person{name:"xxx"}
- 使用new
p:=new(Person) p.name="xxx"
- 使用&
var p *Person=&Person{} p.name="xxx" //等同于(*p).name="xxx"
从一个结构体实例对象中获取字段的值,通常都是使用 . 这个操作符,该操作符叫做 选择器。
可以直接省去 * 取值的操作,选择器 . 会直接解引用
空结构体
type person struct{}
空结构体没有任何属性,不占用空间,size为0
6. 结构体tag
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Addr string `json:"addr,omitempty"`
}
person结构体中Addr字段有omitempty属性,使用encoding/json转会为字符串时,如果该属性为空则会被忽略
获取Tag
field := reflect.TypeOf(obj).FieldByName("Name")
field := reflect.ValueOf(obj).Type().Field(i) // i 表示第几个字段
field := reflect.ValueOf(&obj).Elem().Type().Field(i) // i 表示第几个字段
tag:=field.Tag
// 获取键值对
labelValue := tag.Get("label")//个体是对lookup的封装,如果为空则返回空字符串
labelValue,ok := tag.Lookup("label")
7. 类型断言
- 检查是否为nil
- 检查是否为某个类型
Type Switcht:=i.(T) //断言失败会触发panic t,ok:=i.(T) //断言失败不会触发panic var k interface{} // nil t, ok := k.(interface{}) //这里断言失败,但会继续执行
switch x := i.(type) { case int: fmt.Println(x, "is int") case string: fmt.Println(x, "is string") case nil: fmt.Println(x, "is nil") default: fmt.Println(x, "not type matched") }
8. 空接口interface{}
接口包含两个属性,值和类型,而空接口都为nil
var i interfacece{}
fmt.Printf("type: %T",value: %v",i,v)
//type: <nil>, value: <nil>
用法:
- interface{}作为类型声明,可以承载任何类型的值
var i interface{} i=1 i=false
- 让函数接收任何类型的参数
func test(if intrface{}){} 接收任意个任何类型的参数 func test(ifs ...interface{}){}
- 定义一个接受任何类型的array,slice,map,struct
any:=make([]interface{},3) any[0]=1 any[2]=false
空接口可以承载任何类型的值,但不代表任何类型的值可承接空接口的值
空接口承载数组和切片后,该对象无法再进行切片
使用空接口接收任意类型的参数时,静态类型是interface{},但动态类型并不知道,需要使用类型断言
9. golang反射
- 反射可以将接口类型变量转换为反射类型对象
reflect.TypeOf(i)//获得接口类型的值,类型为*reflect.rtype reflect.ValueOf(i)//获得接口值的值,类型为reflect.Value
- 反射可以将反射类型对象转换为接口类型变量
i := v.Interface() i := v.Interface().(int)
- 如果要修改反射类型对象其类型必须是可写的
要想具有可写性:- 创建反射对象传入变量的指针
- 使用Elem()函数返回指针指向的数据
func main() { var name string = "Go编程时光" v1 := reflect.ValueOf(&name) fmt.Println("v1 可写性为:", v1.CanSet()) v2 := v1.Elem() fmt.Println("v2 可写性为:", v2.CanSet()) }
-
通过反射获取类型Kind(),基础的分类,比如struct(类型为main.Profile)
-
类型转换
var age int = 25 v1 = reflect.ValueOf(age) v2 := v1.Int()
Int() :转成 int
Float():转成 float
String():转成 string
Bool():转成布尔值
Pointer():转成指针
Interface():转成接口类型 -
对切片的操作
Slice(),对切片再切片,返回reflect.Value 反射对象
Slice3():对切片再切片(三下标)
Slice3():对切片再切片(三下标) -
对属性的操作
NumField() 属性数量和 Field(i)获得属性 -
对方法的操作
NumMethod() 方法数量 和 Method()获得方法 -
动态调用参数
type Person struct{} func (p Person)Say(){} func (p Person)SayHello(text string){} p:=&Person{} v:==reflect.ValueOf(p) v.MethodByName("Say").Call(nil)//无参调用 v.MethodByName("SayHello").Call("hello")//有参调用
10. 静态类型和动态类型
静态类型:变量声明时的类型
动态类型:程序运行时系统中的类型
interface组成:
type+date
interface
- iface 带有方法的接口
- eface 不带方法的接口
11. make和new函数
- make 用来创建slice,map,chan类型,make返回类型的本身而不是指针,返回值也依赖于具体传入的类型,因为是引用类型,所以没必要返回指针
- new 分配内存 设置零值 返回指针
12. 包管理
//单行导入
import "fmt"
//多行导入
import (
"fmt"
"math"
)
//别名
import mrand "math/rand"
//点操作,将该包函数视为本包函数
import . "fmt"
//匿名导入
import _ "fmt"
包的初始化,调用包时,会执行该包的init
函数,优先于main函数,包引用链中,会递归init执行
同一个文件,可以有多个init函数,init函数不能有入参和返回值,且所有init函数都会执行,但不能保证执行顺序
包的匿名导入:只会执行init函数,不会报错
相对导入:在\(GOPATH/src或\)GOROOT或$GOPATH/pkg/mod导入
相对导入:在当前目录搜索并导入
import导入的时路径不是包
import "../../module"
13. 包导入优先级
- 使用govendor
- 从项目根目录的vendor查找
- 从$GOROOT/src中查找
- 从$GOPATH/src中查找
- 使用gomodules
- 如果导入的带域名,在$GOPATH/pkg/mod中查找,找不到去该网站查找
- 不带域名则去$GOROOT查找
- vcendor文件夹,无论是否有域名,都只会在vendor中查找
14. 寻址
可寻址:变量,指针,数组元素索引,切片,切片元素索引,组合字面量
不可寻址:常量,字符串,函数,基本类型字面量,符合字面量,map中的元素,数组字面量
15. 内存分配
16. 泛型
func myFunc[K comparable, V int64 | float64](m map[K]V) V {
return res
}
- K,V 泛型别名,作用于在函数内
- comparable go预声明的类型,是可比较,可哈希的类型集合,通常用于定义map中的key