Skip to content

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

go
"esta é uma string comum\n"
"abcdefghijlmn\nopqrst\t\\uvwxyz"
esta é uma string comum
abcdefghijlmn
opqrst  \uvwxyz

Strings 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.

go
`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"

        fim

Acesso

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

go
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

116

Fatiando uma string

go
func main() {
   str := "this is a string"
   fmt.Println(string(str[0:4]))
}
this

Tentando modificar um elemento da string

go
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

go
func main() {
   str := "this is a string"
   str = "that is a string"
   fmt.Println(str)
}
that is a string

Conversã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

go
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.

go
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.

go
func main() {
  s1 := "hello world"
  b1 := unsafe.Slice(unsafe.StringData(s1), len(s1))
  fmt.Printf("%p %p", unsafe.StringData(s1), unsafe.SliceData(b1))
}
0xe27bb2 0xe27bb2

Comprimento

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

go
func main() {
   str := "this is a string" // parece ter comprimento 16
   str2 := "这是一个字符串" // parece ter comprimento 7
   fmt.Println(len(str), len(str2))
}
16 21

Parece 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

go
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ês

Cópia

Similar à forma de copiar arrays e slices, copiar strings é na verdade copiar slices de bytes, use a função incorporada copy

go
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

go
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 +

go
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

go
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

go
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 int

Percorrendo

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

go
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

go
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

go
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

go
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

go
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.

Golang por www.golangdev.cn edit