🐹 Go

Métodos de String — Guia Completo em Go (Golang): Um Guia Detalhado

Что это такое и зачем? (аналогия из реальной жизни)

Представьте, что вы получили длинное письмо от друга. В нём много слов, есть ошибки, некоторые слова написаны с большой буквы, а некоторые — с маленькой. Что вы можете с ним сделать? Вы можете:

  • Посчитать, сколько в нём букв или слов.
  • Найти в нём конкретное слово (например, «встреча»).
  • Заменить одно слово на другое (например, «привет» на «здравствуйте»).
  • Разрезать письмо на части по точкам или запятым.
  • Проверить, начинается ли оно с определённой фразы.
  • Убрать лишние пробелы в начале и в конце.

В программировании строка (string) — это точно такое же «письмо». Это последовательность символов: букв, цифр, пробелов, знаков препинания. А методы строк — это готовые инструменты (команды), которые позволяют нам выполнять все эти действия с текстом. Вместо того чтобы писать сложный код с циклами и проверками каждого символа, мы просто вызываем готовую функцию.

💡 Аналогия: Представьте, что строка — это пластилиновая колбаска. Методы строк — это формочки и ножики, которыми вы можете отрезать кусочек, придать ему форму, соединить с другой колбаской или проверить, какого он цвета. Вам не нужно лепить каждую деталь руками — формочка делает всё за секунду.

Зачем это нужно? Любая программа, которая работает с пользователем, общается с интернетом или читает файлы, постоянно обрабатывает текст. Без методов строк вам пришлось бы вручную перебирать каждый символ, сравнивать его, запоминать позиции. Это долго, скучно и чревато ошибками. Методы строк делают код коротким, понятным и быстрым.

Теория: как это работает изнутри

Прежде чем мы начнём использовать методы, важно понять, как устроена строка в Go. Это поможет избежать многих ошибок.

Строка — это неизменяемый массив байтов

В Go строка — это не просто набор символов, которые вы видите на экране. Внутри компьютера всё хранится в виде чисел — байтов. Каждый символ текста кодируется одним или несколькими байтами. Самый популярный стандарт кодировки — UTF-8. В нём:

  • Латинские буквы, цифры и знаки препинания занимают 1 байт.
  • Русские буквы, иероглифы и эмодзи занимают от 2 до 4 байтов.

Строка в Go — это неизменяемая (immutable) структура данных. Это значит, что вы не можете взять и изменить один символ внутри уже существующей строки. Любая операция «изменения» (например, замена символа) на самом деле создаёт новую строку в памяти, а старая остаётся нетронутой (пока сборщик мусора её не удалит).

⚠️ Важно: Из-за неизменяемости, если вы делаете много операций со строками в цикле (например, склеиваете тысячи кусочков), программа может работать медленно и тратить много памяти. Для таких случаев в Go есть специальный тип strings.Builder.

Строка — это срез байтов, но не срез рун

Когда вы пишете "Привет", Go хранит это как последовательность байтов: [208, 159, 209, 128, 208, 184, 208, 178, 208, 181, 209, 130]. Если вы попробуете взять первый элемент строки по индексу (например, s[0]), вы получите не букву П, а число 208 — первый байт этой буквы.

Для работы с символами (буквами, иероглифами) в Go есть специальный тип — rune. Это число, которое кодирует один символ Unicode. Чтобы пройтись по строке посимвольно, нужно использовать цикл for range, который автоматически распаковывает байты в руны.

Пакет strings

В Go методы для работы со строками находятся не в самой строке (как в Python или Java), а в отдельном стандартном пакете strings. Чтобы ими пользоваться, нужно подключить этот пакет в начале программы:

import "strings"

Это похоже на то, как вы достаёте инструмент из ящика: сам ящик (пакет) всегда с вами, но чтобы взять молоток (функцию), нужно сначала открыть ящик.

Синтаксис в Go (Golang): все формы и варианты

Все функции пакета strings вызываются одинаково: strings.ИмяФункции(аргументы). Первым аргументом почти всегда идёт сама строка, с которой мы работаем. Рассмотрим основные группы методов.

1. Поиск и проверка

Функция Что делает Пример вызова Результат
Contains(s, substr) Проверяет, содержит ли строка s подстроку substr strings.Contains("Привет", "рив") true
HasPrefix(s, prefix) Проверяет, начинается ли строка s с prefix strings.HasPrefix("golang", "go") true
HasSuffix(s, suffix) Проверяет, заканчивается ли строка s на suffix strings.HasSuffix("file.txt", ".txt") true
Index(s, substr) Находит индекс (позицию) первого вхождения substr в s strings.Index("Hello", "l") 2 (счёт с нуля)
LastIndex(s, substr) Находит индекс последнего вхождения substr strings.LastIndex("Hello", "l") 3
Count(s, substr) Считает, сколько раз substr встречается в s strings.Count("ананас", "а") 3

