去,这是你需要知道的任务和分配和初始化吗?

时间:2014-01-06 01:39:22

标签: go

令人难以置信的是,Go有多少种不同的方式可用于变量初始化。也许我完全不理解这一点,或者Go是一个很大的想法。很多东西感觉并不自然,看起来他们添加了功能,因为他们发现它们丢失了。初始化就是其中之一。

Here is running app on Go playground showing different ways of initialization

这是我理解的

  1. 有值和指针。值使用var =:=启动。
  2. :=仅适用于方法
  3. 要创建值和引用,请使用new或&。它们只适用于复合类型。
  4. 有一些创建地图和切片的全新方法
  5. 使用 make var x [] int 创建切片和地图。注意到没有=:=
  6. 有没有简单的方法可以让新手了解这一切?阅读规范无处不在地提供了所有这些。

3 个答案:

答案 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)

是的,这是我早期发现令人困惑的事情之一。我已经提出了自己的经验法则,这可能是也可能不是最佳做法,但它们对我很有帮助。 在斯蒂芬关于零值的评论后调整

  1. 尽可能使用:=,让Go推断类型
  2. 如果您只需要一个空切片或地图(并且不需要设置初始容量),请使用{}语法

    s := []string{}
     m := map[string]string{}

  3. 使用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),这非常简单。但是,当类型本身也很复杂时,它会变得混乱,就像切片类型一样。在您的示例中,slice2slice3都有*[]int类型。例如:

slice3 := new([]int)
*slice3 = []int{1, 2, 3}
fmt.Println((*slice3)[0]) // Prints 1

您可能会将newmake混淆。 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)

地图,就像切片一样,需要一些初始化才能正常工作。这就是为什么上面示例中的map1map2都没有准备好使用的原因。您需要先使用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的值为""

int8uint16float32等其他数字值均为00.0

的零值

对于像结构和数组这样的复合类型,零值是递归的。也就是说,数组的零值是一个数组,其所有条目都设置为各自的零值(即[3]int的零值为[3]int{0, 0, 0},因为0为零价值int)。


需要注意的另一件事是,当使用:=语法时,某些表达式'类型无法推断。值得注意的主要是nil。因此,i := nil将产生编译器错误。原因是nil用于所有指针类型(以及其他一些类型),因此编译器无法知道你的意思是否为nil int指针,或者是一个零指针等等。