Go内建容器

第三章 内建容器

3.1 数组

3.1.1 Go语言数组格式

Go语言数组语法格式要点如下:

  1. 数组成员数量要写在类型的前面
  2. 用“ := ”来创建数组时需要加上“ {} ”来给数组一个初值
  3. “ [] ”里可以用“ ... ”来让编译器识别有多少个数组成员
  4. fmt.Println()可以直接打印数组的成员
package main

import "fmt"

func main() {
   var arr1 [3]int
   arr2 := [3]int{}
   arr3 := [...]int{1, 4, 5, 6, 7}
   var grid [3][4]int

   fmt.Println(
      arr1, //[0 0 0]
      arr2, //[0 0 0]
      arr3, //[1 4 5 6 7]
      grid, //[[0 0 0 0] [0 0 0 0] [0 0 0 0]]
   )
}

3.1.2 数组的遍历(range关键词)

可以用传统的遍历数组方法遍历数组,但一般在Go中用range关键词来遍历

传统:

package main

import "fmt"

func main() {
   arr := [...]int{2, 4, 6, 8, 10}
   for i := 0; i < len(arr); i++ {
      fmt.Printf("%d ", arr[i])
   }
}

range关键词可以获得数组的下标以及下标对应的值

  1. 只想获得数组的下标:
package main

import "fmt"

func main() {
   arr := [...]int{2, 4, 6, 8, 10}
   for i := range arr {
      fmt.Println(i) 
   }
   //输出结果:
   //0
   //1
   //2
   //3
   //4
}
  1. 想获得数组下标以及下标对应的值:
package main

import "fmt"

func main() {
   arr := [...]int{2, 4, 6, 8, 10}
   for i, v := range arr {
      fmt.Println(i, v)
   }
   //输出结果:
   //0 2
   //1 4
   //2 6
   //3 8
   //4 10
}
  1. 只想获得数组的各个值,通过下划线“ _ ”来省略变量,不仅在range里,任何地方都能用下划线来省略变量
package main

import "fmt"

func main() {
   arr := [...]int{2, 4, 6, 8, 10}
   for _, v := range arr {
      fmt.Println(v)
   }
   //输出结果:
   //2
   //4
   //6
   //8
   //10
}

总之,i代表下标,v代表值

3.1.3 在Go中,数组是值类型

  • [i]int[j]inti不等于j)是不同类型,若函数要求传入的是[i]int类型但传入的是[j]int类型,那就会报错
  • 调用func f(arr [10]int)会在函数中拷贝数组
  • Go语言一般不会直接使用数组,而是使用切片

下面将Go和Java中在函数(Go)/方法(Java)中对数组成员进行操作的对比:

1. Go:

package main

import "fmt"

func f(arr [5]int) {
   arr[0] = 100
   for _, v := range arr {
      fmt.Println(v)
   }
   fmt.Printf("函数内,数组第一位是:%d\n", arr[0])
}

func main() {
   arr := [5]int{2, 4, 6, 8, 10}
   f(arr)
   fmt.Printf("main中数组第一位是:%d", arr[0])
}

Go输出结果:

image

2. Java:

package com.oop;

import java.text.ParseException;

public class Application {
    public static void main(String[] args) {
        int[] arr = {2, 4, 6, 8, 10};
        f(arr);
        System.out.println("在main中,数组第一位是:" + arr[0]);
    }

    public static void f(int[] arr) {
        arr[0] = 100;
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }
        System.out.println("在方法中,数组第一位是:" + arr[0]);
    }
}

Java输出结果:

image

可以发现:在Go语言中,函数内对数组的操作并不会改变原本的数组数据,但在Java中,方法内对数组的操作会对原本的数组产生改变

在Go中若想在函数内对数组的操作对原数组产生改变,可以将指向数组的指针传入函数内,以此来达到改变数组的目的:

  • 不同于C语言中数组名是数组的地址,在Go语言中,想获得数组的地址还是要进行&arr操作
package main

import "fmt"

func f(arr *[5]int) {
    arr[0] = 100//Go语言很灵活,即使这里传入的是指针,也不用(*arr)[0] = 100
   for _, v := range arr {
      fmt.Println(v)
   }
   fmt.Printf("函数内,数组第一位是:%d\n", arr[0])
}

