Давайте дружить в Телеграме: рассказываем про новые фичи, общаемся в комментах, прислушиваемся к вашим идеям Подписаться

Строки в GO: знакомство и начало работы

Миша Курушин
Миша Курушин
Технический писатель
26 октября 2023 г.
2242
11 минут чтения
Средний рейтинг статьи: 4.3

Строка (String) — базовый тип в языке Golang, представляющий собой простую последовательность байтов, но обладающий специальными методами для работы с ними.

При этом в Golang входит базовый пакет strings, который содержит основные (и достаточно простые) функций для работы со строковым типом данных. Эти функции похожи на типичные функции для работы со строками из других языков программирования, таких как C или C++.

Стоит также упомянуть базовый пакет fmt, используемый в примерах этой статьи для форматирования и вывода строк в консоль.

Как видно, помимо объявления строк Golang поддерживает обширный список возможностей для выполнения различных манипуляций над ними.

Объявление строк

Прежде всего стоит сказать, что в Golang строки устроены несколько иначе, чем в Java, C++ или Python. По сути, это последовательность символов, каждый из которых имеет разный размер — то есть он может быть представлен одним или несколькими байтами в кодировке UTF-8.

Дело в том, что во времена изобретения языка C, символ в компьютере представлялся 7-битным кодом ASCII. Таким образом, строка представляла собой набор множества 7-битных символов ASCII.

Однако по мере роста использования компьютеров во всем мире 7-битной схемы ASCII стало недостаточно для поддержки символов других языков. Поэтому были предложены различные модели кодировки символов, такие как Unicode, UTF-8, UTF-16, UTF-32 и т. д.

Различные языки программирования имеют свою собственную схему кодировки символов. Например, Java изначально использует UTF-16. А вот Go, напротив, построен на кодировке UTF-8. 

Именно благодаря ей строка Go может содержать универсальный текст, представляющий собой смесь любого существующего в мире языка — без какой-либо путаницы или любых других ограничений. При этом сами строки всегда статичны и имеют неизменный состав.

Через двойные кавычки

Существует несколько распространенных способов объявления (определения) строк в Go:

// явное определение через «var»
var variable1 = "some text"
// явное определение через «var» с указанием типа, но без значения
var variable2 string = "peace"
// более короткое определение
variable3 := "some text"

Строку можно также объявить без явного указания значения. В этом случае строковая переменная инициализируется нулевым значением — пустой строкой:

var variable4 string

При использовании двойных кавычек при создании строковой переменной Go будет интерпретировать специальные (экранированные) символы, написанные через слэш. Например, такие как /n:

import "fmt"

...

var some_variable = "first line/nsecond line/nthird line"

fmt.Println(some_variable) // выводим строку в консоль

// ВЫВОД:

// first line
// second line
// third line

Через одинарные кавычки

Для того, чтобы компилятор Go игнорировал спецсимволы и при этом сохранял оригинальное форматирование строк, нужно использовать одинарные (обратные) кавычки.

Вот пример объявления Golang строки с явным указанием форматирования:

import "fmt"

... 

// переносы строк в переменной указывается явно без добавления спец символов

var some_variable = `first line
second line
third line`

fmt.Println(some_variable) // выводим строку в консоль

// ВЫВОД:

// first line
// second line
// third line

Обратите внимание, что для вывода строк в консоль используется пакет fmt.

А в этом примере Go полностью игнорирует экранированные символы:

import "fmt"

...

var some_variable = `upper line /n lower line`

fmt.Println(some_variable) // выводим строку в консоль

// ВЫВОД: upper line /n lower line

Изменение строк

Смысл существования специального типа String в том, чтобы работать не с «сырой» последовательностью байтов, а со специальными методами, управляющими ей.

Однако, строго говоря, в Golang (в отличие от C и C++) строки изменять нельзя. Они статичны. Можно только получать доступ к конкретным символам по индексу:

import "fmt"

...

variable := "hello"
c := variable[0]

fmt.Printf("%c\n", c) // ВЫВОД: h

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

Важно знать, что некоторые функции для преобразования строк требуют включения специального пакета strings:

import "strings"

Конкатенация строк Golang

Самая базовая манипуляция — соединение (конкатенация) нескольких строк в одну целую. Выполняется с помощью оператора сложения:

import "fmt"

...

var variable1 = "hello"
var variable2 = "world"
var space = " "

var variable3 = variable1 + space + variable2

fmt.Println(variable3) // ВЫВОД: hello world
fmt.Println(variable2 + ", " + variable1) // ВЫВОД: world, hello

Обратите внимание, что вы не можете складывать строки с другими типами. Например, с числовыми:

fmt.Println("i am " + 24 + " years old") // ОШИБКА

Чтобы пример выше работал, необходимо использовать функцию преобразования типов. В данном случае из int в string:

import "fmt"

...

age := 24

fmt.Println("i am " +  strconv.Itoa(age) + " years old") // ВЫВОД: i am 24 years old
fmt.Println("i am " +  strconv.Itoa(24) + " years old") // ВЫВОД: i am 24 years old

