Data
Allocation with new
Go has two allocation primitives, the built-in functions new and make. They do different things and apply to different types, which can be confusing, but the rules are simple. Let's talk about new first. It's a built-in function that allocates memory, but unlike its namesakes in some other languages it does not initialize the memory, it only zeros it. That is, new(T) allocates zeroed storage for a new item of type T and returns its address, a value of type *T. In Go terminology, it returns a pointer to a newly allocated zero value of type T.
Since the memory returned by new is zeroed, it's helpful to arrange when designing your data structures that the zero value of each type can be used without further initialization. This means a user of the data structure can create one with new and get right to work. For example, the documentation for bytes.Buffer states that "the zero value for Buffer is an empty buffer ready to use." Similarly, sync.Mutex does not have an explicit constructor or Init method. Instead, the zero value for a sync.Mutex is defined to be an unlocked mutex.
The zero-value-is-useful property works transitively. Consider this type declaration.
type SyncedBuffer struct {
lock sync.Mutex
buffer bytes.Buffer
}
Values of type SyncedBuffer are also ready to use immediately upon allocation or just declaration. In the next snippet, both p and v will work correctly without further arrangement.
p := new(SyncedBuffer) // type *SyncedBuffer
var v SyncedBuffer // type SyncedBuffer
这段文本描述了 Go 语言中的内存分配方式和如何使用 new 这个内置函数。我将帮助您逐段理解它。
1、new 和 make 的区别:
Go 语言有两个内存分配原语:new 和 make。虽然它们的名字可能在其他语言中有不同的含义,但在 Go 中,它们有特定的用途和行为。这段文本主要关注 new。
2、new 的功能:
new(T) 是一个内置函数,用于为类型 T 分配内存。重要的是要注意,new 只是将内存置零,而不进行特定的初始化。这意味着,如果你使用 new 分配了一个整数,你将得到一个指向该整数的指针,该整数的值为 0(因为它已被置零)。
3、设计数据结构时的零值的用处:
在 Go 中,当一个变量被声明但没有明确的初始化时,它的值为该类型的零值(例如,数值类型的零值是 0,字符串的零值是空字符串,指针的零值是 nil 等)。如果你可以确保数据结构的零值状态是有意义且可用的,那么它就可以在使用 new 或声明时立即使用,而不需要进一步的初始化。
文本中给出了 bytes.Buffer 和 sync.Mutex 两个例子。它们都被设计为其零值状态下是可用的,这意味着你不需要做任何额外的工作就可以开始使用它们。
4、零值的传递性:
当你有一个结构体,其中的字段本身的零值是有意义的,那么这个结构体的零值通常也是有意义的。文本中提到了 SyncedBuffer 这个例子,它有两个字段:一个 sync.Mutex 和一个 bytes.Buffer。由于这两个字段的零值都是有意义和可用的,因此 SyncedBuffer 的零值也是可以直接使用的。
5、声明和分配的例子:
文本给出了两种方式来获取 SyncedBuffer 类型的值。使用 new 会返回一个指向新分配结构体的指针,而直接声明则给你一个结构体值。两者都可以立即使用,因为它们的零值都是有意义的。
总的来说,这段文本强调了在 Go 中,如何通过利用类型的零值来简化代码和设计,使得数据结构在声明或使用 new 分配时立即可用。
Constructors and composite literals
Sometimes the zero value isn't good enough and an initializing constructor is necessary, as in this example derived from package os.
func NewFile(fd int, name string) *File {
if fd < 0 {
return nil
}
f := new(File)
f.fd = fd
f.name = name
f.dirinfo = nil
f.nepipe = 0
return f
}
There's a lot of boilerplate in there. We can simplify it using a composite literal, which is an expression that creates a new instance each time it is evaluated.
func NewFile(fd int, name string) *File {
if fd < 0 {
return nil
}
f := File{fd, name, nil, 0}
return &f
}
Note that, unlike in C, it's perfectly OK to return the address of a local variable; the storage associated with the variable survives after the function returns. In fact, taking the address of a composite literal allocates a fresh instance each time it is evaluated, so we can combine these last two lines.
return &File{fd, name, nil, 0}
The fields of a composite literal are laid out in order and must all be present. However, by labeling the elements explicitly as field:value pairs, the initializers can appear in any order, with the missing ones left as their respective zero values. Thus we could say
return &File{fd: fd, name: name}
As a limiting case, if a composite literal contains no fields at all, it creates a zero value for the type. The expressions new(File) and &File{} are equivalent.
Composite literals can also be created for arrays, slices, and maps, with the field labels being indices or map keys as appropriate. In these examples, the initializations work regardless of the values of Enone, Eio, and Einval, as long as they are distinct.
a := [...]string {Enone: "no error", Eio: "Eio", Einval: "invalid argument"}
s := []string {Enone: "no error", Eio: "Eio", Einval: "invalid argument"}
m := map[int]string{Enone: "no error", Eio: "Eio", Einval: "invalid argument"}
这段话主要讲的是Go语言中的构造函数和复合字面量。
1、构造函数: 在某些情况下,一个类型的零值(默认值)可能不足以满足我们的需要,所以我们需要一个初始化的构造函数。在给出的例子中,有一个NewFile函数,其目的是创建一个File类型的对象。
2、复合字面量: 复合字面量是一种表达式,每次它被评估时都会创建一个新的实例。在第二个NewFile函数版本中,通过使用复合字面量,我们减少了初始化File对象的代码。
3、返回局部变量的地址: 在Go中,与C语言不同,返回局部变量的地址是完全可以的。这是因为在函数返回后,与该变量相关的存储仍然存在。因此,每次评估复合字面量的地址时,都会分配一个新的实例。
4、字段的顺序: 在复合字面量中,字段是按照顺序排列的,必须全部出现。但是,通过明确地标记元素为field:value对,初始化器可以以任何顺序出现,缺失的部分将保持其零值。这意味着你可以只初始化你想要的字段。
5、零值的复合字面量: 如果复合字面量不包含任何字段,那么它将为该类型创建一个零值。表达式new(File)和&File{}是等价的。
6、数组、切片和映射的复合字面量: 除了结构体,复合字面量也可以用于数组、切片和映射。对于这些类型,字段标签是相应的索引或映射键。在给出的例子中,不论Enone、Eio和Einval的值是什么(只要它们是不同的),初始化都是有效的。
简单来说,这段话通过示例展示了如何在Go中使用复合字面量来简化对象的初始化,并强调了与其他编程语言相比Go的一些独特之处。
Allocation with make
Back to allocation. The built-in function make(T, args) serves a purpose different from new(T). It creates slices, maps, and channels only, and it returns an initialized (not zeroed) value of type T (not *T). The reason for the distinction is that these three types represent, under the covers, references to data structures that must be initialized before use. A slice, for example, is a three-item descriptor containing a pointer to the data (inside an array), the length, and the capacity, and until those items are initialized, the slice is nil. For slices, maps, and channels, make initializes the internal data structure and prepares the value for use. For instance,
make([]int, 10, 100)
allocates an array of 100 ints and then creates a slice structure with length 10 and a capacity of 100 pointing at the first 10 elements of the array. (When making a slice, the capacity can be omitted; see the section on slices for more information.) In contrast, new([]int) returns a pointer to a newly allocated, zeroed slice structure, that is, a pointer to a nil slice value.
These examples illustrate the difference between new and make.
var p *[]int = new([]int) // allocates slice structure; *p == nil; rarely useful
var v []int = make([]int, 100) // the slice v now refers to a new array of 100 ints
// Unnecessarily complex:
var p *[]int = new([]int)
*p = make([]int, 100, 100)
// Idiomatic:
v := make([]int, 100)
Remember that make applies only to maps, slices and channels and does not return a pointer. To obtain an explicit pointer allocate with new or take the address of a variable explicitly.
这段话讲述了 Go 语言中 make 和 new 的区别,尤其是在初始化切片、映射和通道时的作用。
1、make 的功能:
make 是一个内置函数,用于创建切片、映射和通道。
与 new 不同,make 返回的是一个已初始化的 T 类型的值,而不是 T 类型的指针。
切片、映射和通道的底层都是数据结构引用,这些数据结构在使用前必须被初始化。例如,切片包括指向数据的指针(存储在数组中)、长度和容量。只有当这些项初始化后,切片才不是 nil。
对于切片、映射和通道,make 会初始化内部数据结构并准备好值供使用。
2、make 示例:
make([]int, 10, 100) 创建一个包含 100 个 int 的数组,然后创建一个长度为 10、容量为 100 的切片,该切片指向数组的前 10 个元素。
而 new([]int) 返回一个指向新分配的、零值化的切片结构的指针,也就是说,它返回一个指向 nil 切片值的指针。
3、new 与 make 的比较:
var p *[]int = new([]int) 分配一个切片结构,并使 *p 等于 nil。这种用法很少见。
var v []int = make([]int, 100) 使切片 v 引用一个新的包含 100 个 int 的数组。
使用 new 和 make 组合来创建和初始化一个切片是不必要的复杂化,而直接使用 make 是惯用的方式。
4、要点回顾:
make 只用于切片、映射和通道,并且它不返回指针。
如果想获得一个明确的指针,可以使用 new 进行分配或直接取一个变量的地址。
简而言之,这段文字解释了在 Go 中如何使用 make 和 new 来创建和初始化特定的数据类型,以及它们之间的主要区别。
Arrays
Arrays are useful when planning the detailed layout of memory and sometimes can help avoid allocation, but primarily they are a building block for slices, the subject of the next section. To lay the foundation for that topic, here are a few words about arrays.
There are major differences between the ways arrays work in Go and C. In Go,
Arrays are values. Assigning one array to another copies all the elements.
In particular, if you pass an array to a function, it will receive a copy of the array, not a pointer to it.
The size of an array is part of its type. The types [10]int and [20]int are distinct.
The value property can be useful but also expensive; if you want C-like behavior and efficiency, you can pass a pointer to the array.
func Sum(a *[3]float64) (sum float64) {
for _, v := range *a {
sum += v
}
return
}
array := [...]float64{7.0, 8.5, 9.1}
x := Sum(&array) // Note the explicit address-of operator
But even this style isn't idiomatic Go. Use slices instead.
这段话是关于 Go 语言中数组的简介,强调数组与切片的关系并对比了 Go 和 C 语言中数组的差异。
1、Go 中的数组:
在 Go 语言中,数组是有固定大小的数据结构,用于存储同一类型的元素序列。
数组在 Go 中有特定的用途,主要是作为切片的基础,切片是接下来要介绍的主题。
Go 与 C 语言中数组的主要差异:
**2、数组作为值:
**在 Go 中,数组是值类型。这意味着当你将一个数组赋值给另一个数组时,所有的元素都会被复制。这与 C 语言不同,在 C 语言中,数组赋值通常是指针操作。
函数传参:如果你将一个数组作为参数传递给函数,函数会接收到数组的一个副本,而不是数组的指针。
3、数组的大小是其类型的一部分:
这意味着长度为10的整数数组的类型([10]int)与长度为20的整数数组的类型([20]int)是不同的。
值属性的双重性:
将数组作为值看待是有用的,因为你不必担心原始数组会被修改。但是,这也可能带来性能损失,因为复制大数组可能会很昂贵。
如果你想要类似 C 语言的行为和效率,你可以传递数组的指针,如文章中的 Sum 函数示例所示。
// 测试 数组 作为参数传入,是值拷贝
// 性能消耗,虽然可以把数组做指针传入,但一般使用切片
func c(s [3]string) {
s[0] = "A"
}
func Test_array(t *testing.T) {
s := [3]string{"a", "b", "c"}
c(s)
fmt.Println(s)
// 打印结果
[a b c]
// 测试 切片 作为参数传入,是指针拷贝
func f(s []string) {
s[0] = "A"
}
func Test_slice(t *testing.T) {
s := []string{"a", "b", "c"}
f(s)
fmt.Println(s)
//打印结果
[A b c]
4、更惯用的 Go 语言风格:
虽然可以传递数组的指针,但这种方式在 Go 中并不是惯用的写法。更推荐的做法是使用切片。
总的来说,这段话强调了 Go 中数组作为值类型的重要性,并推荐在实际编程中更多地使用切片,因为它们更为灵活且更符合 Go 的惯用风格。
Slices
Slices wrap arrays to give a more general, powerful, and convenient interface to sequences of data. Except for items with explicit dimension such as transformation matrices, most array programming in Go is done with slices rather than simple arrays.
Slices hold references to an underlying array, and if you assign one slice to another, both refer to the same array. If a function takes a slice argument, changes it makes to the elements of the slice will be visible to the caller, analogous to passing a pointer to the underlying array. A Read function can therefore accept a slice argument rather than a pointer and a count; the length within the slice sets an upper limit of how much data to read. Here is the signature of the Read method of the File type in package os:
func (f *File) Read(buf []byte) (n int, err error)
The method returns the number of bytes read and an error value, if any. To read into the first 32 bytes of a larger buffer buf, slice (here used as a verb) the buffer.
n, err := f.Read(buf[0:32])
Such slicing is common and efficient. In fact, leaving efficiency aside for the moment, the following snippet would also read the first 32 bytes of the buffer.
var n int
var err error
for i := 0; i < 32; i++ {
nbytes, e := f.Read(buf[i:i+1]) // Read one byte.
n += nbytes
if nbytes == 0 || e != nil {
err = e
break
}
}
The length of a slice may be changed as long as it still fits within the limits of the underlying array; just assign it to a slice of itself. The capacity of a slice, accessible by the built-in function cap, reports the maximum length the slice may assume. Here is a function to append data to a slice. If the data exceeds the capacity, the slice is reallocated. The resulting slice is returned. The function uses the fact that len and cap are legal when applied to the nil slice, and return 0.
func Append(slice, data []byte) []byte {
l := len(slice)
if l + len(data) > cap(slice) { // reallocate
// Allocate double what's needed, for future growth.
newSlice := make([]byte, (l+len(data))*2)
// The copy function is predeclared and works for any slice type.
copy(newSlice, slice)
slice = newSlice
}
slice = slice[0:l+len(data)]
copy(slice[l:], data)
return slice
}
We must return the slice afterwards because, although Append can modify the elements of slice, the slice itself (the run-time data structure holding the pointer, length, and capacity) is passed by value.
The idea of appending to a slice is so useful it's captured by the append built-in function. To understand that function's design, though, we need a little more information, so we'll return to it later.
这段文字介绍了 Go 语言中的“切片”(Slices)概念。切片是 Go 中一个重要的数据结构,它是基于数组的,但提供了更通用、强大和方便的接口。以下是对这段文字的解释:
1、切片与数组:
切片是对数组的封装,提供了一个更加通用、强大和便利的数据序列接口。
除了具有明确维度的项(如转换矩阵),Go 的大部分数组编程都使用切片而不是简单数组。
2、切片的引用特性:
切片保存对底层数组的引用。如果你将一个切片分配给另一个切片,它们都将引用同一个数组。
如果函数接受一个切片作为参数,并修改了该切片的元素,这些更改对调用者是可见的,这与传递底层数组的指针相似。
3、读取函数的切片参数:
函数可以接受切片作为参数,而不是指针和计数。切片的长度设置了要读取的数据的上限。
为了读取缓冲区的前32个字节,你可以“切分”缓冲区。
4、切片长度的变化:
切片的长度可以改变,只要它仍然适合底层数组。只需将其分配给自己的切片。
使用内建函数 cap,可以报告切片可能假定的最大长度。
5、向切片添加数据:
如果数据超出容量,切片将被重新分配。
必须在之后返回切片,因为尽管 Append 可以修改切片的元素,但切片本身(持有指针、长度和容量的运行时数据结构)是按值传递的。
6、内建的 append 函数:
将数据添加到切片是非常有用的,因此有一个内建函数 append。但为了理解这个函数的设计,我们需要更多的信息,这在之后会再次提及。
总的来说,这段话深入介绍了切片的工作原理、其与数组的关系、如何利用切片以及与其相关的操作和函数。
Two-dimensional slices
Go's arrays and slices are one-dimensional. To create the equivalent of a 2D array or slice, it is necessary to define an array-of-arrays or slice-of-slices, like this:
type Transform [3][3]float64 // A 3x3 array, really an array of arrays.
type LinesOfText [][]byte // A slice of byte slices.
Because slices are variable-length, it is possible to have each inner slice be a different length. That can be a common situation, as in our LinesOfText example: each line has an independent length.
text := LinesOfText{
[]byte("Now is the time"),
[]byte("for all good gophers"),
[]byte("to bring some fun to the party."),
}
Sometimes it's necessary to allocate a 2D slice, a situation that can arise when processing scan lines of pixels, for instance. There are two ways to achieve this. One is to allocate each slice independently; the other is to allocate a single array and point the individual slices into it. Which to use depends on your application. If the slices might grow or shrink, they should be allocated independently to avoid overwriting the next line; if not, it can be more efficient to construct the object with a single allocation. For reference, here are sketches of the two methods. First, a line at a time:
// Allocate the top-level slice.
picture := make([][]uint8, YSize) // One row per unit of y.
// Loop over the rows, allocating the slice for each row.
for i := range picture {
picture[i] = make([]uint8, XSize)
}
And now as one allocation, sliced into lines:
// Allocate the top-level slice, the same as before.
picture := make([][]uint8, YSize) // One row per unit of y.
// Allocate one large slice to hold all the pixels.
pixels := make([]uint8, XSize*YSize) // Has type []uint8 even though picture is [][]uint8.
// Loop over the rows, slicing each row from the front of the remaining pixels slice.
for i := range picture {
picture[i], pixels = pixels[:XSize], pixels[XSize:]
}
这段文字描述了如何在 Go 语言中创建和处理二维切片。以下是这段文字的详细解释:
1、一维数组和切片:
Go 的数组和切片是一维的。
2、创建二维数组或切片:
要创建相当于2D的数组或切片,需要定义一个数组的数组或切片的切片。
例如,Transform 是一个3x3的数组(实际上是一个数组的数组),而 LinesOfText 是一个字节切片的切片。
3、变长的特性:
由于切片是变长的,每个内部切片可以有不同的长度。例如,每一行文本都可以有不同的长度。
4、分配二维切片:
在某些情况下,例如处理像素的扫描线时,可能需要分配一个2D切片。
有两种方法可以实现这一目标:
5、逐行分配:
1)、首先,为顶级切片分配空间。
然后,循环遍历每一行,为每一行的切片分配空间。
2)、单一分配,然后进行切片:
首先,为顶级切片分配空间,与之前相同。
接着,为所有像素分配一个大切片。
最后,循环遍历每一行,在前面的剩余像素切片中切片每一行。
选择哪种方法:
如果切片可能会增长或缩小,它们应该独立分配,以避免覆盖下一行。
如果不会改变大小,用一个单一的分配可能更为高效。
简而言之,这段文字描述了如何在 Go 语言中创建二维切片,以及在不同情况下选择最合适的分配策略。