- •Оглавление
- •Предисловие
- •Предисловие к русскому изданию
- •Глава 1. Введение
- •1.1. Новые инструменты
- •1.2. Новые приемы
- •1.3. Новый подход
- •Глава 2. Добро пожаловать в Лисп
- •2.1. Форма
- •2.2. Вычисление
- •2.3. Данные
- •2.4. Операции со списками
- •2.5. Истинность
- •2.6. Функции
- •2.7. Рекурсия
- •2.8. Чтение Лиспа
- •2.9. Ввод и вывод
- •2.10. Переменные
- •2.11. Присваивание
- •2.12. Функциональное программирование
- •2.13. Итерация
- •2.14. Функции как объекты
- •2.15. Типы
- •2.16. Заглядывая вперед
- •Итоги главы
- •Упражнения
- •Глава 3. Списки
- •3.1. Ячейки
- •3.2. Равенство
- •3.3. Почему в Лиспе нет указателей
- •3.4. Построение списков
- •3.5. Пример: сжатие
- •3.6. Доступ
- •3.7. Отображающие функции
- •3.8. Деревья
- •3.9. Чтобы понять рекурсию, нужно понять рекурсию
- •3.10. Множества
- •3.11. Последовательности
- •3.12. Стопка
- •3.13. Точечные пары
- •3.14. Ассоциативные списки
- •3.15. Пример: поиск кратчайшего пути
- •3.16. Мусор
- •Итоги главы
- •Упражнения
- •Глава 4. Специализированные структуры данных
- •4.1. Массивы
- •4.2. Пример: бинарный поиск
- •4.3. Строки и знаки
- •4.4. Последовательности
- •4.5. Пример: разбор дат
- •4.6. Структуры
- •4.7. Пример: двоичные деревья поиска
- •4.8. Хеш-таблицы
- •Итоги главы
- •Упражнения
- •Глава 5. Управление
- •5.1. Блоки
- •5.2. Контекст
- •5.3. Условные выражения
- •5.4. Итерации
- •5.5. Множественные значения
- •5.6. Прерывание выполнения
- •5.7. Пример: арифметика над датами
- •Итоги главы
- •Упражнения
- •Глава 6. Функции
- •6.1. Глобальные функции
- •6.2. Локальные функции
- •6.3. Списки параметров
- •6.4. Пример: утилиты
- •6.5. Замыкания
- •6.6. Пример: строители функций
- •6.7. Динамический диапазон
- •6.8. Компиляция
- •6.9. Использование рекурсии
- •Итоги главы
- •Упражнения
- •Глава 7. Ввод и вывод
- •7.1. Потоки
- •7.2. Ввод
- •7.3. Вывод
- •7.4. Пример: замена строк
- •7.5. Макрознаки
- •Итоги главы
- •Упражнения
- •Глава 8. Символы
- •8.1. Имена символов
- •8.2. Списки свойств
- •8.3. А символы-то не маленькие
- •8.4. Создание символов
- •8.5. Использование нескольких пакетов
- •8.6. Ключевые слова
- •8.7. Символы и переменные
- •8.8. Пример: генерация случайного текста
- •Итоги главы
- •Упражнения
- •Глава 9. Числа
- •9.1. Типы
- •9.2. Преобразование и извлечение
- •9.3. Сравнение
- •9.4. Арифметика
- •9.5. Возведение в степень
- •9.6. Тригонометрические функции
- •9.7. Представление
- •9.8. Пример: трассировка лучей
- •Итоги главы
- •Упражнения
- •Глава 10. Макросы
- •10.1. Eval
- •10.2. Макросы
- •10.3. Обратная кавычка
- •10.4. Пример: быстрая сортировка
- •10.5. Проектирование макросов
- •10.6. Обобщенные ссылки
- •10.7. Пример: макросы-утилиты
- •10.8. На Лиспе
- •Итоги главы
- •Упражнения
- •Глава 11. CLOS
- •11.1. Объектно-ориентированное программирование
- •11.2. Классы и экземпляры
- •11.3. Свойства слотов
- •11.4. Суперклассы
- •11.5. Предшествование
- •11.6. Обобщенные функции
- •11.7. Вспомогательные методы
- •11.8. Комбинация методов
- •11.9. Инкапсуляция
- •11.10. Две модели
- •Итоги главы
- •Упражнения
- •Глава 12. Структура
- •12.1. Разделяемая структура
- •12.2. Модификация
- •12.3. Пример: очереди
- •12.4. Деструктивные функции
- •12.5. Пример: двоичные деревья поиска
- •12.6. Пример: двусвязные списки
- •12.7. Циклическая структура
- •12.8. Неизменяемая структура
- •Итоги главы
- •Упражнения
- •Глава 13. Скорость
- •13.1. Правило бутылочного горлышка
- •13.2. Компиляция
- •13.3. Декларации типов
- •13.4. Обходимся без мусора
- •13.5. Пример: заранее выделенные наборы
- •13.6. Быстрые операторы
- •13.7. Две фазы разработки
- •Итоги главы
- •Упражнения
- •Глава 14. Более сложные вопросы
- •14.1. Спецификаторы типов
- •14.2. Бинарные потоки
- •14.3. Макросы чтения
- •14.4. Пакеты
- •14.5. Loop
- •14.6. Особые условия
- •Глава 15. Пример: логический вывод
- •15.1. Цель
- •15.2. Сопоставление
- •15.3. Отвечая на запросы
- •15.4. Анализ
- •Глава 16. Пример: генерация HTML
- •16.1. HTML
- •16.2. Утилиты HTML
- •16.3. Утилита для итерации
- •16.4. Генерация страниц
- •Глава 17. Пример: объекты
- •17.1. Наследование
- •17.2. Множественное наследование
- •17.3. Определение объектов
- •17.4. Функциональный синтаксис
- •17.5. Определение методов
- •17.6. Экземпляры
- •17.7. Новая реализация
- •17.8. Анализ
- •Комментарии
- •Алфавитный указатель
7
Ввод и вывод
Common Lisp имеет богатые возможности для осуществления ввода-вы вода. Для ввода, наряду с обычными инструментами для чтения симво лов, у нас есть read, который включает в себя полноценный парсер. Для вывода, вместе с привычными средствами для записи символов, мы по лучаем format, который сам по себе является небольшим языком. В этой главе вводятся все основные понятия ввода-вывода.
Существует два вида потоков: потоки знаков и бинарные потоки. В этой главе рассмотрены лишь потоки знаков; бинарные потоки будут описа ны в разделе 14.2.
7.1. Потоки
Потоки – это объекты Лиспа, представляющие собой источники и/или приемники для передачи символов. Чтобы прочитать или записать файл, необходимо открыть соответствующий поток. Однако поток – это не то же самое, что файл. Когда вы читаете или печатаете что-либо в toplevel, вы также используете поток. Создавая потоки, вы даже можете читать из строк или писать в них.
По умолчанию для ввода используется поток *standard-input*, для выво да – *standard-output*. Первоначально они, как правило, ссылаются на один и тот же поток, соответствующий toplevel.
С функциями read и format мы уже знакомы. Ранее мы пользовались ими для чтения и печати в toplevel. Функция read имеет необязательный ар гумент, который определяет входной поток и по умолчанию установлен в *standard-input*. Первый аргумент функции format также может быть потоком. Ранее мы вызывали format с первым аргументом t, который со ответствует потоку *standard-output*. Таким образом, до этого момента мы наблюдали лишь поведение этих функций при использовании аргу
7.1. Потоки |
131 |
ментов по умолчанию. Однако те же операции мы можем совершать и с любыми другими потоками.
Путь (pathname) – это переносимый способ определения пути к файлу. Путь имеет шесть компонентов: хост, устройство, каталог, имя, тип и версию. Вы можете создать путь с помощью make-pathname с одним или более аргументами по ключу. В простейшем случае достаточно задать лишь имя, оставив остальные параметры со значениями по умолчанию:
> (setf path (make-pathname :name "myfile")) #P"myfile"
Базовая функция для открытия файлов – open. Ей необходимо сообщить путь1, а ее поведением можно управлять с помощью многочисленных ар гументов по ключу. В случае успеха она возвращает поток, указываю щий на файл.
Вам нужно указать, как вы собираетесь использовать создаваемый по ток. Параметр :direction определяет, будет ли производиться чтение (:input), запись (:output) или и то и другое одновременно (:io). Если поток используется для чтения, то параметр :if-exists определяет его поведе ние в случае, если файл уже существует. При этом обычно используется :supersede (перезаписать поверх) . Итак, создадим поток, через который мы сможем записать что-либо в файл "myfile":
> (setf str (open path :direction :output :if-exists :supersede))
#<Stream C017E6>
Способ отображения потоков при печати зависит от используемой реа лизации Common Lisp.
Если теперь мы укажем поток str первым аргументом функции format, она будет печатать свой вывод в этот поток, а не в toplevel:
> (format str "Something~%") NIL
Но если мы теперь посмотрим на содержимое файла, то можем и не най ти в нем нашу строку. Некоторые реализации осуществляют вывод пор циями, и ожидаемое содержимое файла может появиться лишь после закрытия потока:
> (close str) NIL
Всегда закрывайте файлы по окончании их использования. Пока вы не сделаете этого, вы не можете быть уверены относительно их содержи мого. Если мы теперь взглянем на содержимое файла "myfile", то обна ружим строчку:
Something
1Вместо пути можно передать просто строку, однако такой способ не перено сим между различными ОС.
132 |
Глава 7. Ввод и вывод |
Если мы хотим всего лишь прочитать содержимое файла, то откроем поток с аргументом :direction :input:
> (setf str (open path :direction :input)) #<Stream C01C86>
Содержимое файла можно читать с помощью любой функции чтения. Более подробно ввод описывается в разделе 7.2. Приведем пример, в ко тором для чтения строки из файла используется функция read-line:
>(read-line str) "Something"
NIL
>(close str)
NIL
Не забудьте закрыть файл после завершения чтения.
Функции open и close практически никогда не используются явно. Гораз до удобнее использовать макрос with-open-file. Первый его аргумент – список, содержащий некоторое имя переменной и все те же аргументы, которые вы могли бы передать функции open. Далее следуют выраже ния, которые могут использовать заданную выше переменную, связан ную внутри тела макроса с именем переменной. После выполнения всех выражений тела макроса поток закрывается автоматически. Таким об разом, операция записи в файл может быть целиком выражена так:
(with-open-file (str path :direction :output :if-exists :supersede)
(format str "Something~%"))
Макрос with-open-file использует unwind-protect (стр. 295), чтобы гаран тировать, что файл будет действительно закрыт, даже если выполнение выражений внутри тела макроса будет аварийно прервано.
7.2. Ввод
Двумя наиболее популярными функциями чтения являются read-line и read. Первая читает все знаки до начала новой строки, возвращая их в виде строки. Один ее необязательный параметр, как и для read, опре деляет входной поток. Если он не задан явно, то по умолчанию исполь зуется *standard-input*:
> (progn
(format t "Please enter your name: ") (read-line))
Please enter your name: Rodrigo de Bivar "Rodrigo de Bivar"
NIL
Эту функцию стоит использовать, если вы хотите получить значения такими, какими они были введены. (Вторым возвращаемым значением
7.2. Ввод |
133 |
будет t, если read-line закончит чтение файла прежде, чем встретит ко нец строки.)
В общем случае read-line принимает четыре необязательных аргумен та: поток; параметр, уведомляющий, вызывать ли ошибку по достиже нии конца файла (eof); параметр, обозначающий, что возвращать, если не вызывать ошибку. Четвертый аргумент (стр. 242) обычно можно про игнорировать.
Итак, отобразить содержимое файла в toplevel можно с помощью сле дующей функции:
(defun pseudo-cat (file)
(with-open-file (str file :direction :input) (do ((line (read-line str nil ’eof)
(read-line str nil ’eof))) ((eql line ’eof))
(format t "~A~%" line))))
Если вы хотите, чтобы ввод проходил синтаксический разбор как объ екты Лиспа, используйте read. Эта функция считывает ровно одно вы ражение и останавливается в его конце. Это значит, что она может счи тывать больше строки или меньше строки. Разумеется, считываемый объект должен быть синтаксически корректным (valid) с точки зрения синтаксиса Лиспа.
Применяя функцию read в toplevel, мы сможем использовать сколько угодно переносов строк внутри одного выражения:
>(read)
(a b c)
(A B C)
Сдругой стороны, если на одной строке будет находиться несколько объ ектов Лиспа, read прекратит считывание знаков, закончив чтение перво го объекта; оставшиеся знаки будут считываться при следующих вызо вах read. Проверим это предположение с помощью ask-number (стр. 37). Введем одновременно несколько выражений и посмотрим, что проис ходит:
>(ask-number)
Please enter a number. a b
Please enter a number. Please enter a number. 43 43
Два приглашения напечатаны одно за другим на одной строке. Первый вызов read возвращает символ a, не являющийся числом, поэтому функ ция заново предлагает ввести число. Первый вызов считал только a и остановился. Поэтому следующий вызов read сразу считывает объект b, который также не число, и приглашение выводится еще раз.