func main() {
   arr := [5]int{2, 4, 6, 8, 10}
   f(&arr)
   fmt.Printf("main中数组第一位是:%d", arr[0])
}

输出结果:

image

传入数组的指针后达到了改变数组内值的目的

3.2 切片(Slice)的概念

3.2.1 切片的特点

切片:对数组进行左闭右开区间的一个切分得出的数组,如下:

package main

import "fmt"

func main() {
   arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
   s1 := arr[2:6]
   s2 := arr[2:]
   s3 := arr[:6]
   s4 := arr[:]
   fmt.Println(
      s1, //[2 3 4 5]
      s2, //[2 3 4 5 6 7]
      s3, //[0 1 2 3 4 5]
      s4, //[0 1 2 3 4 5 6 7]
   )
}
  • 切片本身没有数据,其是对底层array的一个view(所谓view,就是通过view来看array的样子,所以切片变了那么array就会变)
  • 对切片进行改变,不仅切片会发生改变,原来的array也会发生改变

例子如下:

package main

import "fmt"

//方括号里不加数组长度就是切片类型,加了就是数组类型
func updateSlice(s []int) {
	s[0] = 100
}

func main() {
	arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
	s1 := arr[2:6] //s1:[2 3 4 5]
	s2 := arr[:]   //[0 1 2 3 4 5 6 7]

	fmt.Println("After updateSlice(s1):")
	updateSlice(s1)
	fmt.Println(s1)  //[100 3 4 5]
	fmt.Println(arr) //[0 1 100 3 4 5 6 7]
	fmt.Println(s2)  //[0 1 100 3 4 5 6 7]
}

3.13中所阐述的,若想在函数中改变数组的值,可以传入数组的指针来达到目的,但一般不这么做,一般会传入切片类型给函数,这样就能对原数组产生影响,如下:

package main

import "fmt"

func f(arr []int) {
   arr[0] = 100
   for _, v := range arr {
      fmt.Println(v)
   }
   fmt.Println("函数内,数组第一位是:", arr[0])
}

func main() {
   arr := [5]int{2, 4, 6, 8, 10}
    f(arr[:])//这里的arr[:]就是传入的切片类型
   fmt.Println("main中数组第一位是:", arr[0])
}

3.2.2 Reslice

可以对切片再次进行切片

package main

import "fmt"

func updateSlice(s []int) {
   s[0] = 100
}

func main() {
   arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
   s1 := arr[2:6] //s1:[2 3 4 5]
   s2 := arr[:]   //[0 1 2 3 4 5 6 7]

   fmt.Println("After updateSlice(s1):")
   updateSlice(s1)
   fmt.Println(s2) //[0 1 100 3 4 5 6 7]

   fmt.Println("Reslice")
   s2 = s2[:5]
   fmt.Println(s2) //[0 1 100 3 4]
   s2 = s2[2:]
   fmt.Println(s2) //[100 3 4]
}

3.2.3 Slice的扩展

看一个例子:

package main

import "fmt"

func main() {
   arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
   s1 := arr[2:6]
   s2 := s1[3:5]
   fmt.Println(
      s1, //[2 3 4 5]
      s2, //[5 6]
   )
}

s1很容易得出是[2 3 4 5],但s2 := s1[3:5]这一句,s1下标最大为3,为什么s2能够取到s1下标为4的元素呢?这涉及到Slice的扩展

Slice的扩展图解:因为切片是对底层array的一个view,所以s1还是有下标为4,5两个隐藏元素的,同理可得s2有下标为2的隐藏元素

image

Slice的实现图解:ptr指向slice开头的元素;len(length)是slice的长度,在用方括号”[]“取值时只能取到len里的值;cap(capacity)代表ptr到数组尾部的长度,也代表着slice能扩展的最大范围

image

值得注意的是:

  • slice可以向后扩展,但不能向前扩展
  • s[i]不可以超越len(s),向后扩展不可以超越底层数组cap(s)
函数名 说明
func cap(v Type) int 返回 v 的容量
func len(v Type) int 返回 v 的长度

切片的length和capacity都是可以通过len(s)cap(s)来得到的:

package main

import "fmt"

func main() {
   arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
   s1 := arr[2:6]
   s2 := s1[3:5]
   fmt.Println(
      arr,                                          //[0 1 2 3 4 5 6 7]
      s1, "len(s1)=", len(s1), "cap(s2)=", cap(s1), //[2 3 4 5] len(s1)= 4 cap(s2)= 6
      s2, "len(s2)=", len(s2), "cap(s2)=", cap(s2), //[5 6] len(s2)= 2 cap(s2)= 3
   )
}

