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语言学习笔记05--切片slice与字典map》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
JavaScript Patterns
Stoyan Stefanov / O'Reilly Media, Inc. / 2010-09-21 / USD 29.99
What's the best approach for developing an application with JavaScript? This book helps you answer that question with numerous JavaScript coding patterns and best practices. If you're an experienced d......一起来看看 《JavaScript Patterns》 这本书的介绍吧!