new() make() 区别 切片扩容 assignment to entry in nil map append扩容
小结:
1、
在图 7.3 的第一幅图中:
var p *[]int = new([]int) // *p == nil; with len and cap 0
p := new([]int)
在第二幅图中, p := make([]int, 0)
,切片 已经被初始化,但是指向一个空的数组。
以上两种方式实用性都不高。下面的方法:
var v []int = make([]int, 10, 50)
或者
v := make([]int, 10, 50)
这样分配一个有 50 个 int 值的数组,并且创建了一个长度为 10,容量为 50 的 切片 v,该切片指向数组的前 10 个元素。
问题 7.3 给定 s := make([]byte, 5)
,len(s) 和 cap(s) 分别是多少?s = s[2:4]
,len(s) 和 cap(s) 又分别是多少?
问题 7.4 假设 s1 := []byte{'p', 'o', 'e', 'm'}
且 s2 := s1[2:]
,s2 的值是多少?如果我们执行 s2[1] = 't'
,s1 和 s2 现在的值又分别是多少?
译者注:如何理解 new、make、slice、map、channel 的关系
1.slice、map 以及 channel 都是 golang 内建的一种引用类型,三者在内存中存在多个组成部分, 需要对内存组成部分初始化后才能使用,而 make 就是对三者进行初始化的一种操作方式
2. new 获取的是存储指定变量内存地址的一个变量,对于变量内部结构并不会执行相应的初始化操作, 所以 slice、map、channel 需要 make 进行初始化并获取对应的内存地址,而非 new 简单的获取内存地址
实践:
l := new([]string) l1 := make([]string, 10) l2 := []string{""} l3 := [1]string{} l4 := [...]string{""}
l := new([]string) l1 := make([]string, 10) l2 := []string{""} l3 := [1]string{} l4 := [...]string{""} l5 := make([]string, 0) *l = append(*l, "a") l1 = append(l1, "a") l5 = append(l5, "a")
注意:l1 cap值由10变成了20,扩容2倍
词典的new make
注意:
make之后的append,扩容
m := map[string]string{} m["a"] = "a" m1 := make(map[string]string, 1) m1["a"] = "a" m2 := make(map[string]string, 2) m2["a"] = "a" m3 := new(map[string]string) // (*m3)["a"] = "a" // interface {}(string) "assignment to entry in nil map"
2、
类型T的指针 *T
new(T) allocates zeroed storage for a new item of type T and returns its address, a value of type *T
切片、字典、通道的类型 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.
make做的更多,原因:
切片、字典、通道在使用前需要初始化,比如,切片的长度、容量
Effective Go - The Go Programming Language https://go.dev/doc/effective_go
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
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.
如何获取一个指针值?
- 我们可以用内置函数
new
来为任何类型的值开辟一块内存并将此内存块的起始地址做为此值的地址返回。 假设T
是任一类型,则函数调用new(T)
返回一个类型为*T
的指针值。 存储在返回指针值所表示的地址处的值(可被看作是一个匿名变量)为T
的零值。 - 我们也可以使用前置取地址操作符
&
来获取一个可寻址的值的地址。 对于一个类型为T
的可寻址的值t
,我们可以用&t
来取得它的地址。&t
的类型为*T
。
指针 - Go语言101(通俗版Go白皮书) https://gfw.go101.org/article/pointer.html
the-way-to-go_ZH_CN/07.1.md at master · unknwon/the-way-to-go_ZH_CN · GitHub https://github.com/unknwon/the-way-to-go_ZH_CN/blob/master/eBook/07.1.md
Go 语言中的数组是一种 值类型(不像 C/C++ 中是指向首元素的指针),所以可以通过 new()
来创建: var arr1 = new([5]int)
。
那么这种方式和 var arr2 [5]int
的区别是什么呢?arr1 的类型是 *[5]int
,而 arr2 的类型是 [5]int
。
实践:
1)
m := make(map[string]string)
n := new(map[string]string)
fmt.Println(m)
fmt.Println(n)
fmt.Println("&m", &m)
fmt.Println("&n", &n)
m["k"] = "v"
fmt.Println(m)
(*n)["k"] = "v"
fmt.Println(n)
map[]
&map[]
&m &map[]
&n 0xc000092168
map[k:v]
panic: assignment to entry in nil map
the-way-to-go_ZH_CN/06.5.md at master · Unknwon/the-way-to-go_ZH_CN https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/06.5.md
new、make | new 和 make 均是用于分配内存:new 用于值类型和用户定义的类型,如自定义结构,make 用于内置引用类型(切片、map 和管道)。它们的用法就像是函数,但是将类型作为参数:new(type)、make(type)。new(T) 分配类型 T 的零值并返回其地址,也就是指向类型 T 的指针(详见第 10.1 节)。它也可以被用于基本类型:v := new(int) 。make(T) 返回类型 T 的初始化之后的值,因此它比 new 进行更多的工作(详见第 7.2.3/4 节、第 8.1.1 节和第 14.2.1 节)new() 是一个函数,不要忘记它的括号 |
src/builtin/builtin.go:189
// The make built-in function allocates and initializes an object of type
// slice, map, or chan (only). Like new, the first argument is a type, not a
// value. Unlike new, make's return type is the same as the type of its
// argument, not a pointer to it. The specification of the result depends on
// the type:
// Slice: The size specifies the length. The capacity of the slice is
// equal to its length. A second integer argument may be provided to
// specify a different capacity; it must be no smaller than the
// length. For example, make([]int, 0, 10) allocates an underlying array
// of size 10 and returns a slice of length 0 and capacity 10 that is
// backed by this underlying array.
// Map: An empty map is allocated with enough space to hold the
// specified number of elements. The size may be omitted, in which case
// a small starting size is allocated.
// Channel: The channel's buffer is initialized with the specified
// buffer capacity. If zero, or the size is omitted, the channel is
// unbuffered.
func make(t Type, size ...IntegerType) Type
// The new built-in function allocates memory. The first argument is a type,
// not a value, and the value returned is a pointer to a newly
// allocated zero value of that type.
func new(Type) *Type
7.2.4 new() 和 make() 的区别
看起来二者没有什么区别,都在堆上分配内存,但是它们的行为不同,适用于不同的类型。
- new(T) 为每个新的类型T分配一片内存,初始化为 0 并且返回类型为*T的内存地址:这种方法 返回一个指向类型为 T,值为 0 的地址的指针,它适用于值类型如数组和结构体(参见第 10 章);它相当于
&T{}
。 - make(T) 返回一个类型为 T 的初始值,它只适用于3种内建的引用类型:切片、map 和 channel(参见第 8 章,第 13 章)。
换言之,new 函数分配内存,make 函数初始化;
https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/07.2.md
the-way-to-go_ZH_CN/10.2.md at master · Unknwon/the-way-to-go_ZH_CN https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/10.2.md
10.2 使用工厂方法创建结构体实例
10.2.1 结构体工厂
Go 语言不支持面向对象编程语言中那样的构造子方法,但是可以很容易的在 Go 中实现 “构造子工厂”方法。为了方便通常会为类型定义一个工厂,按惯例,工厂的名字以 new 或 New 开头。假设定义了如下的 File 结构体类型:
type File struct {
fd int // 文件描述符
name string // 文件名
}
下面是这个结构体类型对应的工厂方法,它返回一个指向结构体实例的指针:
func NewFile(fd int, name string) *File {
if fd < 0 {
return nil
}
return &File{fd, name}
}
然后这样调用它:
f := NewFile(10, "./test.txt")
在 Go 语言中常常像上面这样在工厂方法里使用初始化来简便的实现构造函数。
如果 File
是一个结构体类型,那么表达式 new(File)
和 &File{}
是等价的。
这可以和大多数面向对象编程语言中笨拙的初始化方式做个比较:File f = new File(...)
。
我们可以说是工厂实例化了类型的一个对象,就像在基于类的OO语言中那样。
如果想知道结构体类型T的一个实例占用了多少内存,可以使用:size := unsafe.Sizeof(T{})
。
如何强制使用工厂方法
通过应用可见性规则参考4.2.1节、9.5 节就可以禁止使用 new 函数,强制用户使用工厂方法,从而使类型变成私有的,就像在面向对象语言中那样。
type matrix struct {
...
}
func NewMatrix(params) *matrix {
m := new(matrix) // 初始化 m
return m
}
在其他包里使用工厂方法:
package main
import "matrix"
...
wrong := new(matrix.matrix) // 编译失败(matrix 是私有的)
right := matrix.NewMatrix(...) // 实例化 matrix 的唯一方式
10.2.2 map 和 struct vs new() 和 make()
new 和 make 这两个内置函数已经在第 7.2.4 节通过切片的例子说明过一次。
现在为止我们已经见到了可以使用 make()
的三种类型中的其中两个:
slices / maps / channels(见第 14 章)
下面的例子说明了在映射上使用 new 和 make 的区别以及可能发生的错误:
示例 10.4 new_make.go(不能编译)
package main
type Foo map[string]string
type Bar struct {
thingOne string
thingTwo int
}
func main() {
// OK
y := new(Bar)
(*y).thingOne = "hello"
(*y).thingTwo = 1
// NOT OK
z := make(Bar) // 编译错误:cannot make type Bar
(*z).thingOne = "hello"
(*z).thingTwo = 1
// OK
x := make(Foo)
x["x"] = "goodbye"
x["y"] = "world"
// NOT OK
u := new(Foo)
(*u)["x"] = "goodbye" // 运行时错误!! panic: assignment to entry in nil map
(*u)["y"] = "world"
}
试图 make()
一个结构体变量,会引发一个编译错误,这还不是太糟糕,但是 new()
一个映射并试图使用数据填充它,将会引发运行时错误! 因为 new(Foo)
返回的是一个指向 nil
的指针,它尚未被分配内存。所以在使用 map
时要特别谨慎。
链接
- 目录
- 上一节:结构体定义
- 下一节:使用自定义包中的结构体
the-way-to-go_ZH_CN/04.2.md at master · Unknwon/the-way-to-go_ZH_CN https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/04.2.md
可见性规则
当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 private )。
问题 7.1 下面代码段的输出是什么?
a := [...]string{"a", "b", "c", "d"}
for i := range a {
fmt.Println("Array item", i, "is", a[i])
}
Go 语言中的数组是一种 值类型(不像 C/C++ 中是指向首元素的指针),所以可以通过 new()
来创建: var arr1 = new([5]int)
。
那么这种方式和 var arr2 [5]int
的区别是什么呢?arr1 的类型是 *[5]int
,而 arr2的类型是 [5]int
。
这样的结果就是当把一个数组赋值给另一个时,需要再做一次数组内存的拷贝操作。例如:
arr2 := *arr1
arr2[2] = 100
这样两个数组就有了不同的值,在赋值后修改 arr2 不会对 arr1 生效。
所以在函数中数组作为参数传入时,如 func1(arr2)
,会产生一次数组拷贝,func1 方法不会修改原始的数组 arr2。
如果你想修改原数组,那么 arr2 必须通过&操作符以引用方式传过来,例如 func1(&arr2),下面是一个例子
示例 7.2 pointer_array.go:
package main
import "fmt"
func f(a [3]int) { fmt.Println(a) }
func fp(a *[3]int) { fmt.Println(a) }
func main() {
var ar [3]int
f(ar) // passes a copy of ar
fp(&ar) // passes a pointer to ar
}
输出结果:
[0 0 0]
&[0 0 0]
另一种方法就是生成数组切片并将其传递给函数(详见第 7.1.4 节)。