Golang的这些坑,你踩了几个

前言

嘿,你知道吗?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)
}

输出结果,10age++居然无效。

这是一个值拷贝问题。在 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, " ")
		}()
	}
}

defer-panic-and-recover

最后

嘿,咱这只是 golang 开发坑的 “冰山一角” 哦!各位大侠,走过路过别错过,快把你们在 golang 江湖中踩过的坑也分享出来吧,让我们一起在 “坑” 中成长,把 golang 玩得更溜!😎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

编程点滴

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值