因为分离编译的原因,在 C++ 中声明和定义是不同的概念。在其它语言中,定义和声明几乎没有区别。
1 2 3
| extern int i; int j = 0; int h;
|
C++ 默认初始化的规则是在函数外定义的变量拥有初始值,而在函数内部没有。c++ 就是这么多规矩。如果不注意的话就会运行时错误。
在 JavaScript 因为动态类型的原因并不能默认初始化,所有变量未赋值则为 undefined。
如果使用 TypeScript 的话,需要开启 strict: true
,如果不开启的不会直接进行 null check,就算是 undefined 也可以在代码中使用 。
1 2 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 中声明即初始化,如果没有初始化值,那么就会赋值为零值。
1 2 3 4 5 6 7 8 9 10 11
| int 0 int8 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。
有了零值在就很大程度避免了运行时错误。
1 2 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) }
|
另外这种初始化是递归进行的。如果未指定任何值,则结构数组的每个元素都将其字段归零。
1 2 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 ,最终也会报运行时错误。
1 2 3 4 5
| func main() { var m map[string]string m["name"] = "test" fmt.Println(m["name"]) }
|
为什么声明了而没有赋初始值呢?因为 map 内部是一个引用类型的结构,如果 map 初始化需要使用 make 。
记住:最佳实践对于 slice map 和 channel 需要使用 make
这对于指针也同样适用,如果定义了指针而没有初始化指针值,那么也会报错。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 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 函数即可。
1 2 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。
比如说下面这个例子:
1 2 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 吗?
1 2 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。