Go 1.19.4 字符串-Day 06

1. 编码表

计算机中只有数字(0和1),如果有一个字符串(字符串由字符组成)要存储,在内存中该如何表达这个字符串?

那么所有的字符都必须数字化,也就是一个字符对应一个特定的数字,最终这些数字和字符一一对应,形成了一张表,也就是“编码表”。

目前常见的编码表如下:

  1. ASCII编码表
  2. Unicode编码表
  3. UTF-8编码表(属于Unicode的一种)

1.1 ASCII编码表

ASCII(美国信息交换标准代码)编码表,是最早的字符编码标准,它用7位(后来扩展到8位,也就是一个字节)来表示字符。
最开始主要用来表示拉丁字符,如大小写的英文字母、数字、标点符号和一些控制字符。
但由于它只能表示128个字符,无法涵盖世界上所有语言字符(如汉字),所以后续还延伸出了其他编码表。
在这里插入图片描述

需要熟记的一些特殊字符,注意字符0-9和十进制0-9是有区别的
在这里插入图片描述

1.2 Unicode编码表

Unicode 是一个旨在解决字符编码混乱问题的国际标准,它为每种语言的每个字符分配了一个唯一的数字码(码点)。

Unicode 包括了ASCII中的所有字符,并扩展了数以万计的其他字符,以支持世界上几乎所有的书写系统。

Unicode 的实现方式有多种,包括UTF-8、UTF-16、UTF-32等,这些实现方式使用不同数量的字节来表示Unicode码点。

1.3 UTF-8编码表

UTF-8是一种可变长度的Unicode字符编码方式,它使用1到4个字节来表示一个字符。
对于ASCII字符,UTF-8的编码方式与ASCII编码表相同,即使用一个字节来表示。
对于其他Unicode字符,UTF-8使用多个字节进行编码,从而能够支持大量的字符,并且兼容ASCII。
UTF-8的优点在于其兼容性(与ASCII兼容)和节约空间(对于大多数常用字符,只需要一个或两个字节)。

2. 字符

本质上来说,计算机中一切都可以表示成一个字节一个字节的,字符串也是多个字节组合而成,就是一个个字节形成的有序序列。

但是对于多字节编码的中文来说,用一个字节描述不了,需要多个字节表示一个字符。

Go在表示一个字符时,如果可以使用ASCII码表,就用一个字节来表达,对应byte类型,byte兼容ASCII码表,别名uint8,占用1个字节。
但是声明变量时,如果不指定数据类型,默认会定义为int32类型,占用4字节。

Go在表示汉字等字符时,要用Unicode码表,对应runte类型,别名int32,占用4个字节。

注意:
单引号括起来的是字符,且一个单引号中只能有一个字符,否则会报错。

双引号括起来的是字符串。
下面代码举例说明:

2.1 字符使用注意事项

注意:字符是不能使用len函数来统计长度的,且一个单引号中只能有一个字符。
len只能用于:字符串、数组、切片、map、channel。
字符默认是rune类型。

package mainimport ("fmt"
)func main() {// s := 'ab' 这样是不可以的,一个单引号中只能有一个字符s := 'a' // 不指定类型,默认为int32(),占用4字节fmt.Printf("s的类型为:%T", s)
}
==========调试结果==========
s的类型为:int32

如上述代码,最终结果显示’a’为int32,也就是rune类型,为什么呢?
在go中,并不会因为你是一个字符(‘a’)就把你当成byte类型,默认情况下一个字符(‘a’)都是当成了rune类型(int32),占用4字节。

那如何让go把一个字符(‘a’)当成byte类型来处理呢?
如下代码:

package mainimport ("fmt"
)func main() {// s := 'a' //这样定义字符,go默认会当成rune(int32)类型处理var s byte = 'a'fmt.Printf("s的类型为:%T", s)
}
==========调试结果==========
s的类型为:uint8

定义变量的时候指定类型为byte(uint8)就可以了。

2.2 字符串使用注意事项、

注意:在go中,len取字符串长度时,只取字符对应的字节数。而其他语言中使用len,则是取字符数。
并且,字符串中定义的是汉字时,使用的是utf-8编码表,一个汉字占用3个字节长度。

package mainimport ("fmt"
)func main() {s1 := "abc" // 字符串中,大小写字母依然使用acsii码表(utf8兼容ascii)s2 := "测试"  // 汉字占用3字节,使用utf-8编码表。fmt.Printf("s1的类型:%T s1的长度:%[2]d\ns2的类型:%[3]T s2的长度:%[4]d", s1, len(s1), s2, len(s2))
}
==========调试结果==========
s1的类型:string s1的长度:3
s2的类型:string s2的长度:6

2.3 字符串与字符序列(字符串)转换

如s1 := "abc这个字符串,我们还可以把它当成一个“有序字节序列”来看,因为字符串也是线性数据结构。
类似切片,因为字符串也有header,并且也有底层字节数组这个概念,但是用起来为了不增加学习难度,设计者把它的使用方式和其他语言统一了。

字符串在Go中是不可变的字节序列。对于ASCII字符,每个字符通常占用一个字节。但是,对于UTF-8编码的字符串,非ASCII字符(如中文字符)可能占用多个字节。

2.3.1 ASCII字符串转字节切片

字节切片是字节的序列。当你将一个ASCII编码的字符串转换为字节切片时,你得到的是字符串中每个字节的原始表示。对于中文字符,这意味着每个字符将被转换为多个字节。

注意这种强制类型转换操作,只能用在字符串上,字符是不可以的,因为字符只有一个元素,不算是字符序列。

package mainimport ("fmt"
)func main() {s1 := "abc" // 字符串中,大小写字母依然使用acsii码表(utf8兼容ascii)// 注意这里[]byte(s1),表示强制类型转换。如果是[]byte{},就是声明切片。t1 := []byte(s1) // 强制类型转换,把字符转成字节切片fmt.Println(t1)fmt.Printf("t1的元素:%v|t1的长度:%d|t1的容量:%d", t1, len(t1), cap(t1))
}
==========调试结果==========
[97 98 99] // 97 98 99,对应的就是ascii码表中abc的十进制数值
t1的元素:[97 98 99]|t1的长度:3|t1的容量:8

