第四节 Go基础容器

 

go的基础容器: 切片、map和rune

与其说是教程,更像是总结

一起学习:smiley:


1. 切片(引用类型)

弥补了数组不能拓展到的缺陷 (是引用类型),相当于是数组的加强版

定义切片和初始化

//定义
var slices []type
//初始化
slices = make([]type,length,capacity)

小案例

func main() {
	slices := make([]int, 3, 5)
	fmt.Printf("len=%d cap=%d slices=%v\n", len(slices), cap(slices), slices)
}

运行结果

len=3 cap=5 slices=[0 0 0]

切片与数组的区别

  • 数组定义时需要指定大小[5]或用…([…])
  • 切片定义时不需要且需要make来初始化,当然可以 := 来偷懒

截取切片(难点)

  • [:] 截取原则为左开右闭(闭就是不包括)区间
  • cap()函数的规则是[n:]中n的值决定的,总体减去下限
  • 注意左边,左边没了那真没了(cap才是真的)
package main

import "fmt"

func main() {
	//创建切片
	numbers := []int{1, 2, 3, 45, 6, 7, 8, 9}
	printSlice(numbers)

	//前3个元素
	numbers3 := numbers[:3]
	printSlice(numbers3)

	//第3个元素
	numbers2 := numbers[2:3]
	printSlice(numbers2)

	//第2,3个元素
	numbers4 := numbers[1:3]
	printSlice(numbers4)
}

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

运行结果

PS F:\Desktop\test> go run main2.go  
len=8 cap=8 slices=[1 2 3 45 6 7 8 9]
len=3 cap=8 slices=[1 2 3]
len=1 cap=6 slices=[3]    
len=2 cap=7 slices=[2 3]  

append() 和 copy()函数

无情copy(),会断绝与以前的联系,会创建一个新的slice,并返回个数

count := copy(numbers1,numbers) //从 numbers -> numbers1
fmt.Println(count)

更无情append(),用于删除元素

也可以更有情用于拓展元素,自动拓展后地址会改变

删除第一个元素

cap的特性

slices = slices[1:]

删除最后一个元素

slices = slices[:len(slices)-1]

删除中间某1个元素

slices = append(slices[:a],numbers[a+1:]...)

小案例

func main() {
   numbers := []int{1, 2, 3, 45, 6, 7, 8, 9}
   printSlice(numbers)

   //删1
   numbers = numbers[1:]
   printSlice(numbers)

   //删尾
   numbers = numbers[:len(numbers)-1]
   printSlice(numbers)

   //删除中间第三个元素
   numbers = append(numbers[:3], numbers[4:]...)
   printSlice(numbers)

   //删除1,2号元素
   numbers = append(numbers[:1], numbers[3:]...) //依然满足左开右闭的原则
   printSlice(numbers)
}

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

运行结果

len=8 cap=8 slices=[1 2 3 45 6 7 8 9]
len=7 cap=7 slices=[2 3 45 6 7 8 9]
len=6 cap=7 slices=[2 3 45 6 7 8]
len=5 cap=7 slices=[2 3 45 7 8]
len=3 cap=7 slices=[2 7 8]

2. map (引用类型)

定义

声明同时赋值

var country = map[string]string{
    "China": "Beijing",
    "Japan": "Tokyo",
}

country1 := map[string]string{"C": "Beijing","J": "Tokyo"}

先声明再赋值

countryMap := make(map[string]string);
countryMap["China"] = "Beijing"
countryMap["Japan"] = "Tokyo"

遍历

和数组一样的

key , value都遍历

for k,v := range countryMap{
    ...
}

value

for _,v := range countryMap{
    ...
}

key

for k := range countryMap{
    ...
}

判断元素是否存在和删除key

func main() {
	m := map[string]string{
		"name": "Lido",
	}

	//如果key输错的,会输出空值
	name := m["name"]
	fmt.Println(name)

	//判断key是否存在
	if name, ok := m["name"]; ok {
		fmt.Println(name)
	} else {
		fmt.Println("Key does not exits")
	}

	delete(m, "name")
	if name, ok := m["name"]; ok {
		fmt.Println(name)
	} else {
		fmt.Println("Key does not exits")
	}
}

运行结果

PS F:\Desktop\test> go run main2.go
Lido
Lido
Key does not exits

情况map

m = make(map[string]string)

要注意的key

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

3. rune

来看个例子

想不忙解释rune

s := "中国冬奥加油,Come on!"
fmt.Println(len(s))//27(3+3+3+3+3+3+1+1+1+1+1+1+1+1+1)

为啥是27,不应该是15的吗?

Go语言的字符与字符串

Go中字符是ASCII码,单位是字节byte,英文和字符占1字节

Go中字符串都以 UTF-8 格式保存,每个中文占用 3 个字节

len()函数

len() 函数的返回值的类型为 int,表示字符串的 ASCII 字符个数或字节长度

一个中文占3个字节,这也是为什么打印出来是27

for _, b := range []byte(s) {
   fmt.Printf("%X ", b) //UTF-8,符号1字节,英文1字节,中文3字节
   //[E4 B8 AD] [E5 9B BD] [E5 86 AC] [E5 A5 A5] [E5 8A A0] [E6 B2 B9] 2C 43 6F 6D 65 20 6F 6E 21 
}

