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》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
HTML 压缩/解压工具
在线压缩/解压 HTML 代码
HSV CMYK 转换工具
HSV CMYK互换工具