2.3.2 ASCII字符串转rune(int32)切片

package mainimport ("fmt"
)func main() {s1 := "abc"t2 := []rune(s1)fmt.Println(t2, len(t2), cap(t2))
}
==========调试结果==========
[97 98 99] 3 4

请问t1 := []byte(s1)和t2 := []rune(s1)有什么区别?
占用内存空间大小不同,byte只占用1字节内存空间,而rune占用4字节,
或者说元素之间的偏移量不同。
如下代码:

package mainimport ("fmt"
)func main() {s1 := "abc"t1 := []byte(s1)t2 := []rune(s1)fmt.Printf("t1首个元素内存地址:%d|t1第二个元素内存地址:%d\nt2首个元素内存地址:%d|t2第二个元素内存地址:%d", &t1[0], &t1[1], &t2[0], &t2[1])
}
==========调试结果==========
t1首个元素内存地址:824633819272|t1第二个元素内存地址:824633819273
t2首个元素内存地址:824633819296|t2第二个元素内存地址:824633819300

根据输出结果可以看到:
t1的首个元素和第二个元素相差1字节(偏移1字节)。
t2的首个元素和第二个元素相差4字节(偏移4字节)。

2.3.3 中文字符串转字节切片

字节切片是字节的序列。当你将一个UTF-8编码的字符串转换为字节切片时,你得到的是字符串中每个字节的原始表示(内存中的字节级表示)。
对于中文字符,这意味着每个字符将被转换为多个字节。

package mainimport ("fmt"
)func main() {s2 := "测试"t3 := []byte(s2)fmt.Println(t3, len(t3), cap(t3))
}
==========调试结果==========
[230 181 139 232 175 149] 6 8

通过结果可以看到:
其实把中文字符串强制转换成字节切片,和使用len(s2)效果相同,因为无论如何,字符串默认都是使用utf-8编码表的,一个元素始终都要占用3个字节长度,所以结果是6个unicode码表值,对应“测试”这俩汉字。

2.3.4 中文字符串转换成rune切片

首先​​rune​​切片是​​int32​​值的序列,用于表示Unicode码点,注意这里的Unicode和uft-8不要混为一谈。

当你将一个UTF-8编码的字符串转换为​​rune​​切片时,Go语言会遍历字符串中每个字符的字节,并根据UTF-8编码规则将它们解码为对应的Unicode码点。每个Unicode码点(无论它在UTF-8编码中占用一个、两个、三个还是四个字节)都会被转换为一个单独的​​rune​​值。

​对于中文字符来说,由于它们在UTF-8编码中通常占用三个字节,当你将这些字符转换为​​rune​​切片时,每个中文字符都会被转换为一个​​rune​​值。这个​​rune​​值就是该中文字符的Unicode码点,

package mainimport ("fmt"
)func main() {s2 := "测试"t4 := []rune(s2)fmt.Println(t4, len(t4), cap(t4))}
==========调试结果==========
[27979 35797] 2 2

通过返回结果也证实了上面的理论,中文字符串转换为rune切片后,每个元素都会被转换成对应的rune值(Unicode码点)。
并且可以直接拿着这个Unicode码点强制转换成string类型(byte也行)。

……省略部分代码
fmt.Println(string(27979)) //把单一的int转换成string
fmt.Println(string([]rune{27979}))//把rune序列转换成utf-8的string序列
fmt.Println(string([]byte{97, 98, 99}))//把rune序列转换成utf-8的string序列
fmt.Println(string([]byte{0x61, '\x62', 0x63}))
==========调试结果==========
测
测
abc
abc

3 . 字符串

特点:
字面常量,只读,不可变(指当前内存空间中)
线性数据结构,可以索引
值类型
utf-8编码

3.1 长度

使用内建函数len,返回字符串占用的字节数。时间复杂度为O(1),字符串是字面常量,定义时已经知道长度,记录下来即可。
之所以字符串的时间复杂度为O(1),是因为在它也有header,里面除了底层数组指针,再就是字符串长度了,所以len就相当于直接读取header中定义好的长度,速度很快。
但字符串的长度是不可变的。

3.2 索引

支持索引,索引范围[0, len(s)-1],但不支持负索引。
即使是有中文,索引指的是按照字节的偏移量。
时间复杂度O(1),使用索引计算该字符相对开头的偏移量即可。
对于顺序表来说,使用索引效率查找效率是最高的。
s[i] 获取索引i处的UTF-8编码的一个字节。

3.2.1 示例一

package mainimport "fmt"func main() {s1 := "abc"// s2 := "测试"fmt.Println(s1[0], s1[1], s1[2])
}
==========调试结果==========
97 98 99

为什么输出了97 98 99而不是"abc"?
首先这里可以使用索引,说明该对象是个字节序列,字节序列就是[]byte,在字节序列[]byte中按索引一个一个取,拿到的就是对应字符的ascii码。

如果要直接显示对应的字符,可以使用强制类型转换或者遍历。

3.2.2 示例二

package mainimport "fmt"func main() {// s1 := "abc"s2 := "测试"fmt.Println(s2[0], s2[1])fmt.Println(len(s2))
}
==========调试结果==========
230 181
6

这里依然是当字节序列来处理的。
由于"测试"是中文,使用utf-8编码,一个字符长度为3一共6。
所以使用索引取的话,也只能拿到对应位置上的utf-8编码,3个编码才能表示一个中文字符。

3.3 遍历

3.3.1 方式一:使用索引遍历

该方式就相当于直接对字符串进行字节遍历了,返回的都是字符对应的ascii码和unicode码。

package mainimport "fmt"func main() {s1 := "abc"s2 := "测试"s3 := s1 + s2for i := 0; i < len(s3); i++ {fmt.Println(i, s3[i])}
}
==========调试结果==========
0 97
1 98
2 99
3 230
4 181
5 139
6 232
7 175
8 149

3.3.2 方式二:使用for range

package mainimport "fmt"func main() {s1 := "abc"s2 := "测试"s3 := s1 + s2for i, v := range s3 {fmt.Println(i, v, string(v), s3[i])}
}
==========调试结果==========
0 97 a 97
1 98 b 98
2 99 c 99
3 27979230
6 35797232

