- •Оглавление
- •Предисловие
- •Предисловие к русскому изданию
- •Глава 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. Анализ
- •Комментарии
- •Алфавитный указатель
12.8. Неизменяемая структура |
217 |
(setf (elt-parent c) p (elt-child p) c)
c))
#1=#S(ELT PARENT #S(ELT PARENT NIL CHILD #1#) CHILD NIL)
Для печати подобных структур необходимо установить *print-circle* в t или же избегать вывода таких объектов.
12.8. Неизменяемая структура
Неизменяемые объекты также являются частью кода, и наша задача не допускать их модификации, потому что в противном случае мы случай но можем создать программу, которая пишет сама себя. Цитируемый список является константой, поэтому следует проявлять аккуратность при работе с отдельными его ячейками. Например, если мы используем следующий предикат для проверки на принадлежность к арифметиче ским операторам,
(defun arith-op (x) (member x ’(+ - * /)))
то в случае истинного значения он будет возвращать часть цитируемого списка. Изменяя возвращаемое значение:
> (nconc (arith-op ’*) ’(as it were)) (* / AS IT WERE)
мы изменяем и сам исходный список внутри arith-op, что приведет к из менению работы этой функции:
> (arith-op ’as) (AS IT WERE)
Возврат константы из функции не всегда является ошибкой. Однако нужно учитывать подобные случаи при принятии решения о безопас ности выполнения деструктивных операций над чем-либо.
Избежать возврата части неизменной структуры в случае arith-op мож но несколькими способами. В общем случае замена цитирования на яв ный вызов функции list решит проблему и приведет к созданию нового списка при каждом вызове:
(defun arith-op (x)
(member x (list ’+ ’- ’* ’/)))
Однако в данном случае вызов list ударит по производительности, по этому здесь лучше вместо member использовать find:
(defun arith-op (x) (find x ’(+ - * /)))
Проблема, рассмотренная в этом разделе, чаще всего возникает при ра боте со списками, однако она актуальна и для других типов данных:
218 |
Глава 12. Структура |
массивов, строк, структур, экземпляров и т. д. Не следует модифициро вать что-либо, заданное в тексте программы буквально.
Даже если вы собираетесь написать самомодифицируемую программу, изменение структур-констант – это неправильный выбор. Компилятор может связывать константы с кодом, а деструктивные операторы мо гут изменять свои аргументы, но ни то, ни другое не гарантируется. Писать самомодифицируемые программы вы можете, например, с по мощью замыканий (см. раздел 6.5).
Итоги главы
1.Два списка могут разделять общий хвост. Списки могут разделять структуры как деревья, без разделения структуры верхнего уровня. Разделения структур можно избежать с помощью копирования.
2.Разделяемость структуры не влияет на поведение функций, но о ней нельзя забывать, если вы собираетесь модифицировать списки. Если два списка разделяют общую структуру, то изменение одного из них может привести к изменению другого.
3.Очереди могут быть представлены как cons-ячейки, car которых ука зывает на первую ячейку списка, а cdr – на последнюю.
4.Из соображений производительности деструктивным операторам раз решается модифицировать свои аргументы.
5.В некоторых приложениях использование деструктивных операто ров более естественно.
6.Списки могут быть циклическими по голове и хвосту. Лисп умеет работать с циклическими и разделяемыми структурами.
7.Не следует изменять константы, встречающиеся в тексте программы.
Упражнения
1.Нарисуйте три различных дерева, которые будут выводиться как ((A) (A) (A)). Напишите выражения, генерирующие каждое из них.
2.Считая уже определеннымиmake-queue, enqueue и dequeue (см. рис. 12.7), нарисуйте представление очереди в виде ячеек после каждого сле дующего шага:
>(setf q (make-queue)) (NIL)
>(enqueue ’a q)
(A)
>(enqueu ’b q) (A B)
>(dequeue q)
A
Упражнения |
219 |
3.Определите функцию copy-queue, возвращающую копию очереди.
4.Определите функцию, принимающую в качестве аргументов объект
иочередь и помещающую этот объект в начало очереди.
5.Определите функцию, принимающую в качестве аргументов объект
иочередь и (деструктивно) перемещающую первый найденный (eql) экземпляр этого объекта в начало очереди.
6.Определите функцию, принимающую объект и циклический спи сок, который может быть циклическим по хвосту, и проверяющую наличие заданного объекта в списке.
7.Определите функцию, проверяющую, является ли ее аргумент цик лическим по хвосту списком.
8.Определите функцию, проверяющую, является ли ее аргумент цик лическим по голове списком.