Лекции_ООП_ИС. Курс лекций по объектноориентированному программированию
Скачать 0.58 Mb.
|
row() { delete [] data; };7. Параметризованные типы и функции. (2 час.)Шаблоны функций. Параметры шаблонов. Шаблоны классов. Наследование и шаблоны. Шаблоны функций. Шаблоны, которые называют иногда родовыми или параметризоавнными типами, позволяют создавать (конструировать) семейства родственных функций и классов. Цель введения шаблонов функций - автоматизация создания функций, которые могут обрабатывать разнотипные данные. В отличие от механизма перегрузки, когда для каждого набора формальных параметров определяется своя функция, шаблон семейства функций определяется один раз, но это определение параметризуется. Параметризовать в шаблоне функций можно тип возвращаемого функцией значения и типы любых параметров, количество и порядок размещения которых должны быть фиксированы. Для параметризации используется список параметров шаблона. В определении шаблона семейства функций используется служебное слово template. Для параметризации используется список формальных параметров шаблона, который заключается в угловые скобки <>. Каждый формальный параметр шаблона обозначается служебным словом class, за которым следует имя параметра (идентификатор). Пример определения шаблона функций, вычисляющих абсолютные значения числовых величин разных типов: template type abs(type x) { return x > 0 ? x: -x; }; Шаблон семейства функций состоит из двух частей - заголовка шаблона: template <список_параметров_шаблона> и из обыкновенного определения функции, в котором тип возвращаемого значения и типы любых параметров обозначаются именами параметров шаблона, введенных в его заголовке. Те же имена параметров шаблона могут использоваться и в теле определения функции для обозначения типов локальных объектов. В качестве еще одного примера рассмотрим шаблон семейства функций для обмена значений двух передаваемых им параметров. template 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; }; Проиллюстрируем сказанное о шаблонах на более конкретном примере. Рассмотрим программу, используем некоторые возможности функций, возвращающих значение типа «ссылка». Но тип ссылки будет определяться параметром шаблона: #include //Функция определяет ссылку на элемент с максимальным значением template type& rmax(int n, type d[]) { int im = 0; for (int i = 1; i < n; i++) im = d[im] > d[i] ? im : i; return d[im]; }; void main() { int n = 4; int x[] = { 10, 20, 30, 14}; //Массив целых чисел cout << "\nrmax(n,x) = " << rmax (n,x); // rmax(n,x) = 30 rmax(n,x) = 0; for (int i = 0; i < n; i++) cout << "\tx[" << i << "] =" << x[i]; // x[0] = 10 x[1] ... float arx[] = { 10.3, 20.4, 10.5}; //Массив вещественных чисел cout << "\nrmax(3,arx) = " << rmax (3,arx); //rmax(3,arx) = 20.4 rmax(3, arx) = 0; for (int i = 0; i < 3; i++) cout << "\tarx[" << i << "] =" << arx[i]; //arx[0] = 10.3 ... }; В программе используются два разных обращения к функции rmax(). В одном случае параметр - целочисленный массив и возвращаемое значение - ссылка типа int. Во втором случае фактический параметр - имя массива типа floatи возвращаемое значение имеет тип ссылки на float. По существу механизм шаблонов функций позволяет автоматизировать подготовку переопределений перегруженных функций. При использовании шаблонов уже нет необходимости готовить заранее все варианты функций с перегруженным именем. Компилятор, автоматически анализируя вызовы функций в тексте программы, формирует необходимые определения именно для таких типов параметров, которые использованы в обращениях. Дальнейшая обработка выполняется так же, как и для перегруженных функций. Параметры шаблонов. Можно считать, что параметры шаблона являются его формальными параметрами, а типы тех параметров, которые используются в конкретных обращениях к функции, служат фактическими параметрами шаблона. Именно по ним выполняется параметрическая настройка и с учетом этих типов генерируется конкретный текст определения функции. Однако, говоря о шаблоне семейства функций, обычно употребляют термин «список параметров шаблона», не добавляя определения «формальных». Перечислим основные свойства параметров шаблона:
template Соответственно, неверен заголовок: template
#include int N = 0; //статическая, инициализирована нулем template N max(N x, N y) { N a = x; cout << "\nСчетчик обращений N = " << ++::N; if (a < y) a = y; return a: }; void main() { int a = 12, b = 42; max(a,b); //Счетчик обращений N = 1 float z = 66.3, f = 222.4; max(z,f); //Счетчик обращений N = 2 }; Итак, одно имя нельзя использовать для обозначения нескольких параметров одного шаблона, но в разных шаблонах функций могут быть одинаковые имена у параметров шаблонов. Ситуация здесь такая же, как и у формальных параметров при определении обычных функций, и на ней можно не останавливаться подробнее. Действительно, раз действие параметра шаблона заканчивается в конце определения шаблона, то соответствующий идентификатор свободен для последующего использования, в том числе и в качестве имени параметра другого шаблона. Все параметры шаблона функций должны быть обязательно использованы в спецификациях параметров определения функции. Таким образом, будет ошибочным такой шаблон: template B func (A n, C m) {B value; ... }; В данном неверном примере остался неиспользованным параметр шаблона с именем B. Его применение в качестве типа возвращаемого функцией значения и для определения объекта value в теле функции недостаточно. Определяемая с помощью шаблона функция может иметь любое количество непараметризованных формальных параметров. Может быть непараметризовано и возвращаемое функцией значение. Например, в следующей программе шаблон определяет семейство функций, каждая из которых подсчитывает количество нулевых элементов одномерного массива параметризованного типа: #include template long count0(int, D*); //Прототип шаблона void main() { int A[] = {1,0,6,0,4,10}; int n = sizeof(A)/sizeof A[0]; cout << "\ncount0(n,A) = " << count0(n,A); float X[] = {10.0, 0.0, 3.3, 0.0, 2.1}; n = sizeof(X)/sizeof X[]; cout << "\ncount0(n,X) = " << count0(n,X); }; template long count0(int size, T* array) { long k = 0l; for (int i = 0; i < size; i++) if (int(array[i]) == 0) k++; return k; }; В шаблоне функций count0 параметр T используется только в спецификации одного формального параметра array. Параметр size и возвращаемое функцией значение имеют явно заданные непараметризованные типы. Как и при работе с обычными функциями, для шаблонов функций существуют определения и описания. В качестве описания шаблона функций используется прототип шаблона: template < список_ параметров_ шаблона > В списке параметров прототипа шаблона имена параметров не обязаны совпадать с именами тех же параметров в определении шаблона. Это и продемонстрировано в программе. При конкретизации шаблонного определения функции необходимо, чтобы при вызове функции типы фактических параметров, соответствующие одинаково параметризованным формальным параметрам, были одинаковыми. Для определенного выше шаблона функций с прототипом template < class E > void swap (E,E); недопустимо использовать такое обращение к функции: int n = 4; double d = 4.3; swap (n,d); // Ошибка в типах параметров Для правильного обращения к такой функции требуется явное приведение типа одного из параметров. Например, вызов swap (double (n) , d); // Правильные типы параметров приведет к конкретизации шаблонного определения функций с параметром типаdouble. При использовании шаблонов функций возможна перегрузка, как шаблонов, так и функций. Могут быть шаблоны с одинаковыми именами, но разными параметрами. Или с помощью шаблона может создаваться функция с таким же именем, что и явно определенная функция. В обоих случаях «распознавание» конкретного вызова выполняется по сигнатуре, т.е. по типам, порядку и количеству фактических параметров. Шаблоны классов. Аналогично шаблонам функций. определяется шаблон семейства классов: template <список_параметров_шаблона> определение_класса Шаблон семейства классов определяет способ построения отдельных классов подобно тому, как класс определяет правила построения и формат отдельных объектов. В определении класса, входящего в шаблон, особую роль играет имя класса. Оно является не именем отдельного класса, а параметризованным именем семейства классов. Как и для шаблонов функций, определение шаблона класса может быть только глобальным. Следуя авторам языка и компилятора Си++, рассмотрим векторный класс (в число данных входит одномерный массив). Какой бы тип ни имели элементы массива (целый, вещественный, с двойной точностью и т.д.), в этом классе должны быть определены одни и те же базовые операции, например доступ к элементу по индексу и т.д. Если тип элементов вектора задавать как параметр шаблона класса, то система будет формировать вектор нужного типа (и соответствующий класс) при каждом определении конкретного объекта. Следующий шаблон автоматически формирует классы векторов с указанными свойствами: // TEMPLATE.VEC - шаблон векторов template class Vector { T *data; // Начало одномерного массива int size; // Количество элементов в иассиве public: Vector (int); // Конструктор класса vector |