令人难以置信的是,Go有多少种不同的方式可用于变量初始化。也许我完全不理解这一点,或者Go是一个很大的想法。很多东西感觉并不自然,看起来他们添加了功能,因为他们发现它们丢失了。初始化就是其中之一。
Here is running app on Go playground showing different ways of initialization
这是我理解的
var =
或:=
启动。:=
仅适用于方法&
。它们只适用于复合类型。=
或:=
有没有简单的方法可以让新手了解这一切?阅读规范无处不在地提供了所有这些。
答案 0 :(得分:12)
首先,提一些不正确的陈述:
要创建值和参考,请使用new或&amp ;.它们只适用于复合类型。
new()适用于所有事情。 &安培;不
使用make或var x [] int创建切片和贴图。注意到没有=或:=
您实际上可以:=
使用x := []int{}
甚至x := []int(nil)
。但是,第一个仅在你想要制作一个不是nil的len 0片段时使用,而第二个片段从未被使用,因为var x []int
更好。
让我们从头开始。您使用var name T
初始化变量。添加了类型推断,因为您可以键入less并避免担心函数返回的类型的名称。所以你可以var name = f()
。
如果要分配指针,则需要首先创建指向的变量,然后获取其指针:
var x int
var px = &x
需要两个语句可能很麻烦所以他们引入了一个名为new()的内置函数。这允许您在一个语句中执行此操作:var px = new(int)
然而,这种制作新指针的方法仍然存在问题。如果你正在构建一个具有期望指针结构的大型文字,该怎么办?这是一件很常见的事情。所以他们添加了一种方法(仅限复合文字)来处理这个问题。
var x = []*T{
&T{x: 1},
&T{x: 2},
}
在这种情况下,我们希望为指针引用的结构的字段分配不同的值。使用new()处理这将是非常糟糕的,所以它是允许的。
每当初始化变量时,始终将其初始化为零值。没有构造函数。对于某些内置类型,这是一个问题。特别是切片,地图和渠道。这些是复杂的结构。它们需要参数和超出内存的初始化设置为零。需要执行此操作的Go用户只需编写初始化函数。所以,这就是他们所做的,他们写了make()
。 neW([]x)
返回指向x切片的指针,而make([]x, 5)
返回由长度为5的数组支持的x切片.New返回指针,而make返回值。这是一个重要的区别,这就是为什么它是分开的。
:=
怎么样?这是一个很大的集群。他们这样做是为了节省打字,但它导致了一些奇怪的行为,并且在不同的规则上加以说明,直到它变得有些可用。
首先,它是var x = y的简短形式。但是,它经常被用于多次返回。而且这些多次退货中的大多数都是错误。所以我们经常有:
x, err := f()
但是标准函数中出现了多个错误,因此人们开始命名:
x, err1 := f()
y, err2 := g()
这太荒谬了,所以他们制定了:=
只有在该范围内尚未宣布才重新声明的规则。但这违背了:=
的要点,所以他们也强调了必须重新宣布至少一个规则的规则。
有趣的是,这解决了95%的问题并使其可用。虽然我遇到的最大问题是左侧的所有变量都必须是标识符。换句话说,以下内容无效,因为x.y
不是标识符:
x.y, z := f()
这是与var关系的遗留物,除了标识符之外,它不能声明任何东西。
至于为什么:=在函数范围之外不起作用?这样做是为了使编写编译器更容易。在函数外部,每个部分都以关键字(var,func,package,import)开头。 :=
意味着声明将是唯一不以关键字开头的声明。
所以,这是我对这个问题的一点咆哮。最重要的是,不同形式的声明在不同领域有用。
答案 1 :(得分:4)
是的,这是我早期发现令人困惑的事情之一。我已经提出了自己的经验法则,这可能是也可能不是最佳做法,但它们对我很有帮助。 在斯蒂芬关于零值的评论后调整
:=
,让Go推断类型如果您只需要一个空切片或地图(并且不需要设置初始容量),请使用{}
语法
s := []string{}
m := map[string]string{}
使用var
的唯一原因是将某些内容初始化为零值(由Stephen在评论中指出)(是的,除了函数之外,您需要var
或{ {1}}以及:
const
var ptr *MyStruct // this initializes a nil pointer
答案 2 :(得分:2)
我认为你的混淆来自于混淆类型系统与声明和初始化。
在Go中,可以通过两种方式声明变量:使用var
或使用:=
。 var
仅声明变量,而:=
也为其分配初始值。例如:
var i int
i = 1
相当于
i := 1
这是可能的原因是:=
只是假设i
的类型与它被初始化为的表达式的类型相同。因此,由于表达式1
具有类型int
,因此Go知道i
被声明为整数。
请注意,您也可以使用var
关键字明确声明和初始化变量:
var i int = 1
这是Go中唯一的两个delcaration / initialization构造。您是对的,不允许在全局范围内使用:=
。除此之外,两者是可以互换的,只要Go能够猜出你在:=
的右手边使用了什么类型(大部分时间都是这样)。
这些结构适用于任何类型。类型系统完全独立于声明/初始化语法。我的意思是没有关于哪些类型可以与声明/初始化语法一起使用的特殊规则 - 如果它是一个类型,你可以使用它。
考虑到这一点,让我们来看看你的例子并解释一切。
示例1
// Value Type assignments [string, bool, numbers]
// = & := Assignment
// Won't work on pointers?
var value = "Str1"
value2 := "Str2"
这将与指针一起使用。关键是你必须将它们设置为类型为指针的表达式。以下是一些带指针类型的表达式:
new()
的任何来电(例如,new(int)
都有类型*int
)i
的类型为int
,则&i
的类型为*int
)因此,要使您的示例使用指针:
tmp := "Str1"
var value = &tmp // value has type *int
value2 := new(string)
*value2 = "Str2"
示例2
// struct assignments
var ref1 = refType{"AGoodName"}
ref2 := refType{"AGoodName2"}
ref3 := &refType{"AGoodName2"}
ref4 := new(refType)
您在此处使用的语法refType{"AGoodName"}
用于在单个表达式中创建和初始化结构。此表达式的类型为refType
。
请注意,这里有一个时髦的东西。通常情况下,您无法获取字面值的地址(例如,&3
在Go中是非法的)。但是,可以获取文字 struct 值的地址,就像上面使用ref3 := &refType{"AGoodName2"}
一样。这看起来很混乱(一开始我当然很困惑)。它被允许的原因是,它实际上只是调用ref3 := new(refType)
然后通过*ref3 = refType{"AGoodName2"}
初始化它的简写语法。
示例3
// arrays, totally new way of assignment now, = or := now won't work now
var array1 [5]int
此语法实际上等同于var i int
,除了类型为int
之外,类型为[5]int
(五个整数的数组)。如果我们想要,我们现在可以分别设置数组中的每个值:
var array1 [5]int
array1[0] = 0
array1[1] = 1
array1[2] = 2
array1[3] = 3
array1[4] = 4
但是,这非常繁琐。就像整数和字符串等一样,数组可以有文字值。例如,1
是类型为int
的文字值。在Go中创建文字数组值的方法是明确命名类型,然后在括号中给出值(类似于结构文字),如下所示:
[5]int{0, 1, 2, 3, 4}
这是一个与任何其他值一样的文字值,因此我们可以将它作为初始化器使用:
var array1 [5]int = [5]int{0, 1, 2, 3, 4}
var array2 = [5]int{0, 1, 2, 3, 4}
array3 := [5]int{0, 1, 2, 3, 4}
示例4
// slices, some more ways
var slice1 []int
切片与数组的初始化方式非常相似。唯一的区别是它们没有固定在特定的长度,因此在命名类型时不必给出长度参数。因此,[]int{1, 2, 3}
是一个整数切片,其长度最初为3(尽管可以在以后更改)。所以,就像上面一样,我们可以这样做:
var slice1 []int = []int{1, 2, 3}
var slice2 = []int{1, 2, 3}
slice3 := []int{1, 2, 3}
示例5
var slice2 = new([]int)
slice3 := new([]int)
当类型变得复杂时,关于类型的推理会变得棘手。如上所述,new(T)
返回一个类型为*T
的指针。当类型为int时(即new(int)
具有类型*int
),这非常简单。但是,当类型本身也很复杂时,它会变得混乱,就像切片类型一样。在您的示例中,slice2
和slice3
都有*[]int
类型。例如:
slice3 := new([]int)
*slice3 = []int{1, 2, 3}
fmt.Println((*slice3)[0]) // Prints 1
您可能会将new
与make
混淆。 make
适用于需要某种初始化的类型。对于切片,make
创建给定大小的切片。例如,make([]int, 5)
会创建一个长度为5的整数切片。make(T)
的类型为T
,而new(T)
的类型为*T
。这当然会让人感到困惑。以下是一些帮助解决问题的示例:
a := make([]int, 5) // s is now a slice of 5 integers
b := new([]int) // b points to a slice, but it's not initialized yet
*b = make([]int, 3) // now b points to a slice of 5 integers
示例6
// maps
var map1 map[int]string
var map2 = new(map[int]string)
地图,就像切片一样,需要一些初始化才能正常工作。这就是为什么上面示例中的map1
和map2
都没有准备好使用的原因。您需要先使用make
:
var map1 map[int]string // Not ready to be used
map1 = make(map[int]string) // Now it can be used
var map2 = new(map[int]string) // Has type *map[int]string; not ready to be used
*map2 = make(map[int]string) // Now *map2 can be used
额外备注
上面没有一个非常好的地方,所以我只是把它放在这里。
在Go中需要注意的一点是,如果你声明一个变量而没有初始化它,它实际上并没有被初始化。相反,它有一个零值。"这与C不同,未初始化的变量可以包含垃圾数据。
每种类型的值都为零。大多数类型的零值非常合理。例如,基本类型:
int
的值为0
bool
的值为false
string
的值为""
(int8
,uint16
,float32
等其他数字值均为0
或0.0
对于像结构和数组这样的复合类型,零值是递归的。也就是说,数组的零值是一个数组,其所有条目都设置为各自的零值(即[3]int
的零值为[3]int{0, 0, 0}
,因为0
为零价值int
)。
需要注意的另一件事是,当使用:=
语法时,某些表达式'类型无法推断。值得注意的主要是nil
。因此,i := nil
将产生编译器错误。原因是nil
用于所有指针类型(以及其他一些类型),因此编译器无法知道你的意思是否为nil int指针,或者是一个零指针等等。