Skip to content

Chaînes en Go

En Go, une chaîne est essentiellement une séquence d'octets en lecture seule et immuable (byte sequence). Ici, « séquence d'octets » signifie que les données sous-jacentes d'une chaîne sont composées d'une série d'octets disposés dans un ordre spécifique, ces octets occupant un espace mémoire continu.

Littéraux

Comme mentionné précédemment, il existe deux façons d'exprimer les littéraux de chaînes : les chaînes ordinaires et les chaînes brutes.

Chaînes ordinaires

Les chaînes ordinaires sont représentées par des guillemets doubles "", prennent en charge l'échappement, mais ne permettent pas l'écriture sur plusieurs lignes. Voici quelques chaînes ordinaires :

go
"这是一个普通字符串\n"
"abcdefghijlmn\nopqrst\t\\uvwxyz"
这是一个普通字符串
abcdefghijlmn
opqrst  \uvwxyz

Chaînes brutes

Les chaînes brutes sont représentées par des accents graves, ne prennent pas en charge l'échappement, mais permettent l'écriture sur plusieurs lignes. Tous les caractères dans une chaîne brute seront affichés tels quels, y compris les sauts de ligne et l'indentation.

go
`这是一个原生字符串,换行
  tab 缩进,\t 制表符但是无效,换行
  "这是一个普通字符串"

  结束
`
这是一个原生字符串,换行
        tab 缩进,\t 制表符但是无效,换行
        "这是一个普通字符串"

        结束

Accès

Comme une chaîne est essentiellement une séquence d'octets, l'opération d'indexation str[i] est conçue pour retourner le i-ème octet. La syntaxe est cohérente avec les tranches. Par exemple, pour accéder au premier élément d'une chaîne :

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

La sortie est une valeur d'encodage d'octet, pas un caractère :

116

Découper une chaîne :

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

Essayer de modifier un élément de chaîne :

go
func main() {
   str := "this is a string"
   str[0] = 'a' // Ne passe pas la compilation
   fmt.Println(str)
}
main.go:7:2: cannot assign to str[0] (value of type byte)

Bien qu'il ne soit pas possible de modifier une chaîne, on peut la remplacer :

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

Conversion

Les chaînes peuvent être converties en tranches d'octets, et les tranches d'octets ou séquences d'octets peuvent également être converties en chaînes. Voici des exemples :

go
func main() {
   str := "this is a string"
   // Conversion explicite en tranche d'octets
   bytes := []byte(str)
   fmt.Println(bytes)
   // Conversion explicite en chaîne
   fmt.Println(string(bytes))
}

Le contenu d'une chaîne est en lecture seule et immuable, il ne peut pas être modifié, mais une tranche d'octets peut être modifiée.

go
func main() {
  str := "this is a string"
  fmt.Println(&str)
  bytes := []byte(str)
    // Modifier la tranche d'octets
  bytes = append(bytes, 96, 97, 98, 99)
    // Assigner à la chaîne d'origine
  str = string(bytes)
  fmt.Println(str)
}

Après avoir converti une chaîne en tranche d'octets, les deux n'ont aucun lien, car Go allouera un nouvel espace mémoire pour la tranche d'octets, puis copiera la mémoire de la chaîne. Modifier la tranche d'octets n'aura aucun effet sur la chaîne d'origine. Cela est fait pour la sécurité de la mémoire.

Dans ce cas, si la chaîne ou la tranche d'octets à convertir est grande, le coût des performances sera élevé. Cependant, vous pouvez également utiliser la bibliothèque unsafe pour effectuer une conversion sans copie, mais les problèmes de sécurité sous-jacents doivent être pris en charge par vous-même. Par exemple, dans l'exemple ci-dessous, les adresses de b1 et s1 sont les mêmes.

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

Longueur

La longueur d'une chaîne n'est en fait pas le nombre de caractères, mais la longueur de la séquence d'octets. C'est juste que dans la plupart des cas, nous traitons des caractères ASCII, chaque caractère pouvant être représenté par un octet, donc la longueur en octets et le nombre de caractères coïncident. Pour obtenir la longueur d'une chaîne, utilisez la fonction intégrée len. Voici un exemple :

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