for range这种写法,会把中文字符转换成rune类型,每个中文字符都会被转成一个rune值。
这里还要注意一下s3[i],这种相当于是在操作字节序列了,一次只能遍历一个字节,但是中文字符都是3字节的,所以使用索引不能完全取出来对应的中文字符,最后显示就会乱码。

4. strings库

strings提供了很多字符串操作函数,使用方便。
注意:字符串是字面常量,不可修改,很多操作都是返回新的字符串。

4.1 字符串拼接

除了使用+,还能使用Sprintf、Join和Builder。

  1. Sprintf:格式化拼接
  2. Join:使用间隔符拼接字符串切片
  3. Builder:推荐的字符串拼接方式

4.1.1 Sprintf拼接字符串

特别是复杂的字符串拼接,就非常推荐使用这种方式。

package mainimport "fmt"func main() {s1 := "abc"s2 := "测试"// s3 := s1 + s2s4 := fmt.Sprintf("%s-----%s", s1, s2)fmt.Println(s4)
}
==========调试结果==========
abc-----测试

4.1.2 Join拼接字符串

语法格式:strings.Join(字符串切片, “分隔符”)
该方式更适合处理已经存在的字符串切片,如果没有字符切片,更适合使用方式一,不然还要像下面一样写一个字符串切片,很麻烦。

package mainimport ("fmt""strings"
)func main() {s1 := "abc"s2 := "测试"s5 := strings.Join([]string{s1, s2}, "***")fmt.Println(s5)
}
==========调试结果==========
abc***测试

4.1.3 Builder拼接字符串

4.1.3.1 介绍

strings.Builder 是一个可变的数据结构,它允许你逐步构建一个字符串,而不是一次性生成。它内部维护了一个字节切片([]byte),随着添加操作的进行,这个切片会动态扩展。

在编程中,如果需要频繁地拼接字符串,使用 strings.Builder 可以提高效率。它比使用加号(+)拼接字符串更快,因为它避免了在每次拼接时创建新的字符串切片。

使用 strings.Builder 可以带来以下好处:

  • 性能提升: 在循环或大量字符串操作中,它比传统的字符串拼接方法更高效。
  • 内存使用优化: 减少因字符串拼接导致的内存分配和复制操作。
4.1.3.2 常用方法

Builder携带的很多方法来操作字符串,如下:

  • WriteString(s string) (n int, err error):将字符串s写入到strints.Builder中。
  • WriteByte(c byte) (err error):将单个字节 c 写入到 strings.Builder 中。
  • WriteRune(r rune) (n int, err error):将单个 Unicode 字符 r 写入到 strings.Builder 中。
  • Write(p []byte) (n int, err error):将字节切片 p 写入到 strings.Builder 中。
  • Grow(n int):增加 strings.Builder 的容量至少到 n 字节。如果 n 小于当前容量,此方法不执行任何操作。
  • String() string:返回 strings.Builder 当前内容的字符串表示。此操作不会影响 strings.Builder 的状态,可以继续使用它来追加更多内容。
  • Len() int:返回 strings.Builder 当前内容的长度。
  • Cap() int:返回 strings.Builder 内部字节切片的容量。
  • Reset():清空 strings.Builder 的内容,使其可以用于新的字符串构建。
  • Runes() []rune:返回包含 strings.Builder 内容的 Unicode 字符切片。
4.1.3.3 示例
package mainimport ("fmt""strings"
)func main() {s1 := "abc"s2 := "测试"// 声明构建器。必须要用var 自定义标识符 来声明。然后这里的builder就能使用strings.Builder中携带的各种方法var builder strings.Builderbuilder.Write([]byte(s1)) // 将"abc"写入构建器builder.WriteByte('-') // 将'-'写入构建器builder.WriteString(s2) // 将 "测试" 写入构建器s6 := builder.String() // 获取最终构建的字符串,并将其存储在变量 s6 中。fmt.Println(s6)
}
==========调试结果==========
abc-测试

4.1.4 字符串拼接总结

简单拼接字符串常用+、fmt.Sprintf。如果手里正好有字符串的序列,可以考虑Join。如果反复多次拼接,strings.Builder是推荐的方式,减少资源开销,如果涉及到使用for循环拼接,最好也用builder。

4.2 字符串查询

4.2.1 查询方法

  • Index:从左至右搜索,返回子串第一次出现的字节索引位置。未找到,返回-1。子串为空,也返回0。
  • LastIndex:从右至左搜索,返回子串第一次出现的字节索引位置。未找到,返回-1。
  • IndexByte、IndexRune与Index类似;LastIndexByte与LastIndex类似。
  • IndexAny:从左至右搜索,找到给定的字符集字符串中任意一个字符就返回索引位置。未找到返回-1。
  • Contains方法本质上就是Index方法,只不过返回bool值,方便使用bool值时使用。
  • LastIndexAny与IndexAny搜索方向相反。
  • Count:从左至右搜索子串,返回子串出现的次数。

4.2.2 Index示例

从左至右搜索,返回子串第一次出现的字节索引位置。未找到,返回-1。子串为空,也返回0。
func strings.Index(s string, substr string) int

  • s:我们定义的字符串或变量
  • substr:字符串的子串,就是s中的一部分。
  • string:substr的类型。
  • int:返回值的类型。
package mainimport ("fmt""strings"
)func main() {s1 := "www.baidu.com"fmt.Println(strings.Index(s1, ".")) // 第一个子串.的索引为3fmt.Println(strings.Index(s1, "w")) // 第一个子串w的索引为0fmt.Println(strings.Index(s1, "好")) // 第一个子串“好”的索引为16fmt.Println(strings.Index(s1, "t")) // 找不到的内容,就返回-1// 注意下面的两行代码fmt.Println(strings.Index(s1, "com你好")) // 这里一定要完全匹配"com你好",才会返回c的索引10。fmt.Println(strings.Index(s1, "你好com")) // 这种就不行,因为找不到"你好com"。
}
==========调试结果==========
3
0
16
-1
10
-1

