Регистрация | Войти
Lisp — программируемый язык программирования
Предыдущая Оглавление Следующая

10. Числа, знаки и строки

В то время как функции, переменные, макросы и 25 специальных операторов составляют основные блоки самого языка, строительными блоками ваших программ будут структуры данных, которые вы будете использовать. Как заметил Фред Брукс (Fred Brooks) в своей книге "Мифический человеко-месяц (The Mythical Man-Month)", "представление данных – это сущность программирования".

Common Lisp предоставляет поддержку для основных типов, обычно существующих в современных языках программирования: чисел (целых, с плавающей запятой и комплексных чисел), знаков (characters)1), строк, массивов (включая многомерные массивы), списков, хеш-таблиц, потоков ввода-вывода, и переносимое представление имен файлов. В Lisp функции также являются типом данных – они могут быть сохранены в переменных, переданы как аргументы, использованы в качестве возвращаемых значений, а также созданы во время выполнения.

Эти встроенные типы являются только началом. Они определены в стандарте языка, так что программисты могут рассчитывать на то, что они будут доступны, а также они могут быть реализованы более эффективно за счет более сильной интеграции с конкретной реализацией. Но, как вы увидите в следующих главах, Common Lisp также предоставляет вам возможности для определения новых типов данных, определения операций для них, и интеграции их со встроенными типами.

Однако сейчас вы начнете изучать встроенные типы данных. Поскольку Lisp является языком высокого уровня, детальная информация о реализации различных типов данных хорошо спрятана. С вашей точки зрения, как пользователя языка, встроенные типы данных определяются функциями, которые работают с ними. Так что, для изучения типа данных, вы просто должны изучить функции, которые используют этот тип. В дополнение к этому, большинство встроенных типов имеет специальный синтаксис, который понимает процедура чтения Lisp и который использует процедура печати Lisp. Поэтому, например, вы можете записывать строки как "foo"; числа как 123, 1/23, и 1.23; а списки как (a b c). Я буду описывать синтаксис для различных видов объектов тогда, когда я буду описывать функции для работы с этими типами данных.

В этой главе я опишу встроенные "скалярные" типы данных: числа, знаки и строки. С технической точки зрения, строки не являются настоящими скалярами: строка – последовательность букв и вы можете получить доступ к отдельным буквам и работать со строками с помощью функций, работающих с последовательностями. Но я опишу строки в этой главе, поскольку большая часть строко-ориентированных функций работает с ними как с едиными объектами, а также поскольку имеется тесная связь между некоторыми строковыми функциями и их аналогами, работающими со знаками.

Числа

Математика, как сказала Барби - очень тяжела.2) Common Lisp не сделает математику более легкой, но он делает работу с ней более простой, чем многие другие языки программирования. В этом нет ничего удивительного, учитывая математическое наследство. Lisp был спроектирован математиками как средство для изучения математических функций. Одним из основных продуктов в составе проекта MAC университета MIT была система символьной алгебры Macsyma, написанная на Maclisp, одном из непосредственных предков Common Lisp. Кроме того, Lisp использовался как язык для обучения в таких заведениях как MIT, где даже профессора компьютерных наук ёжились от мысли о предстоящем объяснении студентам того, что 10/4 = 2, и это привело к поддержке Lisp'ом целочисленных дробей (exact ratios). И много раз Lisp состязался с FORTRAN в области высокопроизводительных вычислений.

Одной из причин, почему Lisp является отличным языком для работы с математикой, является то, что поведение его чисел больше всего похоже на математическое, нежели та жалкая пародия, которую легко реализовать на уровне оборудования. Например, целые числа в Common Lisp могут быть произвольной величины, а не ограничены размером машинного слова.3) И деление двух целых чисел приводит к получению целочисленной дроби, а не усеченному значению. А поскольку дроби, представляются как пары произвольных чисел, они могут представлять числа с произвольной точностью.4)

