什么是字符串
一个Go语言字符串是一个任意字节的常量序列。

Go语言字符串与其他语言(Java,C++,Python)字符串的不同点
Go语言中字符串的字节使用UTF-8编码表示Unicode文本,因此Go语言字符串是变宽字符序列,每一个字符都用一个或者多个字符表示,这跟其他的(C++,Java,Python 3)的字符串类型有着本质上的不同,后者为定宽字符序列。

其他语言的字符串中的单个字符可以被字节索引,而Go语言中只有在字符串只包含7位的ASCII字符(因为它们都是用一个单一的UTF-8字节表示)时才可以被字节索引。那么这是不是表示Go语言在字符串处理能力上就比其他语言弱呢?答案时否定的,应为Go语言支持一个字符一个字符的迭代,而且标准库中存在大量的字符串操作函数,最后我们还可以将Go语言的字符串转化为Unicode码点切片(类型为 [ ]rune),切片是支持直接索引的。

对于英文文本,Go使用8位来表示每一个字节,而Java或Python则需要16位或更多。采用UTF-8编码,使得Go语言无需关系机器码的排列顺序,也无需编码解码来使用其他语言。

注:每一个Unicode字符都有一个唯一的叫做“码点”的标识数字。在Go语言中,一个单一的码点在内存中以 rune 的形式表示,rune表示int32类型的别名

字面量,操作符和转义
在Go语言中,字符串字面量使用双引号 "" 或者反引号 ' 来创建。双引号用来创建可解析的字符串,支持转义,但不能用来引用多行;反引号用来创建原生的字符串字面量,可能由多行组成,但不支持转义,并且可以包含除了反引号外其他所有字符。双引号创建可解析的字符串应用最广泛,反引号用来创建原生的字符串则多用于书写多行消息,HTML以及正则表达式。
比如下面的这些例子,输出结果完全一致

text1 := "\"Hello GoLang\",I said"
text2 :=`"Hello GoLang",I said`  //这个在Java中就无法编译成功

虽然Go语言中的字符串是不可变的,但是字符串支持 + 级联操作和+=追加操作,比如下面这个例子

text1 := "Hello" + " GoLang"
text1 += " Java"
fmt.Println(text1) // 输出 Hello GoLang Java

如果底层的字符串容量不够大,不能适应添加的字符串,级联追加操作将导致底层的字符串被替换。

下面是有关字符串的一些操作符描述

text1 := "abcdefg"
fmt.Println(text1[n]) //获取字符串索引位置为n的原始字节,比如a为97
fmt.Println(text1[n:m]) //截取得字符串索引位置为 n 到 m-1 的字符串
fmt.Println(text1[n:]) //截取得字符串索引位置为 n 到 len(s)-1 的字符串
fmt.Println(text1[:m]) //截取得字符串索引位置为 0 到 m-1 的字符串
fmt.Println(len(text1)) //获取字符串的字节数
fmt.Println(utf8.RuneCountInString(text1)) //获取字符串字符的个数
fmt.Println([]rune(text1)) // 将字符串的每一个字节转换为码点值,比如这里会输出[97 98 99 100 101 102 103]
fmt.Println(string(text1[n])) // 获取字符串索引位置为n的字符值

比较字符串
Go语言中的字符串是支持常规的比较操作(<,>,==,!=,<=,>=),这些操作符会在内存中一个字节一个字节的比较,但是在执行比较操作时,常会出现以下三种问题:
①有些 Unicode 编码的字符可以用两个或者多个不同的字节序列来表示。
如果我们只关心ASCII字符,这个问题将不会存在,若是非ASCII字符,那么我们可以通过自定义标准化函数来隔离接受这些字符串。

②用户希望将不同的字符看作是相同的。
比如字符二,2,Ⅱ,②都可以看作相同的意思,那么用户输入2时,就得匹配这些相同意思的其他字符。这个也可以通过自定义标准化函数来解决。

③字符的排序跟语言的类型有关。

字符与字符串
在Go语言中,可以用rune或者int32来表示一个字符。

字符可以通过+=操作符在一个循环中往字符串末尾追加字符,但这并不是最有效的方式,我们可以使用类似Java中的StringBuilder 来实现。

var buffer bytes.Buffer  //创建一个空的 bytes.Buffer
for   {
   if piece,ok = getNextValidString();ok {
      buffer.WriteString(piece)  //通过 WriteString 方法将我们需要串联的字符串写入到 buffer 中
   }else {
      break
   }
}
fmt.Println(buffer.String())  //最后用于取回整个级联的字符串

使用 bytes.Buffer 进行字符串的累加比起+=要高效的多,尤其是在面对大数量的字符串时。

将字符串一个字符一个字符的迭代出来,可以通过 for range 循环

text1 := "abcdefg"
for index,char := range text1 {
   fmt.Printf("%d %U %c \n",index,char,char)
}

字符串索引与切片
在所有的7位ASCII字符中,通过索引提取其字符是可以的,比如下面的例子

func main() {
   text1 := "abcdefg"
   fmt.Println(string(text1[0])) //获取索引为0的字符(核心就是通过字节来转为字符,这里字节只能为一个)
}

但是对于任意字符串来讲,上面并不一定可靠,因为有些字符可能有多个字节。为了解决这个问题,我们就需要使用字符串切片,这样返回的将是一个字符而不是一个字节。

text1 := "abcdefg"
chars := []rune(text1) //先把字符串转为rune切片
for _,char := range chars { 
   fmt.Println(string(char))
}