携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第11天,点击查看活动详情
前言
本系列文章旨在分享,记录 Go 中的特殊语法,帮助 Gopher 提升对 Go 语法细节的理解和认识。
Go 的特殊语法
分配
上一篇结尾讲到了数组与切片,那么本篇开始就先来讲讲分配。
Go 中提供了两种用来分配的内建函数,分别是 new 和 make,它们做的事情和适用的类型都不同,可能会引起混淆,下面来详细说说。
new
new 在分配内存时,不会初始化内存,只会将内存置零。 也就是说,new(T) 会返回一个指针, 该指针指向新分配的,类型为 T 的零值。
例如
package main
func main() {
var i = new(int)
println(i, *i)
var j = new(float64)
println(j, *j)
var k = new([]int)
println(k, *k)
}
result:
0xc00005bf48 0
0xc00005bf40 +0.000000e+000
0xc00005bf58 [0/0]0x0
主要看最后一行,对切片分配内存后,返回的是一个0长度0容量指向地址0的切片的指针,这样的切片肯定是没有办法使用的,因为地址0不可访问(实际上指向地址0的指针就是nil),所以可以看出来此时切片是没有底层数组的。
make
make(T, args) 只用于创建切片、映射和信道,并返回类型为 T(而非 *T)的一个已初始化的值,这是由于引用数据类型在使用前必须初始化。
下面的示例展示了 new 与 make 的不同:
package main
func main() {
var k = new([]int)
println(k, *k)
*k = make([]int, 100)
println(k, *k)
v := make([]int, 10)
println(&v, v)
}
result:
0xc00005bf58 [0/0]0x0
0xc00005bf58 [100/100]0xc000072000
0xc00005bf40 [10/10]0xc00005bee8
类型
类型转换
当我们声明了一个新类型 type Sequence []int 后,想要让 Sequence 能够使用 sort 包中的 Sort 方法。最朴素的想法是用 Sequence 实现 sort.Interface 接口。但实际上,sort 包中已经为 []int 写好了对接口的实现(升序)叫 IntSlice 类型,我们可以直接对 Sequence 进行类型转换,然后使用 Sort 方法。
type Sequence []int
func (s Sequence) String() string {
sort.IntSlice(s).Sort()
return fmt.Sprint([]int(s))
}
func main() {
s := make(Sequence, 0)
s = append(s, 2, 5, 1, 4, 3, 6)
fmt.Println(s)
}
同时,类型转换也是防止在 Sprint Sprintf 中产生递归调用的解决办法之一。
类型选择
类型选择是类型转换的一种形式:它接受一个接口,在 switch 中根据其判断选择对应的 case ,并将其转换为该种类型。
以下代码通过类型选择将值转换为字符串:如果它已经是字符串,直接打印字符串值;若它有 String 方法,则打印调用该方法所得的结果。
package main
import (
"fmt"
"sort"
)
type Stringer interface {
String() string
}
type Sequence []int
func (s Sequence) String() string {
sort.IntSlice(s).Sort()
return fmt.Sprint([]int(s))
}
func main() {
var value interface{}
// value = "sss"
v := make(Sequence, 0)
value = append(v, 2, 1, 5, 3, 0)
// value = 2
switch str := value.(type) {
case string:
println(str)
case Stringer:
println(str.String())
default:
println("error")
}
}
类型断言
类型断言接受一个接口值,并从中提取指定的明确类型的值。它需要一个明确的类型,而非 type 关键字: value.(typeName),其结果则是拥有静态类型 typeName 的新值。该类型必须为该接口所拥有的具体类型,或者该值可转换成的第二种接口类型。要提取我们知道在该值中的字符串,可以这样:
str := value.(string)
但如果它所转换的值中不包含字符串,该语句就会触发一个恐慌。为避免这种情况,需使用 , ok 惯用语句来测试它的值是否为字符串:
if str, ok := value.(string), ok {
fmt.Printf("string value is: %q\n", str)
} else {
fmt.Printf("value is not a string\n")
}
若类型断言失败,str 将继续存在且为字符串类型,但它将拥有零值,即空字符串。
空白标识符
_ 可以被赋予或声明为任何类型的值,并把值舍弃,用在需要变量但不需要实际值的地方作为占位符。这一特性使它具有很多神奇的用处。
骗过编译器对 unused 的检查
程序开发过程中,可能经常会产生未使用的导入和变量,我们可以用 _ 来使它们能够通过编译。
package main
import (
"fmt"
"log"
"os"
)
var _ = fmt.Printf
func main() {
fs, err := os.Open("test.go")
if err != nil {
log.Fatal(err)
}
_ = fs
}
为副作用而导入
有时导入某个包只是为了其副作用, 而没有任何明确的使用。例如,在 net/http/pprof 包的 init 函数中记录了HTTP处理程序的调试信息。它有个可导出的API, 但大部分客户端只需要注册的 handler 信息。所以当只为了副作用来导入包时,可以将包重命名为空白标识符 import _ net/http/pprof"
接口检查
之前在《HttpRouter 源码分析》的文章中讲过,它里面为了检查 Router 是符合 http.Handler 接口的实现,写了这样一句 var _ http.Handler = New() 这里的 New() 返回的是 *Router
总结
本篇文章介绍了分配函数 new 与 make 的不同(一个是置零,一个是初始化),类型选择、转换和断言,以及空白标识符 _ 的一些神奇的使用方法。
最后,如果本篇文章对你有所帮助,希望你可以 点赞、收藏、评论,感谢支持 ✧(≖ ◡ ≖✿