go语法糖

语法糖

前言

名字由来

  • 语法糖(Syntactic sugar)的概念是由英国计算机科学家 Peter J. Landin 提出的
  • 用于表示编程语言中的某种类型的语法
    • 这些语法不会影响功能
    • 但使用起来却很方便
  • 语法糖
    • 也称糖语法
    • 这些语法不仅不会影响功能
    • 编译后的结果跟不使用语法糖也一样
  • 语法糖
    • 有可能让代码编写变得简单
    • 也有可能让代码可读性更高
    • 也有可能让代码出问题
  • 为了避免陷阱才是这个章节的重点

Go语言语法糖

  • 最常用的语法糖莫过于赋值符 :=
  • 其次表示函数变参的 …
  • 接下来
    • 我们会介绍这两种语法糖的用法
  • 更重要的是结合实际的经历跟大家分享其中的陷阱

简短变量声明

  • 想要声明变量
  • 可以使用关键字 var 或者直接使用简短变量声明( := )
    • 后者使用更频繁一些
    • 尤其是在接收函数返回值场景中
  • 不必使用 var 声明一个变量再变量接收函数返回值
    • 使用 := 可以一步到位
  • 本节我们讨论 := 的一些容易被忽视的规则
  • 以避免一些陷阱

热身测验

前言
  • 相信你已经大量使用过简短变量声明
  • 比如像下面这样
i := 0
j, k := 1, 2
  • := 用来声明变量并赋值
    • 不管是个人项目、公司项目和开源项目都会大量应用
  • 跟据我做过的小范围调查结果看
    • 有多年Go开发经验的工程师也不能很好的回答 := 相关的问题
  • 在开始讨论 := 前
    • 请试着回答一下这些题目
    • 再跟据参考答案对照一下
    • 或许会有令你惊讶的发现
题目
package main

import "fmt"

// 题目一
// 问:下面代码输出什么?
func func1() {
	i := 0
	i, j := 1, 2
	fmt.Printf("i = %d, j = %d\n\n", i, j)
}

// 题目二
// 问:下面代码为什么不能通过编译?
func func2(i int) {
	i := 0
	fmt.Println(i)
}

// 题目三
// 问:下面代码输出什么?
func func3() {
	i, j := 0, 0
	if true {
		j, k := 1, 1
		fmt.Printf("j = %d, k = %d\n", j, k)
	}
	fmt.Printf("i = %d, j = %d\n", i, j)
}
参考答案
  • 题目一

    • 程序输出如下:

      i = 1, j = 2
      
    • 再进一步想一下,前一个语句中已经声明了i, 为什么还可以再次声明呢?

  • 题目二

    • 不能通过编译
    • 原因是形参已经声明了变量i
      • 使用 := 再次声明是不允许的
    • 再进一步想一下
      • 编译时会报”no new variable on left side of :=”错误
      • 该怎么理解?
  • 题目三

    • 程序输出如下:

      j = 1, k = 1
      i = 0, j = 0
      
    • 这里要注意的是,block if 中声明的j,与上面的j属于不同的作用域

使用规则

前言
  • 虽然简短变量声明这个语法糖用起来很方便
  • 但有时也会给你一个意外也可能带你掉入陷阱
  • 我曾因滥用这个 := 语法糖
    • 发生过一次故障
    • 所以才认真研究了一下它的原理和规则
  • 大家可以做为参考
规则
规则一:多变量赋值可能会重新声明
  • 我们知道使用 := 一次可以声明多个变量
  • 像下面这样:
field1, offset := nextField(str, 0)
  • 上面代码定义了两个变量
  • 并用函数返回值进行赋值
  • 如果这两个变量中的一个再次出现在 := 左侧就会重新声明
  • 像下面这样:
field1, offset := nextField(str, 0)
field2, offset := nextField(str, offset)
  • offset被重新声明
  • 重新声明并没有什么问题
    • 它并没有引入新的变量
    • 只是把变量的值改变了
  • 但要明白
  • 这是Go提供的一个语法糖
    • 当 := 左侧存在新变量时(如field2)
      • 那么已声明的变量(如offset)则会被重新声明
      • 不会有其他额外副作用
    • 当 := 左侧没有新变量是不允许的
      • 编译会提示 no new variable on left side of :=
  • 我们所说的重新声明不会引入问题要满足一个前提
    • 变量声明要在同一个作用域中出现
    • 如果出现在不同的作用域
      • 那很可能就创建了新的同名变量
      • 同一函数不同作用域的同名变量往往不是预期做法
      • 很容易引入缺陷
  • 关于作用域 的这个问题
    • 我们在本节后面介绍
