Один из постов серии «Разбираемся вместе«: представляет из себя разбор определенной темы лично мной в целях улучшения понимания и возможно, получения фидбека от более опытных людей.
* Этот пост в совокупности с комментариями может помочь кому-то лучше понять разбираемую тему или сделать более подробный разбор на базе него.
* Этот пост не служит инструкцией, курсом или документацией, где гаранитируется 100% достоверная информация по озвученной теме — автор учится сам, так что ошибки возможны.
Строка (string) — это базовый (basic) тип данных, который представляет из себя неизменяемую последовательность байт, представленных в кодировке UTF-8.
Строки должны быть заключены в двойные кавычки.
str := "A"
fmt.Println(str) // Выводит A
char := 'A'
fmt.Println(char) // Выводит 65 — символ это в первую очередь код Unicode
Unicode — это стандарт, который определяет уникальные коды для каждого символа, независимо от используемой системы или языка.
UTF-8 (8-bit Unicode Transformation Format) — это один из способов (или схем) кодирования символов Unicode в последовательности байт.
Символы из ASCII (U+0000 до U+007F) кодируются одним байтом.
Символы из более широких диапазонов Unicode (например, кириллица, китайские иероглифы, эмодзи) могут занимать 2, 3 или 4 байта.
Индексация строк
Индексация строки по умолчанию работает с байтами, а не с символами.
При обращении по индексу, возвращается тип byte.
Это отлично работает для символов ASCII, которые занимают 1 байт.
Для символов других алфавитов (например, кириллицы) через индексацию возвращается только первый байт, что может вызвать искажения при выводе символов.
Подробнее
Символы английского алфавита (символы ASCII) занимают 1 байт пространства.
Поэтому каждый отдельный символ ASCII (вернее его код) можно получить через индекс.
s := “string”
fmt.Println(s[0]) // Выводит 83 — код символа в Unicode тип byte
fmt.Println(string(s[0])) // Выводит s — символ алфавита тип string
Символы других алфавитов (или эмодзи) могут занимать более 1 байта. Поэтому через индекс нельзя получить полностью код Unicode. Вернется лишь значение первого байта.
Например, символы кириллицы занимают 2 байта пространства — числовое представление символа кириллицы, его код в Unicode не вмещается в 1 байт, поэтому происходит разделение на 2 байта.
s := “Привет”
fmt.Println(s[0]) // вернет Ð — код непонятен для кодировки utf-8, так как первый байт символа содержит лишь часть кода Unicode
символ кириллицы занимает 2 байта, через индекс забирается только первый байт, что искажает целевой символ
если взять срез fmt.Println(s[0:2]) // возвращается П — забираются первые 2 байта, которые представляют из себя код и код автоматически декодируется в понятный человеку символ.
Как работать с символами строк
Чтобы корректно работать с символами, нужно преобразовать строку в срез типа []rune.
s := “Привет”
runes := []rune(s)
fmt.Println(runes[0]) // 1055 (код символа П в Unicode)
fmt.Println(string(runes[0])) // каст кода П в строку
Длина строки
Так как строки в Go представлены как последовательность байт, и их длина измеряется в байтах.
Если передать строку в функцию len(s), функция вернет размер строки в байтах.
Каждый отдельный английский символ (ASCII) занимает 1 байт памяти и размер строки в байтах включающий только ASCII символы будет равен длине строки.
Может возникнуть неточность, если строка состоит из символов другого языка или включает эмодзи, в этом случае размер строки будет превышать фактическую длину, так как символы других алфавитов занимают больше 1 байта пространства.
Для того чтобы получить количество символов (вместо байтов), можно использовать пакет unicode/utf8.
import “unicode/utf8”
s := “Это строка”
fmt.Println(len(s)) // 19
fmt.Println(utf8.RuneCountInString(s)) // 10
Еще один способ получить длину строки (количество символов) это сделать каст строки в срез с элементами типа rune.
s := “Это строка”
fmt.Println(len([]rune(s))) // 10
Эти два способа гарантированно вернут правильное количество символов в строке, даже если в ней содержатся символы разных алфавитов.
Литералы строк
Литерал — это фиксированное значение, напрямую указанное в исходном коде программы.
Литерал представляет собой синтаксическую конструкцию, которая интерпретируется компилятором или интерпретатором как экземпляр определённого типа данных.
С помощью литерала компилятор или интерпретатор понимает, с каким типом данных работает, анализируя его синтаксическое представление (например, кавычки для строк, числовой формат для целых и вещественных чисел, фигурные скобки для коллекций и т.д.).
Литерал строки — это текст в исходном коде программы.
Кавычки, которые окружают литерал строки, — это синтаксическая часть языка Go, они нужны для обозначения строки.
С помощью двойных кавычек, компилятор golang поймет, что он работает со строкой.
Если в значении строки требуются двойные кавычки (например, для цитаты), их нужно явно указать и экранировать в самом литерале строки.
Значение строки — это результат выполнения программы, отображаемый в терминале или другом выходном потоке, без включения кавычек.
Управляющие символы
Для форматирования строк и работы с особыми символами в Go используются управляющие символы.
Управляющие символы начинаются с обратного слеша (\), за которым следует другой символ, определяющий их действие.
Они используются для добавления специальных символов, которые невозможно напрямую ввести в строку, либо для экранирования тех символов, которые иначе воспринимались бы как часть синтаксиса.
Примеры управляющих символов в Go:
Многострочная печать
Размещение строк в нескольких рядах делает текст более понятным и упорядоченным, позволяет форматировать его как письмо или сохранить разрывы строк в стихотворении или тексте песни.
Для создания строк, отображаемых на нескольких рядах, их нужно заключить в обратные апострофы.
В строках, заключенных в обратные апострофы, не требуется экранировать символы. Например, символы \ или кавычки (") можно использовать без обратного слэша.
Такие строки называют "сырыми" (raw string literals), так как они сохраняют текст в его исходном виде без обработки управляющих символов.
text := `This is a
multi-line string.
It preserves line breaks and spaces.`
Конкатенация
Оператор + используется для соединения двух строк.
s1 := "Hello, "
s2 := "world!"
s3 := s1 + s2
fmt.Println(s3) // "Hello, world!"
Эта операция создаёт новую строку, которая является результатом соединения исходных строк.
Важно понимать, что строки в Go неизменяемы, поэтому при использовании операции + возвращается новое значение, а не изменяется одна из исходных строк.
Пакет strings
Встроенный пакет, который предоставляет функции для работы со строками в кодировке utf-8.
strings.Contains()
Эта функция проверяет, содержится ли подстрока в строке.
import "strings"
s := "hello world"
fmt.Println(strings.Contains(s, "world")) // true
fmt.Println(strings.Contains(s, "go")) // false
strings.ToUpper() и strings.ToLower()
Эти функции позволяют преобразовать строку в верхний или нижний регистр.
import "strings"
s := "Hello"
fmt.Println(strings.ToUpper(s)) // "HELLO"
fmt.Println(strings.ToLower(s)) // "hello"
strings.Trim()
Функция strings.Trim() позволяет удалить указанный вторым параметром символы с начала и конца строки.
import "strings"
s := " Hello, world! "
fmt.Println(strings.Trim(s, " ")) // Выведет "Hello, world!"
strings.TrimSpace()
Можно также использовать strings.TrimSpace(), чтобы удалить только пробелы.
strings.Split()
Функция strings.Split() разделяет строку на подстроки по заданному разделителю и возвращает срез строк.
import "strings"
s := "a b c d"
result := strings.Split(s, " ")
fmt.Println(result) // [a b c d]
strings.Join()
Функция strings.Join() объединяет срез строк в одну строку с заданным разделителем.
import "strings"
slice := []string{"a", "b", "c", "d"}
result := strings.Join(slice, "")
fmt.Println(result) // "abcd"
strings.Replace()
Эта функция позволяет заменить все или ограниченное количество вхождений подстроки в строке на другую строку.
import "strings"
s := "hello world"
result := strings.Replace(s, "world", "Go", -1)
fmt.Println(result) // "hello Go"
range
range — это ключевое слово для перебора коллекций в цикле.
При каждом проходе цикла range возвращает два значения:
индекс элемента (массив или срез)
ключ при работе с картами
и затем соответствующее значение из коллекции.
Со строками ключевое слово range используется для итерации и извлечения каждого символа (rune) из строки.
s := "string"
for i, r := range s {
fmt.Printf("%d: %c\n", i, r)
}
fmt.Sprintf()
Аналог fmt.Printf(), но результат сохраняется в строку, а не выводится в консоль.
str := fmt.Sprintf("Hello, %s!", "world")
fmt.Println(str)