0
点赞
收藏
分享

微信扫一扫

go语言基础语法-切片


文章目录

  • 1. 概念
  • 2. 切片的基本操作
  • 1) 切片生成方式
  • a. 从数组或者切片上切的生成方式
  • b. 直接声明切片 : var name []Type
  • 2)make()函数构造切片
  • 3)切片扩容append ()方法
  • 5) 删除元素
  • 4)切片遍历
  • 3. 关于数据类型的补充
  • 1)值类型
  • 2)引用类型
  • 3) 切片是个什么类型
  • 4. 关于切片的原理
  • 总结

1. 概念

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

切片可以动态的扩容,切片的类型和长度无关。

相当于java中的 ArrayList

需要注意的是: 切片是一个引用类型,不存在具体的值。

a := [3]int{1,2,3}
这是个数组,一共3个元素,不能在添加元素了。 而切片可以添加元素。

2. 切片的基本操作

1) 切片生成方式

切片的生成方式有两种:

a. 从数组或者切片上切
b. 直接声明切片 : var name []Type

ame:表示变量名
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, b, c)   //[] [] [false true]
	fmt.Println(a == nil)  //true
	fmt.Println(b == nil)  //false
	fmt.Println(c == nil)  //false
	/// fmt.Println(c == d)   这行报错,切片是引用类型,不支持直接比较,只能和nil比较
}

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

a. 从数组或者切片上切的生成方式

package main
import (
 “fmt”
 “reflect”
 )func main() {
 var arr = [6]int{10, 11, 12, 13, 14, 15}
 var s1 = arr[1:4] // 4-1有三个元素
 fmt.Println(s1, reflect.TypeOf(s1))
 }

执行结果:

go语言基础语法-切片_goland


[ ] int 这种叫切片

[4]int 这种叫数组

这里有个问题:

当我把一个数据改了之后,其他数据会变吗?

package main

import (
	"fmt"
)

func main() {
	var arr = [6]int{10, 11, 12, 13, 14, 15}
	var s1 = arr[1:4] // 4-1有三个元素
	fmt.Println(s1)
	var s2 = arr[2:4]
	fmt.Println(s2)

}

这个时候打印出来:

go语言基础语法-切片_引用类型_02

修改其中的一个元素,来看看s1和s2会变化吗?

package main

import (
	"fmt"
)

func main() {
	var arr = [6]int{10, 11, 12, 13, 14, 15}
	var s1 = arr[1:4] // 4-1有三个元素
	fmt.Println(s1)
	var s2 = arr[2:4]
	fmt.Println(s2)
	arr[2] = 1200
	fmt.Println(s1, s2) //这两个值会变吗
}

go语言基础语法-切片_引用类型_03


发现s1和s2也变化了

为什么呢? 我们放在最后说这个原理的问题

b. 直接声明切片 : var name []Type

[] 中不加数字就是切片

import (
	"fmt"
	"reflect"
)

func main() {
	var s = []int{10, 11, 12, 13, 14, 15}
	fmt.Println(s, reflect.TypeOf(s))

}

问题: 切片还可以切片吗?

答案是可以, 因为数组的切片依旧可以切,现在我们直接是切片了,一样可以切

2)make()函数构造切片

如果需要动态的创建一个切片,我们就需要使用内置的make()函数

格式如下

make([]T, size, cap)

T:切片的元素类型
size:切片中元素的数量
cap:切片的容量

package main

import "fmt"

func main() {
	var s4 =make([]int,3,5) //构建切片类型,s4只占3个长度,所以是0 0 0  ,这里的3是长度,5是容量
	fmt.Println(s4) 
	fmt.Println(len(s4))
	fmt.Println(cap(s4)
}

package main

import "fmt"

func main() {
	var s4 = make([]int, 3, 5) //构建切片类型,s4只占3个长度,所以是0 0 0  ,这里的3是长度,5是容量
	fmt.Println(s4)
	s4[0] = 100
	fmt.Println(s4)
	fmt.Println(len(s4), cap(s4))
}

执行结果:

go语言基础语法-切片_goland_04

3)切片扩容append ()方法

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

package main

import "fmt"

func main() {
	var s4 = make([]int, 3, 5) //构建切片类型,s4只占3个长度,所以是0 0 0  ,这里的3是长度,5是容量
	s1 := append(s4, 1)
	fmt.Println(s1)
}

另外一种方式:

package main

import "fmt"

func main() {
	var s []int
	s1 := append(s, 1, 2, 3, 4, 5, 6)
	fmt.Println(s1)
}

