Historia pytania: W Go ciągi (string) to podstawowy i często używany typ, aktywnie stosowany do wymiany danych, logowania, parsowania itp. Różnicą w Go jest to, że ciągi są niemutowalne, składają się z niezmiennej sekwencji bajtów i mogą zawierać dane w UTF-8.
Problem: Mieszanie pracy z ciągami i fragmentami bajtów ([]byte) prowadzi do błędów podczas modyfikacji stringa, przy rozdzielaniu według run i przy próbach pracy z wielobajtowymi symbolami (na przykład cyrylica, emoji).
Rozwiązanie:
Ciąg jest niemutowalny, nie można zmieniać jego elementów bezpośrednio — próba zmiany s[0] jest niepoprawna. Ciąg kodowany jest w UTF-8, co oznacza, że jeden symbol (runa) może być szerszy niż jeden bajt. Praca z []byte jest tańsza, ale wymaga ręcznej kontroli. Konwersja string <-> []byte zawsze tworzy kopię.
Przykład kodu:
s := "cześć" fmt.Println(len(s)) // 12 bajtów (cyrylica: po 2 bajty) fmt.Println(len([]rune(s))) // 6 run, tyle liter fmt.Println(string([]byte{228, 189, 160, 229, 165, 189})) // chińskie znaki
Kluczowe cechy:
1. Czy można zmienić pojedynczy znak ciągu przez indeks (na przykład, s[1] = 'a')?
Odpowiedź: Nie. Ciągi są niemutowalne, kompilator zgłosi błąd. Należy utworzyć nowy fragment []rune lub []byte, zmienić, a następnie przekształcić z powrotem w ciąg.
2. Dlaczego len(str) nie zawsze zgadza się z liczbą symboli w ciągu?
Odpowiedź: len(str) to liczba bajtów, a nie run (symboli). W przypadku cyrylicy lub emoji długi ciąg może dawać intuicyjnie nieoczekiwaną wartość. Aby uzyskać liczbę symboli, użyj []rune:
s := "świat 😀" fmt.Println(len(s)) // 7 fmt.Println(len([]rune(s))) // 5
3. Czy ciąg jest przekazywany przez referencję czy przez wartość do funkcji?
Odpowiedź: Przez wartość, ale fizycznie wewnątrz przechowywany jest wskaźnik do pamięci i długość. Po przekazaniu dwie zmienne "wskazują" na ten sam tekst, kopia nie jest tworzona automatycznie. Rzeczywista kopia pamięci powstaje podczas konwersji do []byte lub z []byte na string.
Programista ma ciąg z rosyjskimi znakami, bierze pierwsze 4 bajty i oczekuje uzyskać pierwszą literę, ale otrzymuje tylko połowę znaku — powstają "zepsute" znaki.
Zalety: Szybko i krótko. Wady: Niekorektna obsługa danych Unicode, "zepsute" ciągi, panika przy próbie sparsowania takich ciągów w innych miejscach.
Ciągi są przekształcane w []rune do pracy z symbolami, po wykonaniu potrzebnych działań ciąg jest ponownie zbierany przez string(). Praca z []byte wykonywana jest tylko dla niskopoziomowej serializacji, z uwzględnieniem kodowania.
Zalety: Poprawna obsługa Unicode, niezawodność funkcji. Wady: Nieco wolniej, wymaga więcej pamięci, ale jest bezpieczne dla wszelkich ciągów.