Golang的这些坑,你踩了几个嘿,你知道吗?Go 语言那可是相当简单又有趣呢!不过呢,就像其他语言一样,它也有一些小技
前言
嘿,你知道吗?Go 语言那可是相当简单又有趣呢!不过呢,就像其他语言一样,它也有一些小技巧。但你可别误会,这些技巧可不是因为 Go 有啥缺陷才出现的哦。要是你以前用的是别的语言,那这里面有些 “坑” 可能就会让你自然而然地掉进去啦。其他的呢,要么是因为错误的假设,要么就是缺少了点细节。
要是你肯花点时间去学学这门语言,看看官方说明呀、逛逛 wiki 呀、瞅瞅邮件列表里的讨论呀、读一读大量优秀的博文,还有源代码,那这些技巧中的绝大多数就会变得显而易见啦。当然啦,不是每个人一开始都这么学,但也没啥大不了的。如果你是刚接触 Go 语言的新人,那这里的信息绝对能帮你省下大把调试代码的时间呢!
作用域
func Test_xxx(t *testing.T) {
x := 1
fmt.Println(x)
// x := 2
{
x := 3
fmt.Println(x)
}
}
如果取消注释x := 2
,那么就会得到一个编译错误,“no new variables on left side of :=”,
但是一个有趣的例子的是x := 3
居然编译通过还执行了,有人可能觉得平时不可能这么写代码,定义了一次,后面又定义一次,
那么我们来看下面一段代码
func Test_save(t *testing.T) {
a, err := getData()
if err != nil {
fmt.Println(err)
}
// 先简单err置空
err = nil
// 模拟保存数据,先获取到再根据结果判断是要创建还是更新数据
if a == nil {
// create
id, err := getData()
fmt.Println(id, err)
} else {
// update
id, err := getData()
fmt.Println(id, err)
}
fmt.Println(err == nil)
}
func getData() (interface{}, error) {
return "", errors.New("xx")
}
最后输出的结果是true
,err是nil
值拷贝
type User struct {
Id string
Name string
Age int
}
func Test_arr(t *testing.T) {
arr := []User{
{Id: "id1", Age: 10}, {Id: "id2", Age: 13},
}
u0 := arr[0]
u0.Age++
fmt.Println(arr[0].Age)
}
输出结果,10
,age++
居然无效。
这是一个值拷贝
问题。在 Go 中,结构体是值类型。当你执行 u0 := arr[0]
时,实际上是将 arr[0]
的值复制到了 u0
中。因此,对 u0.Age++
的修改只会影响 u0
,而不会改变 arr[0].Age
。
有两个改法u0 := &arr[0]
或者直接arr[0].Age++
切片(数组)
func Test_arr(t *testing.T) {
arr := []int{0, 1, 2, 3, 4, 5}
arr2 := arr[0:2]
arr2 = append(arr2, 200)
arr2[0] = 100
fmt.Println(arr[0], arr2[0])
fmt.Println(arr[2], arr2[2])
}
在Go语言中,切片实际上是一个包含指向底层数组指针、长度和容量的结构体。这意味着切片的长度和容量可以独立变化,底层数组的大小则由容量决定。
只有容量不够了,底层才会新生成一个数组重新指过去。
type slice struct {
array unsafe.Pointer // 指向底层数组的指针
len int // 切片的长度
cap int // 切片的容量
}
切片的类型定义、操作和行为有详细的规范描述 切片的基本概念和行为
比如 s = s[2:4]
range
type User struct {
Id string
Name string
}
func Test_range(t *testing.T) {
users := []User{
{Id: "id1"}, {Id: "id2"},
}
for _, user := range users {
user.Name = "无效修改"
}
fmt.Println(users[0]) // {id1 }
for i, user := range users {
if user.Id == "id1" {
users[i].Name = "index有效修改"
}
}
fmt.Println(users[0]) // {id1 index有效修改}
userPointers := []*User{
{Id: "id1"}, {Id: "id2"},
}
for _, user := range userPointers {
user.Name = "指针有效修改"
}
fmt.Println(userPointers[0]) // &{id1 指针有效修改}
}
使用for _, user := range users
遍历切片,尝试给每个User实例的Name字段赋值为"123"。
但是这里的user是切片中元素的副本,对副本的修改不会影响原始切片中的元素。所以这个循环结束后,users切片中的元素实际上没有被修改。
users[i].Name
这种方式可以成功修改原始切片中的元素,因为是通过索引直接操作切片中的元素,而不是操作副本。
使用for _, user := range userPointers
遍历切片,尝试给每个指针所指向的User实例的Name字段赋值为"xxx"。
这里的user是指针的副本,但通过指针副本仍然可以修改指针所指向的实际对象。所以这个循环结束后,userPointers切片中的指针所指向的User实例的Name字段被成功修改。
json格式转换
type Student struct {
id int
name string
score int
}
func main() {
s := Student{1, "张三", 99}
buf, _ := json.Marshal(s)
fmt.Println(string(buf))
}
在做web开发过程中,基本上每天都要和json格式数据打交道,所以学会转换成json格式的数据是必备技能啊。但上面的写法是错误的,打印出来的为空值。这是因为Student结构体中的元素都是小写的,对外是不可访问的,所以必须改成大写的,才能对外输出json格式的数据。正确写法如下:
type Student struct {
Id int
Name string
Score int
}
defer
defer的规则:
- 规则一 当defer被声明时,其参数就会被实时解析
- 规则二 defer执行顺序为先进后出
- 规则三 defer可以读取有名返回值
func Test_defer(t *testing.T) {
for i := 0; i < 3; i++ {
defer func() {
fmt.Print(i, " ")
}()
}
}
这里是极度容易踩坑的地方,由于defer这里调用的func没有参数,等执行的时候,i已经为0(按3 2 1逆序,最后一个i=1时,i--的结果最后是0),所以这里输出3个0 。
修改如下:
func Test_defer(t *testing.T) {
for i := 0; i < 3; i++ {
a := i
defer func() {
fmt.Print(a, " ")
}()
}
}
最后
嘿,咱这只是 golang 开发坑的 “冰山一角” 哦!各位大侠,走过路过别错过,快把你们在 golang 江湖中踩过的坑也分享出来吧,让我们一起在 “坑” 中成长,把 golang 玩得更溜!😎
转载自:https://juejin.cn/post/7405778015189418021