4.2.3 IndexByte示例

strings.IndexByte 这个函数查找字符串中第一个匹配指定字节(byte)的位置。它将字符串视为一个字节序列,不考虑字符的多字节编码。这意味着它查找的是字节而不是字符,这对于ASCII字符是有效的,但对于Unicode字符可能会返回错误的结果。
注意:IndexByte只适用于查找大小(长度)为1字节的字符。

package mainimport ("fmt""strings"
)func main() {s1 := "www.baidu.com你好"fmt.Println(strings.IndexByte(s1, 'a'))fmt.Println(strings.IndexByte(s1, 0x61))// 下面这个就不可以,因为中文字符占3个字节长度。//fmt.Println(strings.IndexRune(s1, '你'))
}
==========调试结果==========
5
5

4.2.4 IndexRune示例

package mainimport ("fmt""strings"
)func main() {s1 := "www.baidu.com你好"fmt.Println(strings.IndexRune(s1, 'a'))fmt.Println(strings.IndexRune(s1, 0x61))fmt.Println(strings.IndexRune(s1, '你'))
}
==========调试结果==========
5
5
13

4.2.5 IndexAny示例

strings.IndexAny(s string, chars string) int

  • s: 我们定义的字符串或变量
  • chars:指定要搜索的字符合集
  • string:表示这个chars是个字符串类型,也就是说字符合集要用双引号。
  • int:这个是最终的返回值类型。
package mainimport ("fmt""strings"
)func main() {s1 := "www.baidu.com你好"// 不管字符合集中有多少个字符,都只会返回从左到右最先匹配到的这个字符的索引。fmt.Println(strings.IndexAny(s1, "awb."))
}
==========调试结果==========
0

4.2.6 LastIndex示例

从右至左搜索,返回子串第一次出现的字节索引位置。未找到,返回-1。
func strings.LastIndex(s string, substr string) int

package mainimport ("fmt""strings"
)func main() {s1 := "www.baidu.com你好"fmt.Println(strings.Index(s1, "w.b"))// 虽然是从右往左找,但不影响最终返回的索引结果fmt.Println(strings.LastIndex(s1, "w.b"))
}
==========调试结果==========
2
2

4.2.7 Contains示例

匹配字符串中有无包含指定的子串,返回的值为bool。
func strings.Contains(s string, substr string) bool

package mainimport ("fmt""strings"
)func main() {s1 := "www.baidu.com你好"fmt.Println(strings.Contains(s1, "你好"))fmt.Println(strings.Contains(s1, "好你"))}
==========调试结果==========
true
false

4.2.8 Count示例

Count计算s中substr的非重叠实例的个数。如果substr为空字符串,Count返回1 + s中Unicode码点的个数。
func strings.Count(s string, substr string) int

package mainimport ("fmt""strings"
)func main() {s1 := "www.baidu.com你好"fmt.Println(strings.Count(s1, "w"))fmt.Println(strings.Count(s1, "ww"))fmt.Println(strings.Count(s1, "www"))fmt.Println(strings.Count(s1, "你好"))fmt.Println(strings.Count(s1, ""))
}
==========调试结果==========
3
1
1
1
16

4.2.9 字符串查询总结

strings库携带的查询方式,时间复杂度都为O(n),查询效率极低,能不用就不用。如果实在要用,查询次数越少越好。

4.3 大小写转换

ToLower:转换为小写
ToUpper:转换为大写

package mainimport ("fmt""strings"
)func main() {s1 := "www.baidu.com你好"fmt.Println(strings.ToUpper(s1))
}
==========调试结果==========
WWW.BAIDU.COM你好

4.4 前后缀匹配

  1. HasPrefix:是否以子串开头
    语法:func strings.HasPrefix(s string, prefix string) bool
    该方式执行效率很高,底层基于索引匹配,时间复杂度O(1)。
  2. HasSuffix:是否以子串结尾。
    该方式执行效率很高,底层基于索引匹配,时间复杂度O(1)。

两种方式返回结果都为bool。

4.4.1 前缀匹配

package mainimport ("fmt""strings"
)func main() {s1 := "www.baidu.com你好"fmt.Println(strings.HasPrefix(s1, "w."))fmt.Println(strings.HasPrefix(s1, "www"))
}
==========调试结果==========
false
true

4.4.2 后缀匹配

package mainimport ("fmt""strings"
)func main() {s1 := "www.baidu.com你好"fmt.Println(strings.HasSuffix(s1, ".com"))fmt.Println(strings.HasSuffix(s1, "你好"))}
==========调试结果==========
false
true

4.5 移除

  1. TrimSpace:去除字符串两端的空白字符。
  2. TrimPrefix、TrimSuffix:如果开头或结尾匹配,则去除。否则,返回原字符串的副本。
  3. TrimLeft:字符串开头的字符如果在字符集中,则全部移除,直到碰到第一个不在字符集中的字符为止。
  4. TrimRight:字符串结尾的字符如果在字符集中,则全部移除,直到碰到第一个不在字符集中的字符为止。
  5. Trim:字符串两头的字符如果在字符集中,则全部移除,直到左或右都碰到第一个不在字符集中的字符为止。

4.5.1 移除字符串两端的空白字符(TrimSpace)

常用。

package mainimport ("fmt""strings"
)func main() {s2 := "\v\r\n\ta b\tc\r\nd\n\t"fmt.Println(s2)fmt.Println("-----------------")fmt.Println(strings.TrimSpace(s2))}
==========调试结果==========a b	c
d-----------------
a b	c
d

4.5.2 移除最左侧匹配的字符(TrimLeft)

package mainimport ("fmt""strings"
)func main() {// d之所以没有移除,因为间隔了一个c,c不在指定移除的字符集中。fmt.Println(strings.TrimLeft("abcdefg", "bad"))fmt.Println(strings.TrimLeft("abcdefg", "bacd"))
}
==========调试结果==========
cdefg
efg

4.5.3 移除最右侧匹配的字符(TrimRight)

和上面的示例原理相同。

package mainimport ("fmt""strings"
)func main() {fmt.Println(strings.TrimRight("abcdefg", "bacdg"))
}
==========调试结果==========
abcdef

