字符串与数组,切片,复合数据结构
数组
静态语言的数组
- 大小确定
- 类型一致
- 数组内存分配时是连续空间
定义
初始化
func arraytest() {
// 一维数组定义 全部定义和java反着来
var arr1 [5]int
arr2 := [3]int{1, 3, 5}
arr3 := [...]int{2, 4, 6, 8, 10}
arr4 := [2]int{} // 不赋值会初始化为默认值
arr := [10]int {0:100, 2:200, 8:800} // 可以用index:value的形势指定赋值
fmt.Println(arr)
[100 0 200 0 0 0 0 0 800 0] // 打印结果
// 二维数组定义
var grid [4][6]bool // 4行6列
fmt.Println(grid)
fmt.Println(arr1, arr2, arr3)
// 数组遍历
for i := 0; i < len(arr3); i++ {
fmt.Println(arr3[i])
}
// range单个返回值为索引 两个返回值为索引,值 若不用索引只取值 可用匿名变量_取代i
for i, v := range arr3 {
fmt.Println(i, v)
}
}
记录最大值的索引与值
maxi := -1
maxValue := -1
for i, v := range arr3 {
if v > maxValue {
maxi, maxValue = i, v
}
}
return maxi, maxValue
range类似python的 for i, element in enumerate(seq):
数组操作
- 求长度len(arr)
- 数组的遍历 for index,element := range arr {} or for i:= 0; i < len(arr); i++ {}
数组是值类型 函数传递要拷贝
a := [2]string{}
b := [3]string{}
a和b不是同一种类型, 长度不一样的数组类型不一样 a类型[2]string b类型[3]string 故而函数传参数组 类型必须一致
数组当参数必须指定大小 且值传递 修改某个索引的值不会引起调用方数组的修改
func printArray(arr [5]int) {
for _, v := range arr {
fmt.Println(v)
}
arr[0] = 100
}
[10]int 和[5]int是不同类型 printArray只能传[5]int
对应指针操作可以修改
func printArray(arr *[5]int) {
for _, v := range arr {
fmt.Println(v)
}
// 以下两种操作等价
arr[0] = 100
(*arr)[1] = 100
}
调用printArray(&arr)
go一般不直接使用数组 一般用切片
python的值传递是list[:]
切片
slice 相当于动态数组 用法类似python的list
- a := []int{1,2}
- a := make([]int, len, cap) **
- a := arr[:]
- a := *new([]int) 表达式new(T)将创建一个T类型的匿名变量
-
- new()返回指针 *new() 返回指针指向的类型 new([]int) 为*[]int类型 *new([]int) 为[]int切片
创建slice
var s []int // zero value for slice is nil 定义的切片会默认初始化 默认值为nil
var s []int
// nil可直接使用append 此时会分配一个容量为1的slice
for i := 0; i < 100; i++ {
s = append(s, i * 2)
}
s := []int{1,2,3} // 直接定义slice
s := make([]int, 16) // 分配一个长度len 16的slice 全是默认值0
s := make([]int, 16, 32) // 分配一个长度len 16 容量cap为32数组的slice
一般是半开半闭区间 前闭后开
arr := [...]int{0,1,2,3,4,5,6,7}
a := arr[2:5] // 2到4
a = arr[:5] // 0到4
a = arr[2:] // 2到7
a = arr[:] // 全部的视图
切片是对数组的视图 相当于引用 修改切片会导致原数组发生修改
函数调用
func printArraySlice(arr []int) {// []int 相当于切片传参
for _, v := range arr { // python用for in list 内部实现了一个魔法方法__contains__ 语法糖
fmt.Println(v)
}
}
// 调用
printArraySlice(arr[:])
slice本身没有数据,是对底层array的一个view
slice组成
ptr(初始指向) len(控制索引操作) cap(视图的长度)
可以向后扩展不能向前 向后扩展不能超越底层数组cap(s) s[i]不可以超越len(s)
cap长度为初始指针到视图的结尾
reslice操作
a = a[:]
a = a[2:]
a = a[:5]
相当于a[2:5]
slice操作
a := [...]int{0,1,2,3,4,5,6,7}
a1 = a[2:6] // 一共4个值
a1[4] // 报错不存在
a2 = a1[3:5] // 此时取3,4可以取出来
即切片操作针对的是原始视图,索引操作针对切片本身
即 0,1,2,3
a[1:3] 1, 2, 灰3
a := arr[2:3]
a1 := a[3:5]
fmt.Printf("arr=%v, len(arr)=%d, cap(arr)=%d", a1, len(a1), cap(a1))
a1=[5 6], len(a1)=2, cap(a1)=3
切片函数 操作
func append(slice []Type, elems ...Type) []Type // 可追加多个元素
添加元素:添加元素时若超越cap,系统会重新分配更大的底层数组
扩容机制:申请两倍容量的数组
由于值传递的关系,必须接收append的返回值
s = append(s, val)
func copy(dst, src []Type) int
copy slice
copy(slice1, slice2)
将s2的值赋值到s1 取交集赋值
删除操作
s2 = append(s2[:3], s2[4:]...) // 删除第3个元素 后面的元素前移
对于可变参数... 可传递slice 需要在后面加...
头尾弹出
head := s2[0]
s2 = s2[1:]
tail := s2[len(s2) - 1]
s2 = s2[:len(s2) - 1]
go切片和python的列表区别
- go的slice是数据结构,python的slice是一种操作,python数据结构是list
- slice基于数组实现,list也是数组实现
- go的slice切片获取底层数组的引用其操作会影响底层数组,python的list切片获取新的底层数组 复制
- go静态语言对处理器友好 python动态语言对开发者友好
slice原理
1. 内置函数copy(a, b) b只复制a长度的元素给a
因为b不会拓展a的空间
2. a := b[:] 修改a a和b都能显示修改的值
a,b底层指向同一个数组
b = [3]int{}
a := b // 值拷贝 复制的数组 修改拷贝不会修改原数组
c := b[:] // 切片数组引用 修改切片会修改数组
3. 内置函数a = append(a, 1) 参数a和b指向同一个数组 append后返回的a和b显示的元素不一致
切片的扩容机制 动态扩容
cap为0 扩容到1 之后每次扩容为原来的2倍
当旧切片长度大于等于1024 每次扩容1/4 即新容量是原来的1.25
扩容之后的新数组和之前的数组是两个数组 切片会指向新的数组
4. cap(a) 切片容量大于等于 len(a) 切片长度
5.make遇到append; a := make([]int, 2); b := append(a, 3); 打印 0 0 3 make的len定义数组已经存放了元素 一共len个
定义空切片 数组容量3 a := make([]int, 0, 3)
- 切片的底层是数组 由Ptr *Elem; Len int; Cap int;组成
- 切片的Cap是Ptr指向位置向后所有容量 即 切片只能向后切不能向前切 a := b[2:5] c := a[3:6] 但是不能 c := a[1:6]
list
go也有list 基于指针的链表一般用不到
- list 的初始化有两种方法:分别是使用 New() 函数和 var 关键字声明,两种方法的初始化效果都是一致的
-
- 通过 container/list 包的 New() 函数初始化 list
-
-
- a := list.New()
-
-
- 通过 var 关键字声明初始化 list
-
-
- var a := list.List
-
- 列表与切片和 map 不同的是,列表并没有具体元素类型的限制,因此,列表的元素可以是任意类型
- 双链表支持从队列前方或后方插入元素,分别对应的方法是 PushFront 和 PushBack
l := list.New()
element := l.PushBack("1") // 尾push 返回元素句柄*list.Element 配合其他方法删除或插入元素
l.PushFront(2) // 头push
// 返回一个 *list.Element 结构,若需要删除插入的元素,通过 *list.Element 配合 Remove() 方法进行删除
// 在1之后添加3
l.InsertAfter("3", element)
// 在1之前添加4
l.InsertBefore("4", element)
// 删除1
l.Remove(element)
方法 | 作用 |
---|---|
InsertAfter(v interface {}, mark * Element) * Element | 在 mark 点之后插入元素,mark 点由其他插入函数提供 |
InsertBefore(v interface {}, mark * Element) *Element | 在 mark 点之前插入元素,mark 点由其他插入函数提供 |
PushBackList(other *List) | 添加 other 列表元素到尾部 |
PushFrontList(other *List) | 添加 other 列表元素到头部 |
遍历list链表:遍历双链表需要配合 Front() 函数获取头元素,遍历时只要元素不为空就可以继续进行,每一次遍历都会调用元素的 Next() 函数
l := list.New()
// 尾部添加
l.PushBack("1")
// 头部添加
l.PushFront(2)
for i := l.Front(); i != nil; i = i.Next() {
fmt.Println(i.Value)
}
map
map
- map的key需要支持’==’ 和 ‘!=’ 比较运算符 不能用 function, map,slice
-
- 数组和不含有不支持类型的结构体可当key
- map判断包含 v, exist := m[“k”] 通过exist的布尔值判断;python的字典 通过 k in dict
定义 map[key type]value type
func cache() {
// make分配内存的map
cache := make(map[string]string) // cache == empty map
cache["name"] = "cache"
// 普通map
m := map[string]string {
"key": "value",
}
// var定义
var m2 map[string]int // m2 == nil
// 复合map
m1 := map[string]map[string]string {
"key": {
"key": "value",
},
}
// 函数
mf := map[string]func(int, int) int{
"add": func(a, b int) int {
return a + b
},
"subtract": func(a int, b int) int {
return a - b
},
}
// 调用
fmt.Println(mf["add"](3, 4))
}
遍历
for k, v := range m { // 可用匿名变量省略v
fmt.Println(k, v) // 遍历无序
}
map是hashmap 其元素无序
赋值
一个返回值
k := m["key"] // key不存在返回默认值 字符串是空串 int是0 type是nil
两个返回值
k, exist := m["key"] // 若key不存在 k赋值默认值 exist赋值false
删除操作
delete(m, "key") // 之后再获取exist为false 不能用m["key"] = nil 返回的exist依然为true
map操作总结
创建:make(map[k]v)
获取元素:m[k]
key不存在,获得value类型的初始值
用value, exist := m[k] 来判断是否存在key
用delete()函数删除一个key
遍历
使用range遍历k或k,v
不保证遍历顺序,若需要顺序则获取key放入slice手动排序
使用len获取元素个数
map的key
map使用哈希表,必须能比较相等
除了slice,map,function的内建类型都可以作为key
Struct类型不包含上述字段,也可作为key
无重复字符的最长字串 只能用于英文数字 2字节的byte
func max(x, y int) int {
if x < y {
return y
}
return x
}
// 分左右指针 i为左指针
func lengthOfLongestSubstring(str string) int {
m := map[byte]bool{}
n := len(str)
end, ans := -1, 0
for i := 0; i < n; i++ {
if i > 0 {
delete(m, str[i - 1])
}
for end+1 < n && !m[str[end+1]] {
m[str[end + 1]] = true
end++
}
ans = max(ans, end - i + 1)
}
return ans
}
// 遍历一遍 i为右指针
// 对每一个字母
/**
lastOccurred[x]不存在,或者< start 即索引落在当前指针左边 无视
lastOccurred[x] >= start 索引落在当前指针右边 则更新start为索引左1
**/
func lengthOfLongestSubstr(s string) int {
lastOccurred := make(map[byte]int)
start, maxLength := 0, 0
for i, ch := range []byte(s) { // string索引取出来的是int32 rune
if lastIndex, ok := lastOccurred[ch]; ok && lastIndex >= start {
start = lastIndex + 1
}
maxLength = max(maxLength, i - start + 1)
lastOccurred[ch] = i
}
return maxLength
}
字符串
- 字符串是常量,可通过数组索引访问字节单元,但不能修改某字节的值
- 字符串转为[]byte(s)切片会复制 数据量大时注意 发生类型强转都会发生内存拷贝
a := ""
b := []byte(a)
- 避免类型强转导致内存拷贝
type StringHeader struct { // 字符串的底层结构
Data uintptr
Len int
}
type SliceHeader struct { // 切片的底层结构
Data uintptr
Len int
Cap int
}
1.unsafe.Pointer(&a)方法可以得到变量a地址的unsafe.Pointer指针
2.(*reflect.StringHeader)(unsafe.Pointer(&a)) 可以把字符串a地址的unsafe.Pointer指针转成底层结构的指针
3.(*[]byte)(unsafe.Pointer(&b)) 可以把b底层结构体转成byte的切片的指针
4.再通过 *转为指针指向的内容 之后获取时 通过长度遍历 每个元素的长度由指针类型确定 如[]byte切片 指针每个元素遍历1个字节
a :="aaaa"
b := *(*reflect.StringHeader)(unsafe.Pointer(&a))
c := *(*[]byte)(unsafe.Pointer(&b))
fmt.Printf("%v",c)
- 字符串类型底层实现是一个二元数据结构,一个指针指向字节数组的起点,另一个是长度
例:runtime/string.go
type stringStruct struct {
str unsafe.Pointer
len int
}
- 基于字符串创建的切片和原字符串指向相同的底层字符数组,不能修改,对字符串的切片操作返回的字串仍然是string而非slice
- 字符串运算
a := "a"
b := "b"
// 字符串拼接
c := a + b
// 遍历字节数组
for i := 0; i < len(c); i++ {}
// 遍历rune数组
for i, v := range c {}s
- 字符串和切片的转换:字符串可转换为字节数组,也可转为Unicode的字数组
b := []byte(a)
c := []rune(a)
字符串的长度
python中len() 获取字符串的长度 编码细节隐藏
go中len() 获取字符串字节的长度 utf8动态编码 英文一字节 中文3字节 转移字符是一个字节\n
类型转换
r := []rune(str) // 每个字符转一个rune 也可以[]int32(str)
len(r) // 字符的数量 即字符串的长度 但是rune浪费空间 英文1字节占用一个rune 4字节
字符串转义符
python的转义 \n or '""' or r'xxx'
go的转义 \n or `""` ``内可以输入各种需要转义的字符
字符串操作
- 是否包含 strings.Contains(parentStr, subStr) parentStr是否包含subStr字串
- python用法 是否包含 if subStr in parentStr: true表示包含字串
- python用法 某个字符包含在切片r中 if c in r:
- 某个字串的开始位置 strings.Index(parentStr, subStr)
- 统计出现的次数 string.Count(parentStr, subStr)
- 前后缀 strings.HasPrefix(parentStr, prefix) strings.HasSuffix(parentStr, suffix)
- 大小写转换 strings.ToUpper(str) strings.ToLower(str)
- 字符串的比较 strings.Compare(stra, strb) // 比较两者的ascii码 返回-1(a的ascii码小于b的码) 1 0 ;若是多个字符的字符串会逐个字符对比ascii码
- trim strings.TrimSpace(str) 只去掉空格 Trim(str, curset) 去掉左右指定curset TrimLeft TrimRight
- 字符串分割 strings.Split(str, sep) 用指定的sep分割字符串为数组 返回切片
- 合并 join strings.Join(arrs, sep) 用sep将切片指向的数组连接起来
- 字符串替换 strings.Replace(srcStr, old, new, n) 用new替换src中的old 替换n次
字符串格式化
fmt.Println(str + int) 不能直接将字符串和整型相加 需要str + strconv.ItoA(int) 故而需要字符串格式化 fmt.Printf()
python中可以前缀f格式化字符串
println(f"name:{name}, 字符串:{变量名}")
go用fmt.Printf
%d十进制数字%3d前3个空格,%x十六进制,%b二进制,%o二进制
%T类型打印,%v原来是什么值就打印什么值 数组打印[1,2] 缺省格式
%c 字符,%U unicode值
fmt.Sprintf 可以将格式化字符串用于赋值
输入格式化用 fmt.Scan
var str string
fmt.Scanln(&str)
fmt.Scanf("%s", &str)
示例
utf-8 可变 英文1字节 中文3字节
s := "hello世界"
fmt.Println(len(s))
for _, b := range []byte(s) {
// utf-8编码 可变
fmt.Printf("%X ", b)
}
// 11
// 68 65 6C 6C 6F E4 B8 96 E7 95 8C
for i, ch := range s {
// unicode编码 2字节前面空0
fmt.Printf("(%d, %X)", i, ch)
}
utf8操作
// 获取rune对应长度
fmt.Println("Rune count:", utf8.RuneCountInString(s))
bytes := []byte(s)
for len(bytes) > 0 {
// 解码 每次解码一字符 size是该字符长度 单位byte
ch, size := utf8.DecodeRune(bytes)
bytes = bytes[size:]
fmt.Printf("%c", ch)
}
直接用rune遍历 一个字符一个字符获取 一次获取4字节 重新开辟rune数组
带有 range 子句的 for 语句会先把被遍历的字符串值拆成一个字节序列,[]rune是转换成golang中的rune字符类型,它本质是int32,存储的值是该字符对应的unicode码点
for i, ch := range []rune(s) {
fmt.Printf("(%d, %c)", i, ch)
}
rune相当于go的char
用utf8.RuneCountInString获得字符数量
使用len获得字节长度
[]byte获得所有字节
[]rune会先转换获得所有字符
改造 支持中文 国际化
func lengthOfLongestSubstr(s string) int {
lastOccurred := make(map[rune]int) // byte换rune
start, maxLength := 0, 0
for i, ch := range []rune(s) { // string索引取出来的是int32 rune
if lastIndex, ok := lastOccurred[ch]; ok && lastIndex >= start {
start = lastIndex + 1
}
maxLength = max(maxLength, i - start + 1)
lastOccurred[ch] = i
}
return maxLength
}
其他字符串操作
strings包下
Fields,Split,Join
Contails,Index 查找字串
ToLower,ToUpper
Trim,TrimRight,TrimLeft
`` :跨行字符串 不转译字符串的意思,可以json yaml 等一起使用
可作为struct tag
ascii
二进制在计算机中通过补码表示 补码是反码+1 反码正数是原码 负数是原码的反过来
计算机存储单位是字节 1byte=8bit 1字节表示2^8个数
python中查看二进制用内置函数 bin() 看ascii用ord() 本质是unicode 对应go的fmt.Printf("%c, %d", c, d) 打印字符,数字 %T打印类型rune int32
ascii码一开始是一个字节表示
统一编码unicode unicode兼容ascii码
URL编码是一种用于将URL中的非ASCII字符的特殊字符转换为可以为Web浏览器和服务器普遍接受的、有明确的表示形式的格式,因为URL只能通过使用ASCII字符集(十六进制)将特殊字符在Web浏览器和服务器上显示
UTF-8和UTF-16是数据传输的动态编码规则