Материал подготовлен командой Simple-Server для администраторов VPS и выделенных серверов. Команды и пути проверяйте на тестовой машине перед production.
Кратко о задаче
Прежде всего стоит сказать, что в 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"Строку можно также объявить без явного указания значения. В этом случае строковая переменная инициализируется нулевым значением — пустой строкой:
При использовании двойных кавычек при создании строковой переменной 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:
Конкатенация строк 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||oGo предоставляет возможность переключать регистр строк — увеличивать или уменьшать символы:
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Нужен сервер для практики? Арендуйте VPS/VDS в России — root-доступ, NVMe, DDoS-защита и поддержка 24/7.