Go语言之重要数组类型切片(slice)make,append函数

news2024/12/24 8:15:49

切片是一个动态数组,因为数组的长度是固定的,所以操作起来很不方便,比如一个names数组,我想增加一个学生姓名都没有办法,十分不灵活。所以在开发中数组并不常用,切片类型才是大量使用的。

切片基本操作

切片的创建有两种方式:
从数组或者切片上切取获得
直接声明切片 : var name []Type // 不同于数组, []没有数字

切片语法:

arr [start : end] 或者 slice [start : end]  // start: 开始索引  end:结束索引

切片特点:

左闭右开 [ )
取出的元素数量为:结束位置 - 开始位置;
取出元素不包含结束位置对应的索引,切片最后一个元素使用 slice[len(slice)]获取;
当缺省开始位置时,表示从连续区域开头到结束位置;当缺省结束位置时,表示从开始位置到整个连续区域末尾;两者同时缺省时,与切片本身等效;

var arr = [5]int{10, 11, 12, 13, 14}
var s1 = arr[1:4]
fmt.Println(s1, reflect.TypeOf(s1)) // [11 12 13] []int
var s2 = arr[2:5]
fmt.Println(s2, reflect.TypeOf(s2)) // [12 13 14]
var s3 = s2[0:2]                    //  [12 13]

值类型和引用类型

数据类型从存储方式分为两类:值类型和引用类型!

(1) 值类型

基本数据类型(int,float,bool,string)以及数组和struct都属于值类型。

特点:变量直接存储值,内存通常在栈中分配,栈在函数调用完会被释放。值类型变量声明后,不管是否已经赋值,编译器为其分配内存,此时该值存储于栈上。

var a int       //int类型默认值为 0
var b string    //string类型默认值为 nil空
var c bool      //bool类型默认值为false
var d [2]int    //数组默认值为[0 0]

当使用等号=将一个变量的值赋给另一个变量时,如 j = i ,实际上是在内存中将 i 的值进行了拷贝,可以通过 &i 获取变量 i 的内存地址。此时如果修改某个变量的值,不会影响另一个。

// 整型赋值
var a =10
b := a
b = 101
fmt.Printf("a:%v,a的内存地址是%p\n",a,&a)
fmt.Printf("b:%v,b的内存地址是%p\n",b,&b)
//数组赋值
var c =[3]int{1,2,3}
d := c
d[1] = 100
fmt.Printf("c:%v,c的内存地址是%p\n",c,&c)
fmt.Printf("d:%v,d的内存地址是%p\n",d,&d)

(2) 引用类型

指针、slice,map,chan,interface等都是引用类型。
特点:变量通过存储一个地址来存储最终的值。内存通常在堆上分配,通过GC回收。

引用类型必须申请内存才可以使用,new()和make()是给引用类型申请内存空间。

切片原理

切片的构造根本是对一个具体数组通过切片起始指针,切片长度以及最大容量三个参数确定下来的

type Slice struct {
      Data uintptr   // 指针,指向底层数组中切片指定的开始位置
      Len int        // 长度,即切片的长度
      Cap int        // 最大长度(容量),也就是切片开始位置到数组的最后位置的长度 
}

在这里插入图片描述

 var arr = [5]int{10, 11, 12, 13, 14}
    s1 := arr[0:3] // 对数组切片
    s2 := arr[2:5]
    s3 := s2[0:2] // 对切片切片

    fmt.Println(s1) // [10, 11, 12]
    fmt.Println(s2) // [12, 13, 14]
    fmt.Println(s3) // [12, 13]

    // 地址是连续的
    fmt.Printf("%p\n", &arr)
    fmt.Printf("%p\n", &arr[0]) // 相差8个字节
    fmt.Printf("%p\n", &arr[1])
    fmt.Printf("%p\n", &arr[2])
    fmt.Printf("%p\n", &arr[3])
    fmt.Printf("%p\n", &arr[4])

    // 每一个切片都有一块自己的空间地址,分别存储了对于数组的引用地址,长度和容量
    fmt.Printf("%p\n", &s1) // s1自己的地址
    fmt.Printf("%p\n", &s1[0])
    fmt.Println(len(s1), cap(s1))

    fmt.Printf("%p\n", &s2) // s2自己的地址
    fmt.Printf("%p\n", &s2[0])
    fmt.Println(len(s2), cap(s2))

    fmt.Printf("%p\n", &s3) // s3自己的地址
    fmt.Printf("%p\n", &s3[0])
    fmt.Println(len(s3), cap(s3))
