Go——切片

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)
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Visual Studio 2013 - 清理

Visual Studio 2013 - 清理 1. 清理1.1. 工程清理1.2. 解决方案清理 References 1. 清理 Debug Release 1.1. 工程清理 (right mouse click on the project) -> 清理 1.2. 解决方案清理 (right mouse click on the solution) -> 清理解决方案 References [1] Yongq…

vue元素声明之后未使用的解决方法

错误情况&#xff1a;类似的这种元素声明未使用的情况 解决方法 方法一 将lintOnSave :false 改为lintOnSave:true 方法二 在项目中找到package.json........① 使用快捷键Ctrl F 搜索eslintconfig........② 找到eslintconfig..........③ 找到rules .........④ 添…

隐语笔记1 —— 数据可信流通,从运维信任到技术信任

数据可信流通体系 关于可信的反思 信任是涉及交易或交换关系的基础 信任的基石&#xff1a; 身份可确认利益可依赖能力有预期行为有后果 数据流通中的不可信风险&#xff1a;可信链条失效&崩塌 法规层面&#xff1a;数据的持有权&#xff0c;加工权&#xff0c;经营权…

Restful API 日期时间格式与 ISO 8601

Restful API 日期时间格式 Restful API 在传递日期类型的参数时&#xff0c;应该使用什么格式&#xff1f; 查询参数、请求体参数、响应中的日期类型的字段&#xff0c;用什么格式合适&#xff1f; 在 RESTful API 中传递日期类型的参数时&#xff0c;通常建议采用一种普遍可解…

485问题汇总

485问题汇总 485 通信波形没有负电压 问题描述&#xff1a;设备在没有外设的时候通信波形是正常的&#xff0c;即5V可以出来&#xff0c;在连接上设备后&#xff0c;设备的通信波形的-5V会随着设备的增多&#xff0c;电压会慢慢上升。当设备连接到24台设备后&#xff0c;485总…

【C#】数组string类型输出

