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,扩容

    s := make([]int, 1)
    s = append(s, 2)
 
 
[]int len: 2, cap: 2, [0,2]
 
 

 

	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.

 

 

如何获取一个指针值?

有两种方式来得到一个指针值:
  1. 我们可以用内置函数new来为任何类型的值开辟一块内存并将此内存块的起始地址做为此值的地址返回。 假设T是任一类型,则函数调用new(T)返回一个类型为*T的指针值。 存储在返回指针值所表示的地址处的值(可被看作是一个匿名变量)为T的零值。
  2. 我们也可以使用前置取地址操作符&来获取一个可寻址的值的地址。 对于一个类型为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 节)。

 

posted @ 2018-09-11 01:29  papering  阅读(360)  评论(0编辑  收藏  举报