var a = [...]int{1, 2, 3, 4, 5, 6}
a1 := a[0:3]
a2 := a[0:5]
a3 := a[1:5]
a4 := a[1:]
a5 := a[:]
a6 := a3[1:2]
fmt.Printf("a1的长度%d,容量%d\n", len(a1), cap(a1))
fmt.Printf("a2的长度%d,容量%d\n", len(a2), cap(a2))
fmt.Printf("a3的长度%d,容量%d\n", len(a3), cap(a3))
fmt.Printf("a4的长度%d,容量%d\n", len(a4), cap(a4))
fmt.Printf("a5的长度%d,容量%d\n", len(a5), cap(a5))
fmt.Printf("a6的长度%d,容量%d\n", len(a6), cap(a6))

除了可以从原有的数组或者切片中生成切片外,也可以声明一个新的切片,每一种类型都可以拥有其切片类型,表示多个相同类型元素的连续集合,因此切片类型也可以被声明,切片类型声明格式如下:

var name []Type  // []Type是切片类型的标识

其中 name 表示切片的变量名,Type 表示切片对应的元素类型。

var names = []string{"张三","李四","王五"}
fmt.Println(names,reflect.TypeOf(names))  // [张三 李四 王五 赵六 孙七] []string

直接声明切片,会针对切片构建底层数组,然后切片形成对数组的引用

练习

s1 := []int{1, 2, 3}
s2 := s1[1:]    
s2[1] = 4       
fmt.Println(s1) 
var a = []int{1, 2, 3}
b := a
a[0] = 100
fmt.Println(b)

make函数

变量的声明我们可以通过var关键字,然后就可以在程序中使用。当我们不指定变量的默认值时,这些变量的默认值是他们的零值,比如int类型的零值是0,string类型的零值是"",引用类型的零值是nil。

对于例子中的两种类型的声明,我们可以直接使用,对其进行赋值输出。但是如果我们换成引用类型呢?

// arr := []int{}
var arr [] int  // 如果是 var arr [2] int
arr[0] = 1
fmt.Println(arr)

从这个提示中可以看出,对于引用类型的变量,我们不光要声明它,还要为它分配内容空间。
对于值类型的声明不需要,是因为已经默认帮我们分配好了。要分配内存,就引出来今天的make函数。make也是用于chan、map以及切片的内存创建,而且它返回的类型就是这三个类型本身。
如果需要动态地创建一个切片,可以使用 make() 内建函数,格式如下:

make([]Type, size, cap)

其中 Type 是指切片的元素类型,size 指的是为这个类型分配多少个元素,cap 为预分配的元素数量,这个值设定后不影响 size,只是能提前分配空间,降低多次分配空间造成的性能问题。 示例如下:

a := make([]int, 2)
b := make([]int, 2, 10)
fmt.Println(a, b)
fmt.Println(len(a), len(b))
fmt.Println(cap(a), cap(b))

使用 make() 函数生成的切片一定发生了内存分配操作,但给定开始与结束位置(包括切片复位)的切片只是将新的切片结构指向已经分配好的内存区域,设定开始与结束位置,不会发生内存分配操作。

a := make([]int, 5)  
b := a[0:3]        
a[0] = 100         
fmt.Println(a)
fmt.Println(b)

append(重点)

上面我们已经讲过,切片作为一个动态数组是可以添加元素的,添加方式为内建方法append。

