Строка (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"
Самая базовая манипуляция — соединение (конкатенация) нескольких строк в одну целую. Выполняется с помощью оператора сложения:
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».