跳到主要内容

5.字符串

1. 字符串

Go 语言中的字符串内部实现使用 UTF-8 编码。

字符串可以包含任何 Unicode 字符,无论是 ASCII 字符、中文、日文、韩文还是其他特殊字符,都可以直接在 Go 语言中表示。

1.1 字符串定义

字符串的值由双引号 " 包围,如 "hello""你好"

由于 Go 使用 UTF-8 编码,可以在字符串中直接使用非 ASCII 字符,例如中文、日文、韩文、表情符号等。

字符串一旦定义,字符串的内容无法通过下标修改,但可以通过拼接、切片等方式构造新的字符串。

package main

import "fmt"

func main() {
s1 := "hello"
s2 := "你好"

fmt.Println("s1:", s1) // 输出: hello
fmt.Println("s2:", s2) // 输出: 你好
}

1.2 多行字符串

在Go 语言中可以使用反引号(```)定义多行字符串,原样保留内容和格式, 适用于存储多行文本、配置文件、SQL 语句、模板字符串等 需要存储或处理多行文本的场景。


特性

  1. 原始格式输出**:反引号中的内容会以原样输出,包括换行、空格、制表符等,字符串内部的所有字符都会被保留,且不会对任何特殊字符(如 \n\t 等转义字符)进行处理。**
  2. 适合处理多行文本:可以将一段多行内容直接放在反引号中,无需手动添加换行符,保持了文本的原始格式,方便代码的可读性。
package main

import (
"fmt"
)

func main() {
s1 := `
第一行
第二行
第三行`
fmt.Println(s1)
}

输出:

第一行
第二行
第三行

1.3 byte 和 rune

在 Go 语言中,字符串实际上是一个字节序列,支持两种常用的字符类型:byterune

1. byte 类型

  • byteuint8 的别名,代表了 ASCII 码的一个字符。
  • 当需要直接操作字节数据时,适合将字符串转换为 []byte。例如,网络传输、文件读写、处理 ASCII 字符串时,因为这些操作对字节的要求较高,且更高效。

2. rune 类型

  • runeint32 的别名,代表一个 UTF-8 字符。
  • 当需要对字符串中的字符进行处理时,适合将字符串转换为 []rune,尤其是包含非 ASCII 字符的字符串(如中文、日文、韩文、特殊符号等)。因为 []rune 能够正确处理多字节字符,避免了 UTF-8 字符被错误地分割。

字符串与 byte/rune 的转换

  • 在 Go 中,字符串可以看作是 []byte 数组,因此可以直接将字符串转换为 []byte 类型,进行字节级别的操作。
  • 同理,字符串也可以转换为 []rune 类型,方便对字符串中的 Unicode 字符进行处理。

将字符串转换为 []byte

s := "Go语言"
b := []byte(s)
fmt.Println("[]byte:", b) // 输出: [71 111 232 175 173 232 168 152]

可以看到 Go 是两个字节,语言 是三个字节一组,表示每个字符的 UTF-8 编码。

将字符串转换为 []rune

s := "Go语言"
r := []rune(s)
fmt.Println("[]rune:", r) // 输出: [71 111 35821 35328]

rune 切片中每个值都是一个完整的 Unicode 码点,对应于字符 Go

[]byte[]rune 转换回字符串

bToStr := string(b)
fmt.Println("从 []byte 转换回字符串:", bToStr) // 输出: Go语言

rToStr := string(r)
fmt.Println("从 []rune 转换回字符串:", rToStr) // 输出: Go语言

取值范围

byteuint8 的别名,表示一个 8 位无符号整数(即不包含负数) 。 uint8 类型的取值范围正好是 0 到 255,这是因为 8 位(即 1 个字节)可以表示的数值范围是从 0 到 2 的 8 次方-1。

package main

import "fmt"

func main() {
var b byte = 255 // 赋值最大值
fmt.Println("b 的值:", b) // 输出: 255

b = 0
fmt.Println("b 的值:", b) // 输出: 0

// 如果尝试将超出 0-255 范围的值赋给 byte 类型变量,会导致编译错误
// b = 256 // 编译错误: constant 256 overflows byte
}

byte 类型与字符串的关系 **

字符串在 Go 语言中是一个字节序列,使用 byte 来表示和处理。可以将字符串转换为 []byte(字节切片)来查看或操作字符串的每个字节。

package main

import "fmt"

func main() {
s := "hello"
byteSlice := []byte(s) // 将字符串转换为 byte 切片
fmt.Println("字符串转为 byte 切片:", byteSlice) // 输出: [104 101 108 108 111]

// 遍历 byte 切片
for i := 0; i < len(byteSlice); i++ {
fmt.Printf("字符 '%c' 的 ASCII 码是: %d\n", s[i], byteSlice[i])
}
}

输出:

字符串转为 byte 切片: [104 101 108 108 111]
字符 'h' 的 ASCII 码是: 104
字符 'e' 的 ASCII 码是: 101
字符 'l' 的 ASCII 码是: 108
字符 'l' 的 ASCII 码是: 108
字符 'o' 的 ASCII 码是: 111

runeint32 的别名,用于表示一个 Unicode 码点(字符)。因此,rune 的范围与 Unicode 的编码范围相同,是从 0 到 0x10FFFF(即从 0 到 1,114,111)。

package main

import (
"fmt"
)

func main() {
var r rune = 'A' // 单个字符 'A' 的 Unicode 码点
fmt.Printf("字符: %c, Unicode 码点: %U, 十进制值: %d\n", r, r, r)

r = '世'
fmt.Printf("字符: %c, Unicode 码点: %U, 十进制值: %d\n", r, r, r)

r = '\U0010FFFF' // 最大值
fmt.Printf("字符: %c, Unicode 码点: %U, 十进制值: %d\n", r, r, r)
}

输出

字符: A, Unicode 码点: U+0041, 十进制值: 65
字符:, Unicode 码点: U+4E16, 十进制值: 19990
字符: 􏿿, Unicode 码点: U+10FFFF, 十进制值: 1114111

因为 runeint32,它可以完整地表示所有 Unicode 码点(U+0000 到 U+10FFFF)。Unicode 标准允许的码点范围是 0 到 1,114,111,因此 rune 能够涵盖所有有效的 Unicode 字符,包括字母、数字、符号、表情符号和各种语言的字符。

rune **理论十进制值的最大和最小值 **

rune 是 Go 语言中的 int32 类型的别名,专门用来表示一个 Unicode 码点(字符)。因为 rune 本质上是一个 int32,它的理论值范围与 int32 的取值范围相同。 int32 是一个 32 位有符号整数,可以表示的范围是从 -2^312^31 - 1


**实际有效值范围 **

尽管 rune 的理论范围是从 -2,147,483,6482,147,483,647,但在实际应用中,rune 只用于表示 有效的 Unicode 码点,而 Unicode 码点的范围是从 U+0000U+10FFFF,也就是十进制的 01,114,111

因此,实际使用 rune 时的有效范围是:

  • 最小值0
  • 最大值1,114,111(十进制),即 0x10FFFF(十六进制)
package main

import (
"fmt"
"math"
)

func main() {
// rune 是 int32,因此理论上的范围是 int32 的范围
fmt.Printf("rune 理论最小值: %d\n", math.MinInt32) // -2147483648
fmt.Printf("rune 理论最大值: %d\n", math.MaxInt32) // 2147483647

// 实际有效范围
fmt.Println("rune 实际有效最小值:", 0)
fmt.Println("rune 实际有效最大值:", 0x10FFFF) // 1114111 in decimal
}

输出:


rune 理论最小值: -2147483648
rune 理论最大值: 2147483647
rune 实际有效最小值: 0
rune 实际有效最大值: 1114111

2.字符串的常用操作

方法介绍
len(str)求长度
+ 或 fmt.Sprintf拼接字符串
strings.Split分割
strings.Contains判断是否包含
strings.HasPrefix, strings.HasSuffix前缀/后缀判断
strings.Index(), strings.LastIndex()子串出现的位置
strings.Join(a[]string, sep string)join 操作

2.1len(str)

len(s) 函数返回的是字符串的字节长度,而不是字符数。因此,所有字符,包括字母、中文字符、标点符号、空格等都会被计入字节长度。

在 UTF-8 编码中,英文字符和空格都占用 1 个字节,而中文字符通常占用 3 个字节

package main

import (
"fmt"
)

func main() {
s := "你好, Go"
fmt.Println("字符串长度(字节数):", len(s)) // 输出: 9
}

详细计算:

  • 占用 3 个字节
  • 占用 3 个字节
  • ,(逗号)占用 1 个字节
  • 空格 " " 占用 1 个字节
  • Go 各占用 1 个字节

因此,总字节数 = 3 + 3 + 1 + 1 + 1 = 9

2.2 拼接

在 Go 语言中,可以使用 + 运算符来拼接两个或多个字符串,形成一个新的字符串。

package main

import (
"fmt"
)

func main() {
var str1 = "你好"
var str2 = "golang"

// 使用 + 进行字符串拼接
fmt.Println(str1 + ", " + str2) // 输出: 你好, golang
}

str1 + ", " + str2 将字符串 str1", "(一个包含逗号和空格的字符串)、str2 拼接在一起,形成一个新的字符串。

2.3 string.Split()

strings.Split() 是 Go 语言中 strings 包提供的一个函数,用于将一个字符串按照指定的分隔符拆分成一个字符串切片([]string)。

package main

import (
"fmt"
"strings"
)

func main() {
var s = "123-456-789"
var arr = strings.Split(s, "-")
fmt.Println(arr) // 输出: [123 456 789]
}

  • 定义一个字符串 s,内容为 "123-456-789"
  • 使用 strings.Split(s, "-") 将字符串 s 按照 "-" 分割,结果将会得到一个字符串切片 arr,即 ["123", "456", "789"]
  • 使用 fmt.Println(arr) 打印切片,输出结果为 [123 456 789]

2.4 string.Join()

package main

import (
"fmt"
"strings"
)

func main() {
var str = "123-456-789"
var arr = strings.Split(str, "-") // 将字符串按 "-" 分割成切片
var str2 = strings.Join(arr, "*") // 使用 "*" 作为分隔符将切片连接成字符串

fmt.Println(arr) // 输出: [123 456 789]
fmt.Println(str2) // 输出: 123*456*789
}

  • 定义一个字符串 str,内容为 "123-456-789"
  • 使用 strings.Split(str, "-") 将字符串 str"-" 分割,得到一个字符串切片 arr["123", "456", "789"]
  • 使用 strings.Join(arr, "*") 将切片 arr 中的元素重新连接成一个新的字符串,并在每个元素之间插入 "*" 作为分隔符,结果为 123*456*789
  • fmt.Println(arr) 打印切片,输出 [123 456 789]
  • fmt.Println(str2) 打印拼接后的字符串,输出 123*456*789

2.5 单引号

在 Go 语言中,字符用单引号 ('') 包围,表示单个字符的字面值。例如:'a''1''@'

字符在 Go 中实际上是 rune 类型,runeint32 的别名,用于表示一个 Unicode 码点。

package main

import "fmt"

func main() {
a := 'a' // 使用单引号定义字符
name := "zhangsan" // 使用双引号定义字符串

// 直接输出字符变量时,输出的是字符的 ASCII 码值
fmt.Println(a) // 输出: 97,因为 'a' 的 ASCII 码值是 97
fmt.Println(name) // 输出: zhangsan,因为它是一个字符串

// 使用格式化输出将字符的实际值打印出来
fmt.Printf("a 的值是: %c\n", a) // 输出: a 的值是: a
}

字符定义

a := 'a' 使用单引号定义字符 'a'a 的类型是 rune(对应 int32)。

name := "zhangsan" 使用双引号定义字符串,name 的类型是 string


输出字符时的差异

fmt.Println(a) 直接打印 a 时,显示的是字符 'a' 对应的 ASCII 码值 97。这是因为 a 是一个 rune 类型,在打印时会被显示为整数值。

fmt.Printf("a 的值是: %c\n", a) 使用格式化输出 %c 时,显示的是字符本身 a,因为 %c 用于以字符形式输出。


字符串输出

fmt.Println(name) 直接打印字符串 name,输出 zhangsan,因为它是一个完整的字符串。

package main

import (
"fmt"
)

func main() {
ch := '是' // 使用单引号表示字符
fmt.Println(ch) // 输出字符的 Unicode 码点值(十进制)
fmt.Printf("%c\n", ch) // 使用格式化输出显示字符本身
}

输出

26159

fmt.Println(ch):直接打印字符 ch 时,输出的是字符 '是' 的 Unicode 码点值 26159(十进制形式)。这是因为在 Go 语言中,rune 类型实际上是 int32,存储的是字符对应的 Unicode 码点。

fmt.Printf("%c\n", ch):使用格式化输出 %c 可以将 rune 类型的字符显示为字符本身,这样就能正确输出

3.字符串的遍历

以及通过 byterune 两种方式遍历字符串的内容。

3.1 遍历字符串

package main

import (
"fmt"
)

func main() {
s := "hello 张三"

// 使用普通 for 循环按字节遍历
for i := 0; i < len(s); i++ { // byte 遍历
fmt.Printf("%v(%c) ", s[i], s[i])
}
fmt.Println() // 打印一个换行

// 使用 for range 循环按字符遍历
for _, r := range s { // rune 遍历
fmt.Printf("%v=>%c ", r, r)
}
fmt.Println()
}

3.2 第一种循环:for i := 0; i < len(s); i++ { ... }

s := "hello 张三"
for i := 0; i < len(s); i++ {
fmt.Printf("%v(%c) ", s[i], s[i])
}

  • len(s):返回的是字符串的字节长度,而不是字符长度。
  • s[i]:在这种情况下,s[i] 返回的是字符串 s 中第 i字节byte),因此这里是按字节来遍历字符串的。
104(h) 101(e) 108(l) 108(l) 111(o) 32( ) 229(å) 188(¼) 160( ) 228(ä) 184(¸) 137()

  • s[i] 返回字符串中第 i 个字节的值。
  • hello 部分是 ASCII 字符,每个字符占用 1 个字节,可以正确显示。
  • 张三 是中文字符,每个字符占用 3 个字节,因此分解成多个字节显示,这些字节的值以十进制显示。

在这种普通 for 循环中,通过 len(s)s[i] 访问字符串的方式,实际上是将字符串视为一个 []byte(字节数组)来处理,所以每次访问到的都是字符串的单个字节。这也是为什么我们称这种方式为 byte 遍历。

3.3 第二种循环:for _, r := range s { ... }

range s:是 Go 语言中遍历字符串的一个特殊用法。当我们使用 for range 来遍历字符串时,Go 会自动将字符串按 字符(Unicode 码点)进行遍历,变量 r 会接收每个完整的字符,而不是单个字节。

for _, r := range s {
fmt.Printf("%v=>%c ", r, r)
}

因为我们对字符的索引不感兴趣,所以使用了下划线 _ 来忽略索引,只关注字符 r

fmt.Printf 是 Go 语言用于格式化输出的函数。

"%v=>%c " 是一个格式化字符串,表示输出的格式:

%v:通用的占位符,表示以默认格式输出变量 r,在这里会显示 r 的 Unicode 码点值(十进制数)。

%c:表示以字符的形式输出变量 r,即输出 r 所代表的字符。

=>:是一个连接符号,用于将 Unicode 码点值和字符直观地连接起来输出。

104=>h 101=>e 108=>l 108=>l 111=>o 32=> 24352=>19977=>

for _, r := range s 使用 range 遍历时,r 表示每个字符的 Unicode 码点值。

hello 部分显示了对应的 Unicode 码点值(与 ASCII 值一致)。

直接输出对应的 Unicode 码点值 2435219977,并正确输出中文字符。

for _, r := range s 中,r 只是一个用于存储当前字符的变量名,您可以替换成其他名称,如 bbcccharch 等任何合法的 Go 语言变量名。

3.4 理解 byterune 的关系

3.4.1 byterune 的本质区别

  • byte:是 uint8 的别名,代表一个8 位的无符号整数,范围是 0255。它代表了字符串中的一个字节。
  • rune:是 int32 的别名,代表一个32 位的整数,用于表示 Unicode 码点,即一个完整的字符。

3..4.2 字符串的底层存储

  • 在 Go 语言中,字符串的底层是一个 []byte,这意味着字符串中的所有字符都是由一个或多个 byte 组成的,采用的是 UTF-8 编码。
  • 当我们使用 for range 遍历字符串时,Go 会将底层的 []byte 解析成一个个 rune,每个 rune 代表一个完整的 Unicode 字符,而不再是单个 byte

4.转 String

使用 strconv 包将不同数据类型转换为字符串(string)的内容。

4.1 strconv

在 Go 语言中,strconv 包提供了将其他数据类型转换为字符串的常用函数,如 ItoaFormatFloatFormatBoolFormatInt 等。

4.2 使用 strconv.Itoa()int 转换为 string

package main

import (
"fmt"
"strconv"
)

func main() {
// 1. int 转换成 string
var num1 int = 20
s1 := strconv.Itoa(num1)
fmt.Printf("类型: %T ,值=%v \n", s1, s1) // 输出: 类型: string ,值=20
}

strconv.Itoa(num1): ItoaInteger to ASCII 的缩写,用于将 int 类型转换为 string 类型。

s1 将会是字符串 "20",并且类型是 string

4.3 使用 strconv.FormatFloat()float64 转换为 string

    // 2. float 转 string
var num2 float64 = 20.113123
s2 := strconv.FormatFloat(num2, 'f', 2, 64)
fmt.Printf("类型: %T ,值=%v \n", s2, s2) // 输出: 类型: string ,值=20.11

strconv.FormatFloat 用于将浮点数转换为字符串。

参数解释:

  • num2: 要转换的浮点数。
  • 'f': 格式化类型,'f' 表示十进制表示(普通小数形式)。
  • 2: 保留两位小数。
  • 64: 指定转换的是 float64 类型(对于 float32 类型则填 32)。
  • 结果 s2 是字符串 "20.11"

4.4 使用 strconv.FormatBool()bool 转换为 string

    // 3. bool 转 string
s3 := strconv.FormatBool(true)
fmt.Printf("类型: %T ,值=%v \n", s3, s3) // 输出: 类型: string ,值=true

4.5 使用 strconv.FormatInt()int64 转换为 string

    // 4. int64 转 string
var num3 int64 = 20
s4 := strconv.FormatInt(num3, 10)
fmt.Printf("类型: %T ,值=%v \n", s4, s4) // 输出: 类型: string ,值=20
}

参数解释

  • strconv.FormatInt(num3, 10): 将 int64 类型的 num3 转换为字符串,第二个参数 10 表示用十进制格式转换。
  • 结果 s4 是字符串 "20"

5.string 与 int 转换

package main

import (
"fmt"
"strconv"
)

func main() {
num := 100

// 1. int 转换成 string
strNum := strconv.Itoa(num)
fmt.Printf("num: %T %v \n", num, num) // 输出: num: int 100
fmt.Printf("strNum: %T %v \n", strNum, strNum) // 输出: strNum: string 100

// 2. string 转换成 int
intNum, _ := strconv.Atoi(strNum)
fmt.Printf("intNum: %T %v \n", intNum, intNum) // 输出: intNum: int 100
}

5.1 int 转换为 string

strconv.Itoa(num)Itoa 是 "Integer to ASCII" 的缩写,函数 strconv.Itoa 用于将 int 类型转换为 string 类型。

变量 num 的值是 100,转换后 strNum 的值变成字符串 "100",类型为 string

fmt.Printf("strNum: %T %v \n", strNum, strNum):使用 Printf 输出 strNum 的类型和值,%T 显示类型,%v 显示值。输出结果是 strNum: string 100

5.2 string 转换为 int

strconv.Atoi(strNum)Atoi 是 "ASCII to Integer" 的缩写,函数 strconv.Atoi 用于将 string 类型转换为 int 类型。

intNum, _ := strconv.Atoi(strNum)strconv.Atoi 返回两个值,第一个是转换后的 int 值,第二个是错误信息(如果转换失败)。在这里用 _ 忽略错误信息。

strNum 原本是 "100",转换后 intNum 的值变成整数 100,类型为 int

fmt.Printf("intNum: %T %v \n", intNum, intNum):输出结果为 intNum: int 100

输出结果

num: int 100
strNum: string 100
intNum: int 100