1. 本章前瞻

很好,经过很长的时间,你终于来到go语言的复合类型中,这里会介绍go语言的3种复合结构:切片(slice,可变数组),映射(map)和字符串(string)。

有些老手可能会问:

1.那结构体(struct)呢,你怎么不介绍?

答:现在还没法完整地介绍结构体(struct),主要是没法介绍结构体相关的方法。

2.对于字符串(string),字符串(string)怎么会是复合类型呢?

答:字符串(string)可以认为元素无法变更的byte数组,为此我认为它是复合类型。

这里开始会有新版本的内容加入进来,本来的内容会以go1.20为主,但是由于go语言的半年更新周期,现在就必须加上go1.21的相关内容,请学习愉快!

2.来自leetcode的例题

题目非常地简单,让我先水一下,本题唯一的要点是使用map[int]struct{}这种go语言风格的set类型

描述

26. 删除有序数组中的重复项

给你一个 升序排列 的数组 nums ,请你原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。

分析

真的有如开始所说的那样简单,使用map,注意在go语言中map是使用列表实现的,我们就可以轻松地去重

题解

func removeDuplicates(nums []int) (k int) {
numMap := make(map[int]struct{}) //struct{}的大小为0,map[int]struct{}一般用作集合 k = 0
for _, v := range nums {
if _, ok := numMap[v]; !ok {
numMap[v] = struct{}{}
nums[k] = v
k++
}
} return
}

3. 复合类型新版本的变化

这里说的新版本是指go1.20之后的变化, 这里解说的是近几个版本中关于复合类型的重要变化。

3.1 string和[]byte的高效转化

reflect.StringHeader在业内经常被滥用,使用不方便,很容易出现隐性问题,为了解决这个问题,go1.20对于string和[]byte类型进行高效转化,unsafe提供了

func String(ptr *byte, len IntegerType) string
func StringData(str string) *byte
func Slice(ptr *ArbitraryType, len IntegerType) []ArbitraryType

利用上述三个函数,可以方便地进行string和[]byte的高效转化,如果你对于go1.20之前的高效转化有兴趣,可以看字符串的4.3.2章节

func StringToBytes(s string) []byte {
return unsafe.Slice(unsafe.StringData(s), len(s))
} func BytesToString(b []byte) string {
return unsafe.String(&b[0], len(b))
}

3.2 内置函数clear

在go1.21之前,你如果需要清空一个map,那么你必须进行循环delete

	myMap := map[string]int{
"A": 1,
"B": 2,
"C": 3,
} fmt.Println(myMap)
for k := range myMap {
delete(myMap, k)
} fmt.Println(myMap)

而现在go1.21中你可以

	myMap = map[string]int{
"A": 1,
"B": 2,
"C": 3,
}
fmt.Println(myMap) clear(myMap)
fmt.Println(myMap)

但是如果你将其用于切片,你会发现切片中的元素不会被删除,只会被清零。

	mySlice := []int{1, 2, 3, 4, 5, 6, 7, 8}
fmt.Println(mySlice) //输出为[1 2 3 4 5 6 7 8] clear(mySlice)
fmt.Println(mySlice) //输出为[0 0 0 0 0 0 0 0]

4. 复合类型概述

下面是使用AI生成的复合类型,注意:如果你有需求,可以回顾这些知识,但是基本上可以跳过。

4.1 切片

Go语言中的切片(slice)是一种基于数组的灵活且强大的数据结构,它是对数组的一个引用。切片提供了一种方便的方式来操作和操作集合。

切片有三个关键属性:

  1. 底层数组(Underlying Array):切片引用一个具体的数组,底层数组是切片数据的实际存储位置。
  2. 长度(Length):切片的当前元素个数。
  3. 容量(Capacity):切片底层数组中从切片的开始位置到底层数组结束的元素个数。

切片在声明、初始化、操作和比较方面都比数组更加灵活。你可以使用make()函数创建一个切片,使用append()函数向切片添加元素,使用copy()函数复制切片,还可以使用切片的索引操作获取或修改特定位置的元素。

切片还支持一些内置的方法,如len()cap(),分别返回切片的长度和容量。此外,切片是引用类型,传递切片作为参数不会复制整个切片,而只是复制引用,这使得函数能够修改原始数据。

以下是一些关于创建和使用切片的示例代码:

package main

import "fmt"

