Строки
Строки — это конечные последовательности символов. Конечно, настоящие трудности возникают, когда спрашивают, что такое символ. Символы, с которыми знакомы говорящие по-английски, — это буквы A, B, C, и т. д., вместе с цифрами и общими знаками препинания. Эти символы стандартизированы вместе с сопоставлением целочисленным значениям от 0 до 127 стандартом ASCII. Конечно, существуют и многие другие символы, используемые в языках, отличных от английского, включая варианты символов ASCII с диакритическими знаками и другими модификациями, родственные алфавиты, такие как кириллица и греческий, и алфавиты, совершенно не связанные с ASCII и английским, включая арабский, китайский, иврит, хинди, японский и корейский. Стандарт Unicode решает сложные вопросы, что именно представляет собой символ, и в целом считается окончательным стандартом, решающим эту проблему. В зависимости от ваших потребностей, вы можете либо полностью игнорировать эти сложности и просто предположить, что существуют только символы ASCII, либо написать код, который может обрабатывать любые символы или кодировки, с которыми вы можете столкнуться при работе с текстом, отличным от ASCII. Julia делает работу с обычным текстом ASCII простой и эффективной, а обработка Unicode — максимально простой и эффективной. В частности, вы можете написать код строк в стиле C для обработки строк ASCII, и он будет работать как ожидается, как по производительности, так и по семантике. Если такой код столкнется с текстом, отличным от ASCII, он корректно завершится с ясным сообщением об ошибке, а не молча приведет к искаженным результатам. В таких случаях изменение кода для обработки данных, отличных от ASCII, является простым делом.
Есть несколько примечательных функций высокого уровня для строк Julia:
Встроенный конкретный тип, используемый для строк (и строковых литералов) в Julia, — это
String. Он поддерживает весь диапазон символов Unicode с помощью кодировки UTF-8. (Функцияtranscode()предназначена для преобразования в другие кодировки Unicode.)Все типы строк являются подтипами абстрактного типа
AbstractString, а внешние пакеты определяют дополнительныеAbstractStringподтипы (например, для других кодировок). Если вы определяете функцию, ожидающую строковый аргумент, вы должны объявить тип какAbstractStringдля поддержки любых типов строк.Как в C и Java, но в отличие от большинства динамических языков, Julia имеет тип первого класса, представляющий отдельный символ, называемый
Char. Это просто специальный вид 32-битного примитивного типа, числовое значение которого представляет собой код Unicode.Как и в Java, строки неизменяемы: значение объекта
AbstractStringизменить нельзя. Чтобы создать другое строковое значение, создайте новую строку из частей других строк.По сути, строка — это частичная функция от индексов к символам: для некоторых значений индексов значение символа не возвращается, а вместо этого генерируется исключение. Это позволяет эффективно индексировать строки по индексу байта кодированного представления, а не по индексу символа, который не может быть реализован одновременно эффективно и просто для кодировок Unicode с переменной длиной.
Символы
Значение Char представляет собой отдельный символ: это просто 32-битный примитивный тип со специальным литеральным представлением и соответствующим поведением арифметических операций, числовое значение которого интерпретируется как код Unicode.
julia> 'x' 'x': ASCII/Unicode U+0078 (category Ll: Letter, lowercase) julia> typeof(ans) Char
Вы легко можете преобразовать значение Char в его целочисленное значение, т. е. код символа:
julia> Int('x')
120
julia> typeof(ans)
Int64
На 32-битных архитектурах typeof(ans) будет Int32. Вы можете преобразовать целочисленное значение обратно в значение Char также очень легко:
julia> Char(120) 'x': ASCII/Unicode U+0078 (category Ll: Letter, lowercase)
Не все целочисленные значения являются допустимыми кодами Unicode, но для повышения производительности преобразование Char() не проверяет, что каждое значение символа является допустимым. Если вы хотите проверить, что каждое преобразованное значение является допустимым кодом символа, используйте функцию isvalid():
julia> Char(0x110000) '\U110000': Unicode U+110000 (category Cn: Other, not assigned) julia> isvalid(Char, 0x110000) false
На момент написания этого документа допустимые коды Unicode находятся в диапазоне от U+00 до U+d7ff и от U+e000 до U+10ffff. Всем этим значениям еще не присвоены понятные значения, и приложения не обязательно могут их интерпретировать, но все эти значения считаются допустимыми символами Unicode.
Вы можете ввести любой символ Unicode в одинарных кавычках, используя \u за которым следуют до четырех шестнадцатеричных цифр или \U за которым следуют до восьми шестнадцатеричных цифр (самое длинное допустимое значение требует только шести):
julia> '\u0' '\0': ASCII/Unicode U+0000 (category Cc: Other, control) julia> '\u78' 'x': ASCII/Unicode U+0078 (category Ll: Letter, lowercase) julia> '\u2200' '∀': Unicode U+2200 (category Sm: Symbol, math) julia> '\U10ffff' '\U10ffff': Unicode U+10ffff (category Cn: Other, not assigned)
Julia использует локаль и языковые настройки вашей системы, чтобы определить, какие символы можно выводить непосредственно, а какие необходимо выводить с использованием универсальных, экранированных \u или \U форм ввода. В дополнение к этим формам ввода escape-последовательностей Unicode, можно также использовать все традиционные формы ввода escape-последовательностей C:
julia> Int('\0')
0
julia> Int('\t')
9
julia> Int('\n')
10
julia> Int('\e')
27
julia> Int('\x7f')
127
julia> Int('\177')
127
julia> Int('\xff')
255
Вы можете выполнять сравнения и ограниченное количество арифметических операций со значениями Char:
julia> 'A' < 'a' true julia> 'A' <= 'a' <= 'Z' false julia> 'A' <= 'X' <= 'Z' true julia> 'x' - 'a' 23 julia> 'A' + 1 'B': ASCII/Unicode U+0042 (category Lu: Letter, uppercase)
Основы строк
Строковые литералы ограничены двойными кавычками или тройными двойными кавычками:
julia> str = "Hello, world.\n" "Hello, world.\n" julia> """Contains "quote" characters""" "Contains \"quote\" characters"
Если вы хотите извлечь символ из строки, индексируйте её:
julia> str[1] 'H': ASCII/Unicode U+0048 (category Lu: Letter, uppercase) julia> str[6] ',': ASCII/Unicode U+002c (category Po: Punctuation, other) julia> str[end] '\n': ASCII/Unicode U+000a (category Cc: Other, control)
Вся индексация в Julia основана на 1: первый элемент любого объекта с целочисленной индексацией находится по индексу 1. (Как мы увидим ниже, это не обязательно означает, что последний элемент находится по индексу n, где n — длина строки.)
В любом выражении индексации ключевое слово end может использоваться в сокращённом виде для последнего индекса (вычисленного функцией endof(str)). Вы можете выполнять арифметические и другие операции со значением end, как с обычным значением:
julia> str[end-1] '.': ASCII/Unicode U+002e (category Po: Punctuation, other) julia> str[end÷2] ' ': ASCII/Unicode U+0020 (category Zs: Separator, space)
Использование индекса меньше 1 или больше end приводит к ошибке:
julia> str[0] ERROR: BoundsError: attempt to access "Hello, world.\n" at index [0] [...] julia> str[end+1] ERROR: BoundsError: attempt to access "Hello, world.\n" at index [15] [...]
Вы также можете извлечь подстроку, используя индексацию диапазоном:
julia> str[4:9] "lo, wo"
Обратите внимание, что выражения str[k] и str[k:k] не дают одинакового результата:
julia> str[6] ',': ASCII/Unicode U+002c (category Po: Punctuation, other) julia> str[6:6] ","
Первое выражение — это значение одиночного символа типа Char, а последнее — строковое значение, которое, случайно, содержит только один символ. В Julia это совершенно разные вещи.
Unicode и UTF-8
Julia полностью поддерживает символы и строки Unicode. Как обсуждалось выше, в литералах символов коды Unicode могут быть представлены с использованием Unicode \u и \U escape-последовательностей, а также всех стандартных escape-последовательностей C. Эти же escape-последовательности могут быть использованы для записи строковых литералов:
julia> s = "\u2200 x \u2203 y" "∀ x ∃ y"
Будут ли эти символы Unicode отображаться как escape-последовательности или как специальные символы, зависит от локальных настроек вашего терминала и его поддержки Unicode. Строковые литералы закодированы с помощью кодировки UTF-8. UTF-8 — это кодировка с переменной длиной, что означает, что не все символы закодированы одинаковым числом байт. В UTF-8 символы ASCII — т. е. те, у которых код символа меньше 0x80 (128) — кодируются так же, как в ASCII, с использованием одного байта, а символы с кодами 0x80 и выше кодируются с использованием нескольких байт — до четырёх на символ. Это означает, что не каждый байтовый индекс в строке UTF-8 обязательно является допустимым индексом символа. Если вы индексируете строку в таком недопустимом байтовом индексе, генерируется ошибка:
julia> s[1] '∀': Unicode U+2200 (category Sm: Symbol, math) julia> s[2] ERROR: UnicodeError: invalid character index [...] julia> s[3] ERROR: UnicodeError: invalid character index [...] julia> s[4] ' ': ASCII/Unicode U+0020 (category Zs: Separator, space)
В этом случае символ ∀ — это символ из трёх байтов, поэтому индексы 2 и 3 недопустимы, а индекс следующего символа равен 4; этот следующий допустимый индекс может быть вычислен функцией nextind(s,1), а следующий индекс после этого — функцией nextind(s,4) и так далее.
Из-за кодировок переменной длины количество символов в строке (которое задаётся функцией length(s)) не всегда совпадает с последним индексом. Если вы перебираете индексы от 1 до endof(s) и индексируете строку s, последовательность символов, возвращаемых при отсутствии ошибок, — это последовательность символов, составляющих строку s. Таким образом, у нас есть тождество length(s) <= endof(s), так как каждый символ в строке должен иметь свой собственный индекс. Следующее — неэффективный и подробный способ перебора символов в строке s:
julia> for i = 1:endof(s)
try
println(s[i])
catch
# ignore the index error
end
end
∀
x
∃
y
Пустые строки на самом деле содержат пробелы. К счастью, приведенный выше неуклюжий код не нужен для перебора символов в строке, поскольку вы можете просто использовать строку как итерируемый объект, без обработки исключений:
julia> for c in s
println(c)
end
∀
x
∃
y
Julia по умолчанию использует кодировку UTF-8, а поддержку новых кодировок могут добавлять пакеты. Например, пакет LegacyStrings.jl реализует типы UTF16String и UTF32String. Более подробное обсуждение других кодировок и того, как реализовать поддержку для них, выходит за рамки этой документации на данный момент. Более подробное обсуждение вопросов кодировки UTF-8 можно найти в разделе ниже о литералах массивов байтов. Функция transcode() предназначена для преобразования данных между различными кодировками UTF-xx, в основном для работы с внешними данными и библиотеками.
Конкатенация
Одна из самых распространенных и полезных операций со строками — конкатенация:
julia> greet = "Hello" "Hello" julia> whom = "world" "world" julia> string(greet, ", ", whom, ".\n") "Hello, world.\n"
Julia также предоставляет * для конкатенации строк:
julia> greet * ", " * whom * ".\n" "Hello, world.\n"
Хотя * может показаться странным выбором для пользователей языков, которые предоставляют + для конкатенации строк, это использование * имеет прецедент в математике, особенно в абстрактной алгебре.
В математике + обычно обозначает коммутативную операцию, где порядок операндов не имеет значения. Примером этого является сложение матриц, где A + B == B + A для любых матриц A и B одинаковой формы. В отличие от этого, * обычно обозначает некоммутативную операцию, где порядок операндов имеет значение. Примером этого является умножение матриц, где, вообще говоря, A * B != B * A. Как и в случае с умножением матриц, конкатенация строк является некоммутативной: greet * whom != whom * greet. Поэтому * является более естественным выбором для инфиксного оператора конкатенации строк, согласующимся с общепринятым математическим использованием.
Более точно, множество всех строк конечной длины S вместе с оператором конкатенации строк * образует свободный моноид (S, *). Идентичным элементом этого множества является пустая строка, "". В тех случаях, когда свободный моноид не является коммутативным, операция обычно представляется как \cdot, *, или аналогичный символ, а не как +, что, как указано, обычно подразумевает коммутативность.
Интерполяция
Однако построение строк с помощью конкатенации может стать немного обременительным. Чтобы уменьшить необходимость этих громоздких вызовов string() или многократных умножений, Julia позволяет интерполяцию в строковые литералы с помощью $, как в Perl:
julia> "$greet, $whom.\n" "Hello, world.\n"
Это более удобочитаемо и удобно и эквивалентно приведенной выше конкатенации строк — система переписывает этот, казалось бы, одиночный строковый литерал в конкатенацию строковых литералов с переменными.
Самое короткое полное выражение после $ рассматривается как выражение, значение которого должно быть интерполировано в строку. Таким образом, вы можете интерполировать любое выражение в строку, используя круглые скобки:
julia> "1 + 2 = $(1 + 2)" "1 + 2 = 3"
И конкатенация, и интерполяция строк вызывают string() для преобразования объектов в строковый вид. Большинство объектов, не являющихся AbstractString преобразуются в строки, очень близкие к тому, как они вводятся как литеральные выражения:
julia> v = [1,2,3]
3-element Array{Int64,1}:
1
2
3
julia> "v: $v"
"v: [1, 2, 3]"
string() является тождественным элементом для AbstractString и Char значений, поэтому они интерполируются в строки как таковые, без кавычек и экранирования:
julia> c = 'x' 'x': ASCII/Unicode U+0078 (category Ll: Letter, lowercase) julia> "hi, $c" "hi, x"
Чтобы включить литеральную $ в строковый литерал, экранируйте её обратной косой чертой:
julia> print("I have \$100 in my account.\n")
I have $100 in my account.
Строковые литералы с тройными кавычками
Когда строки создаются с помощью тройных кавычек ("""...""") они имеют некоторое специальное поведение, которое может быть полезно для создания более длинных блоков текста. Во-первых, если открывающая """ следует за новой строкой, новая строка удаляется из результирующей строки.
"""hello"""
эквивалентно
""" hello"""
но
""" hello"""
будет содержать литеральную новую строку в начале. Конечные пробелы остаются неизменными. Они могут содержать " символы без экранирования. Строки с тройными кавычками также деиндентифицируются до уровня наименее отстучанной строки. Это полезно для определения строк в отступающем коде. Например:
julia> str = """
Hello,
world.
"""
" Hello,\n world.\n"
В этом случае последняя (пустая) строка перед закрывающей """ задает уровень отступа.
Обратите внимание, что переводы строк в строковых литералах, будь то с одинарными или тройными кавычками, приводят к символу новой строки (LF) \n в строке, даже если ваш редактор использует комбинацию возврата каретки \r (CR) или CRLF для завершения строк. Чтобы включить CR в строку, используйте явное экранирование \r; например, вы можете ввести литеральную строку "a CRLF line ending\r\n".
Общие операции
Вы можете лексикографически сравнивать строки, используя стандартные операторы сравнения:
julia> "abracadabra" < "xylophone" true julia> "abracadabra" == "xylophone" false julia> "Hello, world." != "Goodbye, world." true julia> "1 + 2 = 3" == "1 + 2 = $(1 + 2)" true
Вы можете искать индекс конкретного символа, используя функцию search():
julia> search("xylophone", 'x')
1
julia> search("xylophone", 'p')
5
julia> search("xylophone", 'z')
0
Вы можете начать поиск символа с заданного смещения, указав третий аргумент:
julia> search("xylophone", 'o')
4
julia> search("xylophone", 'o', 5)
7
julia> search("xylophone", 'o', 8)
0
Вы можете использовать функцию contains() для проверки, содержится ли подстрока в строке:
julia> contains("Hello, world.", "world")
true
julia> contains("Xylophon", "o")
true
julia> contains("Xylophon", "a")
false
julia> contains("Xylophon", 'o')
ERROR: MethodError: no method matching contains(::String, ::Char)
Closest candidates are:
contains(!Matched::Function, ::Any, !Matched::Any) at reduce.jl:664
contains(::AbstractString, !Matched::AbstractString) at strings/search.jl:378
Последняя ошибка возникает потому, что 'o' — это литеральный символ, а contains() — это общая функция, которая ищет подпоследовательности. Чтобы найти элемент в последовательности, нужно использовать in() вместо этого.
Еще две полезные функции для строк — repeat() и join():
julia> repeat(".:Z:.", 10)
".:Z:..:Z:..:Z:..:Z:..:Z:..:Z:..:Z:..:Z:..:Z:..:Z:."
julia> join(["apples", "bananas", "pineapples"], ", ", " and ")
"apples, bananas and pineapples"
Некоторые другие полезные функции включают:
endof(str)возвращает максимальный (байтовый) индекс, который может быть использован для индексирования вstr.length(str)число символов вstr.i = start(str)возвращает первый допустимый индекс, в котором можно найти символ вstr(обычно 1).c, j = next(str,i)возвращает следующий символ в или после индексаiи следующий допустимый индекс символа после этого. С помощьюstart()иendof()можно перебирать символы вstr.ind2chr(str,i)возвращает количество символов вstrдо и включая любой символ по индексуi.chr2ind(str,j)возвращает индекс, в которомj-ый символ вstrвстречается.
Нестандартные строковые литералы
Существуют ситуации, когда требуется построить строку или использовать семантику строк, но поведение стандартного строкового конструктора не совсем соответствует требованиям. Для таких случаев Julia предоставляет нестандартные строковые литералы. Нестандартный строковый литерал похож на обычный строковый литерал в двойных кавычках, но сразу же предваряется идентификатором и ведет себя не совсем как обычный строковый литерал. Регулярные выражения, литералы массивов байтов и литералы номеров версий, как описано ниже, являются примерами нестандартных строковых литералов. Другие примеры приведены в разделе Метапрограммирование.
Регулярные выражения
Julia имеет совместимые с Perl регулярные выражения (regexes), предоставленные библиотекой PCRE. Регулярные выражения связаны со строками двумя способами: очевидная связь заключается в том, что регулярные выражения используются для поиска регулярных шаблонов в строках; другая связь заключается в том, что регулярные выражения сами вводятся как строки, которые анализируются в автомат, который может быть использован для эффективного поиска шаблонов в строках. В Julia регулярные выражения вводятся с использованием нестандартных строковых литералов, предваряемых различными идентификаторами, начинающимися с r. Самый базовый литерал регулярного выражения без включенных опций просто использует r"...":
julia> r"^\s*(?:#|$)" r"^\s*(?:#|$)" julia> typeof(ans) Regex
Чтобы проверить, соответствует ли regex строке, используйте ismatch():
julia> ismatch(r"^\s*(?:#|$)", "not a comment") false julia> ismatch(r"^\s*(?:#|$)", "# a comment") true
Как видно здесь, ismatch() просто возвращает true или false, указывая, соответствует ли данное регулярное выражение строке или нет. Однако обычно требуется узнать не только соответствует ли строка, но и как она соответствует. Для захвата этой информации о совпадении используйте функцию match():
julia> match(r"^\s*(?:#|$)", "not a comment")
julia> match(r"^\s*(?:#|$)", "# a comment")
RegexMatch("#")
Если регулярное выражение не соответствует данной строке, match() возвращает nothing — специальное значение, которое ничего не выводит в интерактивном приглашении. Помимо отсутствия вывода, это совершенно обычное значение, и вы можете проверить его программно:
m = match(r"^\s*(?:#|$)", line)
if m === nothing
println("not a comment")
else
println("blank or comment")
end
Если регулярное выражение соответствует, значение, возвращаемое match(), является объектом RegexMatch. Эти объекты регистрируют, как выражение соответствует, включая подстроку, которой соответствует шаблон, и любые захваченные подстроки, если они есть. Этот пример захватывает только часть подстроки, которая соответствует, но, возможно, мы хотим захватить любой текст, не содержащий пробелов, после символа комментария. Мы могли бы сделать следующее:
julia> m = match(r"^\s*(?:#\s*(.*?)\s*$|$)", "# a comment ")
RegexMatch("# a comment ", 1="a comment")
При вызове match() у вас есть возможность указать индекс, с которого следует начать поиск. Например:
julia> m = match(r"[0-9]","aaaa1aaaa2aaaa3",1)
RegexMatch("1")
julia> m = match(r"[0-9]","aaaa1aaaa2aaaa3",6)
RegexMatch("2")
julia> m = match(r"[0-9]","aaaa1aaaa2aaaa3",11)
RegexMatch("3")
Вы можете извлечь следующую информацию из объекта RegexMatch:
вся подстрока, совпавшая:
m.matchзахваченные подстроки как массив строк:
m.capturesсмещение, с которого начинается все совпадение:
m.offsetсмещения захваченных подстрок как вектор:
m.offsets
В тех случаях, когда захват не соответствует, вместо подстроки m.captures содержит nothing в этой позиции, а m.offsets имеет смещение ноль (вспомните, что индексы в Julia основаны на 1, поэтому смещение 0 в строку недопустимо). Вот пара несколько причудливых примеров:
julia> m = match(r"(a|b)(c)?(d)", "acd")
RegexMatch("acd", 1="a", 2="c", 3="d")
julia> m.match
"acd"
julia> m.captures
3-element Array{Union{SubString{String}, Void},1}:
"a"
"c"
"d"
julia> m.offset
1
julia> m.offsets
3-element Array{Int64,1}:
1
2
3
julia> m = match(r"(a|b)(c)?(d)", "ad")
RegexMatch("ad", 1="a", 2=nothing, 3="d")
julia> m.match
"ad"
julia> m.captures
3-element Array{Union{SubString{String}, Void},1}:
"a"
nothing
"d"
julia> m.offset
1
julia> m.offsets
3-element Array{Int64,1}:
1
0
2
Удобно иметь захваты, возвращаемые как массив, так что можно использовать синтаксис деструктуризации для привязки их к локальным переменным:
julia> first, second, third = m.captures; first "a"
К захватам также можно получить доступ, индексируя объект RegexMatch с номером или именем группы захвата:
julia> m=match(r"(?<hour>\d+):(?<minute>\d+)","12:45")
RegexMatch("12:45", hour="12", minute="45")
julia> m[:minute]
"45"
julia> m[2]
"45"
К захватам можно обратиться в строке подстановки при использовании replace(), используя \n для ссылки на n-ую группу захвата и предваряя строку подстановки s. Группа захвата 0 относится к объекту полного совпадения. К именованным группам захвата можно обратиться в подстановке с помощью g<groupname>. Например:
julia> replace("first second", r"(\w+) (?<agroup>\w+)", s"\g<agroup> \1")
"second first"
Группы захвата с номерами также могут быть сосланы как \g<n> для разграничения, как в следующем примере:
julia> replace("a", r".", s"\g<0>1")
"a1"
Вы можете изменить поведение регулярных выражений, используя комбинацию флагов i, m, s, и x после закрывающей двойной кавычки. Эти флаги имеют то же значение, что и в Perl, как объяснено в этом фрагменте из страницы руководства perlre:
i Do case-insensitive pattern matching.
If locale matching rules are in effect, the case map is taken
from the current locale for code points less than 255, and
from Unicode rules for larger code points. However, matches
that would cross the Unicode rules/non-Unicode rules boundary
(ords 255/256) will not succeed.
m Treat string as multiple lines. That is, change "^" and "$"
from matching the start or end of the string to matching the
start or end of any line anywhere within the string.
s Treat string as single line. That is, change "." to match any
character whatsoever, even a newline, which normally it would
not match.
Used together, as r""ms, they let the "." match any character
whatsoever, while still allowing "^" and "$" to match,
respectively, just after and just before newlines within the
string.
x Tells the regular expression parser to ignore most whitespace
that is neither backslashed nor within a character class. You
can use this to break up your regular expression into
(slightly) more readable parts. The '#' character is also
treated as a metacharacter introducing a comment, just as in
ordinary code.
Например, следующее регулярное выражение включает все три флага:
julia> r"a+.*b+.*?d$"ism
r"a+.*b+.*?d$"ims
julia> match(r"a+.*b+.*?d$"ism, "Goodbye,\nOh, angry,\nBad world\n")
RegexMatch("angry,\nBad world")
Также поддерживаются строковые литералы регулярных выражений в тройных кавычках, вида r"""...""" (они могут быть удобны для регулярных выражений, содержащих кавычки или новые строки).
Литералы массивов байтов
Еще один полезный нестандартный строковый литерал — это литерал массива байтов: b"...". Эта форма позволяет использовать строковую запись для представления литеральных массивов байтов — т. е. массивов значений UInt8. Правила для литералов массивов байтов следующие:
Символы ASCII и ASCII-экранированные последовательности дают один байт.
\xи восьмеричные экранированные последовательности дают байт, соответствующий значению экранирования.Последовательности Unicode-экранирования дают последовательность байтов, кодирующую этот код символа в UTF-8.
Существует некоторое пересечение между этими правилами, так как поведение \x и восьмеричных экранирований меньше 0x80 (128) покрывается первыми двумя правилами, но здесь эти правила согласуются. Вместе эти правила позволяют легко использовать символы ASCII, произвольные значения байтов и последовательности UTF-8 для создания массивов байтов. Вот пример, использующий все три:
julia> b"DATA\xff\u2200"
8-element Array{UInt8,1}:
0x44
0x41
0x54
0x41
0xff
0xe2
0x88
0x80
Строка ASCII "DATA" соответствует байтам 68, 65, 84, 65. \xff создаёт один байт 255. Unicode-экранирование \u2200 кодируется в UTF-8 как три байта 226, 136, 128. Обратите внимание, что полученный массив байтов не соответствует допустимой строке UTF-8 — если вы попытаетесь использовать его как обычный строковый литерал, вы получите синтаксическую ошибку:
julia> "DATA\xff\u2200" ERROR: syntax: invalid UTF-8 sequence
Также обратите внимание на существенное различие между \xff и \uff: первая последовательность экранирования кодирует байт 255, тогда как вторая последовательность экранирования представляет код символа 255, который кодируется двумя байтами в UTF-8:
julia> b"\xff"
1-element Array{UInt8,1}:
0xff
julia> b"\uff"
2-element Array{UInt8,1}:
0xc3
0xbf
В литералах символов это различие игнорируется, и \xff разрешено представлять код символа 255, потому что символы всегда представляют коды символов. Однако в строках последовательности экранирования \x всегда представляют байты, а не коды символов, тогда как последовательности экранирования \u и \U всегда представляют коды символов, которые кодируются одним или несколькими байтами. Для кодов символов меньше \u80, оказывается, что кодирование каждого символа в UTF-8 — это просто один байт, созданный соответствующим \x экранированием, поэтому различием можно пренебречь. Однако для экранирований \x80 по \xff по сравнению с \u80 по \uff существует существенная разница: первые экранирования кодируют только одиночные байты, которые — если не следуют очень специфическими байтами продолжения — не образуют допустимые данные UTF-8, тогда как вторые экранирования всегда представляют коды Unicode с двухбайтовыми кодировками.
Если это всё чрезвычайно запутанно, попробуйте прочитать "The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets". Это отличное введение в Unicode и UTF-8, и может помочь прояснить некоторые моменты.
Литералы номеров версий
Номера версий легко выражаются с помощью нестандартных строковых литералов вида v"...". Литералы номеров версий создают объекты VersionNumber, которые следуют спецификациям семантической версии, и поэтому состоят из основных, второстепенных и дополнительных числовых значений, за которыми следуют предварительные и сборки буквенно-цифровые аннотации. Например, v"0.2.1-rc1+win64" разбивается на главную версию 0, второстепенную версию 2, дополнительную версию 1, предварительную версию rc1 и сборку win64. При вводе литерала версии всё, кроме номера основной версии, необязательно, поэтому, например, v"0.2" эквивалентно v"0.2.0" (с пустыми предварительными/сборками), v"2" эквивалентно v"2.0.0", и так далее.
Объекты VersionNumber в основном полезны для удобного и корректного сравнения двух (или более) версий. Например, константа VERSION содержит номер версии Julia как объект VersionNumber, и поэтому можно определить некоторое поведение, специфичное для версии, используя простые инструкции, как:
if v"0.2" <= VERSION < v"0.3-"
# do something specific to 0.2 release series
end
Обратите внимание, что в приведенном выше примере используется нестандартный номер версии v"0.3-" с последующим -. Эта запись — расширение Julia стандартной записи, используемое для обозначения версии, которая ниже любой 0.3 версии, включая все её предварительные версии. Таким образом, в приведенном выше примере код будет выполняться только с стабильными 0.2 версиями и исключит такие версии, как v"0.3.0-rc1". Чтобы также разрешить неустойчивые (т. е. предварительные версии) 0.2 версии, проверку нижней границы нужно изменить следующим образом: v"0.2-" <= VERSION.
Другое расширение спецификации версии позволяет использовать + для выражения верхнего предела версий сборки, например VERSION > v"0.2-rc1+" может означать любую версию выше 0.2-rc1 и любых её сборок: это вернет false для версии v"0.2-rc1+win64" и true для v"0.2-rc2".
Использование таких специальных версий в сравнениях является хорошей практикой (особенно, - должно всегда использоваться для верхних границ, если нет веских причин этого не делать), но их нельзя использовать как фактический номер версии чего-либо, так как они недопустимы в схеме семантической версии.
Помимо использования в константе VERSION, объекты VersionNumber широко используются в модуле Pkg для указания версий пакетов и их зависимостей.
Сырые строковые литералы
Сырые строки без интерполяции или раскодирования могут быть выражены с помощью нестандартных строковых литералов вида raw"...". Сырые строковые литералы создают обычные String объекты, которые содержат заключённое содержимое точно так, как оно было введено, без интерполяции или раскодирования. Это полезно для строк, содержащих код или разметку на других языках, которые используют $ или \ в качестве специальных символов. Исключение составляют кавычки, которые всё ещё должны быть экранированы, например, raw"\"" эквивалентно "\"".
© 2009–2016 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors
Licensed under the MIT License.
https://docs.julialang.org/en/release-0.6/manual/strings/