文档章节

Go new和make的区别 && 数组和切片

秋风醉了
 秋风醉了
发布于 2017/02/13 01:16
字数 2589
阅读 293
收藏 1

Go new和make的区别以及切片的本质

 

make、new操作

make用于内建类型(map、slice 和channel)的内存分配。new用于各种类型的内存分配。

内建函数new本质上说跟其它语言中的同名函数功能一样:new(T)分配了零值填充的T类型的内存空间,并且返回其地址,即一个*T类型的值。用Go的术语说,它返回了一个指针,指向新分配的类型T的零值。有一点非常重要:

new返回指针。

内建函数make(T, args)与new(T)有着不同的功能,make只能创建slice、map和channel,并且返回一个有初始值(非零)的T类型,而不是*T。本质来讲,导致这三个类型有所不同的原因是指向数据结构的引用在使用前必须被初始化。例如,一个slice,是一个包含指向数据(内部array)的指针、长度和容量的三项描述符;在这些项目被初始化之前,slice为nil。对于slice、map和channel来说,make初始化了内部的数据结构,填充适当的值。

make返回初始化后的(非零)值。

下面这个图详细的解释了new和make之间的区别。

make和new对应底层的内存分配

 

Go数组

Go的切片是在数组之上的抽象数据类型,因此在了解切片之前必须要先理解数组。

数组类型定义了长度和元素类型。例如, [4]int 类型表示一个四个整数的数组。 数组的长度是固定的,长度是数组类型的一部分( [4]int 和 [5]int 是完全不同的类型)。 数组可以以常规的索引方式访问,表达式 s[n] 访问数组的第 n 个元素。

var a [4]int
a[0] = 1
i := a[0]
// i == 1

数组不需要显式的初始化;数组的零值是可以直接使用的,数组元素会自动初始化为其对应类型的零值:

// a[2] == 0, int 类型的零值

类型 [4]int 对应内存中四个连续的整数:

Go的数组是值语义。一个数组变量表示整个数组,它不是指向第一个元素的指针(不像 C 语言的数组)。 当一个数组变量被赋值或者被传递的时候,实际上会复制整个数组。 (为了避免复制数组,你可以传递一个指向数组的指针,但是数组指针并不是数组。) 可以将数组看作一个特殊的struct,结构的字段名对应数组的索引,同时成员的数目固定。

数组的字面值像这样:

b := [2]string{"Penn", "Teller"}

当然,也可以让编译器统计数组字面值中元素的数目:

b := [...]string{"Penn", "Teller"}

这两种写法, b 都是对应 [2]string 类型。

 

Go切片

数组虽然有适用它们的地方,但是数组不够灵活,因此在Go代码中数组使用的并不多。 但是,切片则使用得相当广泛。切片基于数组构建,但是提供更强的功能和便利。

切片类型的写法是 []T , T 是切片元素的类型。和数组不同的是,切片类型并没有给定固定的长度。

切片的字面值和数组字面值很像,不过切片没有指定元素个数:

letters := []string{"a", "b", "c", "d"}

切片可以使用内置函数 make 创建,函数签名为:

func make([]T, len, cap) []T

其中T代表被创建的切片元素的类型。函数 make 接受一个类型、一个长度和一个可选的容量参数。 调用 make 时,内部会分配一个数组,然后返回数组对应的切片。

var s []byte

s = make([]byte, 5, 5)

// s == []byte{0, 0, 0, 0, 0}

当容量参数被忽略时,它默认为指定的长度。下面是简洁的写法:

s := make([]byte, 5)

可以使用内置函数 len 和 cap 获取切片的长度和容量信息。

len(s) == 5

cap(s) == 5

接下来的两个小节将讨论长度和容量之间的关系。

切片的零值为 nil 。对于切片的零值, len 和 cap 都将返回0。

切片也可以基于现有的切片或数组生成。切分的范围由两个由冒号分割的索引对应的半开区间指定。 例如,表达式 b[1:4] 创建的切片引用数组 b 的第1到3个元素空间(对应切片的索引为0到2)。

b := []byte{'g', 'o', 'l', 'a', 'n', 'g'}

// b[1:4] == []byte{'o', 'l', 'a'}, sharing the same storage as b

切片的开始和结束的索引都是可选的;它们分别默认为零和数组的长度。

// b[:2] == []byte{'g', 'o'}

