Strings do Go
Em Go, uma string é essencialmente uma sequência de bytes imutável e somente leitura, aqui "sequência de bytes" significa que os dados subjacentes da string são compostos por uma série de bytes dispostos em ordem, esses bytes ocupam um espaço de memória contínuo.
Literais
Mencionamos anteriormente que strings têm duas formas de expressão literal, divididas em strings comuns e strings raw.
Strings comuns
Strings comuns são representadas por aspas duplas "", suportam escape, não suportam escrita em múltiplas linhas, abaixo estão algumas strings comuns
"esta é uma string comum\n"
"abcdefghijlmn\nopqrst\t\\uvwxyz"esta é uma string comum
abcdefghijlmn
opqrst \uvwxyzStrings raw
Strings raw são representadas por crases, não suportam escape, suportam escrita em múltiplas linhas, todos os caracteres dentro de uma string raw são exibidos como estão, incluindo quebras de linha e indentação.
`esta é uma string raw, quebra de linha
tab indentação, \t tabulação mas inválido, quebra de linha
"esta é uma string comum"
fim
`esta é uma string raw, quebra de linha
tab indentação, \t tabulação mas inválido, quebra de linha
"esta é uma string comum"
fimAcesso
Como a string é essencialmente uma sequência de bytes, sua operação de índice str[i] é projetada para retornar o i-ésimo byte, sintaticamente idêntico a slices, por exemplo, acessar o primeiro elemento da string
func main() {
str := "this is a string"
fmt.Println(str[0])
}A saída é o valor de codificação do byte, não o caractere
116Fatiando uma string
func main() {
str := "this is a string"
fmt.Println(string(str[0:4]))
}thisTentando modificar um elemento da string
func main() {
str := "this is a string"
str[0] = 'a' // não pode compilar
fmt.Println(str)
}main.go:7:2: cannot assign to str[0] (value of type byte)Embora não seja possível modificar a string, pode-se sobrescrevê-la
func main() {
str := "this is a string"
str = "that is a string"
fmt.Println(str)
}that is a stringConversão
Strings podem ser convertidas em slices de bytes, e slices de bytes ou sequências de bytes também podem ser convertidos em strings, exemplo abaixo
func main() {
str := "this is a string"
// conversão explícita de tipo para slice de bytes
bytes := []byte(str)
fmt.Println(bytes)
// conversão explícita de tipo para string
fmt.Println(string(bytes))
}O conteúdo da string é somente leitura e imutável, não pode ser modificado, mas slices de bytes podem ser modificados.
func main() {
str := "this is a string"
fmt.Println(&str)
bytes := []byte(str)
// modificar o slice de bytes
bytes = append(bytes, 96, 97, 98, 99)
// atribuir à string original
str = string(bytes)
fmt.Println(str)
}Ao converter uma string em slice de bytes, não há nenhuma relação entre os dois, porque Go vai alocar um novo espaço de memória para o slice de bytes, depois copiar a memória da string, modificar o slice de bytes não causa nenhum impacto na string original, isso é para segurança de memória.
Nesse caso, se a string ou slice de bytes a ser convertido for muito grande, o custo de performance será alto. Mas você também pode usar a biblioteca unsafe para fazer conversão sem cópia, porém a segurança por trás disso é sua responsabilidade, como no exemplo abaixo, os endereços de b1 e s1 são os mesmos.
func main() {
s1 := "hello world"
b1 := unsafe.Slice(unsafe.StringData(s1), len(s1))
fmt.Printf("%p %p", unsafe.StringData(s1), unsafe.SliceData(b1))
}0xe27bb2 0xe27bb2Comprimento
O comprimento de uma string na verdade não é o número de caracteres, mas o comprimento da sequência de bytes. A maioria das vezes lidamos com caracteres ASCII, cada caractere pode ser representado por exatamente um byte, então o comprimento em bytes é exatamente igual ao número de caracteres. Use a função incorporada len para obter o comprimento de uma string, exemplo abaixo
func main() {
str := "this is a string" // parece ter comprimento 16
str2 := "这是一个字符串" // parece ter comprimento 7
fmt.Println(len(str), len(str2))
}16 21Parece que a string em chinês é mais curta que a string em inglês, mas o comprimento real obtido é maior que a string em inglês. Isso porque na codificação unicode, um caractere chinês na maioria dos casos ocupa 3 bytes, um caractere inglês ocupa apenas um byte, pode-se ver o resultado através da saída do primeiro elemento da string
func main() {
str := "this is a string"
str2 := "这是一个字符串"
fmt.Println(string(str[0]))
fmt.Println(string(str2[0]))
fmt.Println(string(str2[0:3]))
}t // letra t
è // um "fragmento" de um caractere chinês (primeiro byte) valor de codificação, por coincidência igual ao valor de codificação do caractere italiano è
这 // caractere chinêsCópia
Similar à forma de copiar arrays e slices, copiar strings é na verdade copiar slices de bytes, use a função incorporada copy
func main() {
var dst, src string
src = "this is a string"
desBytes := make([]byte, len(src))
copy(desBytes, src)
dst = string(desBytes)
fmt.Println(src, dst)
}Também pode usar a função strings.Clone, mas a implementação interna é praticamente a mesma
func main() {
var dst, src string
src = "this is a string"
dst = strings.Clone(src)
fmt.Println(src, dst)
}Concatenação
A concatenação de strings usa o operador +
func main() {
str := "this is a string"
str = str + " that is a int"
fmt.Println(str)
}Também pode converter para slice de bytes e depois adicionar elementos
func main() {
str := "this is a string"
bytes := []byte(str)
bytes = append(bytes, "that is a int"...)
str = string(bytes)
fmt.Println(str)
}Ambos os métodos de concatenação acima têm performance ruim, podem ser usados em situações gerais, mas se houver requisitos de performance mais altos, pode-se usar strings.Builder
func main() {
builder := strings.Builder{}
builder.WriteString("this is a string ")
builder.WriteString("that is a int")
fmt.Println(builder.String())
}this is a string that is a intPercorrendo
No início deste artigo já mencionamos que strings em Go são apenas slices de bytes somente leitura, ou seja, a unidade de composição de strings é bytes, não caracteres. Esta situação é frequentemente encontrada ao percorrer strings, por exemplo, no código abaixo
func main() {
str := "hello world!"
for i := 0; i < len(str); i++ {
fmt.Printf("%d,%x,%s\n", str[i], str[i], string(str[i]))
}
}O exemplo mostra a forma decimal e hexadecimal dos bytes.
104,68,h
101,65,e
108,6c,l
108,6c,l
111,6f,o
32,20,
119,77,w
111,6f,o
114,72,r
108,6c,l
100,64,d
33,21,!Como os caracteres no exemplo são todos caracteres ASCII, que precisam de apenas um byte para serem representados, o resultado é que cada byte corresponde a um caractere. Mas se contiver caracteres não ASCII o resultado é diferente, como abaixo
func main() {
str := "hello 世界!"
for i := 0; i < len(str); i++ {
fmt.Printf("%d,%x,%s\n", str[i], str[i], string(str[i]))
}
}Normalmente, um caractere chinês ocupa 3 bytes, então você pode ver o seguinte resultado
104,68,h
101,65,e
108,6c,l
108,6c,l
111,6f,o
32,20,
228,e4,ä
184,b8,¸
150,96,
231,e7,ç
149,95,
140,8c,
33,21,!Percorrer por bytes vai separar os caracteres chineses, isso obviamente vai causar caracteres ilegíveis. Strings em Go suportam explicitamente UTF-8, para lidar com essa situação precisa-se usar o tipo rune, ao usar for range para percorrer, o tipo de unidade de percorrimento padrão é um rune, por exemplo, no código abaixo
func main() {
str := "hello 世界!"
for _, r := range str {
fmt.Printf("%d,%x,%s\n", r, r, string(r))
}
}Saída abaixo
104,68,h
101,65,e
108,6c,l
108,6c,l
111,6f,o
32,20,
19990,4e16,世
30028,754c,界
33,21,!rune é essencialmente um alias de tipo para int32, o intervalo do conjunto de caracteres unicode está entre 0x0000 - 0x10FFFF, máximo de apenas 3 bytes, a codificação UTF-8 válida tem máximo de 4 bytes, então usar int32 para armazenar é natural, no exemplo acima converter a string para []rune e depois percorrer é o mesmo princípio, como abaixo
func main() {
str := "hello 世界!"
runes := []rune(str)
for i := 0; i < len(runes); i++ {
fmt.Println(string(runes[i]))
}
}Também pode usar as ferramentas do pacote utf8, por exemplo
func main() {
str := "hello 世界!"
for i, w := 0, 0; i < len(str); i += w {
r, width := utf8.DecodeRuneInString(str[i:])
fmt.Println(string(r))
w = width
}
}A saída desses dois exemplos é a mesma.
TIP
Para mais detalhes sobre strings, pode ir para Strings, bytes, runes and characters in Go.
