Golang 【basic_leaming】切片

news2025/3/12 20:22:46

阅读目录

  • 1、为什么要使用切片
  • 2、切片的定义
  • 3、关于nil 的认识
  • 4、切片的循环遍历
  • 5、基于数组定义切片
  • 6、切片再切片
  • 7、关于切片的长度和容量
  • 8、切片的本质
  • 9、使用 make() 函数构造切片
  • 10、切片不能直接比较
  • 11、切片是引用数据类型 -- 注意切片的赋值拷贝
  • 12、append() 方法为切片添加元素
  • 13、使用 copy() 函数复制切片
  • 14、从切片中删除元素
  • 15、练习题

1、为什么要使用切片

因为数组的长度是固定的并且数组长度属于类型的一部分,所以数组有很多的局限性。

例如:

package main

func arraySum(x [4]int) int {
	sum := 0
	for _, v := range x {
		sum = sum + v
	}
	return sum
}
func main() {
	a := [4]int{1, 2, 3, 4}
	println(arraySum(a)) // 10

	b := [5]int{1, 2, 3, 4, 5}
	println(arraySum(b)) //错误
}

这个求和函数只能接受 [4]int 类型,其他的都不支持。所以传入长度为 5 的数组的时候就会报错。

2、切片的定义

切片(Slice)是一个拥有相同类型元素的可变长度的序列。它是基于数组类型做的一层封装。它非常灵活,支持自动扩容。

切片是一个引用类型,它的内部结构包含地址长度容量

声明切片类型的基本语法如下:

var name []T

其中:

1、name:表示变量名
2、T: 表示切片中的元素类型

举个例子:

package main

import "fmt"

func main() {
	// 声明切片类型
	var a []string              //声明一个字符串切片
	var b = []int{}             //声明一个整型切片并初始化
	var c = []bool{false, true} //声明一个布尔切片并初始化
	var d = []bool{false, true} //声明一个布尔切片并初始化
	fmt.Println(a)              //[]
	fmt.Println(b)              //[]
	fmt.Println(c)              //[false true]
	fmt.Println(a == nil)       //true
	fmt.Println(b == nil)       //false
	fmt.Println(c == nil)       //false
	// 切片是引用类型,不支持直接比较,只能和nil 比较
	fmt.Println(c == d)         
}

3、关于nil 的认识

当你声明了一个变量, 但却还并没有赋值时, golang 中会自动给你的变量赋值一个默认零值。

这是每种类型对应的零值。

bool -> false
numbers -> 0
string-> ""
pointers -> nil
slices -> nil
maps -> nil
channels -> nil
functions -> nil
interfaces -> nil

4、切片的循环遍历

切片的循环遍历和数组的循环遍历是一样的。

package main

import "fmt"

func main() {
	var a = []string{"北京", "上海", "深圳"}
	// 方法1:for 循环遍历
	for i := 0; i < len(a); i++ {
		fmt.Println(a[i])
	}
	// 方法2:for range 遍历
	for index, value := range a {
		fmt.Println(index, value)
	}
}
PS E:\golang\src> go run .\main.go
北京
上海
深圳
0 北京
1 上海
2 深圳
PS E:\golang\src>

5、基于数组定义切片

由于切片的底层就是一个数组,所以我们可以基于数组定义切片。

package main

import "fmt"

func main() {
	// 基于数组定义切片
	a := [5]int{55, 56, 57, 58, 59}
	//基于数组a 创建切片,包括元素a[1],a[2],a[3]
	b := a[1:4]
	fmt.Println(b)
	//[56 57 58]
	fmt.Printf("type of b:%T\n", b)
	//type of b:[]int
}

还支持如下方式:

c := a[1:] //[56 57 58 59]
d := a[:4] //[55 56 57 58]
e := a[:] //[55 56 57 58 59]

6、切片再切片

除了基于数组得到切片,我们还可以通过切片来得到切片。

package main

import "fmt"

func main() {
	//切片再切片
	a := [...]string{"北京", "上海", "广州", "深圳", "成都", "重庆"}
	fmt.Printf("a:%v type:%T len:%d cap:%d\n", a, a, len(a), cap(a))
	b := a[1:3]
	fmt.Printf("b:%v type:%T len:%d cap:%d\n", b, b, len(b), cap(b))
	c := b[1:5]
	fmt.Printf("c:%v type:%T len:%d cap:%d\n", c, c, len(c), cap(c))
}
输出:
a:[北京上海广州深圳成都重庆] type:[6]string len:6 cap:6
b:[上海广州] type:[]string len:2 cap:5
c:[广州深圳成都重庆] type:[]string len:4 cap:4