func main() {
// 创建一个整数数组
a := [5]int{1, 2, 3, 4, 5} // 创建一个基于数组的切片
s := a[1:3]
fmt.Println(s) // 输出: [2 3] // 使用make函数创建一个长度为5的切片,初始值为0
m := make([]int, 5)
fmt.Println(m) // 输出: [0 0 0 0 0] // 使用append函数向切片添加元素
m = append(m, 1, 2, 3)
fmt.Println(m) // 输出: [0 0 0 0 0 1 2 3] // 使用copy函数复制切片
n := make([]int, len(m))
copy(n, m)
fmt.Println(n) // 输出: [0 0 0 0 0 1 2 3] // 这里写段
m = append(m[:len(m)-2], m[2:]...)
fmt.Println(m) // 输出: [0 0 3]
}

以上代码展示了切片的基本操作,包括创建、添加元素、复制和删除元素等。切片是Go语言中非常强大且有用的数据结构,可以帮助开发者更高效地处理数据和实现复杂的逻辑。

4.2 映射

Go语言中的映射(map)是一种特殊的数据结构,它是一个无序的键值对集合。映射的键必须是唯一的,但值可以重复。映射的元素是键值对,每个键都映射到一个值。

映射的声明使用如下语法:

var m map[keyType]valueType

其中,keyType是键的类型,valueType是值的类型。

映射的操作包括:

  1. m[k]:使用键k访问映射,返回对应的值。如果键k在映射中不存在,会返回该映射类型的零值。
  2. m[k] = v:使用键k将值v赋值给映射。如果键k在映射中不存在,会创建一个新的键值对。
  3. delete(m, k):删除映射m中键为k的键值对。
  4. len(m):返回映射中键值对的数量。
  5. m[k] == vm[k] != v:用于判断映射中是否存在某个键k,并且其对应的值是否等于v

需要注意的是,映射是引用类型,传递的是引用而不是整个映射的拷贝。因此,对映射的修改会影响原始映射。此外,映射的键必须是唯一的,但值可以重复。映射的元素是无序的,每次迭代时顺序可能不同。

以下是一个使用映射的示例代码:

package main

import "fmt"

func main() {
// 创建一个映射
m := make(map[string]int)
//赋值
m = map[string]int{
"我是水货": 1,
"水货": 2,
"水": 3,
}
// 添加键值对到映射
m["货"] = 4 // 访问映射中的元素
fmt.Println(m["我是水货"]) // 输出: 1
fmt.Println(m["水货"]) // 输出: 2
fmt.Println(m["水"]) // 输出: 3 // 修改映射中的元素
m["水货"] = 10
fmt.Println(m["水货"]) // 输出: 10 // 删除映射中的元素
delete(m, "水")
fmt.Println(m["水"]) // 输出: 0 // 检查映射中是否存在某个键
_, ok := m["水货"]
fmt.Println(ok) // 输出: true
_, ok = m["水"]
fmt.Println(ok) // 输出: false
}

以上代码展示了如何声明、访问、修改和删除映射中的元素,以及如何检查映射中是否存在某个键。

4.3 字符串

在Go语言中,string是一种内建类型,表示字节的序列。这些字节通常用于表示Unicode字符序列。String是不可变的,也就是说,一旦一个字符串被创建,就不能修改它。

字符串之间可以进行比较操作(==、!=、<、>),而字符串与字节数组之间可以通过+号进行拼接,也可以通过==进行比较操作。字符串也可以通过使用索引语法s[i]获取指定位置的字节,但不可进行修改操作。

Go语言的string类型是不可变的,这意味着你不能修改字符串的内容。如果你需要一个可变的字符串,可以将字符串转换为字节数组([]byte),然后进行修改操作。

在Go语言中,字符串的本质是一个字节数组([]byte)。因此,它们之间可以互相转换。例如,你可以将字符串转换为字节数组,然后对字节数组进行修改,再将修改后的字节数组转换回字符串。

需要注意的是,字符串的长度是固定的,不能进行修改。如果你需要一个可变长度的字符串,可以将字符串转换为切片(slice),然后进行修改操作。

以下是一些关于Go语言string类型的示例代码:

package main  

import "fmt"  

func main() {
// 声明一个字符串变量
str := "Hello, World!" // 获取字符串的长度
fmt.Println(len(str)) // 输出: 13 // 将字符串转换为字节数组
bytes := []byte(str) // 修改字节数组中的某个元素
bytes[0] = 'M' // 将修改后的字节数组转换回字符串
str = string(bytes)
fmt.Println(str) // 输出: Mello, World!
}