(1)append的基本用法

 var emps = make([]string, 3, 5)
    emps[0] = "张三"
    emps[1] = "李四"
    emps[2] = "王五"
    fmt.Println(emps)
    emps2 := append(emps, "rain")
    fmt.Println(emps2)
    emps3 := append(emps2, "eric")
    fmt.Println(emps3)
    // 容量不够时发生二倍扩容
    emps4 := append(emps3, "yuan")
    fmt.Println(emps4) // 此时底层数组已经发生变化

在这里插入图片描述

扩容机制

1、每次 append 操作都会检查 slice 是否有足够的容量,如果足够会直接在原始数组上追加元素并返回一个新的 slice,底层数组不变,但是这种情况非常危险,极度容易产生 bug!而若容量不够,会创建一个新的容量足够的底层数组,先将之前数组的元素复制过来,再将新元素追加到后面,然后返回新的 slice,底层数组改变而这里对新数组的进行扩容

2、扩容策略:如果切片的容量小于 1024 个元素,于是扩容的时候就翻倍增加容量。上面那个例子也验证了这一情况,总容量从原来的4个翻倍到现在的8个。一旦元素个数超过 1024 个元素,那么增长因子就变成 1.25 ,即每次增加原来容量的四分之一。

arr := [4]int{10, 20, 30, 40}
s1 := arr[0:2] // [10, 20]
s2 := s1       //  // [10, 20]
s3 := append(append(append(s1, 1), 2), 3)
s1[0] = 1000
fmt.Println(s1)
fmt.Println(s2)
fmt.Println(s3)
fmt.Println(arr)

(2)append的扩展用法

var a []int
a = append(a, 1) // 追加1个元素
fmt.Println(a)
a = append(a, 1, 2, 3) // 追加多个元素, 手写解包方式
fmt.Println(a)
a = append(a, []int{1, 2, 3}...) // 追加一个切片, 切片需要解包
fmt.Println(a)

a = append(a, 1)返回切片又重新赋值a的目的是丢弃老数组

  // 案例1
    a := []int{11, 22, 33}
    fmt.Println(len(a), cap(a))

    c := append(a, 44)
    a[0] = 100
    fmt.Println(a)
    fmt.Println(c)

    // 案例2
    a := make([]int, 3, 10)
    fmt.Println(a)
    b := append(a, 11, 22)
    fmt.Println(a) // 小心a等于多少?
    fmt.Println(b)
    a[0] = 100
    fmt.Println(a)
    fmt.Println(b)

    // 案例3
    l := make([]int, 5, 10)
    v1 := append(l, 1)
    fmt.Println(v1)
    fmt.Printf("%p\n", &v1)
    v2 := append(l, 2)
    fmt.Println(v2)
    fmt.Printf("%p\n", &v2)
    fmt.Println(v1)

切片的插入和删除

开头添加元素

var a = []int{1,2,3}
a = append([]int{0}, a...) // 在开头添加1个元素
a = append([]int{-3,-2,-1}, a...) // 在开头添加1个切片

在切片开头添加元素一般都会导致内存的重新分配,而且会导致已有元素全部被复制 1 次,因此,从切片的开头添加元素的性能要比从尾部追加元素的性能差很多。

任意位置插入元素

var a []int
a = append(a[:i], append([]int{x}, a[i:]...)...) // 在第i个位置插入x
a = append(a[:i], append([]int{1,2,3}, a[i:]...)...) // 在第i个位置插入切片

每个添加操作中的第二个 append 调用都会创建一个临时切片,并将 a[i:] 的内容复制到新创建的切片中,然后将临时创建的切片再追加到 a[:i] 中。
思考这样写可以不:

var a = []int{1,2,3,4}
s1:=a[:2]
s2:=a[2:]
fmt.Println(append(append(s1,100,),s2...))

删除元素

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

// 从切片中删除元素
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:]…)

a:=[...]int{1,2,3}
b:=a[:]
b =append(b[:1],b[2:]...)
fmt.Println(a)
fmt.Println(b)

切片元素排序

a:=[]int{10,2,3,100}
sort.Ints(a)
fmt.Println(a)  // [2 3 10 100]

