字符串是一个不可改变的字节序列。字符串可以包含任意的数据,但是通常是用来包含可读的文本。

Go 语言的字符串

Go 语言的字符串 是一个 不可改变UTF-8 字符序列
Go 语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本。

Unicode标准为全球各种人类语言中的每个字符制定了一个独一无二的值。 Unicode标准中的基本单位不是字符,而是码点(code point)。 大多数的码点实际上就对应着一个字符,但也有少数一些字符是由多个码点组成的。 码点值在Go中用rune值来表示。 内置rune类型为内置int32类型的一个别名。

一个 ASCII 码占用 1个字节,其它字符根据需要占用 2-4 个字节。

这样设计的好处有两个:

  • 减少内存的使用,节约硬盘空间
  • 统一编码格式(UTF-8)有助于减少读取文件时的编码和解码工作

    字符串

  • 声明与初始化

  • 长度
  • 遍历
  • 字符串可以为空,但不能为nil
  • 且字符串的值是不能改变的
  • 截取 ```c package main

import ( “fmt” “unicode/utf8” )

func init() { str := “hello, 世界”

  1. println("hello, 世界 length: ", len(str)) // 13
  2. fmt.Println("hello, 世界 length: ", utf8.RuneCountInString(str)) // 9
  3. r := []rune(str)
  4. fmt.Println("hello, 世界 length: ", len(r)) //9
  5. /*
  6. hello, 世界 length: 13
  7. hello, 世界 length: 9
  8. hello, 世界 length: 9
  9. */
  10. // 错误,字符串是不能更改的
  11. // str := "hello, world"

}

func init() { str2 := “hello, world”

  1. println("\nhello, world length: ", len(str2)) // 12
  2. fmt.Println("hello, world length: ", utf8.RuneCountInString(str2)) // 12
  3. r2 := []rune(str2)
  4. fmt.Println("hello, world length: ", len(r2)) //9
  5. /*
  6. hello, world length: 12
  7. hello, world length: 12
  8. hello, world length: 12
  9. */

}

/* 每一次调用DecodeRuneInString函数都返回一个r和长度,r对应字符本身,长度对应r采用UTF8编码后的编码字节数目 / func init() { str := “hello, 世界” for i := 0; i < len(str); { r, size := utf8.DecodeRuneInString(str[i:]) fmt.Printf(“%d \t %c\n”, i, r) i += size }

  1. /**
  2. 0 h
  3. 1 e
  4. 2 l
  5. 3 l
  6. 4 o
  7. 5 ,
  8. 6
  9. 7 世
  10. 10 界
  11. */

}

func init() { str := “hello, world”

  1. str = str[7:]
  2. fmt.Println(str) // world

}

func main() {

}

  1. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/357813/1628159019819-5313bca1-b013-41cf-a339-b5817adbecc1.png#averageHue=%23efeeee&clientId=u4b856e08-d6e4-4&from=paste&id=JMyzP&originHeight=227&originWidth=580&originalType=binary&ratio=1&rotation=0&showTitle=false&size=17991&status=done&style=none&taskId=u3481bdfb-ae8c-4200-9afc-f2bdf610beb&title=)<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/357813/1628158804490-4d61377d-45a4-4017-9271-5541357f9a2d.png#averageHue=%23f4f4f3&clientId=ubefcd5ca-a03a-4&from=paste&id=ub320b5ae&originHeight=360&originWidth=610&originalType=binary&ratio=1&rotation=0&showTitle=false&size=26431&status=done&style=none&taskId=u33abd65a-ea86-4421-8d11-f1b627e499f&title=)
  2. <a name="USO55"></a>
  3. # 字符串和Byte切片
  4. 在看go一些库源码中会有很多地方使用 `[]byte`
  5. <a name="KCDkh"></a>
  6. ## char & byte 对应关系
  7. > [https://www.yuque.com/ueumd/blog/zo8lcn](https://www.yuque.com/ueumd/blog/zo8lcn)
  8. | **C语言类型** | **CGO类型** | **Go语言类型** |
  9. | --- | --- | --- |
  10. | char | C.char | byte |
  11. | ... | ... | ... |
  12. <a name="oQzEW"></a>
  13. ## 体会下C语言中的字符串
  14. C 语言没有原生的字符串类型,而是使用字符数组来表示字符串,并以字符指针来传递字符串。<br />C中的 字符、字符数组 字符串定义
  15. > [https://www.yuque.com/ueumd/clang/gazmtm](https://www.yuque.com/ueumd/clang/gazmtm)
  16. ```c
  17. // 定义 字符
  18. char c = 'a'
  19. // 定义 字符数组
  20. char charArr[] = {'a','b','c','d','e'}
  21. // 定义 字符串
  22. char str[11] = "I am happy"; // 用数组的形式修改定义的字符串值
  23. char str[] = "I am happy"; // 自动分配内存空间
  24. char *pStr = "I am happy"; // 指针方式

Go 字符串定义

Go源码中 src/runtime/string.go,string定义如:

  1. type stringStruct struct {
  2. str unsafe.Pointer
  3. len int
  4. }

可以看到 str 是一个指针,指向某个数组的首地址。

继续看代码可以找到,实例化这个stringStruct的方法。

  1. //go:nosplit
  2. func gostringnocopy(str *byte) string {
  3. ss := stringStruct{str: unsafe.Pointer(str), len: findnull(str)}
  4. s := *(*string)(unsafe.Pointer(&ss))
  5. return s
  6. }

其实就是byte数组,而且 string其实就是个struct!

何为[]byte?

Go,byte是uint8的别名。slice结构在go的源码中 src/runtime/slice.go定义如:

  1. type slice struct {
  2. array unsafe.Pointer
  3. len int
  4. cap int
  5. }
  • array 数组的指针
  • len 长度
  • cap 容量

除了cap,和string的结构很像。

但其实他们差别真的很大

字符串的值是不能改变的

  1. func main() {
  2. str := "hello"
  3. fmt.Printf("&str: %p \t str: %v", &str, str)
  4. str2 := "世界"
  5. fmt.Printf("\n&str2: %p \t str2: %v", &str2, str2)
  6. str3 := str2
  7. fmt.Printf("\n&str3: %p \t str3: %v", &str3, str3)
  8. str3 = "world"
  9. fmt.Printf("\n&str3: %p \t str3: %v", &str3, str3)
  10. str2 = "world"
  11. fmt.Printf("\n&str2: %p \t str2: %v", &str2, str2)
  12. /**
  13. &str: 0xc000010240 str: hello
  14. &str2: 0xc000010260 str2: 世界
  15. &str3: 0xc000010280 str3: 世界
  16. &str3: 0xc000010280 str3: world
  17. &str2: 0xc000010260 str2: world
  18. */
  19. }

因为string的指针指向的内容是不可以更改的,所以每更改一次字符串,就得重新分配一次内存,之前分配空间的还得由gc回收,这也是导致string操作低效的根本原因。

我们定义了
str2, 指向堆中的 一块地址中的内容 “世界”
str3 也指向了str2中的内容

str3 重新赋值 开辟新空间,内容为world
str2 指向了str3新空间

原来的 “世界” 还会在堆中,中是变成了垃圾内容,被回收。

image.png

参考: