因为分离编译的原因,在 C++ 中声明和定义是不同的概念。在其它语言中,定义和声明几乎没有区别。
| 12
 3
 
 | extern int i; int j = 0;
 int h;
 
 | 
C++ 默认初始化的规则是在函数外定义的变量拥有初始值,而在函数内部没有。c++ 就是这么多规矩。如果不注意的话就会运行时错误。
在 JavaScript 因为动态类型的原因并不能默认初始化,所有变量未赋值则为 undefined。
如果使用 TypeScript 的话,需要开启 strict: true,如果不开启的不会直接进行 null check,就算是 undefined 也可以在代码中使用 。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 
 | interface IPerson {name: string;
 age: number;
 }
 
 let student: IPerson = void 0;
 
 function getName(std: IPerson) {
 return std.name;
 }
 
 
 getName(student);
 
 | 
而在 Go 中声明即初始化,如果没有初始化值,那么就会赋值为零值。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | int     0int8    0
 int32   0
 int64   0
 uint    0x0
 rune    0 // rune的实际类型是 int32
 byte    0x0 // byte的实际类型是 uint8
 float32 0 // 长度为 4 byte
 float64 0 // 长度为 8 byte
 bool    false
 string  ""
 
 | 
而对于指针、函数、接口、切片、通道、字典(映射)的零值都是 nil。
有了零值在就很大程度避免了运行时错误。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 
 | package main
 type person struct {
 name string
 age  uint8
 }
 
 var p person
 
 func main() {
 fmt.Println(p.name, p.age)
 }
 
 | 
另外这种初始化是递归进行的。如果未指定任何值,则结构数组的每个元素都将其字段归零。
| 12
 3
 4
 5
 
 | type T struct {n int
 left *T
 }
 t := new(T)
 
 | 
通常 golang 初学者会犯 nil map 的错误,下面会报 panic: assignment to entry in nil map 错误。这里的 m 是 nil ,最终也会报运行时错误。
| 12
 3
 4
 5
 
 | func main() {var m map[string]string
 m["name"] = "test"
 fmt.Println(m["name"])
 }
 
 | 
为什么声明了而没有赋初始值呢?因为 map 内部是一个引用类型的结构,如果 map 初始化需要使用 make 。
记住:最佳实践对于 slice map 和 channel 需要使用 make
这对于指针也同样适用,如果定义了指针而没有初始化指针值,那么也会报错。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 
 | package main
 
 import (
 "encoding/json"
 "fmt"
 )
 
 func main() {
 
 var p *person
 
 var data = `{"name":"test","age":20}`
 err := json.Unmarshal([]byte(data), p)
 
 if err != nil {
 fmt.Println(err)
 }
 }
 
 
 | 
那么这里就会打印错误:json: Unmarshal(nil *main.person)
避免这样的问题,就需要一个非 nil 指针,很简单,使用 new 函数即可。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | func main() {p := new(person)
 
 var data = `{"name":"test","age":20}`
 err := json.Unmarshal([]byte(data), p)
 
 if err != nil {
 fmt.Println(err)
 }
 }
 
 | 
所以所有语言的最佳实践都是要始终初始化变量。
现在还有一个 gopher 可能遇到的问题,上面说 interface 接口会初始化为 nil。
比如说下面这个例子:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 
 | package main
 import (
 "fmt"
 "io"
 )
 
 func main() {
 var tmp io.Writer
 
 if tmp != nil {
 fmt.Println("tmp isn't nil")
 } else {
 fmt.Println("tmp is nil")
 }
 }
 
 | 
输出的肯定是 tmp is nil。
但是我们改一下。把一个指针为 nil 的赋值为一个接口,那么这个接口也是 nil 吗?
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 
 | package main
 import (
 "bytes"
 "fmt"
 "io"
 )
 
 func main() {
 var buf *bytes.Buffer
 var tmp io.Writer = buf
 
 if tmp != nil {
 fmt.Println("tmp isn't nil")
 } else {
 fmt.Println("tmp is nil")
 }
 }
 
 | 
答案是输出 tmp isn't nil。
因为对于接口 interface 内部存储而言,一个字段存储类型一个字段存储内容。内容为 nil 的接口,本身并不是 nil。