01-06回顾:

Go语言开发环境配置,

常用源码文件写法,

程序实体(尤其是变量)及其相关各种概念和编程技巧:

类型推断,变量重声明,可重名变量,类型推断,类型转换,别名类型和潜在类型

数组:

数组类型的值的长度是固定的,在声明数组的时候,长度必须给定,并且在之后不会改变,可以说数组的长度是其类型的。

比如:[1]string和[2]string就是两个不同的数组类型。

切片:

切片类型的值是可变长的。切片的类型字面量中只有元素的类型([]int),而没有长度。切片的长度可以自动地随着其中元素数量的增长而增加,但是不会随着元素数量的减少而减小。可以把切片看作是对数组的一层简单的封装,因为在每个切片的底层数据结构中,一定会包含一个数组。后者被叫做前者的底层数组,前者被看作是对后者的某个连续片段的引用。

最大区别:数组长度是固定的,切片是可变长的。

数组可以叫切片的底层数组,切片可以看作数组的连续片段的引用。

定义一个切片(三种方式):

①定义一个切片,然后让切片去引用一个已经创建好的数组

var arr []int = []int {, , , , }
var slice = arr[:]

②通过make来创建切片。基本语法:var 切片名 []type = make([], len, [cap]);参数说明:type是数据类型、len是大小、cap是切片容量(容量必须>=长度)

var slice []float64 = make([]float64, , )
  • 通过make方式创建切片可以指定切片大小和容量
  • 如果没有给切片的各个元素赋值,那么就会使用默认值(int、float=>0, strint=>"", bool=>false)
  • 如果make方式创建的切片对应的数组是由make底层维护,对外不可见,也就是只能通过slice访问各个元素

③定义一个切片,直接就指定具体数组,使用原理类似于make的方式

var slice []string = []string{"zhangsan", "lisi", "wangwu"}

第一种方式是直接引用数组,这个数组是事先存在的,程序员可见

第二种方式是通过make来创建切片,make也会创建一个数组,是由切片在底层维护,程序员不可见

切片长度和容量计算公式:

对底层数组容量是 k 的切片 slice[i:j] 来说:

长度: j - i

容量: k - i

扩展:

Go语言引用类型:切片类型,字典类型,通道类型,函数类型。

值类型:基础数据类型,数组类型,结构体类型。

如果传递的值是引用类型的,那么就是“传引用”(符号&);

如果传递的值是值类型的,那么就是“传值”(符号=);

从传递成本的角度讲,引用类型(&)的值要比值类型的值低很多。

面试题:怎样正确估算切片的长度和容量?

// demo15.go
package main import (
"fmt"
) func main() {
s1:=make([]int, )// 指明长度
fmt.Printf("The value of s1 is: %v\n", s1)
fmt.Printf("The length of s1 is: %d\n", len(s1))
fmt.Printf("The capacity of s1 is: %d\n", cap(s1))
fmt.Printf("The value of s1 is: %d\n", s1)
s2:=make([]int, , )// 指明长度和容量
fmt.Printf("The length of s2:%d\n", len(s2))
fmt.Printf("The capacity of s2 is:%d\n", cap(s2))
fmt.Printf("The value of s2 is:%d\n", s2)
}

切片s1和s2的容量分别是5和8。

问题分析:

切片的容量实际上代表它底层数组的长度。切片的底层数组长度是不可变的。

想象有一个窗口,通过这个窗口可以看到一个数组,但是不一定能看到该数组中所有元素,有一部分是被挡住了,有时候只能看到连续的一部分元素。

该例中,这个数组就是切片s2的底层数组,而这个窗口就是s2本身。s2的长度就是这个窗口的宽度,决定了你透过s2可以看到其底层数组中的哪几个连续的元素。

由于s2的长度是5(定义的),所以你可以看到其底层数组中的第1到第5个元素,对应的底层数组的索引范围是0到4([0,4])。