注意: 对切片进行再切片时,索引不能超过原数组的长度,否则会出现索引越界的错误。

7、关于切片的长度和容量

切片拥有自己的长度和容量,我们可以通过使用内置的 len() 函数求长度,使用内置的 cap() 函数求切片的容量。

切片的长度就是它所包含的元素个数。

切片的容量是从它的第一个元素开始数,到其底层数组元素末尾的个数。

切片 s 的长度和容量可通过表达式 len(s)cap(s) 来获取。

package main

import "fmt"

func main() {
	s := []int{2, 3, 5, 7, 11, 13}
	fmt.Println(s)
	
	fmt.Printf("长度:%v 容量%v\n", len(s), cap(s))
	
	c := s[:2]
	fmt.Println(c)
	
	fmt.Printf("长度:%v 容量%v\n", len(c), cap(c))
	
	d := s[1:3]
	fmt.Println(d)
	
	fmt.Printf("长度:%v 容量%v", len(d), cap(d))
}

输出:

PS E:\golang\src> go run .\main.go
[2 3 5 7 11 13]
长度:6 容量6
[2 3]
长度:2 容量6
[3 5]
长度:2 容量5
PS E:\golang\src>

1、第一个输出为 [2,3,5,7,11,13],长度为 6,容量为 6。
在这里插入图片描述
2、c :=s[:2] 后输出:[2 3], 左指针 s[0],右指针 s[2] , 所以长度为 2,容量为 6。

在这里插入图片描述
3、d := s[1:3] 后输出:[3 5], 左指针s[1],右指针s[3] , 所以长度为 2,容量为 5。

在这里插入图片描述

8、切片的本质

切片的本质就是对底层数组的封装,它包含了三个信息:

  • 底层数组的指针
  • 切片的长度(len)
  • 切片的容量(cap)

举个例子,现在有一个数组 a := [8]int{0, 1, 2, 3, 4, 5, 6, 7} ,切片 s1 := a[:5],相应示意图如下。

在这里插入图片描述
切片 s2 := a[3:6] ,相应示意图如下:

在这里插入图片描述

9、使用 make() 函数构造切片

我们上面都是基于数组来创建的切片,如果需要动态的创建一个切片,我们就需要使用内置的 make() 函数,格式如下:

make([]T, size, cap)

其中:
1、 T: 切片的元素类型
2、 size: 切片中元素的数量
3、 cap: 切片的容量

举个例子:

package main

import "fmt"

func main() {
	a := make([]int, 2, 10)
	fmt.Println(a)      //[0 0]
	fmt.Println(len(a)) //2
	fmt.Println(cap(a)) //10
}
PS E:\golang\src> go run .\main.go
[0 0]
2
10
PS E:\golang\src>

上面代码中 a 的内部存储空间已经分配了 10 个,但实际上只用了 2 个。

容量并不会影响当前元素的个数,所以 len(a) 返回2,cap(a) 则返回该切片的容量。

10、切片不能直接比较

切片之间是不能比较的,我们不能使用 == 操作符来判断两个切片是否含有全部相等元素。

切片唯一合法的比较操作是和 nil 比较。

一个 nil 值的切片并没有底层数组,一个 nil 值的切片的长度和容量都是 0。

但是我们不能说一个长度和容量都是 0 的切片一定是nil,例如下面的示例:

var s1 []int         
//len(s1)=0;cap(s1)=0;s1==nil

s2 := []int{}        
//len(s2)=0;cap(s2)=0;s2!=nil

s3 := make([]int, 0) 
//len(s3)=0;cap(s3)=0;s3!=nil

所以要判断一个切片是否是空的,要是用 len(s) == 0 来判断,不应该使用 s == nil 来判断。

11、切片是引用数据类型 – 注意切片的赋值拷贝

下面的代码中演示了拷贝前后两个变量共享底层数组,对一个切片的修改会影响另一个切片的内容,这点需要特别注意。

package main

import "fmt"

func main() {
	s1 := make([]int, 3) 
	//[0 0 0]

	s2 := s1             
	//将s1 直接赋值给s2,s1 和s2 共用一个底层数组

	s2[0] = 100
	fmt.Println(s1) 
	//[100 0 0]

	fmt.Println(s2) 
	//[100 0 0]
}

12、append() 方法为切片添加元素

Go 语言的内建函数 append() 可以为切片动态添加元素,每个切片会指向一个底层数组,这个数组的容量够用就添加新增元素。

当底层数组不能容纳新增的元素时,切片就会自动按照一定的策略进行“扩容”,此时该切片指向的底层数组就会更换。