Обрезание строк

Можно обрезать начальные и конечные символы строки, указав их в отдельном аргументе:

import (
  "fmt"
  "strings"
)

...

result := strings.Trim("xxxhello worldxxx", "xxx")
fmt.Println(result) // ВЫВОД: hello world

Можно также разбить строку на подстроки, указав разделитель:

import (
  "fmt"
  "strings"
)

...

result := strings.Split("hello world", " ")
fmt.Println(result) // ВЫВОД: [hello world]

Склеивание строк

Можно склеить несколько строк, размещенных в массиве, в одну целую строку, явно указав разделитель:

import (
  "fmt"
  "strings"
)

...

result := strings.Join([]string{"hello", "world"}, " ") // в качестве аргумента указывается массив строк
fmt.Println(result) // ВЫВОД: hello world

Тем не менее, использование функции склеивания или оператора сложения для конкатенации строк — не самое лучшее решение. Такой род операций каждый раз создает новую строку, снижая производительность.

Поэтому в Golang есть специальный оптимизированный инструмент для сбора целых строк из составных частей с указанием неких правил — Builder:

import (
  "fmt"
  "strings"
)

...

builded := &strings.Builder{}

builded.WriteString("very")
builded.WriteString(" ")
builded.WriteString("long")
builded.WriteString(" ")
builded.WriteString("line")

fmt.Println(builded.String()) // ВЫВОД: very long line

Более подробно об этом типе можно узнать в официальной документации Go. Тем не менее, использование Builder довольно тривиально, т.к. он не содержит большого числа методов.

Разбиение строк

Разделить строку, указав отдельным аргументом разделитель, можно следующим образом:

import (
  "fmt"
  "strings"
)

...

result := strings.Split("h-e-l-l-o", "-")

fmt.Println(result) // ВЫВОД: [h e l l o]

Замена подстроки

Также есть несколько вариантов замены определенной подстроки на другую подстроку:

import (
  "fmt"
  "strings"
)

...

result := strings.Replace("hello", "l", "|", 1) // заменяем первое вхождение
fmt.Println(result) // ВЫВОД: he|lo

result = strings.Replace("hello", "l", "|", -1) // заменяем все вхождения
fmt.Println(result) // ВЫВОД: he||o

Изменение регистра

Go предоставляет возможность переключать регистр строк — увеличивать или уменьшать символы:

import (
  "fmt"
  "strings"
)

...

result := strings.Replace("hello", "l", "|", 1) // заменяем первое вхождение
fmt.Println(result) // ВЫВОД: he|lo

result = strings.Replace("hello", "l", "|", -1) // заменяем все вхождения
fmt.Println(result) // ВЫВОД: he||o

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

Вы также можете превратить последовательность явно указанных байтов в полноценную строку и работать уже с ней:

import "fmt"

...

// последовательность байтов
any_bytes := []byte{0x47, 0x65, 0x65, 0x6b, 0x73}

// создаем строку
any_string := string(myslice1)

fmt.Println(any_string) // ВЫВОД: Geeks

Сравнение строк

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

Один из вариантов — проверить наличие некоторой подстроки в строке:

import (
  "fmt"
  "strings"
)

...

result := strings.Contains("world", "rl")
fmt.Println(result) // ВЫВОД: true

result := strings.Contains("world", "rrl")
fmt.Println(result) // ВЫВОД: false

Разумеется, классические операторы сравнения тоже приспособлены для поиска совпадений. Каждый из них посимвольно сравнивает строки в их лексическом порядке и с учетом длины:

import "fmt"

...

fmt.Println("hello" == "hello") // ВЫВОД: true
fmt.Println("hello" == "hello world") // ВЫВОД: false
fmt.Println("hello" > "hell") // ВЫВОД: true
fmt.Println("hello" > "lo") // ВЫВОД: false

Наличие префикса и суффикса

Помимо проверки на наличия подстроки, можно проверить вхождение в строку определенного префикса или суффикса:

import (
  "fmt"
  "strings"
)

...

result := strings.HasPrefix("hello", "he")
fmt.Println(result) // ВЫВОД: true

result = strings.HasSuffix("hello", "lo")
fmt.Println(result) // ВЫВОД: true

result = strings.HasPrefix("hello", "el")
fmt.Println(result) // ВЫВОД: false

Индекс вхождения подстроки

Можно получить индекс первого вхождения указанной подстроки:

import (
  "fmt"
  "strings"
)

...

result := strings.Index("hello", "el")
fmt.Println(result) // ВЫВОД: 1

result = strings.Index("hello", "le")
fmt.Println(result) // ВЫВОД: -1

Длина строки

Часто может потребоваться узнать длину строки с помощью специальной функции:

import "fmt"

...

length := len("hello")

fmt.Println(length) // ВЫВОД: 5

Так как Go использует кодировку UTF-8, длина строки соответствует количеству используемых байтов, а не число символов. Это связано с тем, что некоторые символы занимают 2 и более байта:

import "fmt"

...

