1.String结构体定义//src/runtime/string.go:stringStructtypestringStructstruct{strunsafe.Pointerlenint}String类型其实是Go语言内存模型中的一个“描述符”,使用了一个2字节的数据结构表明它本身并不实际存储字符串数据,而仅由指向底层存储的指针和字符串的长度字段组成。str:指向字符串底层存储首地址的指针,1字节。len:字符串的长度,1个字节。因此,即使我们直接使用String类型的变量作为函数参数,传递它的代价也是恒定的,不会随着字符串的大小而变化。二、String的特点1、String类型的值在其生命周期内是不能改变的typestringStructstruct{strunsafe.Pointerlenint}funcmain(){varsstring="hello"s[0]='a'//错误:无法为s[0]赋值,因为字符串内容是不可变的fmt.Printf("%#v\n",(*stringStruct)(unsafe.Pointer(&s)))//Output:&main.stringStruct{str:(unsafe.Pointer)(0x2b599c),len:5}s="world"//修改字符串,字符串底层结构中的str指针变了fmt.Printf("%#v\n",(*stringStruct)(unsafe.Pointer(&s)))//输出:&main.stringStruct{str:(unsafe.Pointer)(0x2b5a00),len:5}fmt.Printf("%#v\n",(*[5]byte)((*stringStruct)(unsafe.Pointer(&s)).str))//Output:&[5]uint8{0x77,0x6f,0x72,0x6c,0x64}对应的ASCII码worldrespectively}由于runtime.stringStruct结构没有导出,不能直接使用,所以手动定义了一个stringStruct结构。String类型数据的不可变特性提高了字符串的并发安全性和存储利用率。字符串可以被多个协程共享,开发者再也不用担心字符串的并发安全问题。对于同一个字符串值,无论在程序中的什么地方使用,编译器只需要为其分配一块存储空间,大大提高了存储空间的利用率。2、没有结尾'\0',存储字符串的长度。Go字符串中没有结尾'\0',存储的是字符串的长度。获取字符串长度的时间复杂度是常数,不管字符串中的字符有多少,我们都可以很快的获取到字符串的长度值。3.String可以为空"",但不能为nilvarsstring=""s=nil//错误4.提供对非ASCII字符的原生支持,杜绝源码在不同环境下显示乱码的可能Go语言源文件默认为Unicode字符集,Unicode字符集是目前市场上最流行的字符集,它包含了几乎所有主流的非ASCII字符(包括汉字)。Go字符串中的每个字符都是一个Unicode字符,这些Unicode字符以UTF-8编码存储在内存中。5.原生支持“所见即所得”原始字符串,大大减轻构造多行字符串时的精神负担。varsstring=`,_----~~~~~----.__,,_,*^_________*g*\"*,--,/__//'^./\^@qf[@f|@))||@))l0_/\/\~____/__\_____/\|_l__l_I}[______]I]||||]~~|||||`fmt.Println(s)三、字符串例程操作1、下标操作在string的实现中,底层数组实际存储的是数据,string的下标操作本质上等同于底层数组的下标操作。我们在前面的代码中其实遇到了字符串的下标操作,形式是这样的:vars="乘风破浪"fmt.Printf("0x%x\n",s[0])//0xe4:The字符与以utf-8编码的第一个字节“相乘”。我们可以看到,通过下标操作,得到的是字符串中具体下标上的字节,而不是字符。2.字符迭代Go有两种迭代形式:regularforiteration和rangeforiteration。通过这两种形式的迭代对字符串进行操作得到的结果是不同的。通过正则for迭代对字符串的操作是字节透视迭代。每轮迭代的结果是一个字节组成字符串的内容和该字节所在的下标值,这也同上相当于迭代底层的字符串数组:vars="乘风破浪"fori:=0;我<长度;i++{fmt.Printf("index:%d,value:0x%x\n",i,s[i])}输出:index:0,value:0xe4index:1,value:0xb9index:2,value:0x98//"\xe4\xb9\x98"乘以index:3,value:0xe9index:4,value:0xa3index:5,value:0x8e//"\xe9\xa3\x8e"windindex:6,value:0xe7index:7,value:0xa0index:8,value:0xb4//"\xe7\xa0\xb4"破损索引:9,value:0xe6index:10,value:0xb5index:11,value:0xaa//"\xe6\xb5\xaa"wave遍历forrange,每次迭代得到的是Unicode字符在字符串值中的codepoint,以及字符在字符串中的偏移值:vars="乘风破浪"fori,v:=ranges{fmt.Printf("index:%d,value:0x%x\n",i,v)}输出:index:0,value:0x4e58index:3,value:0x98ceindex:6,值:0x7834索引:9,值:0x6d6a3。字符串拼接虽然通过+/+=进行字符串拼接的开发体验是最好的,但是拼接性能可能不是最快的。Go还提供了strings.Builder、strings.Join、fmt.Sprintf等函数来进行字符串拼接操作。4.字符串比较Go字符串类型支持多种比较运算符,包括==、!=、>=、<=、>和<。在字符串的比较中,Go采用字典序比较策略,从每个字符串的开头开始逐字节比较两个字符串类型变量。当两个字符串之间出现第一个不同的元素时,比较结束,这两个元素的比较结果将作为字符串的最终比较结果。如果两个字符串的长度不同,则长度较小的字符串将填充空元素,空元素比其他非空元素小。如果两个字符串的长度不一样,那么我们就不用再去比较具体的字符串数据了,就可以断定两个字符串不一样了。但如果两个字符串长度相同,则需要进一步判断数据指针是否指向同一个底层存储数据。如果它们仍然相同,那么我们可以说这两个字符串是等价的。如果不同,则需要进一步比较实际的数据内容。funcmain(){//==s1:="乘风破浪"s2:="乘风破浪"+"乘风破浪"fmt.Println(s1==s2)//true//!=s1="Go"s2="PHP"fmt.Println(s1!=s2)//true//