4.5.4 移除字符集两端匹配的字符(Trim)

可移除的还包含\v\r\n等特殊字符。

package mainimport ("fmt""strings"
)func main() {fmt.Println(strings.Trim("abcdefg", "bacdg"))
}
==========调试结果==========
ef

4.5.5 前缀移除(TrimPrefix)

package mainimport ("fmt""strings"
)func main() {s2 := "www.baidu.com"fmt.Println(strings.TrimPrefix(s2, "www"))
}
==========调试结果==========
.baidu.com

4.5.6 后缀移除(TrimSuffix)

package mainimport ("fmt""strings"
)func main() {s2 := "www.baidu.com"fmt.Println(strings.TrimSuffix(s2, "com"))
}
==========调试结果==========
www.baidu.

4.6 分割

  1. Split:按照给定的分割子串去分割,返回切割后的字符串切片。
    切割字符串是被切掉的,不会出现在结果中。
    没有切到,也会返回一个元素的切片,元素就是被切的字符串。
    分割字符串为空串,那么返回将被切割字符串按照每个rune字符分解后转成string存入切片返回。
  2. SplitN:按照给定的子串和次数去切割。
  3. SplitAfter:和Split相似,但是不会把指定的子串切掉,而是在子串后面切一个空格出来。
  4. SplitAfterN:和SplitN相似,但是不会把指定的子串切掉,而是用空格来分割元素。
  5. Cut:从左到右匹配指定的子串,切掉匹配到的第一个子串,然后停止分割,并默认返回3个元素,2个string,1个bool

4.6.1 Split

split切割后的返回值,一定是一个字符串切片。
最常用的也就是Split。

package mainimport ("fmt""strings"
)func main() {s2 := "www.baidu.du你好"// 指定切割符为“du”。返回结果为一个字符串类型的切片fmt.Printf("%T %[1]v\n", strings.Split(s2, "du"))// 如果指定的切割符在字符串中不存在,则返回字符串原本的元素fmt.Printf("%T %[1]v\n", strings.Split(s2, "c")) // 指定一个不存在子串“c”// 指定子串为""时,会在每个字符中间切一刀。fmt.Println(strings.Split(s2, ""))
}
==========调试结果==========
[]string [www.bai . 你好] // 注意这里切完后,就是3个元素放在切片中的
[]string [www.baidu.du你好] // 没有东西切的,返回的一个元素放到切片中
[w w w . b a i d u . d u 你 好]

4.6.2 SplitN

SplitN(s, sep string, n int) []string
这里的n要分三种情况:

  1. n < 0:能切多少次,就切多少次,就相当于是Split,返回字符串切片。
  2. n == 0:不切,并返回没有元素的字符串切片(空切片),长度为0容量为0。
  3. n > 0:注意,这里的n,指的是返回元素的个数,而不再是切几次。
4.6.2.1 n < 0

能切多少次,就切多少次,就相当于是Split。

package mainimport ("fmt""strings"
)func main() {s2 := "www.baidu.du你好"fmt.Println(strings.SplitN(s2, "du", -1))
}
==========调试结果==========
[www.bai . 你好]
4.6.2.2 n == 0

不切,返回没有元素的字符串切片(空切片),长度为0容量为0。
如果是要生成一个空切片,不要用该方式,生成的切片长度和容量都为0,推荐使用make。

package mainimport ("fmt""strings"
)func main() {s2 := "www.baidu.du你好"fmt.Println(strings.SplitN(s2, "du", 0))
}
==========调试结果==========
[]
4.6.2.3 n > 0

注意,这里的n,指的是返回元素的个数,而不再是切几次。

package mainimport ("fmt""strings"
)func main() {s2 := "www.baidu.du你好"fmt.Println(strings.SplitN(s2, "du", 1))fmt.Println(strings.SplitN(s2, "du", 2))fmt.Println(strings.SplitN(s2, "du", 3))// 这里按照du切割后,最多也只能返回3个元素fmt.Println(strings.SplitN(s2, "du", 4))
}
==========调试结果==========
[www.baidu.du你好] // 返回1个元素
[www.bai .du你好] // 返回2个元素
[www.bai . 你好] // 返回3个元素
[www.bai . 你好] // 返回3个元素

4.6.3 SplitAfter

和Split相似,但是不会把指定的子串切掉,而是在子串后面切一个空格出来。

package mainimport ("fmt""strings"
)func main() {s2 := "www.baidu.du你好"fmt.Println(strings.SplitAfter(s2, "du"))
}
==========调试结果==========
[www.baidu .du 你好]

4.6.4 SplitAfterN

package mainimport ("fmt""strings"
)func main() {s2 := "www.baidu.du你好"fmt.Println(strings.SplitAfterN(s2, "du", 1))fmt.Println(strings.SplitAfterN(s2, "du", 2))fmt.Println(strings.SplitAfterN(s2, "du", 3))fmt.Println(strings.SplitAfterN(s2, "du", 4))
}
==========调试结果==========
[www.baidu.du你好]
[www.baidu .du你好]
[www.baidu .du 你好]
[www.baidu .du 你好]

4.6.5 Cut

package mainimport ("fmt""strings"
)func main() {s2 := "www.baidu.du你好"fmt.Println(strings.Cut(s2, "du"))// 切不到的话,第一个string返回原有的字符串,第二个string就是一个空串fmt.Println(strings.Cut(s2, "c"))
}
==========调试结果==========
www.bai .du你好 true
www.baidu.du你好  false // false前面其实还有一个空串

4.7 替换

Replace(s, old, new string, n int) string

  • n int:表示为替换的次数。
  • n < 0,等价ReplaceAll,全部替换
  • n == 0,或old == new,就返回s(实际是一个全新的副本)
  • n > 0,至多替换n次,如果n超过找到old子串的次数x,也就只能替换x次了
  • 未找到替换处,就返回s