3.3 切片的操作

缩写解释:

  • dst:即destination,目标
  • src:即source,来源
函数名 说明
func append(slice []Type, elems ...Type) []Type 将元素追加到切片的末尾
func make(Type, size IntegerType) Type 分配并初始化一个类型为切片、映射、或通道的对象
func copy(dst, src []Type) int 将元素从来源切片复制到目标切片中,也能将字节从字符串复制到字节切片中

3.3.1 对切片进行新增func append(slice []Type, elems ...Type) []Type

  • 在切片中添加元素时如果超越了cap,系统会重新分配更大的底层数组

append()语法示例:

  1. 新增切片成员,第一位写目标切片,后面写任意数量的要新增的数值
  2. 拼接两个切片,第一位写目标切片,后面的切片后要加” ... “
slice = append(slice, elem1, elem2)
slice = append(slice, anotherSlice...)
package main

import "fmt"

func main() {
   arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
   s1 := arr[2:6] //[2 3 4 5]
   s2 := s1[3:5]  //[5 6]
   s3 := append(s2, 10)
   s4 := append(s3, 11)
   s5 := append(s4, 12)
   fmt.Println(
      s3,  //[5 6 10]
      s4,  //[5 6 10 11]
      s5,  //[5 6 10 11 12]
      arr, //[0 1 2 3 4 5 6 10]
      //s4和s5的capacity已经超过了arr,所以s4和s5的view对象就不再是arr了,而是系统给分配的其他更长的数组
   )
}

3.3.2 对切片进行创建func make(Type, size IntegerType) Type

package main

import "fmt"

//用来打印切片的内容、长度、容量
func printSlice(s []int) {
   fmt.Printf("%v,len=%d,cap=%d\n", s, len(s), cap(s))
}

func main() {
   //不先创建数组,直接创建切片
   //1.创建不赋初值的切片,不给切片赋初值,其初值默认为nil
   var s []int
   for i := 0; i < 10; i++ {
      s = append(s, 2*i+1)
   }
   fmt.Println(s) //[1 3 5 7 9 11 13 15 17 19]

   //2.创建赋初值的切片
   s1 := []int{2, 4, 6, 8}
   printSlice(s1) //[2 4 6 8],len=4,cap=4

   //3.在创建大小已知的切片但切片里的值还不知道时,用make()来创建切片
   s2 := make([]int, 16)
   s3 := make([]int, 10, 32)
   printSlice(s2) //[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0],len=16,cap=16
   printSlice(s3) //[0 0 0 0 0 0 0 0 0 0],len=10,cap=32
}

3.3.3 对切片进行复制func copy(dst, src []Type) int

package main

import "fmt"

func printSlice(s []int) {
   fmt.Printf("%v,len=%d,cap=%d\n", s, len(s), cap(s))
}

func main() {
   s1 := []int{2, 4, 6, 8}
   printSlice(s1) //[2 4 6 8],len=4,cap=4

   s2 := make([]int, 16)
   printSlice(s2) //[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0],len=16,cap=16

   copy(s2, s1)
   printSlice(s2) //[2 4 6 8 0 0 0 0 0 0 0 0 0 0 0 0],len=16,cap=16
}

3.3.4 对切片进行删减

package main

import "fmt"

func printSlice(s []int) {
   fmt.Printf("%v,len=%d,cap=%d\n", s, len(s), cap(s))
}

func main() {
   s := []int{2, 4, 6, 8, 0, 0, 0, 0, 0}
   printSlice(s) //[2 4 6 8 0 0 0 0 0],len=9,cap=9
   //1.删除切片中间的成员:将s中的”8“删除:将切片s切成两个切片再拼接即可
   s = append(s[:3], s[4:]...)
   printSlice(s) //[2 4 6 0 0 0 0 0],len=8,cap=9

   //2.poping from back
   tail := s[len(s)-1]
   s = s[:len(s)-1]
   fmt.Println(tail) //0
   printSlice(s)     //[2 4 6 0 0 0 0],len=7,cap=9

   //3.poping from front
   front := s[0]
   s = s[1:]
   fmt.Println(front) //2
   printSlice(s)      //[4 6 0 0 0 0],len=6,cap=8
}