length := len("привет") // указано 6 символов

fmt.Println(length) // ВЫВОД: 12

Например, вот так можно перебрать все байты на основе фактической (байтовой) длины строки:

import "fmt"

...

any_string := "привет"

// ВЫВОД: d0 bf d1 80 d0 b8 d0 b2 d0 b5 d1 82
for i := 0; i < len(any_string); i++ {
  fmt.Printf("%x ", any_string[i])
  // тут может быть некоторое действие
}

Перебор строки

В некоторых практических случаях (в том числе и при определении схожести строк) может потребоваться последовательный ручной перебор всех символов строки. Сделать это можно с помощью цикла:

import "fmt"

...

for symbol_index, symbol_value := range "Hello For All Worlds" {
  fmt.Printf("Value: %c; Index: %d\n", symbol_value, symbol_index)
  // тут может быть некоторое действие
}

Вывод и форматирование строк

Форматирование с базовыми типами

Уже знакомый вам пакет fmt имеет специальные возможности для форматирования строк в момент их вывода в консоль. На самом деле его возможности не сильно отличаются от методов форматирования в других языках программирования — используются шаблоны (templates) и глаголы нотации (annotation verbs):

import "fmt"

...

// вывод переменной строки через подстановку %s

any_string := "hello"
result := fmt.Sprintf("%s world", any_string)

fmt.Println(result) // ВЫВОД: hello world

// вывод переменной числа через подстановку %d

any_number := 13
result = fmt.Sprintf("there are %d worlds!", any_number)

fmt.Println(result) // ВЫВОД: there are 10 worlds"

// вывод переменной логики через подстановку %t

any_boolean := true
result = fmt.Sprintf("this is the %t world!", any_boolean)

fmt.Println(result) // ВЫВОД: this is the true world!

Обратите внимание, что для форматирования используется функция Sprintf, которая лишь возвращает строку, которая впоследствии выводится в консоль через Println.

Возможно использование и более сложных шаблонов форматирования с использованием нескольких переменных:

import "fmt"

...

// вывод двух строк в одном шаблоне

first_string := "hello"
second_string := "world"
result := fmt.Sprintf("%s %s", first_string, second_string)

fmt.Println(result) // ВЫВОД: hello world

// вывод трех чисел в одном шаблоне

first_number := 10
second_number := 20
third_number := 30
result = fmt.Sprintf("%d and %d and %d", first_number, second_number, third_number)

fmt.Println(result) // ВЫВОД: 10 and 20 and 30

// вывод двух логических в одном шаблоне

first_boolean := true
second_boolean := false
result = fmt.Sprintf("if it is not %t therefore it means it is %t", first_boolean , second_boolean)

fmt.Println(result) // ВЫВОД: if it is not true therefore it means it is false

Вы можете также совместить несколько разных типов переменных в рамках одной функции форматирования:

import "fmt"

...

first_string := "hello"
second_number := 13
third_boolean := true

result := fmt.Sprintf("%s to all %d %t worlds", first_string, second_number, third_boolean)

fmt.Println(result) // ВЫВОД: hello to all 13 true worlds

Форматирование с бинарными данными

Можно также выполнять особый вывод с преобразованием чисел в бинарный форма «нулей и единиц»:

import "fmt"

...

first_number := 13
second_number := 25
result := fmt.Sprintf("%b and %b", first_number, second_number)

fmt.Println(result) // ВЫВОД: 1101 and 11001

Заключение

Итак. Go предоставляет небольшой, но вполне достаточный инструментарий для манипулирования строками, покрывающий большую часть потребностей разработчика.

Одна из важных вещей, которую следует понимать при использовании строк Go, — то, что мы условно называем отдельными элементами строки (символами), на самом деле является последовательностью байтов UTF-8. Это значит, что при работе со строками мы манипулируем значениями байтов.

Поэтому попытка (которая запрещена в Go) изменения двухбайтового символа на однобайтовых приводила бы сбою.

  • Каждый раз, когда мы изменяем строку, на самом деле мы ее пересоздаем заново, но с обновленными значениями.
  • Каждый раз, когда мы запрашиваем длину строки, мы получаем не число символов, а фактическое количество используемых байтов.

Тем не менее, пакеты Go изобилуют функциями «манипулирования» строками. Однако в этой вводной статье были продемонстрированы базовые, но наиболее используемые способы взаимодействия со строками Go.

Помните, что в большинстве случаев продвинутое использование строк требует подключения специального пакета strings, а также задействования пакета fmt для форматирования строк при выводе в консоль.

Полную и подробную информацию о всех существующих методах этого пакета можно найти в официальной документации Go в соответствующем разделе «strings».

Зарегистрируйтесь и начните пользоваться
сервисами Timeweb Cloud прямо сейчас

15 лет опыта
Сосредоточьтесь на своей работе: об остальном позаботимся мы
165 000 клиентов
Нам доверяют частные лица и компании, от небольших фирм до корпораций
Поддержка 24/7
100+ специалистов поддержки, готовых помочь в чате, тикете и по телефону