以上代码演示了如何声明一个字符串变量、获取字符串的长度、将字符串转换为字节数组、修改字节数组中的元素,以及将修改后的字节数组转换回字符串。

4.3.1 字符串的底层机构

在Go语言中,字符串底层结构是一个字节序列,其数据结构定义如下:

type stringStruct struct {
str unsafe.Pointer
len int
}

这里,str是一个指向底层字节数组的指针,而len表示字符串的字节长度。这个结构体定义在runtime/string.go文件中。

字符串的赋值操作实际上是结构体的复制过程,不包含指针指向的内容的复制。这意味着,字符串是不可变的,一旦初始化后就不能修改。如果你需要一个可变的字符串,可以将字符串转换为字节数组,然后进行修改操作。修改后的字节数组再通过string()函数可以转回为字符串。

另外,字符串可以支持切片操作,不同位置的切片底层访问的是同一块内存数据。由于只读的特性,相同字符串面值常量通常对应同一个字符串常量。

需要注意的是,Go语言的字符串底层存储是基于UTF-8编码的。UTF-8是一种可变长度的编码方式,每个字符由1到4个字节组成,具体取决于字符的Unicode码位。在UTF-8编码中,ASCII字符只需要1个字节表示,双字节字符需要2个字节表示,以此类推。因此,对于非ASCII字符的字符串,其长度可能不等于字节长度。

综上所述,Go语言的字符串底层数据结构是一个指向底层字节数组的指针和字符串长度的组合。字符串是不可变的,但可以通过转换为字节数组进行修改。字符串支持切片操作和UTF-8编码。

4.3.2 string和[]byte的转化

在Go语言中,可以使用unsafe包来转换string[]byteunsafe包提供了一组函数,可以在不进行边界检查的情况下直接访问内存地址。

要将string转换为[]byte,可以使用[]byte()类型强转,可以使用unsafe.Pointer()函数将字符串的指针转换为字节数组的指针,然后使用*(*[]byte)(unsafe.Pointer(uintptr))进行类型断言。

下面是一个示例代码:

package main  

import (
"fmt"
"unsafe"
) func main() {
str := "Breeze0806"
strPtr := unsafe.Pointer(&str)
bytesPtr := *(*[]byte)(unsafe.Pointer(uintptr(strPtr)))
fmt.Println(bytesPtr)
}

要将[]byte转换为string,可以使用string()类型强转,也可以使用以下方式。但是,需要注意的是,如果字节数组包含无效的UTF-8序列,转换后的字符串可能会出现乱码。

下面是一个示例代码:

package main  

import (
"fmt"
"unsafe"
) func main() {
b := []byte{66, 114, 101, 101, 122, 101, 48, 56, 48, 54}
str = *(*string)(unsafe.Pointer(&b))
fmt.Println(str) // Output: Breeze0806
}

需要注意的是,使用unsafe包进行类型转换是不安全的,因为它绕过了Go语言的类型检查机制。因此,应该谨慎使用,并确保转换后的数据是正确的。

6. 下一篇

《使用go语言的数据类型解决leetcode题目》