b:=[]string{"melon","banana","caomei","apple"}
sort.Strings(b)
fmt.Println(b) // [apple banana caomei melon]

c:=[]float64{3.14,5.25,1.12,4,78}
sort.Float64s(c)
fmt.Println(c) // [1.12 3.14 4 5.25 78]

// 注意:如果是一个数组,需要先转成切片再排序  [:]

sort.Sort(sort.Reverse(sort.IntSlice(a)))
sort.Sort(sort.Reverse(sort.Float64Slice(c)))
fmt.Println(a,c)

切片拷贝

var s1 = []int{1, 2, 3, 4, 5}
var s2 = make([]int, len(s1))
copy(s2, s1)
fmt.Println(s2)

s3 := []int{4, 5}
s4 := []int{6, 7, 8, 9}
copy(s4, s3)
fmt.Println(s4) //[4 5 3]

练习

func1_指针变量() {
	//取址
	var x int
	x = 10 //x是整型变量
	fmt.Println(x)
	//===============
	var p *int
	p = &x //取址,p是int类型的指针变量
	fmt.Println(p)
	//取值,v=10
	fmt.Println(*p)
}
func2个_new函数() {
	//new 和 make 是 Go 语言中用于内存分配的原语。简单来说,new 只分配内存,make 用于初始化 slice、map 和 channel。
	//之前我们学习的基本数据类型声明之后是有一个默认零值的,但是指针类型呢?
	var p *int
	//var p *int = new(int) //new函数的作用是开辟了一块儿空间,否则*p = 10就报错
	// fmt.Println(p)  // <nil>
	// fmt.Println(*p) // 报错,并没有开辟空间地址
	*p = 10 // 报错
	fmt.Println(*p)
}
func3_数组() {
	//数组其实是和字符串一样的序列类型,不同于字符串在内存中连续存储字符,数组用[]的语法将同一类型的多个值存储在一块连续内存中。
	//数组是值类型,不存地址都是值类型
	var arr [3]int
	fmt.Println(arr)
	fmt.Println(&arr[0])
	fmt.Printf("%p\n", &arr[0])
	fmt.Println(&arr[1])
	fmt.Println(&arr[2])
}
func4_基于数组的切片() {
	//切片有自己的空间
	//存放了3个值,起始地址,长度,容量,
	//为啥说切片是对数组的引用?
	//切片本身不存数据!!
}
func5个_make函数() {
	//make返回的还是引用类型的本身,而new返回的是指向类型的指针。
	//new()返回的是指针
	//make()返回的是引用类型本身,切片本身
	var s = make([]int, 3, 5)
	fmt.Print(s)
}
func6个_append函数1() {
	var emps = make([]string, 3, 5)
	emps[0] = "张三"
	emps[1] = "李四"
	emps[2] = "王五"
	fmt.Println(emps)
	emps2 := append(emps, "rain")
	fmt.Println(emps2) //["张三","李四","王五","rain"]
	emps3 := append(emps2, "eric")
	fmt.Println(emps3) //["张三","李四","王五","rain","eric"]
	//容量不够时发生二倍扩容
	emps4 := append(emps3, "yuan")
	fmt.Println(emps4) //此时底层数组已经发生变化,[张三 李四 王五 rain eric yuan]
}
func7个_append函数2() {
	var emps = make([]string, 3, 5)
	emps[0] = "张三"
	emps[1] = "李四"
	emps[2] = "王五"
	fmt.Println(emps)

	emps2 := append(emps, "rain")
	fmt.Println(emps2)
	emps3 := append(emps, "eric")

	fmt.Println(emps3) //[张三 李四 王五 eric]
	fmt.Println(emps2) //[张三 李四 王五 eric]
}
func8个_append函数3() {
	var s1 = []int{1, 2, 3}
	s1 = append(s1, 4)
	fmt.Println(s1)
	//fmt.Println(s2)
}

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

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

相关文章

Linux系统文件编程及文件读、写操作