切片代表的窗口被划分成一个个的小格子,每个小格子都对应着其底层数组中的某一个元素。

s2中,窗口最左边的小格子对应的正好是其底层数组中的第一个元素,即索引为0的那个元素。因此,s2中的索引从0到4的元素,就是其底层数组中索引从0到4的那5个元素。

------

s3 := []int{1,2,3,4,5,6,7,8}

s4 := s3[3:6]

长度:

切片s3中有8个元素(整数1到8),s3的长度和容量都是8.

s4从s3中通过切片表达式初始化而来,其中[3:6]表达的就是透过新窗口能看到的,用减法可以计算出长度,6减去3为3.而索引范围为从3到5(不包括6,可以引申为区间表示法,即[3,6))。

s4中的索引从0到2,指向的元素对应的是s3中索引从3(起始索引)到5(结束索引)的3(长度)个元素。

容量:

切片的容量代表了它的底层数组的长度,但这仅限于用make函数或者切片字面量([]int{1,2,3})初始化切片的情况。

一个切片的容量可以被看作是透过这个窗口最多可以看到的底层数组中元素的个数。

s4是通过在s3上施加切片操作得来的,所以s3的底层数组等于s4的底层数组;而且,在底层数组不变的情况下,切片代表的窗口可以向右扩展,直至其底层数组的末尾,所以s4的容量就是其底层数组的长度8减去上述切片表达式([3:6])中的起始索引3,等于5。

 

要注意的是,切片代表的窗口是无法向左扩展的,也即无法透过s4看到s3中最左边的那3个元素。

切片和数组关系:

一个切片的容量可以被看作是透过这个窗口最多可以看到的底层数组中元素的个数。

扩展

问题1: 怎么样估算切片容量的增长?

如果一个已定义的切片无法容纳更多的元素,Go就会自动为其扩容。但是,扩容不会改变原来的切片,而是会生成一个容量更大的切片,然后把原切片元素和新元素都复制到新切片中。

对于扩容的计算方法,有两种:

1、一般情况下,新容量是原容量的2倍;

2、当原长度>=(大于或等于)1024时,新容量基准不再是2倍,而是1.25倍。新容量基准会在此基础上不断地被调整,直到不小于原长度与要追加的元素数量之和(新长度),最终,新容量往往会比新长度大一些,也有可能相等。

特殊情况:如果一次追加的元素过多,以至于使得新长度比原容量的2倍还要大,那么新容量就会以新长度为基准,最终的新容量往往比新容量基准要更大一些。

扩容append注意点:

append操作可能会导致原本使用同一个底层数组的两个Slice变量变为使用不同的底层数组。

package main

import "fmt"

func main() {

    var array = []int{, , , , }            // len:5,capacity:5
var newArray = array[:] // len:2,capacity:4 (已经使用了两个位置,所以还空两位置可以append)
fmt.Printf("array addr:%p\n", array) //0xc420098000
fmt.Printf("newArray addr:%p\n", newArray) //0xc420098008 可以看到newArray的地址指向的是array[1]的地址,即他们底层使用的还是一个数组
fmt.Printf("array value:%v\n", array) //[1 2 3 4 5]
fmt.Printf("newArray value:%v\n", newArray) //[2 3]
fmt.Println()
newArray[] = //更改后array、newArray都改变了
fmt.Printf("array value:%v\n", array) // [1 2 9 4 5]
fmt.Printf("newArray value:%v\n", newArray) // [2 9]
fmt.Println()
newArray = append(newArray, , ) //append 操作之后,array的len和capacity不变,newArray的len变为4,capacity:4。因为这是对newArray的操作
fmt.Printf("array value:%v\n", array) //[1 2 9 11 12] //注意对newArray做append操作之后,array[3],array[4]的值也发生了改变
fmt.Printf("newArray value:%v\n", newArray) //[2 9 11 12]
fmt.Println()
newArray = append(newArray, , ) // 因为newArray的len已经等于capacity,所以再次append就会超过capacity值,
// 此时,append函数内部会创建一个新的底层数组(是一个扩容过的数组),并将array指向的底层数组拷贝过去,然后在追加新的值。
fmt.Printf("array addr:%p\n", array) //0xc420098000
fmt.Printf("newArray addr:%p\n", newArray) //0xc4200a0000
fmt.Printf("array value:%v\n", array) //[1 2 9 11 12]
fmt.Printf("newArray value:%v\n", newArray) //[2 9 11 12 13 14] 他两已经不再是指向同一个底层数组了
}

