Главная Случайная страница


Полезное:

Как сделать разговор полезным и приятным Как сделать объемную звезду своими руками Как сделать то, что делать не хочется? Как сделать погремушку Как сделать так чтобы женщины сами знакомились с вами Как сделать идею коммерческой Как сделать хорошую растяжку ног? Как сделать наш разум здоровым? Как сделать, чтобы люди обманывали меньше Вопрос 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)
{
return x > 0? x: -x;
}

Описание шаблона семейства функций состоит из двух частей:

template<class тип_данных> тип_возвр_значения имя_функции(список_параметров)

{тело_функции}

В качестве еще одного примера рассмотрим шаблон семейства функций для обмена значений двух передаваемых им параметров.

template<class T> void swap(T *x, T *y)

{
T z = *x;
*x = *y;
*y = x;
}

Здесь параметр T шаблона функций используется не только в заголовке для спецификации формальных параметров, но и в теле определения функции, где он задает тип вспомогательной переменной z.

Шаблон семейства функций служит для автоматического формирования конкретных определений функций по тем вызовам, которые транслятор обнаруживает в теле программы. Например, если программист употребляет обращение abs(-10.3), то на основе приведенного ранее шаблона компилятор сформирует такое определение функции:

double abs(double x)
{
return x > 0? x: -x;
}

Далее будет организовано выполнение именно этой функции и в точку вызова в качестве результата вернется числовое значение 10.3.

Если в программе присутствует приведенный ранее шаблон семейства функций swap()

long k = 4, d = 8;
swap(&k, &d);

то компилятор сформирует определение функции:

void swap(long *x, long *y)
{
long x = *x;
*x = *y;
*y = x;
}

Затем будет выполнено обращение именно к этой функции и значения переменных k, d поменяются местами.

Если в той же программе присутствуют операторы:

double a = 2.44, b = 66.3;
swap(&a, &b);

то сформируется и выполнится функция

void swap(double *x, double *y)
{
double x = *x;
*x = *y;
*y = x;
}

По существу механизм шаблонов функций позволяет автоматизировать подготовку переопределений перегруженных функций. При использовании шаблонов уже нет необходимости готовить заранее все варианты функций с перегруженным именем. Компилятор автоматически, анализируя вызовы функций в тексте программы, формирует необходимые определения именно для таких типов параметров, которые использованы в обращениях. Дальнейшая обработка выполняется так же, как и для перегруженных функций.

 

 

Шаблоны классов

Аналогично шаблонам функций определяется шаблон семейства классов:

template<список_параметров_шаблона> определение_класса

Шаблон семейства классов определяет способ построения отдельных классов подобно тому, как класс определяет правила построения и формат отдельных объектов. В определении класса, входящего в шаблон, особую роль играет имя класса. Оно является не именем отдельного класса, а параметризованным именем семейства классов.


Как и для шаблонов функций, определение шаблона класса может быть только глобальным.

Следуя авторам языка и компилятора С++, рассмотрим векторный класс (в число данных входит одномерный массив). Какой бы тип ни имели элементы массива (целый, вещественный, с двойной точностью и т.д.), в этом классе должны быть определены одни и те же базовые операции, например доступ к элементу по индексу и т.д. Если тип элементов вектора задавать как параметр шаблона класса, то система будет формировать вектор нужного типа (и соответствующий класс) при каждом определении конкретного объекта.

Следующий шаблон автоматически формирует классы векторов с указанными свойствами:

// vector.h - шаблон векторов
template<class T> // T - параметр шаблона
class Vector
{
public:
Vector(int); // Конструктор класса vector

~Vector() // Деструктор
{
delete [] data;
}

// Расширение действия (перегрузка) операции "[]":
T &operator [](int i)
{
return data [i];
}

protected:
T *data; // Начало одномерного массива
int size; // Количество элементов в массиве
};

// vector.cpp
// Внешнее определение конструктора класса:
template<class T> Vector <T>::Vector(int n)
{
data = new T[n];
size = n;
};

Когда шаблон введен, у программиста появляется возможность определять конкретные объекты конкретных классов, каждый из которых параметрически порожден из шаблона. Формат определения объекта одного из классов, порождаемых шаблоном классов:

имя_параметризованного_класса <фактические_параметры_шаблона>







Date: 2016-07-25; view: 540; Нарушение авторских прав



mydocx.ru - 2015-2024 year. (0.011 sec.) Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав - Пожаловаться на публикацию