rune字符

byte 表示一个字节,可以表示英文字符等占用一个字节的字符,占用多于一个字节的字符就无法正确表示,例如占用 3 个字节的汉字(uint8的别名) rune 表示一个字符,用来表示任何一个Unicode字符,可以输出单个中文字符(int32的别名)

fmt.Println(utf8.RuneCountInString("中国冬奥加油,Come on!"))//15
bytes := []byte(s)
for len(bytes) > 0 {
    ch, size := utf8.DecodeRune(bytes)
    bytes = bytes[size:]
    fmt.Printf("%c", ch) //Yes中国加油!
}
for index, rune := range s {
        fmt.Printf("%c 从字节 %d 处开始\n", rune, index)
    }
for i, ch := range []rune(s) {
	fmt.Printf("(%d %c)", i, ch) 
}

总结

  • ASCII 字符串长度使用 len() 函数。
  • Unicode 字符串长度使用 utf8.RuneCountInString() 函数。

4.字符串基本操作

看看熟悉一下,以后想到拿来用就行

Fields、Split、Join

str := "hello world"
str_1 := []string{"a", "b", "c"}

fmt.Println(strings.Fields(str)[0])     //hello
fmt.Println(strings.Fields(str)[1])     //world

fmt.Println(strings.Split(str, "o")[0]) //hell
fmt.Println(strings.Split(str, "o")[1]) //w
fmt.Println(strings.Split(str, "o")[2]) //rld

fmt.Println(strings.Join(str_1, ",")) //a,b,c

Contains、Index

fmt.Println(strings.Contains("Football", "foot")) //false
fmt.Println(strings.Index("Football", "ot"))      //2

ToUpper、ToLower

fmt.Println(strings.ToUpper("Football"))//FOOTBALL
fmt.Println(strings.ToLower("Football"))//football

Trim、TrimRight、TrimLeft

fmt.Println(strings.Trim("!Football!!!", "!"))		//Football
fmt.Println(strings.TrimRight("!Football!!!", "!"))	//!Football
fmt.Println(strings.TrimLeft("!Football!!!", "!"))  //Football!!!

5. 补充

new和make区别

  • make 的作用是初始化内置的数据结构,也就是我们在前面提到的切片
  • new 的作用是根据传入的类型分配一片内存空间并返回指向这片内存空间的指针;

Unicode与UTF-8

unicode在很长一段时间内无法推广,直到互联网的出现,为解决unicode如何在网络上传输的问题,于是面向传输的众多 UTF(UCS Transfer Format)标准出现了,顾名思义,UTF-8就是每次8个位传输数据,而UTF-16就是每次16个位。UTF-8就是在互联网上使用最广的一种unicode的实现方式,这是为传输而设计的编码,并使编码无国界,这样就可以显示全世界上所有文化的字符了。UTF-8最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度,当字符在ASCII码的范围时,就用一个字节表示,保留了ASCII字符一个字节的编码做为它的一部分,注意的是unicode一个中文字符占2个字节,而UTF-8一个中文字符占3个字节)。从unicode到utf-8并不是直接的对应,而是要过一些算法和规则来转换。

作者:盛世唐朝 链接:https://www.zhihu.com/question/23374078/answer/69732605 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

小案例

1. 两数之和

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

你可以按任意顺序返回答案。

示例 1:

输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。

示例 2:

输入:nums = [3,2,4], target = 6
输出:[1,2]

示例 3:

输入:nums = [3,3], target = 6
输出:[0,1]

解法一:两层for循环遍历

解法二:使用hashMap

/**
  @Go version: 1.17.6
  @project: leetcode
  @ide: GoLand
  @file: main.go
  @author: Lido
  @time: 2022-02-08 23:56
  @description: leetcode 第一题
*/
package main

import "fmt"

//
// @Title twoSum1
// @Description 两层for循环比对
// @Author lido 2022-02-09 09:38:17
// @Param nums
// @Param target
// @Return []int
//
func twoSum1(nums []int, target int) []int {
	for i_1, v_1 := range nums {
		for i_2 := i_1 + 1; i_2 < len(nums); i_2++ {
			if v_1+nums[i_2] == target {
				return []int{i_1, i_2}
			}
		}
	}

	return nil
}

//
// @Title twoSum2
// @Description hashMap的特性,牺牲空间换时间
// @Author lido 2022-02-09 09:38:27
// @Param nums
// @Param target
// @Return []int
//
func twoSum2(nums []int, target int) []int {
	hashTable := map[int]int{}
	for i, x := range nums {
		//直接比对下标是否存在,存在说明值也存在
		if p, ok := hashTable[target-x]; ok {
			return []int{p, i}
		}
		//这里直接将值作为下标放入hashMap
		hashTable[x] = i
	}
	return nil
}

func main() {
	num := []int{2, 7, 11, 15}
	target := 9
	fmt.Println(twoSum1(num, target))
	fmt.Println(twoSum2(num, target))
}

参考

Go 字节 (byte) & 文字符号 (rune)