golang学习随便记x[2,3]-字符串处理与正则表达式
这部分为个人参考网上资料的增补内容。根据遇到的情况不断整理。
字符串基本概念
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字符串可以方便转换成字节切片或码点切片,见下面代码中间部分。
package mainimport "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时,含义就是“无穷大”。例如,下面的代码,字符串起始和结束位置都会添加==
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 实现的,后者更通用,可以指定基数和位数。
str := "-123"n := -456num, err := strconv.Atoi(str)if err != nil {fmt.Println("Error converting string to int:", err)return}sn := strconv.Itoa(n)
strconv.parseXxx 形式的字符串转数值的函数使用例子如下:
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,官网例子如下:
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接口类型。
matched, err := regexp.MatchString(`foo.*`, "seafood")
多数正则表达式操作,需要先解析正则表达式。完成正则表达式解析(称为“编译”)过程的函数有:Compile、MustCompile、CompilePOSIX、MustCompilePOSIX。后面两个只是为了符合POSIX ERE语法,我们这里略过。Compile和MustCompile的差别是对待解析错误的方式不同,前者解析返回*RegExp指针的同时返回错误error,后者如果解析错误直接panic——如果正则表达式本身是静态的字符串,没必要用前者。编译后,也存在“实例方法” MatchString/Match/MatchReader,效果和前面的“类型方法”等价,或许需要多次使用同一个pattern时会显得有优势。
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,匹配和子匹配通过字节索引位置来相互区分,即返回的不是被匹配的字符串,而是它们的位置。
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函数实现动态扩展生成字节切片:可以提供模板和原始数据,原始数据被正则匹配解析出需要的加工后数据,加工后数据填充模板到模板并动态扩展,最后返回结果。
content := `# comment lineoption1: value1option2: value2# another comment lineoption3: 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 类型方法和实例方法。
re := regexp.MustCompile(`[^aeiou]`)fmt.Println(re.ReplaceAllStringFunc("seafood fool", strings.ToUpper))
// SeaFooD FooLs := regexp.MustCompile("a*").Split("abaabaccadaaae", 5)
// s: ["", "b", "b", "c", "cadaaae"]
标准库 regexp 地址:regexp package - regexp - Go Packages