Go语言基础(复合数据类型(上))
- 一、数组
- 二、指针
- 三、切片
\quad 基本数据类型是
Go
语言世界中的原子,以
不同的方式组合基本数据类型得到的就是
复合数据类型。复合数据类型是通过组合基本数据类型,来表达更加复杂的数据结构,即
使用其他类型定义的类型,因而复合类型又称之为派生类型。
\quad
Go
语言复合数据类型包括:指针(pointer
)、数组(array
)、切片(slice
)、字典(map
)、通道(chan
)、结构体(struct
)、接口(interface
)。
\quad
讲到这里我们需要再引入一个新的概念,即值类型与引用类型。Go
语言中所有的数据都可以被分成值类型与引用类型,值类型的特点是变量直接存储值,内存通常在栈中分配;引用类型的特点是变量存储的是一个地址,这个地址对应的空间里才是真正存储的值,内存通常在堆中分配。堆栈先简单理解成内存中两块不同的区域即可。
\quad
Go
语言数值型复合数据类型包括:数组(array
)、结构体(struct
);引用型复合数据类型包括:指针(pointer
)、切片(slice
)、字典(map
)、通道(chan
)。基础数据类型以及数值型复合类型都属于值类型;引用型复合类型、函数、接口类型都属于引用类型。
\quad
值类型是不可改变的,引用类型和值类型恰恰相反,它的修改可以影响到任何引用到它的变量。定义值类型和引用类型变量时,值类型会默认分配内存,而引用类型则需要使用new
或者make
手动分配内存。
\quad
Go
语言默认使用按值传递来为函数传递参数,也就是将实参的值拷贝一份给形参,只不过这个实参的值有可能是地址(引用类型),有可能是数据(值类型)。传递一个数据值,就会得到数据的副本;传递一个地址值,就会得到地址的副本,但不会得到它所指向的数据。
\quad 值类型作为参数时,形参改变,实参不变,因为传递的是值的副本,形参会新开辟一块空间,与实参指向不同;引用类型作为参数时,形参改变,实参数跟随变化,因为传递的是地址,形参和实参都指向同一块地址。注意:形参和实参保存传递的地址值的内存地址是不同的,如果重新为形参赋值了新的引用,那么对形参所作的改变都不会影响到实参。
\quad 总结:值传递和引用传递的区别并不是传递的内容,而是实参到底有没有被复制一份给形参,如果实参直接被传递给了形参则为引用传递。
一、数组
1.1 数组导学
-
Go
语言数组是一个由固定长度的唯一类型元素组成的序列,一个数组可以由零个或多个元素组成。因为数组的长度是固定的,所以在Golang
中很少直接使用数组。一般都是使用切片来代替数组。 -
Go
语言数组元素可以是任意的基本数据类型。 -
Go
语言数组长度必须是一个常量表达式,并且必须是一个非负整数。 -
Go
语言数组元素可以通过索引(位置)来读取(或者修改),索引从0
开始,第一个元素索引为0
,第二个索引为1
,以此类推。格式:array_name[index]
,即数组名array_name
后加中括号[]
,中括号内为索引的值index
。
1.2 数组基本操作
1.2.1 数组声明
-
Go
语言数组声明需要指定元素类型及元素个数,语法格式如下:var array_name [len]array_type
-
var
:定义数组变量使用的关键字。 -
array_name
:数组变量名。 -
len
:数组元素的个数。 -
array_type
:数组每个元素的类型。
-
- 示例:
var balance [5]float32
1.2.2 数组初始化
(1)全部元素初始化
- 通过字面量在声明数组的同时快速初始化数组:
- 格式
1
:使用var
关键字声明数组并固定数组长度len
,初始化数组{}
中元素的个数不能大于len
。var array_name [len]array_type = [len]array_type{element1, element2, element3, element4, element5}
- 示例:
var balance [5]float32 = [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
− − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − \pmb{--------------------------------} −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− - 格式
2
:使用var
关键字声明数组并固定数组长度len
,根据初始化元素类型确定数组类型,初始化数组{}
中元素的个数不能大于len
。var array_name = [len]array_type{element1, element2, element3, element4, element5}
- 示例:
var balance = [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
− − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − \pmb{--------------------------------} −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− - 格式
3
:固定数组长度len
,然后自动推导数组,初始化数组{}
中元素的个数不能大于len
。array_name := [len]array_type{element1, element2, element3, element4, element5}
- 示例:
balance := [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
− − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − \pmb{--------------------------------} −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− - 格式
4
:如果数组长度不确定,可以使用...
代替数组的长度,编译器会根据元素个数自行推断数组的长度。
示例:var array_name = [len]array_type{element1, element2, element3, element4, element5} 或 array_name := [len]array_type{element1, element2, element3, element4, element5}
var balance = [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0} 或 balance := [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
- 格式
(2)部分元素初始化
-
初始化数组从
0
开始的部分元素。- 格式
1
:使用var
关键字声明数组并固定数组长度len
,初始化元素个数小于数组长度len
。var array_name [len]array_type = [len]array_type{element1, element2}
- 示例:
var balance [5]float32 = [5]float32{1000.0, 2.0}
− − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − \pmb{--------------------------------} −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− - 格式
2
:使用var
关键字声明数组并固定数组长度len
,根据初始化元素类型确定数组类型,初始化元素个数小于数组长度len
。var array_name = [len]array_type{element1, element2, element3}
- 示例:
var balance = [5]float32{1000.0, 2.0, 3.4}
− − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − \pmb{--------------------------------} −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− - 格式
3
:固定数组长度len
,然后自动推导数组,初始化元素个数小于数组长度len
。array_name := [len]array_type{element1, element2, element3}
- 示例:
balance := [5]float32{1000.0, 2.0, 3.4}
− − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − − \pmb{--------------------------------} −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
- 格式
-
指定索引初始化数组中的部分元素。
- 如果设置了数组长度
len
,我们还可以通过指定下标来初始化元素,格式如下:
这里我们给索引var array_name = [...]Type{index0:element1, index1:element2, index2:element3}
index0
的位置设置值为element1
,index1
的位置设置值为element2
,index2
的位置设置值为element3
。 - 示例:
balance := [5]float32{1:2.0, 3:7.0}
- 如果设置了数组长度
1.2.3 数组赋值
-
Go
语言数组的赋值,即给定义好的数组指定的索引的位置设置对应的值。 - 语法:
array_name[index] = value
-
array_name
:需要赋值的数组。 -
index
:需要赋值的索引。 -
value
:需要设置的值。
-
- 示例:
balance[0] = 1000.0
1.2.4 数组遍历
\quad
Go
语言的数组遍历有两种方式,分别为:通过for...len()
完成遍历 与 通过for...range
完成遍历 的方式。
(1)通过 for…len() 完成遍历
- 我们通过
len
函数,获取数组元素的个数,然后通过for
循环加索引的形式获取每一个数组元素的值。格式如下:for i := 0; i < len(array_name); i++ { //代码块 }
(2)通过 for…range 完成遍历
- 通过
for...range
的形式来遍历数组元素,index
即是数组的索引,value
是数组的索引index
处对应的数组的值。如果我们不需要索引或者值,可以通过_
的形式忽略。格式如下:for index, value := range array_name{ //代码块 }
1.2.5 数组比较
-
Go
语言数组的比较,是使用==
的方式。 - 如果数组的元素个数不相同,那么不能比较数组,报错:
Invalid operation: array1 == array2 (mismatched types [6]string and [8]string)
。 - 如果数组的元素个数相同,元素类型不相同,那么不能比较数组,报错:
Invalid operation: array1 == array2 (mismatched types [3]string and [3]int)
。
1.2.6 向函数传递数组
- 如果你想向函数传递数组参数,你需要在函数定义时,声明形参为数组,并要在形参中设定数组长度
len
。func funcName([formal_parameter_list]) ([return_types]) { // 函数体... // return valuelist }
- 形参列表
[formal_parameter_list]
中设定数组形参array_name [len]array_type
并设定长度len
。 - 注意:函数中修改数组的值不会影响到原数组。
- 形参列表
- 示例:数组传递,一定要注意数组与切片不要混淆,如果是想向函数传递数组,则一定要在形参中明确数组长度
len
。func main() { var array = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} fmt.Println(modify(array)) fmt.Print(array) } func modify(arr [10]int) [10]int { arr[7] = 77 return arr }
1.3 多维数组
\quad
Go
语言数组是支持多维的,如果是二维数组,那么数组的每一个元素都是一个一维数组;如果数组是三维数组,那么每一个元素都是一个二维数组。一般我们使用最多的就是二维数组,很少用到三维数组,更多维的数组几乎更用不到。
1.3.1 多维数组定义
\quad
多维数组声明方式:var mulArray_name [size1][size2]...[sizeN]mulArray_type
。
(1)二维数组定义
- 定义一个二维数组
dimension2Array
,该数组拥有row
行col
列,每个元素的类型都是ArrayType
。格式:var Dimension2Array [row][col]ArrayType
。 - 示例:创建一个
3
行2
列的二维数组。var dimension2Array [3][2]string
(2)三维数组定义
- 定义一个三维数组
dimension3Array
,该数组含有count
个二维数组,二维数组拥有row
行col
列,每个元素的类型都是ArrayType
。格式:var Dimension3Array[count][row][col]ArrayType
。 - 示例:创建一个三维数组,包含
2
个1
行2
列的二维数组。var dimension3Array [2][1][2]string
1.3.2 多维数组初始化
- 多维数组的初始化与一维数组一样都可以对元素进行全部初始化或部分初始化,示例如下:
var dimension2Array = [3][2]string{{"Server", "Python"}, {"Server", "Golang"}, {"JavaScript", "Vue"}} var dimension3Array = [2][1][2]string{{{"JavaScript", "Vue"}}, {{"Python", "Golang"}}}
-
注意事项
以上代码中倒数第二行的a := [3][4]int{{0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11}, }
}
必须要有逗号,
,因为最后一行的}
不能单独一行,也可以写成这样:a := [3][4]int{{0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11}}
1.3.3 多维数组赋值
-
Go
语言多维数组的赋值类似与一维数组,即给定义好的数组指定的索引的位置设置对应的值。格式:var mulArray_name [index1][index2]...[indexN]mulArray_type = value
1.3.4 多维数组遍历
\quad
Go
语言多维数组的遍历同样有两种方式,分别为:通过for...len()
完成遍历 与 通过for...range
完成遍历 的方式。
(1)通过 for…len() 完成遍历
- 我们通过
len
函数,获取数组元素的个数,然后通过for
循环加索引的形式获取每一个数组元素的值。格式如下:for i := 0; i < len(mulArray_name); i++ { for j := 0; i < len(mulArray_name[i]); j++ { for k := 0; k < len(mulArray_name[i][j]); k++ { ...... for z := 0; z < len(mulArray_name[i][j][k]...); k++ { // 代码块 } } } }
- 示例:三维数组遍历
func main() { var dimension3Array = [5][3][2]int{{{11, 12}, {13, 14}, {15, 16}}, {{21, 22}, {23, 24}, {25, 26}}, {{31, 32}, {33, 34}, {35, 36}}, {{41, 42}, {43, 44}, {45, 46}}, {{51, 52}, {53, 54}, {55, 56}}} for i := 0; i < len(dimension3Array); i++ { for j := 0; j < len(dimension3Array[i]); j++ { for k := 0; k < len(dimension3Array[i][j]); k++ { if k != len(dimension3Array[i][j])-1 { fmt.Print(dimension3Array[i][j][k], ",") } else { fmt.Print(dimension3Array[i][j][k]) } } fmt.Print("\t") } fmt.Println() } }
(2)通过 for…range 完成遍历
- 通过
for...range
的形式来遍历数组元素,index
即是数组的索引,value
是数组的索引index
处对应的数组的值。如果我们不需要索引或者值,可以通过_
的形式忽略。格式如下:for index, value := range mulArray_name { for index2, value2 := range mulArray_name[index]{ for index3, value3 := range mulArray_name[index][index2] { ...... for indexN, valueN := range mulArray_name[index][index2][index3]...[indexN] { // 代码块 } } } }
- 示例:三维数组遍历
func main() { var dimension3Array = [5][3][2]int{{{11, 12}, {13, 14}, {15, 16}}, {{21, 22}, {23, 24}, {25, 26}}, {{31, 32}, {33, 34}, {35, 36}}, {{41, 42}, {43, 44}, {45, 46}}, {{51, 52}, {53, 54}, {55, 56}}} for index, _ := range dimension3Array { for index2, _ := range dimension3Array[index] { for index3, _ := range dimension3Array[index][index2] { fmt.Print(dimension3Array[index][index2][index3], ",") } fmt.Print("\t") } fmt.Println() } }
1.3.5 数组比较
- 多维数组的比较与一维数组比较方法相同。
1.3.6 向函数传递多维数组
- 向函数传递多维数组的方法与一维数组相同。
二、指针
2.1 内存地址
- 计算机中存储器的容量是以字节为基本单位的,也就是说一个内存地址代表一个字节(
1byte=8bit
)的存储空间。 - 内存地址只是一个编号,代表一个内存空间。
- 内存地址的字面形式常用整数的十六进制字面量来表示,比如:
0x1234CDEF
。 -
32
位的操作系统最多支持4GB
的内存空间,也就是说CPU
只能寻址 2 32 \pmb{2^{32}} 232232232(4GB
),注意这里的4GB
是以Byte
为单位的,不是bit
。也就是说有4G = 4 * 1024 Mb = 4 * 1024 * 1024 Kb = 4 * 1024 * 1024 * 1024 Byte
,即 2 32 \pmb{2^{32}} 232232232 个8bit
内存地址。其取值范围为0x 0000 0000~0x ffff ffff
( 1 6 8 \pmb{16^8} 168168168个)。 -
64
位操作系统上的理论最大支持内存容量为 2 64 \pmb{2^{64}} 264264264Byte
,即16EB
(EB
:艾字节,1 EB = 1024 PB = 1024 * 1024 TB = 1024 * 1024 * 1024 GB
, 1 E B = 2 30 G B 1 EB = 2^{30} GB 1EB=230GB)。其取值范围为0x 0000 0000 0000 0000
到0x ffff ffff ffff ffff
。
2.2 变量的地址
- 变量的名字对应着一个内存地址所对应的内存空间,变量内容对应着这个内存空间所存储的数据。
- 变量的地址用于寻找存储空间,存储空间存放变量的值,该值的类型由变量的类型来定义。
- 变量的地址是指此变量内容所在内存的起始地址。
2.3 指针介绍
- 一个指针变量可以指向任何一个值的内存地址,它所指向的值的内存地址在
32
和64
位机器上分别占用4
或8
个字节,占用字节的大小与所指向的值的大小无关,而是跟系统的寻址能力有关。 - 当一个指针被定义后没有分配到任何变量时,它的默认值为
nil
,nil
指针也称为空指针,指针变量通常缩写为ptr
。 - 类似于变量和常量,在使用指针前你需要声明指针。指针声明格式如下:
var var_name *var-type
-
var-type
为指针类型,var_name
为指针变量名,*
号用于指定变量是作为一个指针。 - 示例:
var a int= 20 /* 声明实际变量 */ var ptr *int /* 声明指针变量 */
-
- 每个变量在运行时都拥有一个地址,这个地址代表变量在内存中的位置。
Go
语言中使用在变量名前面添加&
操作符(前缀)来获取变量的内存地址(取地址操作),为指针变量赋值。格式如下:var_name := &v // v 的类型为T
-
v
代表被取地址的变量,变量v
的地址使用指针变量var_name
进行接收,ptr
的类型为*T
,称做T
的指针类型,*
代表指针。 - 示例:
ptr = &a
-
-
从指针变量获取指针指向的值。当使用
&
操作符对普通变量进行取地址操作并得到变量的指针后,可以对指针变量使用*
操作符,也就是指针取值。- 取地址操作符
&
和取值操作符*
是一对互补操作符,&
取出实际变量地址,*
取出指针所指向的值。 - 示例:
fmt.Printf("*ptr 变量的值:%d\n", *ptr )
- 取地址操作符
三、切片
3.1 切片简介
- 切片的英文是
slice
,Golang
中的切片是数组的一个引用,它会生成一个指向数组的指针,因此切片是引用类型,在进行传递时,遵守引用的传递机制。 - 切片的使用和数组类似,遍历切片、访问切片的元素和求切片的长度 len 与数组都一样。但切片的长度是可以变化的,不像数组是固定的,因此也可以说切片是一个可以动态变化的数组。
- 切片的长度是它所包含的元素个数,切片的容量是从它的第一个元素到其底层数组元素末尾的个数。切片
slice
的长度和容量可通过表达式len(slice)
和cap(slice)
来获取。
3.2 切片的声明与创建
3.2.1 切片的声明
- 可以声明一个未指定大小的数组来定义切片:
var sliceName []type
-
var
:定义切片变量使用的关键字。 -
sliceName
:切片变量名。 -
type
:数组每个元素的类型。
-
- 使用切片声明创建出来的
slice
其实是一个nil slice
,它的长度和容量都为0
,和nil
比较的结果为true
。- 格式:
var sliceName []type
等价于var sliceName = *new([]type)
- 格式:
- 空切片
empty slice
,它的长度和容量也都为0
,但是所有的空切片的数据指针都指向同一个地址0xc42003bda0
,空切片和nil
比较的结果为false
。- 格式:
var sliceName = []int{}
等价于var sliceName = make([]type, 0)
- 格式:
3.2.2 切片的创建
-
Go
语言创建切片的方式有三种:从数组创建切片、直接创建切片和使用make()
函数创建切片。
(1)从数组创建切片
- 在创建切片时,可以基于一个底层数组,切片可以只使用数组的一部分元素或所有元素,甚至可以创建一个比底层数组还要大的数组切片,因为切片可以动态增长。从数组创建切片的语法如下:
var sliceName = array[start:end] // 创建一个切片sliceName,该切片元素的内容是从数组array的索引start开始到索引end结束。
-
var
:定义切片变量使用的关键字。 -
sliceName
:切片变量名。 -
array
:数组名。 -
start
:数组的开始索引。 -
end
:数组的结束索引,不包含结束索引位置的值。
-
- 示例:基于底层数组创建切片。
func main() { // 先定义并初始化底层数组 var array = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} // 部分引用 // 注意切片类型和底层数组类型保持一致 var slice1 []int = array[:5] var slice2 = array[:5] slice3 := array[:5] // 全部引用 var slice4 = array[:] slice5 := array[0:len(array)] fmt.Print(slice1, "\n", slice2, "\n", slice3, "\n", slice4, "\n", slice5, "\n") }
(2)直接创建切片
- 直接创建切片,即在定义切片的同时初始化全部切片元素,语法如下:
var sliceName []type= []type{value1, value2, ...}
- 直接创建切片,在定义切片的同时初始化部分切片元素,其余元素为默认值,语法如下:
var sliceName []type= []type{value1, value2, ... ,len1:valuei, ... ,len2:valuej, ...}
-
len1
、len2
等是切片某个元素之前所有元素的数量,valuei
、valuej
为这些元素对应的值。
-
- 示例
1
:创建一个切片,该切片的内容就是后面的数组元素。var slice = []int{1,2,3,4,5,6,7,8,9,10}
- 示例
2
:创建一个切片,设置切片中前8
个元素值、第11
个元素和第16
个元素。slice := []int{1, 2, 3, 4, 5, 6, 7, 8, 10: 11, 15: 16} fmt.Println(slice, len(slice), cap(slice))
(3)使用 make() 函数创建切片
- 内建函数
make
用来为slice
、map
或chan
类型进行初始化,返回这三个引用类型本身。 -
make
函数需要传入三个参数:切片类型,长度,容量(容量可选,默认和长度相等) - 在
Go
语言中,并不是必须基于底层数组才能创建切片,内置函数make()
可以用于灵活的创建切片。语法格式如下:var sliceName []type= make([]type, len, [cap])
-
var
:定义切片变量使用的关键字。 -
sliceName
:切片变量名。 -
type
:切片的每一个元素的类型。 -
len
:切片的长度。 -
cap
:可选,切片的容量。
-
3.3 切片的操作
3.3.1 切片元素的访问
\quad
Go
语言访问切片元素可以使用索引的形式或者索引切片的形式。
(1)访问单个切片元素
- 获取切片
sliceName
的索引为index
处的元素。语法格式:sliceName[index]
。
(2)访问索引切片元素
- 获取切片
sliceName
从起始索引start
到结尾索引end
的元素,不包含结尾索引end
处的元素。如果start
省略,那么默认值为0
,如果end
省略,那么默认就是切片的长度。语法格式:sliceName[start:end]
。
3.3.2 切片的遍历
\quad
Go
语言切片的遍历有两种方式:通过for
循环和通过for range
循环的方式,类似于数组的遍历。
(1)for 循环遍历切片
- 通过
len
函数,获取切片元素的个数,然后通过for
循环遍历切片,可以使用索引index
获取每一个切片元素的值。语法格式如下:for index := 0; index < len(sliceName); index++ { // sliceName[index] }
(2)for range 循环遍历切片
- 通过
for range
的形式来遍历切片元素,index
是切片的索引,value
是切片的索引index
处对应的切片的值。如果我们不需要索引或者值,可以通过_
的形式忽略。语法格式如下:for index, value := range sliceName{ }
3.3.3 切片添加元素
\quad
Go
语言的切片添加元素使用append
函数。
(1)append 函数详解
-
append
函数语法:func append(slice []Type, elems ...Type) []Type
。第一个参数slice
是需要被添加元素的切片,第二个参数elems
是一个可变参数,传入任意个数的切片的元素,最后返回追加元素之后的切片。-
slice
:要追加元素的切片。 -
elems
:要追加的元素列表。
-
(2)使用 append 在切片末尾追加一个元素
- 使用
append
函数,给切片添加一个元素。示例如下:
输出:func main() { var slice = []string{"Tom", "Jerry"} fmt.Println("原切片:", slice) slice = append(slice, "Spike") fmt.Println("追加一个元素后的切片:", slice) }
原切片: [Tom Jerry] 追加一个元素后的切片: [Tom Jerry Spike]
(3)使用 append 在切片末尾追加多个元素
- 使用
append
函数,给切片添加多个元素。示例如下:
输出:func main() { var slice = []string{"Tom", "Jerry"} fmt.Println("原切片:", slice) slice = append(slice, "Spike", "Tyke", "Tuffy") fmt.Println("追加多个元素后的切片:", slice) }
原切片: [Tom Jerry] 追加多个元素后的切片: [Tom Jerry Spike Tyke Tuffy]
(4)使用 append 在切片末尾追加一个切片
- 使用
append
函数,给切片添加一个切片。示例如下:
输出:func main() { var slice = []string{"Tom", "Jerry"} fmt.Println("原切片:", slice) var appendSlice = []string{"Tyke", "Tuffy"} slice = append(slice, appendSlice...) fmt.Println("追加一个切片后的切片:", slice) }
原切片: [Tom Jerry] 追加一个切片后的切片: [Tom Jerry Tyke Tuffy]
-
注: 因为
append
函数的第二个参数是一个可变参数,因此,我们需要在追加的切片appendSlice
末尾加上...
。
-
注: 因为
(5)在切片的 index 处添加元素
- 在切片的
index
处添加单个元素element
的语法:sliceAppend = append(sliceAppend[:index], element)
- 在切片的
index
处添加多个元素element1
、element2
、...
的语法:sliceAppend = append(sliceAppend[:index], element1, element2)
- 在切片的
index
处添加切片slice
的语法:sliceAppend = append(sliceAppend[:index], slice...)
-
index
之后只保留单元素、多元素或切片。示例如下:
输出:func main() { var sliceAppend = []string{"Tom", "Jerry", "Spike", "Tyke"} fmt.Println("原切片:", sliceAppend) // 添加单个元素 var sliceAppend1 = append(sliceAppend[:2], "Tuffy") // 添加多个元素 var sliceAppend2 = append(sliceAppend[:2], "Tuffy", "Pakes") // 添加切片 var slice = []string{"Tuffy", "Pakes"} var sliceAppend3 = append(sliceAppend[:2], slice...) fmt.Println("索引 2 的地方添加单个元素后的切片:", sliceAppend1) fmt.Println("索引 2 的地方添加多个元素后的切片:", sliceAppend2) fmt.Println("索引 2 的地方添加切片后的切片:", sliceAppend3) }
原切片: [Tom Jerry Spike Tyke] 索引 2 的地方添加单个元素后的切片: [Tom Jerry Tuffy] 索引 2 的地方添加多个元素后的切片: [Tom Jerry Tuffy Pakes] 索引 2 的地方添加切片后的切片: [Tom Jerry Tuffy Pakes]
(6)在切片的 index 处插入元素
- 在切片的
index
处插入单元素element
的语法:sliceAppend = append(sliceAppend [:index], append([]string{element}, sliceAppend [index:]...)...)
- 在切片的
index
处插入多元素element1
、element2
、...
的语法:sliceAppend = append(sliceAppend [:index], append([]string{element1,element2}, sliceAppend [index:]...)...)
- 在切片的
index
处插入切片slice
的语法:sliceAppend = append(sliceAppend [:index], append(slice, sliceAppend [index:]...)...)
- 示例如下:
输出:func main() { var sliceAppend = []string{"Tom", "Jerry", "Spike", "Tyke"} fmt.Println("原切片:", sliceAppend) // 插入单元素 var sliceAppend1 = append(sliceAppend[:2], append([]string{"Tuffy"}, sliceAppend[2:]...)...) // 插入多元素 var sliceAppend2 = append(sliceAppend[:2], append([]string{"Tuffy", "Pakes"}, sliceAppend[2:]...)...) // 插入切片 var slice = []string{"Tuffy", "Pakes"} var sliceAppend3 = append(sliceAppend[:2], append(slice, sliceAppend[2:]...)...) fmt.Println("索引 2 的地方插入单个元素后的切片:", sliceAppend1) fmt.Println("索引 2 的地方插入多个元素后的切片:", sliceAppend2) fmt.Println("索引 2 的地方插入切片后的切片:", sliceAppend3) }
原切片: [Tom Jerry Spike Tyke] 索引 2 的地方插入单个元素后的切片: [Tom Jerry Tuffy Spike Tyke] 索引 2 的地方插入多个元素后的切片: [Tom Jerry Tuffy Pakes Spike Tyke] 索引 2 的地方插入切片后的切片: [Tom Jerry Tuffy Pakes Spike Tyke]
(7)在切片的开始处插入元素
- 在切片的开始插入单元素
element
的语法:(append
函数的第一个参数必须为切片)sliceAppend = append([]string{element}, sliceAppend...)
- 在切片的开始插入多元素
element1
、element2
、...
的语法:sliceAppend = append([]string{element1, element2}, sliceAppend...)
- 在切片的开始插入切片
slice
的语法:sliceAppend = append(slice, sliceAppend...)
- 示例如下:
输出:func main() { var sliceAppend = []string{"Tom", "Jerry", "Spike", "Tyke"} fmt.Println("原切片:", sliceAppend) // 添加单个元素 var sliceAppend1 = append([]string{"Tuffy"}, sliceAppend...) // 添加多个元素 var sliceAppend2 = append([]string{"Tuffy", "Pakes"}, sliceAppend...) // 添加切片 var slice = []string{"Tuffy", "Pakes"} var sliceAppend3 = append(slice, sliceAppend...) fmt.Println("开头处添加单个元素后的切片:", sliceAppend1) fmt.Println("开头处添加多个元素后的切片:", sliceAppend2) fmt.Println("开头处添加切片后的切片:", sliceAppend3) }
原切片: [Tom Jerry Spike Tyke] 开头处添加单个元素后的切片: [Tuffy Tom Jerry Spike Tyke] 开头处添加多个元素后的切片: [Tuffy Pakes Tom Jerry Spike Tyke] 开头处添加切片后的切片: [Tuffy Pakes Tom Jerry Spike Tyke]
3.3.4 切片删除元素
\quad
Go
语言的切片删除元素使用append
函数来间接的实现。
(1)删除索引 index 处的元素
- 使用
append
函数,间接实现了删除索引index
处的元素。语法格式如下:sliceDelete = append(slice[:index], slice[index+1:]...)
- 示例如下:删除索引为
2
的元素
输出:func main() { var slice = []string{"Tom", "Jerry", "Spike", "Tyke"} var sliceDelete = append(slice[:2], slice[3:]...) fmt.Println("sliceDelete =", sliceDelete) }
sliceDelete = [Tom Jerry Tyke]
(2)删除索引 index1 到 index2 处的元素
- 使用
append
函数,间接实现了删除索引index1
到index2
处(不包含)的元素。语法格式如下:sliceDelete = append(slice[:index1], slice[index2:]...)
- 示例如下:删除索引
1
到4
(不包含)的元素
输出:func main() { var slice = []string{"Tom", "Jerry", "Spike", "Tyke", "Tuffy", "Pakes"} var sliceDelete = append(slice[:1], slice[4:]...) fmt.Println("sliceDelete =", sliceDelete) }
sliceDelete = [Tom Tuffy Pakes]
(3)删除切片前 N 个元素
- 使用索引切片间接实现删除切片前
N
个元素。语法格式:var sliceDelete = slice[N:]
- 示例如下:
输出:func main() { var slice = []string{"Tom", "Jerry", "Spike", "Tyke", "Tuffy", "Pakes"} fmt.Println("原切片:", slice) // 删除切片第1个元素 var sliceDelete1 = slice[1:] // 删除切片前3个元素 var sliceDelete2 = slice[3:] fmt.Println("删除切片第1个元素:", sliceDelete1) fmt.Println("删除切片前3个元素:", sliceDelete2) }
原切片: [Tom Jerry Spike Tyke Tuffy Pakes] 删除切片第1个元素: [Jerry Spike Tyke Tuffy Pakes] 删除切片前3个元素: [Tyke Tuffy Pakes]
(4)删除切片后 N 个元素
- 使用索引切片间接实现删除切片后
N
个元素。语法格式如下:var sliceDelete = slice[:len(slice)-N]
- 示例如下:
输出:func main() { var slice = []string{"Tom", "Jerry", "Spike", "Tyke", "Tuffy", "Pakes"} fmt.Println("原切片:", slice) // 删除切片最后1个元素 var sliceDelete1 = slice[:len(slice)-1] // 删除切片后3个元素 var sliceDelete2 = slice[:len(slice)-3] fmt.Println("删除切片最后1个元素:", sliceDelete1) fmt.Println("删除切片后3个元素:", sliceDelete2) }
原切片: [Tom Jerry Spike Tyke Tuffy Pakes] 删除切片最后1个元素: [Tom Jerry Spike Tyke Tuffy] 删除切片后3个元素: [Tom Jerry Spike]
3.3.5 切片复制
\quad
Go
语言切片的复制使用内置的copy
函数。使用copy
函数复制切片时,源切片的长度如果大于目的切片的长度,则会复制不完整。copy
复制为深拷贝,改变原切片的值不会影响新切片。
- 浅拷贝:目的切片和源切片指向同一个底层数组,任何一个数组元素改变,都会影响两个数组。
- 深拷贝:目的切片和源切片指向不同的底层数组,任何一个数组元素改变,都不影响另外一个。
(1)copy函数
-
copy
函数的语法:func copy(dst, src []Type) int
。将切片src
拷贝到切片dst
并返回拷贝成功的元素的个数。-
dst
:目标切片。 -
src
:源切片。
-
- 如果切片
src
的长度大于dst
切片的长度,那么只会复制dst
切片长度个元素。 - 如果切片
src
的长度小于dst
切片的长度,那么会使用切片src
中所有元素替换dst
中前len(src)
个元素。
(2)案例1:源切片 src 的长度小于目标切片 dst 的长度
- 源切片
srcSlice
的长度为2
,目标切片dstSlice
的长度为4
,复制源切片到目标切片会使用srcSlice
的2
个元素替换dstSlice
的前2
个元素。
输出:func main() { var srcSlice = []string{"Tuffy", "Pakes"} var dstSlice = []string{"Tom", "Jerry", "Spike", "Tyke"} copy(dstSlice, srcSlice) fmt.Println(dstSlice) }
[Tuffy Pakes Spike Tyke]
(3)案例2:源切片 src 的长度大于目标切片 dst 的长度
- 源切片
srcSlice
的长度为4
,目标切片dstSlice
的长度为2
,复制源切片到目标切片会仅复制srcSlice
的前2
个元素到dstSlice
中。
输出:func main() { var srcSlice = []string{"Tom", "Jerry", "Spike", "Tyke"} var dstSlice = []string{"Tuffy", "Pakes"} copy(dstSlice, srcSlice) fmt.Println(dstSlice) }
[Tom Jerry]