Добавил:
natribu.org Все что нашел в интернете скидываю сюда Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Точно Не проект 2 / Не books / Источник_1

.pdf
Скачиваний:
10
Добавлен:
01.02.2024
Размер:
20.67 Mб
Скачать

250

Глава 5

 

 

Для проверки типов данных в Лиспе имеется большое число встро-

енных предикатов (ATOM, CONSP, LISTP, NUMBERP, STRINGP, ARRAYP и

др.). Определить принадлежность данных к одному из типов, указанных на рисунке 5.10, можно также с помощью обобщенного предиката проверки типа:

(typep объект тип-данного)

Например,

(typep ’(a b c) ’list) Т

Общий тип

Атомы

Последовательности

Функции Потоки Числа Литеры Символы Массивы

Рациональные

Вещественные

Векторы

Списки

Целые Дробные Одинарной Удвоенной Длинные

Строки

Точечные пары

точности

точности

(long)

 

 

(single)

(double)

 

 

 

Фиксированной

Неограниченной

разрядности

разрядности

(fixnum)

(bignum)

Рисунок 5.10 – Классификация основных типов данных языка Лисп

Выяснить тип объекта позволяет функция TYPE-OF, которая возвращает в качестве результата тип своего аргумента:

(type-of ’(a . b)) CONSP

Получить описание объекта можно с помощью функции DESCRIBE:

(setf x ’(a . b))

Язык Лисп

251

 

 

(describe x) (a . b) is a cons

Для преобразования одного типа данных в другой используется обобщенная функция преобразования

(coerce объект тип)

Рассмотрим подробнее некоторые из типов данных, представленных на рисунке 5.10.

Символы (symbol). Ранее уже отмечалось, что с символом может быть связано значение, список свойств и определение функции. В общем случае символ представляет структурную переменную, состоящую из пяти элементов (рисунок 5.11):

-имени символа (отображается в виде строки);

-указателя значения символа;

-указателя свойств;

-указателя определения функции;

-указателя пакета (пространства имен, к которому принадлежит

символ).

Для доступа к каждому из указанных элементов используются сле-

дующие функции:

-к имени символа – (symbol-name символ);

-к значению символа – сам символ или (symbol-value символ);

-к определению функции – (symbol-function символ);

-к списку свойств – (symbol-plist символ) и (get символ имя_свойства);

-к пакету – (symbol-package символ).

Изменение составных элементов символа выполняется с помощью следующих средств:

-имя символа – (setf (symbol-name символ) значение);

-значение символа – (setq символ значение), (setf символ значение).

-определение функции – (setf (symbol-function символ) лямбда-

выражение) или (defun …);

-список свойств – (setf (symbol-plist символ) список-свойств), (setf (get символ имя-свойства) значениесвойства).

Если требуется включить в имя символа пробел или скобку или другой специальный знак, то применяют знак отмены “\” (backslash). Например,

(setf \ (а\ b\) 5) 5

Другой способ включения в имя символа указанных знаков состоит в написании имени между двумя вертикальными линиями:

252

Глава 5

 

 

(setf | (a b ) | 5) 5

Пакет

Список

Имя

Определение

свойств

символа

функции

 

 

 

Значение

Рисунок 5.11-Элементы, образующие символ

Принадлежность объекта данных к типу данных symbol может быть установлена предикатом:

(symbolp объект)

Литеры (character). Литеры представляют собой знаки языка и изображаются в виде #\a. Данная запись соответствует литере ‘a’. Значением литеры является сама литера. Чтобы выяснить, относится ли объект данных к литерному типу, применяется предикат

(characterp объект)

Литера может быть преобразована в строку с помощью функции STRING:

(string #\a) ”A”

Строки (string). Строка состоит из последовательности литер (знаков), заключенных в двойные кавычки. Например, “яблоко”. Строка, записанная таким образом, представляет собой константу, и ее значением является сама строка.

Для работы со строками в Лиспе имеется большой набор функций. Конкатенация (объединение) строк выполняется функцией CONCATENATE. Например,

(concatenate ’string ”abc” ”def”) ”ABCDEF”

Выделение из строки заданной литеры реализуется функцией CHAR:

(char ”abcde” 3) #\d (char ”abc d” 3) #\SPACE

Язык Лисп

253

 

 

Здесь #\SPACE представляет неотображаемую литеру “пробел”. Имеются и другие, неотображаемые литеры, например: #\RETURN – перевод строки, #\TAB – табуляция.

Изменить произвольный элемент строки можно с помощью формы SETF:

(setf (char ”торт” 0) #\п) ”ПОРТ”

Строки можно сравнивать с помощью функций STRING=, STRING>, STRING<. Сравнение строк выполняется в лексикографическом порядке.

Возможны преобразования символа в строку

(string ’symbol) ”SYMBOL”,

а также двусторонние преобразования “строка – список литер ”:

(coerce ”SYMBOL” ’list) (#\S #\Y #\M #\B #\O #\L) (coerce ’(#\A #\T #\O #\M) ’string) ”ATOM”

Чтобы выяснить, относится ли объект к строковому типу, применяется предикат

(stringp объект)

Для создания символа из строки и внесения его в пространство имен применяется функция INTERN:

(intern строка)

Векторы (vector). Векторы в Коммон Лиспе представляют одномерные массивы. Элементами векторов могут быть числа, символы, списки или другие векторы. N – элементный вектор отображается в виде последовательности элементов, заключенной в круглые скобки, перед которыми стоит знак #:

#( x1 x2 … xn)

Создание векторов выполняется с помощью функций VECTOR или MAKEARRAY. Например,

(vector ’a ’b ’c ’d) # (A B C D)

(make-array ’(4)) # (NIL NIL NIL NIL) (make-array ’(3) :initial-element ’b) #(B B B)

(make-array ’(4) :initial-contents ’(a b c d)) #(A B C D)

Указанные функции обычно применяют в комбинации с формой SETF:

(setf v (vector ’a ’b ’c ’d)) #(A B C D) v #(A B C D)

(setf p (make-array ’(4) :initial-contents ’(1 2 3 4)) #(1 2 3 4) p #(1 2 3 4)

254

Глава 5

 

 

Ключевые параметры :initial-element и :initial-contents обеспечивают соответствующие начальные значения элементам вектора.

Доступ к элементам вектора выполняется в соответствии с позицией, которую занимает элемент в векторе. Номер позиции называют индексом. Нумерация начинается с нуля. Для доступа к элементам вектора применяется функция AREF:

(aref v 2) С

(aref p 0) 1

Изменение значений векторов выполняется с помощью присваивания:

(setf (aref v 2) 5.14) 5.14 v # (A B 5.14 D)

Векторы могут иметь указатели заполнения (fill-pointer). Указатель заполнения позволяет обозначить активные элементы вектора и соответствует наибольшему индексу элемента, который входит в активную часть вектора. Значение указателя заполнения определяется ключевым параметром FILL-POINTER функции MAKE-ARRAY:

(setf stack (make-array ’(9) :fill-pointer 0)) #( ).

Многие функции выполняют действия над векторами с учетом значения указателя заполнения, например, функции VECTOR-PUSH и VECTOR-POP, позволяющие организовать стековый механизм работы с вектором. При добавлении элемента в стек (функция VECTOR-PUSH) он вносится по индексу, соответствующему указателю заполнения, после этого значение указателя заполнения автоматически инкрементируется. В качестве результата функция VECTOR-PUSH возвращает индекс добавленного элемента, а функция VECTOR-POP – значение удаленного элемента. При удалении выполняются обратные операции. Выяснить текущее значение указателя можно с помощью функции FILL-POINTER:

(vector-push ’a stack) 0 (vector-push ’b stack) 1 stack #(A B)

(fill-pointer stack) 2

(vector-pop stack) В

(fill-pointer stack) 1 stack #(А)

Функция VECTOR-PUSH возвращает значение NIL, если указатель заполнения достиг предельного значения, соответствующего размерности вектора. Функция VECTOR-POP генерирует ошибку, если указатель заполнения равен нулю.

Отметим, что строка – частный случай вектора, элементами которого являются литеры. Поэтому к строкам могут применяться функции, определенные над векторами.

Выяснить принадлежность объекта данных к векторному типу можно с помощью предиката:

(vectorp объект)

Язык Лисп

255

 

 

Последовательности (sequence). Последовательности представляют упорядоченные множества элементов данных. В Коммон Лиспе последовательности обобщают понятие векторов и списков. На элементы последовательности можно ссылаться по их позиции в последовательности. Над последовательностями можно выполнять общие действия, как-то:

-определение длины последовательности (LENGTH);

-объединение последовательностей (CONCATENATE);

-обращение последовательности (REVERSE);

-определение позиции элемента в последовательности (POSITION);

-удаление элемента из последовательности (REMOVE);

-замена элементов последовательности (SUBSTITUTE);

-заполнение последовательности (FILL) и др.

Создание последовательности можно выполнить с помощью функции MAKESEQUENCE, имеющей формат:

(make-sequence тип количество &KEY :initial-element)

Обязательные параметры функции MAKE-SEQUENCE определяют тип и количество элементов последовательности. Например,

(setf s (make-sequence ’list ;тип элемента последовательности

5 ;количество элементов

:initial-element ’a)); инициализация

( A A A A A)

Доступ к элементу последовательности выполняется вызовом функции ELT:

(elt последовательность индекс)

Например, (elt s 4) А.

Изменить значение элемента последовательности можно с помощью присваива-

ния:

( setf (elt s 3) ’B) В

S (А А А В А)

Массивы (array). Массивы в языке Лисп могут иметь произвольное количество измерений, в том числе и ноль. Массив нулевой размерности содержит один элемент. Любая версия Коммон Лиспа должна поддерживать, как минимум, работу с семимерными массивами. Создание массива выполняется функцией MAKE-ARRAY:

(setf feld (make-array ’(2 3) :initial-contents ’((a b c)(d e f))))

#2A ((A B C)(D E F))

Для доступа к элементам массива применяется функция AREF:

(aref feld 0 1) В

Изменение значений элементов массивов выполняется присваиванием по указателю, определяемому с помощью вызова функции AREF:

256

Глава 5

 

 

(setf (aref feld 1 2) ‘Z) Z feld #2A((A B C)(D E Z))

Определить размерность массива можно с помощью функций:

(array-dimension feld 1) 3

(array dimensions feld) (2 3)

Выяснить, принадлежит ли объект данных к типу массив, можно с помощью предиката:

(arrayp объект)

Потоки (stream). В Лиспе операции ввода-вывода выполняются с помощью потоков. Потоки представляют собой хранилища данных, из которых можно читать данные или в которые можно записывать данные. Потоки могут быть связаны с устройствами ввода-вывода, файлами на дисках, строками в памяти. Поток характеризуется направлением (direction) и типом элементов, которые берутся из потока или направляются в поток.

Поток в Лиспе связывается с динамической переменной, значения которой и представляют поток. Имеется несколько стандартных потоков: *STANDARD-INPUT*, *STANDARD-OUTPUT*, *ERROR-OUTPUT*, *TRACE-OUTPUT* и др. Эти динами-

ческие переменные определяют для функций ввода (READ) и вывода (PRINT) файлы ввода-вывода. В начале сеанса работы с Лисп системой эти файлы связаны с терминалом.

Если требуется выполнять ввод-вывод из других файлов, то их необходимо открыть. Для этого применяется функция OPEN, упрощенный формат которой можно представить в форме

(open имя-файла &KEY

 

: direction значение

 

: element-type значение

 

: if-exists значение

 

: if-does-not-exist значение)

Ключевые параметры имеют следующие значения:

direction

– направление передачи данных в потоке; возможные значения: 1)

 

:input – ввод; 2) :output – вывод; 3) :io – двунаправленный поток и

element-type

др.;

– тип элементов, передаваемых в потоке (по умолчанию

if-exists

character);

– выполняемые операции, если заданный файл уже существует;

 

возможные значения: 1) :error - ошибка; 2) :override – перезапи-

 