С другой стороны, для высокопроизводительных вычислений вы можете захотеть пожертвовать точностью в погоне за скоростью, которая может быть достигнута за счет использования операций с плавающей точкой, поддерживаемых оборудованием. Так что Common Lisp также предлагает несколько типов чисел с плавающей точкой, которые преобразуются реализацией в соответствующие аппаратные представления, поддерживаемые конкретной платформой.5) Числа с плавающей точкой также используются для представления результатов вычислений, чьё математическое значение может быть иррациональным числом.

Кроме того, Common Lisp имеет поддержку комплексных чисел – чисел, которые получаются в результате таких операций, как вычисление квадратного корня и логарифмов отрицательных чисел. Стандарт Common Lisp имеет даже больше возможностей и позволяет указывать FIXME главные значения (principal values) и точки ветвления (branch cuts) для иррациональных и трансцендентных функций в комплексной плоскости.

Запись чисел

Вы можете записывать числа разными способами; вы видели это на примерах в главе 4. Однако, очень важно помнить о разнице между процедурой чтения Lisp и процедурой вычисления – процедура чтения отвечает за преобразование текста в объекты Lisp, а процедура вычисления, работает только с этими объектами. Для конкретного числа с конкретным типом может быть множество способов текстовой записи, все из которых преобразуются процедурой чтения в один и тот же объект. Например, вы можете записать целое число 10 как 10, 20/2, #xA, или еще дюжиной способов, но процедура чтения преобразует все эти числа в один и тот же объект. Когда числа выводятся на печать, например в REPL, тогда они печатаются в канонической текстовой форме, которая может отличаться от той формы, которая использовалась для задания числа. Например:

CL-USER> 10
10
CL-USER> 20/2
10
CL-USER> #xa
10

Целые числа записываются в следующем виде: необязательный знак числа (+ или -), за которым следует одна или несколько цифр. Дроби записываются как необязательный знак числа, за ним последовательность цифр, представляющих числитель, знак косая черта (/), и другая последовательность цифр, представляющая знаменатель. Все рациональные числа "канонизируются" во время чтения – вот поэтому 10 и 20/2 представляют одно и тоже число, также как и 3/4 и 6/8. Рациональные числа печатаются в "упрощенной" форме – целые числа печатаются с использованием синтаксиса для целых чисел, а дроби печатаются с числителем и знаменателем, сокращенным до минимальных значений.

Также возможна запись числа с основанием, отличным от 10. Если число предваряется #B или #b, то число считывается как двоичное, в котором разрешены только цифры 0 и 1. Строки #O или #o используются для восьмеричных чисел (допустимые цифры – 0-7), а #X или #x используются для шестнадцатеричных (допустимые цифры 0-F или 0-f). Вы можете записывать числа с использованием других оснований (от 2 до 36) с указанием префикса #nR, где n определяет основание (всегда записывается в десятичном виде). В качестве дополнительных "цифр" используются буквы A-Z или a-z. Заметьте, что знак основания применяется ко всему числу – невозможно написать дробь, где числитель использует одно основание, а знаменатель – другое. Вы также можете записывать целые значения (но не дроби!) в виде десятичных цифр, завершаемых десятичной точкой.6) Ниже приводятся некоторые примеры рациональных чисел, с их каноническим, десятичным представлением:

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

Числа с плавающей точкой вы также можете записывать разными способами. В отличие от рациональных чисел, синтаксис, используемый для записи может влиять на тип считываемого числа. Common Lisp имеет четыре подтипа для чисел с плавающей точкой: short, single, double и long. Каждый подтип может использовать разное количество бит для представления, что означает, что каждый тип может представлять значения разных диапазонов и с разной точностью. Большее количество бит дает более широкий диапазон и бо́льшую точность.7)

