Ввод/вывод для типов данных, определенных пользователем


Реальное преимущество потоков С++ заключается в легкости, с которой можно совместить >> и << для управления вводом/выводом пользовательских типов данных. Рассмотрим простую структуру данных, которую можно объявить:

struct emp{ char *name; int dept; long sales;};

Чтобы совместить << для вывода объектов типа emp, необходимо следующее определение:

ostream& operator << (ostream& str, emp& e){ str << setw(25) << e.name << ": Отдел " << setw(6) << e.dept << tab << "З.П. " << e.sales << " р." << '\n'; return str;}

Функция-оператор << должна возвращать ostream&, ссылку на ostream, так что можно подсоединить новую функцию << подобно предварительно определенному оператору вставки. Теперь можно вывести объекты типа emp следующим образом:

#include <iostream.h>#include <iomanip.h>...emp jones = {"Иванов", 25, 1000};cout << jones;

получив вывод на дисплей

Иванов: Отдел 25 З.П. 1000 р.

Манипулятор tab в определении << определен пользователем:

ostream& tab(ostream& str){ return str << '\t';}

Шаблоны функций

Рассмотрим простую функцию, реализующую алгоритм сравнения двух величин:

int minimum (int iVal_1, int iVal_2){ return iVal_1 < iVal_2 ? iVal_1 : iVal_2; /* Возвращается значение iVal_1, если это значение меньше iVal_2. В противном случае возвращается значение iVal_2. */}

Для каждого типа сравниваемых величин должен быть определен собственный вариант функции min(). Вот как эта функция выглядит для float:

float minimum (float fVal_1, float fVal_2){ return fVal_1 < fVal_2 ? fVal_1 : fVal_2;}

А для double… А для…

И так для всех используемых в программе типов сравниваемых величин. Мы можем бесконечно упражняться в создании совместно используемых функций, хотя можно воспользоваться средствами препроцессирования:

#define minimum (a,b) ((a)<(b)?(a):(b))

Это определение правильно работает в простых случаях:

minimum (10, 20);minimum (10.0, 20.0);

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

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

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

Объявление и определение шаблона функции начинается ключевым словом template, за которым следует заключенный в угловые скобки и разделенный запятыми непустой список параметров шаблона. Эта часть объявления или определения обычно называется заголовком шаблона.

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

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

Шаблон функции служит инструкцией для транслятора. По этой инструкции транслятор может самостоятельно построить определение новой функции.

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

В качестве примера рассмотрим программу, в которой для определения минимального значения используется шаблон функции min().