сать; 3) :append – добавить новые записи в конец; 4) :rename

if-does-not-exist

переименовать и др.;

– выполнение операций, если заданный файл отсутствует; возмож-

 

ные значения: 1) :error – ошибка (для файлов открытых в режиме

 

чтения); 2) :create – создать.

Пример вызова функции OPEN:

Язык Лисп

257

 

 

(setf vyvod (open ”extern.dat” :direction :output :if-exists :append

:if-does-not-exist :create))

Здесь функция OPEN направляет выходной поток в файл ”extern.dat”, а функция SETF связывает с открытым потоком символ VYVOD, который в дальнейшем используется в качестве имени потока.

Связи стандартных потоков могут быть переопределены с помощью присваивания. Например,

(setf *standard-output* vyvod)

После завершения работы с потоком его необходимо закрыть

(close vyvod)

Выяснить, является ли объект данных потоком, можно с помощью предиката

(streamp объект)

5.16. Ввод-вывод

При работе с интерпретатором Лиспа ввод-вывод выполняется автоматически. Интерпретатор читает вводимое пользователем s-выражение (READ), вычисляет его (EVAL) и выводит результаты (PRINT). В простейшем случае функция READ не требует аргументов. После вызова этой функции происходит обращение к стандартному входному потоку и считывание введенного пользователем s-выражения. Введенное выражение возвращается в точку вызова READ.

