- •Предисловие
- •Структура книги
- •Благодарности
- •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.7. Использование пространства имен
- •2.8. Стандартный массив – это вектор
- •Часть II
- •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.14. Класс pair
- •3.15. Типы классов
- •4. Выражения
- •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.1. Что такое выражение?
- •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.2. Поиск и извлечение элемента отображения
- •6.12.3. Навигация по элементам отображения
- •6.12.4. Словарь
- •6.12.5. Удаление элементов map
- •6.13. Построение набора стоп-слов
- •6.13.2. Поиск элемента
- •6.13.3. Навигация по множеству
- •6.14. Окончательная программа
- •6.15. Контейнеры multimap и multiset
- •6.16. Стек
- •6.17. Очередь и очередь с приоритетами
- •6.18. Вернемся в классу iStack
- •Часть III
- •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.5. Значения параметров по умолчанию
- •7.3.6. Многоточие
- •7.4. Возврат значения
- •7.5. Рекурсия
- •7.6. Встроенные функции
- •7.7. Директива связывания extern "C" A
- •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.2. Шаблон auto_ptr А
- •8.4.3. Динамическое создание и уничтожение массивов
- •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.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.2. Арифметические объекты-функции
- •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()
- •Часть IV
- •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.11. Классы как члены пространства имен A
- •13.12. Локальные классы A
- •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.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.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
- •Часть V
- •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.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.1. Готовим сцену
- •18.2. Множественное наследование
- •18.3. Открытое, закрытое и защищенное наследование
- •18.3.1. Наследование и композиция
- •18.3.2. Открытие отдельных членов
- •18.3.3. Защищенное наследование
- •18.3.4. Композиция объектов
- •18.4. Область видимости класса и наследование
- •18.5. Виртуальное наследование A
- •18.5.1. Объявление виртуального базового класса
- •18.5.2. Специальная семантика инициализации
- •18.5.3. Порядок вызова конструкторов и деструкторов
- •18.5.4. Видимость членов виртуального базового класса
- •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.3. Разрешение перегрузки и наследование A
- •19.3.1. Функции-кандидаты
- •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. Сильно типизированная библиотека
- •accumulate()
- •adjacent_difference()
- •adjacent_find()
- •binary_search()
- •copy()
- •copy_backward()
- •count_if()
- •equal()
- •equal_range()
- •fill()
- •find()
- •find_if()
- •find_end()
- •find_first_of()
- •generate()
- •generate_n()
- •includes()
- •inplace_merge()
- •iter_swap()
- •lexicographical_compare()
- •max_element()
- •merge()
- •next_permutation()
- •nth_element()
- •partial_sort()
- •partial_sort_copy()
- •partition()
- •prev_permutation()
- •random_shuffle()
- •remove()
- •remove_if()
- •remove_copy_if()
- •replace_copy()
- •replace_if()
- •replace_copy_if()
- •reverse_copy()
- •rotate()
- •search_n()
- •set_difference()
- •set_intersection()
- •set_union()
- •sort()
- •stable_partition()
- •swap()
- •swap_ranges()
- •transform()
- •unique_copy()
- •upper_bound()
- •Алгоритмы для работы с хипом
- •make_heap()
- •pop_heap()
- •push_heap()
- •sort_heap()
С++ для начинающих |
829 |
конкретизированный экземпляр. Точка конкретизации функции-члена или статического члена шаблона класса всегда следует непосредственно за объявлением или определением, которое ссылается на конкретизированный член.
В предыдущем примере точка конкретизации Queue<LongDouble> находится перед main(), и при разрешении зависящих от параметров имен, которые используются в определении шаблона Queue, компилятор просматривает все объявления до этой точки.
Аналогично при таком разрешении в определении remove() компилятор просматривает все объявления до точки конкретизации, расположенной после main().
Как отмечалось в разделе 16.2, шаблон конкретизируется, если он используется в контексте, требующем полного определения класса. Члены шаблона не конкретизируются автоматически вместе с ним, а лишь тогда, когда сами используются в программе.
Поэтому точка конкретизации шаблона класса может не совпадать с точками конкретизации его членов, да и сами члены могут конкретизироваться в разных точках. Чтобы избежать ошибок, объявления имен, упоминаемых в определениях шаблона и его членов, рекомендуется помещать в заголовочные файлы, включая их перед первой конкретизацией шаблона класса или любого из его членов.
16.12. Пространства имен и шаблоны классов
Как и любое определение в глобальной области видимости, определение шаблона класса можно поместить внутрь пространства имен. (Пространства имен рассматривались в разделах 8.5 и 8.6.) Наш шаблон будет скрыт в данном пространстве имен; лишь в этом отличие от ситуации, когда шаблон определен в глобальной области видимости. При употреблении вне пространства имя шаблона следует либо квалифицировать его именем,
#include <iostream> #include <cstdlib>
namespace cplusplus_primer {
template <class Type> class Queue { // ...
};
template <class Type> Type Queue<Type>::remove()
{
// ...
}
либо воспользоваться using-объявлением:
}
Если имя Queue шаблона класса используется вне пространства имен cplusplus_primer, то оно должно быть квалифицировано этим именем или введено с помощью using-объявления. Во всех остальных отношениях шаблон Queue используется так, как описано выше: конкретизируется, может иметь функции-члены, статические члены, вложенные типы и т.д. Например:
С++ для начинающих |
830 |
int main() { |
// using-объявление |
using cplusplus_primer Queue; |
//ссылается на шаблон класса в пространстве имен cplusplus_primer Queue<int> *p_qi = new Queue<int>;
//...
p_qi->remove();
}
Шаблон cplusplus_primer::Queue<int> конкретизируется, так как использован в выражении new:
... = new Queue<int>;
p_qi – это указатель на тип класса cplusplus_primer::Queue<int>. Когда он применяется для адресации функции-члена remove(), то речь идет о члене именно этого конкретизированного экземпляра класса.
Объявление шаблона класса в пространстве имен влияет также на объявления специализаций и частичных специализаций шаблона класса и его членов (см. разделы 16.9 и 16.10). Такая специализация должна быть объявлена в том же пространстве имен, где и общий шаблон.
В следующем примере в пространстве имен cplusplus_primer объявляются специализации типа класса Queue<char *> и функции-члена remove() класса
#include <iostream> #include <cstdlib>
namespace cplusplus_primer {
template <class Type> class Queue { ... };
template <class Type>
Type Queue<Type>::remove() { ... }
// объявление специализации
//для cplusplus_primer::Queue<char *> template<> class Queue<char*> { ... };
//объявление специализации
//для функции-члена cplusplus_primer::Queue<double>::remove()
template<> double Queue<double>::remove() { ... }
Queue<double>:
}
Хотя специализации являются членами cplusplus_primer, их определения в этом пространстве отсутствуют. Определить специализацию шаблона можно и вне пространства имен при условии, что определение будет находиться в некотором пространстве, объемлющем cplusplus_primer, и имя специализации будет квалифицировано его именем :
С++ для начинающих |
831 |
namespace cplusplus_primer
{
// определение Queue и его функций-членов
}
//объявление специализации
//cplusplus_primer::Queue<char*>
template<> class cplusplus_primer::Queue<char*> { ... };
//объявление специализации функции-члена
//cplusplus_primer::Queue<double>::remove()
template<> double cplusplus_primer::Queue<double>::remove()
{ ... }
Объявления специализаций класса cplusplus_primer::Queue<char*> и функции-члена remove() для класса cplusplus_primer::Queue<double> находятся в глобальной области видимости. Поскольку такая область содержит пространство имен cplusplus_primer, а имена специализаций квалифицированы его именем, то определения специализаций для шаблона Queue вполне законны.
16.13. Шаблон класса Array
В этом разделе мы завершим реализацию шаблона класса Array, введенного в разделе 2.5 (этот шаблон будет распространен на одиночное наследование в разделе 18.3 и на множественное наследование в разделе 18.6). Так выглядит полный заголовочный файл:
С++ для начинающих |
832 |
#ifndef ARRAY_H #define ARRAY_H #include <iostream>
template <class elemType> class Array; template <class elemType> ostream&
operator<<( ostream &, Array<elemType> & );
template <class elemType> class Array {
public:
explicit Array( int sz = DefaultArraySize ) { init( 0, sz ); }
Array( const elemType *ar, int sz ) { init( ar, sz ); }
Array( const Array &iA )
{ init( iA._ia, iA._size ); }
~Array() { delete[] _ia; }
Array & operator=( const Array & ); int size() const { return _size; }
elemType& operator[]( int ix ) const { return _ia[ix]; }
ostream &print( ostream& os = cout ) const; void grow();
void sort( int,int ); int find( elemType ); elemType min(); elemType max();
private:
void init( const elemType*, int ); void swap( int, int );
static const int DefaultArraySize = 12;
int _size; elemType *_ia;
};
#endif
Код, общий для реализации всех трех конструкторов, вынесен в отдельную функцию- член init(). Поскольку она не должна напрямую вызываться пользователями шаблона класса Array, мы поместили ее в закрытую секцию:
С++ для начинающих |
833 |
||
|
|
template <class elemType> |
|
|
|
|
|
|
|
void Array<elemType>::init( const elemType *array, int sz ) |
|
|
|
{ |
|
|
|
_size = sz; |
|
|
|
_ia = new elemType[ _size ]; |
|
|
|
for ( int ix = 0; ix < _size; ++ix ) |
|
|
|
if ( ! array ) |
|
|
|
_ia[ ix ] = 0; |
|
|
|
else _ia[ ix ] = array[ ix ]; |
|
|
|
} |
|
|
|
||
|
|
|
|
Реализация копирующего оператора присваивания не вызывает затруднений. Как |
|
||
|
|
template <class elemType> Array<elemType>& |
|
|
|
|
|
|
|
Array<elemType>::operator=( const Array<elemType> &iA ) |
|
|
|
{ |
|
|
|
if ( this != &iA ) { |
|
|
|
delete[] _ia; |
|
|
|
init( iA._ia, iA._size ); |
|
|
|
} |
|
|
|
return *this; |
|
отмечалось в разделе 14.7, в код включена защита от копирования объекта в самого себя: |
|
||
|
|
} |
|
|
|
||
|
|
|
|
Функция-член print() отвечает за вывод объекта того типа, которым конкретизирован |
|
||
шаблон Array. Возможно, реализация несколько сложнее, чем необходимо, зато данные |
|
||
аккуратно размещаются на странице. Если экземпляр конкретизированного класса |
|
||
Array<int> содержит элементы 3, 5, 8, 13 и 21, то выведены они будут так: |
|
(5) < 3, 5, 8, 13, 21 >
Оператор потокового вывода просто вызывает print(). Ниже приведена реализация обеих функций:
С++ для начинающих |
834 |
||
|
|
template <class elemType> ostream& |
|
|
|
|
|
|
|
operator<<( ostream &os, Array<elemType> &ar ) |
|
|
|
{ |
|
|
|
return ar.print( os ); |
|
|
|
} |
|
|
|
template <class elemType> |
|
|
|
ostream & Array<elemType>::print( ostream &os ) const |
|
|
|
{ |
|
|
|
const int lineLength = 12; |
|
|
|
os << "( " << _size << " )< "; |
|
|
|
for ( int ix = 0; ix < _size; ++ix ) |
|
|
|
{ |
|
|
|
if ( ix % lineLength == 0 && ix ) |
|
|
|
os << "\n\t"; |
|
|
|
os << _ia[ ix ]; |
|
|
|
// не выводить запятую за последним элементом в строке, |
|
|
|
// а также за последним элементом массива |
|
|
|
if ( ix % lineLength != lineLength-1 && ix != _size-1 ) |
|
|
|
os << ", "; |
|
|
|
} |
|
|
|
os << " >\n"; |
|
|
|
return os; |
|
|
|
} |
|
|
|
||
|
|
|
|
Вывод значения элемента массива в функции print() осуществляет такая инструкция: |
|
||
|
|
os << _ia[ ix ]; |
|
|
|
|
|
Для ее правильной работы должно выполняться требование к типам, которыми |
|
||
конкретизируется шаблон Array: такой тип должен быть встроенным либо иметь |
|
||
собственный оператор вывода. В противном случае любая попытка распечатать |
|
||
содержимое класса Array приведет к ошибке компиляции в том месте, где используется |
|
||
несуществующий оператор. |
|
||
Функция-член grow() увеличивает размер объекта класса Array. В нашем примере – в |
|
||
полтора раза: |
|
С++ для начинающих |
835 |
||
|
|
template <class elemType> |
|
|
|
|
|
|
|
void Array<elemType>::grow() |
|
|
|
{ |
|
|
|
elemType *oldia = _ia; |
|
|
|
int oldSize = _size; |
|
|
|
_size = oldSize + oldSize/2 + 1; |
|
|
|
_ia = new elemType[_size]; |
|
|
|
int ix; |
|
|
|
for ( ix = 0; ix < oldSize; ++ix ) |
|
|
|
_ia[ix] = oldia[ix]; |
|
|
|
for ( ; ix < _size; ++ix ) |
|
|
|
_ia[ix] = elemType(); |
|
|
|
delete[] oldia; |
|
|
|
} |
|
|
|
||
|
|
|
|
Функции-члены find(), min() и max() осуществляют последовательный поиск во |
|
||
внутреннем массиве _ia. Если бы массив был отсортирован, то, конечно, их можно было |
|
||
|
|
template <class elemType> |
|
|
|
|
|
|
|
elemType Array<elemType>::min( ) |
|
|
|
{ |
|
|
|
assert( _ia != 0 ); |
|
|
|
elemType min_val = _ia[0]; |
|
|
|
for ( int ix = 1; ix < _size; ++ix ) |
|
|
|
if ( _ia[ix] < min_val ) |
|
|
|
min_val = _ia[ix]; |
|
|
|
return min_val; |
|
|
|
} |
|
|
|
template <class elemType> |
|
|
|
elemType Array<elemType>::max() |
|
|
|
{ |
|
|
|
assert( _ia != 0 ); |
|
|
|
elemType max_val = _ia[0]; |
|
|
|
for ( int ix = 1; ix < _size; ++ix ) |
|
|
|
if ( max_val < _ia[ix] ) |
|
|
|
max_val = _ia[ix]; |
|
|
|
return max_val; |
|
|
|
} |
|
|
|
template <class elemType> |
|
|
|
int Array<elemType>::find( elemType val ) |
|
|
|
{ |
|
|
|
for ( int ix = 0; ix < _size; ++ix ) |
|
|
|
if ( val == _ia[ix] ) |
|
|
|
return ix; |
|
|
|
return -1; |
|
бы реализовать гораздо эффективнее. |
|
||
|
|
} |
|
|
|
||
|
|
|
|
С++ для начинающих |
836 |
В шаблоне класса Array есть функция-член sort(), реализованная с помощью алгоритма быстрой сортировки. Она очень похожа на шаблон функции, представленный в разделе 10.11. Функция-член swap() – вспомогательная утилита для sort(); она не
template <class elemType>
void Array<elemType>::swap( int i, int j )
{
elemType tmp = _ia[i]; _ia[i] = _ia[j]; _ia[j] = tmp;
}
template <class elemType>
void Array<elemType>::sort( int low, int high )
{
if ( low >= high ) return; int lo = low;
int hi = high + 1; elemType elem = _ia[low];
for ( ;; ) {
while ( _ia[++lo] < elem ) ; while ( _ia[--hi] > elem ) ; if ( lo < hi )
swap( lo,hi ); else break;
}
swap( low, hi ); sort( low, hi-1 ); sort( hi+1, high );
является частью открытого интерфейса шаблона и потому помещена в закрытую секцию:
}
То, что код реализован, разумеется, не означает, что он работоспособен. try_array() – это шаблон функции, предназначенный для тестирования реализации шаблона Array:
С++ для начинающих |
837 |
#include "Array.h"
template <class elemType>
void try_array( Array<elemType> &iA )
{
cout << "try_array: начальные значения массива\n"; cout << iA << endl;
elemType find_val = iA [ iA.size()-1 ]; iA[ iA.size()-1 ] = iA.min();
int mid = iA.size()/2; iA[0] = iA.max(); iA[mid] = iA[0];
cout << "try_array: после присваиваний\n"; cout << iA << endl;
Array<elemType> iA2 = iA; iA2[mid/2] = iA2[mid];
cout << "try_array: почленная инициализация\n"; cout << iA << endl;
iA = iA2;
cout << "try_array: после почленного копирования\n"; cout << iA << endl;
iA.grow();
cout << "try_array: после вызова grow\n"; cout << iA << endl;
int index = iA.find( find_val );
cout << "искомое значение: " << find_val;
cout << "\tвозвращенный индекс: " << index << endl;
elemType value = iA[index];
cout << "значение элемента с этим индексом: "; cout << value << endl;
}
Рассмотрим шаблон функции try_array(). На первом шаге печатается исходный объект Array, что подтверждает успешную конкретизацию оператора вывода шаблона, а заодно дает начальную картину, с которой можно будет сверяться при последующих модификациях. В переменной find_val хранится значение, которое мы впоследствии передадим find(). Если бы try_array() была обычной функцией, роль такого значения сыграла бы константа. Но поскольку никакая константа не может обслужить все типы, которыми допустимо конкретизировать шаблон, то приходится выбирать другой путь.
Далее одним элементам Array случайным образом присваиваются значения других элементов, чтобы протестировать min(), max(), size() и, конечно, оператор взятия индекса.
Затем объект iA2 почленно инициализируется объектом iA, что приводит к вызову копирующего конструктора. После этого тестируется оператор взятия индекса с объектом ia2: производится присваивание элементу с индексом mid/2. (Эти две строки представляют интерес в случае, когда iA – производный подтип Array, а оператор взятия индекса объявлен виртуальной функцией. Мы вернемся к этому в главе 18 при обсуждении наследования.) Далее в iA почленно копируется модифицированный объект iA2, что приводит к вызову копирующего оператора присваивания класса Array. Затем проверяются функции-члены grow() и find(). Напомним, что find() возвращает значение –1, если искомый элемент не найден. Попытка выбрать из “массива” Array
С++ для начинающих |
838 |
элемент с индексом –1 приведет к выходу за левую границу. (В главе 18 для перехвата этой ошибки мы построим производный от Array класс, который будет проверять выход за границы массива.)
Убедиться, что наша реализация шаблона работает для различных типов данных, например целых чисел, чисел с плавающей точкой и строк, поможет программа main(),
#include "Array.C" #include "try_array.C" #include <string>
int main()
{
static int ia[] = { 12,7,14,9,128,17,6,3,27,5 }; static double da[] = { 12.3,7.9,14.6,9.8,128.0 }; static string sa[] = {
"Eeyore", "Pooh", "Tigger",
"Piglet", "Owl", "Gopher", "Heffalump"
};
Array<int> iA( ia, sizeof(ia)/sizeof(int) ); Array<double> dA( da, sizeof(da)/sizeof(double) ); Array<string> sA( sa, sizeof(sa)/sizeof(string) );
cout << "template Array<int> class\n" << endl; try_array(iA);
cout << "template Array<double> class\n" << endl; try_array(dA);
cout << "template Array<string> class\n" << endl; try_array(sA);
return 0;
которая вызывает try_array() с каждым из указанных типов:
}
Вот что программа выводит при конкретизации шаблона Array типом double:
try_array: начальные значения массива
( 5 )< 12.3, |
7.9, |
14.6, |
9.8, 128 > |
|
|
try_array: после присваиваний |
|
||||
( 5 |
)< 14.6, |
7.9, 14.6, |
9.8, 7.9 > |
|
|
try_array: почленная инициализация |
|
||||
( 5 |
)< 14.6, |
7.9, 14.6, |
9.8, 7.9 > |
|
|
try_array: после почленного копирования |
|||||
( 5 |
)< 14.6, |
14.6, 14.6, 9.8, 7.9 > |
|
||
try_array: после вызова |
grow |
0, 0, 0 > |
|||
( 8 |
)< 14.6, |
14.6, 14.6, 9.8, 7.9, |
|||
искомое значение: 128 |
возвращенный индекс: -1 |
||||
значение элемента с этим индексом: |
3.35965e-322 |
Выход индекса за границу массива приводит к тому, что последнее напечатанное программой значение неверно. Конкретизация шаблона Array типом string заканчивается крахом программы:
С++ для начинающих |
839 |
template Array<string> class |
||
try_array: начальные |
значения массива |
|
( 7 |
)< Eeyore, Pooh, |
Tigger, Piglet, Owl, Gopher, Heffalump > |
try_array: после присваиваний |
||
( 7 |
)< Tigger, Pooh, |
Tigger, Tigger, Owl, Gopher, Eeyore > |
try_array: почленная |
инициализация |
|
( 7 |
)< Tigger, Pooh, |
Tigger, Tigger, Owl, Gopher, Eeyore > |
try_array: после |
почленного копирования |
||
( 7 )< Tigger, Tigger, Tigger, Tigger, Owl, Gopher, Eeyore > |
|||
try_array: после |
вызова |
grow |
|
( 11 )< Tigger, Tigger, |
Tigger, Tigger, Owl, Gopher, Eeyore, <пусто>, <пусто>, |
||
<пусто>, <пусто> |
> |
|
|
искомое значение: Heffalump |
возвращенный индекс: -1 |
||
Memory fault (coredump) |
|
|
Упражнение 16.11
Измените шаблон класса Array, убрав из него функции-члены sort(), find(), max(), min() и swap(), и модифицируйте шаблон try_array() так, чтобы она вместо них пользовалась обобщенными алгоритмами (см. главу 12).