结果:

go语言基础语法-切片_数组_05

追加一个切片进去,需要用到… (三个点)

package main

import "fmt"

func main() {
	var s []int
	s1 := append(s,[]int{1,2,3,4}...)
	fmt.Println(s1)
}

go语言基础语法-切片_数组_06

不加…会报错,因为我们本身定义的是int类型

总结:

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

5) 删除元素

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

package main

import "fmt"

func main() {
	s := []int{1, 3, 4, 5, 6}
	fmt.Println(s[:2])
	fmt.Println(s[3:])

	s = append(s[:2], s[3:]...)
	fmt.Println(s)
}

执行结果:

go语言基础语法-切片_引用类型_07

4)切片遍历

方法一:

package main

import "fmt"

func main() {
	s := []int{1, 3, 4}
	for i := 0; i < len(s); i++ {
		fmt.Println(i, s[i])
	}
}

方法二: 用range

package main

import "fmt"

func main() {
	s := []int{1, 3, 4}
	for i, v := range s {
		fmt.Println(i, v)
	}
}

3. 关于数据类型的补充

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

1)值类型

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

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

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

2)引用类型

切片是对某一个存储空间的引用,而不会赋值

指针,slice,map,chan,interface等都是引用类型。

特点:变量存储的是一个地址,这个地址存储最终的值。内存通常在堆上分配,通过GC回收。

变量直接存放的就是一个内存地址值,这个地址值指向的空间存的才是值。所以修改其中一个,另外一个也会修改(同一个内存地址)。

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

一个切片包含三个部分: 引用地址,长度,容量

3) 切片是个什么类型

切片是第一个引用类型

在不赋值的时候,他的值 默认为空

package main

import (
	"fmt"
)

func main() {
	var s3 []int
	fmt.Println(s3)
}

go语言基础语法-切片_goland_08


可以看到切片类型没有默认值, 这个是与值类型最大的不同。

值类型默认是有值的。

当引用类型,只声明,未赋值,在内存中并不开辟空间

所以当你赋值的时候会报错。

package main

import (
	"fmt"
)

func main() {
	var s3 []int
	fmt.Println(s3)
	//s3[0]=1  这种赋值会报错,因为默认值为空
}

如果想赋值怎么办呢?
答案是使用make()函数,这个我们前面讲过了,可以再回去回顾一下。

4. 关于切片的原理

go语言基础语法-切片_数组_09

package main

import "fmt"

func main() {
	s := [...]int{1, 3, 4, 5, 6, 7}
	slice := s[1:3]

	fmt.Println(s)     //[1 3 4 5 6,7]
	fmt.Println(slice) //[3 4]

	fmt.Println(cap(slice)) // 切片的容量是可以动态变化的,可以理解为底层数组容量个数-1

}

接下来我们看看切片和数组之间的内存地址有什么规律

package main

import "fmt"

func main() {
	s := [...]int{1, 3, 4, 5, 6, 7}
	slice := s[1:3]

	fmt.Println(s)     //[1 3 4 5 6,7]
	fmt.Println(slice) //[3 4]

	fmt.Println(cap(slice)) // 切片的容量是可以动态变化的,可以理解为底层数组容量个数-1
	fmt.Printf("切片为0的内存地址:%p,数组索引为1的内存地址:%p,", &slice[0], &s[1])
	/*
		可以看到两个地址相同  切片为0的内存地址:0xc00000a368,数组索引为1的内存地址:0xc00000a368,

	*/

	//如果我们该切片的值,数组的值会变化吗?答案是会
	slice[0] = 100
	fmt.Println(s) //,[1 100 4 5 6 7]

	fmt.Println(slice) //[100 4]

}

总结

  1. cap是一个内置函数,用于统计切片的容量,即 最大可以存放多少个元素。
  2. 切片定义完之后,还不能使用,因为本身是空,需要让其引用到一个数组,或者make一个空间供切片使用
  3. 切片也是可以继续切片的
  4. 切片中append函数实际上就是对数组扩容,go会在底层创建一个新的数组,然后把原来的元素拷贝过去,在指向新的数组。当然这个新的数组对程序员不可见。
  5. 切片的拷贝是通过内置函数实现的,copy函数
  6. 切片的内置函数汇总
    slice表示切片
    len 获取slice的长度
    cap 获取slice的最大容量
    append 向slice里面追加一个或者多个元素,然后返回一个和slice一样类型的slice
    copy 函数copy从源slice的src中复制元素到目标dst,并且返回复制的元素的个数


举报

相关推荐

0 条评论