2. Преобразование и очистка

Функция Что делает Пример вызова Результат
ToUpper(s) Переводит все буквы в верхний регистр (ЗАГЛАВНЫЕ) strings.ToUpper("Go") "GO"
ToLower(s) Переводит все буквы в нижний регистр (маленькие) strings.ToLower("Go") "go"
TrimSpace(s) Удаляет пробелы, табуляции и переносы строк в начале и конце strings.TrimSpace(" Привет! ") "Привет!"
Trim(s, cutset) Удаляет указанные символы из начала и конца strings.Trim("!!!Go!!!", "!") "Go"
Replace(s, old, new, n) Заменяет old на new в строке s. n — сколько раз заменить (-1 = все) strings.Replace("ooo", "o", "a", 2) "aao"
Repeat(s, count) Повторяет строку s указанное количество раз strings.Repeat("Ha", 3) "HaHaHa"

3. Разделение и соединение

Функция Что делает Пример вызова Результат
Split(s, sep) Разрезает строку на части по разделителю sep strings.Split("a,b,c", ",") ["a", "b", "c"]
SplitN(s, sep, n) Разрезает на n частей (остаток идёт в последнюю часть) strings.SplitN("a,b,c", ",", 2) ["a", "b,c"]
Fields(s) Разрезает строку по пробельным символам (пробел, табуляция) strings.Fields("Go is fun") ["Go", "is", "fun"]
Join(elems, sep) Склеивает срез строк в одну строку через разделитель strings.Join(["Go", "Rocks"], " ") "Go Rocks"

4. Сравнение

Функция Что делает Пример вызова Результат
Compare(a, b) Сравнивает две строки лексикографически (по алфавиту). Возвращает -1, 0 или 1 strings.Compare("a", "b") -1 (a меньше b)
EqualFold(a, b) Сравнивает строки без учёта регистра strings.EqualFold("Go", "go") true

Примеры кода — от простого к сложному

Давайте посмотрим, как все эти функции работают на практике. Каждый пример — это полноценная программа, которую можно запустить.

Пример 1: Приветствие с проверкой

Проверим, начинается ли имя пользователя с большой буквы, и приведём его к красивому виду.

package main

import (
    "fmt"
    "strings"
)

func main() {
    // Представим, что это имя ввёл пользователь
    userName := "  алексей  "

    // 1. Убираем лишние пробелы в начале и конце
    cleanedName := strings.TrimSpace(userName)
    fmt.Println("После TrimSpace:", cleanedName) // Вывод: "алексей"

    // 2. Проверяем, начинается ли имя с большой буквы
    // Для этого сравним первую букву с её заглавной версией
    firstChar := string(cleanedName[0]) // Берём первый байт (для латиницы ок, для кириллицы — смотри пример 6!)
    isCapital := strings.ToUpper(firstChar) == firstChar
    fmt.Println("Первая буква заглавная?", isCapital) // Вывод: false

    // 3. Делаем первую букву заглавной (упрощённо для примера)
    // Настоящий способ — через руны, но пока просто заменим
    if !isCapital {
        // Берём первую букву, делаем её большой, и приклеиваем остаток строки
        capitalName := strings.ToUpper(firstChar) + cleanedName[1:]
        fmt.Println("Исправленное имя:", capitalName) // Вывод: "Алексей"
    }
}

Объяснение: Мы использовали TrimSpace для очистки, ToUpper для преобразования регистра и оператор + для склейки строк. Обратите внимание: мы взяли первый символ по индексу [0] — это работает только для латиницы или если мы уверены, что первый символ однобайтовый.

Пример 2: Анализ предложения

Посчитаем слова и найдём определённое слово в тексте.

package main

import (
    "fmt"
    "strings"
)

func main() {
    text := "Go — это компилируемый язык программирования от Google."

    // 1. Считаем количество слов
    // Fields автоматически разбивает по пробелам
    words := strings.Fields(text)
    fmt.Println("Количество слов:", len(words)) // Вывод: 7

    // 2. Проверяем, содержит ли текст слово "язык"
    containsGo := strings.Contains(text, "язык")
    fmt.Println("Слово 'язык' найдено?", containsGo) // Вывод: true

    // 3. Находим позицию слова "Google"
    index := strings.Index(text, "Google")
    fmt.Println("Слово 'Google' начинается на позиции:", index) // Вывод: 56

    // 4. Считаем, сколько раз встречается буква "о"
    countO := strings.Count(text, "о")
    fmt.Println("Буква 'о' встречается:", countO, "раз(а)") // Вывод: 6
}

Объяснение: Fields — очень удобная функция, она сама находит все слова, игнорируя лишние пробелы. Contains и Count работают с подстрокой, а не с отдельным символом.

