Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
branch: master
Fetching contributors…

Cannot retrieve contributors at this time

633 lines (552 sloc) 56.044 kb
\chapter{Числа, знаки и строки}
\label{ch:10}
\thispagestyle{empty}
В~то время как функции, переменные, макросы и 25 специальных операторов составляют
основные блоки самого языка, строительными блоками ваших программ будут структуры данных,
которые вы будете использовать. Как заметил Фред Брукс (Fred Brooks) в своей книге
<<Мифический человеко-месяц (The Mythical Man-Month)>>, <<представление данных~-- это
сущность программирования>>.
Common Lisp предоставляет поддержку для основных типов данных, обычно существующих в
современных языках программирования: чисел (целых, с плавающей запятой и комплексных
чисел), знаков (characters)\translationnote{Более уместно было бы использовать тут слово
<<символ>>, но это слово уже занято для перевода термина <<symbol>>.}, строк, массивов
(включая многомерные массивы), списков, хэш-таблиц, потоков ввода-вывода, и абстрактное
представление имён файлов. В~Lisp функции также являются типом данных~-- они могут быть
сохранены в переменных, переданы как аргументы, использованы в качестве возвращаемых
значений, а также созданы во время выполнения.
Эти встроенные типы являются только началом. Они определены в стандарте языка, так что
программисты могут рассчитывать на то, что они будут доступны, а также они могут быть
реализованы более эффективно за счёт более сильной интеграции с конкретной реализацией.
Но, как вы увидите в следующих главах, Common Lisp также предоставляет вам возможности для
определения новых типов данных, определения операций для них и интеграции их со
встроенными типами.
Однако сейчас вы начнёте изучать встроенные типы данных. Поскольку Lisp является языком
высокого уровня, детальная информация о реализации различных типов данных хорошо спрятана.
С вашей точки зрения, как пользователя языка, встроенные типы данных определяются
функциями, которые работают с ними. Так что для изучения типа данных вы просто должны
изучить функции, которые используют этот тип. В~дополнение к этому большинство
встроенных типов имеет специальный синтаксис, который понимает процедура чтения Lisp и
который использует процедура печати Lisp. Поэтому, например, вы можете записывать строки
как \lstinline{"foo"}; числа как \lstinline{123}, \lstinline{1/23} и \lstinline{1.23}; а списки как
\lstinline{(a b c)}. Я буду описывать синтаксис для различных видов объектов тогда, когда я буду
описывать функции для работы с этими типами данных.
В~этой главе я опишу встроенные <<скалярные>> типы данных: числа, знаки и строки. С
технической точки зрения, строки не являются настоящими скалярами: строка~--
последовательность букв, и вы можете получить доступ к отдельным буквам и работать со
строками с помощью функций, работающих с последовательностями. Но я опишу строки в этой
главе, поскольку большая часть строкоориентированных функций работает с ними как с
едиными объектами, а также поскольку имеется тесная связь между некоторыми строковыми
функциями и их аналогами, работающими со знаками.
\section{Числа}
Математика, как сказала Барби, очень тяжела\pclfootnote{Цитата из Mattel's Teen Talk Barbie.}.
Common Lisp не сделает математику более лёгкой, но он делает работу с ней более простой,
чем многие другие языки программирования. В~этом нет ничего удивительного, учитывая
математическое наследство. Lisp был спроектирован математиками как средство для изучения
математических функций. Одним из основных продуктов в составе проекта MAC университета
MIT была система символьной алгебры Macsyma, написанная на Maclisp, одном из
непосредственных предков Common Lisp. Кроме того, Lisp использовался как язык для
обучения в таких заведениях, как MIT, где даже профессора компьютерных наук ёжились от
мысли о предстоящем объяснении студентам того, что $10/4 = 2$, и это привело к поддержке
Lisp'ом целочисленных дробей (exact ratios). И много раз Lisp состязался с FORTRAN в
области высокопроизводительных вычислений.
Одной из причин, почему Lisp является отличным языком для работы с математикой, является
то, что поведение его чисел больше всего похоже на математическое, нежели та жалкая
пародия, которую легко реализовать на уровне оборудования. Например, целые числа в Common
Lisp могут быть произвольной величины, а не ограничены размером машинного
слова\pclfootnote{Очевидно, что размер числа, который может быть представлен компьютером с
конечным объёмом памяти, всё также ограничен; кроме того, представление чисел,
используемое в конкретной реализации Common Lisp, может налагать другие ограничения на
размер представляемого числа. Но эти пределы находятся далеко за границами
<<астрономически>> больших чисел. Например, число атомов во Вселенной оценивается
немногим меньше, чем $2^{269}$; текущие реализации Common Lisp легко обрабатывают числа,
большие, чем $2^{262144}$.}. И деление двух целых чисел приводит к получению
целочисленной дроби, а не усечённому значению. А поскольку дроби представляются как пары
произвольных чисел, они могут представлять числа с произвольной точностью\footnote{Те,
кто интересуются использованием Common Lisp для интенсивных вычислений, могут заметить,
что наивное сравнение производительности кода на Common Lisp и языках, таких как C или
FORTRAN, вероятно, покажет, что код на Common Lisp значительно медленней. Это происходит
потому, что такие выражения, как \lstinline{(+ a b)}, на Common Lisp выполняют больше, чем
эквивалент \lstinline{a + b} на любом из этих языков. Поскольку Lisp использует динамическую
типизацию, а также обеспечивает поддержку чисел произвольного размера и комплексные
числа, просто выглядящаая операция сложения делает больше вещей, чем сложение двух
чисел, представленных как машинные слова. Однако вы можете использовать объявления
типов, чтобы дать Common Lisp информацию о типах чисел, которые вы используете, что
позволит сгенерировать код, который будет работать также как и код, сгенерированный
компилятором C или FORTRAN. Описание тонкой настройки кода, работающего с числами, для
улучшения производительности не является темой данной книги, но это возможно сделать.}\hspace{\footnotenegspace}.
С другой стороны, для высокопроизводительных вычислений вы можете захотеть пожертвовать
точностью в погоне за скоростью, которая может быть достигнута за счёт использования
операций с плавающей точкой, поддерживаемых оборудованием. Так что Common Lisp также
предлагает несколько типов чисел с плавающей точкой, которые преобразуются реализацией в
соответствующие аппаратные представления, поддерживаемые конкретной
платформой\pclfootnote{Поскольку стандарт этого не требует, многие реализации Common Lisp
поддерживают стандарт IEEE на работу с числами с плавающей точкой~-- IEEE Standard for
Binary Floating-Point Arithmetic, ANSI/ IEEE Std 754-1985 (Institute of Electrical and
Electronics Engineers, 1985).}. Числа с плавающей точкой также используются для
представления результатов вычислений, чьё математическое значение может быть
иррациональным числом.
Кроме того, Common Lisp имеет поддержку комплексных чисел~-- чисел, которые получаются в
результате таких операций, как вычисление квадратного корня, и логарифмов отрицательных
чисел. Стандарт Common Lisp имеет даже больше возможностей и позволяет указывать главные
значения (principal values) и точки ветвления (branch cuts) для иррациональных и
трансцендентных функций в комплексной плоскости.
\section{Запись чисел}
Вы можете записывать числа разными способами; вы видели это на примерах в
главе~\ref{ch:04}. Однако очень важно помнить о разнице между процедурой чтения Lisp и
процедурой вычисления~-- процедура чтения отвечает за преобразование текста в объекты
Lisp, а процедура вычисления работает только с этими объектами. Для конкретного числа с
конкретным типом может быть множество способов текстовой записи, все из которых
преобразуются процедурой чтения в один и тот же объект. Например, вы можете записать
целое число \lstinline{10} как \lstinline{10}, \lstinline{20/2}, \lstinline{#xA} или ещё дюжиной способов,
но процедура чтения преобразует все эти числа в один и тот же объект. Когда числа
выводятся на печать, например в REPL, тогда они печатаются в канонической текстовой форме,
которая может отличаться от той формы, что использовалась для задания числа.
Например:
\begin{myverb}
CL-USER> 10
10
CL-USER> 20/2
10
CL-USER> #xa
10
\end{myverb}
Целые числа записываются в следующем виде: необязательный знак числа (\lstinline{+} или~\lstinline{-}), за
которым следует одна или несколько цифр. Дроби записываются как необязательный знак
числа, за ним последовательность цифр, представляющих числитель, знак косая черта
(\lstinline{/}) и другая последовательность цифр, представляющая знаменатель. Все
рациональные числа <<канонизируются>> во время чтения~-- вот поэтому \lstinline{10} и \lstinline{20/2}
представляют одно и то же число, так же как и \lstinline{3/4} и \lstinline{6/8}. Рациональные числа
печатаются в <<упрощённой>> форме~-- целые числа печатаются с использованием синтаксиса для
целых чисел, а дроби печатаются с числителем и знаменателем, сокращённым до минимальных
значений.
Также возможна запись числа с основанием, отличным от 10. Если число предваряется
\lstinline{#B} или \lstinline{#b}, то число считывается как двоичное, в котором разрешены только
цифры \lstinline{0} и \lstinline{1}. Строки \lstinline{#O} или \lstinline{#o} используются для восьмеричных
чисел (допустимые цифры~-- \lstinline{0-7}), а \lstinline{#X} или \lstinline{#x} используются для
шестнадцатеричных (допустимые цифры \lstinline{0-F} или \lstinline{0-f}). Вы можете записывать
числа с использованием других оснований (от 2 до 36) с указанием префикса \lstinline{#nR}, где
\lstinline{n} определяет основание (всегда записывается в десятичном виде). В~качестве
дополнительных <<цифр>> используются буквы \lstinline{A-Z} или \lstinline{a-z}. Заметьте, что знак
основания применяется ко всему числу~-- невозможно написать дробь, где числитель
использует одно основание, а знаменатель~-- другое. Вы также можете записывать целые
значения (но не дроби!) в виде десятичных цифр, завершаемых десятичной
точкой\footnote{Кроме того, возможно изменить основание исчисления, которое по умолчанию
использует процедура чтения, без задания маркеров у самих чисел. Это делается путём
изменения глобальной переменной \lstinline{*READ-BASE*}. Однако это не приводит ни к чему
хорошему, кроме как полному непониманию.}\hspace{\footnotenegspace}. Ниже приводятся некоторые примеры
рациональных чисел, с их каноническим, десятичным представлением:
\begin{myverb}
123 ==> 123
+123 ==> 123
-123 ==> -123
123. ==> 123
2/3 ==> 2/3
-2/3 ==> -2/3
4/6 ==> 2/3
6/3 ==> 2
#b10101 ==> 21
#b1010/1011 ==> 10/11
#o777 ==> 511
#xDADA ==> 56026
#36rABCDEFGHIJKLMNOPQRSTUVWXYZ ==> 8337503854730415241050377135811259267835
\end{myverb}
Числа с плавающей точкой вы также можете записывать разными способами. В~отличие от
рациональных чисел, синтаксис, используемый для записи, может влиять на тип считываемого
числа. Common Lisp имеет четыре подтипа для чисел с пла\-ваю\-щей точкой: short, single,
double и long. Каждый подтип может использовать разное количество бит для представления,
что означает, что каждый тип может представлять значения разных диапазонов и с разной
точностью. Большее количество бит даёт более широкий диапазон и б\'{о}льшую
точность\pclfootnote{Поскольку главной целью использования чисел с плавающей точкой является
эффективное использование возможностей аппаратуры, то каждая реализация Lisp может
отображать эти четыре подтипа в соответствующие аппаратные типы чисел с плавающей
точкой. Если процессор поддерживает меньше, чем четыре типа, то один или несколько
типов могут быть эквивалентными.}.
Основным форматом для чисел с плавающей точкой является следующий: необязательный знак
числа, за которым следует непустая последовательность десятичных цифр, возможно с
указанием десятичной точки. За этой последовательностью может следовать маркер экспоненты
для <<компьютеризированной научной нотации>>\footnote{<<Компьютеризированная научная
нотация>>~-- это отпугивающая цитата, поскольку, хотя она часто используется в
компьютерных языках со времён FORTRAN, она отличается от настоящей научной нотации. В
частности, \lstinline{1.0e4} означает \lstinline{10000.0}, но в настоящей научной нотации оно
должно быть записано как $1.0 \times 10^4$. И в дополнение к этому в настоящей научной
записи буква $e$ означает основание натурального логарифма, так что запись вида
$1.0 \times e^4$ хоть и похожа на \lstinline{1.0e4}, но является совершенно другим
значением, равным примерно \lstinline{54.6}.}\hspace{\footnotenegspace}. Маркер экспоненты состоит из единственной
буквы, за которой следует необязательный знак числа и последовательность цифр, которая
интерпретируется как степень десяти, на которую должно быть умножено число, указанное до
маркера экспоненты. Буква в маркере экспоненты играет двойную роль: она обозначает начало
маркера и показывает что, для числа должно использоваться представление с плавающей
точкой. Маркеры экспоненты в виде букв \lstinline{s}, \lstinline{f}, \lstinline{d}, \lstinline{l} (и их
заглавные эквиваленты) обозначают использование short, single, double и long подтипов.
Буква \lstinline{e} показывает, что должно использоваться представление по умолчанию
(первоначально подтип single).
Числа без маркера экспоненты считываются с применением представления по умолчанию и
должны содержать десятичную точку и как минимум одну цифру пос\-ле неё, чтобы быть отличимыми
от целых чисел. Цифры в числах с плавающей запятой всегда рассматриваются как имеющие
основание исчисления 10~-- синтаксис с префиксами \lstinline{#B}, \lstinline{#X}, \lstinline{#O}, и
\lstinline{#R} может использоваться только с рациональными числами. Ниже приведены примеры
чисел с плавающей точкой, и их соответствующее каноническое представление:
\begin{myverb}
1.0 ==> 1.0
1e0 ==> 1.0
1d0 ==> 1.0d0
123.0 ==> 123.0
123e0 ==> 123.0
0.123 ==> 0.123
.123 ==> 0.123
123e-3 ==> 0.123
123E-3 ==> 0.123
0.123e20 ==> 1.23e+19
123d23 ==> 1.23d+25
\end{myverb}
В~заключение комплексные числа имеют отличающийся синтаксис, а именно: префикс \lstinline{#C}
или \lstinline{#c}, за которым следует список из двух чисел, представляющих реальную и мнимую
части комплексного числа. В~действительности существует пять видов комплексных чисел,
поскольку реальная и мнимая части могут быть либо рациональными числами, либо числами с
плавающей точкой.
Но вы можете записывать их как хотите: если в комплексном числе одна часть записана как
рациональное число, а вторая~-- как число с плавающей точкой, то рациональное число
преобразуется в число с плавающей точкой. Аналогично если обе части являются числами с
плавающей точкой, но с разным представлением, то число с типом с меньшей точностью будет
преобразовано в число с типом с большей точностью.
Однако не существует комплексных чисел с рациональной реальной частью и мнимой частью,
равной нулю, поскольку такие числа с математической точки зрения являются рациональными и
они будут представлены соответствующими рациональными значениями. То же самое должно быть
сделано и для чисел с компонентами, состоящими из чисел с плавающей точкой, но для этих
комплексных типов число с нулевой мнимой частью всегда является объектом, отличающимся от
числа с плавающей точкой, представляющего реальную часть. Вот некоторые примеры чисел,
записанных с использованием синтаксиса для комплексных чисел:
\begin{myverb}
#c(2 1) ==> #c(2 1)
#c(2/3 3/4) ==> #c(2/3 3/4)
#c(2 1.0) ==> #c(2.0 1.0)
#c(2.0 1.0d0) ==> #c(2.0d0 1.0d0)
#c(1/2 1.0) ==> #c(0.5 1.0)
#c(3 0) ==> 3
#c(3.0 0.0) ==> #c(3.0 0.0)
#c(1/2 0) ==> 1/2
#c(-6/3 0) ==> -2
\end{myverb}
\vfill{}
\section{Базовые математические операции}
Базовые математические операции~-- сложение, вычитание, умножение и деление реализуются
всеми типами чисел Lisp с помощью функций \lstinline{+}, \lstinline{-}, \lstinline{*} и \lstinline{/}. Вызов
любой из этих функций с количеством аргументов больше двух эквивалентен вызову той же
самой функции для первых двух аргументов и последующему вызову функции для результата и
оставшихся аргументов. Например, \lstinline{(+ 1 2 3)} эквивалентно \lstinline{(+ (+ 1 2) 3)}. При
вызове с одним аргументом \lstinline{+} и \lstinline{*} возвращают само значение; \lstinline{-}
возвращает отрицание значения, а \lstinline{/} возвращает значение, обратное
аргументу\footnote{Для полной консистентности \lstinline{+} и \lstinline{*} могут быть вызваны без
аргументов, в таком случае они возвращают соответствующее значение: \lstinline{0} для \lstinline{+}
и \lstinline{1} для \lstinline{*}.}\hspace{\footnotenegspace}.
\begin{myverb}
(+ 1 2) ==> 3
(+ 1 2 3) ==> 6
(+ 10.0 3.0) ==> 13.0
(+ #c(1 2) #c(3 4)) ==> #c(4 6)
(- 5 4) ==> 1
(- 2) ==> -2
(- 10 3 5) ==> 2
(* 2 3) ==> 6
(* 2 3 4) ==> 24
(/ 10 5) ==> 2
(/ 10 5 2) ==> 1
(/ 2 3) ==> 2/3
(/ 4) ==> 1/4
\end{myverb}
Если все аргументы имеют один тип (рациональный, с плавающей точкой или комплексный), то
тип результата будет тем же, за исключением случая, когда операция с комплексными числами приводит
к получению результата с нулевой мнимой частью, и тог\-да результат будет иметь рациональный
тип. Однако комплексные числа и числа с плавающей точкой являются <<заразными>>~-- если все
аргументы являются рациональными числами, а одно или больше чисел являются числами с
плавающей точкой, то все остальные аргументы преобразуются в соответствующие числа с
плавающей точкой с типом, соответствующим <<наибольшему>> типу аргументов с плавающей
точкой. Числа с плавающей точкой с <<меньшим>> представлением также преобразуются в тип с
<<большим>> представлением. То же самое происходит и с комплексными числами~-- все обычные
числа преобразуются в комплексные числа.
\begin{myverb}
(+ 1 2.0) ==> 3.0
(/ 2 3.0) ==> 0.6666667
(+ #c(1 2) 3) ==> #c(4 2)
(+ #c(1 2) 3/2) ==> #c(5/2 2)
(+ #c(1 1) #c(2 -1)) ==> 3
\end{myverb}
Поскольку \lstinline{/} при делении не отбрасывает остаток, то Common Lisp предлагает четыре
вида функций для усечения и округления для вещественных чисел (рациональных или с
плавающей точкой) в целые числа: \lstinline{FLOOR} усекает число в сторону отрицательной
бесконечности, возвращая наибольшее целое, меньшее или равное аргументу. \lstinline{CEILING}
усекает число в сторону положительной бесконечности, возвращая наименьшее целое, большее
или равное аргументу. \lstinline{TRUNCATE} усекает число в сторону нуля, ведя себя как
\lstinline{FLOOR} для положительных аргументов и как \lstinline{CEILING} для отрицательных. А
\lstinline{ROUND} округляет число до ближайшего целого. Если аргумент находится ровно между
двумя целыми числами, то округление происходит в сторону ближайшего чётного числа.
К этой же теме можно отнести и две функции~-- \lstinline{MOD} и \lstinline{REM}, которые возвращают
модуль и остаток деления с усечением чисел. Эти две функции соотносятся с \lstinline{FLOOR} и
\lstinline{TRUNCATE} следующими отношениями:
\begin{myverb}
(+ (* (floor (/ x y)) y) (mod x y)) === x
(+ (* (truncate (/ x y)) y) (rem x y)) === x
\end{myverb}
Таким образом, для положительных частных они будут эквивалентны, но для отрицательных они
будут давать разные результаты\footnote{Грубо говоря, \lstinline{MOD} аналогичен операции
\lstinline{%} в Perl и Python, а \lstinline{REM} эквивалентен \lstinline{%} в C и
Java. (С~технической точки зрения, точное поведение \lstinline{%} в C не было определено до
выхода стандарта C99.)}\hspace{\footnotenegspace}.
Функции \lstinline{1+} и \lstinline{1-} могут использоваться как сокращения для добавления и
вычитания единицы из числа. Заметьте, что они отличаются от макросов \lstinline{INCF} и
\lstinline{DECF}. \lstinline{1+} и \lstinline{1-} являются функциями, которые возвращают значения, а
\lstinline{INCF} и \lstinline{DECF} изменяют заданное значение. Следующие примеры показывают
соответствие между \lstinline{INCF}/\lstinline{DECF}, \lstinline{1+}/\lstinline{1-} и \lstinline{+}/\lstinline{-}:
\begin{myverb}
(incf x) === (setf x (1+ x)) === (setf x (+ x 1))
(decf x) === (setf x (1- x)) === (setf x (- x 1))
(incf x 10) === (setf x (+ x 10))
(decf x 10) === (setf x (- x 10))
\end{myverb}
\section{Сравнение чисел}
Функция \lstinline{=} является предикатом для проверки равенства чисел. Она сравнивает
значения математически, игнорируя разницу в типах. Таким образом, \lstinline{=} будет считать
равными математически равные значения разных типов, в то время как общий предикат
равенства \lstinline{EQL} будет считать их неравными из-за разницы типов. (При этом общий
предикат равенства \lstinline{EQUALP} использует \lstinline{=} для сравнения чисел.) Если эта
функция была вызвана с более чем одним аргументом, то она вернёт истинное значение, только
если они все имеют одно и то же значение. Так что:
\begin{myverb}
(= 1 1) ==> T
(= 10 20/2) ==> T
(= 1 1.0 #c(1.0 0.0) #c(1 0)) ==> T
\end{myverb}
В~противоположность этому функция \lstinline{/=} вернёт истинное значение, если все её
аргументы имеют разное значение.
\begin{myverb}
(/= 1 1) ==> NIL
(/= 1 2) ==> T
(/= 1 2 3) ==> T
(/= 1 2 3 1) ==> NIL
(/= 1 2 3 1.0) ==> NIL
\end{myverb}
Функции \lstinline{<}, \lstinline{>}, \lstinline{<=} и \lstinline{>=} сравнивают порядок
рациональных чисел и чисел с пла\-ваю\-щей точкой (другими словами, вещественных чисел).
Подобно \lstinline{=} и \lstinline{/=}, эти функции могут быть вызваны с более чем двумя
аргументами, и в этом случае каждый аргумент сравнивается с аргументом, находящимся справа
от него.
\begin{myverb}
(< 2 3) ==> T
(> 2 3) ==> NIL
(> 3 2) ==> T
(< 2 3 4) ==> T
(< 2 3 3) ==> NIL
(<= 2 3 3) ==> T
(<= 2 3 3 4) ==> T
(<= 2 3 4 3) ==> NIL
\end{myverb}
Для выбора наименьшего или наибольшего числа из нескольких вы можете использовать функцию
\lstinline{MIN} или \lstinline{MAX}, которые принимают любое число вещественных аргументов и
возвращают минимальное или максимальное значение.
\begin{myverb}
(max 10 11) ==> 11
(min -12 -10) ==> -12
(max -1 2 -3) ==> 2
\end{myverb}
Другими полезными функциями являются \lstinline{ZEROP}, \lstinline{MINUSP} и \lstinline{PLUSP}, которые
проверяют, является ли одиночное вещественное число равным, меньшим или большимm чем ноль.
Два других предиката, \lstinline{EVENP} и \lstinline{ODDP}, проверяют, является число чётным или
нечётным. Суффикс \lstinline{P} в именах этих функций соответствует стандарту наименования
предикатных функций, которые проверяют некоторое условие и возвращают логическое значение.
\section{Высшая математика}
Функции, которые вы уже увидели, являются началом списка встроенных математических
функций. Lisp также поддерживает: логарифмы (\lstinline{LOG}); экспоненты (\lstinline{EXP} и
\lstinline{EXPT}); основные тригонометрические функции (\lstinline{SIN}, \lstinline{COS} и \lstinline{TAN}) и
их противоположности (\lstinline{ASIN}, \lstinline{ACOS} и \lstinline{ATAN}); гиперболические функции
(\lstinline{SINH}, \lstinline{COSH} и \lstinline{TANH}) и их противоположности (\lstinline{ASINH},
\lstinline{ACOSH} и \lstinline{ATANH}). Также имеются функции для доступа к отдельным битам целых
чисел и для извлечения частей комплексных чисел. Для получения полного списка функций
смотрите любой справочник по Common Lisp.
\section{Знаки (characters)}
В~Common Lisp знаки являются отдельным типом объектов, а не числами. Это то, что и должно
быть,~-- знаки не являются числами, и языки, которые рассматривают их как числа, столкнутся
с проблемами, когда изменится кодировка знаков, например с 8-битного ASCII на 21-битный
Unicode\pclfootnote{Даже Java, которая с самого начала была спроектирована с учётом
использования Unicode как единственной кодировки для знаков, также столкнулась с
трудностями, поскольку знаки в Java определены как 16-битные значения, а стандарт
Unicode 3.1 расширил набор знаков Unicode, так что он теперь требует 21-битного
представления. Ой!}. Поскольку стандарт Common Lisp не требует конкретного
представления для знаков, некоторые из существующих реализаций Lisp используют Unicode как
<<родную>> кодировку знаков, несмотря на то что Unicode только задумывался в то время,
когда проводилась стандартизация Common Lisp.
Синтаксис чтения для объектов-знаков очень простой: префикс \lstinline!#\!, за которым
следует нужный знак. Так что \lstinline!#\x! обозначает знак \lstinline{x}. После
\lstinline!#\! может использоваться любой знак, включая специальные знаки, такие как
\lstinline{"}, \lstinline{(} и пробел. Однако поскольку запись пробелов и аналогичных знаков не
особенно хорошо выглядит (для человека), то для некоторых знаков существует альтернативный
синтаксис, состоящий из \lstinline!#\!, за которым следует название знака. Список
поддерживаемых имён зависит от набора знаков и реализации Lisp, но все реализации
поддерживают имена \lstinline{Space} и \lstinline{Newline}. Так что вы должны писать
\lstinline!#\Space! вместо \lstinline!#\ !, хотя последний вариант и допустим с
технической точки зрения. Другими полустандартными именами (которые реализации должны
использовать, если набор знаков содержит соответствующие знаки) являются \lstinline{Tab},
\lstinline{Page}, \lstinline{Rubout}, \lstinline{Linefeed}, \lstinline{Return} и \lstinline{Backspace}.
\section{Сравнение знаков}
Основной операцией, которую вы можете выполнить со знаками, отличной от помещения их в
строки (что будет описано дальше в этой главе), является их сравнение с другими знаками.
Поскольку знаки не являются числами, то вы не можете использовать такие функции, как
\lstinline{<} и~\lstinline{>}. Вместо этого два набора функций обеспечивают операции над знаками,
аналогичные операциям сравнения чисел; одни функции учитывают регистр знаков, а другие~--
нет.
Чувствительным к регистру аналогом численной функции \lstinline{=} является функция
\lstinline{CHAR=}. Так же как и \lstinline{=}, \lstinline{CHAR=} может получать любое количество
аргументов и возвращает истину, если они все являются одним и тем же знаком.
Нечувствительная к регистру функция называется \lstinline{CHAR-EQUAL}.
Остальные функции сравнения знаков следуют той же схеме наименования: чувствительные к
регистру функции именуются путём добавления \lstinline{CHAR} к имени аналогичной функции
для сравнения чисел; а нечувствительные к регистру функции добавляют к \lstinline{CHAR}
название операции, отделённое знаком минус. Однако заметьте, что \lstinline{<=} и \lstinline{>=}
<<именуются>> логическими эквивалентами операций \lstinline{NOT-GREATERP} и
\lstinline{NOT-LESSP}, а не более подробными названиями вида \lstinline{LESSP-OR-EQUALP} и
\lstinline{GREATERP-OR-EQUALP}. Так же как и их численные аналоги, все эти функции могут
принимать один или больше аргументов. Таб.~\ref{table:10-1} показывает отношение между
функциями сравнения для чисел и знаков.
\begin{table}[h]
\centering{}
\begin{tabular}{|c|c|c|}
\hline
Аналог для чисел & Чувствительные к регистру & Регистронезависимые \\
\hline
\code{=} &\code{CHAR=} &\code{CHAR-EQUAL} \\
\code{/=} &\code{CHAR/=} &\code{CHAR-NOT-EQUAL}\\
\code{<} &\code{CHAR<} &\code{CHAR-LESSP}\\
\code{>} &\code{CHAR>} &\code{CHAR-GREATERP}\\
\code{<=} &\code{CHAR<=} &\code{CHAR-NOT-GREATERP}\\
\code{>=} &\code{CHAR>=} &\code{CHAR-NOT-LESSP}\\
\hline
\end{tabular}
\caption{Функции сравнения знаков}
\label{table:10-1}
\end{table}
Другие функции, работающие со знаками, среди прочего предоставляют операции по проверке,
является знак буквой или цифрой, проверке регистра знака, получению соответствующего
знака в другом регистре, а также по преобразованию между численным значением и
соответствующим знаком, и наоборот. Для получения полной информации об этих функциях
смотрите справочник по Common Lisp.
\section{Строки}
Как упоминалось ранее, строки в Common Lisp на самом деле являются составными типами
данных, а именно одномерными массивами знаков. Соответственно, я опишу много вещей,
которые можно сделать со строками, когда в следующей главе я буду описывать функции для
работы с последовательностями, подтипом которых и являются строки. Но строки также имеют
свой собственный синтаксис и библиотеку функций для выполнения операций, специфичных для
строк. Я буду обсуждать данные особенности в этой главе, а всё остальное будет
обсуждаться в главе~\ref{ch:11}.
Как вы уже видели, строки записываются заключенными в двойные кавычки. Вы можете включить
в строку любой знак, поддерживаемый набором знаков, за исключением двойной кавычки
(\lstinline{"}) и обратного слэша (\lstinline!\!). Вы можете включить и эти два знака, если вы
замаскируете их с помощью обратного слэша. В~действительности знак обратный слэш всегда
маскирует следующий знак, независимо от того, чем он является, хотя нет необходимости
использовать его для чего-то отличного от \lstinline{"} и самого себя.
Таблица~\ref{table:10-2} показывает, как различные записи строк будут считываться
процедурой чтения Lisp.
\begin{table}[h]
\centering{}
\begin{tabular}{|c|c|p{75mm}|}
\hline
Литерал &Содержимое &Комментарий \\
\hline
\code{"foobar"} &\code{foobar} &Обычная строка\\
\lstinline!"foo\"bar"! &\code{foo"bar} &Обратный слэш маскирует кавычку\\
\lstinline!"foo\\bar"! &\lstinline!foo\bar! &Первый обратный слэш маскирует второй\\
\lstinline!"\"foobar\""! &\code{"foobar"} &Обратные слэши маскируют кавычки\\
\lstinline!"foo\bar"! &\code{foobar} &Обратный слэш маскирует знак b\\
\hline
\end{tabular}
\caption{Представление строк}
\label{table:10-2}
\end{table}
Заметьте, что REPL обычно печатает строки в форме, пригодной для считывания, добавляя
охватывающие знаки кавычек и нужные маскирующие знаки. Так что если вы хотите видеть
содержимое строки, то вы должны использовать какую-либо функцию, такую как
\lstinline{FORMAT}, спроектированную для печати данных в форме, удобной для человека.
Например, вот что вы увидите, если напечатаете строку, содержащую знак кавычки, в REPL:
\begin{myverb}
CL-USER> "foo\"bar"
"foo\"bar"
\end{myverb}
С другой стороны, \lstinline{FORMAT} выведет содержимое строки\footnote{Однако заметьте, что
не все строки могут быть напечатаны путём передачи их в качестве второго аргумента
функции \lstinline{FORMAT}, поскольку некоторые последовательности знаков имеют специальное
значение для \lstinline{FORMAT}. Чтобы безопасно вывести содержимое любой строки, например
значение переменной \lstinline{s}, с помощью \lstinline{FORMAT}, вы должны написать
\lstinline!(format t "~a" s)!.}\hspace{\footnotenegspace}:
\begin{myverb}
CL-USER> (format t "foo\"bar")
foo"bar
NIL
\end{myverb}
\section{Сравнение строк}
Вы можете сравнивать строки, используя набор функций, который использует то же самое
соглашение о наименовании, что и функции для сравнения знаков, с той разницей, что они
используют в качестве префикса слово \lstinline{STRING} вместо \lstinline{CHAR} (смотрите
таблицу~\ref{table:10-3}).
\begin{table}[h]
\centering{}
\begin{tabular}{|c|c|c|}
\hline
Аналог для чисел & Чувствительные к регистру & Регистронезависимые \\
\hline
\code{=} &\code{STRING=} &\code{STRING-EQUAL} \\
\code{/=} &\code{STRING/=} &\code{STRING-NOT-EQUAL}\\
\code{<} &\code{STRING<} &\code{STRING-LESSP}\\
\code{>} &\code{STRING>} &\code{STRING-GREATERP}\\
\code{<=} &\code{STRING<=} &\code{STRING-NOT-GREATERP}\\
\code{>=} &\code{STRING>=} &\code{STRING-NOT-LESSP}\\
\hline
\end{tabular}
\caption{Функции сравнения строк}
\label{table:10-3}
\end{table}
Однако, в отличие от сравнения знаков и чисел, функции сравнения строк могут сравнивать
только две строки. Это происходит потому, что эти функции также получают именованные
аргументы, которые позволяют вам ограничить области сравнения одной или обеих строк.
Аргументы: \lstinline{:start1}, \lstinline{:end1}, \lstinline{:start2} и \lstinline{:end2} указывают начальный
(включая) и конечный (не включая) индексы подстрок в первой и второй строках. Таким
образом, следующий код:
\begin{myverb}
(string= "foobarbaz" "quuxbarfoo" :start1 3 :end1 6 :start2 4 :end2 7)
\end{myverb}
\noindent{}сравнивает подстроки \lstinline{"bar"} в двух аргументах и возвращает истинное
значение\translationnote{Надеюсь, для вас не является секретом, что индексы строк начинаются с
0, а не 1.}. Аргументы \lstinline{:end1} и \lstinline{:end2} могут иметь значение
\lstinline{NIL} (или именованные аргументы могут быть полностью убраны) для указания, что
соответствующие подстроки берутся до конца строк.
Функции сравнения строк, которые возвращают истинное значение, когда их аргументы
отличаются друг от друга (это все функции, за исключением \lstinline{STRING=} и
\lstinline{STRING-EQUAL}), возвращают индекс позиции в первой строке, где строки начинают
отличаться.
\begin{myverb}
(string/= "lisp" "lissome") ==> 3
\end{myverb}
Если первая строка является префиксом второй строки, то возвращаемое значение будет равно
длине первой строки, что на единицу больше, чем самый большой допустимый индекс в этой
строке.
\begin{myverb}
(string< "lisp" "lisper") ==> 4
\end{myverb}
При сравнении подстрок результатом всё равно будет являться индекс в полной строке.
Например, следующий код сравнивает подстроки \lstinline{"bar"} и \lstinline{"baz"}, но возвращает
\lstinline{5}, поскольку это индекс знака \lstinline{r} в первой строке:
\begin{myverb}
(string< "foobar" "abaz" :start1 3 :start2 1) ==> 5 ; N.B. not 2
\end{myverb}
Другие строковые функции позволяют вам преобразовывать регистр знаков в строке, а также
удалять (trim) знаки с одного или обоих концов строки. И, как я уже отмечал перед этим,
поскольку строки на самом деле являются последовательностями, все функции работы с
последовательностями, которые я буду описывать в следующей главе, могут быть использованы
для работы со строками. Например, вы можете узнать длину строки с помощью функции
\lstinline{LENGTH} и можете получить отдельный знак строки с помощью функции доступа к элементу
последовательности \lstinline{ELT} или функции доступа к элементу массива \lstinline{AREF}.
Или вы можете использовать функцию доступа к элементу строки \lstinline{CHAR}. Но эти
функции являются предметом обсуждения следующей главы, так что перейдём к ней.
%%% Local Variables:
%%% mode: latex
%%% TeX-master: "pcl-ru"
%%% TeX-open-quote: "<<"
%%% TeX-close-quote: ">>"
%%% End:
Jump to Line
Something went wrong with that request. Please try again.