# Глава 10. Строки
## Кодировки
Первоначально на все случаи жизни использовалась одна кодировка — [ASCII.](https://www.ascii-code.com/) Она расшифровывается как American Standard Code for Information Interchange — Американский стандартный код для обмена информацией. В ней каждому символу поставлен в соответствие некоторый код. Всего — 128 символов. В ней собраны английские буквы, цифры, знаки пунктуации и управляющие символы.
Для представления других символов была разработана кодировка [Unicode.](https://symbl.cc/ru/unicode-table/) В ней содержится более 120 тыс. символов для различных языков. Номер символа Unicode представляется типом *int32*. В Go для этого типа существует синоним — *руна* `rune`.
Представлять каждый символ в виде 32-битного значения расточительно. Многие символы имеют номер, для которого можно было бы выделить в разы меньше памяти. Так, для всех символов ASCII было бы достаточно одного байта. Более аккуратным решением является кодировка переменной длины — [UTF-8.](https://ru.wikipedia.org/wiki/UTF-8) Она изобретена двумя из создателей языка Go, Кеном Томпсоном и Робом Пайком.
UTF-8 представляется разумным решением, однако возникает проблема. Теперь длина текста в символах необязательно равна длине этого текста в байтах. Как же быть, когда необходимо знать длину текста в символах? Для этого используют функцию [RuneCountInString](https://pkg.go.dev/unicode/utf8#RuneCountInString) из стандартного пакета [unicode/utf8](https://pkg.go.dev/unicode/utf8):
```go {.example_for_playground}
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
s := "привет!"
fmt.Println(utf8.RuneCountInString(s))
}
```
```
7
```
Функция `utf8.RuneCountInString` возвращает количество рун или символов Unicode, которые закодированы в строке. А `utf8.DecodeRuneInString` возвращает руну и размер первого входящего в строку символа Unicode:
```go {.example_for_playground}
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
s := "привет!"
for i := 0; i < len(s); {
r, size := utf8.DecodeRuneInString(s[i:])
fmt.Printf("%d:\t%c\n", r, r)
i += size
}
}
```
```
1087: п
1088: р
1080: и
1074: в
1077: е
1090: т
33: !
```
Здесь мы печатаем код символа и сам символ.
Чтобы превратить срез из рун в строку UTF-8, достаточно сконструировать из него объект типа `string`:
```go {.example_for_playground .example_for_playground_001}
r := []rune{1087, 1088, 1080, 1074, 1077, 1090, 33}
fmt.Println(string(r))
```
```
привет!
```
В случае некорректного кода генерируется символ, который обычно выглядит как белый вопросительный знак в черном ромбе:
```go
fmt.Println(string(1234567))
```
```
�
```
## Приемы по работе со строками
В Go строка `string` представляет собой последовательность байтов, доступную только для чтения. Встроенная функция `len` возвращает количество байтов (**не символов!**) в строке. Так, программа ниже выведет на экран число `6`.
```go {.example_for_playground}
package main
import "fmt"
func main() {
s := "hello!"
fmt.Println(len(s))
}
```
Однако вот эта программа выведет на экран число `13`:
```go {.example_for_playground}
package main
import "fmt"
func main() {
s := "привет!"
fmt.Println(len(s))
}
```
Попытка обратиться к байту за пределами строки приведет к панике:
```go {.example_for_playground}
package main
import "fmt"
func main() {
s := "привет!"
fmt.Println(s[13])
}
```
```
panic: runtime error: index out of range [13] with length 13
```
Чтобы получить подстроку, поступают следующим образом:
```go {.example_for_playground}
package main
import "fmt"
func main() {
s := "hello!"
fmt.Println(s[0:4])
}
```
В результате будет напечатана подстрока строки `hello`, с нулевого (включительно) до четвертого (не включительно) байта:
```
hell
```
Если задать левую границу больше правой, то возникает паника. В случае, когда не задана левая граница, то подставляется число `0`. Когда не задана правая граница, подставляется длина строки:
```go {.example_for_playground}
package main
import "fmt"
func main() {
s := "hello!"
fmt.Print(s[:4])
fmt.Println(s[5:])
}
```
```
hell!
```
Строки можно сравнивать. Сравниваются они байт за байтом, в лексиграфическом порядке. Так, строка `abda` больше строки `abcd`, поскольку третий символ первой строки `d` имеет больший номер, чем третий символ второй строки `c`.
Напишите тело функции `isPalindrome`, которая проверяет, читается ли строка в обоих направлениях одинаково. Например, для слова `kayak` функция должна вернуть `true`, а для слова `blabla` — `false`. {.task_text}
```go {.task_source #golang_chapter_0100_task_0010}
package main
import "fmt"
func isPalindrome(s string) bool {
// ваш код здесь
}
func main() {
fmt.Println(isPalindrome("kayak"))
fmt.Println(isPalindrome("blabla"))
}
```
Функцию `isPalindrome` удобно реализовать как рекурсивную функцию. {.task_hint}
```go {.task_answer}
package main
import "fmt"
func isPalindrome(s string) bool {
if len(s) < 2 {
return true
}
if s[0] != s[len(s)-1] {
return false
}
return isPalindrome(s[1 : len(s)-1])
}
func main() {
fmt.Println(isPalindrome("kayak"))
fmt.Println(isPalindrome("blabla"))
}
```
Допустима конкатенация строк через символ «+»:
```go {.example_for_playground .example_for_playground_002}
helloMessage := "hello"
name := "gopher"
fmt.Println(helloMessage + ", " + name + "!")
```
```
hello, gopher!
```
Однако для конкатенации рекомендуется использовать возможности пакета `fmt`. Это делает код более читаемым: слева оказывается сама строка, а справа — переменные.
```go {.example_for_playground .example_for_playground_003}
helloMesage := "hello"
name := "gopher"
fmt.Printf("%s, %s!\n", helloMesage, name)
```
```
hello, gopher!
```
В данном случае на места `%s` подставятся наши строки. Управляющая последовательность `\n` выполняет перенос строки.
Если нет необходимости печатать строку, то воспользуйтесь функцией `fmt.Sprintf`:
```go
user := "Buba"
app := "telegram"
s := fmt.Sprintf("Get message from %s user with %s!", user, app)
```
Теперь переменная `s` содержит строку `Get message from Buba user with telegram!`, но сообщение на экран не выводится.
В Go 1.10 появился тип `strings.Builder`. Когда необходимо выполнять конкатенацию строк многократно, то используют его. Это на порядок эффективнее. Откройте пример кода в плэйграунде и убедитесь в этом:
``` go {.example_for_playground .example_for_playground_007}
var str strings.Builder
for i := 0; i < 10; i++ {
str.WriteString("a")
}
fmt.Println(str.String())
```
```
aaaaaaaaaa
```
Чтобы не обрабатывать управляющие последовательности, а напечатать строку как есть, используют обратные одинарные кавычки:
```go {.example_for_playground .example_for_playground_004}
helloMesage := `Hello, gopher!
Here we use raw string.
This won't work: \n`
fmt.Println(helloMesage)
```
```
Hello, gopher!
Here we use raw string.
This won't work: \n
```
Переносы строки печатаются, тогда как управляющая последовательность `\n` выводится как есть. Иными словами, строка печатается буквально.
Строки являются неизменяемыми. Даже если вы используете конкатенацию для строки, то на месте старой строки оказывается новая. Попытка изменить конкретный байт ведет к ошибке компиляции:
```go {.example_for_playground .example_for_playground_005}
helloMessage := "hello"
helloMessage[1] = "b"
```
```
./main.go:5:2: cannot assign to helloMessage[1] (neither addressable nor a map index expression) (exit status 1)
```
Такое решение было принято для эффективного использования памяти. Если строки неизменяемы, то они могут разделять общую память. Например, для получения подстроки никакой новой памяти выделено не будет.
## Преобразование строк
Строки могут быть преобразованы к байтам, а байты — к строкам:
```go {.example_for_playground .example_for_playground_006}
fmt.Println([]byte("gopher"))
fmt.Println(string([]byte{103, 111, 112, 104, 101, 114}))
```
```
[103 111 112 104 101 114]
gopher
```
В данном случае при преобразовании строки `gopher` к срезу байтов будет выделена дополнительная память под него. Аналогично — для преобразования среза байтов в строку. Чтобы отказаться от лишних выделений памяти, лучше избегать таких преобразований, когда в этом нет необходимости. Поэтому многие функции стандартного пакета `bytes` дублируют функции стандартного пакета `strings`. Например, для поиска подстроки в строке используется функция [strings.Index](https://pkg.go.dev/strings#Index), а для поиска подмассива в массиве байтов — [bytes.Index](https://pkg.go.dev/bytes#Index).
Иногда также требуется преобразование между числами и их строковым представлением. Для этого используются функции пакета [strconv](https://pkg.go.dev/strconv). Например, преобразование целого к строке выполняется функцией `strconv.Itoa`. Название функции — сокращение от «integer to ASCII». Для обратного преобразования подойдет `strconv.Atoi`. Для значений с плавающей точкой — `strconv.FormatFloat` превращает число в строку, а `strconv.ParseFloat` строку — в число.
Необходимо разобрать адрес `address`, состоящий из `ip` и порта `port`. Реализуйте тело функции `parseAddress`, которая их возвращает. Например, для строки `127.0.0.1:4040` необходимо вернуть `127.0.0.1` в качестве `ip` и `4040` — в качестве порта. Четыре числа `ip` ограничены от `0` до `255` включительно. Порт ограничен значениями от `0` до `65535` включительно. В случае неправильной строки с помощью функции `errors.New` верните ошибку с сообщением `invalid address`. {.task_text}
```go {.task_source #golang_chapter_0100_task_0020}
package main
import (
"errors"
"fmt"
"strconv"
"strings"
)
func parseAddress(address string) (ip string, port string, err error) {
// ваш код здесь
}
func main() {
fmt.Println(parseAddress("192.168.23.48:6060"))
fmt.Println(parseAddress("127.0.0.1:4040"))
fmt.Println(parseAddress("192.168.256.48:6060"))
fmt.Println(parseAddress("192.168.23.48:60b8"))
}
```
Индекс подстроки в строке позволяет найти функция `strings.Index`. Чтобы преобразовать строку в число, воспользуйтесь функцией `strconv.Atoi`. {.task_hint}
```go {.task_answer}
package main
import (
"errors"
"fmt"
"strconv"
"strings"
)
func parseAddress(address string) (ip string, port string, err error) {
invalid := errors.New("invalid address")
i := strings.Index(address, ":")
if i == -1 {
err = invalid
return
}
ip = address[:i]
port = address[i+1:]
rest := ip
counter := 0
// check ip
for {
idx := strings.Index(rest, ".")
if idx == -1 {
idx = len(rest)
}
var number int
number, err = strconv.Atoi(rest[:idx])
if err != nil {
err = invalid
return
}
if number < 0 || number > 255 {
err = invalid
return
}
counter++
if idx == len(rest) {
break
}
rest = rest[idx+1:]
}
const ipSize = 4
if counter != ipSize {
err = invalid
return
}
// check port
number, err := strconv.Atoi(port)
if err != nil {
err = invalid
return
}
if number < 0 || number > 65535 {
err = invalid
return
}
return
}
func main() {
fmt.Println(parseAddress("192.168.23.48:6060"))
fmt.Println(parseAddress("127.0.0.1:4040"))
fmt.Println(parseAddress("192.168.256.48:6060"))
fmt.Println(parseAddress("192.168.23.48:60b8"))
}
```
## Резюме
1. Строки неизменяемы и доступны только для чтения.
2. С помощью встроенной функции `len` можно узнать длину строки в байтах, но не в символах.
3. Для работы с символами Unicode используйте `utf8.RuneCountInString` и `utf8.DecodeRuneInString`.
4. Существуют различные кодировки. Строки могут содержать произвольные байты. Однако, если строка была сформирована из строковых констант, то она всегда представляется в кодировке UTF-8.
5. Кодировка UTF-8 — кодировка переменной длины.
6. Компилятор языка Go обрабатывает управляющие последовательности внутри строки. Например, `\n` — это перенос строки, `\t` — табуляция. Непосредственные переносы внутри строки с двойными кавычками недопустимы. Чтобы не обрабатывать управляющие последовательности, а также использовать непосредственные переносы, используйте обратные одинарные кавычки.
7. Для массовой конкатенации строк используйте тип `strings.Builder`. Это существенно повысит эффективность ваших программ.
8. Для преобразования между числами и их строковым представлением используйте пакет `strconv`.
Следующие главы находятся в разработке
Наша группа в telegram. Здесь можно задавать вопросы и общаться.
Задонатить. Если вам нравится курс, вы можете поддержать развитие площадки!