Пример 3: Очистка и форматирование данных

Представьте, что вы получили список email-адресов, но они в ужасном состоянии.

package main

import (
    "fmt"
    "strings"
)

func main() {
    // "Грязные" данные
    rawData := "  USER@EXAMPLE.COM; admin@site.org;  Test@Go.ORG  "

    // 1. Убираем пробелы по краям
    trimmed := strings.TrimSpace(rawData)
    fmt.Println("1. После TrimSpace:", trimmed)

    // 2. Разбиваем строку на части по точке с запятой
    emails := strings.Split(trimmed, ";")
    fmt.Println("2. После Split:", emails) // ["USER@EXAMPLE.COM", " admin@site.org", "  Test@Go.ORG"]

    // 3. Проходим по каждому email и чистим его
    for i, email := range emails {
        // Убираем пробелы вокруг каждого адреса
        email = strings.TrimSpace(email)
        // Приводим всё к нижнему регистру (email-адреса регистронезависимы)
        email = strings.ToLower(email)
        emails[i] = email
    }
    fmt.Println("3. После очистки:", emails) // ["user@example.com", "admin@site.org", "test@go.org"]

    // 4. Склеиваем обратно в строку, но уже через запятую
    result := strings.Join(emails, ", ")
    fmt.Println("4. Итоговая строка:", result) // "user@example.com, admin@site.org, test@go.org"
}

Объяснение: Мы последовательно применили TrimSpace, Split, ToLower и Join. Это классический пайплайн обработки данных: очистка → разделение → преобразование → сборка.

Пример 4: Замена и повторение

Создадим простой цензор и генератор шаблонов.

package main

import (
    "fmt"
    "strings"
)

func main() {
    // --- Цензор ---
    message := "Этот код — полный отстой и ужас!"
    // Заменяем плохие слова на звёздочки
    // Replace с четвёртым аргументом -1 заменяет все вхождения
    censored := strings.Replace(message, "отстой", "****", -1)
    censored = strings.Replace(censored, "ужас", "****", -1)
    fmt.Println("Оригинал:", message)
    fmt.Println("Цензура:", censored) // "Этот код — полный **** и ****!"

    // --- Генератор разделителя ---
    // Создаём линию из 20 символов "="
    separator := strings.Repeat("=", 20)
    fmt.Println(separator) // "===================="

    // --- Маскировка номера карты ---
    cardNumber := "1234 5678 9012 3456"
    // Показываем только последние 4 цифры
    // Находим последние 4 символа
    lastFour := cardNumber[len(cardNumber)-4:]
    // Создаём маску: 12 звёздочек + пробелы
    mask := strings.Repeat("*", 12) + " "
    // Склеиваем
    maskedCard := mask + lastFour
    fmt.Println("Замаскированная карта:", maskedCard) // "************ 3456"
}

Объяснение: Replace с -1 заменяет всё. Repeat — простой способ создать повторяющийся шаблон. Обратите внимание на срез [len(cardNumber)-4:] — это стандартный способ взять последние N символов.

Пример 5: Сравнение строк (регистр и алфавит)

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

package main

import (
    "fmt"
    "strings"
)

func main() {
    a := "Go"
    b := "go"
    c := "Go"

    // 1. Обычное сравнение (учёт регистра)
    fmt.Println("a == b:", a == b) // false
    fmt.Println("a == c:", a == c) // true

    // 2. Сравнение без учёта регистра
    fmt.Println("EqualFold(a, b):", strings.EqualFold(a, b)) // true

    // 3. Лексикографическое сравнение (кто больше?)
    // Compare возвращает -1, 0, 1
    fmt.Println("Compare('a', 'b'):", strings.Compare("a", "b")) // -1 (a меньше b)
    fmt.Println("Compare('b', 'a'):", strings.Compare("b", "a")) // 1  (b больше a)
    fmt.Println("Compare('a', 'a'):", strings.Compare("a", "a")) // 0  (равны)

    // 4. Сортировка имён (простой пример)
    names := []string{"Зоя", "Анна", "Борис"}
    // В Go нет встроенной сортировки строк в пакете strings,
    // но можно использовать sort.Strings
    // sort.Strings(names) // Отсортирует по алфавиту
    fmt.Println("Имена до сортировки:", names)
}

Объяснение: EqualFold — лучший друг при проверке паролей или email-адресов. Compare используется редко, обычно достаточно операторов ==, <, >.

Пример 6: Правильная работа с русскими буквами (руны)

Этот пример показывает, почему нельзя просто брать s[0] для кириллицы.

package main

import (
    "fmt"
    "unicode/utf8"
)