4.go语言复合类型简述的更多相关文章

  1. GO语言复合类型04---映射

    package main import "fmt" /* 映射(map)是键值对(key-value)数据的集合 根据键key可以快速检索值value 键值的类型可以是任意的,ke ...

  2. GO语言复合类型03---切片

    切片相当于长度可以动态扩张的数组 array[start:end]从数组身上截取下标为[start,end)片段,形成切片start代表开始下标,不写默认代表从头开始切end代表结束下标(本身不被包含 ...

  3. GO语言复合类型02---数组

    package main import "fmt" /* 固定长度.固定类型的数据容器 */ /*数组的声明*/ func main031() { //var array [5]i ...

  4. GO语言复合类型01---指针

    package main /* %T 类型占位符 %v 值占位符 %p 地址(指针)占位符,只有地址才能替换%p &value 对值取地址 *addr 对地址取值 **int 指向int型指针 ...

  5. GO语言复合类型05---递归

    package main import ( "fmt" "time" ) /* ·递归就是自己调自己 ·递归一定要有终止条件(否则就是无限死循环) */ /*使 ...

  6. go语言 类型:基础类型和复合类型

    Go 语言中包括以下内置基础类型:布尔型:bool整型:int int64 int32 int16 int8 uint8(byte) uint16 uint32 uint64 uint浮点型:floa ...

  7. 带你学够浪:Go语言基础系列 - 8分钟学复合类型

    ★ 文章每周持续更新,原创不易,「三连」让更多人看到是对我最大的肯定.可以微信搜索公众号「 后端技术学堂 」第一时间阅读(一般比博客早更新一到两篇) " 对于一般的语言使用者来说 ,20% ...

  8. C语言中的复合类型

    复合类型 一.掌握的类型 1. 指针数组 int * arr[10]; //arr是一个数组,有10个元素,每个元素都是一个指针,即arr是一个指针数组 int a,b,c,d; arr[0] = & ...

  9. postgresql 函数 参数为复合类型

    postgresql没有存储过程,但是函数功能很强大. 在近期开发的电商管理平台中,对于产品的类目管理,设计时有个属性字段,设为字符数组,但是EF不支持数组的操作,所以在添加和修改类目时,需要对属性的 ...

  10. go - 复合类型 array, slice, map

    Go 语言支持复合类型: 数组:array 切片:slice 指针:pointer 字典:map 通道:chan 结构体:struct 接口:interface 1. array   同一类型数据的集 ...

随机推荐

  1. Java程序设计复习提纲(下:图形界面)

    目录 上:Java程序设计复习提纲(上:入门语法) - 孤飞 - 博客园 (cnblogs.com) 基本语法与编译运行 数据类型和关键字 常用语法 数组与字符串 异常处理 中:Java程序设计复习提 ...

  2. 记一次 .NET 某汽贸店 CPU 爆高分析

    一:背景 1. 讲故事 上周有位朋友在 github 上向我求助,说线程都被卡住了,让我帮忙看下,截图如下: 时隔两年 终于有人在上面提 Issue 了,看样子这块以后可以作为求助专区来使用,既然来求 ...

  3. 2023-05-23:如果交换字符串 X 中的两个不同位置的字母,使得它和字符串 Y 相等, 那么称 X 和 Y 两个字符串相似。如果这两个字符串本身是相等的,那它们也是相似的。 例如,“tars“

    2023-05-23:如果交换字符串 X 中的两个不同位置的字母,使得它和字符串 Y 相等, 那么称 X 和 Y 两个字符串相似.如果这两个字符串本身是相等的,那它们也是相似的. 例如,"t ...

  4. SVM主体思路和代码实现

    之前学习的KNN算法属于直接将所有的训练图片数据化,根据图片的像素值进行判断,最简单的NN算法是用与待判断图片的差距最小(距离最近)的那张图片的类别当做此图片的类别,我们不难看到,1NN算法的正确性很 ...

  5. Eclipse的Console如何实现中文输出(Eclipse Display Chinese)

    最近遇到Eclipse的Console中文输出乱码的问题,现象如下: 在网上找到一些方法,一般均不好用,直到找到"如何在Eclipse控制台中显示汉字",链接如下 https:// ...

  6. WPF实现新手引导

    1. 半透明灰的遮罩层 新建一个遮盖的window窗体 canvas是后期可以在思显示高亮区域 //定义一个window将它的样式设置透明等可以覆盖到其他窗体上,其中遮罩层使用border控件 //原 ...

  7. 快速把PDF文档里的表格粘贴到excel的方法

    1 打开需要复制的PDf文件,点一下页面上方的"选择文本"按钮(如下图中手图标左边的箭头),以便选中文本 2 ctrl c 需要复制的表格,到excel中ctrl v.这时候所有类 ...

  8. Go语言中的结构体:灵活性与可扩展性的重要角色

    1. 引言 结构体是Go语言中重要且灵活的概念之一.结构体的使用使得我们可以定义自己的数据类型,并将不同类型的字段组合在一起,实现更灵活的数据结构.本文旨在深入介绍Go语言中的结构体,揭示其重要性和灵 ...

  9. gitlab docker升级报错

    背景 使用docker部署gitlab(9.5.4)后,发现合并代码有问题 日志: 看gitlab官网此问题已修复,由于上传了一批代码,又懒得重建,决定对gitlab升级 docker启动命令: do ...

  10. 通用密钥,无需密码,在无密码元年实现Passkeys通用密钥登录(基于Django4.2/Python3.10)

    毋庸讳言,密码是极其伟大的发明,但拜病毒和黑客所赐,一旦密码泄露,我们就得绞尽脑汁再想另外一个密码,但记忆力并不是一个靠谱的东西,一旦遗忘密码,也会造成严重的后果,2023年业界巨头Google已经率 ...