规则二:不能用于函数外部
  • 简短变量场景只能用于函数中
  • 使用 := 来声明和初始化全局变量是行不通的
package sugar
import fmt
rule := "Short variable declarations" // syntax error: non-declaration statement outside function body
  • 这里的编译错误提示 syntax error: non-declaration statement outside function body
    • 表示非声明语句不能出现在函数外部
  • 可以理解成 := 实际上会拆分成两个语句
    • 即声明和赋值
    • 赋值语句不能出现在函数外部的
变量作用域问题
  • 几乎所有的工程师都了解变量作用域
    • 但是由于 := 使用过于频繁的话
    • 还是有可能掉进陷阱里
  • 下面代码源自真实项目
    • 但为了描述方便
    • 也为了避免信息安全风险
  • 简化如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u5Q1enSb-1639317633713)(C:\Users\您是弟弟\AppData\Roaming\Typora\typora-user-images\image-20211209194720144.png)]

  • 注意上面声明的三个err变量
    • 2号err与1号err不属于同一个作用域
    • := 声明了新的变量
      • 所以2号err与1号 err属于两个变量
      • 2号err与3号err属于同一个作用域
        • := 重新声明了err但没创建新的变量
        • 所以2号err与3 号err是同一个变量
  • 如果误把2号err与1号err混淆
    • 就很容易产生意想不到的错误

可变参函数

前言

  • 可变参函数是指函数的某个参数可有可无
    • 即这个参数个数可以是0个或多个
  • 声明可变参数函数的方式是在参数类型 前加上 … 前缀
  • 比如 fmt 包中的 Println :
func Println(a ...interface{})
  • 本节我们会总结一下其使用方法
  • 顺便了解一下其原理
  • 以避免在使用过程中进入误区

函数特征

  • 我们先写一个可变参函数:
package main

import "fmt"

func Greeting(prefix string, who ...string) {
	if who == nil {
		fmt.Printf("Nobody to say hi.")
		return
	}
	
	for _, people := range who {
		fmt.Printf("%s %s\n", prefix, people)
	}
}
  • Greeting 函数负责给指定的人打招呼
  • 其参数 who 为可变参数
  • 这个函数几乎把可变参函数的特征全部表现出来了:
    • 可变参数必须在函数参数列表的尾部
      • 即最后一个(如放前面会引起编译时歧义)
    • 可变参数在函数内部是作为切片来解析的
    • 可变参数可以不填
      • 不填时函数内部当成 nil 切片处理
    • 可变参数必须是相同类型的
      • (如果需要是不同类型的可以定义为interface{}类型)

使用举例

  • 我们使用 testing 包中的Example函数来说明上面 Greeting 函数(函数位于sugar包中)用法
不传值
  • 调用可变参函数时
  • 可变参部分是可以不传值的
  • 例如:
func ExampleGreetingWithoutParameter() {
	sugar.Greeting("nobody")
    // OutPut:
	// Nobody to say hi.
}
  • 这里没有传递第二个参数
    • 可变参数不传递的话
    • 默认为nil
传递多个参数
  • 调用可变参函数时
  • 可变参数部分可以传递多个值
  • 例如:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OfoyEygv-1639317633714)(C:\Users\您是弟弟\AppData\Roaming\Typora\typora-user-images\image-20211209195932101.png)]

  • 可变参数可以有多个
    • 多个参数将会生成一个切片传入
  • 函数内部按照切片来处理
传递切片
  • 调用可变参函数时
    • 可变参数部分可以直接传递一个切片
    • 参数部分需要使用 slice… 来表示切片
  • 例如:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2NIGfXT3-1639317633714)(C:\Users\您是弟弟\AppData\Roaming\Typora\typora-user-images\image-20211209200134432.png)]

  • 此时需要注意的一点是
  • 切片传入时不会生成新的切片
  • 也就是说
    • 函数内部使用的切片与传入的切片共享相同的存储空间
  • 说得再直白一点就是
    • 如果函数内部修改了切片
    • 可能会影响外部调用的函数

总结

  • 可变参数必须要位于函数列表尾部
  • 可变参数是被当作切片来处理的
  • 函数调用时
    • 可变参数可以不填
  • 函数调用时
    • 可变参数可以填入切片
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值