func main() {
    word := "Привет"

    // НЕПРАВИЛЬНО: берём первый байт
    firstByte := word[0]
    fmt.Printf("Первый байт: %d (символ: %c)
", firstByte, firstByte) // Выведет 208 (символ: Ð)

    // ПРАВИЛЬНО: используем utf8.DecodeRuneInString
    firstRune, size := utf8.DecodeRuneInString(word)
    fmt.Printf("Первая руна: %c (занимает %d байт)
", firstRune, size) // "П" (2 байта)

    // ПРАВИЛЬНО: проход по всем символам через for range
    fmt.Println("Все символы по порядку:")
    for i, r := range word {
        fmt.Printf("  Индекс байта: %d, символ: %c
", i, r)
    }
    // Вывод:
    //   Индекс байта: 0, символ: П
    //   Индекс байта: 2, символ: р
    //   Индекс байта: 4, символ: и
    //   Индекс байта: 6, символ: в
    //   Индекс байта: 8, символ: е
    //   Индекс байта: 10, символ: т

    // ПРАВИЛЬНО: перевод первой буквы в заглавную
    // Преобразуем строку в срез рун
    runes := []rune(word)
    // Меняем первую руну на её заглавную версию
    runes[0] = 'П' // В реальном коде используйте unicode.ToUpper
    result := string(runes)
    fmt.Println("Результат:", result) // "Привет" (не изменилось, т.к. уже было заглавным)
}

Объяснение: Для работы с многобайтовыми символами (русский, китайский, эмодзи) всегда используйте for range или преобразование в []rune. Индексы байтов и индексы символов — это разные вещи.

Пример 7: Сборка строки с помощью strings.Builder

Покажем, как эффективно склеивать много строк.

package main

import (
    "fmt"
    "strings"
)

func main() {
    // ПЛОХОЙ способ: склейка через + в цикле
    // (для демонстрации, не запускайте с миллионом итераций!)
    badResult := ""
    for i := 0; i < 10; i++ {
        badResult += fmt.Sprintf("Число %d, ", i)
    }
    fmt.Println("Плохой способ:", badResult)

    // ХОРОШИЙ способ: используем strings.Builder
    var builder strings.Builder

    // Записываем данные в builder
    for i := 0; i < 10; i++ {
        builder.WriteString(fmt.Sprintf("Число %d, ", i))
    }

    // Получаем итоговую строку
    goodResult := builder.String()
    fmt.Println("Хороший способ:", goodResult)

    // Дополнительно: можно узнать длину
    fmt.Println("Длина строки:", builder.Len()) // 120
}

Объяснение: strings.Builder работает как динамический буфер. Он не создаёт новую строку при каждом добавлении, а накапливает данные внутри. Это в десятки раз быстрее при большом количестве операций.

Сравнение с Python: чем похоже и чем отличается

Если вы уже знакомы с Python, вам будет проще понять Go. Но есть важные отличия.

Операция Python Go (Golang) Примечание
Длина строки len(s) len(s) В Go len возвращает количество байтов, а не символов. Для символов используйте utf8.RuneCountInString(s)
Приведение к верхнему регистру s.upper() strings.ToUpper(s) В Go это функция пакета, а не метод строки
Разделение строки s.split(",") strings.Split(s, ",") Результат — срез строк ([]string)
Соединение строк ",".join(list) strings.Join(slice, ",") В Go разделитель — второй аргумент
Проверка вхождения "sub" in s strings.Contains(s, "sub") В Go нет оператора in для строк
Замена s.replace("old", "new") strings.Replace(s, "old", "new", -1) В Go нужно явно указать количество замен
Удаление пробелов s.strip() strings.TrimSpace(s) Аналог strip()
Индекс первого вхождения s.index("sub") strings.Index(s, "sub") В Go возвращает -1, если не найдено (в Python — исключение)
Срез строки s[1:3] s[1:3] В Go срез работает по байтам, а не по символам. Будьте осторожны с кириллицей!
Форматирование f"Hello {name}" fmt.Sprintf("Hello %s", name) В Go нет f-строк, используется fmt.Sprintf
💡 Главное отличие: В Python строка — это объект, у которого есть свои методы (s.upper()). В Go строка — это просто тип данных, а все операции вынесены в отдельный пакет strings. Это сделано для простоты и производительности.

Типичные ошибки и подводные камни

Даже опытные программисты иногда спотыкаются на строках в Go. Вот самые частые ошибки.

Ошибка 1: Путаница между байтами и символами

Самая распространённая ошибка. len("Привет") вернёт 12, а не 6. "Привет"[0] вернёт не П, а мусор.

Решение: Используйте utf8.RuneCountInString для подсчёта символов и for range для перебора.

Ошибка 2: Изменение строки через индекс

Код s[0] = 'A' не скомпилируется. Строки неизменяемы.

Решение: Преобразуйте строку в []byte или []rune, измените, и преобразуйте обратно: s = string([]rune(s)).

Ошибка 3: Забыли импортировать пакет strings

Код не скомпилируется