Go——切片

news2024/11/28 14:53:32

1. 特点

        slice并不是数组或数组指针。它通过内部指针和相关属性引用数组片段,以实现变长方案。

  • 切片:切片是数组的一个引用,因此切片是引用类型。但自身是结构体,值拷贝传递。
  • 切片的长度可以改变,因此,切片是一个可变数组。
  • 切片的遍历方式和数组一样,可以用len()求长度。表示可用元素的数量,读写操作不能超过该限制。
  • cap可以求出slice最大扩张容量,不能超出数组限制。0 <= len(slice) <= len(array)。其中array是slice引用的数组。
  • 切片的定义:var 变量名 []类型,比如:var str []string  var arr []int。
  • 如果slice等于nil,那么len,cap结果都等于0。

2. 切片源码

        切片对应的数据结构定义位于runtime/slice.go文件中,其定义如下:

type slice struct {
    array unsafe.Pointer
    len   int
    cap   int
}
  • array:指针,指向引用数组对应位置。
  • len:可用元素个数。
  • cap:容量,可放元素个数。

        我们在进行切片赋值,传参,截断时,其实是复制一个slice结构体,只不过底层数组是同一个。这就导致了无论是在复制的切片中修改值,还是修改形参切片值,都会修改到原来的切片和引用的数组。

        总的来说:切边底层有一个数组(make创建的切片,底层也会先创建一个数组),切片是数组的引用。

3. 创建切片

4. 切片初始化

package main

import "fmt"