// b[2:] == []byte{'l', 'a', 'n', 'g'}

// b[:] == b

下面语法也是基于数组创建一个切片:

x := [3]string{"Лайка", "Белка", "Стрелка"}

s := x[:] // a slice referencing the storage of x

 

切片的本质

一个切片是一个数组片段的描述。它包含了指向数组的指针,片段的长度, 和容量(片段的最大长度)。

前面使用 make([]byte, 5) 创建的切片变量 s 的结构如下:

长度是切片引用的元素数目。容量是底层数组的元素数目(从切片指针开始)。 关于长度和容量和区域将在下一个例子说明。

我们继续对 s 进行切片,观察切片的数据结构和它引用的底层数组:

s = s[2:4]

切片操作并不复制切片指向的元素。它创建一个新的切片并复用原来切片的底层数组。 这使得切片操作和数组索引一样高效。因此,通过一个新切片修改元素会影响到原始切片的对应元素。

d := []byte{'r', 'o', 'a', 'd'}

e := d[2:]

// e == []byte{'a', 'd'}

e[1] = 'm'

// e == []byte{'a', 'm'}

// d == []byte{'r', 'o', 'a', 'm'}

前面创建的切片 s 长度小于它的容量。我们可以增长切片的长度为它的容量:

s = s[:cap(s)]

切片增长不能超出其容量。增长超出切片容量将会导致运行时异常,就像切片或数组的索引超 出范围引起异常一样。同样,不能使用小于零的索引去访问切片之前的元素。

 

切片的生长(copy and append 函数)

要增加切片的容量必须创建一个新的、更大容量的切片,然后将原有切片的内容复制到新的切片。 整个技术是一些支持动态数组语言的常见实现。下面的例子将切片 s 容量翻倍,先创建一个2倍 容量的新切片 t ,复制 s 的元素到 t ,然后将 t 赋值给 s :

t := make([]byte, len(s), (cap(s)+1)*2) // +1 in case cap(s) == 0
for i := range s {
        t[i] = s[i]
}
s = t

循环中复制的操作可以由 copy 内置函数替代。copy 函数将源切片的元素复制到目的切片。 它返回复制元素的数目。

func copy(dst, src []T) int

copy 函数支持不同长度的切片之间的复制(它只复制较短切片的长度个元素)。 此外, copy 函数可以正确处理源和目的切片有重叠的情况。

使用 copy 函数,我们可以简化上面的代码片段:

t := make([]byte, len(s), (cap(s)+1)*2)

copy(t, s)

s = t

一个常见的操作是将数据追加到切片的尾部。下面的函数将元素追加到切片尾部, 必要的话会增加切片的容量,最后返回更新的切片:

func AppendByte(slice []byte, data ...byte) []byte {
    m := len(slice)
    n := m + len(data)
    if n > cap(slice) { // if necessary, reallocate
        // allocate double what's needed, for future growth.
        newSlice := make([]byte, (n+1)*2)
        copy(newSlice, slice)
        slice = newSlice
    }
    slice = slice[0:n]
    copy(slice[m:n], data)
    return slice
}

下面是 AppendByte 的一种用法:

p := []byte{2, 3, 5}

p = AppendByte(p, 7, 11, 13)

// p == []byte{2, 3, 5, 7, 11, 13}

类似 AppendByte 的函数比较实用,因为它提供了切片容量增长的完全控制。 根据程序的特点,可能希望分配较小的活较大的块,或则是超过某个大小再分配。

但大多数程序不需要完全的控制,因此Go提供了一个内置函数 append , 用于大多数场合;它的函数签名:

func append(s []T, x ...T) []T

append 函数将 x 追加到切片 s 的末尾,并且在必要的时候增加容量。

a := make([]int, 1)

// a == []int{0}

a = append(a, 1, 2, 3)

// a == []int{0, 1, 2, 3}

如果是要将一个切片追加到另一个切片尾部,需要使用 ... 语法将第2个参数展开为参数列表。

由于切片的零值 nil 用起来就像一个长度为零的切片,我们可以声明一个切片变量然后在循环 中向它追加数据:

// Filter returns a new slice holding only
// the elements of s that satisfy f()
func Filter(s []int, fn func(int) bool) []int {
    var p []int // == nil
    for _, v := range s {
        if fn(v) {
            p = append(p, v)
        }
    }
    return p
}

 

可能的“陷阱”