“扩容”操作往往发生在 append() 函数调用时,所以我们通常都需要用原变量接收 append 函数的返回值。

给切片追加元素的错误写法:

s3 := []int{1, 2, 3, 5, 6, 7}
s3[6] = 8
fmt.Println(s3) 
//index out of range [6] with length 6

append() 方法为切片追加元素:

package main

import "fmt"

func main() {
	//append()添加元素和切片扩容
	var numSlice []int

	for i := 0; i < 10; i++ {
		numSlice = append(numSlice, i)
		fmt.Printf("%v len:%d cap:%d ptr:%p\n", numSlice, len(numSlice), cap(numSlice), numSlice)
	}
}
PS E:\golang\src> go run .\main.go
[0] len:1 cap:1 ptr:0xc00000e098
[0 1] len:2 cap:2 ptr:0xc00000e0e0
[0 1 2] len:3 cap:4 ptr:0xc0000141a0
[0 1 2 3] len:4 cap:4 ptr:0xc0000141a0
[0 1 2 3 4] len:5 cap:8 ptr:0xc000012380
[0 1 2 3 4 5] len:6 cap:8 ptr:0xc000012380
[0 1 2 3 4 5 6] len:7 cap:8 ptr:0xc000012380
[0 1 2 3 4 5 6 7] len:8 cap:8 ptr:0xc000012380
[0 1 2 3 4 5 6 7 8] len:9 cap:16 ptr:0xc00010c080
[0 1 2 3 4 5 6 7 8 9] len:10 cap:16 ptr:0xc00010c080
PS E:\golang\src>

从上面的结果可以看出:

1、append() 函数将元素追加到切片的最后并返回该切片。
2、切片 numSlice 的容量按照1,2,4,8,16 这样的规则自动进行扩容,每次扩容后都是扩容前的2 倍。

append() 函数还支持一次性追加多个元素。例如:

package main

import "fmt"

func main() {
	var citySlice []string

	// 追加一个元素
	citySlice = append(citySlice, "北京")

	// 追加多个元素
	citySlice = append(citySlice, "上海", "广州", "深圳")

	// 追加切片
	a := []string{"成都", "重庆"}
	citySlice = append(citySlice, a...)
	
	fmt.Println(citySlice) //[北京上海广州深圳成都重庆]
}

切片的追加切片。

s1 := []int{100, 200, 300}
s2 := []int{400, 500, 600}
s3 := append(s1, s2...)
fmt.Println(s3)

13、使用 copy() 函数复制切片

首先我们来看一个问题:

package main

import "fmt"

func main() {
	a := []int{1, 2, 3, 4, 5}
	b := a
	fmt.Println(a) //[1 2 3 4 5]
	fmt.Println(b) //[1 2 3 4 5]
	b[0] = 1000
	fmt.Println(a) //[1000 2 3 4 5]
	fmt.Println(b) //[1000 2 3 4 5]
}

由于切片是引用类型,所以 a 和 b 其实都指向了同一块内存地址。修改 b 的同时 a 的值也会发生变化。

Go 语言内建的 copy() 函数可以迅速地将一个切片的数据复制到另外一个切片空间中,copy() 函数的使用格式如下:

copy(destSlice, srcSlice []T)

其中:

  • srcSlice: 数据来源切片
  • destSlice: 目标切片

举个例子:

package main

import "fmt"

func main() {
	// copy()复制切片
	a := []int{1, 2, 3, 4, 5}
	c := make([]int, 5, 5)
	copy(c, a)     
	//使用copy()函数将切片a 中的元素复制到切片c

	fmt.Println(a) //[1 2 3 4 5]
	fmt.Println(c) //[1 2 3 4 5]
	
	c[0] = 1000
	fmt.Println(a) //[1 2 3 4 5]
	fmt.Println(c) //[1000 2 3 4 5]
}

14、从切片中删除元素

Go 语言中并没有删除切片元素的专用方法,我们可以使用切片本身的特性来删除元素。

代码如下:

package main

import "fmt"

func main() {
	// 从切片中删除元素
	a := []int{30, 31, 32, 33, 34, 35, 36, 37}
	// 要删除索引为2 的元素
	a = append(a[:2], a[3:]...)
	fmt.Println(a) //[30 31 33 34 35 36 37]
}

总结一下就是:要从切片 a 中删除索引为 index 的元素,操作方法是 a = append(a[:index],a[index+1:]...)

15、练习题

1、请写出下面代码的输出结果。

package main

import "fmt"

