|
Литералы, операторы и экранированные последовательностиСтр 1 из 2Следующая ⇒ Строковые литералы определяются с помощью кавычек (") или обратных апострофов (`). Кавычки используются для определения интерпретируемых строковых литералов – такие строки поддерживают экранированные последовательности, перечисленные в табл. 3.1, но они не могут занимать несколько строк в программе. Обратные апострофы используются для определения обычных строковых литералов, такие строки могут занимать несколько строк в программе, но они не поддерживают экранированных последовательностей и могут содержать любые символы, кроме обратных апострофов. Интерпретируемые строковые литералы используются чаще, но для записи многострочных сообщений, разметки HTML и регулярных выражений удобнее использовать строковые литералы в обратных апострофах. Ниже приводятся несколько примеров литералов. text1:= "\"what’s that?\", he said" // Интерпретируемый строковый литерал text2:= `"what’s that?", he said` // Простой строковый литерал radicals:= " Ö \u221A \U0000221a" // radicals == " ÖÖÖ " Таблица 3.1 - Экранированные последовательности
В этом примере были созданы три переменные типа string, при этом переменные text1 и text2 содержат один и тот же текст. Поскольку для файлов с расширением.go используется кодировка UTF-8, в них можно включать любые символы Юникода. Однако сохраняется возможность использовать экранированные последовательности Юникода, как это сделано для второго и третьего символов Ö. Здесь невозможно использовать 8-битное восьмеричное или шестнадцатеричное представление кодового пункта, так как они ограничены диапазоном от U+0000 до U+00FF, слишком узкого для представления кодового пункта U+221A, соответствующего символу Ö. Если потребуется определить длинный и интерпретируемый строковый литерал, разместив его на нескольких строках в тексте программы, можно разбить его на несколько литералов и объединить их оператором конкатенации (+). Кроме того, несмотря на то что строки в языке Go являются неизменяемыми, они поддерживают оператор добавления +=. Он замещает имеющуюся строку результатом конкатенации двух строк, если емкости исходной строки недостаточно для размещения добавляемой строки. Эти операторы перечислены в табл. 3.2. Строки могут сравниваться с помощью операторов сравнения. Ниже демонстрируется использование этих операторов: book:= "The Spirit Level" + // Конкатенация строк " by Richard Wilkinson" book += " and Kate Pickett" // Добавление в конец строки fmt.Println("Josey" < "José", "Josey" == "José") // Сравнение строк В результате выполнения этого фрагмента переменная book будет содержать текст «The Spirit Level by Richard Wilkinson and Kate Pickett», а в поток os.Stdout будет выведена строка «true false». Сравнение строк Как уже отмечалось, строки в языке Go поддерживают обычные операторы сравнения (<, <=, ==,!=, >, >=). Сравнение строк этими операторами выполняется побайтно. Строки могут сравниваться непосредственно, например на равенство, и косвенно, например когда оператор < используется для сравнения строк с целью сортировки содержимого среза []string. К сожалению, при выполнении сравнения могут возникать три проблемы. Эти проблемы проявляются во всех языках программирования, поддерживающих строки Юникода, и не являются характерными только для языка Go. Оператор [] извлечения среза без всяких ограничений может применяться только к строкам из 7-битных символов ASCII, во всех остальных случаях необходимо проявлять осторожность. Строки могут сравниваться с помощью стандартных операторов сравнения: <, <=, ==,!=, >=, >.
Таблица 3.2 - Операции со строками
Символы в языке Go могут быть представлены двумя разными (но взаимозаменяемыми) способами. Единственный символ может быть представлен значением типа rune (или int32). С этого момента термины «символ», «кодовый пункт», «символ Юникода» и «кодовый пункт Юникода» будут использоваться взаимозаменяемо для ссылки на значение типа rune (или int32), хранящее единственный символ. Строки в языке Go представлены последовательностями из нуля или более символов – каждый символ внутри строки представлен одним или более байт в кодировке UTF-8. С помощью операции преобразования типа (string(символ)) единственный символ можно преобразовать в односимвольную строку. Например: đs:= "" for _, char:= range []rune{‘đ’, 0x111, 0421, 273, '\u0111'} { fmt.Printf("[0x%X ‘%c’] ", char, char) đs += string(char) } Этот фрагмент выведет строку, в которой текст [0x111 ‘đ’] повторяется пять раз, а после его выполнения переменная đs будет содержать строку, содержащую текст đđđđđ. Преобразовать строку в срез со значениями типа rune (то есть кодовых пунктов) можно с помощью операции преобразования chars:= []rune(s), где s – значение типа string. Значение chars в этом случае будет иметь тип []int32, поскольку тип rune является синонимом типа int32. Такая возможность может пригодиться, например, когда потребуется выполнить посимвольный анализ строки и при этом выбирать символы, стоящие перед и после текущего. Обратное преобразование выполняется так же просто: s:= string(chars), где значение chars имеет тип []rune, или []int32, а значение s будет иметь тип string. Несмотря на удобство, оператор += обеспечивает не самый эффективный способ наращивания строк в циклах. Более удачный способ заключается в заполнении среза со строками ([]string) с последующим объединением его элементов вызовом функции strings.Join(). Цикл for...range можно использовать для итераций по символам строки. В этом случае в каждой итерации программе становятся доступны индекс текущей позиции в строке и кодовый пункт в этой позиции. Ниже приводятся пример использования этой версии цикла и посимвольный вывод строки написанной на греческом языке и означающей «Язык программирования». Результат работы приложения представлен на рисунке 3.1. phrase:= "Γλώσσα προγραμματισμού" fmt.Printf("string: \"%s\"\n", phrase) fmt.Println("index\trune\t\tchar\tbytes") for index, char:= range phrase { fmt.Printf("%-2d\t%U\t\t‘%c’\t% X\n", index, char, char, []byte(string(char))) } В начале фрагмента создается строковый литерал phrase и в следующей строке выводится на экран. Затем выполняются итерации по символам в строке – в языке Go цикл for...range автоматически декодирует байты UTF-8 в кодовые пункты Юникода (значения типа rune), поэтому нет необходимости беспокоиться о внутреннем их представлении. Для каждого символа выводится номер его позиции, значение кодового пункта (в форме записи, принятой в стандарте Юникода), сам символ и соответствующие ему байты в кодировке UTF-8. Рисунок 3.1 - Результат работы приложения
Чтобы получить список байтов, кодовые пункты (значения char типа rune) преобразуются в строку (содержащую единственный символ, который состоит из одного или более байтов в кодировке UTF-8). Затем эта односимвольная строка преобразуется в значение типа []byte, то есть в срез с байтами, благодаря чему появляется возможность доступа к фактическим байтам. Преобразование []byte(string) выполняется очень быстро (O(1)), так как []byte просто ссылается на внутреннее представление строки string, без необходимости копировать какие-либо данные. То же справедливо и для обратного преобразования string([]byte) – здесь байты внутреннего представления строки также никуда не копируются, поэтому данное преобразование тоже имеет сложность O(1). Преобразования между строками и последовательностями байтов перечислены в табл. 3.2 (выше). Нотация O(...) Нотация O(…) используется в теории сложности алгоритмов для описания эффективности и потребления памяти конкретными алгоритмами. В большинстве случаев в скобках указываются значения в пропорциях к n – числу обрабатываемых элементов или длине обрабатываемого элемента. В скобках также может указываться мера потребления памяти или время обработки. Запись O(1) означает постоянное время, то есть время обработки не зависит от величины n. Запись O(log n) означает увеличение времени по логарифмическому закону – это очень быстрый алгоритм, время работы которого пропорционально log n. Запись O(n) означает линейное увеличение времени – это довольно быстрый алгоритм, время работы которого пропорционально n. Запись O(n2) означает увеличение времени по квадратичному закону – это медленный алгоритм, время работы которого пропорционально n2. Запись O(nm) означает увеличение времени по полиномиальному закону – скорость работы такого алгоритма падает очень быстро с ростом n, особенно при значениях m ≥ 3. Запись O(n!) означает увеличение времени по факториальному закону – даже при маленьких значениях n такой алгоритм становится слишком медленным, чтобы иметь практическую ценность. Спецификаторы формата %-2d, %U, %c и % X описываются в таблице 3.4. Как будет показано далее, спецификатор %X используется для вывода целых чисел в шестнадцатеричном виде, а когда он применяется к значению []byte, выводится последовательность чисел, состоящих из двух шестнадцатеричных цифр, по одному на каждый байт. Наличие пробела в спецификаторе указывает, что байты должны выводиться через пробел. Индексирование строк Отсчет индексов, то есть позиций байтов UTF-8 в строке, начинается с 0 и продолжается до значения, определяющего длину строки минус единицу. Также имеется возможность индексирования в обратном направлении – с конца строки, с использованием индексов со значениями len(s) - n, где n – количество байтов, отсчитываемых с конца. Например, для выражения s:= "clár", на рис. 3.2 показана строка s в виде последовательностей символов Юникода, кодовых пунктов и байтов, а также приводятся несколько допустимых индексов и пара срезов. Для доступа к каждой позиции в строке, изображенной на рис. 3.2, можно использовать оператор индексирования [], который возвратит соответствующий ASCII-символ (как значение типа byte). Например, s[0] == ‘c’, а s[len(s) - 1] == ‘r’. Первый байт последовательности, соответствующей символу ‘á’, имеет индекс 2, но, если обратиться к элементу строки s[2], программа получит только первый байт (0xC3) символа ‘á’ в кодировке UTF-8.
Рисунок 3.2 - Строение строки
Для строк, содержащих только 7-битные ASCII-символы, первый символ (в виде значения типа byte) можно извлечь с помощью выражения s[0], а последний – с помощью выражения s[len(s) - 1]. Однако в общем случае для извлечения первого символа (в виде значения типа rune, содержащего все байты UTF-8, представляющие символ) следует использовать функцию utf8. DecodeRuneInString(), а для извлечения последнего символа – функцию utf8.DecodeLastRuneInString(). Для доступа к отдельным символам имеется несколько возможностей. Для строк, содержащих только 7-битные ASCII-символы, можно использовать обычный оператор индексирования [], обеспечивающий очень быстрый (O(1)) доступ. В случае с другими строками можно преобразовать строку в значение типа []rune и использовать оператор индексирования [] с этим значением. В этом случае индексирование тоже выполняется очень быстро (O(1)), но сама операция преобразования является достаточно дорогостоящей, с точки зрения производительности и потребления памяти (O(n)). Для произвольных строк (то есть для строк, которые могут содержать неASCII-символы), обычная операция индексирования далеко не всегда дает желаемый результат. Получение срезов строк Язык Go поддерживает операцию получения срезов строк, используя синтаксис, напоминающий синтаксис языка Python. Этот синтаксис можно использовать для получения срезов значений любых типов. Поскольку строки в языке Go хранят текст в виде байтов в кодировке UTF-8, необходимо соблюдать меры предосторожности, чтобы при создании срезов не нарушить границ символов. В этом нет ничего сложного при работе с текстом, состоящим из 7-битных символов ASCII, поскольку каждый символ представлен единственным байтом, но в других случаях ситуация может оказаться намного более сложной, так как символы могут быть представлены одним и более байтами. Как правило, в обычной практике вообще не требуется извлекать срезы строк – достаточно иметь простую возможность итераций по символам в цикле for...range, но иногда действительно бывает необходимо получить срез, чтобы извлечь подстроку. Один из способов, гарантирующих целостность границ символов при извлечении среза, заключается в использовании функций из пакета strings, таких как strings.Index() или strings.LastIndex(). В случае с s:= "clár", если записать инструкцию chars:= []rune(s), будет создана переменная chars, хранящая срез значений типа rune (то есть int32) с четырьмя кодовыми пунктами, представляющими пять байт, как показано на рис. 3.2. Любое значение типа rune (кодовый пункт) легко можно преобразовать обратно в строку, содержащую единственный символ, с помощью выражения преобразования string(char). Для большей надежности извлечения срезов из произвольных строк, определения позиции символа, начиная с которого или заканчивая которым требуется получить срез, лучше использовать функции из пакета strings. Следующее равенство справедливо не только для срезов строк, но и для срезов любых других типов: s == s[:i] + s[i:] // s – это строка; i – значение типа int; 0 <= i <= len(s) Рассмотрим пример извлечения среза с использованием упрощенного подхода. Допустим, что имеется строка, из которой требуется извлечь первое и последнее слово. Ниже представлен простейший способ решения этой задачи: line:= "ağır tədris, asanlıqla döyüş" // на азербайджанском «Тяжело в учении, легко в бою» i:= strings.Index(line, " ") // Получить индекс первого пробела firstWord:= line[:i] // Получить срез до первого пробела j:= strings.LastIndex(line, " ") // Получить индекс последнего пробела lastWord:= line[j+1:] // Получить срез от последнего пробела fmt.Println(firstWord, lastWord) // Выведет: ağır döyüş Переменной firstWord (типа string) будут присвоены байты из строки line, начиная с позиции 0 (первый байт) до позиции с индексом i - 1 (то есть до последнего байта перед пробелом включительно), потому что срезы извлекаются до конечной, указанной позиции, не включая ее. Аналогично переменной lastWord будут присвоены байты из строки line, начиная с позиции j + 1 (первый байт после пробела), до последнего байта в строке line включительно (то есть до позиции с индексом len(line) - 1). Этот способ прекрасно подходит для случая с пробелами и другими 7-битными ASCII-символами, но он не пригоден для случаев, когда слова отделяются произвольными пробельными символами Юникода, такими как U+2028 (Line Separator, – разделитель строк) или U+2029 (Paragraph Separator, – разделитель абзацев). Ниже показан пример поиска первого и последнего слова в строках, где слова могут разделяться произвольными пробельными символами. line:= "ağır\u2028tədris\u2029döyüş" i:= strings.IndexFunc(line, unicode.IsSpace) // i == 6 firstWord:= line[:i] j:= strings.LastIndexFunc(line, unicode.IsSpace) // j == 16 _, size:= utf8.DecodeRuneInString(line[j:]) // size == 3 lastWord:= line[j+size:] // j + size == 19 fmt.Println(firstWord, lastWord) // Выведет: ağır döyüş Содержимое строки line в виде последовательности символов, кодовых пунктов и байтов показано на рис. 3.3. Здесь также показаны номера позиций байтов и срезы, получаемые во фрагменте кода выше.
Рисунок 3.3 - Строение строки с пробельными символами
Функция strings.IndexFunc() возвращает индекс первой позиции в строке, определяемой первым аргументом, для которой функция, определяемая вторым аргументом (имеющая сигнатуру func(rune) bool), вернет true. Функция strings.LastIndexFunc() действует аналогично, за исключением того, что она начинает просмотр строки с конца и возвращает индекс последней позиции, для которой функция во втором аргументе вернет true. Здесь во втором аргументе передается функция IsSpace() из пакета unicode – она принимает кодовый пункт Юникода (типа rune) в виде единственного аргумента и возвращает true, если он соответствует пробельному символу. Имена функций интерпретируются как ссылки на функции, поэтому они могут передаваться в виде параметров другим функциям, при условии что сигнатуры передаваемых функций соответствуют типам параметров. Операции поиска первого пробельного символа с помощью функции strings.IndexFunc() и извлечения среза от начала строки до этого символа (не включая его), чтобы получить первое слово, реализуются просто. Но при поиске последнего пробельного символа необходимо быть внимательными, потому что некоторые пробельные символы в кодировке UTF-8 кодируются более чем одним байтом. В данном примере эта проблема решена за счет использования функции utf8.DecodeRuneInString(), возвращающей количество байт в первом символе среза строки, начинающегося с последнего пробельного символа. Затем это число добавляется к индексу последнего пробельного символа, чтобы перешагнуть через него, то есть через байты, представляющие этот пробельный символ, и извлекается срез, содержащий только последнее слово. Пакет strings Начнем с примера разбиения строки. s:= "мама мыла раму с мылом" for _, str:= range strings.Split(s, " ") { Fmt.Println(str) } Здесь имеется строка слов, разделенных одним пробелом, которая разбивается с помощью функции strings.Split(). Эта функция принимает исходную строку и строку-разделитель, по которой должно выполняться разбиение, и делит исходную строку на максимально возможное количество фрагментов. Если потребуется ограничить количество разбиений, можно воспользоваться функцией strings.SplitN(). Функция strings.SplitAfter() разбивает исходную строку точно так же, как и функция strings.Split(), но сохраняет строку-разделитель. Существует также функция strings.SplitAfterN() на случай, если потребуется ограничить число разбиений. Для разбиения строк по любому из двух или более различных символов можно использовать функцию strings.FieldsFunc(). for _, str:= range []string{"Gottfried*Wilhelm*Leibniz\t1646*1716", "Николай|Иванович|Лобачевский\t1792*1856", "Ο|πυθαγόρας\tVI*αι.*π.*χ."}{fmt.Println(strings.FieldsFunc(str, func(char rune) bool { switch char { case '\t', '*', '|': Return true } Return false })) Функция strings.FieldsFunc() принимает строку (в данном примере – переменную str) и ссылку на функцию с сигнатурой func(rune) bool. Поскольку функция достаточно маленькая и используется только в одном месте, она была определена как анонимная функция в месте, где она используется. Функция strings.FieldsFunc() выполняет итерации по всем символам в указанной строке, для каждого из них вызывает функцию, переданную во втором аргументе, и выполняет разбиение, если вызванная функция вернет true. В данном случае строка разбивается по символам табуляции, звездочки и вертикальной черты. В таблице 3.3 рассмотрим функции пакета strings. В таблице 3.3 используются следующие переменные: s и t имеют тип string, xs – тип []string, i – тип int, и f – ссылка на функцию с сигнатурой func(rune) bool. Индексы соответствуют первым байтам в кодировке UTF-8 для кодовых пунктов Юникода (символов) или строк, или имеют значение -1 в случае отсутствия соответствия. Переменная r имеет тип unicode.SpecialCase и используются для определения дополнительных правил Юникода. Таблица 3.3 - Функции из пакета strings
Замену всех вхождений подстроки в строке можно выполнить с помощью функции strings.Replace(). Например: s = "ένα\tδύο\tτρία" s = strings.Replace(s, "\t", " ", -1) fmt.Printf("%s", s) Функция strings.Replace() принимает строку для обработки, искомую подстроку, строку замены, количество замен (значение –1 означает максимально возможное количество замен) и возвращает строку, где все вхождения (неперекрывающиеся) искомой подстроки замещены указанной строкой замены. Строки, введенные пользователем или полученные из внешних источников, часто бывает необходимо нормализовать по пробельным символам: то есть удалить пробельные символы в начале и в конце строки и заменить последовательности пробельных символов внутри строки единственным пробелом. s = " ένα δύο τρία " Fmt.Println(s) fmt.Println(strings.Join(strings.Fields(strings.TrimSpace(s)), " ")) Функция strings.TrimSpace() возвращает копию переданной ей строки, в которой удалены все начальные и конечные пробельные символы. Функция strings.Fields() разбивает исходную строку по пробельным символам и возвращает значение типа []string. А функция strings.Join() принимает значение типа []string, строку-разделитель (может быть пустой строкой, однако здесь используется пробел) и возвращает единую строку, в которую через строку-разделитель объединены все строки из среза типа []string. С помощью комбинации этих трех функций выполняется нормализация строк по пробельным символам. Пакет strconv Пакет strconv содержит множество функций для преобразования строк в значения других типов и значений других типов в строки. Функции, имеющиеся в пакете, перечислены в таблице 3.4, в которой параметр bs – значение типа []byte, base – число, определяющее основание системы счисления (от 2 до 36), bits – требуемый размер результата в битах (8, 16, 32, 64 или 0 – для результата типа int; 32 или 64 – для результата типа float64), и s – строка.
Таблица 3.4 - Функции из пакета strconv
Часто бывает необходимо преобразовать строковое представление значения истинности в значение типа bool. Сделать это можно с помощью функции strconv.ParseBool(). for _, truth:= range []string{"1", "t", "TRUE", "false", "F", "0", "5"} { if b, err:= strconv.ParseBool(truth); err!= nil { fmt.Printf("\n{%v}", err) } else { fmt.Print(b, " ") } } Fmt.Println() Результат работы приложения представлен на рисунке 3.4. Рисунок 3.4 - Результат работы приложения Все функц< Что вызывает тренды на фондовых и товарных рынках Объяснение теории грузового поезда Первые 17 лет моих рыночных исследований сводились к попыткам вычислить, когда этот... ЧТО И КАК ПИСАЛИ О МОДЕ В ЖУРНАЛАХ НАЧАЛА XX ВЕКА Первый номер журнала «Аполлон» за 1909 г. начинался, по сути, с программного заявления редакции журнала... Живите по правилу: МАЛО ЛИ ЧТО НА СВЕТЕ СУЩЕСТВУЕТ? Я неслучайно подчеркиваю, что место в голове ограничено, а информации вокруг много, и что ваше право... Что способствует осуществлению желаний? Стопроцентная, непоколебимая уверенность в своем... Не нашли то, что искали? Воспользуйтесь поиском гугл на сайте:
|