- •Оглавление
- •Предисловие
- •Предисловие к русскому изданию
- •Глава 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
Структура
В разделе 3.3 было объяснено, каким образом использование указателей позволяет поместить любое значение куда угодно. Это утверждение включает массу возможностей, но не все они могут принести пользу. На пример, объект может быть элементом самого себя. Хорошо это или пло хо, зависит от того, делается это намеренно или происходит случайно.
12.1. Разделяемая структура
Списки могут совместно использовать одни и те же ячейки. В простей шем случае один список может быть частью другого. После выполнения
>(setf part (list ’b ’c)) (B C)
>(setf whole (cons ’a part)) (A B C)
первая ячейка становится частью (а точнее, cdr) второй. В подобных случаях принято говорить, что два списка разделяют одну структуру. Структура, лежащая в основе двух таких списков, представлена на рис. 12.1.
part = |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
whole = |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
nil |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
a |
|
|
|
|
b |
|
|
|
|
c |
|||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Рис. 12.1. Разделяемая структура
12.1. Разделяемая структура |
203 |
Подобные ситуации выявляет предикат tailp. Он принимает два списка и возвращает истину, если встретит первый список при обходе второго.
> (tailp part whole) T
Мы можем реализовать его самостоятельно:
(defun our-tailp (x y) (or (eql x y)
(and (consp y)
(our-tailp x (cdr y)))))
Согласно этому определению каждый список является хвостом самого себя, а nil является хвостом любого правильного списка.
В более сложном случае два списка могут разделять общую структуру, даже когда один из них не является хвостом другого. Это происходит, когда они делят общий хвост, как показано на рис. 12.2. Создадим по добную ситуацию:
(setf part (list ’b ’c) whole1 (cons 1 part) whole2 (cons 2 part))
Теперь whole1 и whole2 разделяют структуру, но один не является хво стом другого.
part = |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|||
whole1 = |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
whole2 = |
|
|
|
1 |
|
|
|
|
|
|
|
|
|
|
nil |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
b |
|
|
|
|
c |
||||||
|
|
|
|
|
|
|
|
|
|||||||||
|
|
|
|
|
|
|
|
|
|
|
|
||||||
|
|
|
|
|
|
|
|
|
|
|
|
||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Рис. 12.2. Разделяемый хвост
В случае вложенных списков важно отличать списки с разделяемой структурой от их элементов с разделяемой структурой. Структура спи ска верхнего уровня включает ячейки, из которых состоит сам список, но не включает какие-либо ячейки, из которых состоят отдельные эле менты списка. Пример структуры верхнего уровня вложенного списка приведен на рис. 12.3.
Имеют ли две ячейки разделяемую структуру, зависит от того, считаем мы их списками или деревьями. Два вложенных списка могут разде лять одну структуру как деревья, но не разделять ее как списки. Сле
204 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Глава 12. Структура |
дующий код создает ситуацию, изображенную на рис. 12.4, где два спи |
|||||||||||||||||||||||
ска содержат один и тот же элемент-список: |
|
|
|
|
|
|
|||||||||||||||||
|
(setf element |
(list |
’a ’b) |
|
|
|
|
|
|
||||||||||||||
|
holds1 |
(list |
1 element 2) |
|
|
|
|
|
|
||||||||||||||
|
holds2 |
(list |
element 3)) |
|
|
|
|
|
|
||||||||||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
nil |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
a |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
d |
||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
nil |
|
|
||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|||||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bc
Рис. 12.3. Структура списка верхнего уровня
holds1 = |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
nil |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
|
|
|
|
|
|
|
2 |
|
|||||||||||||||
|
|
|||||||||||||||||||||||
element = |
|
|
|
|
|
|
|
|
|
|
nil |
|
||||||||||||
|
|
|
|
|
|
|
|
|
|
|||||||||||||||
|
|
|
|
|
|
|
|
|||||||||||||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
a |
|
|
|
|
b |
||||||||
|
|
|
|
|
||||||||||||||||||||
holds2 = |
|
|
|
|
|
|
|
nil |
|
|||||||||||||||
|
|
|
|
|
||||||||||||||||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3
Рис. 12.4. Разделяемое поддерево
Хотя второй элемент holds1 разделяет структуру с первым элементом holds2 (в действительности, он ему идентичен), holds1 и holds2 не делят между собой общую структуру как списки. Два списка разделяют струк туру как списки, только если они делят общую структуру верхнего уровня, чего не делают holds1 и holds2.
Избежать использования разделяемой структуры можно с помощью ко пирования. Функция copy-list, определяемая как
(defun our-copy-list (lst) (if (null lst)
nil
(cons (car lst) (our-copy-list (cdr lst)))))
12.2. Модификация |
205 |
вернет список, не разделяющий структуру верхнего уровня с исходным списком. Функция copy-tree, которая может быть определена следую щим образом:
(defun our-copy-tree (tr) (if (atom tr)
tr
(cons (our-copy-tree (car tr)) (our-copy-tree (cdr tr)))))
возвратит список, который не разделяет структуру всего дерева с исход ным списком. Рисунок 12.5 демонстрирует разницу между вызовом co py-list и copy-tree для вложенного списка.
|
x |
(copy list x) |
(copy tree x) |
|||||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
nil |
|
|
nil |
|
|
|
|
|
nil |
|
|
|
|
||||||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
nil |
|
|
|
|
|
|
|
|
nil |
|
|
|
|
|
|
|
|||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
a
Рис. 12.5. Два способа копирования
12.2. Модификация
Почему стоит избегать использования разделяемой структуры? До сих пор это явление рассматривалось всего лишь как забавная головолом ка, а в написанных ранее программах мы прекрасно обходились и без него. Разделяемая структура вызывает проблемы, если объекты, кото рые имеют такую структуру, модифицируются. Дело в том, что моди фикация одного из двух списков с общей структурой повлечет за собой непреднамеренное изменение другого.
В предыдущем разделе мы видели, как сделать один список хвостом другого:
(setf whole (list ’a ’b ’c) tail (cdr whole))
Изменение списка tail повлечет симметричное изменение хвоста whole, и наоборот, так как по сути это одна и та же ячейка:
>(setf (second tail) ’e)
E
>tail
(B E)