Golang语言系列-05-数组和切片
数组和切片
数组
概念
数组是同一种数据类型元素的集合;数组的长度必须是常量,并且长度是数组类型的一部分,一旦定义,长度不能变
例如:[5]int 和 [10]int 是不同的数组类型
使用时可以修改数组成员,但是数组大小长度不可变化
数组的初始化
package main
import (
"fmt"
)
func main() {
var a1 [3]bool
var a2 [4]bool
var a122 [3]func()
fmt.Printf("a1:%T a2:%T a122:%T\n", a1, a2, a122) //a1:[3]bool a2:[4]bool a122:[3]func()
// 数组的初始化
// 如果不初始化:默认元素都是零值(布尔值:false,整型和浮点型都是0,字符串为空:"",函数为nil)
// a1:[false false false] a2:[false false false false] a122:[<nil> <nil> <nil>]
fmt.Printf("a1:%v a2:%v a122:%v\n", a1, a2, a122)
// 初始化的方式
// 1.初始化方式1
a1 = [3]bool{true, true, true}
fmt.Println(a1) //[true true true]
// 2.初始化方式2:根据初始值自动推断数组的长度是多少
a10 := [...]int{1, 2, 3, 4, 5, 6, 7, 8}
fmt.Println(a10) //[1 2 3 4 5 6 7 8]
// 3.初始化方式3:根据索引来初始化
// a3 := [...]int{0:1, 4:2} //索引为0的值为1,索引为4的值为2
a3 := [5]int{0: 1, 4: 2} //索引为0的值为1,索引为4的值为2
fmt.Println(a3) //[1 0 0 0 2]
}
数组的遍历
package main
import (
"fmt"
)
func main() {
// 数组的遍历
citys := [...]string{"北京", "上海", "深圳"}
// 1.根据索引遍历
// 用 len() 函数统计数组的长度然后在进行 for 循环是可以的。这里和string类型的遍历有差别
for i := 0; i < len(citys); i++ {
fmt.Println(citys[i])
}
// 2.for range遍历
for _, v := range citys {
fmt.Println(v)
}
}
多维数组
package main
import (
"fmt"
)
func main() {
// 多维数组
// 注意:多维数组只有第一层可以使用 [...]int{1, 2, 3} 来让编译器推导数组长度
// [ [1 2] [3 4] [5 6] ]
var a11 [3][2]int
a11 = [3][2]int{
[2]int{1, 2},
[2]int{3, 4},
[2]int{5, 6},
}
fmt.Println(a11) //[[1 2] [3 4] [5 6]]
a12 := [3][2]int{
[2]int{1, 2},
[2]int{3, 4},
[2]int{5, 6},
}
fmt.Println(a12) //[[1 2] [3 4] [5 6]]
// 多维数组遍历
// 1 2 3 4 5 6
for _, v1 := range a11 {
for _, v2 := range v1 {
fmt.Println(v2)
}
}
}
数组是值类型
package main
import (
"fmt"
)
func main() {
// 数组是值类型
// 赋值和传参会复制整个数组,因此改变副本的值,不会改变原来数组本身的值
fmt.Println("------ 数组是值类型 ------")
b1 := [3]int{1, 2, 3} //[1 2 3]
b2 := b1 //[1 2 3] Ctrl+C Ctrl+V 相当于把world文档从文件夹A拷贝到文件夹B
b2[0] = 100 //b2:[100 2 3]
//b1[0] = 2 //数组可以修改值,但是长度不能变
fmt.Println(b1, b2) //b1:[1 2 3] b2:[100 2 3]
// 数组支持 “==“、”!=” 操作符,因为内存总是被初始化过的
arr1 := [...]int{1, 2, 3}
arr2 := [...]int{1, 2, 3}
fmt.Println(arr1 == arr2) //true
}
指针数组和数组的指针
package main
import (
"fmt"
)
func main() {
// 指针数组
// [n]*T表示指针数组
s1 := "abc"
s2 := &s1
s3 := &s1
arr3 := [...]*string{s2, s3}
fmt.Println(arr3) //[0xc00008e240 0xc00008e240]
// 数组的指针
// *[n]T表示数组的指针
arr4 := [...]int{1, 2}
op := &arr4
fmt.Println(*op) //[1 2]
fmt.Printf("%T\n", *op) //[2]int
fmt.Printf("%p\n", op) //0xc0000180e0 op的值是数组arr4这个变量的指针
fmt.Printf("%p\n", &op) //0xc00000e030 op自己在内存中的地址
fmt.Printf("%p\n", &arr4) //0xc0000180e0 op的值是数组arr4这个变量的指针
}
数组练习
package main
import "fmt"
// array数组练习题
// 1.求数组[1, 3, 5, 7, 8]所有元素的和
// 2.找出数组中和为指定值的两个元素的下标,比如从数组[1, 3, 5, 7, 8]中找出和为8的两个元素的下标分别为(0,3)和(1,2)
func main() {
// 1
a1 := [...]int{1, 3, 5, 7, 8}
sum1 := 0
for _, v := range a1 {
sum1 += v
}
fmt.Println("sum1:", sum1)
// 2
a2 := [...]int{1, 3, 5, 7, 8}
for i := 0; i < len(a2); i++ {
for j := i + 1; j < len(a2); j++ {
if a2[i]+a2[j] == 8 {
fmt.Printf("(%d,%d)\n", i, j)
}
}
}
}
切片
概念
切片是一个拥有相同类型元素的可变长度的序列。它是基于数组类型做的一层封装。它非常灵活,支持自动扩容
切片是一个引用类型(数组是一个值类型),它的内部结构包含地址、长度和容量。切片一般用于快速地操作一块数据集合
内置的len()
函数求切片长度,内置的cap()
函数求切片的容量
切片的底层就是一个数组,所以我们可以基于数组通过切片表达式得到切片
判断切片是否为空 请始终使用len(s) == 0
来判断,而不应该使用s == nil来判断 初始化以后的切片 != nil
package main
import (
"fmt"
)
func main() {
// 切片的定义
var s1 []int //定义一个存放int类型元素的切片
var s2 []string //定义一个存放string类型元素的切片
fmt.Println(s1, s2) //[] []
fmt.Println(s1 == nil) //true
fmt.Println(s2 == nil) //true
fmt.Println(len(s1), len(s2)) // 0 0
// 切片初始化
s1 = []int{1, 2, 3}
s2 = []string{"沙河", "张江", "平山村"}
fmt.Println(s1, s2) //[1 2 3] [沙河 张江 平山村]
fmt.Println(s1 == nil) //false
fmt.Println(s2 == nil) //false
fmt.Println(len(s1), len(s2)) // 3 3
// 长度和容量
fmt.Printf("len(s1):%d cap(s1):%d\n", len(s1), cap(s1)) //len(s1):3 cap(s1):3
fmt.Printf("len(s2):%d cap(s2):%d\n", len(s2), cap(s2)) //len(s2):3 cap(s2):3
// 由数组得到切片
a1 := [...]int{1, 3, 5, 7, 9, 11, 13} //数组
s3 := a1[0:4] //基于一个数组切割,顾首不顾尾
fmt.Println(s3) //[1 3 5 7]
s4 := a1[1:6]
fmt.Println(s4) //[3 5 7 9 11]
s5 := a1[:4]
s6 := a1[3:]
s7 := a1[:]
fmt.Println(s5) //[1 3 5 7]
fmt.Println(s6) //[7 9 11 13]
fmt.Println(s7) //[1 3 5 7 9 11 13]
// 切片的容量:是指底层数组的容量
fmt.Printf("len(s5):%d cap(s5):%d\n", len(s5), cap(s5)) // len(s5):4 cap(s5):7
// 切片容量:底层数组从切片的第一个元素到最后的元素数量
fmt.Printf("len(s6):%d cap(s6):%d\n", len(s6), cap(s6)) // len(s6):4 cap(s6):4
// 切片再切割
s8 := s6[3:] //[13]
fmt.Printf("len(s8):%d cap(s8):%d\n", len(s8), cap(s8)) //len(s8):1 cap(s8):1
// 切片是引用类型,都指向了底层的一个数组
// 修改底层数组的值,会影响切片
fmt.Println("s6: ", s6) //[7 9 11 13]
a1[6] = 1300 //修改底层数组的值
fmt.Println("s6: ", s6) //[7 9 11 1300]
fmt.Println("s8: ", s8) //[1300]
fmt.Println("a1: ", a1) //[1 3 5 7 9 11 1300]
// 修改切片,会修改底层数组吗?如果只是修改值的话不涉及到扩容,是会修改原底层数组的
s8[0] = 10000
fmt.Println(s8) //[10000]
fmt.Println(a1) //[1 3 5 7 9 11 10000] a1数组发生改变
s8 = append(s8, 20000) //此处已经产生了新的数组,切片s8的容量是1,s8的切片不再指向原来的底层数组
fmt.Printf("s8:%v\n", s8) //[10000 20000]
fmt.Println(a1) //[1 3 5 7 9 11 10000] a1数组不发生改变
// append追加数组元素,如果底层数组容量不够的时候会扩容,产生新的数组
a10 := [5]int{1, 2, 3}
s10 := a10[:]
fmt.Printf("len(s10):%d cap(s10):%d\n", len(s10), cap(s10)) //len(s10):5 cap(s10):5
//s10 = append(s10, 4)
//fmt.Println(cap(s10)) //10
s10[3] = 1
//fmt.Println(a10, s10) //[1 2 3 0 0] [1 2 3 1 0 4] 如果没注释s10的append,证明Go已经把底层的数组换了
fmt.Println(a10, s10) //[1 2 3 1 0] [1 2 3 1 0] 如果注释了s10的append,则s10[3]=1修改的是底层元素
}
make函数初始化切片
package main
import (
"fmt"
)
func main() {
// make函数初始化切片
var a = make([]string, 5, 10) // make(类型,长度,容量)
fmt.Printf("cap:%d %#v\n", cap(a), a) //cap:10 []string{"", "", "", "", ""}
// append()
for i := 0; i < 10; i++ {
a = append(a, fmt.Sprintf("%v", i)) // 拼接成字符串
}
fmt.Printf("%#v\n", a)
//[]string{"", "", "", "", "", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}
fmt.Println(len(a), cap(a)) //15 20
// 初始化
s1 := make([]int, 5, 10) //make(类型,长度,容量)
fmt.Printf("s1=%v len(s1)=%d cap(s1)=%d\n", s1, len(s1), cap(s1)) //s1=[0 0 0 0 0] len(s1)=5 cap(s1)=10
s2 := make([]int, 0, 10)
fmt.Printf("s1=%v len(s1)=%d cap(s1)=%d\n", s2, len(s2), cap(s2)) //s1=[] len(s1)=0 cap(s1)=10
// 切片的赋值
s3 := []int{1, 3, 5}
s4 := s3 //s3和s4都指向了同一个底层数组
fmt.Println(s3, s4) //[1 3 5] [1 3 5]
s3[0] = 1000
fmt.Println(s3, s4) //[1000 3 5] [1000 3 5]
}
切片的遍历
package main
import (
"fmt"
)
func main() {
// 切片的遍历
s3 := []int{1, 3, 5}
// 1.索引遍历
for i := 0; i < len(s3); i++ {
fmt.Println(s3[i])
}
// 2.for range循环
for _, v := range s3 {
fmt.Println(v)
}
}
切片的append()
package main
import (
"fmt"
)
// append()为切片追加元素
// 可能会导致数组扩容,从而让切片指向新的数组
func main() {
s1 := []string{"北京", "上海", "深圳"}
fmt.Printf("s1=%v len(s1)=%d cap(s1)=%d\n", s1, len(s1), cap(s1)) //s1=[北京 上海 深圳] len(s1)=3 cap(s1)=3
//s1[3] = "guangzhou" //panic: runtime error: index out of range [3] with length 3
// 调用append函数必须用原来的切片变量接收返回值
// append追加元素,原来的底层数组放不下的时候,Go就会把底层数组换一个
// 必须用变量接收append的返回值
s1 = append(s1, "广州")
fmt.Printf("s1=%v len(s1)=%d cap(s1)=%d\n", s1, len(s1), cap(s1))
//s1=[北京 上海 深圳 广州] len(s1)=4 cap(s1)=6
s1 = append(s1, "杭州", "成都")
fmt.Printf("s1=%v len(s1)=%d cap(s1)=%d\n", s1, len(s1), cap(s1))
//s1=[北京 上海 深圳 广州 杭州 成都] len(s1)=6 cap(s1)=6
ss := []string{"武汉", "西安", "苏州"}
s1 = append(s1, ss...)
//表示拆开(打散),一个一个的元素添加到别的切片
fmt.Printf("s1=%v len(s1)=%d cap(s1)=%d\n", s1, len(s1), cap(s1))
//s1=[北京 上海 深圳 广州 杭州 成都 武汉 西安 苏州] len(s1)=9 cap(s1)=12
}
切片的拷贝
package main
import (
"fmt"
)
// 切片的copy
func main() {
a1 := []int{1, 3, 5}
aa := a1[:1]
fmt.Println(cap(aa), len(aa), aa) // 容量是3 长度是1 [1]
a2 := a1 //赋值 a2 a1 指向的是同一个底层数组
var a3 = make([]int, 3, 3)
copy(a3, a1) //copy 底层数组复制了一份,a3 和 a1 指向的不是同一个底层数组
fmt.Println(a1, a2, a3) //[1 3 5] [1 3 5] [1 3 5]
a1[0] = 100
fmt.Println(a1, a2, a3) //[100 3 5] [100 3 5] [1 3 5]
// 将a1中的索引为1的 3 这个元素删掉。 ... 由于切片没有直接删除元素的方法,所以可以采用这种方法
a1 = append(a1[:1], a1[2:]...) // a1[:1] 容量是3,append 1 个元素的时候,底层数组没有发生变化
fmt.Println(a1) // [100 5]
fmt.Println(cap(a1)) // 3
fmt.Println(a2) // [100 5 5]
x1 := [...]int{1, 3, 5} //数组
fmt.Println("===", x1)
s1 := x1[:] //数组经过[L:M]以后,可以得到切片
fmt.Println(s1, len(s1), cap(s1))
// 1.切片不保存具体的值
// 2.切片对应一个底层数组
// 3.底层数组都是占用一块连续的内存
fmt.Printf("%p\n", &s1[0])
s1 = append(s1[:1], s1[2:]...) //s1[:1] 容量是3,append 1 个元素的时候,底层数组没有发生变化
fmt.Printf("%p\n", &s1[0]) //Go语言中不存在指针操作,只需要记住两个符号:&:取地址 *:根据地址取值
fmt.Println(s1, len(s1), cap(s1)) //[1 5] 2 3
fmt.Println(x1) //[1 5 5]
s1[0] = 100
fmt.Println(x1) //[100 5 5]
}
切片练习
package main
import (
"fmt"
"sort"
)
// 切片练习题
func main() {
var a = make([]int, 5, 10) //初始化切片,长度5,容量10
fmt.Println(a) //[0 0 0 0 0]
for i := 0; i < 10; i++ {
a = append(a, i)
}
fmt.Println(a) //[0 0 0 0 0 0 1 2 3 4 5 6 7 8 9]
fmt.Println(cap(a)) //20
var a1 = [...]int{3, 7, 8, 9, 1}
sort.Ints(a1[:]) //对切片进行排序
fmt.Println(a1) //[1 3 7 8 9]
// 要检查切片是否为空,请始终使用len(s) == 0来判断,而不应该使用s == nil来判断
var aa []int //只定义,没初始化
fmt.Println(aa == nil) //true
var bb []int //定义
bb = []int{} //初始化后就不为nil了,但是还是空的切片
fmt.Println(bb == nil) //false
fmt.Println(aa, bb) //[] []
if len(bb) == 0 {
fmt.Println("空数组")
}
}
package main
import "fmt"
//切片练习
func main() {
//ex1
//s1 := make([]int, 4, 100)
//s1[0] = 1
//s1[1] = 2
//fmt.Println(s1) //[1 2 0 0]
//s2 := s1[:] //再切片,切片的切片,底层数组还是同一个
////s2 := s1 //赋值 底层数组是同一个
//s2[0] = 100
//fmt.Println(s1) //[100 2 0 0]
//fmt.Println(s2) //[100 2 0 0]
//fmt.Println(cap(s1), cap(s2)) //100 100
//ex2
//s1 := make([]int, 4, 4)
//s1[0] = 1
//s1[1] = 2
//fmt.Println(s1) //[1 2 0 0]
//s2 := s1[:] //切片的再切片 len:4 cap:4
//
////append()扩容以后会形成新的数组
//s2 = append(s2, 100) //形成新的底层数组 [1 2 0 0 100] len:4 cap:8
//
//fmt.Println(s1) //[1 2 0 0]
//fmt.Println(s2) //[1 2 0 0 100]
//fmt.Println(cap(s1), cap(s2)) //4 8
//ex3
//s1 := make([]int, 4, 4)
//s1[0] = 1
//s1[1] = 2
//fmt.Println(s1) //[1 2 0 0]
//s2 := s1[:2] //[1 2] len:2 cap:4
//fmt.Println("==", s2, len(s2), cap(s2)) //[1 2] 2 4
//s2 = append(s2, 100) //append()这里没有扩容,因为长度是2,容量是4,所以不会形成新的底层数组,此时底层数组是:[1 2 100]
//fmt.Println(s1) //[1 2 100 0]
//fmt.Println(s2) //[1 2 100] 这里s2的长度是3,不是4哦,所以是 [1 2 100]
//fmt.Println(cap(s1), cap(s2)) //4 4
//fmt.Println(len(s1), len(s2)) //4 3
//ex4
//var s1 = [...]int{1, 2, 3, 4, 5}
//s2 := s1[2:] //[3 4 5] len:3 cap:3
//
//s2 = append(s2, 100) //扩容了,产生新的底层数组 [3 4 5 100] len:4 cap:6
//
//fmt.Println(s1) //[1 2 3 4 5]
//fmt.Println(s2) //[3 4 5 100]
//fmt.Println(cap(s1), cap(s2)) //5 6
//fmt.Println(len(s1), len(s2)) //5 4
//ex5
//var arr = [...]int{1, 2, 3, 4, 5}
//slice := arr[:2] //[1 2] len:2 cap:5
//slice = append(slice, 6, 7) //没扩容 切片是:[1 2 6 7] len:4 cap:5
//slice[0] = 100 //[100 2 6 7]
//fmt.Println(arr) //[100 2 6 7 5]
//fmt.Println(slice) //[100 2 6 7]
//fmt.Println(cap(arr), cap(slice)) //5 5
//fmt.Println(len(arr), len(slice)) //5 4
//ex6
var arr = [...]int{1, 2, 3, 4, 5}
slice := arr[:2] // [1 2] len:2 cap:5
slice = append(slice, 6, 7, 8, 9, 10) //底层数组扩容了,切片是[1 2 6 7 8 9 10]
slice[0] = 100 //[100 2 6 7 8 9 10]
fmt.Println(arr) //[1 2 3 4 5]
fmt.Println(slice) //[100 2 6 7 8 9 10]
fmt.Println(cap(arr), cap(slice)) //5 10
fmt.Println(len(arr), len(slice)) //5 7
}
Golang语言系列-05-数组和切片的更多相关文章
- Go语言中底层数组和切片的关系以及数组扩容规则
Go语言中底层数组和切片的关系以及数组扩容规则 demo package main import ( "fmt" ) func main() { // 声明一个底层数组,长度为10 ...
- GO语言总结(3)——数组和切片
上篇博文简单介绍了一下Go语言的基本类型——GO语言总结(2)——基本类型,本篇博文开始介绍Go语言的数组和切片. 一.数组 与其他大多数语言类似,Go语言的数组也是一个元素类型相同的定长的序列. ( ...
- go语言之行--数组、切片、map
一.内置函数 append :追加元素到slice里,返回修改后的slice close :关闭channel delete :从map中删除key对应的value panic : 用于异常处理,停 ...
- [Golang学习笔记] 07 数组和切片
01-06回顾: Go语言开发环境配置, 常用源码文件写法, 程序实体(尤其是变量)及其相关各种概念和编程技巧: 类型推断,变量重声明,可重名变量,类型推断,类型转换,别名类型和潜在类型 数组: 数组 ...
- PHP转Go系列:数组与切片
数组的定义 用过PHP的同学应该很清楚,无论多么复杂的数据格式都可以用数组来表达,什么类型的数据都可以往里塞,它是工作必备的一部分,使用很简单,易用程度简直变态. $array = [1, 'name ...
- 换个语言学一下 Golang (6)——数组,切片和字典
在上面的章节里面,我们讲过Go内置的基本数据类型.现在我们来看一下Go内置的高级数据类型,数组,切片和字典. 数组(Array) 数组是一个具有相同数据类型的元素组成的固定长度的有序集合.比如下面的例 ...
- Golang基础(3):数组,切片和字典
一:数组 数组是一个具有相同类型元素,固定长度的有序集合,一般定义如下:var x [5]int表示数组是一个整数型数组,长度为5数组的几种定义方式 a: var来定义数组,然后依次赋值 packag ...
- PHP转Go系列:数组与切片 转
数组的定义# 用过PHP的同学应该很清楚,无论多么复杂的数据格式都可以用数组来表达,什么类型的数据都可以往里塞,它是工作必备的一部分,使用很简单,易用程度简直变态. Copy $array = [1, ...
- Golang语言系列-08-结构体
结构体 自定义类型和类型别名的区别 package main import "fmt" // 自定义类型和类型别名的区别 // type后面跟的是类型 type myInt int ...
随机推荐
- Python迭代器和生成器你学会了吗?
在了解什么是迭代器和生成器之前,我们先来了解一下容器的概念.对于一切皆对象来说,容器就是对象的集合.例如列表.元祖.字典等等都是容器.对于容器,你可以很直观地想象成多个元素在一起的单元:而不同容器的区 ...
- MySql:使用Navicat定时备份数据库
Navicat自带就有备份 还可以直接计划任务,很方便. 1. 新建计划 打开navicat客户端,连上mysql后,双击左边你想要备份的数据库.点击"计划",再点击" ...
- Shell常用工具find,grep,sed,awk,xargs命令
最近学习shell命令,对grep,sed,awk命令有点混乱,故小结一下,巩固一遍. 注意:find , grep , sed, awk可使用基本正则表达式字符,find,grep,awk也支持扩展 ...
- Kotlin Coroutine(协程): 二、初识协程
@ 目录 前言 一.初识协程 1.runBlocking: 阻塞协程 2.launch: 创建协程 3.Job 4.coroutineScope 5.协程取消 6.协程超时 7.async 并行任务 ...
- FirstDay
昨天心血来潮,想着注册一博客,没想到今天再登时,审阅就通过了,多少有点庆辛.从今天起,我也算是有博客的人了! 为什么选博客园开通?好多IT论坛里都允许有博文,CSDN感觉过于高大上,其他系列论坛大多内 ...
- mongodb,redis,mysql的区别和具体应用场景(转)
一.MySQL 关系型数据库. 在不同的引擎上有不同 的存储方式. 查询语句是使用传统的sql语句,拥有较为成熟的体系,成熟度很高. 开源数据库的份额在不断增加,mysql的份额页在持续增长. 缺点就 ...
- linux驱动之获取设备树信息
上一篇文章学习了字符设备的注册,操作过的小伙伴都知道上一篇文章中测试驱动时是通过手动创建设备节点的,现在开始学习怎么自动挂载设备节点和设备树信息的获取,这篇文章中的源码将会是我以后编写字符驱动的模板. ...
- ARTS起始篇
ARTS简要说明(每周需要完成以下四项): Algorithm:每周至少做一道 leetcode 的算法题,编程训练.刻意练习. Review:需要阅读并点评至少一篇英文技术文章,这个是四项里面对我最 ...
- 史上最全的Nginx配置文档
Nginx是一个异步框架的Web服务器,也可以用作反向代理,负载平衡器 和 HTTP缓存.该软件由Igor Sysoev 创建,并于2004年首次公开发布.同名公司成立于2011年,以提供支持.Ngi ...
- WSL2:在Windows系统中开发Linux程序的又一神器
作 者:道哥,10+年的嵌入式开发老兵. 公众号:[IOT物联网小镇],专注于:C/C++.Linux操作系统.应用程序设计.物联网.单片机和嵌入式开发等领域. 公众号回复[书籍],获取 Linux. ...