问题2:切片的底层数组什么时候会被替换?

一个切片的底层数组永远不会被替换。

因为在“扩容”的时候,也一定会生成新的底层数组,同时也生成新的切片,而对原切片及其底层数组没有做任何改动。

append函数使用注意:在无需扩容时,返回的是指向原底层数组的切片;需要扩容时,返回的是指向新底层数组的新切片。

添加成员时,容量是2的指数递增的,2,4,8,16,32。而且是在长度要超过容量时,才增加容量。

只要新长度不会超过切片的原容量,那么使用append函数对其追加元素的时候就不会引起扩容,这只会使得紧邻切片窗口(抽象)右边的元素被新的元素替换掉。

思考题:

1、如果有多个切片指向了同一个底层数组,应该注意什么?

当若干个切片指向同一个底层数据时,对每一个切片的修改都会反映在底层数组中。如果覆盖其他切片已经指向的数组的值。

可以使用copy函数,重新创建一个切片,不影响源切片。

number5 := make([]int, 2)

copy(number5, numbers[:2])

2、怎样沿用“扩容”的思想来对切片进行“缩容”?

对切片再次切片,缩小起止范围,就可以缩容?

总结

切片的特点:占用内存少、创建便捷,可以通过“窗口”快速地定位并获取,并可以修改底层数组中的元素。

缺点1:删除切片中的元素就会造成大量元素的移动,注意空出的元素位置要“清空”,否则会造成内存泄露。

缺点2:切片频繁“扩容”,底层数组不断产生,内存分配的量以及元素复制的次数也会越来越多,影响运行性能。

如果没有一个合理、有效的切片“缩容”策略(旧底层数组无法回收,新底层数组不断产生),会导致内存的过度浪费,降低程序运行性能,使得内存溢出,并可能导致程序崩溃。

本学习笔记仅为了总结自己学到的Go语言核心知识,方便以后回忆,文中部分内容摘录自极客时间的《Go语言核心36讲》专栏,如有侵权,请联系我删除。

