Go语言学习笔记05--切片slice与字典map

FrankAdd · · 1422 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

1.数组案例 //统计输入20个字符中字母出现的个数案例 func checkNumFromInput(){     inputCharArr := [20]byte{},checkNum := [26]byte{}     //输入     for i:=0; i<20; i++{         fmt.Scanf("%c",&inputCharArr[i]);     }     //核验     for j:=0; j<20; j++{         checkNum[inputCharArr[j]-'a']++;     }     //输出     for k:=0; k<len(checkNum); k++{         if checkNum[k] != 0{             fmt.Printf("字母%c,出现的次数是%d次\n",'a'+k,checkNum[k]);         }     } } //模拟双色球摇号,6个不重复(1~33)红球,和一个蓝球 func getTicketArray () [7]int{     finalArr := [7]int{}     rand.Seed(time.Now().UnixNano())     //一轮一个随机红球     for i:=0; i<6; i++{         temp := rand.Intn(33)+1,flag := true         //判重         for j:=0; j<i; j++{             if temp == finalArr[j]{                 i--,flag = false                 break             }         }         //判是否加         if(flag){             finalArr[i] = temp         }     }     //随机蓝球     finalArr[6] = rand.Intn(33)+1     return finalArr } 2.二维数组     go语言中的二维数组和传统c语言中的二维数组大同小异,还是需要注意声明和赋值的语法就行     至于数组的特性,仍旧是第一维度表示行,第二维度表示列。数组访问也仍旧需要行列两个维度的参数。     eg:         //var arr [3][5]int = [3][5]int{{1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7}};         var arr [3][5]int = [3][5]int{};         for i:=0; i<3; i++{             for j:=0; j<5; j++{                 arr[i][j] = rand.Intn(10);             }         }         fmt.Println(arr);//[[1 7 7 9 1] [8 5 0 6 0] [4 1 2 9 8]]         fmt.Println(arr[0][1]);//7 3.切片slice     与传统语言相比,go语言中的数组是一个长度固定的固有结构,因此对于数组的所有操作是不会影响到原数组的。     这样统一的规定虽然避免了很多情况下对于原数组的误操作,但是数组大多数情况下是有必要发生修改的,     因此go语言提出了切片(Slice)的概念,切片从某种意义上来说可以认为是数组的一种可修改的表现方式。         var 切片名 []数据类型 = []数据类型{...}     eg:         var slice1 []int = []int{1,2,3,4,5,...}     如果单独查看切片的语法可能会觉得有些奇怪,那么直接将切片的语法和数组的语法相对比结合就能看到些端倪     eg:         var slice1 []int = []int{1,2,3,4,5,...}         var array [5]int = [5]int{1,2,3,4,5}     非常明显看到,切片可以认为其实就是长度不固定的数组。其定义语法与数组定义方式几乎相同。     ps:         数组数据存栈区,切片数据存堆区     (1)切片的操作         1)make方法与切片的自动推导类型             如果每一次定义切片都采用标准声明方式,会显得代码十分冗余,因此提出了make方式                 切片名 := make(切片类型,切片长度);             eg:                 slice2 := make([]int, 5);                 slice2[0] = 10;                 slice2[1] = 11;                 ...             需要说明的是这里的长度并不是说切片就只能有5个长度,而是暂时只分配5个容量             之后会随着需求不断对切片进行容量扩充。             ps:切片长度,即切片中实际存储了内容的部分             ps:切片容量,是切片中用于系统分配存储空间的标尺         2)append方法与切片扩展             前面提到过切片是一种类似于可以被修改的数组,因此go语言提供了append方法对切片进行扩容                 切片名 = append(切片名,扩展内容1,扩展内容2,...)             eg:                 slice3 := []int{1,2,3,4,5};                 fmt.Println(slice3);//[1,2,3,4,5]                 slice3 = append(slice3,11,22,33,44,55);                 fmt.Println(slice3);//[1,2,3,4,5,11,22,33,44,55]             ps:go语言中的append方法其意义为【扩容】,而不是修改,                 因此append扩充的切片内容会在已知内容之后                 eg:                     slice4 := make([]int, 5);//[0,0,0,0,0]                     slice4 = append(slice4,1,2,3);//[0,0,0,0,0,1,2,3]         3)cap方法-切片容量与切片长度             cap(切片名)方法能够返回切片容量             len(切片名)方法能够返回切片长度             其区别就是:                 ·容量必然大于等于长度,因为系统必须为内容分配出足够的存储空间。                 ·只要添加内容长度必然增加,而容量却不一定增加                 ·如果长度超过了容量,切片才会对容量进行扩展,而且每次扩展都是上次的倍数。                 eg:                     slice5 := []int{0,0,0,0,0};            //[0,0,0,0,0]                     fmt.Printf("长度:%d",len(slice5));    //5                     fmt.Printf("容量:%d",cap(slice5));    //5                     slice5 = append(slice5, 1,2,3);        //[0,0,0,0,0,1,2,3]                     fmt.Printf("长度:%d",len(slice5));    //8                     fmt.Printf("容量:%d",cap(slice5));    //10                     slice5 = append(slice5, 4);            //[0,0,0,0,0,1,2,3,4]                     fmt.Printf("长度:%d",len(slice5));    //9                     fmt.Printf("容量:%d",cap(slice5));    //10             ps:                 如果切片的长度扩充超过了容量扩充的2倍,那么本次容量扩充就会以长度扩充为标准                 eg:                     slice6 := []int{0,0,0,0,0}                //[0,0,0,0,0]                     fmt.Printf("长度:%d",len(slice6));        //5                     fmt.Printf("容量:%d",cap(slice6));        //5                     slice6 = append(slice6, 1,2,3,4,5,6,7,8);    //[0,0,0,0,0,1,2,3,4,5,6,7,8]                     fmt.Printf("长度:%d",len(slice6));        //13                     fmt.Printf("容量:%d",cap(slice6));        //13                 上面这个例子就能够明显看出,本来切片slice6的一次扩容应当是扩展2倍,即变成10                 但是由于本次内容长度的扩展已经超过了10,所以容量的扩充就会以长度为最低标准,也是13                 并且下次在扩展的时候,倍数基准就会以13这个数为基准来扩容了                     slice6 = append(slice6, 10,11);    //[0,0,0,0,0,1,2,3,4,5,6,7,8,10,11]                     fmt.Printf("长度:%d",len(slice6));        //15                     fmt.Printf("容量:%d",cap(slice6));        //26             ps:                 如果整体数据没有超过1024byte,每次扩展为上次的倍数                 如果整体数据超过了1024byte,每次扩展为上次的1/4             ps:                 有个疑问就是当切片没能被初始化,而是通过make方法只创建出结构时                 超过2倍的单次扩容总是会让容量趋向于大于长度的一个偶数值,而不是倍数增长                 目前这个问题的具体原因还没能被我理解                 期望随着学习的深入,能够搞懂切片的创建方式对容量有什么不同的影响。                 sliceTemp := make([]int, 5);                 sliceTemp = append(sliceTemp, 1,2,3,4,5,6,7,8);                 fmt.Printf("长度:%d",len(sliceTemp));        //13                 fmt.Printf("容量:%d",cap(sliceTemp));        //14         4)切片快速遍历             go语言中的切片和数组除了不能修改之外,可以认为切片的标准访问方法操作与数组相同             eg:                 slice7 := []int{1,2,3,4,5};                 for i,v := range slice7{                     fmt.Printf("序号:%d,值:%d\n",i,v);                 }             ps:但是一定一定注意,切片在打印的时候只能以长度为标准,而不能以容量作为输出标准。                因为go语言不会对切片容量中的数据初始化,而是只初始化长度内的数据                eg:                        slice8 := []int{0,0,0,0,0}; //[0,0,0,0,0]                        slice8 = append(slice8,1,2);//[0,0,0,0,0,1,2] 此时长度为7,容量为10                        //合法操作                        for i:=0; i<len(slice8); i++{                            fmt.Printf("%d",slice8[i]);                        }                        //违法操作                         for i:=0; i<cap(slice8); i++{                            fmt.Printf("%d",slice8[i]);                        }     (2)切片截取         事实上在很多情况下,go语言的开发过程中大都使用切片来代替数组使用。         而切片的截取从某种意义上来说,go语言的切片截取与其他所有传统语言的操作都不太相同。         eg:             baseSlice := []int{1,2,3,4,5,6};             subSlice := baseSlice[1:4];          1)切片截取              go语言的切片截取不会对原切片造成影响                   eg:                       fmt.Println(subSlice);//[2,3,4]                     fmt.Println(baseSlice);//[1,2,3,4,5,6]             因为go语言切片截取是从原有的切片中“放大”一部分,并不会真的从原切片中将数据剪切出来         2)切片截取的操作             但是对截取切片的操作会对原切片造成影响,因为子切片和切片本身来讲都是同一个东西(内存地址)                 eg:                     fmt.Printf("%p",baseSlice);//0xc00008a000                     fmt.Printf("%p",subSlice);//0xc00008a008                     subSlice[1] = 100;                     fmt.Println(subSlice);//[2,100,4]                     fmt.Println(baseSlice);//[1,2,100,4,5,6]         3)切片截取的“阈值”与扩展             在对切片进行截取的时候,实际上还存在第三个参数:容量。                 切片[其实下标:结束下标:子切片容量]             其表示的含义是对切片截取后,生成的子切片最大容量是多少。             由于子切片是原切片的“放大”操作,实际上是二位一体的东西,             子切片的容量必须小于等于原切片的容量!                 eg:                     baseSlice := []int{1,2,3,4,5,6};                     baseSlice = append(baseSlice, 1000);                     fmt.Printf("%d",len(baseSlice));//原长度:7                     fmt.Printf("%d",cap(baseSlice));//原容量:12                     -------------------------------------------                     -------------------------------------------                     subSlice := baseSlice[1:4:3];                     fmt.Println(subSlice);                     fmt.Printf("%d",len(subSlice));                     fmt.Printf("%d",cap(subSlice));                     错误,【容量】必须大于等于【结束下标】                     -------------------------------------------                     subSlice := baseSlice[1:4:5];                     fmt.Println(subSlice);//[2,3,4];                     fmt.Printf("%d",len(subSlice));//切片长度:3                     fmt.Printf("%d",cap(subSlice));//切片容量:4                     -------------------------------------------                     subSlice := baseSlice[1:4:10];                     fmt.Println(subSlice);//[2,3,4];                     fmt.Printf("%d",len(subSlice));//切片长度:3                     fmt.Printf("%d",cap(subSlice));//切片容量:9                     -------------------------------------------                     subSlice := baseSlice[1:4:13];                     fmt.Println(subSlice);                     fmt.Printf("%d",len(subSlice));                     fmt.Printf("%d",cap(subSlice));                     错误,【容量】不得超过【原切片的容量】     (3)切片追加与拷贝         1)append方法             append追加后内存地址可能发生变化,因为旧的容量不够用。             这可能对于指针传递时产生一些“旧变,新不变”的问题。             eg:                 slice := []int{};                 /*                     这意味着这两个变量指向同一块内存地址,换句话说                     这两个变量都指向了这个空切片                 */                 temp := slice;                               fmt.Printf("%p\n",slice);    //0x1181f88                 fmt.Printf("%p\n",temp);    //0x1181f88                  fmt.Println(temp);            //[]                 /*                     此时切片的容量增加,因为原有的内存地址处的切片已经放不下追加的内容                     因此go语言会自动寻找能够放下追加内容的切片的合适位置                     因此在slice追加内容后,slice指向了一个新的内存地址                     而temp还仍然指向着之前的内存地址 (即值传递,传递的内容是一个地址而已)                                          所以,追加append操作过程中如果出现了赋值传递                     那么小心传递的内容不会跟随传递后的内容变化而一同变化,那只是一张快照而已。                     */                 slice = append(slice, 1,2,3);                 fmt.Printf("%p\n",slice);    //0xc00008e000                 fmt.Printf("%p\n",temp);    //0x1181f88                 fmt.Println(temp);            //[]         2)copy方法             go语言中数组切片的copy方法,非常类似于JS中的数组截取方法subString                 copy(desSlice目标切片[起始下标:结束下标], resSlice原切片[起始下标:结束下标]);             对于拷贝范围是可选部分,允许不写,而如果不写默认从起始位置开始拷贝,内容是全部原切片             eg:                 slice := []int{1,2,3,4,5};                 slice2 := make([]int, 5);                 //默认拷贝全部                     copy(slice2, slice);                     fmt.Println(slice2);//[1,2,3,4,5]                 //指定拷贝范围,但起始结束都不写,也是拷贝全部                     copy(slice2, slice[:]);                     fmt.Println(slice2);//[1,2,3,4,5]                 //指定拷贝范围,起始不写,默认从起始拷贝                     copy(slice2, slice[:3]);                     fmt.Println(slice2);//[1,2,3,0,0]                 //指定拷贝范围,结束不写,默认拷贝到结束                     copy(slice2, slice[2:]);                     fmt.Println(slice2);//[3,4,5,0,0]                 //指定拷贝范围,指定拷贝到范围                     copy(slice2[1:], slice[2,4]);                     fmt.Println(slice2);//[0,3,4,0,0]             ps:对于拷贝切片操作来说,目标切片的长度是必须能够存放下要拷贝的内容的                只要长度超过要拷贝的内容,那么多长都无所谓,没有限制说必须小于原切片。                但是若目标切片的长度小于要拷贝的内容,则会造成拷贝内容的丢失                eg:                        slice := []int{1,2,3,4,5};                     slice2 := make([]int, 2);                     copy(slice2, slice);                     fmt.Println(slice2);//[1,2]             ps:目标拷贝切片和原切片是两个完全没有任何关联的内容,一处修改另一处不会跟随变化                这与切片拷贝完全不是一回事。                eg:                        slice := []int{1,2,3,4,5};                     slice2 := make([]int, 5);                     copy(slice2, slice);                     slice2[2] = 100;                     fmt.Println(slice1);//[1,2,3,4,5]                     fmt.Println(slice2);//[1,2,100,4,5]     (4)切片传参         切片传参是地址传递,换句话说就是内部修改外部跟随变化,与go语言中的数组是一个明显的不同。             eg:             func test(slice []int){                 slice[2] = 100;             }             func main() {                 slice := []int{1,2,3,4};                 test(slice);                 fmt.Println(slice);//[1,2,100,4]             }         但切片传参后若内容追加append,则外部不跟随变化(内容追加后内存发生变更)         eg:             func test(slice []int){                 slice = append(slice, 100);             }             func main() {                 slice := []int{1,2,3};                 test(slice);                 fmt.Println(slice);//[1,2,3]             }         总之,修改没问题,但追加就会出问题。当然将追加后的内容作为返回值再返回出来则一定不会出问题。     (5)切片案例:猜数字         func splitNumberToSlice(num int) []int{             numGe := num%10;             numShi := num%100/10;             numBai := num/100;             return []int{numBai,numShi,numGe};         }         func main() {             rand.Seed(time.Now().UnixNano());             pivotSlice := splitNumberToSlice(rand.Intn(899)+100);             var userNum int;             for{                 fmt.Println("请输入一个三位数:");                 fmt.Scan(&userNum);                 if userNum>=100&&userNum<=999{                     userSlice := splitNumberToSlice(userNum);                     flag := 0;                     fmt.Println("从左到右:");                     for i:=0; i<3; i++{                         if userSlice[i]<pivotSlice[i]{                             fmt.Printf("第%d位小了\n",i+1);                         }else if userSlice[i]>pivotSlice[i]{                             fmt.Printf("第%d位大了\n",i+1);                         }else{                             fmt.Printf("第%d位正确\n",i+1);                             flag++;                         }                     }                     if flag==3{                         fmt.Printf("您猜对了,数字就是:%d\n", userNum);                         break;                     }                 }else{                     fmt.Println("您输入的数字范围不合法,请输入100~999之间的三位数\n");                 }             }         } 4.字典map     (1)基本信息         在go语言中map数据类型表示字典结构,类似于传统c/c++/php中的字典、py中的列表、javascript的对象。         它是由键值对构成,键与值之间用冒号分隔,键值对之间用逗号分隔的无序存储方式。         (key可以是任何非复杂数据类型,value可以是任意数据类型。)         eg:             var map名称 map[keyType]valueType = map[keyType]valueType{};         eg:             var userDic map[string]int = map[string]int{"jack":100};         1)map的初始化             eg:                 userDic := map[string]int{"lilei":10};                 userDic := make(map[string]int, 10);             ps:                 在go语言中map数据类型的零值是nil,因此map创建后必须初始化一下,否则无法正常使用。                 eg:                     var userDic map[string]int;                     userDic["jack"] = 100;//违法操作         2)map自动扩容             在go语言中map和数组切片并不相同,map是自动扩容的,因此使用make初始化时给多少长度无所谓。             eg:                     userDic := make(map[string]int);//长度压根就可以不写                 userDic["jack"] = 100;                 userDic["andy"] = 50;//正确         3)map的长度与容量             在go语言中map的长度是不能够用len来计算的,因为map内部对于数据的存储是无序的链式存储             (链式存储,即每个键值对在存储内容之外,还会存储下一个键值对所在的内存首地址)             因此对于map的长度只能通过range遍历计算。正因如此,对于map而言容量和长度的概念变得毫无意义。             但是某些情况下我们却仍然需要获知map中的键值对的个数             所以最终,go语言规定len方法作用于map的时候,返回的结果是map键值对的个数             而且对于map也不在考虑容量的问题,因为容量恒等于长度,即map中键值对的个数。             eg:                 fmt.Println(len(userDic));//1                 for k,v := range userDic{                     fmt.Printf("%s--%d\n",k,v);                 }         4)map的键名唯一             在go语言中map字典在【定义】时,key是唯一的,重复定义key会导致抛出异常。             注意仅仅是定义,使用的时候是无所谓的。因为使用重复key值表示对key所对应的值的修改。             eg:                 userDic := map[string]int{"jack":100,"jack":20};//绝对违法!                 //                 userDic := map[string]int{"jack":100};                 userDic["jack"] = 20;//没毛病     (2)猜数字案例-改写:         func splitNumberToMap(num int) map[string]int{             numGe := num%10;             numShi := num%100/10;             numBai := num/100;             return map[string]int{"百位":numBai,"十位":numShi,"个位":numGe};         }         func main() {             rand.Seed(time.Now().UnixNano());             pivotMap := splitNumberToMap(rand.Intn(899)+100);             var userNum int;             for{                 fmt.Println("请输入一个三位数:");                 fmt.Scan(&userNum);                 if userNum>=100&&userNum<=999{                     userMap := splitNumberToMap(userNum);                     flag := 0;                     for k,v := range userMap{                         if v<pivotMap[k]{                             fmt.Printf("%s小了\n",k);                         }else if v>pivotMap[k]{                             fmt.Printf("%s大了\n",k);                         }else{                             fmt.Printf("%s正确\n",k);                             flag++;                         }                     }                     if flag==3{                         fmt.Printf("您猜对了,数字就是:%d\n", userNum);                         break;                     }                 }else{                     fmt.Println("您输入的数字范围不合法,请输入100~999之间的三位数\n");                 }             }         }     (3)map的值         因为在go语言中,对map的访问总是能够得到一个确定的值,哪怕这个key并不存在于map中也是如此。         因此map提出了一种判别key值是否存在的机制。         eg:             userDic := map[string]int{"jack":100,"frank":300};             //真值             val,flag := userDic["jack"];             fmt.Printf("%d,%t");//100,true             //假值             val,flag := userDic["Alice"];             fmt.Printf("%d,%t");//0,false     (4)delete-map的删除         在go语言中map的内容的删除,实际上就是键值对的删除。         eg:             delete(要执行删除操作的map, 要删除键值对的key)         eg:             delete(userDic, "jack");         ps:             需要注意的是,delete秉承了go语言的一贯简洁的作风,             不但不存在返回值,而且不论key值是否存在都会正常向下执行。(简洁的有点大劲了感觉)             因此一般不确定key值是否存在时,用val,flag判别一下在删除是一个不错的选择             eg:                 userDic := map[string]int{"jack":100};                 val,flag = userDic["frank"];                 if flag{                     delete(userDic, "frank");                 }else{                     fmt.Println("map中不存在frank这样的key");                 }     (5)map传参与返回值         在go语言中的map由于是自动扩容的,所以内存地址在变量完成内存分配后是不会发生变动的         所以map的传值采用的是真正意义上的地址传递。内部操作,外部变化         eg:             //合并map中的数组切片             func joinSliceFromMap(tempMap map[string][]int)[]int{                                  //粗糙算法,先算容量,在做合并                 //代码冗余到刺眼                 //finalArrLength := 0;                 //for _,v := range tempMap{                 //    finalArrLength += len(v);                 //}                 //finalArr := make([]int, finalArrLength);                 //testIndex := 0;                 //for _,v := range tempMap{                 //    copy(finalArr[testIndex:],v);                 //    testIndex += len(v);                 //}                 //扩容算法,长度无所谓,有内容就填充                 finalArr := make([]int,0);                 for _,v := range tempMap{                     for i:=0; i<len(v); i++{                         finalArr = append(finalArr, v[i]);                     }                 }                 return finalArr;             }             func main() {                 myMap := map[string][]int{                     "jack":[]int{1,2,3,4,5},                     "frank":[]int{5,4,3,2,1},                 }                 resultArr := joinSliceFromMap(myMap);                 fmt.Println(resultArr);//[1,2,3,4,5,5,4,3,2,1]             } --------------------- 作者:Frank·Ming 来源:CSDN 原文:https://blog.csdn.net/u013792921/article/details/84504092 版权声明:本文为博主原创文章,转载请附上博文链接!

有疑问加站长微信联系(非本文作者))

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1422 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传