一 背景
最近开始接触go语言的业务,在开发中遇到了一些问题。这里坐下总结和记录,防止踩类似的坑。
二 使用tips
1 go语言引用类型和指针类型2
go语言的变量主要可以分为两种类型:引用类型和值类型。
如果想在开发中快速明白两者的使用姿势的话,可以参考这篇文章:go中值传递、引用传递、指针传递的区别
值类型有int、float、bool、array、sturct等。引用类型有slice,map,channel,interface。两者的简单区别在于作为形参传递的时候,在函数内部如果改变值类型的值,不会改变函数外的值类型变量的值。在函数内部如果改变引用类型的值,会改变函数外的值类型变量的值。这点和java 有点相似,但是和java不同的是java如果在函数内部改变某个属性的值是有效的,而go语言中在函数内部改变结构体的值是无效的。其实可以发现业务写多了之后大家都不怎么用java的基本类型,都用java的包装类型。比如用Integer代替int,Double代替double。
以前是个人理解,仅供参考。
无论是java,go,还是c++。在参数传递过程中其实都是值传递。不区分值类型还是引用类型,在参数传递过程中都会复制一个副本到函数中。引用类型能改变原来的值的原因是因为引用类型其实是指向某一种类型的指针。在参数传递过程中,函数的形参其实和函数外的变量指向的地址是一样的,所以就相当于两个指针都指向内存的同一个地方,一个指针改了指向的地址的值,另一个指针指向相同地址的内容自然也变了。但是他们本身的地址肯定是不一样的。(好好理解下这句话!!!)
举个栗子:
func main() {
card1 := Card{1}
fmt.Printf("初始值=%v", card1)
fmt.Println()
fmt.Printf("初始值的地址=%p", &card1)
fmt.Println()
var cardPtr = &card1
fmt.Printf("函数外指针变量的地址=%v", &cardPtr)
fmt.Println()
testParamPass(cardPtr)
fmt.Println()
fmt.Printf("调用函数之后的值=%v", card1)
}
// 测试引用传递
func testParamPass(card *Card) {
card.GroupID = 2
fmt.Printf("函数外指针变量的地址=%p", &card)
}
// 输出结果
初始值={1}
初始值的地址=0xc000020070
函数外指针变量的地址=0xc00000e030
函数外指针变量的地址=0xc00000e038
调用函数之后的值={2}
函数外的变量和函数内的形参指向的地址都是struct类型的card变量的地址,但是各自指针的地址都是不同的,印证了我的想法。
2 利用thrift的作用get方法防止npe
go语言的kite在idl生成的时候,可能生成的参数是指针,所以我们需要手动判断下是不是nil,然后在用。否则就会发生panic。这时候我们就可以用thrft生层的获取字段的逻辑,防止panic。比如这样的
func (p *GetCardInfoRequest) GetUserID() int64 {
if !p.IsSetUserID() {
return GetCardInfoRequest_UserID_DEFAULT
}
return *p.UserID
}
var GetCardInfoRequest_DeviceID_DEFAULT int64
func (p *GetCardInfoRequest) GetDeviceID() int64 {
if !p.IsSetDeviceID() {
return GetCardInfoRequest_DeviceID_DEFAULT
}
return *p.DeviceID
}
3 数组指针和指针数组的区别
这两个概念当作偏重词来看。数组指针是指针(指向数组的指针)。指针数组是数组(数组的每一个元素都是指针)。
这篇文章介绍的很好:Golang 学习——数组指针和指针数组的区别
演示下遍历数组指针
4 初始化map和map写入数据
声明map并进行初始化的时候不能通过var的方式声明,要通过make方式声明