Перейти к содержимому
Теория · Строки · урок 5 из 8

Пакет strings

Одни и те же задачи с текстом встречаются почти в каждой программе: найти слово, сменить регистр, разбить фразу на части. Для них в стандартной библиотеке Go есть пакет strings.

Вернёмся к картинке с фишками. Пакет strings — набор инструментов для ряда фишек: найти в нём узор, заменить фишки, разрезать ряд на куски и сложить обратно. Старый ряд ни один инструмент не трогает — строки неизменяемы. Каждый собирает новый ряд и отдаёт его вам. Давайте посмотрим на главные инструменты в деле:

GO
package main

import (
    "fmt"
    "strings"
)

func main() {
    s := "Go is fun"
    fmt.Println(strings.Contains(s, "is")) // true
    fmt.Println(strings.ToLower(s))        // go is fun
    words := strings.Split(s, " ")
    fmt.Println(len(words), strings.Join(words, "-")) // 3 Go-is-fun
}

Разберём по строкам:

  1. Contains находит "is" в строке и возвращает true.
  2. ToLower возвращает копию строки строчными буквами.
  3. Split режет фразу по пробелам на три слова.
  4. Join склеивает их обратно через дефис, и программа печатает 3 Go-is-fun.

Поиск подстроки

strings.Contains(s, sub) возвращает true, если подстрока sub встречается в s. Поиск учитывает регистр:

  • strings.Contains("Go", "go") возвращает false;
  • strings.Contains("привет", "иве") возвращает true.

Смена регистра

strings.ToLower и strings.ToUpper переводят строку в нижний и верхний регистр, кириллицу тоже: strings.ToUpper("мир") возвращает "МИР".

Каждая функция пакета возвращает результат новой строкой, а исходная остаётся прежней: строки неизменяемы. Чтобы сравнить слова без учёта регистра, сравнивают их версии в нижнем регистре: strings.ToLower("Go") == strings.ToLower("GO") даёт true.

Разбить и склеить

Три функции пакета режут строку на части и собирают части обратно. Давайте разберём каждую.

strings.Split

strings.Split(s, sep) режет строку по разделителю и возвращает срез строк []string. Она режет по каждому разделителю, поэтому:

  • если пробелы стоят подряд или по краям, Split возвращает между ними пустые строки;
  • если разделителя в строке нет, Split возвращает срез из одного элемента — всей строки.

Split выбирают, когда разделитель задан точно и важна каждая часть между разделителями, даже пустая.

strings.Join

strings.Join(words, sep) делает обратное: склеивает срез в одну строку и вставляет разделитель между элементами. Она принимает только срез строк []string. Пустой разделитель "" склеивает части вплотную.

strings.Fields

strings.Fields(s) режет по любому числу пробелов и пустых строк не возвращает. Пробелом она считает и табуляцию, и перевод строки. Fields выбирают, когда нужны слова, а сколько между ними пробелов, неважно. Сравним её со Split на одной строке:

GO
s := " go  is "
fmt.Println(len(strings.Split(s, " ")), len(strings.Fields(s))) // 5 2
fmt.Println(strings.Fields(s))                                  // [go is]
fmt.Println(strings.Join([]string{"go", "is"}, ""))             // gois

Давайте разберём пример по строкам:

  1. Первый Println печатает 5 2: Split вернул пять частей, а Fields — два слова.
  2. Второй Println печатает [go is]: Fields оставил только слова.
  3. Третий Println печатает gois: пустой разделитель склеил части вплотную.

Пять частей Split такие:

  • пустая строка до первого пробела;
  • "go";
  • пустая строка между пробелами;
  • "is";
  • пустая строка в конце.

Поэтому strings.Join(strings.Fields(s), " ") убирает из фразы лишние пробелы: Fields находит слова, а Join ставит между ними ровно по одному пробелу.

Сборка строки в цикле

На собеседовании любят спрашивать, почему длинную строку не собирают через += в цикле. Давайте сравним два способа.

Оператор +=

Строка неизменяема, поэтому каждый s += w создаёт новую строку и копирует в неё все накопленные байты. Для тысячи слов это тысяча копий, и каждая длиннее предыдущей. Для нескольких частей вне цикла оператор + подходит: копий там мало.

strings.Builder

Тип strings.Builder копит байты в одном месте, как append копит элементы среза:

GO
words := []string{"go", "is", "fun"}
var b strings.Builder
for _, w := range words {
    b.WriteString(w)
}
fmt.Println(b.String()) // goisfun

Давайте разберём пример по строкам:

  1. var b strings.Builder заводит пустой сборщик: его нулевое значение уже готово к работе.
  2. b.WriteString(w) в цикле дописывает слово в конец, не копируя заново всё, что накоплено раньше.
  3. b.String() возвращает готовую строку, и программа печатает goisfun.

Частая ошибка

Функцию пакета вызывают и ждут, что изменится сама строка:

GO
s := "go"
strings.ToUpper(s) // ✗
fmt.Println(s)     // go

Программа собирается и печатает go: ToUpper вернула новую строку, и её никто не сохранил. В отличие от append, здесь компилятор не сообщает о неиспользованном результате, и ошибку видно только по выводу. Результат присваивают переменной: s = strings.ToUpper(s).

Главное

  • Функции пакета strings не меняют строку, а возвращают новую — результат нужно сохранить.
  • Split режет по каждому разделителю и оставляет пустые строки, а Fields режет по пробелам без пустых строк.
  • Join склеивает срез строк через разделитель.
  • Длинную строку в цикле собирают через strings.Builder: каждый += копирует всю строку заново.

Подпишись, чтобы пройти курс

Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.

Войти другим способом