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 ...
随机推荐
- “限时分享“ 本地80个小游戏 HTML+CSS+JS源码分享
里面有80款小游戏源码,支持内置导航,可以拿来练手或者消磨时间,具体功能以及游戏请看下图 维京战争小游戏源码 链接:https://pan.baidu.com/s/ ...
- CentOS-配置jar包自启动(SpringBoot)
在pom.xml文件<plugin>中添加配置后,再打包(开发人员) <plugin> <groupId>org.springframework.boot& ...
- Python自动化之封装日志模块(一)
------------恢复内容开始------------ 简介: 自己也在训练营学习之中,闲来之余,自己写着玩的,主要还是为了学习,希望和前辈和大佬相互学习共进. 日志模块主要有四大组件:日志器, ...
- Java并发实战一:线程与线程安全
从零开始创建一家公司 Java并发编程是Java的基础之一,为了能在实践中学习并发编程,我们跟着创建一家公司的旅途,一起来学习Java并发编程. 进程与线程 由于我们的目标是学习并发编程,所以我不会把 ...
- 关于varnish缓存
目录 缓存的概念 一.varnish缓存 1. 简介 2. 总体结构 2.1 两个主进程 2.1.1 Management进程 2.1.2 Child/Cacher进程 2.2 Varnish的日志收 ...
- 第一个用户进程 - Android 的 Init 进程
本文尝试对着 <深入理解 Android 5.0 系统>来对 android 9.0 的启动代码进行分析,但是分析过程中发现自己缺乏操作系统方面的知识,以致于只能做一些简单分析.最近也买了 ...
- Codeforces Round#687 Div2 题解
打这场的时候迷迷糊糊的,然后掉分了( A Prison Break: 题面很复杂,但是题意很简单,仅需求出从这个点到四个角的最大的曼哈顿距离即可 #include <bits/stdc++.h& ...
- SVN教程(包括小乌龟) 全图解
转载自http://www.cnblogs.com/armyfai/p/3985660.html SVN使用教程总结 SVN简介: 为什么要使用SVN? 程序员在编写程序的过程中,每个程序员都会生成很 ...
- Scala学习——模式匹配
scala模式匹配 1.基础match case(类似java里switch case,但功能强大些) object MatchApp { def main(args: Array[String]): ...
- 算法leetcode二分算法
二分算法通常用于有序序列中查找元素: 有序序列中是否存在满足某条件的元素: 有序序列中第一个满足某条件的元素的位置: 有序序列中最后一个满足某条件的元素的位置. 思路很简单,细节是魔鬼. 一.有序序列 ...