这部分为个人参考网上资料的增补内容。根据遇到的情况不断整理。
字符串基本概念
golang字符串用双引号表示,字符用单引号,这一点和C/C++语言一样。golang没有和C/C++那样搞两套字符(char,wchar_t),golang字符串存放的是UTF8编码的Unicode rune(码点)序列。要获取字符串长度,需要使用系统函数len(返回的其实是字节数)。
golang可以类似JavaScript之类的语言那样直接用+(加号)拼接字符串,用==和<比较字符串。golang字符串是immutable(不可变)的,因此,拼接本质上是产生新对象的过程。
golang可以和多数语言那样用下标来引用字符串中的单个字节 (不一定对应字符!但总存在一个字节值),还可以用Python类似的切片方式来引用子字符串(其实对应子字节串) ------注意,切片包含数据起始指针data、长度len、容量cap ,它差不多就是一个结构体,用字段指明引用了原始数据的哪些部分,它代表变长序列。切片语法定义**起始位置、结束位置(左闭右开区间)**这两项信息,如果没有提供,就用默认值。即便是引用字符串中所有字符的切片,它的性质也和原始字符串是不同的。
因为方括号下标和切片,都有可能"切中"非码点起始的位置,所以,以下代码最后一段可能乱码。用 for......range遍历字符串,它是按码点遍历的,以下代码前面部分。golang用UTF8码点存放字符串,而UTF8和Unicode码点顺序是相同的,前缀编码方式保证了中间位置也能很容易确定码点边界,又兼容ASCII码,所以,golang字符串可以方便转换成字节切片或码点切片,见下面代码中间部分。
Go
package main
import "fmt"
func main() {
str := "Hello, 世界!这里后面都是中文字符。"
for i, r := range str {
fmt.Printf("Character %d: %c\n", i, r)
}
fmt.Println("Length of string:", len(str))
fmt.Println("Length of runes:", len([]rune(str)))
fmt.Println("Length of bytes:", len([]byte(str)))
fmt.Println(str[14:17])
}
字符串输出除了上面的 fmt.Println 、fmt.Printf 向控制台输出,还可以用 fmt.Sprintf 格式化输出存放到新的字符串变量。
strings标准库
判断字符串a中是否包含子字符串b,可以用 strings.Contains(a,b) ,其中 a 、b 可以是变量,也可以是字面量。如果要判断的包含状态是前缀、后缀判断,可以换成 strings.HasPrefix 和 strings.HasSuffix 。
将字符串str中的内容x替换成y,替换对前n次查找执行,最终返回新字符串,使用 newStr := strings.Replace(str, x, y, n)。当旧内容x为空字符串时,查找中会匹配每个码点的边界位置。当执行次数n<0时,含义就是"无穷大"。例如,下面的代码,字符串起始和结束位置都会添加==
Go
str := "ok世界!"
newStr := strings.Replace(str, "", "==", -1)
fmt.Println(newStr) // 输出: ==o==k==世==界==!==
虽然我们可以用上述 strings.Replace 来去除字符串中所有空白,但经常遇到的是去掉左右两端或一端的空白,我们可以用 newStr := strings.TrimSpace(str) 去掉两端空白,另外,还有 strings.TrimLeft、strings.TrimRight、strings.TrimPrefix、strings.TrimSuffix 。对于需要左边或者右边定制化去掉某些字符,还可以用带回调的strings.TrimLeftFunc、strings.TrimRightFunc。
字符串分割可以用 parts := strings.Split(str, sep),返回的是一个个切片对象。如果 sep 是空字符串,那么类似上面的 strings.Replace,sep会匹配每个码点之后的位置,相当于会将字符串按码点分割成一串切片对象,每个对象就一个字符,起到打散效果。
strconv标准库
对于字符串和整数之间的相互转换,strconv包提供了和C语言类似的 strconv.Atoi 和 strconv.Itoa,因为字符串转换成整数是解析过程,可能存在无法转换的情况,所以,返回值是包含 error 的。事实上,strconv.Atoi 是通过 strconv.parseInt 实现的,后者更通用,可以指定基数和位数。
Go
str := "-123"
n := -456
num, err := strconv.Atoi(str)
if err != nil {
fmt.Println("Error converting string to int:", err)
return
}
sn := strconv.Itoa(n)
strconv.parseXxx 形式的字符串转数值的函数使用例子如下:
Go
b, err := strconv.ParseBool("true")
f, err := strconv.ParseFloat("3.1415", 64)
i, err := strconv.ParseInt("-42", 10, 64)
u, err := strconv.ParseUint("42", 10, 64)
相应地,也有数值转换为字符串的函数strconv.FormatXxx,官网例子如下:
Go
s := strconv.FormatBool(true)
s := strconv.FormatFloat(3.1415, 'E', -1, 64)
s := strconv.FormatInt(-42, 16)
s := strconv.FormatUint(42, 16)
regexp标准库
和 strings.Contains 对标的用来判断是否存在某个子串,用 regexp.MatchString 。因为正则表达式存在本身解析是否正确的问题,所以,该函数返回参数中包含 error。同类的还有 regexp.Match 和 regexp.MatchReader,差异是被匹配的对象分别是字节切片[]byte和io.RuneReader接口类型。
Go
matched, err := regexp.MatchString(`foo.*`, "seafood")
多数正则表达式操作,需要先解析正则表达式。完成正则表达式解析(称为"编译")过程的函数有:Compile 、MustCompile 、CompilePOSIX 、MustCompilePOSIX 。后面两个只是为了符合POSIX ERE语法,我们这里略过。Compile和MustCompile的差别是对待解析错误的方式不同,前者解析返回*RegExp指针的同时返回错误error,后者如果解析错误直接panic------如果正则表达式本身是静态的字符串,没必要用前者。编译后,也存在"实例方法" MatchString/Match/MatchReader,效果和前面的"类型方法"等价,或许需要多次使用同一个pattern时会显得有优势。
Go
re, err := regexp.Compile(".even")
var validID = regexp.MustCompile(`^[a-z]+\[[0-9]+\]$`)
fmt.Println(validID.MatchString("adam[23]"))
用正则来查找的方法很多,这些方法的名字是有特点的,它们可以用正则表达式来表达!这些方法模式为 Find(All)?(String)?(Submatch)?(Index)?------如果名字带有All,就会不断匹配互不重叠的子串,此时,有一个额外的参数n,用来指示最多返回多少个匹配的结果,n<0相当于最多无穷次(一般用n=-1)。如果带有String,那么参数是字符串类型,反之参数是字节切片类型,返回值也会和参数类型相应。如果名字带有Submatch,返回值是一个切片,可以指出每个子匹配(用于捕获组)。如果名字带有Index,匹配和子匹配通过字节索引位置来相互区分,即返回的不是被匹配的子字符串,而是它们的位置。
Go
re := regexp.MustCompile(`a(x*)b(y|z)c`)
fmt.Printf("%q\n", re.FindStringSubmatch("-axxxbyc-"))
fmt.Printf("%q\n", re.FindStringSubmatch("-abzc-"))
// ["axxxbyc" "xxx" "y"]
// ["abzc" "" "z"]
Expand 、ExpandString 函数实现动态扩展生成字节切片:可以提供模板(以下代码中template)和原始数据(以下代码中content),原始数据被正则匹配(以下代码中pattern)解析出需要的加工后数据(以下代码中submatches),加工后数据按正则匹配填充模板并附加到结果(以下代码中result)中实现动态扩展,最后返回结果。golang正则表达式语法概览参考 syntax package - regexp/syntax - Go Packages。下面的代码中,模式中的 (?m) 用来设置flag,表示使用多行模式(^分别匹配行首行尾而非文首文尾),这个分组不会被捕获。模式中 (?P\
Go
content := `
# comment line
option1: value1
option2: value2
# another comment line
option3: value3
`
pattern := regexp.MustCompile(`(?m)(?P<key>\w+):\s+(?P<value>\w+)$`)
template := "$key=$value\n"
result := []byte{}
for _, submatches := range pattern.FindAllStringSubmatchIndex(content, -1) {
result = pattern.ExpandString(result, template, content, submatches)
}
fmt.Println(string(result))
对于正则替换,同时有 ReplaceAll、ReplaceAllString、ReplaceAllLiteralString、ReplaceAllStringFunc 类型方法和实例方法(带有 Literal,待替换子串中的$符号不会作为子匹配引用记号被使用,它用作字符本身)。对于正则分割,也同时有 Split 类型方法和实例方法。
Go
re := regexp.MustCompile(`[^aeiou]`)
fmt.Println(re.ReplaceAllStringFunc("seafood fool", strings.ToUpper))
// SeaFooD FooL
s := regexp.MustCompile("a*").Split("abaabaccadaaae", 5)
// s: ["", "b", "b", "c", "cadaaae"]
标准库 regexp 地址:regexp package - regexp - Go Packages