- •Джесс Либерти
- •Об авторе
- •Посвящение
- •Благодарности
- •Введение
- •Для кого эта книга
- •Неделя №1 Основные вопросы
- •Несколько слов для программистов на языке c
- •Что дальше
- •День 1-й. Первые шаги Введение
- •Программы
- •Решение многих проблем
- •Процедурное, структурированное и объектно-ориентированное программирование
- •Инкапсуляция
- •Наследование и многократное использование
- •Полиморфизм
- •Нужно ли сначала изучить язык c
- •Стандарт ansi
- •Подготовка к программированию
- •Среда разработки
- •Компиляция исходного кода программы
- •Создание исполняемого файла с помощью компоновщика
- •Цикл разработки
- •Построение проекта приветствия
- •Ошибки компиляции
- •Вопросы и ответы
- •Коллоквиум
- •Контрольные вопросы
- •Упражнения
- •Кратко об объекте cout
- •Комментарии
- •Виды комментариев
- •Использование комментариев
- •Напоследок предупреждение: осторожнее с комментариями!
- •Функции
- •Использование функций
- •Вопросы и ответы
- •Коллоквиум
- •Контрольные вопросы
- •Упражнения
- •День 3-й. Переменные и константы
- •Что такое переменная
- •Резервирование памяти
- •Размер целых
- •Знаковые и беззнаковые типы
- •Базовые типы переменных
- •Определение переменной
- •Чувствительность к регистру букв
- •Ключевые слова
- •Создание нескольких переменных одного типа
- •Присваивание значений переменным
- •Ключевое слово typedef
- •В каких случаях следует использовать типы short и long
- •Переполнение беззнаковых целых
- •Переполнение знаковых целочисленных значений
- •Символы
- •Символы и числа
- •Специальные символы
- •Константы
- •Литеральные константы
- •Символьные константы
- •Определение констант с помощью директивы #deiine
- •Определение констант с помощью ключевого слова const
- •Константы перечислений
- •Вопросы и ответы
- •Коллоквиум
- •Контрольные вопросы
- •Упражнения
- •День 4-й. Выражения и операторы
- •Выражения
- •Символы пробелов
- •Блоки и комплексные варажения
- •Операции
- •Операторы
- •Оператор присваивания
- •Математические операторы
- •Целочисленное деление и деление по модулю
- •Совместное использование математических операторов с операторами присваивания
- •Инкремент декремент
- •Префикс и постфикс
- •Приоритеты операторов
- •Вложение круглых скобок
- •Что такое истинно
- •Операторы отношений
- •Оператор if
- •Использование отступов в программных кодых
- •Ключевое слово else
- •Сложные конструкции с if
- •Использование фигурных скобок для вложенных операторов if
- •Логические операторы
- •Логическое и
- •Логическое или
- •Логическое нет
- •Вычисление по сокращенной схеме
- •Приоритеты операторов отношений
- •Подробнее об истине и лжи
- •Условный оператор
- •Вопросы и ответы
- •Коллоквиум
- •Контрольные вопросы
- •Упражнения
- •День 5-й. Функции
- •Что такое функция
- •Возвращаемые значения, параметры и аргументы
- •Объявление и определение функций
- •Объявление функции
- •Прототипы функций
- •Определение функции
- •Выполнение функций
- •Локальные переменные
- •Глобальные переменные
- •Глобальные переменные; будьте начеку
- •Подробнее о локальных переменных
- •Операторы, используемые в функциях
- •Подробнее об аргументах функций
- •Использование функций в качестве параметров функций
- •Параметры - это локальные переменные
- •Подробнее о возвращаемых значениях
- •Значения параметров, используемые по умолчанию
- •Перегрузка функций
- •Дополнительные сведения о функциях
- •Подставляемые inline-функции
- •Рекурсия
- •Работа функций - приподнимаем завесу тайны
- •Уровни абстракции
- •Разбиение памяти
- •Стек и функции
- •Вопросы и ответы
- •Коллоквиум
- •Контрольные вопросы
- •Упражнения
- •День 6-й. Базовые классы
- •Создание новых типов
- •Зачем создавать новый тип
- •Классы и члены классов
- •Объявление класса
- •Несколько слов об используемых именах
- •Определение объекта
- •Классы в сравнении с объектами
- •Получение доступа к членам класса
- •Значения присваиваются объектам, а не классам
- •Что объявишь, то и будешь иметь
- •Ограничение доступа к членам класса
- •Оставьте данные класса закрытыми
- •Ограничение доступа к данным - это не способ защиты данных, а лишь средство облегчения программирования
- •Определение методов класса
- •Конструкторы и деструкторы
- •Конструкторы и деструкторы, заданные по умолчанию
- •Использование конструктора, заданного по умолчанию
- •Объявление функций-членов со спецификатором const
- •Чем отличается интерфейс от выполнения класса
- •Где следует распологать в программе объявления классов и определения методов
- •Выполнение с подстановкой
- •Классы содержащие другие классы в качестве данных-членов
- •Структуры
- •Почему два ключевых слова несут одинаковую смысловую нагрузку
- •Вопросы и ответы
- •Коллоквиум
- •Контрольные вопросы
- •Упражнения
- •День 7-й. Циклы
- •Организация циклов
- •История оператора goto
- •Почему следует избегать оператора goto
- •Организация циклов с помощью оператора while
- •Сложные конструкции с оператором while
- •Операторы break и continue
- •Использование конструкции while(true)
- •Организация циклов с помощью конструкции do...While
- •Использование конструкции do...While
- •Оператор for
- •Сложные выражения с оператором for
- •Множественные инициализации и приращения счетчиков цикла
- •Нулевые параметры цикла for
- •Использование пустых циклов for
- •Вложенные циклы
- •Область видимости переменных-счетчиков циклов for
- •Обобщение сведений о циклах
- •Оператор switch
- •Обработка комманд менб с помощью оператора switch
- •Вопросы и ответы
- •Коллоквиум
- •Контрольные вопросы
- •Упражнения
- •Подведение итогов
- •Итоги первой недели
- •Неделя №2 Основные вопросы
- •Что дальше
- •День 8-й. Указатели
- •Что такое указатель
- •Использование указателя как средства хранения адреса
- •Имена указателей
- •Оператор разыменовывания
- •Указатели, адреса и переменные
- •Обращение к данным через указатели
- •Использование адреса, хранящегося в указателе
- •Для чего нужны указатели
- •Память стековая и динамически распределяемая
- •Оператор new
- •Оператор delete
- •Что такое утечка памяти
- •Размещение объектов в области динамической памяти
- •Удаление объектов
- •Доступ к членам класса
- •Динамическое размещение членов класса
- •Указатель this
- •Блуждающие, дикие или зависшие указатели
- •Использование ключевого слова const при объявлении указателей
- •Использование ключевого слова const при объявлении указателей и функций-членов
- •Указатель const this
- •Вычисления с указателями
- •Вопросы и ответы
- •Коллоквиум
- •Контрольные вопросы
- •Упражнения
- •День 9-й. Ссылки
- •Что такое ссылка
- •Использование оператора адреса (&) при работе со ссылками
- •Ссылки нельзя переназначать
- •На что можно ссылаться
- •Нулевые указатели и нулевые ссылки
- •Передача аргументов функций как ссылок
- •Передача указателей в функцию swap()
- •Передача ссылок в функцию swap()
- •Представления о заголовках функций и прототипах
- •Возвращение нескольких значений
- •Возвращение значений с помощью ссылок
- •Передача ссылок на переменные как средство повышения эффективности
- •Передача константного указателя
- •Ссылки в качестве альтернативы
- •Когда лучше использовать ссылки, а когда - указатели
- •Коктейль из ссылок и указателей
- •Не возвращайте ссылку на объект, который находиться вне области видимости!
- •Возвращение ссылки на в области динамического обмена
- •А где же уазатель?
- •Вопросы и ответы
- •Коллоквиум
- •Контрольные вопросы
- •Упражнения
- •День 10-й. Дополнительные возможности использования функции
- •Перегруженные функции-члены
- •Использование значений, заданных по умолчанию
- •Выбор между значениями по умолчанию и перегруженными функциями
- •Конструктор, принятый по умолчанию
- •Перегрузка конструкторов
- •Инициализация объектов
- •Конструктор-копировщик
- •Перегрузка операторов
- •Запись Функции инкремента
- •Перегрузка префиксных операторов
- •Типы возвратов перегруженных функций операторов
- •Возвращение безымянных временных объектов
- •Использование указателя this
- •Перегрузка постфиксных операторов
- •Различия между преинкрементном и постинкрементном
- •Оператор суммирования
- •Перегрузка оператора суммирования
- •Основные принципы перегрузки операторов
- •Ограничения перегрузки операторов
- •Что можно перегружать
- •Оператор присваивания
- •Операторы преобразований
- •Операторы преобразования типов
- •Вопросы и ответы
- •Коллоквиум
- •Контрольные вопросы
- •Упражнения
- •День 11-й. Наследование
- •Что такое наследование
- •Иерархия и наследование
- •Царство животных
- •Синтаксис наследования классов
- •Закрытый или защищенный
- •Конструкторы и деструкторы
- •Передача аргументов в базовые конструкторы
- •Замещение функций
- •Сокрытие метода базового класса
- •Вызов базового метода
- •Виртуальные методы
- •Как работают виртуальные методы
- •Нельзя брать там, находясь здесь
- •Дробление объекта
- •Виртуальные деструкторы
- •Виртуальный конструктор-копировщик
- •Цена виртуальности методов
- •Вопросы и ответы
- •Коллоквиум
- •Упражнения
- •День 12-й. Массивы и связанные листы
- •Что такое массивы
- •Элементы массива
- •Вывод данных за пределами массива
- •Ошибки подсчета столбцов для забора
- •Инициализация массива
- •Объявление массивов
- •Массивы объектов
- •Многомерные массивы
- •Инициализация многомерного массива
- •Несколько слов о памяти
- •Массивы указателей
- •Объявление массивов в области динамического обмена
- •Указатель на массив или массив указателей
- •Имена массивов и указателей
- •Удаление массива из области динамической памяти
- •Массивы символов
- •Функции strcpy() и strncpy()
- •Классы строк
- •Связанные списки и другие структуры
- •Общие представления о связанных списках
- •Делегирование ответственности
- •Компоненты связанных списков
- •Что мы узнали в этой главе
- •Классы массивов
- •Вопросы и ответы
- •Коллоквиум
- •Контрольные вопросы
- •Упражнения
- •День 13-й. Полиморфизм
- •Проблемы с одиночным наследованием
- •Перенос метода вверх по иерархии классов
- •Приведение указателя к типу производного класса
- •Добавление объекта в два списка
- •Множественное наследование
- •Из каких частей состоят объекты, полученные в результате множественного наследования
- •Конструкторы классов, полученных в результате множественного наследования
- •Двусмысленность ситуации
- •Наследование от общего базового класса
- •Виртуальное наследование
- •Проблемы с множественным наследованием
- •Классы-мандаты
- •Абстрактные типы данных
- •Чистые виртуальные функции
- •Выполнение чистых виртуальных функций
- •Сложная иерархия абстракций
- •Когда следует использовать абстрактные типы данных
- •Логика использования абстрактных классов
- •Пара слов о множественном наследовании, абстрактных типах данных и языке Java
- •Вопросы и ответы
- •Коллоквиум
- •Контрольные вопросы
- •Упражнения
- •День 14-й. Специальные классы и функции
- •Статические переменные-члены
- •Статические функции-члены
- •Указатели на функции
- •Зачем нужны указатели на функции
- •Массивы указателей на функции
- •Передача указателей на функции в другие функции
- •Использование typedef с указателями на функции
- •Указатели на функции члены
- •Массивы указателейна функции-члены
- •Вопросы и ответы
- •Коллоквиум
- •Контрольные вопросы
- •Упражнения
- •Подведение итогов
- •Неделя №3 Основные вопросы
- •Что дальше
- •День 15-й. Дополнительные возможности наследования
- •Вложение
- •Доступ к членам вложенного класса
- •Фильтрация доступа к вложенным классам
- •Цена вложений
- •Передача объекта как значения
- •Различные пути передачи функциональности классу
- •Делегирование
- •Закрытое наследование
- •Классы друзья
- •Функции друзья
- •Функции друзья и перегрузка оператора
- •Перегрузка оператора вывода
- •Вопросы и ответы
- •Коллоквиум
- •Контрольные вопросы
- •Упражнения
- •День 16-й. Потоки
- •Знакомство с потоками
- •Инкапсуляция
- •Буферизация
- •Потоки и буферы
- •Стандартные объекты ввода-вывода
- •Переадресация
- •Вывод данных с помощью cin
- •Проблемы, возникающие при вводе строк
- •Другие методы объекта cin
- •Ввод одного символа
- •Использование функции gef() без параметров
- •Использование функции get() с параметром
- •Ввод строк со стандартного устройства ввода
- •Использование функции cin.Ignore()
- •Функции-члены peek() и putback()
- •Ввод данных с помощью cout
- •Очистка буфера вывода
- •Функции-члены объекта cout
- •Манипуляторы, флаги и команды форматирования
- •Использование функции cout.Width()
- •Установка символов заполнения
- •Установка флагов
- •Сравнение потоков и функции printf()
- •Использование файлов для ввода и вывода данных
- •Объекты ofstream
- •Состояния условий
- •Открытие файлов для ввода-вывода
- •Настройка открытия файла объектом ofstream
- •Двоичные и тектовые файлы
- •Установка параметров ввода-вывода с помощью коммандной строки
- •Вопросы и ответы
- •Коллоквиум
- •Контрольные вопросы
- •Упражнения
- •День 17-й. Пространства имен
- •Введение
- •Вызов по имени функций и классов
- •Создание пространства имен
- •Объявление и определение типов
- •Объявление функций за пределами пространства имен
- •Добавление новых членов
- •Вложения пространства имен
- •Использование пространства имен
- •Ключевое слово using
- •Использование using как оператора
- •Использование using в объявлениях
- •Псевдонимы пространства имен
- •Неименованные пространства имен
- •Стандартное пространство имен std
- •Вопросы и ответы
- •Построение моделей
- •Проектирование программ: язык моделирования
- •Процесс проектирования программ
- •Анализ требований
- •Ситуация использования
- •Определение пользователей
- •Определение первой ситуации использования
- •Создание модели домена
- •Обобщение
- •Вложение
- •Ассоциация
- •Разработка сценариев
- •Разработка путеводителей
- •Диаграммы взаимодействий
- •Создание пакетов
- •Анализ совместимости приложения
- •Анализ существующих систем
- •Прочая документация
- •Визуализация
- •Артефакты
- •Проектирование
- •Что такое классы
- •Преобразования
- •Другие преобразования
- •Обработка данных
- •Устройства
- •Статическая модель
- •Карточки crc
- •Как проводить заседания с карточками
- •Сфокусируемся на распределении ответственности
- •Как сделать класс живым
- •Ограничения карточек crc
- •Создание модели uml no картонкам crc
- •Отношения между классами
- •Множественное наследование против вложения
- •Дискриминаторы и силовые классы
- •Динамическая модель
- •Диаграммы переходов состояний
- •Сверхсостояния
- •Вопросы и ответы
- •Коллоквиум
- •Контрольные вопросы
- •Упражнения
- •День 19-й. Шаблоны
- •Что такое шаблоны
- •Параметризованные типы
- •Создание экземпляра шаблона
- •Объявление шаблона
- •Использование имени шаблона
- •Выполнение шаблона
- •Функции шаблона
- •Шаблоны и друзья
- •Дружественные классы и функции, не являющиеся шаблонами
- •Дружественный класс или функция как общий шаблон
- •Использование экземпляров шаблона
- •Специализированные функции
- •Статические члены и шаблоны
- •Стандартная библиотека шаблонов
- •Контейнеры
- •Последовательные контейнеры
- •Контейнер двухсторонней очереди
- •Очередь
- •Ассоциативные контейнеры
- •Другие ассоциативные контейнеры
- •Классы алгоритмов
- •Операции, не изменяющие последовательность
- •Алгоритмы изменения последовательности
- •Вопросы и ответы
- •Коллоквиум
- •Контрольные вопросы
- •Упражнения
- •День 20-й. Отслеживание исключительных ситуаций и ошибок
- •Ошибки, погрешности, ляпусы и "гнилой" код
- •Исключительные ситуации
- •Несколько слов о "гнилом" коде
- •Исключения
- •Как используются исключения
- •Использование блоков try и catch
- •Перехват исключений
- •Использование нескольких операторов catch
- •Наследование исключений
- •Данные в классах исключений и присвоение имен объектам исключений
- •Исключения и шаблоны
- •Исключения без ошибок
- •Ошибки и отладка программы
- •Точка останова
- •Анализ значений переменных
- •Исследование памяти
- •Код ассемблера
- •Вопросы и ответы
- •Коллоквиум
- •Контрольные вопросы
- •Упражнения
- •День 21-й. Что дальше
- •Процессор и компилятор
- •Просмотр промежуточного файла
- •Использование директивы #define
- •Использование директивы #define для создания констант
- •Использование директивы #define для тестирования
- •Комманда препроцессора #else
- •Включение файлов и предупреждение ошибок включения
- •Макросы
- •Зачем нужны все эти круглые скобки
- •Макросы в сравнении с функциями шаблонов
- •Подставляемые функции
- •Операции со строками
- •Оператор взятия в кавычки
- •Конкатенация
- •Встроенные макросы
- •Макрос assert()
- •Оладка программы с помощью assert()
- •Макрос assert() вместо исключений
- •Побочные эффекты
- •Инварианты класса
- •Печать промежуточных значений
- •Уровни отладки
- •Операции с битами данных
- •Сброс битов
- •Инверсия битов
- •Битовые поля
- •Стиль программирования
- •Отступы
- •Фигурные скобки
- •Длинные строки
- •Конструкции с оператором switch
- •Текст программы
- •Имена идентификаторов
- •Правописание и использование прописных букв в именах
- •Комментарии
- •Организация доступа к данным и методам
- •Определения классов
- •Включение файлов
- •Макрос assert()
- •Ключевое слово const
- •Сделаем еще один шаг вперед
- •Где получить справочную информацию и советы
- •Журналы
- •Выскажите свое мнение о книге
- •Вопросы и ответы
- •Коллоквиум Контрольные вопросы
- •Упражнения
- •Подведение итогов
- •Приложение а Приоритеты операторов
- •Приложение б
- •Приложение в Двоичные о числа
- •Другие системы счисления
- •Еще об основаниях
- •Двоичная система счисления
- •Почему именно основание 2
- •Биты, байты и полубайты
- •Что такое килобайт
- •Двоичные числа
- •Шестнадцатерична система счисления
- •Приложение г - Ответы День 1 Контрольные вопросы
- •Упражнения
- •День 2 Контрольные вопросы
- •Упражнения
- •День 3 Контрольные вопросы
- •Упражнения
- •День 4 Контрольные вопросы
- •Упражнения
- •День 5 Контрольные вопросы
- •Упражнения
- •День 6 Контрольные вопросы
- •Упражнения
- •День 7 Контрольные вопросы
- •Упражнения
- •День 8 Контрольные вопросы
- •Упражнения
- •День 9 Контрольные вопросы
- •Упражнения
- •День 10 Контрольные вопросы
- •Упражнения
- •День 11 Контрольные вопросы
- •Упражнения
- •День 12 Контрольные вопросы
- •Упражнения
- •День 13 Контрольные вопросы
- •Упражнения
- •День 14 Контрольные вопросы
- •Упражнения
- •День 15 Контрольные вопросы
- •Упражнения
- •День 16 Контрольные вопросы
- •Упражнения
- •День 17 Контрольные вопросы
- •Упражнения
- •День 18 Контрольные вопросы
- •Упражнения
- •День 19 Контрольные вопросы
- •Упражнения
- •День 20 Контрольные вопросы
- •Упражнения
- •День 21 Контрольные вопросы
- •Упражнения
Компоненты связанных списков
Связанный список состоит из узлов. Узлы представляют собой абстрактные классы. В нашем примере для построения связанного списка используются три подтипа данных. Один узел будет представлять голову связанного списка и отвечать за его инициализацию. Попробуйте догадаться сами, за что отвечает хвостовой узел. Между ними могут быть представлены (либо могут отсутствовать) один или несколько промежуточных узлов, которые отвечают за обработку данных, переданных в список.
Обратите внимание, что данные списка и сам список — это не одно и то же. В списке могут быть представлены данные любого типа, но связываются друг с другом не данные, а узлы, которые содержат данные.
Выполняемой части программы ничего не известно об узлах, она работает со связанным списком как с единым целым. В то же время функциональная нагрузка на список как таковой весьма ограничена — он просто распределяет ответственность за выполнение задач между узлами.
В листинге 12.13 рассматривается пример программы со связанным списком, а затем детально анализируется ее работа.
Листинг 12.13. Связанный список
0: // **********************************************
1: // Листинг 12.13.
2: //
3: // ЦЕЛЬ: Показать использование связанного списка
4: // ПРИМЕЧАНИЯ:
5: //
6: // Авторское право: Copyright (С) 1998 Liberty Associates, Inc.
7: // Все права защищены
8: //
9: // Показан один из подходов обьектно-ориентированного
10: // программирования по созданию связанных списков.
11: // Список распределяет задачи между узлами,
12: // представляющими собой абстрактные типы данных.
13: // Список состоит из трех узлов: головного,
14: // хвостового и промежуточного. Данные содержит
15: // только промежуточный узел.
16: // Все объекты, используемые в списке, относятся
17: // к классу Data.
18: // **********************************************
19:
20:
21: #include <iostream.h>
22:
23: enum { kIsSmaller, kIsLarger, kIsSame};
24:
25: // Связанный список основывается на обьектах класса Data
26: // Любой класс в связанном списке должен поддерживать два метода:
27: // Show (отображение значения) и Compare (возвращение относительной позиции узла)
28: class Data
29: {
30: public:
31: Data(intval):myValue(val){ }
32: ~Data(){ }
33: int Compare(const Data &);
34: void Show() { cout << myValue << endl; }
35: private:
36: int myValue;
37: };
38:
39: // Сравнение используется для определения
40: // позиции в списке для нового узла.
41: int Data::Compare(const Data & theOtherData)
42: {
43: if (myValue < theOtherData.myValue)
44: return kIsSmaller;
45: if (myValue > theOtherData.myValue)
46: return kIsLarger;
47: else
48: return kIsSame;
49: }
50:
51: // Объявления
52: class Node;
53: class HeadNode;
54: class TailNode;
55: class InternalNode;
56:
57: // ADT-представление узловых объектов списка.
58: // В каждом производном классе должны быть замещены функции Insert и Show
59: class Node
60: {
61: public:
62: Node(){ }
63: virtual ~Node(){ }
64: virtual Node * Insert(Data * theData)=0;
65: virtual void Show() = 0;
66: private:
67: };
68:
69: // Этот узел поддерживает реальные объекты.
70: // В данном случае объект имеет тип Data
71: // 0 другом, более общем методе решения этой
72: // задачи мы узнаем при рассмотрении шаблонов.
73: class InternalNode: public Node
74: {
75: public:
76: InternalNode(Data * theData, Node * next);
77: ~InternalNode() { delete myNext; delete myData; }
78: virtual Node * Insert(Data * theData);
79: virtual void Show() { myData->Show(); myNext->Show(); } // Делегирование!
80:
81: private:
82: Data * myData; // данные списка
83: Node * myNext; // указатель на следующий узел в связанном списке
84: };
85:
86: // Инициализация, выполняемая каждым конструктором
87: InternalNode::InternalNode(Data * theData, Node * next):
88: myData(theData), myNext(next)
89: {
90: }
91:
92: // Сущность списка.
93: // Когда в список передается новый объект,
94: // программа определяет позицию в списке
95: // для нового узла
96: Node * InternalNode::Insert(Data * theData)
97: {
98:
99: // Этот новенький больше или меньше чем я?
100: int result = myData->Compare(*theData);
101:
102:
103: switch(result)
104: {
105: // По соглашению, если он такой же как я, то он идет первым
106: case kIsSame: // условие выполняется
107: case kIsLarger: // новые данные вводятся перед моими
108: {
109: InternalNode * dataNode = new InternalNode(theData, this);
110: return dataNode;
111: }
112:
113: // Он больше чем я, поэтому передается в
114: // следующий узел, и пусть тот делает с этими данными все, что захочет.
115: case kIsSmaller:
116: myNext = myNext->Insert(theData);
117: return this;
118: }
119: return this; // появляется MSC
120: }
121:
122:
123: // Хвостовой узел выполняет роль часового
124:
125: class TailNode : public Node
126: {
127: public:
128: TailNode(){ }
129: ~TailNode(){ }
130: virtual Node * Insert(Data * theData);
131: virtual void Show() { }
132:
133: private:
134:
135: };
136:
137: // Если данные подходят для меня, то они должны быть вставлены передо мной,
138: // так как я хвост и НИЧЕГО не может быть после меня
139: Node * TailNode::Insert(Data * theData)
140: {
141: InternalNode * dataNode = ew InternalNode(theData, this);
142: return dataNode;
143: }
144:
145: // Головной узел не содержит данных, он только
146: // указывает на начало списка
147: class HeadNode : public Node
148: {
149: public:
150: HeadNode();
151: ~HeadNode() { delete myNext; }
152: virtual Node * Insert(Data * theData);
153: virtual void Show() { myNext->Show(); }
154: private:
155: Node * myNext;
156: };
157:
158: // Как только создается головной узел,
159: // он создает хвост
160: HeadNode::HeadNode()
161: {
162: myNext = new TailNode;
163: }
164:
165: // Ничего не может быть перед головой, поэтому
166: // любые данные передаются в следующий узел
167: Node * HeadNode::Insert(Data * theData)
168: {
169: myNext = myNext->Insert(theData);
170: return this;
171: }
172:
173: // Я только распределяю задачи между узлами
174: class LinkedList
175: {
176: public:
177: LinkedList();
178: ~LinkedList() { delete myHead; }
179: void Insert(Data * theData);
180: void ShowAll() { myHead->Show(); }
181: private:
182: HeadNode * myHead;
183: };
184:
185: // Список появляется с созданием головного узла,
186: // который сразу создает хвостовой узел.
187: // Таким образом, пустой список содержит указатель на головной узел,
188: // указывающий, в свою очередь, на хвостовой узел, между которыми пока ничего нет.
189: LinkedList::LinkedList()
190: {
191: myHead = new HeadNode;
192: }
193:
194: // Делегирование, делегирование, делегирование
195: void LinkedList::Insert(Data * pData)
196: {
197: myHead->Insert(pData);
198: }
199:
200: // выполняемая тестовая программа
201: int main()
202: {
203: Data * pData;
204: int val;
205: LinkedList 11;
206:
207: // Предлагает пользователю ввести значение,
208: // которое передается в список
209: for (;;)
210: {
211: cout << "What value? (0 to stop): ";
212: cin >> val;
213: if (!val)
214: break;
215: pData = new Data(val);
216: ll.Insert(pData);
217: }
218:
219: // теперь пройдемся по списку и посмотрим значения
220: ll.ShowAll();
221: return 0; // 11 выходит за установленные рамки и поэтому удалено!
222: }
Результат:
What value? (0 to stop) 5
What value? (0 to stop) 8
What value? (0 to stop) 3
What value? (0 to stop) 9
What value? (0 to stop) 2
What value? (0 to stop) 10
What value? (0 to stop) 0
2
3
5
8
9
10
Анализ: Первое, на что следует обратить внимание, — это константное перечисление, в котором представлены константы kIsSmaller, kIsLarger и kIsSame. Любой объект, представленный в списке, должен поддерживать метод Compare('). Константы, показанные выше, возвращаются в результате выполнения этого метода.
В строках 28—37 объявляется класс Data, а в строках 39—49 выполняется метод Compare(). Объекты класса Data содержат данные и могут использоваться для сравнения с другими объектами класса Data. Они также поддерживают метод Show(), отображающий значение объекта класса Data.
Чтобы лучше разобраться в работе связанного списка, проанализируем шаг за шагом выполнение программы, показанной выше. В строке 201 объявляется выполняемый блок программы, в строке 203 — указатель на класс Data, а в строке 205 определяется связанный список.
Для создания связанного списка в строке 189 вызывается конструктор. Единственное, что он делает, — выделяет области памяти для объекта HeadNode и присваивает адрес объекта указателю, поддерживаемому связанным списком и объявленному в строке 182.
При создании объекта HeadNode вызывается еще один конструктор, объявленный в строках 160—163, который, в свою очередь, создает объект TailNode и присваивает его адрес указателю myNext, содержащемуся в объекте HeadNode. При создании объекта TailNode вызывается конструктор TailNode, объявленный в строке 128. Тело конструктора содержится в той же строке программы, и он не создает никаких новых объектов.
Таким образом, создание связанного списка вызывает последовательность взаимосвязанных процессов, в результате которых для него выделяется область стековой памяти, создаются головной и хвостовой узлы и устанавливаются взаимосвязи между ними, как показано на рис. 12.6.
В строке 209 начинается бесконечный цикл. Появляется предложение пользователю ввести значение, которое будет добавлено в связанный список. Ввод новых значений можно продолжать до бесконечности. Ввод значения 0 завершает цикл. Введенное значение проверяется в строке 213.
Если введенное значение отличается от 0, то в строке 215 создается новый объект типа Data, а в строке 216 он вводится в связанный список. Предположим, что пользователь ввел число 15, после чего в строке 195 будет вызван метод Insert.
Рис. 12.6. Связанный список сразу после создания
Связанный лист немедленно передаст ответственность за ввод объекта головному узлу, вызвав в строке 167 метод Insert. Головной узел немедленно делегирует ответственность любому другому узлу (вызывает в строке 139 метод Insert), адрес которого хранится в указателе myNext. Сначала в этом указателе представлен адрес хвостового узла (вспомните, что при создании головного узла автоматически создается хвостовой узел и ссылка на него добавляется в головной узел).
Хвостовому узлу TailNode известно, что любой объект, переданный обращением TailNode::Insert, нужно добавить в список непосредственно перед собой. Так, в строке 141 создается объект InternalNode, который добавляется в список перед хвостовым узлом и принимает введенные данные и указатель на хвостовой узел. Эта процедура выполняется с помощью объявленного в строке 87 конструктора объекта InternalNode.
Конструктор объекта InternalNode просто инициализирует указатель класса Data адресом переданного объекта этого класса, а также присваивает указателю myNext этого
объекта адрес того узла, из которого он был передан. В случае создания первого промежуточного узла этому указателю будет присвоен адрес хвостового узла, поскольку, как вы помните, именно хвостовой узел передал свой указатель this.
Теперь, после того как был создан узел InternalNode, адрес этого узла присваивается указателю dataNode в строке 141, и именно этот адрес возвращается теперь методом TailNode::Insert(). Так мы возвращаемся к методу HeadNode::Insert(), где адрес узла InternalNode присваивается указателю myNext узла HeadNode (строка 169). И наконец, адрес узла HeadNode возвращается в связанный список — туда, где в строке 197 он был сброшен (ничего страшного при этом не произошло, так как связанному списку уже был известен адрес головного узла).
Зачем было беспокоиться о возвращении адреса, если он не используется? Метод Insert был объявлен в базовом классе Node. Для выполнения метода необходимо задать значение возврата. Если изменить значение возврата функции HeadNode::Insert(), то компилятор покажет сообщение об ошибке. Это все равно что возвратить узел HeadNode и позволить связанному списку выбросить его адрес.
Так что же все-таки произошло? Данные были введены в список. Список передал эти данные головному узлу. Головной узел передал их дальше по тому адресу, что хранится в его указателе. В первом цикле в этом указателе хранился адрес хвостового узла. Хвостовой узел немедленно создает новый промежуточный узел, указателю которого присваивается адрес хвостового узла. Затем хвостовой узел возвращает адрес промежуточного узла в головной узел, где он присваивается указателю myNext головного узла. Итак, свершилось то, что требовалось: данные расположились в списке правильным образом (рис. 12.7).
После ввода первого промежуточного узла программа переходит к строке 211 и выводит предложение пользователю ввести новое значение. Предположим, что в этот раз было введено значение 3. В результате в строке 215 создается новый объект класса Data и вводится в список в строке 216.
Рис. 12.7. Вид связанного списка после того, как был добавлен первый промежуточный узел
И вновь в строке 197 список передает новое значение в головной узел HeadNode. Метод HeadNode: :Insert(), в свою очередь, передает эти данные по адресу, хранящемуся в указателе myNext. Как вы помните, теперь он указывает на узел, содержащий объект типа Data со значением 15. В результате в строке 96 вызывается метод InternalNode::Insert(). В строке
100 указатель myData узла InternalNode сообщает объекту этого узла (значение которого сейчас равно 15) о необходимости вызвать метод Compare(), принимающий в качестве аргумеи'- та новый объект Data со значением 3. Метод Compare() объявлен в строке 41.
Происходит сравнение значений двух объектов, и, поскольку значение myValue соответствует 15, а theOtherData.myValue равно 3, метод возвращает константу kIsLarget. B соответствии со значением возвращенной константы программа переходит к выполнению строки 109.
Создается новый узел InternalNode для нового объекта данных. Новый узел будет указывать на текущий объект узла InternalNode, и адрес нового узла InternalNode возвращается методом InternalNode:;Insert() в головной узел HeadNode. Таким образом, новый узел, значение которого меньше значения текущего объекта, добавляется в связанный список, после чего связанный список выглядит так, как показано на рис, 12.8.
Натретьем цикле пользователь ввел значение 8. Оно больше чем 3, но меньше чем 15, поэтому программа должна ввести новый объект данных между двумя существующими промежуточными узлами. Последует та же серия операций, что и в предыдущем цикле, за тем исключением, что при вызове метода Compare() для объекта типа Data, значение кото- рогоравно 3, будет вОзвращена константа kIsSmaller, а не kIsLarger, как в предыдущем случае (поскольку значение текущего объекта 3 меньше значения нового объекта 8).
В результате метод InternalNode::Insert() переведет выполнение программы на строку 116. Вместо создания и ввода в список нового узла, новые данные будут переданы в метод Insert того объекта, на который ссылается указатель myNext текущего объекта. В данном случае будет вызван метод InsertNode для промежуточного узла, значение объекта которого равняется 15.
Вновь будет проведено сравнение данных, которое в этот раз завершится созданием нового промежуточного узла. Этот новый узел будет ссылаться на тот промежуточный узел, значение которого 15, а адрес нового узла будет присвоен указателю узла со значением 3, как показано в строке 116.
Рис. 12.8. Вид связанного списка после того, как был добавлен второй промежуточный узел
В результате новый узел вновь будет вставлен в правильную позицию.
Если вы переписали эту программу И запустили на своем компьютере, то с помощью средства отладки можно посмотреть, как будет происходить ввод других данных программой. Каждый раз будет проводиться сравнение данных и новые узлы будут добавляться в список строго в порядке возрастания значений.
