1.数组
-
数组是同一种数据类型元素的集合。 在Go语言中,数组从声明时就确定,使用时可以修改数组成员,但是数组大小不可变化。 基本语法:
// 数组长度必须是常量,且是类型的组成部分,一旦定义,长度不变。 var a [3]int
-
数组可以通过下标进行访问,下标从0开始,最后一个元素下标是:len-1。
// 遍历方式 for i:=0;i<len(Arr);i++ { } for index,v := range Arr { }
-
如果下标在数组的合法范围之外,则触发访问越界,会panic
var arr = [4]string{"1","2","3","4"}
fmt.Println(arr[5])
// 错误信息: invalid array index 5 (out of bounds for 4-element array)
-
数组的初始化:
package main import "fmt" //数组相关内容 func main() { var a [3]int var b [4]int //注意a与b不能互相赋值,长度不同 fmt.Println(a, b) //数组的初始化 //1定义时使用初始值列表的方式初始化 var cityArray = [4]string{"北京", "上海", "广州", "深圳"} fmt.Println(cityArray) fmt.Println(cityArray[3]) //2.编译器推导数组的长度 var boolArray = [...]bool{true, false, true, false, false} fmt.Println(boolArray) //3.使用索引值方式初始化 var langeArray = [...]string{1: "Golang", 3: "Python", 7: "Java"} fmt.Println(langeArray) fmt.Printf("%T ", langeArray) }
-
数组内定义结构体
d := [...]struct { name string age uint16 }{ // 注意最后一行要加逗号,否则报错:syntax error: unexpected newline, expecting comma or { {"u1",123}, {"u2",456}, {"u3",789}, } // [{u1 123} {u2 456} {u3 789}] fmt.Println(d)
-
数组的遍历:
- for 循环遍历
var cityArray = [4]string{"北京", "上海", "广州", "深圳"} //1.for 循环遍历 for i := 0; i < len(cityArray); i++ { fmt.Println(cityArray[i]) }
- for range循环遍历
var cityArray = [4]string{"北京", "上海", "广州", "深圳"} for index, value := range cityArray { fmt.Println(index, value) }
-
二维数组:
- 注意:多维数组,只有最外层可以用[...]让编译器自己识别编译,内层不能使用。
//定义一个多维数组 cityArray := [...][2]string{ {"北京", "西安"}, {"上海", "重庆"}, {"杭州", "成都"}, {"广州", "深圳"}, } //打印杭州 fmt.Println(cityArray[2][0]) //遍历循环二维数组, for _, value1 := range cityArray { for _, value2 := range value1 { fmt.Println(value2) } } // demo2 package main import "fmt" func main() { // 这里需要注意,第二个维度的不能使用"..." var arr1 [2][3]int = [...][3]int{{1,2,3},{4,5,6}} // [[1 2 3] [4 5 6]] fmt.Println(arr1) }
-
数组类型
- 数组是值类型,赋值和传参会赋值整个数组,而不是指针。所以改变副本的值,不会改变本身的值。
var arr = [4]string{"1","2","3","4"} arr2 := arr arr2[3] = "10" fmt.Println(arr,arr2) // [1 2 3 4] [1 2 3 10]
- demo
package main import "fmt" //数组相关内容 func main() { //数组 是 值类型 x := [3]int{1, 2, 3} fmt.Println(x)//[1 2 3] f1(x) y:= x y[0] = 1000 fmt.Println(x)//[1 2 3] } func f1(a [3]int) { a[0] = 100 } //前后打印结果一样,数组是值类型,无论是把它当参数传到函数里,还是做一个变量赋值,它都是把数组的值完整拷贝一份再复制给变量 //无论是一维数组,还是二维数组,还是给数组里的值赋值。它都不会改变
-
因数组是值拷贝,会造成性能问题,通常会建议使用slice,或数组指针
package main import "fmt" func test(x [2]int) { // &x fmt.Printf("x: %p ",&x) x[1] = 1000 } func main() { a := [2]int{} fmt.Printf("a:%p ",&a) test(a) // a:0x1104a058 // x: 0x1104a078 // [0 0] }
func printArr(arr *[5]int) { arr[0] = 10 } func main() { var arr1 [5]int printArr(&arr1) // [10 0 0 0 0] fmt.Println(arr1) }
-
通过len和cap返回数组长度
a := [2]int{} println(len(a),cap(a)) // 2 2
- 练习题1:求数组和
求数组[1,3,5,7,8]的所有元素和 package main import "fmt" func main() { // 练习题: var result int var sumList = [5]int{1, 3, 5, 7, 8} for i := 0; i < len(sumList); i++ { result += sumList[i] } fmt.Println(result) } 找出数组中和为指定两个元素的下标,比如[1,3,5,7,8]和为8的两个元素下标分别为(0,3)和(1,2) package main import "fmt" func main() { var sumList = [5]int{1, 3, 5, 7, 8} for index, v1 := range sumList { for temp, v2 := range sumList { if index < temp && v1+v2 == 8 { fmt.Println(index, temp) } } } }
- 求数组内随机数的和
package main import ( "fmt" "math/rand" "time" ) func sumArr(a [10]int) int { var sum int = 0 for _,value := range a { sum += value } return sum } func main() { // 设置随机数种子, 可以保证每次随机都是随机的 rand.Seed(time.Now().Unix()) var b [10]int for i:=0;i<len(b);i++ { // 生成一个0-1000随机数 b[i] = rand.Intn(1000) } sum := sumArr(b) fmt.Printf("sum=%d ",sum) }
-
找出数组中和为给定值的两个元素的下标,例如数组[1,3,5,8,7],找出两个元素之和等于8的下标分别是(0,4)和(1,2)
func testArr(arr [5]int, num int) { for i:=0;i<len(arr);i++ { for j:=i+1;j<len(arr);j++ { if arr[i]+arr[j] == num { fmt.Printf("(%d,%d) ",i,j) } } } } func main() { b := [5]int{1,3,5,8,7} testArr(b,8) }
2.切片
-
首先slice切片并不是数组或数组指针,它用过内部指针和相关属性引用数组片段,从而实现变长方案。
1.切片:切片是数组的一个引用,所以切片是引用类型,但自身是结构体,值拷贝传递 2.切片的长度可以改变,因此切片是一个可变数组 3.切片遍历方式和数组一样,可以用len求长度,表示可用元素数量,读写操作不能超过该限制。 4.cap可以求出slice最大扩张容量,不能超出数组限制。0 <= len(slice) <= len(array),其中array是slice引用的数组。。 5.切片的定义:var 变量名 []类型,比如 var str []string var arr []int。 6.如果 slice == nil,那么 len、cap 结果都等于 0。 最终: 切片是一个拥有相同类型元素的可变长度的序列,它基于数组类型做的一层封装,灵活度高,支持自动扩容,切片是一个引用类型,它的内部结构包含地址,长度和容量。切片一般用于快速地操作一块数据集合。
-
定义一个切片
package main import "fmt" //生成切片(slice) func main() { var a []string var b []int var c = []bool{false, true} fmt.Println(a) fmt.Println(b) fmt.Println(c) }
-
切片的长度和容量
- 切片拥有自己长度和容量,我们可以通过使用内置len函数求长度,使用内置cap()函数求切片容量。
-
基于数组切片
package main import "fmt" //生成切片(slice) func main() { //基于数组得到切片 a := [5]int{22, 23, 24, 25, 26} b := a[1:4] fmt.Println(b) //打印类型 fmt.Printf("%T ", b) }
-
make函数构造切片
- len长度,cap计算切片容量
package main import "fmt" //生成切片(slice) func main() { //make函数构造函数 //d为一个元素个数为5,容量为10的切片 d := make([]int, 5, 10) fmt.Println(d) fmt.Printf("%T ", d) //通过len()函数获取切片的长度 fmt.Println(len(d)) //通过cap()函数获取切片的容量 fmt.Println(cap(d)) //[0 0 0 0 0] //[]int //5 //10
-
面试题:
func main() { arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7} s1 := arr[2:] fmt.Println(s1)//[2 3 4 5 6 7] //修改 s1[0] = 100 //是view的操作(切片映射数组),不同于python,python切片相当于单复制一份表 fmt.Println(s1)//[100 3 4 5 6 7] fmt.Println(arr)//[0 1 100 3 4 5 6 7] s2 := arr[2:6] fmt.Println(s2) //[100 3 4 5] //前面不能取定死,可以从原数组往后面取(通过映射),超过了原数组长度会报错。但append不会报错 // 错误信息:invalid slice index 10 (out of bounds for 5-element array) s3 := s2[3:5] fmt.Println(s3) //[5 6] }
-
切片初始化
全局: var arr = [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} var slice0 []int = arr[start:end] var slice1 []int = arr[:end] var slice2 []int = arr[start:] var slice3 []int = arr[:] var slice4 = arr[:len(arr)-1] //去掉切片的最后一个元素 局部: arr2 := [...]int{9, 8, 7, 6, 5, 4, 3, 2, 1, 0} slice5 := arr[start:end] slice6 := arr[:end] slice7 := arr[start:] slice8 := arr[:] slice9 := arr[:len(arr)-1] //去掉切片的最后一个元素
-
切片原理:
-
切片的本质就是对底层数组的封装,它包含了三个信息:底层数组的指针、切片的长度(len)和切片的容量(cap)
-
举个例子,现在有一个数组
a := [8]int{0, 1, 2, 3, 4, 5, 6, 7}
,切片s1 := a[:5]
,相应示意图如下。
- 切片
s2 := a[3:6]
,相应示意图如下:
-
-
nil
- 切片之间不能直接比较,我们不能使用 == 操作符来判断两个切片是否含有全部相等元素,切片唯一合法的比较操作是和nil比较,一个nil值的切片并没有底层数组,一个nil值的切片的长度和容量都是0,但是我们不能说一个长度和容量为都是0的切片一定是nil.
package main import "fmt" //生成切片(slice) func main() { var a []int //声明int类型切片 var b = []int{} //声明并且初始化,会再底层创建数组与其对应 c := make([]int, 0) if a == nil { fmt.Println("a是一个nil") } fmt.Println(a, len(a), cap(a)) if b == nil { fmt.Println("b是一个nil") } fmt.Println(b, len(b), cap(b)) if c == nil { fmt.Println("c是一个nil") } fmt.Println(c, len(c), cap(c)) }
-
切片的赋值拷贝
- 下面的代码中演示了拷贝前后两个变量共享底层数组,对一个切片的修改会影响另一个切片的内容,这点需要特别
s1 := make([]int, 3) s2 := s1 s2[0] = 100 fmt.Println(s1)//[100 0 0] fmt.Println(s2)//[100 0 0]
- a赋值给了b,a和b共用一个底层数组,当我们把b[0]设置为100,打印出来a,b都更改
package main
import "fmt"
//生成切片(slice)
func main() {
//切片的赋值拷贝
a := make([]int, 3)
b := a
b[0] = 100
fmt.Println(a)
fmt.Println(b)
}
//[100,0,0]
//[100,0,0]
// 访问底层数组元素指针
s:= []int{0,1,2,3}
p := &[2]
*p += 100
fmt.Println(s) // 102
[][]T
,元素类型为[]T
func main() {
data := [][]int {
[]int{1,2,3},
[]int{100,200},
[]int{11,22,33,44},
}
// [[1 2 3] [100 200] [11 22 33 44]]
fmt.Println(data)
}
- 可直接修改struct array /slice 成员
package main
import "fmt"
func main() {
d := [5]struct{
x int
}{}
d[1].x = 10
d[2].x = 20
// [{0} {10} {20} {0} {0}]
//
// 0x11010320,0x11010320
fmt.Println(d)
fmt.Printf("%p,%p
",&d,&d[0])
}
- 切片的遍历
package main
import "fmt"
func main() {
//切片的遍历
//索引遍历
a := []int{1, 2, 3, 4, 5}
for i := 0; i < len(a); i++ {
fmt.Println(i, a[i])
}
//for...range遍历
for index, value := range a {
fmt.Println(index, value)
}
}
- 切片的扩容
- append 函数将元素值追加到数组的最后并返回数组
- 当数组容量不满足继续放入元素,会发生扩容现象。切片numSlice的容量按照1,2,4,8,16这样规则自动进行扩容,每次扩容都是扩容前2倍
package main
import "fmt"
//生成切片(slice)
func main() {
//切片要初始化后才能使用
var a []int //此时并没有申请内存
for i := 0; i < 10; i++ {
a = append(a, i) //需要变量来接收,因为你不知道原来数组是否会扩容
fmt.Printf("%v len:%d cap:%d ptr:%p
", a, len(a), cap(a), a)
}
}
// [0] len:1 cap:2 ptr:0x11010080
// [0 1] len:2 cap:2 ptr:0x11010080
// [0 1 2] len:3 cap:4 ptr:0x110100d0
// [0 1 2 3] len:4 cap:4 ptr:0x110100d0
// [0 1 2 3 4] len:5 cap:8 ptr:0x1100e3a0
// [0 1 2 3 4 5] len:6 cap:8 ptr:0x1100e3a0
// [0 1 2 3 4 5 6] len:7 cap:8 ptr:0x1100e3a0
// [0 1 2 3 4 5 6 7] len:8 cap:8 ptr:0x1100e3a0
// [0 1 2 3 4 5 6 7 8] len:9 cap:16 ptr:0x1100c280
// [0 1 2 3 4 5 6 7 8 9] len:10 cap:16 ptr:0x1100c280
- append支持一次性添加多个元素
package main
import "fmt"
//生成切片(slice)
func main() {
//切片要初始化后才能使用
var a []int //此时并没有申请内存
a = append(a,1,2,3,4,5)
b:=[]int{11,12,13}
a = append(a,b...)//将切片b加入到里面必须使用...
fmt.Println(a)
}
[1 2 3 4 5 11 12 13 14]
-
切片区别python对比go
#python a = [1,2,3,4,5,6] t = a[0:4] t[0] = 100 print(t)#[100, 2, 3, 4] print(a)#[1, 2, 3, 4, 5, 6] p = a[0:5] print(p)#[1, 2, 3, 4, 5] p.append(1000) print(p)#[1, 2, 3, 4, 5, 1000] print(a)#[1, 2, 3, 4, 5, 6] #python切片后赋值数据,做更改,增添,都不会影响源数据以及以后切片数据,像深拷贝 #go func main() { arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7} s1 := arr[2:] fmt.Println(s1)//[2 3 4 5 6 7] //修改 s1[0] = 100 //是view的操作(切片映射数组),不同于python,python切片相当于单复制一份表 fmt.Println(s1)//[100 3 4 5 6 7] fmt.Println(arr)//[0 1 100 3 4 5 6 7] s2 := arr[2:6] fmt.Println(s2) //[100 3 4 5] //前面不能不取定死,可以从原数组往后面取(通过映射),超过了原数组长度会报错。 s3 := s2[3:5] fmt.Println(s3) //[5 6] } #go语言,切片相当于映射数组,当切片更改数据,源数组也会更改,后续切片数据也会更改。
-
总结:
- 首先,如果新申请容量(cap) 大于2倍的旧容量(old.cap),最终容量(newcap)就是新申请的容量(cap) - 否则判断,如果旧切片长度小于1024,则最终容量(newcap)就是旧容量的两倍,即(newcap= doublecap) - 否则判断,如果旧切片长度大于等于1024,则最终容量(newcap)从旧容量(old.cap)开始循环增加原来的1/4,即(newcap=old.cap.for{newcap += newcap/4})直到最终容量(newcap)大于等于新申请的容量(cap),即(newcap >= cap) - 如果最终容量(cap)计算值溢出,则最终容量(cap) 就是新申请容量(cap) !需要注意的是:切片扩容还会根据切片中元素的类型不同而做不同的处理,比如int和string类型的处理方式就不一样。
-
copy函数应用
package main import "fmt" func main() { //切片的copy a := []int{1, 2, 3, 4, 5} b := make([]int, 5, 5) c := b//c为b的赋值,c指向的地址与b一样 copy(b, a)//b切片为从a拷贝过来的,指向新的地址 b[0] = 100 fmt.Println(a) fmt.Println(b) fmt.Println(c) } //所以当b第0个元素发生变化,c也跟着变化,而a不发生变化 //[1 2 3 4 5] //[100 2 3 4 5] //[100 2 3 4 5]
-
copy 函数在两个slice间复制数据,复制长度以len小的为准,两个slice可指向同一底层数组,允许元素区间重叠
func main() { data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} fmt.Println("array data : ", data) s1 := data[8:] s2 := data[:5] fmt.Printf("slice s1 : %v ", s1) fmt.Printf("slice s2 : %v ", s2) // 将s1元素拷贝到s2上,并覆盖 copy(s2, s1) fmt.Printf("copied slice s1 : %v ", s1) fmt.Printf("copied slice s2 : %v ", s2) fmt.Println("last array data : ", data) } // array data : [0 1 2 3 4 5 6 7 8 9] // slice s1 : [8 9] // slice s2 : [0 1 2 3 4] // copied slice s1 : [8 9] // copied slice s2 : [8 9 2 3 4] // last array data : [8 9 2 3 4 5 6 7 8 9]
-
切片resize,调整大小
var a = []int{1,3,4,5} fmt.Printf("%v,%v ",a,len(a)) b := a[1:2] fmt.Printf("%v,%v ",b,len(b)) c := b[0:3] fmt.Printf("%v,%v ",c,len(c)) // [1 3 4 5],4 // [3],1 // [3 4 5],3
-
切片元素的删除
//从切片a中删除"青岛" package main import "fmt" func main() { //切片删除元素 a := []string{"北京", "上海", "青岛", "深圳"} a = append(a[0:2], a[3:]...) fmt.Println(a) } //[北京 上海 深圳]
练习题
//1.请写出下面代码的输出结果
func main() {
var a = make([]string,5,10)//此时切片容量10,里面已经有5个空格
for i:=0;i<10;i++{
a.append(a,fmt.Sprintf("%v",i))//当添加元素0,1,2,3,4此时a容量已经满了,而append恰好能对其进行扩容
}
fmt.Println(a)
}
//最后输出结果:
//[ 0 1 2 3 4 5 6 7 8 9]
//2.排序题
package main
import (
"fmt"
"sort"
)
func main() {
//切片删除元素
// a := []string{"北京", "上海", "青岛", "深圳"}
// a = append(a[0:2], a[3:]...)
// fmt.Println(a)
var a = [...]int{3, 7, 8, 1, 9}//a为int类型数组
sort.Ints(a[:])//接收参数为int类型切片。其实a[:]得到的切片,指向底层数组a,对切片排序,相当于对数组a排序
fmt.Println(a)
}