template <class Type> Type minimum (Type a, Type b);/*Прототип шаблона функции.Ключевое слово template обозначает начало списка параметров шаблона. Этот список содержит единственный идентификатор Type. Сама функция содержит два объявления шаблонных параметра, специфицированных шаблоном параметра Type.Спецификация возвращаемого значения также представлена шаблоном параметра Type.*/int main (void){ minimum (10,20);// int minimum (int, int); minimum (10.0,20.0);// float minimum (float, float); /* Вызовы шаблонной функции. Тип значений параметров определен. На основе выражения вызова (транслятор должен распознать тип параметров) и определения шаблона транслятор самостоятельно строит различные определения шаблонных функций. И только после этого обеспечивает передачу управления новорожденной шаблонной функции. */ return 1;}template <class Type>Type minimum (Type a, Type b){ return a < b ? a : b;}/*По аналогии с определением функции, эту конструкцию будем называтьопределением шаблона функции.*/

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

В результате конкретизации шаблона функции min() транслятор строится следующий вариант программы с двумя шаблонными функциями. По выражению вызова на основе шаблона строится шаблонная функция. Следует обратить внимание на то, что шаблон функции и шаблонная функция — два разных понятия.

int minimum (int a, int b);float minimum (float a, float b);int main (void){ minimum(10, 20); minimum(10.0, 20.0); return 1;}int minimum(int a, int b){ return a < b ? a : b;}float minimum(float a, float b){ return a < b ? a : b;}

Построение шаблонной функции осуществляется на основе выражений вызова. При этом в качестве значений параметров в выражении вызова могут быть использованы значения любых типов, для которых определены используемые в теле функции операции. Так, для функции minimum() тип параметров зависит от области определения операции сравнения <.

Типы формального параметра шаблона и значения параметра выражения вызова сопоставляются без учета каких-либо модификаторов типа. Например, если параметр шаблона в определении функции объявлен как

template <class Type>Type minimum(Type *a, Type *b){ return *a < *b ? *a : *b;}и при этом вызов функции имеет вид:int a = 10, b = 20;int *pa = &a, *pb = &b;minimum(pa, pb);

то в процессе конкретизации идентификатор типа Type будет замещен именем производного типа int:

int minimum(int *a, int *b){ return a < b ? a : b;}

В процессе конкретизации недопустимы расширения типов и другие преобразования типов параметров:

template <class Type>Type minimum(Type a, Type b){ return a < b ? a : b;}unsigned int a = 10;:::::minimum(1024,a);/*Здесь транслятор сообщит об ошибке. В вызове функции тип второго фактического параметра модифицирован по сравнению с типом первого параметра - int и unsigned int. Это недопустимо. В процессе построения новой функции транслятор не распознает модификации типов. В вызове функции типы параметров должны совпадать. Исправить ошибку можно с помощью явного приведения первого параметра.*/min((unsigned int)1024,a);:::::

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

Рассмотрим пример с классом ComplexType. На множестве комплексных чисел определены лишь два отношения: равенства (предполагает одновременное равенство действительной и мнимой частей) и неравенства (предполагает все остальные случаи). В нашей новой программе мы объявим и определим шаблон функции neq(), которая будет проверять на неравенство значения различных типов.

Для того, чтобы построить шаблонную функцию neq() для комплексных чисел, нам придется дополнительно определить операторную функцию-имитатор операции != для объектов-представителей множества комплексных чисел. Это важно, поскольку операция != явным образом задействована в шаблоне neq(). Транслятор не поймет, как трактовать символ !=, а, значит, и как строить шаблонную функцию neq(ComplexType, ComplexType), если эта операторная функция не будет определена для класса ComplexType.

#include <iostream.h>template <class Type>int neq (Type, Type); /*Прототип шаблона функции.*/class ComplexType{public: double real; double imag;// Конструктор умолчания. ComplexType(double re = 0.0, double im = 0.0) {real = re; imag = im;}/*Операторная функция != . Без нее невозможно построение шаблонной функции neq() для комплексных чисел.*/ int operator != (ComplexType &KeyVal) { return !(real == KeyVal.real && imag == KeyVal.imag); }};void main (){ // Определены и проинициализированы переменные трех типов. int i = 1, j = 2; float k = 1.0, l = 2.0; ComplexType CTw1(1.0,1.0), CTw2(2.0,2.0); //На основе выражений вызова транслятор строит три шаблонных функции. cout << "neq() for int:" << neq(i,j) << endl; cout << "neq() for float:" << neq(k,l) << endl; cout << "neq() for ComplexType:" << neq(CTw1, CTw2) << endl;}/*Определение шаблона функции.*/template <class Type>int neq (Type a, Type b){ return a != b; }

И еще один пример. Этот пример подтверждает обязательность включения всех параметров шаблона в список параметров шаблона определяемой функции. Независимо от того, какая роль предназначается шаблонному параметру (он вообще может не использоваться в шаблонной функции), его присутствие в списке параметров обязательно. В процессе построения шаблонной функции транслятор модифицирует весь шаблон полностью — его заголовок и его тело. Так что в теле шаблона можно объявлять переменные, специфицированные параметрами шаблона.

#include <iostream.h>#include <typeinfo.h> /*В программе используется объект класса Type_info, позволяющий получать информацию о типе. Здесь подключается заголовочный файл, содержащий объявление этого класса */template <class YYY, class ZZZ> YYY Tf (ZZZ, YYY, int);/*Шаблон прототипа функции. Функция Tf возвращает значение пока еще неопределенного типа, обозначенного параметром шаблона YYY. Список ее параметров представлен двумя (всеми!) параметрами шаблона и одним параметром типа int.*/void main(){ cout << Tf((int) 0, '1', 10) << endl;/*Собственно эти вызовы и управляют работой транслятора. Тип передаваемых значений параметров предопределяет структуру шаблонной функции. В первом случае шаблону параметра ZZZ присваивается значение "int", шаблону параметра YYY присваивается значение "char", после чего прототип шаблонной функции принимает вид char Tf (int, char, int);*/ cout << Tf((float) 0, "This is the string...", 10) << endl;/*Во втором случае шаблону параметра ZZZ присваивается значение "float", шаблону параметра YYY присваивается значение "char *", после чего прототип шаблонной функции принимает вид char* Tf (float, char *, int); В результате, используя один общий шаблон, мы получаем две совершенно различных совместно используемых функции.*/}/*Шаблон функции. Первый параметр не используется, поэтому в списке параметров он представлен спецификатором объявления. Второй шаблонный параметр определен и также зависит от шаблона, третий параметр от шаблона не зависит.*/template <class YYY, class ZZZ> YYY Tf (ZZZ, YYY yyyVal, int x){ ZZZ zzzVal; int i; for (i = 0; i < x; i++) { cout << "Tf() for " << typeid(zzzVal).name() << endl; } return yyyVal;}

Вывод для приведенного примера:

Tf() for intTf() for int1Tf() for floatTf() for floatTf() for floatThis is the string...

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

Шаблон — это предписание для создания класса, в котором один или несколько типов либо значений параметризованы.

Предположим, что нам нужно определить класс, поддерживающий механизм очереди. Очередь – это структура данных для хранения коллекции объектов; они помещаются в конец очереди, а извлекаются из ее начала. Поведение очереди описывают аббревиатурой FIFO — «первым пришел, первым ушел».

Тогда объявление класса Queue будет выглядеть примерно так:

class Queue {public: Queue(); ~Queue(); Type& remove(); void add( const Type & ); bool is_empty(); bool is_full();private: // ...};

Вопрос в том, какой тип использовать вместо Type? Предположим, что мы решили реализовать класс Queue, заменив Type на int. Тогда Queue может управлять коллекциями объектов типа int. Если бы понадобилось поместить в очередь объект другого типа, то его пришлось бы преобразовать в тип int, если же это невозможно, компилятор выдаст сообщение об ошибке.

Конечно, эту проблему можно решить, создав копию класса Queue для работы с типом double, затем для работы с комплексными числами, затем со строками и т.д. А поскольку имена классов перегружать нельзя, каждой реализации придется дать уникальное имя: IntQueue, DoubleQueue, ComplexQueue, StringQueue. При необходимости работать с другим классом придется снова копировать, модифицировать и переименовывать.

Механизм шаблонов C++ позволяет автоматически генерировать такие типы. Шаблон класса можно использовать при создании Queue для очереди объектов любого типа. Определение шаблона этого класса могло бы выглядеть следующим образом:

template <class Type>class Queue {public: Queue(); ~Queue(); Type& remove(); void add( const Type & ); bool is_empty(); bool is_full();private: // ...};

Чтобы создать классы Queue, способные хранить целые числа, комплексные числа и строки, программисту достаточно написать:

Queue<int> qi;Queue<complex> qc;Queue<string> qs;

Пример: Реализация шаблона класса CStack — стек на основе динамического массива.

// Файл CStack.h#ifndef _CStack_H#define _CStack_H #include <iostream.h>#include <stdlib.h> template <class Type>class CStack {private: Type *data; //Массив для хранения стека int top; //Индекс вершины стека int size; //Макс. размер стекаpublic: CStack(int); //Конструктор – передается максимальное количество элементов ~CStack() {free(data);} void push(Type); //Добавление эл-та int pop(Type &); //Выталкивание эл-та. Возвращает 0, если стек пуст int isempty() {return !top;} void print(); //Вывод содержимого стека}; template <class Type>CStack<Type>::CStack(int sz){ data=(Type*) calloc(sz, sizeof(Type)); top=0; size=sz;} template <class Type>void CStack<Type>::push(Type Item){ if (top==size) { //Если массив заполнен - расширить size+=8; data=(Type*)realloc(data, size*sizeof(Type)); } data[top++]=Item;} template <class Type>int CStack<Type>::pop(Type &Item){ if (!isempty()) { Item=data[--top]; return 1; } else return 0;} template <class Type>void CStack<Type>::print(){ for (int i=top-1; i>=0; i--) cout << data[i] << ' ' ; cout << endl ;} #endif //Тестирующая программа#include <stdio.h>#include <math.h>#include "CStack.h"int main(int argc, char* argv[]){ printf("Int\n"); CStack <int> ti(2); for (int i=0; i<4; i++) ti.push(i); ti.print(); int k; for (int i=0; i<7; i++) if (ti.pop(k)) printf("%d\n",k); else printf("Empty\n"); printf("Double\n"); CStack <double> td(2); for (int i=10; i<14; i++) td.push(sqrt((double)i)); td.print(); double f; for (int i=0; i<7; i++) if (td.pop(f)) printf("%lf\n",f); else printf("Empty\n"); getchar(); return 0;}

Результаты работы программы:

Int3 2 1 03210EmptyEmptyEmptyDouble3.60555 3.4641 3.31662 3.162283.6055513.4641023.3166253.162278EmptyEmptyEmpty


Дата добавления: 2016-07-27; просмотров: 1906;


Поиск по сайту:

Воспользовавшись поиском можно найти нужную информацию на сайте.

Поделитесь с друзьями:

Считаете данную информацию полезной, тогда расскажите друзьям в соц. сетях.
Poznayka.org - Познайка.Орг - 2016-2024 год. Материал предоставляется для ознакомительных и учебных целей.
Генерация страницы за: 0.012 сек.