示例 代码 internal class Program{static void Main(string[] args){List<int> list new List<int>() { 1,2,3,4,5,6,7,8,9,10};string output1 string.Join(",", list);Console.WriteLine(output1);string output2 string.Join("\n", lis…

数据结构(五)——树的基本概念

五、树 5.1 树的基本概念 5.1.1 树的定义 树是n(n>0)个结点的有限集合&#xff0c;结点数为0的树称为空树 非空树的特性 有且仅有一个根节点没有后继的结点称为“叶子结点”&#xff08;或终端结点&#xff09;有后继的结点称为“分支结点”&#xff08;或非终端结点&a…

Java项目基于SpringBoot和Vue的时装购物系统的设计与实现

今天要和大家聊的是一款基于SpringBoot和Vue的时装购物系统。 &#x1f495;&#x1f495;作者&#xff1a;李同学 &#x1f495;&#x1f495;个人简介&#xff1a;混迹在java圈十年有余&#xff0c;擅长Java、微信小程序、Python、Android等&#xff0c;大家有这一块的问题可…

OPPO 后端二面,凉凉。。。

美众议院通过 TikTok 法案 之前我们讲了 老美要求字节跳动在 165 天内剥离短视频应用 TikTok&#xff0c;当时的最新进度是 TikTok 给 1.7 亿美国用户发弹窗&#xff0c;发动用户群众给国会打电话进行抗议。 但显然这点力度的抗议并不会造成什么实质影响。 昨晚&#xff0c;美国…

SpringBoot与SpringCloud的版本对应详细版

在实际开发过程中&#xff0c;我们需要详细到一一对应的版本关系&#xff1a;Spring 官方对应版本地址&#xff1a; (https://start.spring.io/actuator/info)&#xff0c;建议用firefox浏览器打开&#xff0c;你会看见格式化好了json信息&#xff1a; 手动记录一些经本人实际…

【译】矢量数据库 101 - 什么是矢量数据库?

原文地址&#xff1a;Vector Database 101 - What is a Vector Database? 1. 简介 大家好——欢迎回到 Milvus 教程。在上一教程中&#xff0c;我们快速浏览了每天产生的日益增长的数据量。然后&#xff0c;我们介绍了如何将这些数据分成结构化/半结构化数据和非结构化数据&…

使用WordPress在US Domain Center上建立招聘网站的详细教程

第一部分&#xff1a;介绍招聘网站 招聘网站是指用于发布招聘信息、吸引求职者、进行简历筛选和管理招聘流程的网站。在WordPress中&#xff0c;您可以轻松地创建一个功能齐全的招聘网站&#xff0c;以便企业能够方便地管理招聘流程&#xff0c;并为求职者提供信息和应聘渠道。…

论文浅尝 | GPT-RE:基于大语言模型针对关系抽取的上下文学习

笔记整理&#xff1a;张廉臣&#xff0c;东南大学硕士&#xff0c;研究方向为自然语言处理、信息抽取 链接&#xff1a;https://arxiv.org/pdf/2305.02105.pdf 1、动机 在很多自然语言处理任务中&#xff0c;上下文学习的性能已经媲美甚至超过了全资源微调的方法。但是&#xf…

力扣Lc18--- 168. Excel表列名称(java版)-2024年3月19日

1.题目描述 2.知识点 注1&#xff1a;StringBuilder 对象的 insert() 方法用于在字符串的指定位置插入字符或字符序列。这里的第一个参数是插入位置的索引&#xff0c;而第二个参数是要插入的字符或字符序列。 public class InsertExample {public static void main(String[…

彻底学会系列:一、机器学习之梯度下降(2)

1 梯度具体是怎么下降的&#xff1f; ∂ J ( θ ) ∂ θ \frac{\partial J (\theta )}{\partial \theta} ∂θ∂J(θ)​&#xff08;损失函数&#xff1a;用来衡量模型预测值与真实值之间差异的函数&#xff09; 对损失函数求导&#xff0c;与学习率相乘&#xff0c;按梯度反方…

搭建基于 Snowflake 的 CI/CD 最佳实践!

Snowflake 提供了可扩展的计算和存储资源&#xff0c;和基于 SQL 的界面 Snowsight&#xff0c;方便用户进行数据操作和分析。然而&#xff0c;如果用户想将自己的 CI/CD 流程与 Snowflake 集成时&#xff0c;会发现一些不便之处&#xff08;尤其相比其 SnowSight 优秀的查询能…

三段提交的理解

三阶段提交是在二阶段提交上的改进版本&#xff0c;3PC 最关键要解决的就是协调者和参与者同时挂掉的问题&#xff0c;所以3PC把2PC的准备阶段再次一分为二&#xff0c;这样三阶段提交。 处理流程如下 &#xff1a; 阶段一 协调者向所有参与者发出包含事务内容的 canCommit …

无人机助力违法毒品种植智能监测预警,基于轻量级YOLOv5n开发构建无人机航拍场景下的农村田园场景下非法种植罂粟花检测预警识别系统

打击毒品人人有责&#xff0c;毒品带来的危害是人尽皆知的&#xff0c;我们不仅自身要严厉拒绝接触任何形式的毒品&#xff0c;更要言传身教告诫他人不要与任何形式的任何渠道的毒品有关联&#xff0c;但是在实际生活中&#xff0c;在一些偏远的乡村、田园、山丘、村落等地方&a…

Markdown 最全语法指南 —— 看这一篇就够了

目录 一. 前言 二. Markdown 标题语法 三. Markdown 段落语法 四. Markdown 换行语法 五. Markdown 强调语法 六. Markdown 引用语法 七. Markdown 列表语法 八. Markdown 代码语法 九. Markdown 分隔线语法 十. Markdown 链接语法 十一. Markdown 图片语法 十二. Markdown 转义…

【技术栈】Redis 企业级解决方案

​ SueWakeup 个人主页&#xff1a;SueWakeup ​​​​​​​ 系列专栏&#xff1a;学习技术栈 ​​​​​​​ ​​​​​​​ ​​​​​​​ ​​​​​​​ ​​​​​​​ ​​​​​​​ ​​​​​​​ 个性签名&…