func main() {
	var a = make([]string, 5, 10)
	for i := 0; i < 12; i++ {
		a = append(a, fmt.Sprintf("%v", i))
	}
	fmt.Println(a)
}
PS E:\golang\src> go run .\main.go
[     0 1 2 3 4 5 6 7 8 9 10 11]
PS E:\golang\src>

2、请使用内置的 sort 包对数组 var a = [...]int{3, 7, 8, 9, 1} 进行排序()。

package main

import (
	"fmt"
	"sort"
)

func main() {
	var a = [...]int{3, 7, 8, 9, 1}
	sort.Ints(a[:])
	fmt.Printf("%v\n", a)
	// [1 3 7 8 9]
}

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

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

相关文章

Mycat(10):分片详解之固定分片hash算法

1 找到conf/schema.xml并备份 2 固定分片hash算法 本条规则类似于十进制的求模运算&#xff0c;区别在于是二进制的操作,是取id的二进制低10位&#xff0c;即id二进制 。 此算法的优点在于如果按照 10进制取模运算&#xff0c;在连续插入1-10 时候1-10会被分到1-10个分片&…

手机号格式检查系统(Java)

本系统支持的手机号检查如下所示&#xff1a; /** * 中国移动&#xff0c;中国联通&#xff0c;中国电信都为11位的手机号 * 中国"移动"前三位: * 135、136、137、138、139、147、150、151、152、157、 * 158、159、172、178、182、183、184、187、188、195、197、19…

铝网初效过滤器及金属网过滤器的区别

广州特耐苏净化设备有限公司详细介绍&#xff1a;粗效过滤器主要技术参数 什么叫铝网初效过滤器及金属网过滤器?铝网初效过滤器也叫金属网过滤器也可叫GH金属孔网过滤器只是人们的叫法不同。 铝网初效过滤器均具安全&#xff0c;坚固&#xff0c;耐高温,耐酸碱,之特性.一般应…

WebDAV之葫芦儿•派盘+Obsidian笔记

Obsidian 支持WebDAV方式连接葫芦儿派盘。 还在为大量的日记、笔记管理而烦恼?推荐一款可以作为第二大脑、支持双向链接、基于Markdown文件的本地知识管理软件。 Obsidian是一款全设备端的笔记软件,让用户能够非常方便的进行笔记上面的记录,纸张无限边界,想到哪,写到哪,不…

点成分享 | 蛋白质浓度测定之BCA法

蛋白质浓度的测定是常见的生物实验之一。本文介绍的是使用BCA法&#xff08;二辛可酸法或二喹啉甲酸法&#xff09;进行蛋白质浓度的测定。 BCA分子式 1 实验原理 BCA是一种稳定的碱性水溶性复合物。在碱性条件下&#xff0c;蛋白质可以将BCA试剂中的二价铜离子Cu2还原成一价…

C++类和对象(上)

学习“类”不“类”&#xff0c;有“对象”了吗&#xff1f; 目录 面向过程和面向对象 类的引入 类的定义 访问限定符 封装 类的作用域 类的实例化 类对象的存储方式 计算类对象的大小 this指针 this指针的特性 this指针两问 面向过程和面向对象 ●C语言是面向过程的…

【Javassist】快速入门系列03 使用Javassist实现方法异常处理

系列文章目录 01 在方法体的开头或结尾插入代码 02 使用Javassist实现方法执行时间统计 03 使用Javassist实现方法异常处理 文章目录系列文章目录前言引入Javassist jar包使用Javassist实现方法异常处理总结说明前言 上一章我们介绍了使用使用Javassist实现了对方法执行时间的…

07_哈希表

哈希表 1.为什么需要构建哈希表 现在有一组数据&#xff0c;我们想查找一个值&#xff08;x&#xff09;是否在这组数据中&#xff0c;通常来说&#xff0c;我们需要把这组数据遍历一遍&#xff0c;来看看有没有x这个值。 这时&#xff0c;我们发现这样查找数据要花费的时间…

C++ 类和对象

C认为万事万物都皆为对象&#xff0c;对象上有其属性和行为&#xff0c;C面向对象的三大特性为&#xff1a;封装、继承、多态。 一. 封装 封装是C面向对象三大特性之一。 封装的意义&#xff1a; 将属性和行为作为一个整体&#xff0c;表现生活中的事物将属性和行为加以权限控…

【数据库】时间戳并发控制

Timestamp ordering(T/O) 根据事务的时间戳来决定顺序。 如果T1 的时间戳小于T2 的时间戳&#xff0c;那么执行的结果要等价于T1 执行早于T2 的执行。 时间戳的实现策略&#xff1a; 系统时钟 逻辑计数 混合方法 Basic Timestamp Ordering&#xff08;T/O&#xff09;Prtot…