Linux 系统编程Day01 文章目录 Linux 系统编程Day011.文件编程概述1.1 文件的打开及创建1.1.1 参数说明1.1.2 文件写入操作示例1.1.3 文件的权限 2.文件操作2.1 写入文件2.2 文件读取操作2.3 文件的光标移动操作 3.文件操作原理简述3.1文件描述符3.2 Linux文件操作流程 1.文件编…

【数据结构】二叉树的前中后序遍历(C语言)

文章目录 什么是二叉树树相关的概念树的表示形式特殊的二叉树如何创造出一棵二叉树二叉树的遍历先序遍历(前序遍历)中序遍历后序遍历 总结 什么是二叉树 [二叉树] 顾名思义就是有两个分支节点的树&#xff0c;不仅如此&#xff0c;除了叶子外的所有节点都具有两个分支节点&…

单个电源模块给多个负载使用,并且电源后还经过了磁珠-二级电源直流压降仿真

单个电源模块给多个负载使用,并且电源后还经过了磁珠-二级电源直流压降仿真 下面介绍单个电源模块给多个负载使用,并且电源后还经过了磁珠-二级电源直流压降仿真,常见于二级压降仿真,以下图为例

备战秋招 | 笔试强训5

目录 一、选择题 二、编程题 三、选择题题解 四、编程题题解 一、选择题 1、在上下文和头文件均正常情况下&#xff0c;以下程序的输出结果是&#xff08;&#xff09; int x 1; do {printf("%2d\n",x); }while(x--); A. 1 B. 无任何输出 C. 2 D. 陷入死循环 …

三种智能算法优化PID参数软件,MATLABAPP开发

今天的主题是&#xff1a;三种智能算法优化常见传递函数的PID参数&#xff0c;采用MATLAB APP Designer 开发。提供代码源程序&#xff0c;可以自行修改源代码&#xff08;不是封装软件&#xff09; 这个软件基本涵盖了所有的传递函数类型&#xff0c;传递函数的参数简单易改。…

【Java反射机制详解】—— 每天一点小知识

&#x1f4a7; J a v a 反射机制详解 \color{#FF1493}{Java反射机制详解} Java反射机制详解&#x1f4a7; &#x1f337; 仰望天空&#xff0c;妳我亦是行人.✨ &#x1f984; 个人主页——微风撞见云的博客&#x1f390; &#x1f433; 《数据结构与算法》专栏的文章…

pytest 参数化进阶

目录 前言&#xff1a; 语法 参数化误区 实践 简要回顾 前言&#xff1a; pytest是一个功能强大的Python测试框架&#xff0c;它提供了参数化功能&#xff0c;可以帮助简化测试用例的编写和管理。 语法 本文就赶紧聊一聊 pytest 的参数化是怎么玩的。 pytest.mark.par…

week27

这周是磨难的一周不知道NT装了多少次系统&#xff0c;删除了多少数据好消息是把BIOS和ubuntu安装地很熟练了&#xff0c;而且经过爱上了心仪的Ubuntu23.04&#xff0c;就是她了坏消息是一个学期做的笔记全都没了&#xff0c;以后不好回忆了&#xff0c;好消息是不用考试了&…

总结929

今日做了一篇阅读题&#xff0c;差点全军覆没&#xff0c;通过这篇阅读&#xff0c;主要说明了两大问题&#xff0c;一个是单词&#xff0c;背的还不够牢固&#xff0c;其二&#xff0c;语法功底还不够扎实。但说实话&#xff0c;在语法方面&#xff0c;还是下了一番功夫&#…

linux 内网批量快速传输大文件 nc

使用nc工具 传输内网宽带拉满先运行接收端 开始监听使用 ansible 拷贝脚本到其它接收端服务器批量运行接收端脚本查看nc是否运行运行发送端运行发送端脚本开始传输文件 传输内网宽带拉满 先运行接收端 开始监听 接收端脚本 re.sh #!/bin/bash #Revision: 1.0 #Author:…

动态规划(一) —— 从背包系列问题看DP

