Строки и руны
Строки — это байты, не символы
В Go строка — это неизменяемый срез байт. Это важно понять сразу:
s := "Hello"
fmt.Println(len(s)) // 5 — байт, не символовДля ASCII (английские буквы, цифры) 1 символ = 1 байт. Но для кириллицы, иероглифов и emoji — нет:
s := "Привет"
fmt.Println(len(s)) // 12 — байт (каждая буква = 2 байта в UTF-8)
fmt.Println(len([]rune(s))) // 6 — символовДва вида строковых литералов
Двойные кавычки — интерпретируемые строки:
s := "Hello\nWorld" // \n — реальный перенос строки
s2 := "Tab:\there" // \t — реальная табуляцияОбратные кавычки — raw string, буквально:
s := `Hello\nWorld` // \n — это два символа \ и n, не перенос
json := `{
"name": "Alice",
"age": 30
}`
regex := `\d+\.\d+` // регулярка без ада с экранированиемRaw-строки удобны для JSON, SQL-запросов, регулярных выражений — везде, где много обратных слэшей.
Rune — символ Unicode
rune — это псевдоним для int32. Хранит Unicode code point:
var r rune = 'А' // 1040 — код кириллической А
fmt.Printf("%c %d\n", r, r) // А 1040Итерация по строке через range автоматически даёт руны:
s := "Hi, мир!"
for i, r := range s {
fmt.Printf("байт[%d]: %c (U+%04X)\n", i, r, r)
}
// байт[0]: H (U+0048)
// байт[1]: i (U+0069)
// байт[2]: , (U+002C)
// байт[3]: (U+0020)
// байт[4]: м (U+043C) ← индекс 4, но следующий будет 6!
// байт[6]: и (U+0438) ← пропрыг через 2 байта
// ...Обрати внимание: i — это байтовый индекс, а не порядковый номер символа.
Если нужны порядковые индексы — конвертируй:
runes := []rune(s)
for i, r := range runes {
fmt.Printf("символ[%d]: %c\n", i, r) // теперь i = 0,1,2,...
}Срезы строк — ловушка с Unicode
s := "Hello"
fmt.Println(s[1:3]) // "el" — OK, ASCII
s2 := "Привет"
fmt.Println(s2[0:2]) // "П" — OK, первый кириллический символ = 2 байта
fmt.Println(s2[0:1]) // ПЛОХО: разрезает многобайтовый символ → некорректный UTF-8Безопасный способ — через []rune:
s := "Привет"
runes := []rune(s)
fmt.Println(string(runes[0:3])) // "При" — первые 3 символаКонкатенация
Простая (через +):
name := "Alice"
greeting := "Hello, " + name + "!"В цикле — используй strings.Builder:
import "strings"
var b strings.Builder
for i := 0; i < 1000; i++ {
b.WriteString("Go")
b.WriteByte(' ')
}
result := b.String()Почему не += в цикле? Каждый += создаёт новую строку в памяти — это O(n²). Builder накапливает в буфер — O(n).
Пакет strings
Всё для работы со строками:
import "strings"
s := "Hello, World!"
strings.ToUpper(s) // "HELLO, WORLD!"
strings.ToLower(s) // "hello, world!"
strings.Contains(s, "World") // true
strings.HasPrefix(s, "Hello") // true
strings.HasSuffix(s, "!") // true
strings.Count(s, "l") // 3
strings.Replace(s, "World", "Go", 1) // "Hello, Go!"
strings.TrimSpace(" hi ") // "hi"
strings.Split("a,b,c", ",") // ["a", "b", "c"]
strings.Join([]string{"a","b","c"}, "-") // "a-b-c"Пакет strconv — числа ↔ строки
import "strconv"
// int → string
s := strconv.Itoa(42) // "42"
// string → int
n, err := strconv.Atoi("42") // 42, nil
if err != nil { /* не число */ }
// float → string
f := strconv.FormatFloat(3.14, 'f', 2, 64) // "3.14"
// string → float
f2, _ := strconv.ParseFloat("3.14", 64) // 3.14fmt.Sprintf тоже работает, но strconv быстрее — нет парсинга формата.
fmt.Sprintf — форматирование строк
name, age := "Alice", 30
s := fmt.Sprintf("Имя: %s, Возраст: %d", name, age)
// Полезные форматы:
fmt.Sprintf("%d", 255) // "255" десятичный int
fmt.Sprintf("%x", 255) // "ff" hex
fmt.Sprintf("%b", 255) // "11111111" binary
fmt.Sprintf("%f", 3.14) // "3.140000"
fmt.Sprintf("%.2f", 3.14) // "3.14"
fmt.Sprintf("%v", []int{1,2,3}) // "[1 2 3]" любой тип
fmt.Sprintf("%T", 42) // "int" тип переменной
fmt.Sprintf("%q", "hello") // `"hello"` с кавычкамиВ следующем уроке разберём float64 — как Go хранит дробные числа, почему 0.1 + 0.2 ≠ 0.3 и как с этим жить.
\nПеренос строки (newline)\tТабуляция\"Кавычка внутри строки\\Обратный слэш\rВозврат каретки (Windows newline)\u0041Unicode: A (U+0041)\x41Hex: A (0x41 = 65)