Строки и срезы на практике

1. Строки как последовательность байтов

Основные свойства строк в Go

Строки иммутабельны — их нельзя изменить напрямую.

text := "Hello"
// text[0] = 'h'  // ❌ Ошибка компиляции: строки неизменяемые

Строка = последовательность байтов.


2. Преобразование в срезы: []byte и []rune

Два типа преобразований

Тип Назначение Использование
[]byte(str) Срез байтов Передача по сети, работа с ASCII
[]rune(str) Срез Unicode-символов Работа с многобайтными символами

Преобразование в []byte

text := "Hello"
byteSlice := []byte(text)

fmt.Println(byteSlice)  // [72 101 108 108 111]

Каждый элемент — код ASCII символа.


Преобразование в []rune

text := "Hello"
runeSlice := []rune(text)

fmt.Println(runeSlice)  // [72 101 108 108 111]

Руна — это int32, представляющий Unicode code point.


3. Изменение строк через срезы

Паттерн: конвертация → изменение → обратная конвертация

text := "Hello"

// 1. Преобразуем в срез рун
runeSlice := []rune(text)

// 2. Изменяем элемент
runeSlice[1] = '!'

// 3. Конвертируем обратно в строку
newText := string(runeSlice)

fmt.Println(text)     // "Hello" — оригинал не изменён
fmt.Println(newText)  // "H!llo" — новая строка

Важно: Создаётся новая строка в памяти, оригинал остаётся неизменным.


Визуализация процесса

"Hello"
   ↓ []rune()
[72, 101, 108, 108, 111]
   ↓ изменение
[72, 33, 108, 108, 111]  // 33 = '!'
   ↓ string()
"H!llo"

4. Разница []byte vs []rune

ASCII строки (одинаковы)

text := "Hello"
fmt.Println([]byte(text))  // [72 101 108 108 111]
fmt.Println([]rune(text))  // [72 101 108 108 111]

Unicode строки (РАЗНЫЕ!)

text := "Привет"

byteSlice := []byte(text)
fmt.Println(len(byteSlice))  // 12 байт (UTF-8)

runeSlice := []rune(text)
fmt.Println(len(runeSlice))  // 6 символов

Вывод: Для работы с символами используйте []rune.


5. Практический пример: To-Do список

Простой вариант

tasks := []string{}  // Пустой список задач

// Добавление задач
tasks = append(tasks, "Написать код")
tasks = append(tasks, "Протестировать")
tasks = append(tasks, "Задеплоить")

// Вывод списка
for i, task := range tasks {
    fmt.Printf("%d. %s\n", i+1, task)
}

Вывод:

1. Написать код
2. Протестировать
3. Задеплоить

Удаление выполненной задачи

// Удаляем первую задачу (индекс 0)
tasks = tasks[1:]

for i, task := range tasks {
    fmt.Printf("%d. %s\n", i+1, task)
}

Вывод:

1. Протестировать
2. Задеплоить

6. Расширенный вариант: задачи с метками

Идея из материала

tasks := []string{
    "[ ] Написать код",
    "[ ] Протестировать",
    "[ ] Задеплоить",
}

// Отметить первую задачу как выполненную
runeSlice := []rune(tasks[0])
runeSlice[1] = '✓'
tasks[0] = string(runeSlice)

for _, task := range tasks {
    fmt.Println(task)
}

Вывод:

[✓] Написать код
[ ] Протестировать
[ ] Задеплоить

7. Операции с задачами

Добавление

tasks = append(tasks, "[ ] Новая задача")

Изменение конкретной задачи

tasks[1] = "[✓] Протестировать"

Удаление (по индексу)

index := 1
tasks = append(tasks[:index], tasks[index+1:]...)

8. Почему срезы, а не массивы

Из материала:
“В 90%, а то и 95% случаев вы будете использовать именно срезы”.

Причины

Задача Массив Срез
Неизвестное количество элементов ❌ Нужен фиксированный размер ✅ Динамический рост
Добавление/удаление ❌ Невозможно append(), slicing
Передача в функцию ❌ Копируется целиком ✅ Передаётся структура (ptr)
Гибкость ❌ Жёсткая структура ✅ Любые операции

9. Типичные сценарии использования срезов

Динамические коллекции

var results []int  // Неизвестное количество результатов

for condition {
    results = append(results, computeValue())
}

Стек (LIFO)

stack := []int{}

// Push
stack = append(stack, value)

// Pop
value := stack[len(stack)-1]
stack = stack[:len(stack)-1]

Очередь (FIFO)

queue := []int{}

// Enqueue
queue = append(queue, value)

// Dequeue
value := queue[0]
queue = queue[1:]

10. Строки + срезы: комбинированное применение

Разбиение текста на слова

text := "Go is awesome"
words := []string{}

// Здесь упрощённо (в реальности используйте strings.Fields)
for _, word := range []string{"Go", "is", "awesome"} {
    words = append(words, word)
}

fmt.Println(words)  // [Go is awesome]

Фильтрация символов

text := "H3ll0 W0rld!"
lettersOnly := []rune{}

for _, r := range text {
    if (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') || r == ' ' {
        lettersOnly = append(lettersOnly, r)
    }
}

result := string(lettersOnly)
fmt.Println(result)  // "Hll Wrld"

11. Итоги

Строки иммутабельны — изменение через []byte или []rune[]byte(str) — для работы с байтами (сеть, файлы) ✅ []rune(str) — для работы с Unicode-символами ✅ string(slice) — обратное преобразование
Срезы — основная структура данных в Go (90-95% случаев) ✅ Применение: списки задач, стеки, очереди, динамические коллекции
✅ Фантазия + насмотренность = эффективные решения

Ключевая формула:

Иммутабельная строка → []rune → изменение → string() → новая строка

Практический совет:

Для динамических данных → всегда используйте срезы
Для фиксированных размеров → рассмотрите массивы