Golang数据初始化,零值以及一些陷阱

基础不牢,地动山摇

不像Python这种动态语言,遇见对象就是一言不合的赋给变一个变量,然后查看;而Go语言是静态类型语言,因此变量是有明确类型的,编译器也会检查变量类型的正确性。

1. 变量的初始化

1.1 声明

声明变量的一般形式是使用 var 关键字:var name type,其中,var 是声明变量的关键字,name 是变量名,type 是变量的类型。
也可以进行批量声明:

1
2
3
4
5
6
7
8
9
var (
a int
b string
c []float32
d func() bool
e struct {
x int
}
)

1.2 初始化

变量初始化的标准格式var 变量名 类型 = 表达式,例如var age int = 25;在标准格式的基础上,将 int 省略后,编译器会尝试根据等号右边的表达式推导 age 变量的类型:var age = 25,这个很容易理解。

1.3 短变量声明并初始化

var 的变量声明还有一种更为精简的写法,例如:age := 100,这是Go语言的推导声明写法,编译器会自动根据右值类型推断出左值的对应类型,但只能在函数中使用。

2. Go类型默认的零值

不带初始值的变量声明会被设置为它们的零值:

  • 0 for all integer types
  • 0.0 for floating point numbers
  • false for booleans
  • “” for strings
  • nil for interfaces, slices, channels, maps, pointers and functions

如果arraystruct声明后未指定值,则其中的元素的字段将为零值。该初始化以递归方式完成:

1
2
3
4
5
6
type T struct {
n int
f float64
next *T
}
fmt.Println([2]T{}) // [{0 0 <nil>} {0 0 <nil>}]

3. make和new的区别

Go语言中new和make是内建的两个函数,主要用来创建分配类型内存。 在我们定义生成变量的时候,可能会觉得有点迷惑,其实他们的规则很简单。

3.1 new

new(T)为一个 T 类型新值分配空间并将此空间初始化为 T 的零值,返回的是新值的地址,也就是 T 类型的指针 *T,该指针指向 T 的新分配的零值。new要点:

  • 内置函数 new 分配空间
  • 传递给new 函数的是一个类型,不是一个值
  • 返回值是 指向这个新分配的零值的指针

3.2 make

make(T, args) 返回的是初始化之后的 T 类型的值,这个新值并不是 T 类型的零值,也不是指针 *T,是经过初始化之后的 T 的引用。make 也是内建函数,你可以从官方文档这里看到,它的函数原型 比 new 多了一个(长度)参数,返回值也不同.

make 只能用于 slice,map,channel 三种类型, 并且只能是这三种对象。 和 new 一样,第一个参数是 类型,不是一个值. 但是make 的返回值就是这个类型(即使一个引用类型),而不是指针.具体的返回值,依赖具体传入的类型.

3.3 两者区别

new(T) 返回 T 的指针 *T 并指向 T 的零值。
make(T) 返回的初始化的 T,只能用于 slice,map,channel,要获得一个显式的指针,使用new进行分配,或者显式地使用一个变量的地址。
new 函数分配内存,make函数初始化。

具体可以看mojoyv的文章 以及三月沙的理解 Go make 和 new 的区别,包含很多代码例子

4. 一些初始化相关的陷阱

4.1 在nil map中赋值

1
2
3
4
5
6
7
8
9
# error code:
var m map[string]float64
m["pi"] = 3.1416

# OUTPUT: panic: assignment to entry in nil map

# we should:
m := make(map[string]float64)
m["pi"] = 3.1416

4.2 无效的内存地址或nil指针取消引用

1
2
3
4
5
6
7
8
9
10
11
12
type Point struct {
X, Y float64
}

func (p *Point) Abs() float64 {
return math.Sqrt(p.X*p.X + p.Y*p.Y)
}

func main() {
var p *Point
fmt.Println(p.Abs())
}

上面代码会报错:

1
2
3
4
5
6
7
8
9
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0xffffffff addr=0x0 pc=0xd2c5a]

goroutine 1 [running]:
main.(*Point).Abs(...)
../main.go:6
main.main()
../main.go:11 +0x1a

未初始化的指针是nil,我们没法使用它,有两种解决方案:
创建一个指针

1
2
3
4
func main() {
var p *Point = new(Point)
fmt.Println(p.Abs())
}

或者直接跳过指针,用值接收者调用指针方法

1
2
3
4
func main() {
var p Point // has zero value Point{X:0, Y:0}
fmt.Println(p.Abs())
}

4.3 一个copy错误

1
2
3
4
5
6
var src, dst []int
src = []int{1, 2, 3}
copy(dst, src) // Copy elements to dst from src.
fmt.Println("dst:", dst)

# OUTPUT: dst: []

很明显这里的src是一个slice的零值nil,要完整副本的COPY,必须分配容量足够大的目标切片:

1
2
3
4
5
6
7
var src, dst []int
src = []int{1, 2, 3}
dst = make([]int, len(src))
n := copy(dst, src)
fmt.Println("dst:", dst, "(copied", n, "numbers)")

# OUTPUT: dst: [1 2 3] (copied 3 numbers)

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!