本章讲解了3方面的内容
1. 数组
2. 切片
3. map
一、数组
1. 数组的定义方式
var arr1 [5]int arr2 := [3]int{1,3,1)">5} arr3 := [...]2,1)">4,1)">6,1)">8,1)">10} var grid [4][ fmt.Println(arr1,arr2,arr3) fmt.Println(grid)
输出结果:
[0 0] [1 3 5] [2 4 6 8 ] [[0]]
2. 数组遍历的方式:
for i := 0; i < len(arr2); i++ { fmt.Println(arr2[i]) }
for i,v := range arr3 { fmt.Println(i,v) }
rang方式遍历的三种
// 第一种: 只获取数组的下标 for i := range arr3 { fmt.Println(arr3[i]) } 第二种: 获取数组的下标和值 range arr3 { fmt.Println(i,v) } 第三种: 只获取值 for _,1)"> range arr3 { fmt.Println(v) }
3. arr[5] 和 arr[3]是不同的类型
func printArray(arr [) { arr[0] = 100 range arr { fmt.Println(v) } } func main() { var arr1 [ arr2 := [} arr3 := [...]} var grid [ fmt.Println(arr1,arr3) fmt.Println(grid) printArray(arr1) printArray(arr3) 下面这个打印会报错,因为arr2是3个容量的数组 // printArray(arr2) }
这里传递arr1 和arr3过去的时候,可以正常打印数组,但是传递arr2过去的时候,会报异常.
cannot use arr2 (type [int) as type [int in argument to printArray
原因是: [3]int 和[5]int是不同的类型
4. 数组的传递是值拷贝类型.
func printArray(arr [ range arr { fmt.Println(v) } } func main() { 定义数组的三种方法 var arr1 [} 证明数组是值拷贝类型 fmt.Println("证明数组是值拷贝类型") printArray(arr1) printArray(arr3) fmt.Println(arr1,arr3) }
我们在函数printArray中修改数组第一个元素的值是100. 然后打印. 在打印原数组. 结果如下:
证明数组是值拷贝类型 100 0 4 6 8 [10]
5. 如何实现数组的地址传递呢? 使用指针
func printArray1(arr *[) { arr[0] = 100 如何让数组实现值拷贝呢) printArray(&arr1) fmt.Println(arr1) }
结果
如何让数组实现地址拷贝呢? 0100 0]
注意:
a. 在方法printArray中参数接收是一个地址类型. 并且传递参数的时候也传递一个地址类型
b. 在arr[0] = 100赋值的时候,无需获取地址的值. 直接给指针类型的数组赋值即可
二、切片slice
1. 什么是slice
func main() { 定义一个数组 arr1 := [...]0,1)">5,1)">7} fmt.Println(arr1[2:6] = ",arr1[2:6]) fmt.Println(arr1[2:] = 2:]) fmt.Println(arr1[:6] = arr1[:] = ,arr1[:] ) }
结果
arr1[6] = [] arr1[2:] = [5 ] arr1[:] arr1[:] = [7]
a. 通过arr[a:b]方式获取的值就是slice
b. slice是数组的一个视图. slice不是值传递的. slice内存储的是数组的地址
2. 验证slice不是值传递
func updateSlice(s []) { s[0] =100 } func main() { } s1 := arr1[:] s2 := arr1[:] fmt.Println(after updateSlice(s1)) updateSlice(s1) fmt.Println(s1) updateSlice(s2) fmt.Println(s2) fmt.Println(arr1) }
结果:
after updateSlice(s1) s1 = [] s2 = [] arr1 = [7]
可以看到很有趣的现象. s1 的第一个元素被改100. 同时影响了s2的第3个元素. s2的第一个元素修改后,s1,s2同时都影响了数组arr1.
由此可见: s1,s2 都是指向的数组的地址
将s传递给函数printSlice,在打印原来的s. 发现s的值变化了. 说明,切片传递是地址传递,而不是值传递.
可是上一章讲指针的时候,不是说go中只有值拷贝一种类型么? 那么为什么slice不是值拷贝呢? 因为slice是数组的一个视图. (可以理解为,他取的是数组中指定元素的地址. 所以,slice不是值拷贝,他的元素是地址)
思考: 在上面将数组的第5个问题,数组如何作为一个地址拷贝的方式作为参数传递到方法里面呢? 我们的做法是: 将数组作为一个指针传过去. 学习了slice,我们可以换一种方法.
func printArray1(arr []int) { arr[ range arr { fmt.Println(v) } } func main() { 定义数组的三种方法 var arr1 [} fmt.Println(arr1,arr3) 如何让数组实现地址拷贝呢? fmt.Println(如何让数组实现地址拷贝呢?方法2) printArray1(arr3[:]) fmt.Println(arr3) }
结果:
[] 如何让数组实现地址拷贝呢?方法2 10]
1. printArray1(arr []int)接收参数的时候,使用arr []int就是切片. 如果是arr [5]int就是数组.
2.如何将一个数组传给接收切片的方法呢? 如下:printArray1(arr3[:])
是不是很巧妙. 这种方法就不用使用& *了
3. reslice
我们对数组可以进行slice操作,对slice还可以继续进行slice操作,就是reslice
package main import fmt func updateSlice(s []} s2 := arr1[:] fmt.Println(reslice操作) fmt.Println(slice 后: ] fmt.Println(reslice 后: :] fmt.Println(再次reslice 后: updateSlice(s2) fmt.Println(再次reslice 然后修改第一个元素的值 后: 原数组:输出结果:reslice操作 slice 后: [] reslice 后: [4] 再次reslice 后: [] 再次reslice 然后修改第一个元素的值 后: [] 原数组: [7]我们看到对slice再次进行slice,就和对数组slice是类似的. 最终指向的都是数组中的地址
updateSlice(s2): 这个操作就比较有意思了. 我们看到,对二次reslice后的数组,修改他的第一个元素的值. 然后在打印原数组,发现,原数组的值也修改了. 再次证明,slice是数组的一个视图,最终存储的是数组的地址. 一旦被修改,会影响原数组.
4. slice扩展
看下面这个例子:
] s2 := s1[3:] fmt.Println(s1 =结果:
s1 = [5],s2 = [6]很奇怪: s1取的数组的4个元素. s2想要取s1的3-5个元素,可s1只有4个元素呀,s2还成功取出来了. 这是怎么回事呢?
为什么会这样呢?我们来分析一下
最开始,数组中的值是0,1,2,3,4,5,6,7
当我们取s1切片的时候,s1是底层数组的一个视图,他取值是从2-6,虽然,他只取了4个元素,但是因为他是对底层的一个视图,所以他是可以看到底层后面的元素的.
然后取s2切片. 我们发现他去的是s1下标的3-5个元素. 虽然s1只有4个元素,因为他是可以看到底层其他后面的元素的,所以,s2能够把数组中第5个元素取出来.
那么取出来对应的元素值时多少呢?对应到底层数组,他的值就是5和6
那么s1只有4个元素,为什么能够看到第五和第六个元素呢?
原来slice的底层结构是这么定义的
slice定义了3个对象.
ptr是一个指针,记录的是切片的第一个地址(下面的方格代表数组),
len是切片中元素的个数,如果获取超过元素个数的值,会报下标越界
cap: 是capacity,容量. 他存储的是从ptr指向的数组的元素开始,一直到数组结束的元素.
所以,s1[3:5]获取的是cap中的数据. 正常返回.
总结: 1. slice 可以向后扩展,但不能向前扩展. slice中cap的起始元素是从ptr开始
2. s[i]不可以超越len(s),向后扩展可以超越len(s),但不可以超过底层数组cap(t)
5. 向slice添加元素
定义一个数组--为什么这样定义就能够被识别为一个数组呢 arr1 := [...]6] 2,5 s2 := s1[5] 4,5 fmt.Println(s2 = arr1 = 10) s3 = = append(s3,1)">11) s4 = = append(s4,1)">12) s5 = = s5[s6 =结果
s2 = [6],arr1 = [] s3 = [10],1)">] s4 = [10 11],1)">] s5 = [11 12],1)">10]
s2的值是5,6 原数组:[0 1 2 3 4 5 6 7]
s3在s2基础上增加了元素10,我们发现原数组变为:[0 1 2 3 4 5 6 10]. 这说明,append也是对slice的cap进行处理的
s4在s3基础上增加了元素11, 我们发现原数组较上一个没变化:[0 1 2 3 4 5 6 10]. 原因是什么呢?s4 增加的数组已经超过了cap范围,这时会重新开辟一块新的空间. 因为这块空间已经保存不下这个数据了.
s5的变化同s4,虽增加了一个元素,但是,原数组无变化
s5第二次打印可以证明,数组已经重新开辟了一块新的空间. 因为我对s5的第一个元素值进行了修改,但修改后数组的值没有变化,表明此时s5的切片指向的地址已经不是原来数组的地址了.
下面来看看这几个元素的len 和cap是如何变化的
= append(s2,s3,len(s3)= append(s3,s4,len(s4)= append(s4,s5,len(s5)
s2 = [7],len(s2) 3 s3 = [ s4 = [ s5 = [6我们打印出了每一次变化后len的值和cap的值. 从s4开始,增加了一个元素,原来的地址已经容纳不了这么多数据了,于是新开辟了一块空间. 元素的len是4,cap是当前数组的2倍. 已经不是原来的数组了
总结:
1. 添加元素时,如果超越capacity,那么会重新开辟一块更大的底层数组,把slice切片值copy过去. 原来的数组,如果有人用就依然存在,如果没人用就会被垃圾回收掉.
2. 由于是值传递,所以append必须要有一个返回值接收. 原因是: 当append的容量超过原来数组的时候,会新开辟一块空间,新开辟的空间需要有新的参数来接收.
7.slice的copy
} func printSlice(s []) { fmt.Printf(len=%d,cap=%d \nvar sli [] 这样定义就是一个zero value,他的值时nil 给sli赋值. 因为是zero value,可以直接复制 0; i < 200 ;i ++ { printSlice(sli) sli = append(sli,1)">2*i+1) } }结果
View Codelen= len=87,1)">9,1)">1610,1)">11,1)">12,1)">13,1)">14,1)">15,1)">16,1)">17,1)">3218,1)">19,1)">20,1)">21,1)">22,1)">23,1)">24,1)">25,1)">26,1)">27,1)">28,1)">29,1)">30,1)">31,1)">32,1)">33,1)">6434,1)">35,1)">36,1)">37,1)">38,1)">39,1)">40,1)">41,1)">42,1)">43,1)">44,1)">45,1)">46,1)">47,1)">48,1)">49,1)">50,1)">51,1)">52,1)">53,1)">54,1)">55,1)">56,1)">57,1)">58,1)">59,1)">60,1)">61,1)">62,1)">63,1)">64,1)">65,1)">12866,1)">67,1)">68,1)">69,1)">70,1)">71,1)">72,1)">73,1)">74,1)">75,1)">76,1)">77,1)">78,1)">79,1)">80,1)">81,1)">82,1)">83,1)">84,1)">85,1)">86,1)">87,1)">88,1)">89,1)">90,1)">91,1)">92,1)">93,1)">94,1)">95,1)">96,1)">97,1)">98,1)">99,1)">100,1)">101,1)">102,1)">103,1)">104,1)">105,1)">106,1)">107,1)">108,1)">109,1)">110,1)">111,1)">112,1)">113,1)">114,1)">115,1)">116,1)">117,1)">118,1)">119,1)">120,1)">121,1)">122,1)">123,1)">124,1)">125,1)">126,1)">127,1)">128,1)">129,1)">256130,1)">131,1)">132,1)">133,1)">134,1)">135,1)">136,1)">137,1)">138,1)">139,1)">140,1)">141,1)">142,1)">143,1)">144,1)">145,1)">146,1)">147,1)">148,1)">149,1)">150,1)">151,1)">152,1)">153,1)">154,1)">155,1)">156,1)">157,1)">158,1)">159,1)">160,1)">161,1)">162,1)">163,1)">164,1)">165,1)">166,1)">167,1)">168,1)">169,1)">170,1)">171,1)">172,1)">173,1)">174,1)">175,1)">176,1)">177,1)">178,1)">179,1)">180,1)">181,1)">182,1)">183,1)">184,1)">185,1)">186,1)">187,1)">188,1)">189,1)">190,1)">191,1)">192,1)">193,1)">194,1)">195,1)">196,1)">197,1)">198,1)">199,1)"> Process finished with exit code 0从打印结果可以看出. 1) 没有个切片赋初始值,他的默认长度是0,因此给切片append不会报错. 2) 每次cap容量不够的时候,增长是按照当前元素值的2倍增长的.
%v,len=%d,s,cap(s)) } func main() { s1 := []} s2 := make([]int,1)">) s3 := make([]) printSlice(s1) printSlice(s2) printSlice(s3) copy(s2,s1) printSlice(s2)
copy(s3,s1)
printSlice(s3)}
结果:
[4],len= [0],1)">2],1)">21) 定义切片可以使用的方法
- var s1 []int. 初始值是nil,len是0 cap是0,可直接append
- var s2 = []int{1,4},有4个元素,len是4,cap是4
- var s3 = make([]int,16),有16个元素,初始值都是0,len是16,cap是16.
2) copy是将s1 copy给s2.
3) 如果,目标容量小于原容量,则只copy目标容量个数的值
8. slice的delete
slice不能直接delete,也就是没有提供delete方法,那么如果想要删除中间的某一个元素,怎么办呢?
删除元素值4) s4 := append(s2[:3],s2[:]...) printSlice(s4) }
结果
[ 删除s2的元素值4 [16s2的值是[1 2 3 4 0 0 0 0 0 0 0 0 0 0 0 0],len=16,cap=16
删除中间的元素4,怎么操作呢? 使用s4 := append(s2[:3],s2[4:]...)
注意:的一点是append的第二个参数是一个数组,而不是切片,那么如何将切片转换为数组呢? 在切片后面加三个点...
三、map
map定义的三种方式
// 第一种 m := map[string]string { aaa":111bbb222ccc333ddd444 第二种 m1 := make(map[int) 使用make,创建了一个empty map 第三种 var m2 map[int 使用var,创建了一个nil. nil和java中的null不同的是:nil可以参与运算.对null对象进行运算报异常 fmt.Println(m,m1,m2)重点看一下第二种和第三种方式有何不同.
方式二: 创建了一个empty map
方式三: 创建了一个nil 的map
虽然如此,二者都可以参与运算. go中的nil和java中的null不同的是: nil可以参与运算. 对null对象进行运算报异常
二.循环遍历使用range
循环遍历 for k,1)"> range m { fmt.Println(k,v) map是无需的,每次输出可能不一样 }
三.判断key是否存在
判断key是否存在 if v,ok := m["aaa"]; ok { fmt.Println(v,ok) } else { fmt.Println(ok) }v,ok := m["aaa"]用来判断key是否存在
四. 删除一个元素
delete(m,1)">")