前言 动态规划可以算是算法初学者的噩梦哈哈&#xff0c;这段时间荔枝在持续学习Java后端的同时也没有忘记刷题嘿嘿嘿&#xff0c;总算把代码随想录上给出的有关动态规划的题目刷完了。接下来的几篇文章荔枝将会对于刷过的动态规划问题做出总结并给出相应的个人体会和理解。在本…

compose之沉浸式(侵入式)状态栏(隐藏状态栏)

沉浸式(侵入式)状态栏 效果图&#xff1a; 1、代码加入&#xff1a;WindowCompat.setDecorFitsSystemWindows(window, false) ComposeTestTheme {WindowCompat.setDecorFitsSystemWindows(window, false)Greeting("Android")} 2、沉浸式(侵入式)主题&#xff1a; …

消息推送(websocket)集群化解决方案

目录 需求分析解决方案实现步骤架构图配置websocket请求地址配置websocket连接前置和连接关闭监听配置websocket处理程序配置redis交换机配置redis订阅监听配置redis发布监听需求分析 及时信息传递:消息推送功能能够确保网站向用户发送及时的重要信息,包括新闻更新、促销活动…

消息队列——rabbitmq的不同工作模式

目录 Work queues 工作队列模式 Pub/Sub 订阅模式 Routing路由模式 Topics通配符模式 工作模式总结 Work queues 工作队列模式 C1和C2属于竞争关系&#xff0c;一个消息只有一个消费者可以取到。 代码部分只需要用两个消费者进程监听同一个队里即可。 两个消费者呈现竞争关…

Redis进阶底层原理-主从复制

Redis的主从节点都会记录对方的信息&#xff0c;核心还包括ReplicationID 和 offset &#xff0c; ReplicationID &#xff1a; 主从节点实例的ID &#xff0c;redis内部就是通过这个id去识别主从节点。offset&#xff1a;数据同步偏移量&#xff0c;也就是从节点每次从主节点同…

3.6 Bootstrap 导航元素

文章目录 Bootstrap 导航元素表格导航或标签胶囊式的导航菜单基本的胶囊式导航菜单垂直的胶囊式导航菜单 两端对齐的导航禁用链接下拉菜单带有下拉菜单的标签带有下拉菜单的胶囊标签页与胶囊式标签页 Bootstrap 导航元素 本文将讲解 Bootstrap 提供的用于定义导航元素的一些选项…

使用thrift编写C++服务器、客户端

在上一节《Linux 下编译 thrift》中&#xff0c;我们成功编译出了thrift的库文件&#xff0c;本节我们来编写thrift的C服务器&#xff0c;客户端。 官网 https://thrift.apache.org/tutorial/cpp.html 有thrift的C例子。在我们之前下载下来的thrift 源码根目录的tutorial/cpp目…

MySQL高级管理

目录 一、指定主键的一种方式 1.1高级操作 1.2数据表高级操作,克隆表 1.2.1 克隆表名 1.2.2备份表内容 1.3复制表 1.4删除指令 方法一&#xff1a; 方法二&#xff1a; 删除速度 二、创建临时表 三、MySQL中6种常见的约束 3.1创建主表 3.2创建从表 3.3为主表test01添加…

[Docker异常篇]解决Linux[文件异常]导致开机Docker服务无法启动

文章目录 一&#xff1a;场景复现二&#xff1a;解决思路2.1&#xff1a; 对比其他节点docker配置2.2&#xff1a;试着修改为正常节点配置2.2&#xff1a;根据上面异常显示&#xff0c;不一定是配置不对&#xff0c;可能是文件系统有损坏 三&#xff1a;解决 -> 执行命令 mo…

【机器学习算法】奇异值分解(SVD)

文章目录 奇异值分解(SVD)1.理论部分1.1特征分解(ED)1.2 奇异值分解(SVD)求解U和V求解Σ 2.应用部分2.1图像压缩2.2图像数据集成分分析2.3 数据降维(PCA的一种解法) Reference 奇异值分解(SVD) 奇异值分解(Singular Value Decomposition) 是矩阵低秩分解的一种方法&#xff0c;…