Основным форматом для чисел с плавающей точкой является следующий: необязательный знак числа, за которым следует непустая последовательность десятичных цифр, возможно с указанием десятичной точки. За этой последовательностью может следовать маркер экспоненты для "компьютеризированной научной нотации."8) Маркер экспоненты состоит из единственной буквы, за которой следует необязательный знак числа и последовательность цифр, которая интерпретируется как степень десяти на которую должно быть умножено число, указанное до маркера экспоненты. Буква в маркере экспоненты играет двойную роль: она обозначает начало маркера, и показывает что для числа должно использоваться представление с плавающей точкой. Маркеры экспоненты в виде букв s, f, d, l (и их заглавные эквиваленты) обозначают использование short, single, double и long подтипов. Буква e показывает, что должно использоваться представление по умолчанию (первоначально подтип single).

Числа без маркера экспоненты считываются с использованием представления по умолчанию и должны содержать десятичную точку, и как минимум одну цифру после нее, чтобы быть отличимы от целых чисел. Цифры в числах с плавающей запятой всегда рассматриваются как имеющие основание исчисления 10 – синтаксис с префиксами #B, #X, #O, и #R может использоваться только с рациональными числами. Ниже приведены примеры чисел с плавающей точкой, и их соответствующее каноническое представление:

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

В заключение, комплексные числа имеют отличающийся синтаксис, а именно: префикс #C или #c, за которым следует список из двух чисел, представляющих реальную и мнимую часть комплексного числа. В действительности существует пять видов комплексных чисел, поскольку реальная и мнимая части могут быть либо рациональными числами, либо числами с плавающей точкой.

Но вы можете записывать их как хотите – если в комплексном числе одна часть записана как рациональное число, а вторая – как число с плавающей точкой, то рациональное число преобразуется в число с плавающей точкой. Аналогично, если обе части являются числами с плавающей точкой, но с разным представлением, то число с типом с меньшей точностью, будет преобразовано в число с типом с большей точностью.

Однако не существует комплексных чисел с рациональной реальной частью и мнимой частью равной нулю, поскольку такие числа с математической точки зрения являются рациональными и они будут представлены соответствующими рациональными значениями. То же самое должно быть сделано и для чисел с компонентами, состоящими из чисел с плавающей точкой, но для этих комплексных типов, число с нулевой мнимой частью всегда является объектом, отличающимся от числа с плавающей точкой, представляющего реальную часть. Вот некоторые примеры чисел, записанных с использованием синтаксиса для комплексных чисел:

#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

Базовые математические операции

Базовые математические операции – сложение, вычитание, умножение и деление, реализуются всеми типами чисел Lisp с помощью функций +, -, * и /. Вызов любой из этих функций с количеством аргументов больше двух, эквивалентен вызову той же самой функции для первых двух аргументов, и последующим вызовом функции для результата и оставшихся аргументов. Например, (+ 1 2 3) эквивалентно (+ (+ 1 2) 3). При вызове с одним аргументом, + и * возвращают само значение; - возвращает отрицание значения, а / возвращает значение обратное аргументу.9)

(+ 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

Если все аргументы имеют один тип (рациональный, с плавающей точкой или комплексный), то тип результата будет тем же, за исключением случая, когда операция с комплексными приводит к получению результата с нулевой мнимой частью, и тогда результат будет иметь рациональный тип. Однако комплексные числа и числа с плавающей точкой являются "заразными" – если все аргументы являются рациональными числами, а одно или больше чисел являются числами с плавающей точкой, то все остальные аргументы преобразуются в соответствующие числа с плавающей точкой с типом, соответствующим "наибольшему" типу аргументов с плавающей точкой. Числа с плавающей точкой с "меньшим" представлением, также преобразуются в тип с "большим" представлением. То же самое происходит и с комплексными числами – все обычные числа преобразуются в комплексные числа.

(+ 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

Поскольку / при делении не отбрасывает остаток, то Common Lisp предлагает четыре вида функций для усечения и округления для вещественных чисел (рациональных или с плавающей точкой) в целые числа: FLOOR усекает число в сторону отрицательной бесконечности, возвращая наибольшее целое, меньшее или равное аргументу. CEILING усекает число в сторону положительной бесконечности, возвращая наименьшее целое, большее или равное аргументу. TRUNCATE усекает число в сторону нуля, ведя себя как FLOOR для положительных аргументов, и как CEILING для отрицательных. А ROUND округляет число до ближайшего целого. Если аргумент находится ровно между двумя целыми числами, то округление происходит в сторону ближайшего четного числа.

К этой же теме можно отнести и две функции – MOD и REM, которые возвращают модуль и остаток деления с усечением чисел. Эти две функции соотносятся с FLOOR и TRUNCATE следующими отношениями:

(+ (* (floor    (/ x y)) y) (mod x y)) === x
(+ (* (truncate (/ x y)) y) (rem x y)) === x

Таким образом, для положительных частных они будут эквивалентны, но для отрицательных, они будут давать разные результаты.10)

Функции 1+ и 1- могут использоваться как сокращения для добавления и вычитания единицы из числа. Заметьте, что они отличаются от макросов INCF и DECF. 1+ и 1- являются функциями, которые возвращают значения, а INCF и DECF изменяют заданное значение. Следующие примеры показывают соответствие между INCF/DECF, 1+/1- и +/-:

(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))

