- •1. Начинаем
- •1.1. Решение задачи
- •1.2. Программа на языке C++
- •1.2.1. Порядок выполнения инструкций
- •1.3. Директивы препроцессора
- •1.4. Немного о комментариях
- •1.5. Первый взгляд на ввод/вывод
- •1.5.1. Файловый ввод/вывод
- •2. Краткий обзор С++
- •2.1. Встроенный тип данных “массив”
- •2.2. Динамическое выделение памяти и указатели
- •2.3. Объектный подход
- •2.4. Объектно-ориентированный подход
- •2.5. Использование шаблонов
- •2.6. Использование исключений
- •2.7. Использование пространства имен
- •2.8. Стандартный массив – это вектор
- •3. Типы данных С++
- •3.1. Литералы
- •3.2. Переменные
- •3.2.1. Что такое переменная
- •3.2.2. Имя переменной
- •3.2.3. Определение объекта
- •3.3. Указатели
- •3.4. Строковые типы
- •3.4.1. Встроенный строковый тип
- •3.4.2. Класс string
- •3.5. Спецификатор const
- •3.6. Ссылочный тип
- •3.7. Тип bool
- •3.8. Перечисления
- •3.9. Тип “массив”
- •3.9.1. Многомерные массивы
- •3.9.2. Взаимосвязь массивов и указателей
- •3.10. Класс vector
- •3.11. Класс complex
- •3.12. Директива typedef
- •3.13. Спецификатор volatile
- •3.14. Класс pair
- •3.15. Типы классов
- •4. Выражения
- •4.1. Что такое выражение?
- •4.2. Арифметические операции
- •4.3. Операции сравнения и логические операции
- •4.4. Операции присваивания
- •4.5. Операции инкремента и декремента
- •4.6. Операции с комплексными числами
- •4.7. Условное выражение
- •4.8. Оператор sizeof
- •4.9. Операторы new и delete
- •4.10. Оператор “запятая”
- •4.11. Побитовые операторы
- •4.12. Класс bitset
- •4.13. Приоритеты
- •4.14. Преобразования типов
- •4.14.1. Неявное преобразование типов
- •4.14.2. Арифметические преобразования типов
- •4.14.3. Явное преобразование типов
- •4.14.4. Устаревшая форма явного преобразования
- •4.15. Пример: реализация класса Stack
- •5. Инструкции
- •5.1. Простые и составные инструкции
- •5.2. Инструкции объявления
- •5.3. Инструкция if
- •5.4. Инструкция switch
- •5.5. Инструкция цикла for
- •5.6. Инструкция while
- •5.8. Инструкция do while
- •5.8. Инструкция break
- •5.9. Инструкция continue
- •5.10. Инструкция goto
- •5.11. Пример связанного списка
- •5.11.1. Обобщенный список
- •6. Абстрактные контейнерные типы
- •6.1. Система текстового поиска
- •6.2. Вектор или список?
- •6.3. Как растет вектор?
- •6.4. Как определить последовательный контейнер?
- •6.5. Итераторы
- •6.6. Операции с последовательными контейнерами
- •6.6.1. Удаление
- •6.6.2. Присваивание и обмен
- •6.6.3. Обобщенные алгоритмы
- •6.7. Читаем текстовый файл
- •6.8. Выделяем слова в строке
- •6.9. Обрабатываем знаки препинания
- •6.10. Приводим слова к стандартной форме
- •6.11. Дополнительные операции со строками
- •6.12. Строим отображение позиций слов
- •6.12.1. Определение объекта map и заполнение его элементами
- •6.12.2. Поиск и извлечение элемента отображения
- •6.12.3. Навигация по элементам отображения
- •6.12.4. Словарь
- •6.12.5. Удаление элементов map
- •6.13. Построение набора стоп-слов
- •6.13.1. Определение объекта set и заполнение его элементами
- •6.13.2. Поиск элемента
- •6.13.3. Навигация по множеству
- •6.14. Окончательная программа
- •6.15. Контейнеры multimap и multiset
- •6.16. Стек
- •6.17. Очередь и очередь с приоритетами
- •6.18. Вернемся в классу iStack
- •7. Функции
- •7.1. Введение
- •7.2. Прототип функции
- •7.2.1. Тип возвращаемого функцией значения
- •7.2.2. Список параметров функции
- •7.2.3. Проверка типов формальных параметров
- •7.3. Передача аргументов
- •7.3.1. Параметры-ссылки
- •7.3.2. Параметры-ссылки и параметры-указатели
- •7.3.3. Параметры-массивы
- •7.3.4. Абстрактные контейнерные типы в качестве параметров
- •7.3.5. Значения параметров по умолчанию
- •7.3.6. Многоточие
- •7.4. Возврат значения
- •7.4.1. Передача данных через параметры и через глобальные объекты
- •7.5. Рекурсия
- •7.6. Встроенные функции
- •7.7. Директива связывания extern "C" A
- •7.8. Функция main(): разбор параметров командной строки
- •7.8.1. Класс для обработки параметров командной строки
- •7.9. Указатели на функции
- •7.9.1. Тип указателя на функцию
- •7.9.2. Инициализация и присваивание
- •7.9.3. Вызов
- •7.9.4. Массивы указателей на функции
- •7.9.5. Параметры и тип возврата
- •7.9.6. Указатели на функции, объявленные как extern "C"
- •8. Область видимости и время жизни
- •8.1. Область видимости
- •8.1.1. Локальная область видимости
- •8.2. Глобальные объекты и функции
- •8.2.1. Объявления и определения
- •8.2.2. Сопоставление объявлений в разных файлах
- •8.2.3. Несколько слов о заголовочных файлах
- •8.3. Локальные объекты
- •8.3.1. Автоматические объекты
- •8.3.2. Регистровые автоматические объекты
- •8.3.3. Статические локальные объекты
- •8.4. Динамически размещаемые объекты
- •8.4.1. Динамическое создание и уничтожение единичных объектов
- •8.4.2. Шаблон auto_ptr А
- •8.4.3. Динамическое создание и уничтожение массивов
- •8.4.4. Динамическое создание и уничтожение константных объектов
- •8.4.5. Оператор размещения new А
- •8.5. Определения пространства имен А
- •8.5.1. Определения пространства имен
- •8.5.2. Оператор разрешения области видимости
- •8.5.3. Вложенные пространства имен
- •8.5.4. Определение члена пространства имен
- •8.5.5. ПОО и члены пространства имен
- •8.5.6. Безымянные пространства имен
- •8.6. Использование членов пространства имен А
- •8.6.1. Псевдонимы пространства имен
- •8.6.2. Using-объявления
- •8.6.3. Using-директивы
- •8.6.4. Стандартное пространство имен std
- •9. Перегруженные функции
- •9.1. Объявления перегруженных функций
- •9.1.1. Зачем нужно перегружать имя функции
- •9.1.2. Как перегрузить имя функции
- •9.1.3. Когда не надо перегружать имя функции
- •9.1.4. Перегрузка и область видимости A
- •9.1.5. Директива extern "C" и перегруженные функции A
- •9.1.6. Указатели на перегруженные функции A
- •9.1.7. Безопасное связывание A
- •9.2. Три шага разрешения перегрузки
- •9.3. Преобразования типов аргументов A
- •9.3.1. Подробнее о точном соответствии
- •9.3.2. Подробнее о расширении типов
- •9.3.3. Подробнее о стандартном преобразовании
- •9.3.4. Ссылки
- •9.4. Детали разрешения перегрузки функций
- •9.4.1. Функции-кандидаты
- •9.4.2. Устоявшие функции
- •9.4.3. Наилучшая из устоявших функция
- •9.4.4. Аргументы со значениями по умолчанию
- •10. Шаблоны функций
- •10.1. Определение шаблона функции
- •10.2. Конкретизация шаблона функции
- •10.3. Вывод аргументов шаблона А
- •10.4. Явное задание аргументов шаблона A
- •10.5. Модели компиляции шаблонов А
- •10.5.1. Модель компиляции с включением
- •10.5.2. Модель компиляции с разделением
- •10.5.3. Явные объявления конкретизации
- •10.6. Явная специализация шаблона А
- •10.7. Перегрузка шаблонов функций А
- •10.8. Разрешение перегрузки при конкретизации A
- •10.9. Разрешение имен в определениях шаблонов А
- •10.10. Пространства имен и шаблоны функций А
- •10.11. Пример шаблона функции
- •11. Обработка исключений
- •11.1. Возбуждение исключения
- •11.2. try-блок
- •11.3. Перехват исключений
- •11.3.1. Объекты-исключения
- •11.3.2. Раскрутка стека
- •11.3.3. Повторное возбуждение исключения
- •11.3.4. Перехват всех исключений
- •11.4. Спецификации исключений
- •11.4.1. Спецификации исключений и указатели на функции
- •11.5. Исключения и вопросы проектирования
- •12. Обобщенные алгоритмы
- •12.1. Краткий обзор
- •12.2. Использование обобщенных алгоритмов
- •12.3. Объекты-функции
- •12.3.1. Предопределенные объекты-функции
- •12.3.3. Сравнительные объекты-функции
- •12.3.4. Логические объекты-функции
- •12.3.5. Адаптеры функций для объектов-функций
- •12.3.6. Реализация объекта-функции
- •12.4. Еще раз об итераторах
- •12.4.1. Итераторы вставки
- •12.4.2. Обратные итераторы
- •12.4.3. Потоковые итераторы
- •12.4.4. Итератор istream_iterator
- •12.4.5. Итератор ostream_iterator
- •12.4.6. Пять категорий итераторов
- •12.5. Обобщенные алгоритмы
- •12.5.1. Алгоритмы поиска
- •12.5.2. Алгоритмы сортировки и упорядочения
- •12.5.3. Алгоритмы удаления и подстановки
- •12.5.4. Алгоритмы перестановки
- •12.5.5. Численные алгоритмы
- •12.5.6. Алгоритмы генерирования и модификации
- •12.5.7. Алгоритмы сравнения
- •12.5.8. Алгоритмы работы с множествами
- •12.5.9. Алгоритмы работы с хипом
- •12.6.1. Операция list_merge()
- •12.6.2. Операция list::remove()
- •12.6.3. Операция list::remove_if()
- •12.6.4. Операция list::reverse()
- •12.6.5. Операция list::sort()
- •12.6.6. Операция list::splice()
- •12.6.7. Операция list::unique()
- •13. Классы
- •13.1. Определение класса
- •13.1.1. Данные-члены
- •13.1.2. Функции-члены
- •13.1.3. Доступ к членам
- •13.1.4. Друзья
- •13.1.5. Объявление и определение класса
- •13.2. Объекты классов
- •13.3. Функции-члены класса
- •13.3.1. Когда использовать встроенные функции-члены
- •13.3.2. Доступ к членам класса
- •13.3.3. Закрытые и открытые функции-члены
- •13.3.4. Специальные функции-члены
- •13.3.5. Функции-члены со спецификаторами const и volatile
- •13.3.6. Объявление mutable
- •13.4. Неявный указатель this
- •13.4.1. Когда использовать указатель this
- •13.5. Статические члены класса
- •13.5.1. Статические функции-члены
- •13.6. Указатель на член класса
- •13.6.1. Тип члена класса
- •13.6.2. Работа с указателями на члены класса
- •13.6.3. Указатели на статические члены класса
- •13.7. Объединение – класс, экономящий память
- •13.8. Битовое поле – член, экономящий память
- •13.9. Область видимости класса A
- •13.9.1. Разрешение имен в области видимости класса
- •13.10. Вложенные классы A
- •13.10.1. Разрешение имен в области видимости вложенного класса
- •13.11. Классы как члены пространства имен A
- •13.12. Локальные классы A
- •14. Инициализация, присваивание и уничтожение класса
- •14.1. Инициализация класса
- •14.2. Конструктор класса
- •14.2.1. Конструктор по умолчанию
- •14.2.2. Ограничение прав на создание объекта
- •14.2.3. Копирующий конструктор
- •14.3. Деструктор класса
- •14.3.1. Явный вызов деструктора
- •14.3.2. Опасность увеличения размера программы
- •14.4. Массивы и векторы объектов
- •14.4.1. Инициализация массива, распределенного из хипа A
- •14.4.2. Вектор объектов
- •14.5. Список инициализации членов
- •14.6. Почленная инициализация A
- •14.6.1. Инициализация члена, являющегося объектом класса
- •14.7. Почленное присваивание A
- •14.8. Соображения эффективности A
- •15. Перегруженные операторы и определенные пользователем преобразования
- •15.1. Перегрузка операторов
- •15.1.1. Члены и не члены класса
- •15.1.2. Имена перегруженных операторов
- •15.1.3. Разработка перегруженных операторов
- •15.2. Друзья
- •15.3. Оператор =
- •15.4. Оператор взятия индекса
- •15.5. Оператор вызова функции
- •15.6. Оператор “стрелка”
- •15.7. Операторы инкремента и декремента
- •15.8. Операторы new и delete
- •15.8.1. Операторы new[ ] и delete [ ]
- •15.8.2. Оператор размещения new() и оператор delete()
- •15.9. Определенные пользователем преобразования
- •15.9.1. Конвертеры
- •15.9.2. Конструктор как конвертер
- •15.10. Выбор преобразования A
- •15.10.1. Еще раз о разрешении перегрузки функций
- •15.10.2. Функции-кандидаты
- •15.10.3. Функции-кандидаты для вызова функции в области видимости класса
- •15.10.4. Ранжирование последовательностей определенных пользователем преобразований
- •15.11. Разрешение перегрузки и функции-члены A
- •15.11.1. Объявления перегруженных функций-членов
- •15.11.2. Функции-кандидаты
- •15.11.3. Устоявшие функции
- •15.12. Разрешение перегрузки и операторы A
- •15.12.1. Операторные функции-кандидаты
- •15.12.2. Устоявшие функции
- •15.12.3. Неоднозначность
- •16. Шаблоны классов
- •16.1. Определение шаблона класса
- •16.1.1. Определения шаблонов классов Queue и QueueItem
- •16.2. Конкретизация шаблона класса
- •16.2.1. Аргументы шаблона для параметров-констант
- •16.3. Функции-члены шаблонов классов
- •16.3.1. Функции-члены шаблонов Queue и QueueItem
- •16.4. Объявления друзей в шаблонах классов
- •16.4.1. Объявления друзей в шаблонах Queue и QueueItem
- •16.5. Статические члены шаблонов класса
- •16.6. Вложенные типы шаблонов классов
- •16.7. Шаблоны-члены
- •16.8. Шаблоны классов и модель компиляции A
- •16.8.1. Модель компиляции с включением
- •16.8.2. Модель компиляции с разделением
- •16.8.3. Явные объявления конкретизации
- •16.9. Специализации шаблонов классов A
- •16.10. Частичные специализации шаблонов классов A
- •16.11. Разрешение имен в шаблонах классов A
- •16.12. Пространства имен и шаблоны классов
- •16.13. Шаблон класса Array
- •17. Наследование и подтипизация классов
- •17.1. Определение иерархии классов
- •17.1.1. Объектно-ориентированное проектирование
- •17.2. Идентификация членов иерархии
- •17.2.1. Определение базового класса
- •17.2.2. Определение производных классов
- •17.2.3. Резюме
- •17.3. Доступ к членам базового класса
- •17.4. Конструирование базового и производного классов
- •17.4.1. Конструктор базового класса
- •17.4.2. Конструктор производного класса
- •17.4.3. Альтернативная иерархия классов
- •17.4.4. Отложенное обнаружение ошибок
- •17.4.5. Деструкторы
- •17.5. Виртуальные функции в базовом и производном классах
- •17.5.1. Виртуальный ввод/вывод
- •17.5.2. Чисто виртуальные функции
- •17.5.3. Статический вызов виртуальной функции
- •17.5.4. Виртуальные функции и аргументы по умолчанию
- •17.5.5. Виртуальные деструкторы
- •17.5.6. Виртуальная функция eval()
- •17.5.7. Почти виртуальный оператор new
- •17.5.8. Виртуальные функции, конструкторы и деструкторы
- •17.6. Почленная инициализация и присваивание A
- •17.7. Управляющий класс UserQuery
- •17.7.1. Определение класса UserQuery
- •17.8. Соберем все вместе
- •18. Множественное и виртуальное наследование
- •18.1. Готовим сцену
- •18.2. Множественное наследование
- •18.3. Открытое, закрытое и защищенное наследование
- •18.3.1. Наследование и композиция
- •18.3.2. Открытие отдельных членов
- •18.3.3. Защищенное наследование
- •18.3.4. Композиция объектов
- •18.4. Область видимости класса и наследование
- •18.4.1. Область видимости класса при множественном наследовании
- •18.5. Виртуальное наследование A
- •18.5.1. Объявление виртуального базового класса
- •18.5.2. Специальная семантика инициализации
- •18.5.3. Порядок вызова конструкторов и деструкторов
- •18.5.4. Видимость членов виртуального базового класса
- •18.6. Пример множественного виртуального наследования A
- •18.6.1. Порождение класса, контролирующего выход за границы массива
- •18.6.2. Порождение класса отсортированного массива
- •18.6.3. Класс массива с множественным наследованием
- •19. Применение наследования в C++
- •19.1. Идентификация типов во время выполнения
- •19.1.1. Оператор dynamic_cast
- •19.1.2. Оператор typeid
- •19.1.3. Класс type_info
- •19.2. Исключения и наследование
- •19.2.1. Исключения, определенные как иерархии классов
- •19.2.2. Возбуждение исключения типа класса
- •19.2.3. Обработка исключения типа класса
- •19.2.4. Объекты-исключения и виртуальные функции
- •19.2.5. Раскрутка стека и вызов деструкторов
- •19.2.6. Спецификации исключений
- •19.2.7. Конструкторы и функциональные try-блоки
- •19.2.8. Иерархия классов исключений в стандартной библиотеке C++
- •19.3. Разрешение перегрузки и наследование A
- •19.3.1. Функции-кандидаты
- •19.3.2. Устоявшие функции и последовательности пользовательских преобразований
- •19.3.3. Наилучшая из устоявших функций
- •20. Библиотека iostream
- •20.1. Оператор вывода <<
- •20.2. Ввод
- •20.2.1. Строковый ввод
- •20.3. Дополнительные операторы ввода/вывода
- •20.4. Перегрузка оператора вывода
- •20.5. Перегрузка оператора ввода
- •20.6. Файловый ввод/вывод
- •20.7. Состояния потока
- •20.8. Строковые потоки
- •20.9. Состояние формата
- •20.10. Сильно типизированная библиотека
- •21. Обобщенные алгоритмы в алфавитном порядке
- •Алгоритм accumulate()
- •Алгоритм adjacent_difference()
- •Алгоритм adjacent_find()
- •Алгоритм binary_search()
- •Алгоритм copy()
- •Алгоритм copy_backward()
- •Алгоритм count()
- •Алгоритм count_if()
- •Алгоритм equal()
- •Алгоритм equal_range()
- •Алгоритм fill()
- •Алгоритм fill_n()
- •Алгоритм find()
- •Алгоритм find_if()
- •Алгоритм find_end()
- •Алгоритм find_first_of()
- •Алгоритм for_each()
- •Алгоритм generate()
- •Алгоритм generate_n()
- •Алгоритм includes()
- •Алгоритм inner_product()
- •Алгоритм inplace_merge()
- •Алгоритм iter_swap()
- •Алгоритм lexicographical_compare()
- •Алгоритм lower_bound()
- •Алгоритм max()
- •Алгоритм max_element()
- •Алгоритм min()
- •Алгоритм min_element()
- •Алгоритм merge()
- •Алгоритм mismatch()
- •Алгоритм next_permutation()
- •Алгоритм nth_element()
- •Алгоритм partial_sort()
- •Алгоритм partial_sort_copy()
- •Алгоритм partial_sum()
- •Алгоритм partition()
- •Алгоритм prev_permutation()
- •Алгоритм random_shuffle()
- •Алгоритм remove()
- •Алгоритм remove_copy()
- •Алгоритм remove_if()
- •Алгоритм remove_copy_if()
- •Алгоритм replace()
- •Алгоритм replace_copy()
- •Алгоритм replace_if()
- •Алгоритм replace_copy_if()
- •Алгоритм reverse()
- •Алгоритм reverse_copy()
- •Алгоритм rotate()
- •Алгоритм rotate_copy()
- •Алгоритм search()
- •Алгоритм search_n()
- •Алгоритм set_difference()
- •Алгоритм set_intersection()
- •Алгоритм set_symmetric_difference()
- •Алгоритм set_union()
- •Алгоритм sort()
- •Алгоритм stable_partition()
- •Алгоритм stable_sort()
- •Алгоритм swap()
- •Алгоритм swap_ranges()
- •Алгоритм transform()
- •Алгоритм unique()
- •Алгоритм unique_copy()
- •Алгоритм upper_bound()
- •Алгоритмы для работы с хипом
- •Алгоритм make_heap()
- •Алгоритм pop_heap()
- •Алгоритм push_heap()
- •Алгоритм sort_heap()
При его явном вызове необходимо все же передать значение второго целого аргумента. В случае нашего класса ScreenPtr это значение игнорируется, поэтому может быть любым:
parr.operator++(1024); // вызов постфиксного operator++
Перегруженные операторы инкремента и декремента разрешается объявлять как дружественные функции. Изменим соответствующим образом определение класса
class ScreenPtr { |
|
// объявления не членов |
// префиксные операторы |
friend Screen& operator++( Screen & ); |
|
friend Screen& operator--( Screen & ); |
|
friend Screen& operator++( Screen &, int); // постфиксные операторы
friend Screen& operator--( Screen &, int); public:
// определения членов
ScreenPtr:
};
Упражнение 15.7
Напишите определения перегруженных операторов инкремента и декремента для класса ScreenPtr, предположив, что они объявлены как друзья класса.
Упражнение 15.8
С помощью ScreenPtr можно представить указатель на массив объектов класса Screen. Модифицируйте перегруженные operator*() и operator->() (см. раздел 15.6) так, чтобы указатель ни при каком условии не адресовал элемент перед началом или за концом массива. Совет: в этих операторах следует воспользоваться новыми членами size и offset.
15.8. Операторы new и delete
По умолчанию выделение объекта класса из хипа и освобождение занятой им памяти выполняются с помощью глобальных операторов new() и delete(), определенных в стандартной библиотеке C++. (Мы рассматривали эти операторы в разделе 8.4.) Но класс может реализовать и собственную стратегию управления памятью, предоставив одноименные операторы-члены. Если они определены в классе, то вызываются вместо глобальных операторов с целью выделения и освобождения памяти для объектов этого класса.
Определим операторы new() и delete() в нашем классе Screen.
Оператор-член new() должен возвращать значение типа void* и принимать в качестве первого параметра значение типа size_t, где size_t – это typedef, определенный в системном заголовочном файле <cstddef>. Вот его объявление:
class Screen { public:
void *operator
new( size_t );
// ...
};
Когда для создания объекта типа класса используется new(), компилятор проверяет, определен ли в этом классе такой оператор. Если да, то для выделения памяти под объект вызывается именно он, в противном случае – глобальный оператор new(). Например, следующая инструкция
Screen *ps = new Screen;
создает объект Screen в хипе, а поскольку в этом классе есть оператор new(), то вызывается он. Параметр size_t оператора автоматически инициализируется значением, равным размеру Screen в байтах.
Добавление оператора new() в класс или его удаление оттуда не отражаются на пользовательском коде. Вызов new выглядит одинаково как для глобального оператора, так и для оператора-члена. Если бы в классе Screen не было собственного new(), то обращение осталось бы правильным, только вместо оператора-члена вызывался бы глобальный оператор.
С помощью оператора разрешения глобальной области видимости можно вызвать глобальный new(), даже если в классе Screen определена собственная версия:
Screen *ps = ::new Screen;
Оператор delete(), являющийся членом класса, должен иметь тип void, а в качестве первого параметра принимать void*. Вот как выглядит его объявление
class Screen { public:
void operator delete( void * );
для Screen:
};
Когда операндом delete служит указатель на объект типа класса, компилятор проверяет, определен ли в этом классе оператор delete(). Если да, то для освобождения памяти вызывается именно он, в противном случае – глобальная версия оператора. Следующая инструкция
delete ps;
освобождает память, занятую объектом класса Screen, на который указывает ps. Поскольку в Screen есть оператор-член delete(), то применяется именно он. Параметр оператора типа void* автоматически инициализируется значением ps.
Добавление delete() в класс или его удаление оттуда никак не сказываются на пользовательском коде. Вызов delete выглядит одинаково как для глобального
оператора, так и для оператора-члена. Если бы в классе Screen не было собственного оператора delete(), то обращение осталось бы правильным, только вместо операторачлена вызывался бы глобальный оператор.
С помощью оператора разрешения глобальной области видимости можно вызвать глобальный delete(), даже если в Screen определена собственная версия:
::delete ps;
В общем случае используемый оператор delete() должен соответствовать тому оператору new(), с помощью которого была выделена память. Например, если ps указывает на область памяти, выделенную глобальным new(), то для ее освобождения следует использовать глобальный же delete().
Оператор delete(), определенный для типа класса, может содержать два параметра вместо одного. Первый параметр по-прежнему должен иметь тип void*, а второй –
class Screen { public:
//заменяет
//void operator delete( void * ); void operator delete( void *,
size_t );
предопределенный тип size_t (не забудьте включить заголовочный файл <cstddef>):
};
Если второй параметр есть, компилятор автоматически инициализирует его значением, равным размеру адресованного первым параметром объекта в байтах. (Этот параметр важен в иерархии классов, когда оператор delete() может наследоваться производным классом. Подробнее наследование обсуждается в главе 17.)
Рассмотрим реализацию операторов new() и delete() в классе Screen более детально. В основе нашей стратегии распределения памяти будет лежать связанный список объектов Screen, на начало которого указывает член freeStore. При каждом обращении к оператору-члену new() возвращается следующий объект из списка. При вызове delete() объект возвращается в список. Если при создании нового объекта список, адресованный freeStore, пуст, то вызывается глобальный оператор new(), чтобы получить блок памяти, достаточный для хранения screenChunk объектов класса Screen.
Как screenChunk, так и freeStore представляют интерес только для Screen, поэтому мы сделаем их закрытыми членами. Кроме того, для всех создаваемых объектов нашего класса значения этих членов должны быть одинаковыми, а следовательно, нужно объявить их статическими. Чтобы поддержать структуру связанного списка объектов Screen, нам понадобится третий член next:
class Screen { public:
void *operator new( size_t ); void operator delete( void *,
size_t );
// ...
private:
Screen *next;
static Screen *freeStore; static const int screenChunk;
};
#include "Screen.h" #include <cstddef>
//статические члены инициализируются
//в исходных файлах программы, а не в заголовочных файлах
Screen *Screen::freeStore = 0; const int Screen::screenChunk = 24;
void *Screen::operator new( size_t size )
{
Screen *p;
if ( !freeStore ) {
//связанный список пуст: получить новый блок
//вызывается глобальный оператор new
size_t chunk = screenChunk * size; freeStore = p =
reinterpret_cast< Screen* >( new char[ chunk ] );
// включить полученный блок в список for ( ;
p != &freeStore[ screenChunk - 1 ]; ++p )
p->next = p+1; p->next = 0;
}
p = freeStore;
freeStore = freeStore->next; return p;
Вот одна из возможных реализаций оператора new() для класса Screen:
}
А вот реализация оператора delete():
void Screen::operator delete( void *p, size_t )
{
//вставить "удаленный" объект назад,
//в список свободных
( static_cast< Screen* >( p ) )->next = freeStore;
freeStore = static_cast< Screen* >( p );
}
Оператор new() можно объявить в классе и без соответствующего delete(). В таком случае объекты освобождаются с помощью одноименного глобального оператора. Разрешается также объявить и оператор delete() без new(): объекты будут создаваться с помощью одноименного глобального оператора. Однако обычно эти операторы реализуются одновременно, как в примере выше, поскольку разработчику класса, как правило, нужны оба.
Они являются статическими членами класса, даже если программист явно не объявит их таковыми, и подчиняются обычным ограничениями для подобных функций-членов: им не передается указатель this, а следовательно, напрямую они могут получить доступ только к статическим членам. (См. обсуждение статических функций-членов в разделе 13.5.) Причина, по которой эти операторы делаются статическими, заключается в том, что они вызываются либо перед конструированием объекта класса (new()), либо после его уничтожения (delete()).
Выделение памяти с помощью оператора new(), например:
Screen *ptr = new Screen( 10, 20 );
// Псевдокод на C++ ptr = Screen::operator
new( sizeof( Screen ) );
эквивалентно последовательному выполнению таких инструкций:
Screen::Screen( ptr, 10, 20 );
Иными словами, сначала вызывается определенный в классе оператор new(), чтобы выделить память для объекта, а затем этот объект инициализируется конструктором. Если new() неудачно завершает работу, то возбуждается исключение типа bad_alloc и конструктор не вызывается.
Освобождение памяти с помощью оператора delete(), например:
delete ptr;
// Псевдокод на C++ Screen::~Screen( ptr )
;
эквивалентно последовательному выполнению таких инструкций:
Screen::operator delete( ptr, sizeof( *ptr ) );