3.4 map

3.4.1 map的三种定义方式

package main

import "fmt"

func main() {
   //第一种声明方式
   //声明myMap1是一种map类型,key是string,value是string
   var myMap1 map[string]string
   if myMap1 == nil {
      fmt.Println("myMap1是一个空map")
   }
   //在使用map前,需先用make()给map分配空间
   myMap1 = make(map[string]string, 10)
   myMap1["one"] = "java"
   myMap1["two"] = "php"
   myMap1["three"] = "python"
   fmt.Println(myMap1) //map[one:java three:python two:php]
   //输出的map成员顺序是无序的

   //第二种声明方式
   myMap2 := make(map[int]string)
   myMap2[1] = "cpp"
   myMap2[2] = "c"
   myMap2[3] = "go"
   fmt.Println(myMap2) //map[1:cpp 2:c 3:go]

   //第三种声明方式
   myMap3 := map[string]string{
      "one":   "javascript",
      "two":   "cpp",
      "three": "golang",
   }
   fmt.Println(myMap3)//map[one:javascript three:golang two:cpp]
}
  • 还有复合mapmap[K1]map[K2]V,即外面一层的map的key是K1,值是map[K2]V,里面一层map的key是K2,值是V

3.4.2 map的操作

1.map的各类操作:

  • 创建:m := make(map[string]string)
  • 获取元素:m[key]
  • key不存在时,获得Value类型的初始值
  • value,ok = m[key]来判断是否存在key
  • delete()删除一个key

2.map的遍历:

  • 使用range遍历key,或者遍历key,value对
  • 不保证遍历顺序,如需顺序,需要手动对key排序
  • 使用len()来获得map的元素个数

3.可以当map的key所需条件:

  • map使用哈希表,必须可以比较是否相等
  • 除了slicemapfunction的内建类型可以作为key
  • Struct类型不包含上述字段,也可以作为key
package main

import (
   "fmt"
)

func main() {
   myMap := map[string]string{
      "name":    "ccmouse",
      "course":  "golang",
      "site":    "imooc",
      "quality": "notbad",
   }

   //1.遍历map,可以用range来遍历map,和数组一样,可以读取map的key以及value
   for k, v := range myMap {
      fmt.Println(k, v)
   }
   for k := range myMap {
      fmt.Println(k)
   }
   for _, v := range myMap {
      fmt.Println(v)
   }
   fmt.Println("============================")

   //2.读取map中的值
   courseName := myMap["course"]
   fmt.Println(courseName) //golang
   fmt.Println("============================")

   //3.判断某个键是否存在
   //Go语言中有个判断map中键是否存在的特殊写法,格式如下:value, ok := map[key]
   teacherName1, ok := myMap["teacher"]
   fmt.Println(teacherName1, ok) // false
   teacherName2, ok := myMap["name"]
   fmt.Println(teacherName2, ok) //ccmouse true
   // 如果key存在ok为true,v为对应的值;不存在ok为false,v为值类型的零值
   fmt.Println("============================")

   //4.删除元素
   delete(myMap, "name")
   name, ok := myMap["name"]
   fmt.Println(name, ok) // false
   fmt.Println(myMap)
   fmt.Println(len(myMap))
}

3.5 map案例

需求:寻找最长不含有重复字符的字串,请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。

例:

  • abcabcbbabc,输出3
  • bbbbbb,输出1
  • pwwkewwke,输出3
package main

import "fmt"

func lengthOfNonRepeatingSubStr(s string) int {
   lastOccurred := make(map[byte]int)//记录每个字符位置的map,此时创建的是空map
   start := 0//表示无重复字符串的开头
   maxLength := 0//最大长度子串的长度

   //[]byte(s)意思是将s强转为byte切片
   for i, ch := range []byte(s) {
      //若字符在map中存在过(即此字符出现过),且出现的位置>=start,将start移动至上次出现位置的+1处
      if lastI, ok := lastOccurred[ch]; ok && lastI >= start {
         start = lastI + 1
      }
      //若此字符到i的长度>maxLength,令maxLength=此字符到i的长度
      if i-start+1 > maxLength {
         maxLength = i - start + 1
      }
      //将此次循环到的字符所存在的位置记录在map中
      lastOccurred[ch] = i
   }
   return maxLength
}