Сравнение чисел

Функция = является предикатом для проверки равенства чисел. Она сравнивает значения математически, игнорируя разницу в типах. Таким образом, = будет считать равными математически равные значения разных типов, в то время как общий предикат равенства EQL будет считать их не равными, из-за разницы типов. (При этом общий предикат равенства EQUALP использует = для сравнения чисел.) Если эта функция была вызвана с более чем одним аргументом, то она вернёт истинное значение только если они все имеют одно и тоже значение. Так что:

(= 1 1)                        ==> T
(= 10 20/2) ==> T
(= 1 1.0 #c(1.0 0.0) #c(1 0)) ==> T

В противоположность этому, функция /= вернёт истинное значение, если все её аргументы имеют разное значение.

(/= 1 1)        ==> NIL
(/= 1 2) ==> T
(/= 1 2 3) ==> T
(/= 1 2 3 1) ==> NIL
(/= 1 2 3 1.0) ==> NIL

Функции <, >, <= и >= сравнивают порядок рациональных чисел и чисел с плавающей точкой (другими словами, вещественных чисел). Подобно = и /=, эти функции могут быть вызваны с более чем двумя аргументами, и в этом случае каждый аргумент сравнивается с аргументом, находящимся справа от него.

(< 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

Для выбора наименьшего или наибольшего числа из нескольких, вы можете использовать функцию MIN или MAX, которые принимают любое число вещественных аргументов, и возвращают минимальное или максимальное значение.

(max 10 11)    ==> 11
(min -12 -10) ==> -12
(max -1 2 -3) ==> 2

Другими полезными функциями являются ZEROP, MINUSP и PLUSP, которые проверяют, является ли одиночное вещественное число равным, меньшим или большим чем ноль. Два других предиката, EVENP и ODDP, проверяют является ли число чётным или нечётным. Суффикс P в именах этих функций соответствует стандарту наименования предикатных функций, которые проверяют некоторое условие и возвращают логическое значение.

Высшая математика

Функции которые вы уже увидели являются началом списка встроенных математических функций. Lisp также поддерживает: логарифмы (LOG); экспоненты (EXP и EXPT); основные тригонометрические функции (SIN, COS и TAN) и их противоположности (ASIN, ACOS и ATAN); гиперболические функции (SINH, COSH и TANH) и их противоположности (ASINH, ACOSH и ATANH). Также имеются функции для доступа к отдельным битам целых чисел и для извлечения частей комплексных чисел. Для получения полного списка функций смотрите любой справочник по Common Lisp.

Знаки (Characters)

В Common Lisp знаки являются отдельным типом объектов, а не числами. Это то, что и должно быть – знаки не являются числами, и языки, которые рассматривают их как числа, столкнутся с проблемами, когда изменится кодировка знаков, например, с 8-битного ASCII на 21-битный Unicode.11) Поскольку стандарт Common Lisp не требует конкретного представления для знаков, некоторые из существующих реализаций Lisp используют Unicode как "родную" кодировку знаков, несмотря на то, что Unicode только задумывался в то время, когда проводилась стандартизация Common Lisp.

Синтаксис чтения для объектов-знаков очень простой: префикс #\ за которым следует нужный знак. Так что, #\x обозначает знак x. После #\ может использоваться любой знак, включая специальные знаки, такие как ", ( и пробел. Однако, поскольку запись пробелов и аналогичных знаков не особенно хорошо выглядит (для человека), то для некоторых знаков существует альтернативный синтаксис, состоящий из #\, за которым следует название знака. Список поддерживаемых имен зависит от набора знаков и реализации Lisp, но все реализации поддерживают имена Space и Newline. Так что вы должны писать #\Space вместо #\ , хотя последний вариант и допустим с технической точки зрения. Другими полу-стандартными именами (которые реализации должны использовать, если набор знаков содержит соответствующие знаки) являются Tab, Page, Rubout, Linefeed, Return и Backspace.

Сравнение знаков

Основной операцией которую вы можете выполнить со знаками, отличной от помещения их в строки (что будет описано дальше в этой главе), является их сравнение с другими знаками. Поскольку знаки не являются числами, то вы не можете использовать такие функции как < и >. Вместо этого два набора функций обеспечивают операции над знаками, аналогичные операциям сравнения чисел; одни функции учитывают регистр знаков, а другие – нет.

Чувствительным к регистру аналогом численной функции = является функция CHAR=. Также как и =, CHAR= может получать любое количество аргументов, и возвращает истину, если они все являются одним и тем же знаком. Нечувствительная к регистру функция называется CHAR-EQUAL.

Остальные функции сравнения знаков, следуют той же схеме наименования: чувствительные к регистру функции именуются путём добавления CHAR к имени аналогичной функции для сравнения чисел; а нечувствительные к регистру функции добавляют к CHAR название операции, отделенное знаком минус. Однако заметьте, что <= и >= "именуются" логическими эквивалентами операций NOT-GREATERP и NOT-LESSP, а не более подробными названиями вида LESSP-OR-EQUALP и GREATERP-OR-EQUALP. Также как и их численные аналоги, все эти функции могут принимать один или больше аргументов. Таблица 10-1 показывает отношение между функциями сравнения для чисел и знаков.

Таблица 10-1. Функции сравнения знаков

Numeric Analog Case-Sensitive Case-Insensitive
= CHAR= CHAR-EQUAL
/= CHAR/= CHAR-NOT-EQUAL
< CHAR< CHAR-LESSP
> CHAR> CHAR-GREATERP
<= CHAR<= CHAR-NOT-GREATERP
>= CHAR>= CHAR-NOT-LESSP

Другие функции работающие со знаками, среди прочего предоставляют операции по проверке является ли знак буквой или цифрой, проверке регистра знака, получению соответствующего знака в другом регистре, а также по преобразованию между численным значением и соответствующим знаком, и наоборот. Для получения полной информации об этих функциях, смотрите справочник по Common Lisp.

Строки

Как упоминалось ранее, строки в Common Lisp на самом деле являются составными типами данных, а именно – одномерными массивами знаков. Соответственно, я опишу много вещей, которые можно сделать со строками, когда в следующей главе я буду описывать функции для работы с последовательностями, подтипом которых и являются строки. Но строки также имеют свой собственный синтаксис и библиотеку функций для выполнения операций, специфичных для строк. Я буду обсуждать данные особенности в этой главе, а всё остальное будет обсуждаться в главе 11.

Как вы уже видели, строки записываются заключенными в двойные кавычки. Вы можете включить в строку любой знак, поддерживаемый набором знаков, за исключением двойной кавычки (") и обратного слэша (\). Вы можете включить и эти два знака, если вы замаскируете их с помощью обратного слэша. В действительности, знак обратный слэш всегда маскирует следующий знак, независимо от того, чем он является, хотя нет необходимости использовать его для чего-то отличного от " и самого себя. Таблица 10-2 показывает как различные записи строк будут считываться процедурой чтения Lisp.

Таблица 10-2. Представление строк

Literal Contents Comment
"foobar" foobar Обычная строка.
"foo\"bar" foo"bar Обратный слэш маскирует кавычку.
"foo\\bar" foo\bar Первый обратный слэш маскирует второй.
"\"foobar\"" "foobar" Обратные слэши маскируют кавычки.
"foo\bar" foobar Обратный слэш "маскирует" знак b

Заметьте, что REPL обычно печатает строки в форме, пригодной для считывания, добавляя охватывающие знаки кавычек, и нужные маскирующие знаки. Так что если вы хотите видеть содержимое строки, то вы должны использовать какую-либо функцию, такую как FORMAT, спроектированную для печати данных в форме, удобной для человека. Например, вот что вы увидите, если напечатаете строку, содержащую знак кавычки, в REPL:

CL-USER> "foo\"bar"
"foo\"bar"

С другой стороны, FORMAT выведет содержимое строки:12)

CL-USER> (format t "foo\"bar")
foo"bar
NIL

Сравнение строк

Вы можете сравнивать строки используя набор функций, который использует то же самое соглашение о наименовании, что и функции для сравнения знаков, с той разницей, что они используют в качестве префикса слово STRING вместо CHAR (смотрите таблицу 10-3).

Таблица 10-3. Функции сравнения строк

Numeric Analog Case-Sensitive Case-Insensitive
= STRING= STRING-EQUAL
/= STRING/= STRING-NOT-EQUAL
< STRING< STRING-LESSP
> STRING> STRING-GREATERP
<= STRING<= STRING-NOT-GREATERP
>= STRING>= STRING-NOT-LESSP

Однако, в отличие от сравнения знаков и чисел, функции сравнения строк могут сравнивать только две строки. Это происходит потому, что эти функции также получают именованные аргументы, которые позволяют вам ограничить области сравнения одной или обоих строк. Аргументы: :start1, :end1, :start2 и :end2 указывают начальный (включая) и конечный (не включая) индексы подстрок в первой и второй строке. Таким образом, следующий код:

(string= "foobarbaz" "quuxbarfoo" :start1 3 :end1 6 :start2 4 :end2 7)

сравнивает подстроки "bar" в двух аргументах, и возвращает истинное значение.13) Аргументы :end1 и :end2 могут иметь значение NIL (или именованные аргументы могут быть полностью убраны) для указания, что соответствующие подстроки берутся до конца строк.

