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

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         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             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                            fmt.Printf("%d",slice8[i]);
                       }
                       //违法操作 
                       for i:=0; 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]                             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                             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                         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]
            }

你可能感兴趣的:(go语言基础)