func main() {
   fmt.Println(lengthOfNonRepeatingSubStr("abcaebb"))
}

3.6 字符和字符串处理

3.6.1 unicode/utf8

  • utf8包实现了对utf-8文本的常用函数和常数的支持,包括runeutf-8编码byte序列之间互相翻译的函数。

3.6.2 unicode/utf8包常用函数

函数名 说明
func RuneCount(p []byte) int 返回p中的utf-8编码的码值的个数
func RuneCountInString(s string) (n int) 类似RuneCount但输入参数是一个字符串
func DecodeRune(p []byte) (r rune, size int) 函数解码p开始位置的第一个utf-8编码的码值,返回该码值和编码的字节数。如果编码不合法,会返回(RuneError, 1)
package main

import (
   "fmt"
   "unicode/utf8"
)

func main() {
   //part.1 用len()直接打印含中文字符串的长度
   s := "Yes我爱慕课网!"
   fmt.Println(len(s)) //若用len()查看s的长度会发现不是9而是19,len()只能获得字节长度而不能获得字符长度
   for _, b := range []byte(s) {
      fmt.Printf("%X ", b)
   } //59 65 73 E6 88 91 E7 88 B1 E6 85 95 E8 AF BE E7 BD 91 21
   //若用range遍历将s转换为字节切片再遍历,会发现只有9个字符的含中文的字符串会打印出19个字节
   fmt.Println()

   //part.2 中文字符占3个字节
   for i, ch := range s { //ch是rune类型,int32是rune的别名,rune是4个字节
      fmt.Printf("(%d %X) ", i, ch)
   } //(0 59) (1 65) (2 73) (3 6211) (6 7231) (9 6155) (12 8BFE) (15 7F51) (18 21)
   //用range遍历s字符串,会发现中文字符占3字节
   //此外,在UTF-8编码中,一个中文标点符号占三个字节;一个英文字符等于一个字节,一个英文标点占一个字节;一个数字符号等于一个字节
   fmt.Println()

   //part.3.1 用func RuneCountInString(s string) (n int)获得含中文的字符串的字符数量
   //对含有中文的字符串进行操作可以用utf8包中的函数会很方便
   fmt.Println("Rune count:", utf8.RuneCountInString(s)) //Rune count: 9,RuneCountInString可以返回utf-8编码的码值的个数

   //part.3.2 func DecodeRune(p []byte) (r rune, size int)
   bytes := []byte(s)
   for len(bytes) > 0 { //若bytes的长度大于0则进入循环
      ch, size := utf8.DecodeRune(bytes) //获取bytes第一个编码的码值和该码值占的字节数
      fmt.Printf("%c ", ch)              //打印bytes第一个编码的码值
      bytes = bytes[size:]               //将bytes切掉第一个码值
   } //输出结果:Y e s 我 爱 慕 课 网 !
   fmt.Println()

   //part.4 将字符串转换为rune切片,再遍历即可获得字符串每个字符的下标,不像转换为byte切片时会因为字符的字节数不一样而跳过一些下标
   for i, ch := range []rune(s) {
      fmt.Printf("(%d %c)", i, ch)
   }
}

rune相当于go的char

  • 遍历字符串可以使用[]rune(s)
  • 使用utf8.RuneCountInString()获得字符数量
  • 使用len()获得字节长度
  • 使用[]byte获得字节

3.5map案例不支持中文,将[]byte(s)改为[]rune(s)即可:

package main

import "fmt"

func lengthOfNonRepeatingSubStr(s string) int {
   lastOccurred := make(map[rune]int)//改为[]rune(s)
   start := 0
   maxLength := 0

   for i, ch := range []rune(s) {//改为[]rune(s),这里不能直接range s,这样会将中文字符算作3个字节
      if lastI, ok := lastOccurred[ch]; ok && lastI >= start {
         start = lastI + 1
      }
      if i-start+1 > maxLength {
         maxLength = i - start + 1
      }
      lastOccurred[ch] = i
   }
   return maxLength
}

func main() {
   fmt.Println(lengthOfNonRepeatingSubStr("一二三三三"))
}

其他字符串操作

关于字符串操作的函数在strings包中

  • FieldsSplitJoin
  • ContainsIndex
  • ToLowerToUpper
  • TrimTrimRightTrimLeft
posted @   雪碧锅仔饭  阅读(99)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示