Функции сравнения строк, которые возвращают истинное значение, когда их аргументы отличаются друг от друга (это все функции за исключением STRING= и STRING-EQUAL), возвращают индекс позиции в первой строке, где строки начинают отличаться.

(string/= "lisp" "lissome") ==> 3

Если первая строка является префиксом второй строки, то возвращаемое значение будет равно длине первой строки, что на единицу больше чем самый большой допустимый индекс в этой строке.

(string< "lisp" "lisper") ==> 4

При сравнении подстрок, результатом всё равно будет являться индекс в полной строке. Например, следующий код сравнивает подстроки "bar" и "baz", но возвращает 5, поскольку это индекс знака r в первой строке:

(string< "foobar" "abaz" :start1 3 :start2 1) ==> 5   ; N.B. not 2

Другие строковые функции позволяют вам преобразовывать регистр знаков в строке, а также удалять (trim) знаки с одного, или обоих концов строки. И, как я уже отмечал перед этим, поскольку строки на самом деле являются последовательностями, все функции работы с последовательностями, которые я буду описывать в следующей главе, могут быть использованы для работы со строками. Например, вы можете узнать длину строки с помощью функции LENGTH и можете получить отдельный знак строки с помощью функции доступа к элементу последовательности – ELT, или функции доступа к элементу массива – AREF. Или вы можете использовать функцию доступа к элементу строки – CHAR. Но эти функции являются предметом обсуждения следующей главы, так что перейдём к ней.