package mainimport ("fmt""strings"
)func main() {s2 := "www.baidu.du你好"// 匹配到的所有子串都会被替换,类似于ReplaceAllfmt.Println(strings.Replace(s2, "du", ".com", -1))// 替换0次,依然返回相同内容的字符串,但这个字符串是一个全新的字符串。fmt.Println(strings.Replace(s2, "du", ".com", 0))fmt.Println(strings.Replace(s2, "du", ".com", 1))fmt.Println(strings.Replace(s2, "du", ".com", 2))
}
==========调试结果==========
www.bai.com..com你好
www.baidu.du你好
www.bai.com.du你好
www.bai.com..com你好

4.8 重复

重复一个字符串为指定的次数。

package mainimport ("fmt""strings"
)func main() {fmt.Println(strings.Repeat("#", 3))
}
==========调试结果==========
###

4.9 Map

strings.Map 函数用于将一个函数应用于字符串的每个字符上,并返回修改后的字符串。注意Map是一对一的映射,不能减少元素个数。
适合对每个字符都修改的场景。

package mainimport ("fmt""strings"
)func main() {s1 := "www.baidu.du你好"fmt.Println(strings.Map(func(r rune) rune {fmt.Println(r, "$$") // 打印每次遍历的rune值。return r // 返回最后Map拼接的字符串}, s1)) // 对s1进行rune遍历,可以理解为for range s1,每次只遍历一个rune值,并返回一个字符,遍历完后Map将每次返回的字符拼接成一个全新的字符串。
}
==========调试结果==========
119 $$
119 $$
119 $$
46 $$
98 $$
97 $$
105 $$
100 $$
117 $$
46 $$
100 $$
117 $$
20320 $$
22909 $$
www.baidu.du你好
package mainimport ("fmt""strings"
)func main() {s1 := "www.baidu.du你好"fmt.Println(strings.Map(func(r rune) rune {fmt.Println(r, "$$")return 46 //通过修改返回值,可以影响Map处理的最终结果}, s1))
}
==========调试结果==========
119 $$
119 $$
119 $$
46 $$
98 $$
97 $$
105 $$
100 $$
117 $$
46 $$
100 $$
117 $$
20320 $$
22909 $$
.............. // 全部修改为.了

5. 类型转换

5.1 数值类型转换

1.低精度(整数)向高精度(浮点数)转换可以,高精度向低精度转换会损失精度
2.无符号向有符号转换,最高位是符号位
3.byte和int可以互相转换
4.float和int可以相互转换,float转到int会丢失精度
5.bool和int不能相互转换
6.不同长度的int和float之间可以互相转换

5.1.1 有符号转无符号

package mainimport ("fmt"
)func main() {var i int8 = -1fmt.Println(i)var j uint8 = uint8(i)// 强制类型转换fmt.Println(j)
}
==========调试结果==========
-1
255

为什么int8的-1转为uint8后,结果变成了255?
首先有符号的最高位为符号位,当我们把一个有符号通过强制类型转换为无符号时,此时原先的最高位就变成了普通的数字,而不再表示符号。
接着说回-1,这里以一个字节为例,-1的int8的表示为0xff,怎么来的呢?这里就不得不说下原码、反码、补码了(正整数的原码、反码、补码都相同,负数不同)。

  • 原码:
    一字节的-1原码为1000 0001。负数的原码在计算机中是不用的,只是单纯给人看的。
  • 反码:
    符号位不变,其他位全部取反。
    如一字节的-1的反码为1111 1110。
  • 补码
    符号位不变,其他全部取反并且最低位+1。
    如一字节的-1的补码为1111 1111。
    计算机内部使用。

最终二进制0b1111 1111对应16进制0xff,对应10进制呢?
主要看我们赋予的数据类型,就像本示例中转成uint8后,就对应10进制的255。
那如果是int8呢?
补码1111 1111(补码做补码为原码),最高位符号位1表示负号依然不变,其他的取反,最低位+1,结果就是1000 0001,对应10进制就是-1。
总结:有符号和无符号的强制类型转换是有风险的,转换前自己必须计算出转换后的结果,否则就会异或-1为啥变成了255。

5.1.2 folat转int

无类型的浮点是不可以直接转成int的,只能转成float32或64。
除非定义时明确指定数据类型为float或者短格式声明,由系统自动推断float类型,但float转为int后,会直接丢弃小数部分。

package mainimport ("fmt"
)func main() {// fmt.Println(int(3.14))fmt.Println(float64(3.14))
}
==========调试结果==========
3.14
package mainimport ("fmt"
)func main() {var a = 3.14fmt.Println(int(a))
}
==========调试结果==========
3

5.1.3 字符结合无类型int

声明变量时,如果不指定字符的数据类型,默认会定义为int32类型,占用4字节。

package mainimport ("fmt"
)func main() {b := 'a'c := b + 1//无类型int是可以和int32运算的,系统会针对无类型int做隐式类型转换fmt.Printf("%T\n", b)fmt.Println(b)fmt.Println(c)
}
==========调试结果==========
int32
97
98

看个错误的

package mainimport ("fmt"
)func main() {b := 'a'c := 1//这样是不可以的,前面说过,不同数据类型不可以相互运算。//d := b + c // 此处的b为rune类型,c为自适应int。不能相互运算。d := int(b) + c // 解决办法就是转换为相同类型就可以了
}

5.2 类型别名和类型定义

package mainimport "fmt"func main() {var a byte = 'C'var b uint8 = 49fmt.Println(a, b, a+b)
}

思考一个问题?a和b能相加吗?
答案是肯定的,源码中定义了 type byte = uint8 ,byte是uint8的别名。
补充一点,一字节的C,Go会拿着它对应的acsii码和b相加。

再看下面一段代码:

5.2.1 类型定义

package mainimport "fmt"func main() {type myByte byte // 注意这种是自定义类型,不是定义别名var a byte = 'C'var c myByte = 50// 上面的myByte虽然和byte相同,但实际上GO是把它们当成了不同类型,所以不能相互运算fmt.Println(a, c, a + c) // 这里a + c就报错了,因为go认为myByte和byte不是同一种数据类型fmt.Println(a, c, a + byte(c))//强制类型转换后是可以的
}

5.2.2 别名定义

package mainimport "fmt"func main() {// 定义byte的别名为myBytetype myByte = bytevar a byte = 'C'var c myByte = 50fmt.Println(a, c, a+c)
}
==========调试结果==========
67 50 117

5.3 字符串转换

转换过程中应该基于nil做判断,下面只是一个基本的演示,所以没加。

5.3.1 将string转为int

5.3.1.1 strconv.Atoi

func Atoi(s string) (int, error)

  • s:要转换的字符串,它应该包含一个可解析的整数,如ascii码。
  • int:s转换后的int类型整数
  • error:有错误显示错误,没错误显示<nil>
package mainimport ("fmt""strconv"
)func main() {s1 := "127"fmt.Println(strconv.Atoi(s1))// 这里就是一个错误的转换,下面直接报错了fmt.Println(strconv.Atoi("xxx")) 
}
==========调试结果==========
127 <nil>
0 strconv.Atoi: parsing "xxx": invalid syntax
5.3.1.2 strconv.ParseInt

strconv.ParseInt 是 Go 语言标准库中 strconv 包提供的一个函数,用于将字符串转换为整数。
语法:ParseInt(s string, base int, bitSize int) (i int64, err error)

  • s:我们需要处理的字符串。
  • base:是字符串表示的数字的基数,比如10表示十进制,16表示十六进制。
  • bitSize:是结果整数的大小,比如0、4、8、16、32、64位等。注意单位是位(bit),如果配置为0,默系统会默认配置为64。
  • i:返回值,也就是转换后的整数,int64类型。
  • err:有错误显示错误,无错误显示nil。
package mainimport ("fmt""strconv"
)func main() {s1 := "127"// s1转换为10进制,占8位(1个字节)result, err := strconv.ParseInt(s1, 10, 8)if err != nil {fmt.Println("类型转换错误:", err)return}fmt.Printf("转换后的类型:%T|转换后的结果:%[1]v", result)
}
==========调试结果==========
转换后的类型:int64|转换后的结果:127

再看另一个例子

package mainimport ("fmt""strconv"
)func main() {s1 := "127"result, err := strconv.ParseInt(s1, 16, 64)//s1转为16进制,占64位if err != nil {fmt.Println("类型转换错误:", err)return}fmt.Printf("转换后的类型:%T|转换后的结果:%[1]v", result)
}
==========调试结果==========
转换后的类型:int64|转换后的结果:295

注意看这段代码:strconv.ParseInt(s1, 16, 64),实际上这里是把s1当成了16进制来处理,它占用64位,也就是0x127。
0x127转为10进制就是295。

5.3.2 将string转为float

5.3.2.1 strconv.ParseFloat

strconv.ParseFloat 是 Go 语言标准库中 strconv 包提供的一个函数,用于将字符串转换为浮点数。
语法:func strconv.ParseFloat(s string, bitSize int) (float64, error)

  • s:要转换的字符串
  • bitSize: 指定了结果浮点数的位数,通常是 32 或 64,分别对应 float32 和 float64。
  • float64:函数返回值类型。
  • error:有错误显示错误,无错误显示nil
package mainimport ("fmt""strconv"
)func main() {s2 := "3.14516"fmt.Println(strconv.ParseFloat(s2, 32))
}
==========调试结果==========
3.145159959793091 <nil>

为啥结果和最初定义的不同?
浮点数字符串转换为浮点数后,它只能无限接近这个数,并不能完全一模一样。

5.3.3 string转为bool

ParseBool(str string) (bool, error)

  • str:要转换的字符串,注意,要转的字符串只能是1, t, T, TRUE, true, True, 0, f, F, FALSE, false, False。
  • bool:最终结果为bool类型。
  • error:成功显示nil,失败显示错误。
package mainimport ("fmt""strconv""strings"
)func main() {t, ok := strconv.ParseBool("1")if ok != nil {fmt.Println("转换错误:", ok)}fmt.Println(t)fmt.Println(strings.Repeat("-", 20))t, ok = strconv.ParseBool("2")if ok != nil {fmt.Println("转换错误:", ok)}fmt.Println(t)
}
==========调试结果==========
true
--------------------
转换错误: strconv.ParseBool: parsing "2": invalid syntax
false

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1451650.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

腾讯云对象存储不绑定自定义备案域名不给下载应该如何处理?

从2024年1月1日起&#xff0c;腾讯云对象存储&#xff08;COS&#xff09;将实施新政策&#xff1a;新创建的存储桶不再支持使用path-style域名&#xff08;即存储桶绝对路径&#xff09;。此外&#xff0c;使用默认域名访问的新存储桶将不再支持任意类型文件的预览&#xff0c…

Mac error:0308010C:digital envelope routines::unsupported

背景&#xff1a; node版本20.14.0 执行npm run start命令的时候报错 问题&#xff1a; error:0308010C:digital envelope routines::unsupported 分析&#xff1a; 出现这个错误是因为 node.js V17版本中最近发布的OpenSSL3.0, 而OpenSSL3.0对允许算法和密钥大小增加了严…

VMware安装ubuntu22.04虚拟机超详细图文教程

一 、下载镜像 下载地址&#xff1a;Index of /ubuntu-releases/22.04.4/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror 二、创建虚拟机 打开VMware点击左上角文件&#xff0c;创建新的虚拟机&#xff0c;打开后如下图&#xff1a; 下一步&#xff0c;镜像文件就是…

视频号怎么保存视频到手机?推荐4种方法!

短视频已经成为了网友们的新宠&#xff0c;那么对于我们这些普通人来说&#xff0c;如何能够轻松提取视频号上的视频呢&#xff1f;今天&#xff0c;就让我们一起来探讨一下视频号视频提取各种方法和工具&#xff01; 虽然视频号视频的保存方式多种多样&#xff0c;但为了照顾那…

【嵌入式DIY实例】-Nokia 5110显示DS18B20传感器数据

Nokia 5110显示DS18B20传感器数据 文章目录 Nokia 5110显示DS18B20传感器数据1、硬件准备2、代码实现本文将介绍如何使用 ESP8266 NodeMCU 板和 DS18B20 数字温度传感器实现简单的温度测量站。 NodeMCU 微控制器 (ESP8266EX) 从 DS18B20 传感器读取温度值,并将其打印在诺基亚 …

怎么做才能推动产业园区的数字化转型和升级

树莓集团在产业园运营中建设了产业园共同体生态模型&#xff0c;以园区作为核心载体&#xff0c;汇聚了众多优质企业&#xff0c;形成了强大的产业集群效应。这一模型通过产业汇集、资源共享和生态构建&#xff0c;为企业提供了一站式的解决方案&#xff0c;助力企业在激烈的市…

基于SpringBoot+VueBBS论坛系统设计和实现(源码+LW+调试文档+讲解等)

&#x1f497;博主介绍&#xff1a;✌全网粉丝1W,CSDN作者、博客专家、全栈领域优质创作者&#xff0c;博客之星、平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; &#x1f31f;文末获取源码数据库&#x1f31f; 感兴趣的可以先收藏起来&#xff0c;还…

HCIA-Datacom H12-811 题库

LDP 邻居发现有不同的实现机制和规定&#xff0c;下面关于LDP 邻居发现的描述错误的是&#xff1a; A&#xff1a;LDP发现机制包括LDP基本发现机制和LDP扩展发现机制 B&#xff1a;LDP基本发现机制可以自动发现直连在同条链路上的LDP Peers C&#xff1a;LDP扩展发现机制够发现…

Wireless Network(百练,POJ)

题目链接: http://bailian.openjudge.cn/tm2019/F/ 2236 -- Wireless Network 题面描述: 思路: 这题开了10s&#xff0c;所以可以暴力点&#xff0c;每次修复一个点&#xff0c;就将该点相连的那些边建出来&#xff0c;总的时间复杂度为: O(nm)。关键在于如何判定两个点是否…

挂耳式耳机哪个牌子好性价比高、五大招牌力作精选归纳

如果说你很喜欢户外运动&#xff0c;日常生活中也是需要经常佩戴耳机&#xff0c;那么你一定有了解到耳机是开放式耳机&#xff0c;这类耳机无论在户外运动防水防汗还是在耳朵健康方面都具备它的优点&#xff0c;在市面上是很受欢迎的。 但面对市面上不同品牌的耳机都会显得眼…

网工内推 | 上海网工,熟悉华为数通,最高35K,IP/IE认证优先

01 四川茶姬企业管理有限公司 &#x1f537;招聘岗位&#xff1a;网络运维工程师 &#x1f537;任职要求&#xff1a; 1. 负责设计、部署和维护基于云平台的企业网络基础架构&#xff0c;包括公有云&#xff08;如阿里云、腾讯云、AWS、Azure&#xff09;和私有云环境&#x…

微信小游戏插件申请,微信小程序插件管理

微信小游戏的插件申请与小程序不一样&#xff0c;官方没有提供一个统一的管理入口进行申请插件&#xff0c;以及查看插件&#xff0c;没有小程序方便的&#xff1b; 小程序申请查看插件入口如下图所示&#xff1a; 小游戏的插件可以通过以下的方式进行申请&#xff1a; 如下…

怎么将webp转换jpg?关于图片格式转换的四种方法

怎么将webp转换jpg&#xff1f;在数字时代的浪潮中&#xff0c;图像格式的演变日新月异。在这个变化的潮流中&#xff0c;webp作为一种由谷歌开发的现代图像格式&#xff0c;旨在提供更高效的压缩和更快速的网络传输。它的出现&#xff0c;改变了我们处理图片的方式&#xff0c…

若依微服务Docker部署验证码出不来怎么办?

最近,有许多人反馈在使用 Docker 部署若依微服务项目时,遇到验证码无法显示的问题。本文将重点介绍解决该问题的注意事项以及整个项目的部署流程。之前我们也撰写过微服务部署教程,本文将在此基础上进行优化和补充。你也可以参考我之前写的部署教程:https://yang-roc.blog.…

JavaScript-事件监听

添加事件监听 语法&#xff1a;对象名.addEventListener(事件类型,要执行的函数) 作用&#xff1a;当事件触发时&#xff0c;就调用这个函数 事件类型&#xff1a;比如用鼠标点击&#xff0c;或用滚轮滑动&#xff0c;鼠标经过这些 要执行的函数&#xff1a;要做的事 &l…

Elasticsearch:简化数据流的数据生命周期管理

作者&#xff1a;来自 Elastic Andrei Dan 今天&#xff0c;我们将探索 Elasticsearch 针对数据流的新数据管理系统&#xff1a;数据流生命周期&#xff0c;从版本 8.14 开始提供。凭借其简单而强大的执行模型&#xff0c;数据流生命周期可让n 你专注于数据生命周期的业务相关方…

液晶拼接屏企业应该采取哪些措施来提升整体竞争力和市场地位呢?

步入智能科技时代以来&#xff0c;商显行业面对着各式各样的挑战&#xff0c;人工智能、AI大模型等整合中&#xff0c;液晶拼接屏企业应该采取哪些措施以提升整体竞争力和市场地位。下面小编个人观点简单说一下&#xff1b;下是一些关键的措施&#xff1a; 首先&#xff0c;加…

EndNote 专业的文献管理软件下载,强大的引用和参考文献生成功能

EndNote&#xff0c;它以其强大的功能和便捷的操作赢得了广大学术工作者的青睐&#xff0c;成为了他们不可或缺的研究助手。 EndNote软件的出现&#xff0c;极大地简化了学术文献的管理和组织工作。用户只需将收集到的文献导入软件&#xff0c;便可轻松实现对文献的分类、排序和…

白酒:茅台镇白酒的品鉴课程与文化学习

茅台镇&#xff0c;这个位于中国贵州省的美丽小镇&#xff0c;以其与众不同的自然环境和杰出的酿酒工艺&#xff0c;成为了世界著名的白酒产区。作为茅台镇的杰出代表&#xff0c;云仓酒庄豪迈白酒不仅在国内享有盛誉&#xff0c;在国际市场上也备受瞩目。为了更好地推广中国白…

Python的网络请求

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 在上一节中多次提到了URL地址与下载网页&#xff0c;这两项是网络爬虫必备而又关键的功能&#xff0c;说到这两个功能必然会提到HTTP。本节将介绍在P…