//全局变量
var arr = [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
var slice0 []int = arr[2:8]
var slice1 []int = arr[0:6]            //可以简写为arr[:6]
var slice2 []int = arr[5:10]           //可以简写为arr[5:]
var slice3 []int = arr[0:len(arr)]     //可以简写为arr[:]
var slice4 []int = arr[0 : len(arr)-1] //去掉最后一个元素

func main() {
	fmt.Printf("全局变量arr %v\n", arr)
	fmt.Printf("全局变量slice0 %v\n", slice0)
	fmt.Printf("全局变量slice1 %v\n", slice1)
	fmt.Printf("全局变量slice2 %v\n", slice2)
	fmt.Printf("全局变量slice3 %v\n", slice3)
	fmt.Printf("全局变量slice4 %v\n", slice4)

	fmt.Println("--------------------------")
	arr2 := [...]int{9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
	slice5 := arr2[2:8]
	slice6 := arr2[0:6]         //可以简写为arr[:6]
	slice7 := arr2[5:10]        //可以简写为arr[5:]
	slice8 := arr2[0:len(arr)]  //可以简写为arr[:]
	slice9 := arr2[:len(arr)-1] //去掉最后一个元素
	fmt.Printf("局部变量arr %v\n", arr2)
	fmt.Printf("局部变量slice5 %v\n", slice5)
	fmt.Printf("局部变量slice6 %v\n", slice6)
	fmt.Printf("局部变量slice7 %v\n", slice7)
	fmt.Printf("局部变量slice8 %v\n", slice8)
	fmt.Printf("局部变量slice9 %v\n", slice9)
}

 5. 通过make来创建切片

var slice []type = make([]type, len)
var slice []type = make([]type, len, cap)
slice := make([]type, len)
slice := make([]type, len, cap)

  • 切片的内存布局 

  • 读写操作实际目标是底层数组,只需要注意索引号的差别。
package main

import "fmt"

func main() {
	data := [...]int{0, 1, 2, 3, 4, 5}

	s := data[2:4]
	s[0] += 100
	s[1] += 200

	fmt.Println(s)
	fmt.Println(data)
}

  • 可直接创建slice对象,自动分配底层数组
package main

import "fmt"

func main() {
	var s1 []int = []int{0, 1, 2, 3, 8: 100} //通过初始化表达式构造,可使用索引号
	fmt.Println(s1, len(s1), cap(s1))

	s2 := make([]int, 6, 8) //使用make创建,指定len和cap
	fmt.Println(s2, len(s2), cap(s2))

	s3 := make([]int, 6) //使用make创建,省略cap,相当于cap=len
	fmt.Println(s3, len(s3), cap(s3))
}

  • 使用make动态创建slice,避免了数组必须用常量的麻烦。还可以用指针直接访问底层数组,退化成普通数组操作。 
package main

import "fmt"

func main() {
	s := []int{1, 2, 3, 4}
	p := &s[1] //获得底层数组元素指针
	*p += 100
	fmt.Println(s)
}

  •  至于[][]T,是指元素类型为[]T
package main

import "fmt"

func main() {
	data := [][]int{
		[]int{1, 2, 3},
		[]int{10, 20, 30, 40},
		[]int{100, 200},
	}

	fmt.Println(data)
}

  • 可以直接修改struct array/slice成员 
package main

import "fmt"

func main() {
	data := [5]struct {
		x int
	}{}

	s := data[:]

	s[1].x = 10
	s[2].x = 20

	fmt.Println(data)
	fmt.Printf("%p, %p\n", &data, &data[0])
}

6. 用append内置函数操作切片(切片追加)

        append:向slice尾部添加数据,返回新的slice对象。

package main

import (
	"fmt"
)

func main() {
	var s []int //s是nil,没有分配内存
	fmt.Println(s)
	s = append(s, 1) //append会先为s分配内存,再存放数据
	fmt.Println(s)

	var a []int = []int{1, 2, 3}
	fmt.Printf("slice a: %v\n", a)

	b := []int{4, 5, 6}
	fmt.Printf("slice b: %v\n", b)

	//append向slice尾部添加数据
	c := append(a, b...)
	fmt.Printf("slice c: %v\n", c)

	d := append(a, 10)
	fmt.Printf("slice d: %v\n", d)

	e := append(a, 100, 200, 300)
	fmt.Printf("slice e: %v\n", e)

	//append返回的新的slice对象
	fmt.Printf("a:%p, c:%p, d:%p, e:%p", &a, &c, &d, &e)
}

8. slice扩容

  • 超出元slice.cap限制,就会重新分配底层数组,即便原数组并未填满。  

        从下面现象可以看出: 

  • 当数据没有超出slice的cap时,如果引用存在的数组变量(不是make创建的),底层数组使用的是存在的数组变量。修改切片的值,就会修改数组的值。
  • 当数据超出slice的cap时,会重新分配底层数组,修改切片的值,不会修改存在数组变量的值,而是修改切片底层数组的值。
package main

import "fmt"

func main() {
	var data = [...]int{1, 2, 3, 4, 10: 0}

	s := data[:2:3]

	fmt.Printf("data:%p, data[0]:%p\n", &data, &data[0]) //data:0xc0000103f0, data[0]:0xc0000103f0

	//slice中数据保存的是数组对应下标的地址,使用索引访问,相当于对指针解引用,在取地址,实际取的是数组中的地址
	fmt.Printf("s:%p, s[0]:%p\n", &s, &s[0]) //s:0xc000008048, s[0]:0xc0000103f0

	//未扩容前修改值
	s[0] = 11
	fmt.Println(s, data)

	s = append(s, 100, 200)
	fmt.Println(s, data)
	fmt.Printf("s[0]:%p, data[0]:%p\n", &s[0], &data[0])

	//扩容后修改值
	s[0] = 10
	fmt.Println(s, data)
}

  • slice中cap重新分配规律 

        从下面输出可以看出,slice扩容通常是以两倍原来容量重新分配底层数组。

        建议一次性分配足够大的空间,以减少内存分配和数据复制的开销,或者初始化足够长的len属性,改用索引号进行操作。

        及时释放不再使用的slice对象,避免持有过期数组,造成GC无法回收。

package main

import "fmt"

func main() {
	s := make([]int, 0, 2)
	c := cap(s)

	for i := 0; i < 50; i++ {
		s = append(s, i)
		if c < cap(s) {
			fmt.Printf("cap: %d->%d\n", c, cap(s))
			c = cap(s)
		}
	}
}

9. 切片拷贝

        函数copy在两个slice间复制数据,复制长度以len小的为准。两个slice可指向同一底层数组,允许元素区间重叠(本来创建切片不同的slice也可以指向同一个底层数组)。

        应及时将所需数据copy到较小的slice,以便释放超大号底层数组内存。

10. slice遍历

        slice遍历实际和数组遍历一样。

package main

import "fmt"

func main() {
	data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

	s1 := data[:]
	for index, value := range s1 {
		fmt.Printf("s1[%d] = %d\n", index, value)
	}

	fmt.Println("-----------------------------")
	for i := 0; i < len(s1); i++ {
		fmt.Printf("s1[%d] = %d\n", i, s1[i])
	}
}

11. 切片调整大小

        由于底层数组相同,且数组空间连续。所以可以像下面赋值。

12. 数组和切片的内存分布

        底层数组不一定要声明出来,比如:make的切片,或者 s := []int{1, 2, 3}

13.  字符串和切片 

 string底层就是一个byte的数组,因此,也可以进行切片操作。

package main

import "fmt"

func main() {
	str := "hello world"

	s := str[:5]
	fmt.Println(s)

	s1 := str[6:]
	fmt.Println(s1)
}

  • 修改字符串 

string本身是不可修改的,要修改string,需要先转成[]byte或[]rune,进行修改,再转成string。

英文字符串

package main

import "fmt"

func main() {
	str := "hello world"

	//字符串不能修改
	// s1 := str[:8]
	// s1[6] = 'G'
	// str[6] = 'G'

	//转成[]byte
	bt := []byte(str) //中文字符需要用[]rune(str)
	//修改[]byte
	bt[6] = 'G'

	bt = bt[:8]
	bt = append(bt, '!')
	str = string(bt)
	fmt.Println(str)
}

含中文字符串

package main

import "fmt"

func main() {
	str := "你好 世界 hello world"

	//字符串不能修改
	// s1 := str[:8]
	// s1[6] = 'G'
	// str[6] = 'G'

	rn := []rune(str)
	//修改[]byte
	rn[3] = '够'
	rn[4] = '浪'

	rn = rn[:12]
	rn = append(rn, '好')
	//再转回字符串
	str = string(rn)
	fmt.Println(str)
}

  • 数组或切片转字符串 
strings.Replace(strings.Trim(fmt.Sprint(array_or_slice), "[]"), "", ",", -1)

 golang slice data[:6:8]两个冒号的理解:

        对于data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

        常规slice,data[6:8],从data数组索引6到索引7,长度len为2,最大可扩充长度cap为4(slice底层数组为data,指针指向索引6位置,cap从索引6位置到data最后,即6-9,所以cap为4)。

        另一种写法:data[:6:8]每一个数字前面都有冒号,slice内容为data索引从0到6,长度len为6,最大扩充项cap设置为8。

        a[x:y:z]切片内容[x:y](前闭后闭),切片长度:y-x,切片容量:z-x

package main

import "fmt"

func main() {
	data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
	s1 := data[6:8]
	fmt.Println(len(s1), cap(s1))

	//s2 := data[6: :8] 中间必须要有数字
	s2 := data[:6:8]
	fmt.Println(len(s2), cap(s2))
	fmt.Println(s2)
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1530169.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

arm 解决Rk1126 画框颜色变色问题(RGB转NV12)

在Rv1126上直接对Nv12图像进行绘制时&#xff0c;颜色是灰色。故将Nv12转BGR后绘制图像&#xff0c;绘制完成后转成Nv12&#xff0c;BGR的图像颜色是正常的&#xff0c;但是NV12的图像颜色未画全&#xff0c;如图&#xff1a; 1.排查发现是RGB转NV12的函数出现问题&#xff0c…

Springboot+vue的仓库管理系统(有报告)。Javaee项目,springboot vue前后端分离项目。

演示视频&#xff1a; Springbootvue的仓库管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot vue前后端分离项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层…

深入理解栈和队列(一):栈

个人主页&#xff1a;17_Kevin-CSDN博客 专栏&#xff1a;《数据结构》 一、栈的概念 栈&#xff08;Stack&#xff09;是一种特殊的线性表&#xff0c;它遵循后进先出&#xff08;Last-In-First-Out&#xff0c;LIFO&#xff09;的原则。栈可以被看作是一个只能在一端进行操作…

记录解决问题--activiti8.2 流程图图片由png改为svg前端不显示图片问题

1.说明 如果是vue svg显示&#xff0c;请查阅其他标准资料&#xff0c;类似使用svg标签。我这里讲的另外一种情况&#xff0c;链接返回的是svg文件&#xff0c;需要用v-html显示图片。 2.activiti6流程图图片格式 ①png格式。可以查看链接返回&#xff0c;以png开头。 ②前端…

如何查看局域网内所有的ip和对应的mac地址

1、windows下查看 方法一、 按快捷键“winr”打开运行界面&#xff0c;输入“CMD”回车: 输入以下命令&#xff1a; for /L %i IN (1,1,254) DO ping -w 1 -n 1 192.168.0.%i 其中 192.168.0.%i 部分要使用要查询的网段&#xff0c;比如 192.168.1.%i 192.168.137.%i 172.16.2…

Nginx日志分割实战指南:让日志管理更轻松

Nginx默认没有提供对日志文件的分割功能&#xff0c;所以随着时间的增长&#xff0c;access.log和error.log文件会越来越大&#xff0c;尤其是access.log&#xff0c;其日志记录量比较大&#xff0c;更容易增长文件大小&#xff0c;影响日志写入性能。 分割Nginx日志的方法有很…

上位机图像处理和嵌入式模块部署(qmacvisual图像预处理)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 不管大家是在读书的时候学习的图像处理&#xff0c;还是在后来的工作中&#xff0c;重新学习了图像处理&#xff0c;相信大家对图像预处理的概念并…

2024年国内彩妆行业市场数据分析:增长机会在哪?

从伊蒂之屋、菲诗小铺等平价韩妆的退出&#xff0c;到全球第一眉妆贝玲妃的落幕&#xff0c;曾经的“网红”-“全网断货选手”纷纷退出中国市场。 有人认为是国内彩妆市场不景气&#xff1f;事实上&#xff0c;国内彩妆市场线上市场规模仍在持续扩大。根据鲸参谋数据统计&…

梅森增益与劳斯稳定判据

梅森增益&#xff1a; 注意前两行的系数&#xff0c;第一行是偶次项&#xff0c;从高幂走向低幂&#xff1b;第二行是奇次项&#xff0c;从高幂走向低幂。 第一中情况&#xff1a; 第二种情况&#xff1a; 讲的真的很好&#xff0c;受益颇多&#xff1a; https://www.bilibili…

整蛊小教程|让朋友手足无措的电脑自动关机

前言 这几天讲到shutdown关机命令&#xff0c;于是就出现了整蛊类的电脑教程。 这个故事我记得很清楚&#xff1a;在2012年的春天……当时的小白对电脑还不是很熟悉。某一天跟着朋友去网吧上网&#xff0c;这时候突然有个朋友发来一个.bat的文件&#xff0c;说双击打开有惊喜…

FX-数组的使用

1一维数组 1.1一维数组的创建和初始化 1.1.1数组的创建 //代码1 int arr1[10]; char arr2[10]; float arr3[1]; double arr4[20]; //代码2 //用宏定义的方式 #define X 3 int arr5[X]; //代码3 //错误使用 int count 10; int arr6[count];//数组时候可以正常创建&#xff1…

供应链安全之被忽略的软件质量管理平台安全

背景 随着我国信息化进程加速&#xff0c;网络安全问题更加凸显。关键信息基础设施和企业单位在满足等保合规的基础上&#xff0c;如何提升网络安全防御能力&#xff0c;降低安全事件发生概率&#xff1f;默安玄甲实验室针对SonarQube供应链安全事件进行分析&#xff0c;强调供…

系统资源耗尽对服务器的影响有什么?

在当今数字化时代&#xff0c;服务器作为核心计算设备&#xff0c;为企业和组织的业务连续性提供了重要保障。然而&#xff0c;随着业务的增长和复杂性的提升&#xff0c;服务器也面临着越来越多的挑战。其中&#xff0c;系统资源耗尽是服务器面临的一个重要问题。今天德迅云安…

中间件-消息队列

消息队列基础知识 什么是消息队列 本处提到的消息队列是指各个服务以及系统组件/模块之间的通信&#xff0c;属于一种中间件。参与消息传递的双方称为生产者和消费者&#xff0c;生产者负责发送消息&#xff0c;消费者负责处理消息。 消息队列作用 通过异步处理&#xff0…

五、C#归并排序算法

简介 归并排序是一种常见的排序算法&#xff0c;它采用分治法的思想&#xff0c;在排序过程中不断将待排序序列分割成更小的子序列&#xff0c;直到每个子序列中只剩下一个元素&#xff0c;然后将这些子序列两两合并排序&#xff0c;最终得到一个有序的序列。 归并排序实现原…

JVM常用垃圾收集器

JVM 4.1 哪些对象可以作为GC ROOT? 虚拟机栈&#xff08;栈帧中的局部变量表&#xff09;中引用的对象本地方法栈中引用的对象方法区静态变量引用的对象方法区常量引用的对象被同步锁持有的对象JNI&#xff08;Java Native Interface&#xff09;引用的对象 4.2 常用垃圾收集…

2.28CACHE,虚拟存储器

主存储器,简称主存。CPU可以直接随机地对其进行访问&#xff0c;也可以和高速缓存器及辅助存储器交换数据。 2> 辅助存储器,简称辅存&#xff0c;不能与CPU直接相连&#xff0c;用来存放当前暂时不用的程序和数据 3> 高速缓冲存储器,位于主存和CPU之间&#xff0c;用来…

Cesium:按行列绘制3DTiles的等分线

作者:CSDN @ _乐多_ 本文将介绍如何使用 Cesium 引擎根据模型的中心坐标,半轴信息,绘制 3DTiles 对象的外包盒等分线。 外包盒是一个定向包围盒(Oriented Bounding Box),它由一个中心点(center)和一个包含半轴(halfAxes)组成。半轴由一个3x3的矩阵表示,这个矩阵是…

基于Spring boot食品安全信息管理系统

摘 要 食品安全信息管理系统设计的目的是为用户提供食品信息、科普专栏、食品检测、检测结果、交流论坛等方面的平台。 与PC端应用程序相比&#xff0c;食品安全信息管理系统的设计主要面向于用户&#xff0c;旨在为管理员和用户提供一个食品安全信息管理系统。用户可以通过AP…

openEuler 22.03(华为欧拉)一键安装 Oracle 19C(19.22) 数据库

前言 Oracle 一键安装脚本&#xff0c;演示 openEuler 22.03 一键安装 Oracle 19C 单机版过程&#xff08;全程无需人工干预&#xff09;&#xff1a;&#xff08;脚本包括 ORALCE PSU/OJVM 等补丁自动安装&#xff09; ⭐️ 脚本下载地址&#xff1a;Shell脚本安装Oracle数据…