Go语言切片一网打尽,别和Java语法傻傻分不清楚
前言
我总想着搞清楚,什么样的技术文章才算是好的文章呢?因为写一篇今后自己还愿意阅读的文章并不容易,暂时只能以此为目标努力。
最近开始用Go刷一些题,遇到了一些切片相关的细节问题,这里做一些总结。切片的设计想法是由动态数组概念而来,为了开发者可以更加方便的使一个数据结构可以自动增加和减少。但是切片本身并不是动态数据或者数组指针。
切片的结构
type slice struct {
array unsafe.Pointer // 一个指向底层数组的指针(切片中元素是存在这个指向的数组中)
len int // 切片的长度:包含的元素个数
cap int // 切片的容量,len <= cap,如果len == cap,则添加元素会触发切片的扩容
}
长度为3,容量为5的int切片的图示如下,此时切片数组中可访问的部分只有下标0,1,2,超过部分不能访问。
声明和初始化
nil切片
声明nil切片,声明之后就会初始化(默认会用nil初始化),此时slice == nil成立,用于描述一个不存在的切片。
var slice []int // 此时 slice == nil 成立
空切片
声明并初始化空切片,表示一个空的集合,空切片指向地址不是nil。
slice := make([]int, 0) // 此时 slice == nil 不成立
slice := []int{}
无论是nil切片还是空切片在调用内置函数append、len和cap的效果都是一样的。
含有元素的切片
此时切片非空。
slice := []int{1, 2, 3, 4, 5} // 声明并初始化一个切片,len和cap都是5
slice := make([]int, 4) // 声明并初始化一个切片,第二个参数表示切片的长度len和容量cap都为4
slice := make([]int, 4, 6) // 声明并初始化一个切片,第二个参数表示len,第三个表示cap
-----------------------------------------------------------
array := [4]int{1, 2, 3, 4}
slice := array[1:2] // 对于array[i:j]来说,新切片的len=j-i,且cap=k-i(这里k是原数组的大小)
测试上面第四种初始化切片的方法:
func main() {
array := [...]int{1, 2, 3, 4}
slice := array[1:2]
fmt.Printf("%p %d %d %v\n", &slice, len(slice), cap(slice), slice)
}
/*
输出:
0xc00000c030 1 3 [2]
*/
拷贝
使用 := 拷贝
注意:下面代码中newSlice切片是通过slice切片声明并初始化的,虽然两个切片打印的地址不同,但是切片的地址指针指向的数组是同一个。修改slice[0] = 100之后,newSlice[0]也变成100。这种规则适用于将切片作为参数传递给函数,在函数的内部使用的是传入切片的值拷贝(创建一块新的内存存放切片,但切片的地址指针指向的是同一个数组)
func main() {
array := [...]int{1, 2, 3, 4}
slice := array[1:2]
newSlice := slice // 拷贝,newSlice由一块新的内存存放slice切片信息
/*
上面这种初始化newSlice的写法等价于:
var newSlice []int
newSlice = slice
*/
fmt.Printf("%p %d %d %v\n", &slice, len(slice), cap(slice), slice)
fmt.Printf("%p %d %d %v\n", &newSlice, len(newSlice), cap(newSlice), newSlice)
slice[0] = 100
fmt.Printf("%p %d %d %v\n", &slice, len(slice), cap(slice), slice)
fmt.Printf("%p %d %d %v\n", &newSlice, len(newSlice), cap(newSlice), newSlice)
}
/*
输出:
0xc00000c030 1 3 [2]
0xc00000c048 1 3 [2]
0xc00000c030 1 3 [100]
0xc00000c048 1 3 [100]
*/
使用copy函数拷贝
copy函数的两个参数是两个切片(将第二个切片的值覆盖到第一个切片),二者地址指针指向两个不同的数组。
func main() {
slice1 := []int{1, 2, 3, 4, 5}
slice2 := []int{5, 4, 3}
fmt.Printf("%d %d %p %v\n", len(slice1), cap(slice1), &slice1, slice1)
fmt.Printf("%d %d %p %v\n", len(slice2), cap(slice2), &slice2, slice2)
//copy(slice2, slice1) // 只会复制slice1的前3个元素到slice2中
copy(slice1, slice2) // 只会复制slice2的3个元素到slice1的前3个位置
fmt.Printf("%d %d %p %v\n", len(slice1), cap(slice1), &slice1, slice1)
//fmt.Printf("%d %d %p %v\n", len(slice2), cap(slice2), &slice2, slice2)
slice2[0] = 200
slice1[0] = 100
fmt.Printf("%d %d %p %v\n", len(slice1), cap(slice1), &slice1, slice1)
fmt.Printf("%d %d %p %v\n", len(slice2), cap(slice2), &slice2, slice2)
}
/*
输出:
5 5 0xc00000c018 [1 2 3 4 5]
3 3 0xc00000c030 [5 4 3]
5 5 0xc00000c018 [5 4 3 4 5]
5 5 0xc00000c018 [100 4 3 4 5] copy函数的两个切片的地址指针分别指向两个不同的数组(修改值互不影响)
3 3 0xc00000c030 [200 4 3]
*/
扩容
扩容使用append方法。
len == cap 时添加元素
func main() {
slice := []int{1, 2, 3} // 此时slice的len == cap == 3,append元素会触发扩容,扩容后切片地址指向新数组(具体扩容策略这里暂时不多深入)
fmt.Printf("%p %d %d %v\n", &slice, len(slice), cap(slice), slice)
newSlice := append(slice, 1)
fmt.Printf("%p %d %d %v\n", &newSlice, len(newSlice), cap(newSlice), newSlice)
slice[0] = 100
fmt.Printf("%p %d %d %v\n", &slice, len(slice), cap(slice), slice)
fmt.Printf("%p %d %d %v\n", &newSlice, len(newSlice), cap(newSlice), newSlice)
}
/*
输出:
0xc00000c030 3 3 [1 2 3]
0xc00000c060 4 6 [1 2 3 1] // append一个元素之后,切片中指针指向一个扩容后新的数组(地址指针变化,这里打印出的是切片地址,无论是否扩容newSlice和slice内存地址必然是不同的,和二者拥有的地址指针不要搞混)
0xc00000c030 3 3 [100 2 3] // 修改slice[0]的元素为100
0xc00000c060 4 6 [1 2 3 1] // 但是newSlice[0]中的元素没有变化(这里才可以证明两个切片的地址指针指向两个不同的数组)
*/
len < cap 时添加元素
此时调用append方法添加一个元素1并不会创建新的数组,但是1会去覆盖掉array[2]。
func main() {
array := [4]int{1, 2, 3, 4}
slice := array[0:2]
fmt.Printf("%p %d %d %v\n", &slice, len(slice), cap(slice), slice)
newSlice := append(slice, 1)
fmt.Printf("%p %d %d %v\n", &newSlice, len(newSlice), cap(newSlice), newSlice)
slice[0] = 100
fmt.Printf("%p %d %d %v\n", &slice, len(slice), cap(slice), slice)
fmt.Printf("%p %d %d %v\n", &newSlice, len(newSlice), cap(newSlice), newSlice)
fmt.Println("array = ", array)
}
/*
输出:
0xc00000c030 2 4 [1 2] // 这里看到从数组中初始化的slice的len==2且cap==4
0xc00000c060 3 4 [1 2 1] // append之后,添加一个元素,len变为3(且append元素会覆盖底层数组,这里1覆盖了之前的3)
0xc00000c030 2 4 [100 2] // 注意,这里slice打印出来的len还是2,slice的len和cap与newSlice的len和cap是隔离的,尽管它们地址指针指向的是同一个数组,这里修改了slice[0]=100
0xc00000c060 3 4 [100 2 1] // newSlice[0]也被修改为100
array = [100 2 1 4] // 证明指向的array数组也被修改了
*/
结束
快过年了,祝大家新年快乐,春招offer拿不停。
建了一个春秋招备战/内推/闲聊群,欢迎大家加入。
关注公众号【程序员白泽】,带你走近一个有点话痨的程序员/学生党。
Go语言切片一网打尽,别和Java语法傻傻分不清楚的更多相关文章
- IT兄弟连 Java语法教程 Java语言的其他特性
Java语言中除了非常重要的跨平台特性外,还有如下几个关键特性: ● 语法简单易学 Java语言的语法简单明了,容易掌握,而且是纯面向对象(OOP)的语言,Java语言的简单性主要体现在以下几个方面 ...
- Go 语言入门(一)基础语法
写在前面 在学习 Go 语言之前,我自己是有一定的 Java 和 C++ 基础的,这篇文章主要是基于A tour of Go编写的,主要是希望记录一下自己的学习历程,加深自己的理解 Go 语言入门(一 ...
- Java语法糖1:可变长度参数以及foreach循环原理
语法糖 接下来几篇文章要开启一个Java语法糖系列,所以首先讲讲什么是语法糖.语法糖是一种几乎每种语言或多或少都提供过的一些方便程序员开发代码的语法,它只是编译器实现的一些小把戏罢了,编译期间以特定的 ...
- 2016年11月3日JS脚本简介数据类型: 1.整型:int 2.小数类型: float(单精度) double(双精度) decimal () 3.字符类型: chr 4.字符串类型:sting 5.日期时间:datetime 6.布尔型数据:bool 7.对象类型:object 8.二进制:binary 语言类型: 1.强类型语言:c++ c c# java 2.弱类型语
数据类型: 1.整型:int 2.小数类型: float(单精度) double(双精度) decimal () 3.字符类型: chr 4.字符串类型:sting 5.日期时间:datetime 6 ...
- Java语法基础(1)
Java语法基础(1) 1. Java是一门跨平台(也就是跨操作系统)语言,其跨平台的本质是借助java虚拟机 (也就是JVM(java virtual mechinal))进行跨平台使用. ...
- Java语法之反射
一.反射机制 在前面Java语法之注解自定义注解时我们也有提到反射,要获取类方法和字段的注解信息,必须通过Java的反射技术来获取 Annotation对象.那什么是反射呢?JAVA反射机制是在运行状 ...
- Java语法知识总结
一:java概述: 1991 年Sun公司的James Gosling等人开始开发名称为 Oak 的语言,希望用于控制嵌入在有线电视交换盒.PDA等的微处理器: 1994年将Oak语言更名为Java: ...
- Java语法糖设计
语法糖 Java语法糖系列,所以首先讲讲什么是语法糖.语法糖是一种几乎每种语言或多或少都提供过的一些方便程序员开发代码的语法,它只是编译器实现的一些小把戏罢了,编译期间以特定的字节码或者特定的方式对这 ...
- 深入理解java虚拟机(十二) Java 语法糖背后的真相
语法糖(Syntactic Sugar),也叫糖衣语法,是英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语.指的是,在计算机语言中添加某种语法,这些语法糖虽然不会对语言 ...
随机推荐
- 由于ios由UIWebView换成了WKWebview内核后导致webview请求接口文件上传,后台接收不到文件
2020年4月起App Store将不再接受使用UIWebView的新App上架.2020年12月起将不再接受使用UIWebView的App更新. 解决后台文件接收不到的问题 function GLA ...
- [opencv]计算多边形逼近曲线的长度
//利用曲线逼近,计算逼近曲线的长度 //首先创建一个逼近曲线 vector<Point2f> approx; approxPolyDP(contours[i], approx, 2, t ...
- CS5262设计DP转HDMI 4K60HZ +VGA 1080P方案芯片
CS5262是一款带嵌入式MCU的4通道DisplayPort1.4到HDMI2.0/VGA转换器芯片,设计用于将DP1.4信号源连接到HDMI2.0接收器.CS5262集成了DP1.4兼容接收机和H ...
- Dapper in .Net Core
一.前言 关于什么是Dapper,在此不做赘述:本文仅对Dapper在.Net Core中的使用作扼要说明,所陈代码以示例讲解为主,乃抛砖引玉,开发者可根据自身需要进行扩展和调整:其中如有疏漏之处,望 ...
- 使用PyTorch构建神经网络模型进行手写识别
使用PyTorch构建神经网络模型进行手写识别 PyTorch是一种基于Torch库的开源机器学习库,应用于计算机视觉和自然语言处理等应用,本章内容将从安装以及通过Torch构建基础的神经网络,计算梯 ...
- 编写Java程序,读取文本文档的内容,去除文本中包含的“广告”字样,把更改后的内容保存到一个新的文本文档中
查看本章节 查看作业目录 需求说明: 读取文本文档的内容,去除文本中包含的"广告"字样,把更改后的内容保存到一个新的文本文档中 实现思路: 在main() 方法中,使用 new F ...
- MySQL8.0.20安装详解
https://blog.csdn.net/yeb112233/article/details/106042867/ alter user root@localhost identified by ' ...
- windows环境jdk8下载安装与配置环境变量
1)jdk8官网下载地址 Java Downloads | Oracle 下载前需登录Oracle账号,没有的话可以用邮箱注册一个,登录之后即可进行下载. 2)jdk8安装 ①下载完成之后双击运行文件 ...
- 初识python:tkinter 实现 弹球小游戏(面向对象)
使用蹩脚式面相对象,实现弹球小游戏(非面向对象实现,主要介绍tk基础用法). #!/user/bin env python # author:Simple-Sir # time:2020/8/7 10 ...
- centos7 自动交互expect 安装使用
1.安装 https://www.cnblogs.com/rocky-AGE-24/p/7256800.html 安装expect命令 两种方式yum安装 yum -y install expect ...