[Golang学习笔记] 07 数组和切片的更多相关文章

  1. go 学习笔记之数组还是切片都没什么不一样

    上篇文章中详细介绍了 Go 的基础语言,指出了 Go 和其他主流的编程语言的差异性,比较侧重于语法细节,相信只要稍加记忆就能轻松从已有的编程语言切换到 Go 语言的编程习惯中,尽管这种切换可能并不是特 ...

  2. Go语言学习笔记(4)——数组和切片

    1 数组的特点: 长度固定.元素数据类型相同.下标从0开始 1.1 声明和初始化: var array_name [size] type         var arr1 [10] float32   ...

  3. PHP学习笔记之数组篇

    摘要:其实PHP中的数组和JavaScript中的数组很相似,就是一系列键值对的集合.... 转载请注明来源:PHP学习笔记之数组篇   一.如何定义数组:在PHP中创建数组主要有两种方式,下面就让我 ...

  4. 学习笔记 07 --- JUC集合

    学习笔记 07 --- JUC集合 在讲JUC集合之前我们先总结一下Java的集合框架,主要包含Collection集合和Map类.Collection集合又能够划分为LIst和Set. 1. Lis ...

  5. go语言,golang学习笔记1 官网下载安装,中文社区,开发工具LiteIDE

    go语言,golang学习笔记1 官网下载安装,中文社区,开发工具LiteIDE Go语言是谷歌2009发布的专门针对多处理器系统应用程序的编程进行了优化,使用Go编译的程序可以媲美C或C++代码的速 ...

  6. 机器学习实战(Machine Learning in Action)学习笔记————07.使用Apriori算法进行关联分析

    机器学习实战(Machine Learning in Action)学习笔记————07.使用Apriori算法进行关联分析 关键字:Apriori.关联规则挖掘.频繁项集作者:米仓山下时间:2018 ...

  7. golang学习笔记20 一道考察对并发多协程操作一个共享变量的面试题

    golang学习笔记20 一道考察对并发多协程操作一个共享变量的面试题 下面这个程序运行的能num结果是什么? package main import ( "fmt" " ...

  8. JavaScript学习笔记之数组(二)

    JavaScript学习笔记之数组(二) 1.['1','2','3'].map(parseInt) 输出什么,为什么? ['1','2','3'].map(parseInt)//[1,NaN,NaN ...

  9. golang学习笔记19 用Golang实现以太坊代币转账

    golang学习笔记19 用Golang实现以太坊代币转账 在以太坊区块链中,我们称代币为Token,是以太坊区块链中每个人都可以任意发行的数字资产.并且它必须是遵循erc20标准的,至于erc20标 ...

随机推荐

  1. 软件磁盘阵列 (Software RAID)

    什么是 RAID 磁盘阵列全名是『 Redundant Arrays of Inexpensive Disks, RAID 』,容错式廉价磁盘阵列. RAID 可以通过一些技术(软件或硬件),将多个较 ...

  2. 沉淀,再出发:python中的pandas包

    沉淀,再出发:python中的pandas包 一.前言 python中有很多的包,正是因为这些包工具才使得python能够如此强大,无论是在数据处理还是在web开发,python都发挥着重要的作用,下 ...

  3. docker 17.09.0-ce 启动更换网络地址

    一.环境准备 环境1 台虚拟机,系统为centos7 二.17.09.0-ce 安装 卸载安装的所有Docker组件 在 Docker17.03.0-ce 版本中,与在 Docker 1.12 中引入 ...

  4. HDFS Namenode&Datanode

    HDFS Namenode&Datanode HDFS 机制粗略示意图 客户端写入文件流程: NN && DN Namenode(NN)工作机制 NN是整个文件系统的管理节点. ...

  5. php截取后台登陆密码的代码

    php截取后台登陆密码的代码,很多时候黑客留了这样的代码,大家一定要注意下if($_POST[loginsubmit]!=){ //判断是否点了登陆按钮 $sb=user:.$_POST[userna ...

  6. 使用python 操作liunx的svn,方案一

    在服务器中要做几个操作,使用命令操作svn,svn文件的创建,svn文件更新,并把指定demo路径,移动到创建的文件夹中,进行提交, # -*- coding:utf-8 -*- import pys ...

  7. Centos7 之目录处理命令(八)

    linux中 关于目录 有几个重要概念 一个是 / 根目录  还有一个当前用户的家目录 比如 root用户的家目录是 /root  普通用户的家目录是/home/xxx 下 root登录 默认家目录 ...

  8. cogs 2355. [HZOI 2015] 有标号的DAG计数 II

    题目分析 来自2013年王迪的论文<浅谈容斥原理> 设\(f_{n,S}\)表示n个节点,入度为0的点集恰好为S的方案数. 设\(g_{n,S}\)表示n个节点,入度为0的点集至少为S的方 ...

  9. CADisplayLink分析

    1.固定频率定时器: 2.UI帧率性能检测: 3.cpu动画控制器:

  10. java BigDecimal解析及注意事项

    BigDecimal简介 JDK文档(中文)中的解释如下: 不可变的.任意精度的有符号十进制数.BigDecimal 由任意精度的整数非标度值 和 32 位的整数标度 (scale) 组成.如果为零或 ...