在接触 Go 这么语言,可能你常常会听到这样一句话。对于字符串不能批改,可能你很纳闷,日常开发中咱们对字符串进行批改也是很失常的,为什么又说 Go 中的字符串不能进行批改呢?
本文就来通过理论案例给大家演示,为什么 Go 中的字符串不能进行批改。
在演示这个问题之前,咱们先对字符串类型的基础知识做个大抵的演示,这样便于大家对问题的进一步理解。
本文已收录 Gitee、Github。分享 Go、PHP、MySQL、Redis 等等技术干货。举荐拜访 Gitee,速度快并且可能失常解析文档中的图片文件。
字符串定义
字符串是一种用来示意字符的数据类型。在应用时,应用 ” “ 将字符内容蕴含起来。例如上面的模式:
package main
import "fmt"
func main() {var str string = "Hello World!"}
在 Go 中,字符串通常有三种定义形式:
// 第一种(全量定义)
var 变量名称 string = "字符串内容"
// 类型推导
var 变量名称 = "字符串内容"
// 短标记(只实用于局部变量)
变量名称 := "字符串内容"
字符串的定义,其实也能够通过字节的形式。这里列举的形式是最为常见的形式。
字符串的组成
Go 中的字符串合乎 Unicode 规范,并且采纳 UTF- 8 编码。字符串底层其实也是由 byte 组成(前面会认真解说)。通过上面的示例,打印查看具体的字节内容:
s := "Hello World!"
for _, v := range s {fmt.Print(v)
fmt.Print("\t")
}
// 72 101 108 108 111 32 87 111 114 108 100 33
下面代码打印的内容,就是每一个字符所示意的字节码。
字符串不能批改
通过下面的大抵演示,咱们对字符串有一个根本的理解。对于字符串不能批改,可能你很纳闷,日常开发中咱们对字符串进行从新赋值也是很失常的,为什么又说 Go 中的字符串不能进行批改呢?
其实这里要纠正这个谈话,对于字符串批改并不等价于从新赋值。开发中罕用的形式,其实是一种从新赋值的概念。
str := "Hello World!"
// 从新赋值
str = "Hello Go!"
// 字符串批改
str[0] = "I"
通常听到的不能批改,其实就是指的下面代码的第二种形式。并且通过这种形式批改会报错::cannot assign to s[0] (value of type byte)
回归正题,为什么 Go 中的字符串不能通过下标的形式来进行批改呢?
这是因为Go 中的字符串的数据结构体是由一个指针和长度组成的构造体,该指针指向的一个切片才是真正的字符串值
。Go 中源码有这样一段定义:
type stringStruct struct {
str unsafe.Pointer // 指向一个 byte 类型的切片指针
len int // 字符串的长度
}
正是因为底层是一个[]byte 类型的切片,当咱们应用下标的形式去批改值,这时候将一个字符内容赋值给 byte 类型,必定是不容许的。然而咱们能够通过下标的形式去拜访对应的 byte 值。
fmt.Println(s[0]) // output:72
那咱们要想通过下标的形式去批改值该怎么办呢?这时候,就须要通过切片的形式来定义,而后在转成字符串。
package main
import ("fmt")
func main() {s1 := []byte{72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33}
fmt.Println(string(s1))
// 将 "H" 批改为 l
s1[0] = 108
fmt.Println(string(s1))
}
// output:
Hello World!
lello World!
字符串的赋值
下面剖析了为什么字符串不能应用下标去赋值,回过来解答一下日常开发中的赋值形式。
package main
import ("fmt")
func main() {
// 申明一个字符串,并给与初始值
s := "Hello World!"
// 对变量 s 进行从新赋值
s := "Hello Go!"
}
那为什么这种场景下又能够给字符串从新赋值呢?
这是因为,在 Go 的底层其实是新创建了一个 []byte{} 类型的切片,将变量 s 中的指针指向了新的内存空间地址 (也就是这里的Hello Go!
)。原有的Hello World!
内存空间会随着垃圾回收机制被回收掉。
为什么这么设计
可能大家都会思考到,为什么一个一般的字符串要设计这么简单,还须要应用指针。临时没找到官网文档的阐明,
- 集体猜测,当遇到一个十分长的字符时,这样做使得 string 变得十分轻量,能够很不便的进行传递而不必放心内存拷贝。尽管在 Go 中,不论是援用类型还是值类型参数传递都是值传递。但指针显著比值传递更节俭内存。