После изучения данной статьи вы сможете:
- Получить доступ к отдельным буквам, а также манипулировать ими;
- Зашифровать и расшифровать сообщение;
- Написать программу для людей, что говорят на разных языках.
С самого начала изучения программирования при наборе простого кода «Hello, playground» вы уже задействовали текст. Буквы, цифры и различные знаки называют символами (characters). Символы, помещенные между кавычками, называют строковым литералом (literal string).
Рекомендуем вам супер TELEGRAM канал по Golang где собраны все материалы для качественного изучения языка. Удивите всех своими знаниями на собеседовании! 😎
Мы публикуем в паблике ВК и Telegram качественные обучающие материалы для быстрого изучения Go. Подпишитесь на нас в ВК и в Telegram. Поддержите сообщество Go программистов.
Содержание статьи
- Объявление строковых переменных Go
- Необработанные строковые литералы Golang
- Символы, коды, rune и byte
- Можно ли изменять строки в Golang?
- Манипуляция символами с помощью шифра Цезаря
- ROT13 — современный вариант шифра Цезаря
- Декодирование строк в руны
Наверняка вам известно, что компьютеры оперируют через нули и единицы. Будучи компьютером, как бы вы переписали алфавит, а заодно и человеческий язык? С помощью цифр, верно. У символов алфавита есть цифровые значения, следовательно, ими можно манипулировать как числами.
Однако, все не так просто. У многих языков есть уникальные знаки, плюс ко всему к тысячам символов добавляются многочисленные эмодзи. В Go есть свои способы представления текста в наиболее удобном и простом виде.
Объявление строковых переменных Go
Литеральные значения, помещенные в кавычки, относятся к типу string
. Следующие три строки кода эквивалентны:
1 2 3 |
peace := "peace" var peace = "peace" var peace string = "peace" |
При объявлении переменной без указания значения она будет инициализирована с нулевым значением. Нулевым значением типа string являются пустые кавычки (""
):
1 |
var blank string |
Необработанные строковые литералы Golang
В строковых литералах могут находиться специальные экранированные символы наподобие \n
. Во избежание замены \n
новой строкой можно поместить текст в обратные кавычки `
вместо обычных "
, как показано в примере ниже. Обратные кавычки выводят строку в необработанном виде.
1 2 |
fmt.Println("peace be upon you\nupon you be peace") fmt.Println(`strings can span multiple lines with the \n escape sequence`) |
Вывод предыдущего листинга:
1 2 3 |
peace be upon you upon you be peace strings can span multiple lines with the \n escape sequence |
В отличие от обычных литеральных строк необработанные строки могут охватывать несколько строк исходного кода, как показано в примере ниже:
1 2 3 |
fmt.Println(` peace be upon you upon you be peace`) |
Результат листинга 2, включая отступы:
1 2 |
peace be upon you upon you be peace |
Как строковые литералы, так и необработанные строковые литералы выводят данные типа string
, как показано в примере ниже.
1 2 |
fmt.Printf("%v is a %[1]T\n", "literal string") // Выводит: literal string is a string fmt.Printf("%v is a %[1]T\n", `raw string literal`) // Выводит: raw string literal is a string |
Вопрос для проверки:
Для пути Windows C:\go
лучше использовать строковой литерал или необработанный строковой литерал? Почему?
Символы, коды, rune и byte
Unicode Consortium присваивает числовые значения, или коды, более миллиону уникальных символов. К примеру, 65 является кодом заглавной буквы А, а 1285515 — смайлика ?.
Для представления Юникода в Go используется тип rune
, который иначе называется int32
.
byte
является другим названием типа uint8
. Он используется для бинарных данных, хотя byte
может использоваться для символа английского алфавита, определенного ASCII, более старого 128-символьного набора Юникод.
Разные названия одного типа: В Go у одного и того же типа могут быть разные названия. К примеру,
rune
иint32
являются взаимозаменяемыми. Хотяbyte
иrune
были в Go с самого начала, с появлением версии Go 1.9 стало возможным объявлять свои собственные названия типов. Синтаксис выглядит следующим образом:
12 type byte = uint8type rune = int32
Оба byte
и rune
ведут себя также, как и типы целых чисел, другими названиями которых они являются. Это показано в следующем листинге:
1 2 3 4 5 6 |
var pi rune = 960 var alpha rune = 940 var omega rune = 969 var bang byte = 33 fmt.Printf("%v %v %v %v\n", pi, alpha, omega, bang) // Выводит: 960 940 969 33 |
Для отображения символов вместо их числовых значений можно использовать специальный символ %c
вместе с Printf
:
1 |
fmt.Printf("%c%c%c%c\n", pi, alpha, omega, bang) // Выводит: πάω! |
Вместе с %с
будет работать любой целочисленный тип, однако название rune
является своеобразной подсказкой, что число 960 на самом деле является каким-то символом.
Запоминать все Юникоды нет нужды, в Go есть символьный литерал. Просто поместите символ в одинарные кавычки: 'A'
. Если тип не уточняется, Go присвоит тип rune
. Следующие три строки кода эквиваленты:
1 2 3 |
grade := 'A' var grade = 'A' var grade rune = 'A' |
Переменная grade
по-прежнему содержит числовое значение, в данном случае 65
, то есть код для заглавной буквы 'A'
. Символьные литералы также можно использовать для типа с названием byte
:
1 |
var star byte = '*' |
Вопросы для проверки:
- Сколько символов кодирует ASCII?
- Другим названием какого типа является
byte
? Аrune
? - Какой код используется для звездочки
*
, смайлика ? и буквые
со знаком ударенияé
?
Можно ли изменять строки в Golang?
Строки в Golang не предназначены для манипуляции. Переменной можно присвоить другую строку, однако строки сами по себе редактировать нельзя:
1 2 |
peace := "shalom" peace = "salām" |
Программа может получить доступ к отдельным символам, но не может редактировать саму строку. В следующем листинге используются квадратные скобки []
для уточнения индекса внутри строки, что обращается к одному байту (символ ASCII). Индексы начинаются с нуля.
1 2 3 |
message := "shalom" c := message[5] fmt.Printf("%c\n", c) // Выводит: m |
Строки Go неизменны, их также нельзя менять в таких языках, как Python, Java и JavaScript. В отличие от строк в Ruby и массивов символов в С, строку в Go отредактировать нельзя:
1 |
message[5] = 'd' // Нельзя присвоить message[5] |
Задание для проверки:
Напишите программу для вывода каждого байта (символ ASCII) слова «shalom», по одному символу на строку.
Манипуляция символами с помощью шифра Цезаря
Эффективным методом отправки секретных сообщений во втором веке был сдвиг букв. Таким образом, a
становится d
, b
становится e
и так далее. В результате получается странный текст, будто написанный на иностранном языке: L fdph, L vdz, L frqtxhuhg.
Как оказалось, манипуляция символами как цифровыми значениями дается компьютерам очень легко. Это показано в коде ниже:
1 2 3 |
c := 'a' c = c + 3 fmt.Printf("%c", c) // Выводит: d |
У данного кода есть проблема. Он не подойдет для сообщений слов с буквами ‘x’, ‘y’ и ‘z’, то есть тремя последними буквами английского алфавита. Избавиться от данного неудобства можно с помощью переполнения значений, то есть возврата к началу алфавита. Таким образом, ‘x’ станет ‘а’, ‘y’ станет ‘b’, а ‘z’ станет ‘c’. С 26 знаками английского алфавита это достаточно просто:
1 2 3 |
if c > 'z' { c = c - 26 } |
Чтобы декодировать данный шифр Цезаря, нужно убрать 3, а не добавлять. Однако в таком случае вам придется учитывать, что c < ‘a’, добавляя 26.
Вопрос для проверки:
Каким будет результат выражения c = c — ‘a’ + ‘A’, если с
является 'g'
в нижнем регистре?
ROT13 — современный вариант шифра Цезаря
ROT13 (rotate 13, сдвинуть на 13 позиций) является современным вариантом шифра Цезаря. С одним отличием: сдвиг происходит не на 3, а на 13 символов. Вместе с ROT13 шифрование и дешифрование становятся одинаково удобными операциями.
Предположим, что при сканировании воздушной оболочки на наличие иноземных объектов Институт SETI получил сообщение, содержание которого выглядит следующим образом:
1 |
message := "uv vagreangvbany fcnpr fgngvba" |
Есть подозрение, что message
на самом деле является английским текстом, зашифрованным с ROT13. Перед взломом кода вам нужно кое-что знать. Длина message
составляет 30 символов, что можно определить через встроенную функцию len
:
1 |
fmt.Println(len(message)) // Выводит: 30 |
В Go есть удобные встроенные функции, которые можно использовать без оператора импорта пакетов. Функция len
определяет длину различных типов. В данном случае len
возвращает длину string
в байтах.
Следующий листинг дешифрует сообщение из космоса. Запустите код на Go Playground и выясните, что за сообщение передают инопланетяне.
1 2 3 4 5 6 7 8 9 10 11 12 |
message := "uv vagreangvbany fcnpr fgngvba" for i := 0; i < len(message); i++ { // Итерирует каждый символ ASCII c := message[i] if c >= 'a' && c <= 'z' { // Оставляет оригинальную пунктуацию и пробелы c = c + 13 if c > 'z' { c = c - 26 } } fmt.Printf("%c", c) } |
Обратите внимание, что реализация ROT13 в данном коде предназначена только для символов ASCII (байтов). Сообщение, написанное на русском или испанском, пришлось бы расшифровывать иначе. Поговорим об этом далее.
Вопросы для проверки:
- Для чего нужна встроенная функция
len
? - Запустите Листинг 7 на Go Playground? Что говорится в сообщении?
Декодирование строк в руны
Строки в Go закодированы в UTF-8, одним из нескольких стандарт кодирования юникод символов. UTF-8 является эффективным кодированием переменной длины. Один код может использовать 8, 16 или 32 бита памяти. Используя кодирование переменной длины, UTF-8 делает перевод из ASCII довольно несложным, ведь символ ASCII идентичным своим зашифрованным вариантам из UTF-8.
На заметку: В Интернете UTF-8 является доминирующим стандартом кодировки. Он был введен в 1992 году Кеном Томпсоном, одним из дизайнеров Go.
Программа с ROT13 из Листинга 7 получает доступ к отдельным байтам (8-bit) строки message
без подсчета символов длиной в большее количество байт (16-bit или 32-bit). Это неплохо подходит для английских символов (ASCII), но в случае русского или испанского — получается непонятная мешанина. Ты способен на лучшее, амиго.
Первым шагом на пути к поддержке других языков является декодирование символов в тип rune
, что делается перед манипуляцией ими. К счастью, у Go есть функции и другие особенности языка для декодирования закодированных UTF-8 строк.
Пакет utf8
предоставляет функции для определения длины строки в рунах, а не байтах, а также декодирования первого символа строки. Функция DecodeRuneInString
возвращает первый символ и количество байт, что символ тратит, как показано в Листинге 8.
На заметку: В отличие от многих других языков программирования, функции в Go могут вернуть много значений. Множественные возвращаемые значения будут подробно рассмотрены в одном из следующих уроков.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
package main import ( "fmt" "unicode/utf8" ) func main() { question := "¿Cómo estás?" fmt.Println(len(question), "bytes") // Выводит: 15 bytes fmt.Println(utf8.RuneCountInString(question), "runes") // Выводит: 12 runes c, size := utf8.DecodeRuneInString(question) fmt.Printf("First rune: %c %v bytes", c, size) // Выводит: First rune: ¿ 2 bytes } |
В языке Go есть ключевое слово range
для итерирования разнообразных наборов. Оно также может декодировать закодированные строки UTF-8, как показано в следующем листинге.
1 2 3 4 5 |
question := "¿Cómo estás?" for i, c := range question { fmt.Printf("%v %c\n", i, c) } |
При каждой итерации переменные i
и c
присваиваются текущий индекс в строке и коду (rune
) на данной позиции.
Если вам не нужен индекс, пустой идентификатор (нижнее подчеркивание) позволяет проигнорировать его:
1 2 3 |
for _, c := range question { fmt.Printf("%c ", c) // Выводит: ¿ C ó m o e s t á s ? } |
Вопросы для проверки:
- Сколько рун в английском алфавите
"abcdefghijklmnopqrstuvwxyz"
? Сколько байтов? - Сколько байтов в руне
'¿'
?
Заключение
- Экранированные символы вроде
\n
игнорируются в необработанных строковых литералах (`
); - Строки не изменяются. К отдельным символам можно получить доступ, но изменить их нельзя;
- Строки используют кодировку под названием UTF-8, где каждому символу требуется от 1 до 4 байтов;
byte
является другим названием типаuint8
, arune
является другим названием типаint32
;- Ключевое слово
range
может декодировать закодированную строку UTF-8 в руны.
Итоговое задание для проверки #1:
Расшифруйте цитату Юлия Цезаря: L fdph, L vdz, L frqtxhuhg.
Ваша программа должна будет сдвинуть буквы верхнего и нижнего регистра на -3. Помните, что ‘a’ становится ‘x’, ‘b’ становится ‘y’, а ‘c’ становится ‘z’. То же самое происходит с буквами верхнего регистра.
Итоговое задание для проверки #2:
Зашифруйте сообщение на испанском: “Hola Estación Espacial Internacional” через ROT13. Модифицируйте Листинг 7 с использованием ключевого слова range
. Теперь, когда вы используете ROT13 c испанским текстом, ударение над буквами сохраняется.
Администрирую данный сайт с целью распространения как можно большего объема обучающего материала для языка программирования Go. В IT с 2008 года, с тех пор изучаю и применяю интересующие меня технологии. Проявляю огромный интерес к машинному обучению и анализу данных.
E-mail: vasile.buldumac@ati.utm.md
Образование
Технический Университет Молдовы (utm.md), Факультет Вычислительной Техники, Информатики и Микроэлектроники
- 2014 — 2018 Universitatea Tehnică a Moldovei, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
- 2018 — 2020 Universitatea Tehnică a Moldovei, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»
Здравствуйте.я сделал вот такой код и он тоже работает и он чуть покороче так как нету смысла проверять заглавные они в любом случае не могут быть вне диапазона уходящего за границу заглавной «A»
Как вы считаете так можно?
mess := «L fdph, L vdz, L frqtxhuhg.»
for i := 0; i < len(mess); i++ {
c := mess[i]
if c >= ‘a’ && c <= ‘z’ || c >= ‘A’ && c <= ‘Z’ {
c -= 3
}
fmt.Printf(«%c», c)
}
Добрый день! Решения не идентичны по результатам, например в Вашем случае если в mess будет символ ‘a’ то в вашем решении он будет переведен в ‘^’, а по логике должен в ‘x’.
Это из-за того что если в mess есть вхождения a,b,c,A,B,C то при c-=3 мы выходим влево за границу a-z или A-Z