Строки и срезы на практике
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() → новая строка
Практический совет:
Для динамических данных → всегда используйте срезы
Для фиксированных размеров → рассмотрите массивы