Определим функцию, которая находит сумму четырех введенных с клавиатуры чисел:

(defun sum4 ( )

(+ (read) (read) (read) (read))

Для вызова этой функции необходимо напечатать (sum4), а затем ввести четыре числа:

> (sum4)

1 2 3 4

10

>

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

(setf vvod (open ”extern.dat” :direction :input))

258

Глава 5

 

 

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

(read vvod)

В этом случае произойдет обращение к файлу extern.dat”, и из него будет считано одно s-выражение. Результатом вызова функции READ будет s-выражение, введенное из файла.

Для вывода значений в Лиспе используется функция PRINT. Эта функция имеет один аргумент, значение которого выводится в выходной поток. Функция PRINT перед выводом значения осуществляет переход на новую строку, a после печати значения выводит пробел

(print ’(a b c)) (a b c)

(А В С)

Здесь список (А В С) дважды отображается на экране. Дополнительный автоматический вызов функции PRINT происходит в цикле READ-EVAL-PRINT интерпретатора.

Для вывода значений могут также использоваться функции PRIN1 и PRINC. Функция PRIN1 аналогична PRINT, но не выполняет переход на новую строку и не выводит пробел. Функция PRINC дает возможность напечатать строку без ограничивающих кавычек:

(princ ”lisp”) lisp

Для перехода при выводе значений на новую строку можно использовать функцию TERPRI. У функции TERPRI нет аргументов. Если требуется вывести на печать имя символа, содержащее пробелы, скобки или строчные и заглавные буквы, то применяют знак отмены “\” или ограничивающие вертикальные линии “|”. Все рассмотренные функции вывода позволяют осуществлять вывод в выходной поток открытый, с помощью функции OPEN:

(setf stran (open ”extern.dat”

:direction :output :if-exists :append))

(print ’|a b c d| stran) |a b c d|

(print ’\a\C \ \d stran) |aC

d|

(princ ’|a b c d| stran) |a b c d|

Здесь вторым аргументом функции является выходной поток STRAN, связанный с файлом EXTERN.DAT.

При работе с файлами удобно использовать макроформу WITH-OPEN- FILE. Она позволяет открыть поток, задать режим работы с ним, выполнить необходимую его обработку и закрыть его:

Язык Лисп

259

 

 

(with-open-file (поток имя-файла {опция}*) {декларация}* {форма}*)

Вызов WITH-OPEN-FILE базируется на использовании функции OPEN. Режимы открытия потока задаются с помощью аргумента опция. Они полностью соответствуют ключевым параметрам, указываемым при вызове функции OPEN. Формы вызова WITH-OPEN-FILE вычисляются последовательно. В качестве результата возвращается значение последней формы. В приведенном ниже примере открывается поток с именем STORE, который связывается с файлом TEST1.LSP. В файл циклически записываются s- выражения, вводимые с клавиатуры. Формирование файла завершается, если с клавиатуры вводится атом EOF:

(defun writer ( )

 

 

(with-open-file (store ”test1.lsp”

: direction

:output

 

: if-exists

: append

: if-does-not-exist :create ) (prinс ”Введите s-выражение”)

(terpri)

(princ ”Для завершения ввода напечатайте EOF”)

(terpri)

(do ((item (read) (read))) ;чтение s-выражения ((eq item ’eof ) ’end-of-session )

(print item store ) )))

Для управления формой вывода в Коммон Лиспе применяется функ-

ция FORMAT:

(format поток шаблон &REST аргументы )

Если поток задан символом Т, то функция осуществляет вывод на экран. Шаблон представляет собой строку, которая может содержать коды для управления печатью. Перед управляющими кодами записывается знак “~” (тильда). Если в шаблоне нет управляющих кодов, то функция FORMAT выводит строку шаблона так же, как и функция PRINC. Некоторые управляющие коды и их назначения приведены в таблице 5.2.

Если в строке шаблона встречается символ перехода на новую строку, то при печати будет выполняться переход на новую строку. Приведем пример:

(format t ”Смотри: ~S! ~% А сейчас по-английски: ~R dog ~:P ” ’| A B | 5 )

Смотри: | A B | !

Асейчас по-английски: five dogs

Соседние файлы в папке Не books