正如前面所说,切片操作并不会复制底层的数组。整个数组将被保存在内存中,直到它不再被引用。 有时候可能会因为一个小的内存引用导致保存所有的数据。

例如, FindDigits 函数加载整个文件到内存,然后搜索第一个连续的数字,最后结果以切片方式返回。

var digitRegexp = regexp.MustCompile("[0-9]+")

func FindDigits(filename string) []byte {
    b, _ := ioutil.ReadFile(filename)
    return digitRegexp.Find(b)
}

这段代码的行为和描述类似,返回的 []byte 指向保存整个文件的数组。因为切片引用了原始的数组, 导致 GC 不能释放数组的空间;只用到少数几个字节却导致整个文件的内容都一直保存在内存里。

要修复整个问题,可以将感兴趣的数据复制到一个新的切片中:

func CopyDigits(filename string) []byte {
    b, _ := ioutil.ReadFile(filename)
    b = digitRegexp.Find(b)
    c := make([]byte, len(b))
    copy(c, b)
    return c
}

转载:

https://blog.go-zh.org/go-slices-usage-and-internals

http://www.cnblogs.com/junneyang/p/6070238.html

==========END==========

本文转载自:

共有 人打赏支持
秋风醉了
粉丝 232
博文 574
码字总数 405033
作品 0
朝阳
程序员
【原创翻译】深度剖析Go数据结构

当向一个新程序员解释Go语言时,我发现如果解释Go的数据是如何在内存中表示的,将有助于建立编写高效程序的良好直觉。 基础类型 让我们从一些简单的例子开始: 变量i是int类型,在内存中占用...

zingscript
2014/01/21
579
3
go语言学习-数组, slice和map

数据结构 数组 slice map 数组为值传递,当赋值给另外一个变量的时候是将值全部都copy,存一个副本,改变副本的值不会改变原有数组。slice为指针传递,改变值会改变原有slice中存的值 数组 ...

solate
2016/07/15
13
0
Go内置高级数据类型

Go内置高级数据类型 Go数组的定义 Go初始化数组 像刚才上面那样,数组的元素时一个一个初始化的,如果知道了数组的所有元素,在定义的时候就可以初始化, 数组的长度在定义时可以不写,也可以...

秋风醉了
2016/05/09
64
0
Golang 中的数组 (array) 和切片 (slice)

huangwenwei - 字里行间 | Golang 中的数组 (array) 和切片 (slice) 中文描述约定 array: 数组 slice: 切片 来做个测试,不在机器上运行下面的代码,请给出输出 如果你猜测的结果是 ,恭喜你...

hww_面条酱
2017/10/08
0
0
go语言定义变量/类型的方式

1、数组 2、切片 切片可以通过数组构建,这里不讨论这种方式 可以发现[]中有数字的是固定长度的数组,没有数字的表示切片 3、map // 先声明map var m1 map[string]string // 再使用make函数创...

tree2013
2016/10/23
7
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

kernel version does not match DSO version

错误信息: kernel version 384.11 does not match DSO version 384.130.0 原因是: cuda driver版本太低,不匹配DSO 简单有效的修复方法,升级nvidia driver, 步骤如下: 1. google seach ...

刘小米
今天
0
0
maven坐标和依赖

一、maven坐标详解 <groupId>com.fgt.club</groupId><artifactId>club-common-service-facade</artifactId><version>3.0.0</version><packaging>jar</packaging> maven的坐标元素说......

老韭菜
今天
1
0
springmvc-servlet.xml配置表功能解释

问:<?xml version="1.0" encoding="UTF-8" ?> 答: xml version="1.0"表示是此xml文件的版本是1.0 encoding="UTF-8"表示此文件的编码方式是UTF-8 问:<!DOCTYPE beans PUBLIC "-//SPRING//......

隐士族隐逸
今天
1
0
基于TP5的微信的公众号获取登录用户信息

之前讲过微信的公众号自动登录的菜单配置,这次记录一下在TP5项目中获取自动登录的用户信息并存到数据库的操作 基本的流程为:微信设置自动登录的菜单—>访问的URL指定的函数里获取用户信息—...

月夜中徘徊
今天
0
0
youTrack

package jetbrains.teamsys.license.runtime; 计算lis package jetbrains.ring.license.reader; 验证lis 安装后先不要生成lis,要把相关文件进行替换 ring-license-checker-1.0.41.jar char......

max佩恩
今天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部