1)более уместно было бы использовать тут слово "символ", но это слово уже занято для перевода термина "symbol" – прим. переводчика
2)Цитата из Mattel's Teen Talk Barbie
3)Очевидно, что размер числа, который может быть представлен компьютером с конечным объёмом памяти, всё также ограничен; кроме того, представление чисел, используемое в конкретной реализации Common Lisp может налагать другие ограничения на размер представляемого числа. Но эти пределы находятся далеко за границами "астрономически" больших чисел. Например, число атомов во Вселенной оценивается немногим меньше чем 2^269; текущие реализации Common Lisp легко обрабатывают числа, большие чем 2^262144.
4)Те, кто интересуются использованием Common Lisp для интенсивных вычислений, могут заметить, что наивное сравнение производительности кода на Common Lisp, и языках, таких как C или FORTRAN, вероятно покажет, что код на Common Lisp значительно медленней. Это происходит потому, что такие выражения как (+ a b) на Common Lisp выполняют больше, чем эквивалент a + b на любом из этих языков. Поскольку Lisp использует динамическую типизацию, а также обеспечивает поддержку чисел произвольного размера и комплексные числа, просто выглядящаая операция сложения делает больше вещей, чем сложение двух чисел, представленных как машинные слова. Однако вы можете использовать объявления типов, чтобы дать Common Lisp информацию о типах чисел, которые вы используете, что позволит сгенерировать код, который будет работать также как и код, сгенерированный компилятором C или FORTRAN. Описание тонкой настройки кода, работающего с числами, для улучшения производительности, не является темой данной книги, но это возможно сделать.
5)Поскольку стандарт этого не требует, многие реализации Common Lisp поддерживают стандарт IEEE на работу с числами с плавающей точкой – IEEE Standard for Binary Floating-Point Arithmetic, ANSI/ IEEE Std 754-1985 (Institute of Electrical and Electronics Engineers, 1985).
6)Кроме того, возможно изменить основание исчисления, которое по умолчанию использует процедура чтения, без задания маркеров у самих чисел. Это делается путём изменения глобальной переменной *READ-BASE*. Однако, это не приводит ни к чему хорошему, кроме как полному непониманию.
7)Поскольку, главной целью использования чисел с плавающей точкой, является эффективное использование возможностей аппаратуры, то каждая реализация Lisp может отображать эти четыре подтипа в соответствующие аппаратные типы чисел с плавающей точкой. Если аппаратура поддерживает меньше чем четыре типа, то один или несколько типов могут быть эквивалентными.
8)"Компьютеризированная научная нотация" – это отпугивающая цитата, поскольку, хотя она часто используется в компьютерных языках со времён FORTRAN, она отличается от настоящей научной нотации. В частности, 1.0e4 означает 10000.0, но в настоящей научной нотации, оно должно быть записано как 1.0 x 10^4. И в дополнение к этому, в настоящей научной записи, буква e означает основание натурального логарифма, так что запись вида 1.0 x e^4, хоть и похожа на 1.0e4, но является совершенно другим значением, равным примерно 54.6.
9)Для полной консистентности, + и * могут быть вызваны без аргументов, в таком случае они возвращают соответсвующее значение: 0 для + и 1 для *.
10)Грубо говоря, MOD аналогичен операции % в Perl и Python, а REM эквивалентен % в C и Java. (С технической точки зрения, точное поведение % в C не было определено до выхода стандарта C99.)
11)Даже Java, которая с самого начала была спроектирована с учетом использования Unicode, как единственной кодировки для знаков, также столкнулась с трудностями, поскольку знаки в Java определены как 16-битные значения, а стандарт Unicode 3.1 расширил набор знаков Unicode, так что он теперь требует 21-битного представления. Ой!
12)Однако заметьте, что не все строки могут быть напечатаны путём передачи их в качестве второго аргумента функции FORMAT, поскольку некоторые последовательности знаков имеют специальное значение для FORMAT. Чтобы безопасно вывести содержимое любой строки, например, значение переменной s, с помощью FORMAT, вы должны написать (format t "~a" s).
13)Надеюсь, для вас не является секретом, что индексы строк начинаются с 0, а не 1 – прим.ред.
Предыдущая Оглавление Следующая
@2009-2013 lisper.ru