【WPF绑定2】 ComboBox MVVM SelectedValue复杂数据类型绑定

前言 这次绑定是一次非常痛苦的经历&#xff0c;因为SelectedValue总是不能生效&#xff01;我一度怀疑是wpf的Bug。其实还是自己没搞清楚。 在之前的一篇文章中&#xff1a; http://t.csdn.cn/A4W6Ahttp://t.csdn.cn/A4W6A我也写个ComboBox的绑定&#xff0c;但是当时没有指…

【实时数仓】DWM层订单宽表之实现基本的维度查询、加入旁路缓存模式

文章目录一 DWM层-订单宽表1 维表关联代码实现&#xff08;1&#xff09;首先实现基本的维度查询功能a 封装Phoenix查询的工具类PhoenixUtilb 封装查询维度的工具类DimUtil&#xff08;2&#xff09; 优化1&#xff1a;加入旁路缓存模式a 缓存策略的几个注意点b 缓存的选型c 在…

AnimateGAN 迁移部署

文章目录1. 模型概述2. 迁移过程2.1 将ckpt的权重文件转换为pb的权重文件。2.2 将pb的权重文件迁移为 BM1684 bmodel模型2.3 迁移后pipeline搭建2.4 使用streamlit部署3. 效果展示AnimateGAN 是一个基于 GAN 的动漫生成模型&#xff0c;可以将真实的场景照片转换成动漫形式。本…

CASA(Carnegie-Ames-Stanford Approach)模型

植被作为陆地生态系统的重要组成部分对于生态环境功能的维持具有关键作用。植被净初级生产力&#xff08;Net Primary Productivity, NPP&#xff09;是指单位面积上绿色植被在单位时间内由光合作用生产的有机质总量扣除自养呼吸的剩余部分。植被NPP是表征陆地生态系统功能及可…

设计模式之美总结(创建型篇)

title: 设计模式之美总结&#xff08;创建型篇&#xff09; date: 2022-11-03 13:58:36 tags: 设计模式 categories:技术书籍及课程 cover: https://cover.png feature: false 文章目录1. 单例模式&#xff08;Singleton Design Pattern&#xff09;1.1 为什么要使用单例&…

如何在高密度的IB学习中杀出重围?

建议选择IB所需具备的能力/特点 ▣ 敢于挑战自我&#xff0c;愿意通过努力换取个人能力的飞跃 ▣ 如果擅长或喜欢写作&#xff08;中英文&#xff09;&#xff0c;IB对于你来说可能不会那么难。 ▣ 有自主学习、自主研究的能力。有些老师可能教的并不太让人满意&#xff0c;因此…

OpenTelemetry系列 (三)| 神秘的采集器 - Opentelemetry Collector

前言 上个篇章中我们主要介绍了OpenTelemetry的客户端的一些数据生成方式&#xff0c;但是客户端的数据最终还是要发送到服务端来进行统一的采集整合&#xff0c;这样才能看到完整的调用链&#xff0c;metrics等信息。因此在这个篇章中会主要介绍服务端的采集能力。 客户端数…

学Python能做哪些副业?我一般不告诉别人

前两天一个朋友找到我吐槽&#xff0c;说工资一发交完房租水电&#xff0c;啥也不剩&#xff0c;搞不懂朋友圈里那些天天吃喝玩乐的同龄人钱都是哪来的&#xff1f; 确实如此&#xff0c;刚毕业的大学生工资起薪都很低&#xff0c;在高消费、高租金的城市&#xff0c;别说存钱…

日志篇- ES+Logstash+Filebeat+Kibana+Kafka+zk 安装配置与使用详解

1- 学习目标 ELK基本概念&#xff0c;特点安装部署 Kibana ES集群 Logstash Filebeat Kafka集群性能瓶颈以及优化QA汇总 2- 介绍 2.1- 基本概念 Elasticsearch 分布式搜索和分析引擎&#xff0c;具有高可伸缩、高可靠和易管理等特点。基于 Apache Lucene 构建&#xff0c…

xv6---Lab4 traps

参考&#xff1a; Lab: Traps 关于寄存器s0和堆栈https://pdos.csail.mit.edu/6.828/2020/lec/l-riscv-slides.pdf RISC-V assembly Q: 哪些寄存器包含函数的参数?例如&#xff0c;哪个寄存器在main对printf的调用中保存了传参13 ? A: a2保存13(通过gdb调试可看出寄存器a2的…