La chaîne de caractères chinois semble plus courte que la chaîne de caractères anglais, mais la longueur réelle obtenue est plus longue. En effet, dans l'encodage unicode, un caractère chinois occupe généralement 3 octets, tandis qu'un caractère anglais n'occupe qu'un octet. En affichant le premier élément de la chaîne, on peut voir le résultat :

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 // La lettre t
è // Un « fragment » (premier octet) d'un caractère chinois, qui coïncide avec la valeur d'encodage du caractère italien è
这 // Caractère chinois

Copie

Similaire à la méthode de copie des tranches de tableaux, la copie de chaînes est en fait une copie de tranches d'octets. Utilisez la fonction intégrée 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)
}

Vous pouvez également utiliser la fonction strings.Clone, mais l'implémentation interne est à peu près la même :

go
func main() {
   var dst, src string
   src = "this is a string"
   dst = strings.Clone(src)
   fmt.Println(src, dst)
}

Concaténation

La concaténation de chaînes utilise l'opérateur + :

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

Vous pouvez également convertir en tranche d'octets puis ajouter des éléments :

go
func main() {
   str := "this is a string"
   bytes := []byte(str)
   bytes = append(bytes, "that is a int"...)
   str = string(bytes)
   fmt.Println(str)
}

Les performances des deux méthodes de concaténation ci-dessus sont très mauvaises. Elles peuvent généralement être utilisées, mais si vous avez des exigences de performances plus élevées, vous pouvez utiliser 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

Parcours

Comme mentionné au début de cet article, une chaîne en Go est une tranche d'octets en lecture seule, ce qui signifie que l'unité de composition d'une chaîne est un octet et non un caractère. Cette situation se rencontre souvent lors du parcours de chaînes. Par exemple, le code ci-dessous :

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]))
  }
}

L'exemple affiche respectivement la forme décimale et hexadécimale des octets.

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,!

Comme les caractères de l'exemple appartiennent tous aux caractères ASCII, un seul octet suffit pour les représenter, donc chaque octet correspond恰好 à un caractère. Mais si des caractères non ASCII sont inclus, le résultat sera différent :

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]))
  }
}

Généralement, un caractère chinois occupe 3 octets, donc vous pourriez voir le résultat suivant :

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,!

Parcourir par octets décomposera les caractères chinois, ce qui entraînera clairement un affichage incorrect. Les chaînes en Go prennent explicitement en charge UTF-8. Pour gérer cette situation, il est nécessaire d'utiliser le type rune. Lors de l'utilisation de for range pour le parcours, l'unité de parcours par défaut est un rune. Voici le code :

go
func main() {
   str := "hello 世界!"
   for _, r := range str {
      fmt.Printf("%d,%x,%s\n", r, r, string(r))
   }
}

La sortie est la suivante :

104,68,h
101,65,e
108,6c,l
108,6c,l
111,6f,o
32,20,
19990,4e16,世
30028,754c,界
33,21,!

rune est essentiellement un alias de type pour int32. La plage de l'ensemble de caractères Unicode se situe entre 0x0000 et 0x10FFFF, avec un maximum de seulement trois octets. Le nombre maximum d'octets pour un encodage UTF-8 valide est de 4 octets, il est donc logique d'utiliser int32 pour le stockage. Convertir la chaîne en []rune puis la parcourir revient au même :

go
func main() {
   str := "hello 世界!"
   runes := []rune(str)
   for i := 0; i < len(runes); i++ {
      fmt.Println(string(runes[i]))
   }
}

Vous pouvez également utiliser les outils du paquet utf8. Par exemple :

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
  }
}

Les sorties de ces deux exemples sont identiques.

TIP

Pour plus de détails sur les chaînes, vous pouvez consulter Strings, bytes, runes and characters in Go.

Golang by www.golangdev.cn edit