本文我们重点介绍 Go 语言字符串使用方式与技巧。
02 、字符串类型转换
Go 语言是强类型语言,在使用 Go 语言时,通常会遇到需要将字符串与其它类型相互转换的场景。
此时,我们可以使用标准库 strconv。
字符串 to 布尔
示例代码:
func main() {
v := "true"
if s, err := strconv.ParseBool(v); err == nil {
fmt.Printf("%T, %v\n", s, s)
}
}
输出结果:
bool, true
阅读上面这段代码,我们使用 func ParseBool(str string) (bool, error) 将字符串转换为布尔。需要注意的是,该函数接收参数的值是有限制的,除了 1、t、T、TRUE、true、True、0、f、F、FALSE、false、False 之外,其它任何值都会返回 error。
字符串 to 浮点型
示例代码:
func main() {
v := "3.1415926535"
if s, err := strconv.ParseFloat(v, 32); err == nil {
fmt.Printf("%T, %v\n", s, s)
}
if s, err := strconv.ParseFloat(v, 64); err == nil {
fmt.Printf("%T, %v\n", s, s)
}
if s, err := strconv.ParseFloat("NaN", 32); err == nil {
fmt.Printf("%T, %v\n", s, s)
}
// ParseFloat is case insensitive
if s, err := strconv.ParseFloat("nan", 32); err == nil {
fmt.Printf("%T, %v\n", s, s)
}
if s, err := strconv.ParseFloat("inf", 32); err == nil {
fmt.Printf("%T, %v\n", s, s)
}
if s, err := strconv.ParseFloat("+Inf", 32); err == nil {
fmt.Printf("%T, %v\n", s, s)
}
if s, err := strconv.ParseFloat("-Inf", 32); err == nil {
fmt.Printf("%T, %v\n", s, s)
}
if s, err := strconv.ParseFloat("-0", 32); err == nil {
fmt.Printf("%T, %v\n", s, s)
}
if s, err := strconv.ParseFloat("+0", 32); err == nil {
fmt.Printf("%T, %v\n", s, s)
}
}
输出结果:
float64, 3.1415927410125732
float64, 3.1415926535
float64, NaN
float64, NaN
float64, +Inf
float64, +Inf
float64, -Inf
float64, -0
float64, 0
阅读上面这段代码,我们使用 func ParseFloat(s string, bitSize int) (float64, error) 将字符串转换为 64 位浮点数。需要注意的是,该函数接收参数可以识别值为 NaN、Inf(有符号 +Inf 或 -Inf),并且忽略它们的大小写。
字符串 to 整型
示例代码:
func main() {
v32 := "-354634382"
if s, err := strconv.ParseInt(v32, 10, 32); err == nil {
fmt.Printf("s:%T, %v\n", s, s)
}
if s1, err := strconv.ParseInt(v32, 16, 32); err == nil {
fmt.Printf("s1:%T, %v\n", s1, s1)
}
v64 := "-3546343826724305832"
if s2, err := strconv.ParseInt(v64, 10, 64); err == nil {
fmt.Printf("s2:%T, %v\n", s2, s2)
}
if s3, err := strconv.ParseInt(v64, 16, 64); err == nil {
fmt.Printf("s3:%T, %v\n", s3, s3)
}
}
输出结果:
s:int64, -354634382
s2:int64, -3546343826724305832
阅读上面这段代码,我们使用 func ParseInt(s string, base int, bitSize int) (i int64, err error) 将字符串转换为整型。
需要注意的是,该函数的第一个入参为字符串类型的数值,可以 "+" 或 "-" 符号开头;
第二个参数指定进制,它的值如果是 0,进制则以第一个参数符号后的前缀决定,例如:"0b" 为 2,"0" 或 "0o" 为 8,"0x" 为 16,否则为 10;
第三个参数指定返回结果必须符合整数类型的取值范围,它的值为 0、8、16、32 和 64,分别代表 int、int8、int16、int32 和 int64。
细心的读者朋友们可能已经发现,示例代码中,第 2 和 第 4 返回错误,原因是第二个参数指定的进制与第一个参数的数值不相符,超出取值范围。
此外,函数 func ParseUint(s string, base int, bitSize int) (uint64, error) 与之类似,但是用于无符号整数。
在实际项目开发中,十进制使用的最多,所以标准库 strconv 提供了函数 func Atoi(s string) (int, error),它的功能类似 ParseInt(s, 10, 0),需要注意的是,它的返回值类型是 int(需要注意取值范围),而不是 int64。
布尔 to 字符串
示例代码:
func main() {
v := true
s := strconv.FormatBool(v)
fmt.Printf("%T, %v\n", s, s)
}
输出结果:
string, true
阅读上面这段代码,我们使用 func FormatBool(b bool) string 将布尔转换为字符串。
浮点型 to 字符串
示例代码:
func main() {
v := 3.1415926535
s32 := strconv.FormatFloat(v, 'E', -1, 32)
fmt.Printf("%T, %v\n", s32, s32)
s64 := strconv.FormatFloat(v, 'E', -1, 64)
fmt.Printf("%T, %v\n", s64, s64)
fmt64 := strconv.FormatFloat(v, 'g', -1, 64)
fmt.Printf("%T, %v\n", fmt64, fmt64)
}
输出结果:
string, 3.1415927E+00
string, 3.1415926535E+00
string, 3.1415926535
阅读上面这段代码,我们使用 func FormatFloat(f float64, fmt byte, prec, bitSize int) string 将浮点型转换为字符串。该函数包含 4 个参数,第一个参数是需要转换的浮点数;第二个参数是进制;第三个参数是精度,第四个参数是转换后值的取值范围。
其中,第二个参数 b 代表二进制指数;e 或 E 代表十进制指数;f 代表无进制指数;g 或 G 代表指数大时 为 e,反之为 f;x 或 X 代表十六进制分数和二进制指数。
第三个参数,精度 prec 控制由 'e','E','f','g','G','x' 和 'X' 格式打印的位数(不包括指数)。对于 'e','E','f','x' 和 'X',它是小数点后的位数。对于 'g' 和 'G',它是有效数字的最大数目(去掉后面的零)。特殊精度 -1 使用所需的最小位数,以便 ParseFloat 精确返回 f。
整型 to 字符串
示例代码:
func main() {
v := int64(-42)
s10 := strconv.FormatInt(v, 10)
fmt.Printf("%T, %v\n", s10, s10)
s16 := strconv.FormatInt(v, 16)
fmt.Printf("%T, %v\n", s16, s16)
}
输出结果:
string, -42
string, -2a
阅读上面这段代码,我们使用 func FormatInt(i int64, base int) string 将整型转换为字符串。需要注意的是,第二个参数的取值范围 2 <= base <= 36。
此外,函数 func FormatUint(i uint64, base int) string 与之功能类型,区别是仅用于转换无类型整数。
在实际项目开发中,十进制使用的最多,所以标准库 strconv 提供了函数 func Itoa(i int) string,它的功能类似 FormatInt(int64(i), 10),需要注意的是,该函数入参的类型是 int。
03 、字符串操作
关于字符串操作,标准库 strings 提供了相关函数,我们介绍几个常用的函数。
字符串中是否包含指定字符串
示例代码:
func main() {
fmt.Println(strings.Contains("seafood", "foo"))
fmt.Println(strings.Contains("seafood", "bar"))
fmt.Println(strings.Contains("seafood", ""))
fmt.Println(strings.Contains("", ""))
}
输出结果:
true
false
true
true
阅读上面这段代码,我们使用 func Contains(s, substr string) bool 在字符串 substr 中查找 s,存在则返回 true,反之返回 false。
字符串中是否包含指定字符串中任意字符
示例代码:
func main() {
fmt.Println(strings.ContainsAny("team", "i"))
fmt.Println(strings.ContainsAny("fail", "ui"))
fmt.Println(strings.ContainsAny("ure", "ui"))
fmt.Println(strings.ContainsAny("failure", "ui"))
fmt.Println(strings.ContainsAny("foo", ""))
fmt.Println(strings.ContainsAny("", ""))
}
输出结果:
false
true
true
true
false
false
阅读上面这段代码,我们使用 func ContainsAny(s, chars string) bool 在字符串 s 中查找是否包含字符串 chars 中任意字符,存在则返回 true,反之返回 false。
删除字符串中指定字符
示例代码:
func main() {
fmt.Print(strings.Trim("¡¡¡Hello, Gophers!!!", "!¡"))
}
输出结果:
Hello, Gophers
阅读上面这段代码,我们使用 func Trim(s, cutset string) string 删除字符串 s 中的字符 cutset。
字符串转换为大写
示例代码:
func main() {
fmt.Println(strings.ToUpper("Gopher"))
}
输出结果:
GOPHER
阅读上面这段代码,我们使用 func ToUpper(s string) string 将字符串中的字符全部转换为大写。
字符串以指定字符拆分为字符串切片
示例代码:
func main() {
fmt.Printf("%q\n", strings.Split("a,b,c", ","))
fmt.Printf("%q\n", strings.Split("a man a plan a canal panama", "a "))
fmt.Printf("%q\n", strings.Split(" xyz ", ""))
fmt.Printf("%q\n", strings.Split("", "Bernardo O'Higgins"))
}
输出结果:
["a" "b" "c"]
["" "man " "plan " "canal panama"]
[" " "x" "y" "z" " "]
[""]
阅读上面这段代码,我们使用 func Split(s, sep string) []string 将字符串 s 以字符串 sep 为分隔符,拆分为字符串切片。
字符串切片拼接为字符串
示例代码:
func main() {
s := []string{"foo", "bar", "baz"}
fmt.Println(strings.Join(s, ", "))
}
输出结果:
foo, bar, baz
阅读上面这段代码,我们使用 func Join(elems []string, sep string) string 将字符串切片中的所有元素,以 sep 为分隔符,拼接为字符串。
04 、字符串拼接
关于字符串拼接,分为编译时字符串拼接,和运行时字符串拼接。
其中,编译时字符串拼接,即使用 + 将多个字符串拼接为一个字符串,需要注意的是,在使用 + 拼接字符串时,如果存在字符串变量,则会在运行时拼接。
示例代码:
func main() {
str := "hello" + " world"
fmt.Println(str)
name := "frank"
outPut := "My name is " + name
fmt.Println(outPut)
}
输出结果:
hello world
My name is frank
阅读上面这段代码,第一个字符串拼接是在编译时拼接,第二个字符串拼接是在运行时拼接。
需要注意的是,运行时拼接是分配一块新的内存空间,通过内存拷贝的方式将字符串拷贝到新内存空间。
如果拼接后的字符串小于 32 字节,可以使用临时缓存;如果拼接后的字符串大于 32 字节,需要在堆区分配一块内存空间,并将需要拼接的多个字符串通过内存拷贝的形式拷贝过去。
字符串与字节数组互相转换时,也需要通过内存拷贝的方式,如果字符串大于 32 字节,需要在堆区分配一块内存空间,所以在一些转换密集的场景,我们需要特别注意。
此外,除了使用操作符 + 或 += 拼接字符串之外,还有多种字符串拼接方式,例如,fmt.Sprintf、bytes.Buffer、strings.Join 和 stings.Builder。这些字符串拼接方式在之前的文章 「Golang 语言怎么高效拼接字符串?」 介绍过,本文不再赘述。
05 、总结
本文我们介绍 Go 语言中字符串的使用方式,包括类型转换、字符串操作、字符串拼接。
除了使用标准库 strconv 进行字符串类型转换之外,读者朋友们也可以选择三方库,例如:github.com/spf13/cast。
建议读者朋友们阅读标准库文档,了解更多关于标准库 strconv 和 strings 的函数。