Полезное:
Как сделать разговор полезным и приятным
Как сделать объемную звезду своими руками
Как сделать то, что делать не хочется?
Как сделать погремушку
Как сделать так чтобы женщины сами знакомились с вами
Как сделать идею коммерческой
Как сделать хорошую растяжку ног?
Как сделать наш разум здоровым?
Как сделать, чтобы люди обманывали меньше
Вопрос 4. Как сделать так, чтобы вас уважали и ценили?
Как сделать лучше себе и другим людям
Как сделать свидание интересным?
Категории:
АрхитектураАстрономияБиологияГеографияГеологияИнформатикаИскусствоИсторияКулинарияКультураМаркетингМатематикаМедицинаМенеджментОхрана трудаПравоПроизводствоПсихологияРелигияСоциологияСпортТехникаФизикаФилософияХимияЭкологияЭкономикаЭлектроника
|
Полиморфизм. Перегрузка функций.
полиморфизм — возможность использовать в различных классах иерархии одно имя для обозначения сходных по смыслу действий и гибко выбирать требуемое действие во время выполнения программы Понятие полиморфизма используется в C++ весьма широко. Простым примером полиморфизма может перегрузка функций, когда из нескольких вариантов выбирается наиболее подходящая функция по соответствию ее прототипа передаваемым параметрам. Другой пример — использование шаблонов функций , когда один и тот же код видоизменяется в соответствии с типом, переданным в качестве параметра. Чаще всего понятие полиморфизма связывают с механизмом виртуальных методов. Очевидно, что это неудобно и неестественно. q В случае, когда разные функции осуществляют похожие действия над данными разных типов, удобно присвоить им одинаковое имя. q Пример: возведение в квадрат. Язык C++ позволяет создавать функции с одинаковым именем. Можно написать так: int Square(int x) { return x * x; }; double Square(double x) { return x*x; }; float Square(float x) { return x * x; }; … Очевидно, что это удобно и естественно. Перегрузка функций – использование одного и того же идентификатора (имени) для именования разных функций, совершающих семантически сходные действия над аргументами разных типов. q Для того, чтобы определить, какую из перегруженных функций необходимо вызвать, производится проверка соответствия списка параметров при вызове функции и списка параметров при объявлении функции. int Square(int x) { return x * x; }; double Square(double x) { return x*x; }; float Square(float x) { return x * x; }; … double a = 1; double d = Square(a); Алгоритм выбора перегруженной функции: для обнаружения соответствия используется понятие “сигнатура”. Сигнатура – список типов параметров функции. int SquareI(int x) { return x * x; }; // сигнатура “int” double SquareD(double x) { return x * x; }; // сигнатура “double” float SquareF(float x) { return x * x; }; // сигнатура “float” Алгоритм выбора перегруженной функции действует так: q ищет точное соответствие сигнатуре, если оно существует; q выполняет стандартные преобразования типов одного ранга (char->int, int, float->double); q выполняет стандартные преобразования типов (расширения int -> double и т.д.); q выполняет преобразования типов, определенные пользователем (при перегрузке приведения типа); q работа со значениями по умолчанию. Необходимо избегать двусмысленностей. q Компилятор должен суметь установить соответствие между вызовом перегруженной функции и конкретной сигнатурой. q Тип возвращаемого значения не входит в сигнатуру и, следовательно, не влияет на процесс установления соответствия. Реализации: int Square(int x) { return x * x; }; double Square(int x) { return x * x; }; неотличимы Перегрузка функций – специальный полиморфизм. Вернемся к исходной задаче, но посмотрим на нее под другим углом зрения. q Мы научились давать одинаковые имена функциям, осуществляющим сходные действия над значениями разных типов. q А что, если действия не просто сходные, а абсолютно одинаковые? В примере с возведением в квадрат дело обстоит именно так. q Задача: не размножать один и тот же текст программы многократно для разных типов параметров. Есть ли такие средства? Да, это макрос: #define SUM(x1, x2) x1 + x2 … int a = 1, b = 2, c = 3; int d = SUM(a, b); // d = 3, все хорошо int d = SUM(a, b) * с; // d = 7, вместо d=9 //a + b * c Рисуем скобки: #define SUM(x1, x2) (x1 + x2) Тогда: #define SQUARE(x) (x * x) … int a = 1, b = 2, c = 3; int d = SQUARE(c); // d = 9, все хорошо int d = SQUARE(c + 1); // d = 7, все плохо с + 1 * с + 1 Рисуем еще скобки: #define SQUARE(x) ((x) * (x)) Тогда: #define SQUARE(x) ((x) * (x)) … int c = SQUARE(x++); //((x++) * (x++)) Побочный эффект: двойное увеличение на 1. А вот такую проблему уже победить не удастся.
Шаблоны функций. Шаблоны, которые называют иногда родовыми или параметризованными типами, позволяют создавать (конструировать) семейства родственных функций и классов. Цель введения шаблонов функций - автоматизация создания функций, которые могут обрабатывать разнотипные данные. В отличие от механизма перегрузки, когда для каждой сигнатуры определяется своя функция, шаблон семейства функций определяется один раз, но это определение параметризуется. Параметризовать в шаблоне функций можно тип возвращаемого функцией значения и типы любых параметров, количество и порядок размещения которых должны быть фиксированы. Для параметризации используется список параметров шаблона. В определении шаблона семейства функций используется служебное слово template. Для параметризации используется список формальных параметров шаблона, который заключается в угловые скобки <>. Каждый формальный параметр шаблона обозначается служебным словом class, за которым следует имя параметра (идентификатор). Пример определения шаблона функций, вычисляющих абсолютные значения числовых величин разных типов: template<class type> type abs(type x) Описание шаблона семейства функций состоит из двух частей: template<class тип_данных> тип_возвр_значения имя_функции(список_параметров) {тело_функции} В качестве еще одного примера рассмотрим шаблон семейства функций для обмена значений двух передаваемых им параметров. template<class T> void swap(T *x, T *y) { Здесь параметр T шаблона функций используется не только в заголовке для спецификации формальных параметров, но и в теле определения функции, где он задает тип вспомогательной переменной z. Шаблон семейства функций служит для автоматического формирования конкретных определений функций по тем вызовам, которые транслятор обнаруживает в теле программы. Например, если программист употребляет обращение abs(-10.3), то на основе приведенного ранее шаблона компилятор сформирует такое определение функции: double abs(double x) Далее будет организовано выполнение именно этой функции и в точку вызова в качестве результата вернется числовое значение 10.3. Если в программе присутствует приведенный ранее шаблон семейства функций swap() long k = 4, d = 8; то компилятор сформирует определение функции: void swap(long *x, long *y) Затем будет выполнено обращение именно к этой функции и значения переменных k, d поменяются местами. Если в той же программе присутствуют операторы: double a = 2.44, b = 66.3; то сформируется и выполнится функция void swap(double *x, double *y) По существу механизм шаблонов функций позволяет автоматизировать подготовку переопределений перегруженных функций. При использовании шаблонов уже нет необходимости готовить заранее все варианты функций с перегруженным именем. Компилятор автоматически, анализируя вызовы функций в тексте программы, формирует необходимые определения именно для таких типов параметров, которые использованы в обращениях. Дальнейшая обработка выполняется так же, как и для перегруженных функций.
Шаблоны классов Аналогично шаблонам функций определяется шаблон семейства классов: template<список_параметров_шаблона> определение_класса Шаблон семейства классов определяет способ построения отдельных классов подобно тому, как класс определяет правила построения и формат отдельных объектов. В определении класса, входящего в шаблон, особую роль играет имя класса. Оно является не именем отдельного класса, а параметризованным именем семейства классов. Как и для шаблонов функций, определение шаблона класса может быть только глобальным. Следуя авторам языка и компилятора С++, рассмотрим векторный класс (в число данных входит одномерный массив). Какой бы тип ни имели элементы массива (целый, вещественный, с двойной точностью и т.д.), в этом классе должны быть определены одни и те же базовые операции, например доступ к элементу по индексу и т.д. Если тип элементов вектора задавать как параметр шаблона класса, то система будет формировать вектор нужного типа (и соответствующий класс) при каждом определении конкретного объекта. Следующий шаблон автоматически формирует классы векторов с указанными свойствами: // vector.h - шаблон векторов ~Vector() // Деструктор // Расширение действия (перегрузка) операции "[]": protected: // vector.cpp Когда шаблон введен, у программиста появляется возможность определять конкретные объекты конкретных классов, каждый из которых параметрически порожден из шаблона. Формат определения объекта одного из классов, порождаемых шаблоном классов: имя_параметризованного_класса <фактические_параметры_шаблона> Date: 2016-07-25; view: 540; Нарушение авторских прав |