Out.close();//закрытие потока 4 глава




Перед тем как рассказать что то о шаблонах рассмотрим простую функцию, которая меняет значение двух переменных между собой. Например, если до обращения к программе переменная x=1, а y=0, то после обращения, наоборот x=0, а y=1. Код такой функции можно записать так:

void Swap (int& x, int& y)

{

int temp;

temp=x;

x=y;

y=temp;

}

Обратим внимание на то, что этот алгоритм применим не только к переменным типа int но и к переменным любого типа. Поэтому тот же алгоритм для переменных типа char будет выглядеть так

void Swap (char& x, char& y)

{

char temp;

temp=x;

x=y;

y=temp;

}

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

 

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

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

Объявление шаблона функции выглядит так:

 

 

template <typename имя_типа> тип_результата имя_функции (список параметров)

{

// Тело функции

}

 

или так

 

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

{

// Тело функции

}

 

 

Cтрока template <class тип> называется префиксом шаблона. Слово template в переводе на русский означает шаблон. Далее идет слово class, которое в данном случае читается как тип. Согласно стандарту ANSI C++ в префиксе шаблона вместо слова class можно применять ключевое слово typename. Иначе говоря, слово class вовсе не означает, что шаблоном является класс. Далее идет, так называемый, параметр типа. Параметр типа это любой идентификатор означающий тип. Параметр типа можно рассматривать как имя обобщенного (любого) типа. В частности для рассмотренных выше примеров где использовались типы int и char можно использовать, например, имя inch или chin, т.е. любое имя разрешенное в С++.

Продемонстрируем сказанное на рассмотренной ранее программе.

 

 

#include <iostream>

using namespace std;

#include <windows.h>

template <class T> // Параметр типа обозначен через Т

/* Далее идет о писание функции в котором тип задан параметром T. Передача параметра, в данном случае, осуществляется по ссылке */

void Swap (T& x, T& y) /* к моменту обращения тип Т будет известен и заменен, например, на int */

{

T temp;

temp=x;

x=y;

y=temp;

}

/* Далее идет основная программа */

Int main()

{

SetConsoleOutputCP(1251);

//--------------------------------

int integer1=1,integer2=2;

cout<<"Начальные значения integer1="<<integer1<<" integer2="<<integer2<<endl;

Swap (integer1,integer2);

cout<<"Измененные значения integer1="<<integer1<<" integer2="<<integer2<<endl;

//--------------------------------

char symbol1='A', symbol2='B';

cout<<" Начальные значения symbol1="<<symbol1<<" symbol2="<<symbol2<<endl;

Swap (symbol1,symbol2);

cout<<" Измененные значения symbol1="<<symbol1<<" symbol2="<<symbol2<<endl;

return 0;

}

 

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

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

 

template<class T1, class T2> /* Пробел после запятой в префиксе не обязателен */

 

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

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

 

Тип Минимальный диапазон
int -32767 - 32767
unsigned int 0 - 65535
signed int Аналогичен int
short int Аналогичен int
unusigned short int 0 - 65535
signed short int Аналогичен short int
long int -2 147 483 647 - 2 147 483 647
signet long int Аналогичен long int
unsigned long int 0 – 4 294 967 295
long long int -(263-1) - (263-1)
signed long long int Аналогичен long long int
unsigned long long int 0 – 264-1
float 6 значащих цифр
double 10 значащих цифр
long double 10 значащих цифр
wchar_t Аналогичен unsigned int

 

Согласитесь, что для 16 типов чисел писать одну и туже программу не имеет никакого смысла. Лучше воспользоваться шаблоном функции.

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

Сначала в массиве, предназначенном для сортировки, начиная с первого элемента массива производится поиск наименьшего числа. Причем делается это в соответствии со следующим алгоритмом. Предполагается, что первый элемент минимален. Для чего его величину записывают в специально выделенную переменную min, а его номер в indexOfMin. Далее величина min сравнивается с величиной остальных элементов. Как только находится меньшее число - оно записывается в min, а его номер в indexOfMin. После того как весь массив просмотрен, т.е. минимальное число массива найдено оно переписывается в первый этемент массива, а на его место записывается число, находившееся ранее в первом элементе массива (строго говоря в обратной последовательности). Говоря иначе, числа меняются местами. Потом эту операцию повторяется для всех элементов массива начиная со второго, и т.д. Этот алгоритм не очень эффективен, с точки зрения временных затрат, но зато наиболее прост для программирования учебной задачи. Причем функцию swap, осуществляющую перестановку переменных мы уже создали. Теперь нужно сделать шаблонную функцию для поиска минимального числа, точнее для поиска номера элемента массива в котором находится минимальное число. Вот эта функция.

template <class T>

int indexOfSmallest(T array[], int startIndex, int maxIndex){

/* T-тип элементов массива с именем array

* элементы этого массива подлежат сортировке */

//startIndex – номер элемента с которого начинается поиск

//maxIndex – максимальный индекс массива

/*символ амперсанд не используется, т.к. массивы и без того передаются в функции по ссылке*/

T min=array[startIndex];

//min это минимальное число в массиве

int indexOfMin=startIndex;

// indexOfMin это индекс наименьшего числа в массиве

for(int index=startIndex+1; index<maxIndex; index++)

if (array[index]<min){

min=array[index];

indexOfMin=index;

}

return indexOfMin;

}

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

//Имя этой функции sort

 

template <class T>

void sort(T array[], int maxIndex){ /*передали массив и его размер */

int indexOfNext;

for(int index=0; index<maxIndex-1; index++){

indexOfNext=indexOfSmallest(array,index,maxIndex);

//Нашли индекс наименьшего числа

//Поменяем

swap (array[index],array[indexOfNext]);

}

}

 

Наконец осталось определить массив и вызвать функцию sort

 

#include <iostream>

#include <string>

using namespace std;

int main(){

//определяем массив целых чисел

int a[10]={9,8,7,6,5,4,3,2,1,0};

for(int n=0;n<10;n++)

cout<<"a["<<n<<"]="<<a[n]<<endl;

cout<<endl;

sort(a,10);

for(int n=0;n<10;n++)

cout<<"a["<<n<<"]="<<a[n]<<endl;

cout<<endl;

//определяем массив символов

char ch[10]={'w','e','t','g','d','c','j','m','1','0'};

for(int n=0;n<10;n++)

cout<<"a["<<n<<"]="<<ch[n]<<endl;

cout<<endl;

sort(ch,0);

for(int n=0;n<10;n++)

cout<<"a["<<n<<"]="<<ch[n]<<endl;

cout<<endl;

//определяем массив строк

string str[10]={"tau2","epsilon","alpha","omega","sigma",

"dzeta","ro","pi","tau3","tau1"};

for(int n=0;n<10;n++)

cout<<"strb["<<n<<"]="<<str[n]<<endl;

cout<<endl;

sort(str,10);

for(int n=0;n<10;n++)

cout<<"a["<<n<<"]="<<str[n]<<endl;

return 0;

}

 

Программа сортирует любые массивы.

 

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

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

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

template <class тип_данных> class имя_класса

{

//тело класса

}; //точка с запятой – обязательно, ведь это класс

Здесь тип_данных является параметром типа данных, с которыми этот класс будет работать. После того как шаблон класса определен можно объявлять объекты этого класса.

Формат объявления объекта класса порожденного шаблоном выглядит так:

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

имя_объекта(параметры_конструктора);

 

Рассмотрим пример. Очень часто приходится хранить информацию в виде пары символов, например это может быть дата и день недели «30 пн» - 30 число, понедельник, или например, координаты ячейки таблицы, подобно тому как это принято в EXCEL. Содадим шаблон класса, объектом которого является пара значений произвольного типа Т. Если Т это числа типа int, то значением объекта является пара целых чисел, если Т заменяется на тип char, то объект представляет собой пару символов.

Создадим шаблон класса, который может использоваться как для записи счета игры, так и для записи места в самолете.

 

#include<iostream>

using namespace std;

#include<stdlib.h>

template<class T> //объявление шаблона класса

class Pair

{

public:

Pair(T first_value, T second_value); ///*конструктор с параметрами */

void set_element(int position, T value); /*функция устанавливает

значение элемента в соответствующей позиции*/

 

T first;

T second;

}; //Конец описания класса

//Далее идет описание функций

/* Определение функций-членов шаблона класса*/

/*эти функции опредаряются строкой template<class Параметр_типа>*/

template<class T> //Добавка к описанию функции

void Pair <T>::set_element(int position, T value)

/*Обратите внимание, что к имени класса перед оператором разрешения области видимости добавляется идентификатор типа Т, взятый в угловые скобки */

{

if (position==1) first=value;

else if(position==2) second=value;

else {cout<<"Error"; exit(1);}

}

 

template<class T>

Pair <T>:: Pair(T first_value, T second_value) //Конструктор

{

first=first_value;

second=second_value;

}

//==========================================

void main(){

Pair<int> score(0,0); /*объект класса Pair с указанием

* типа вместо заполнителя T

* в переводе score означает счет Иры */

 

Pair<char> seats(1,'a'); //второй объект использует символы

score.set_element(1,3); //В первую позицию записать 3

score.set_element(2,4); //Во вторую позицию записать 4

cout<<score.first<<score.second<<endl;

seats.set_element(1,'b');

seats.set_element(2,'c');

cout<<seats.first<<seats.second<<endl;

}

Результат выглядит так

 

Первая строчка соответствует счету игры, вторая месту в самолете. Вы скажете, что счет так не записывается, а ряд места в самолете указыватся в виде числа. Да это верно, но мы только рассмотрели пример того, как можно сделать программу. Дальше ее можно бесконечно совершенствовать.

Вот еще один пример шаблона класса. Правда в нем используется не совсем обычно указатель this.

// Простой связанный список

#include <iostream>

using namespace std;

 

template <class data_t> class list { /*Класс с именем список,

который используя патаметр типа data_t */

data_t data;

list *next; //указатель на объект типа list

public:

list (data_t d); //конструктор класса

void add(list *node) { //

node->next = this; next = 0; }

list *getnext() { return next; }

data_t getdata() { return data; }

};

 

template <class data_t> list<data_t>::list(data_t d)

{

data = d;

next = 0;

}

 

int main()

{

list<char> start('a');

list<char> *p, *last;

int i;

 

// ñîçäàíèå ñïèñêà

last = &start;

for(i=1; i<26; i++) {

p = new list<char> ('a' + i);

p->add(last);

last = p;

}

 

// âûâîä ñïèñêà

p = &start;

while(p) {

cout << p->getdata();

p = p->getnext();

}

 

return 0;

}

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

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

//Шаблон класса определяющего вектор

template<class T> //Т -параметр шаблона

class Vector

{

//объявляются две переменные

T *data; /* объявляется указатель на начальный адрес в

* котором будет храниться объект типа T */

int size; / * объявляется количество элементов массиваи или иначе

* количество координат вектора */

public:

Vector(int); // Конструктор класса

~Vector(){delete[] data;} /* Деструктор освобождает память,

*выделенную при создании экземпляра

*типа T[n] с помощью *зарезервированного слова delete*/

/* Деструктор, в прнципе можно не вставлять в тело программы, если

*нас не беспокоит объем оставшейся памяти */

//Перегрузка операции “[]”

/* в этом примере функция перегрузки описана прямо в классе

* объявяляетяся функция с типом передача по ссылке */

T& operator[] (int i) {return data[i];} //передача по ссылке

}; //Конец класса

// Определение методов класса

template <class T> //указание на то, что класс - шаблон

Vector <T>::Vector(int n) //Определение конструктора

{data=new T[n]; /* просим назначить начальный адрес указателю

* data типа T */

size=n;

};

 

Теперь нужно создать объекты этого класса. При объявлении объекта обобщенного класса тип данных нужно задавать в угловых скобках, используя следующий синтаксис:

имя_класса <фактические_параметры_шаблона> имя_объекта (параметры_конструктора)

Если теперь потребуется определить вектор, имеющий 5 координат, то делается это следующим образом:

Vector <double> Z(5);

Сказанное можно проиллюстрировать программой

#include <iostream>

using namespace std;

void main()

{

Vector <int> X(5);

Vector <char> C(5);

for(int i=0;i<5;i++)

{X[i]=i;C[i]='A'+i;}

for(int i=0;i<5;i++)

cout<<" "<<X[i]<<' '<<C[i];

}

Результат выполнения программы

0 A 1 B 2 C 3 D 4 E

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

T& operator[] (int i);

Тогда после описания класса нужно дать описание функции перегрузки:

template <class T>

T& Vector <T>:: operator[] (int i) {return data[i];}

которая, по понятным причинам, предворяется описанием template <class T>.

 

 

6.3 Демонстрационные программы

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

Для массива с десятью целыми числами фрагмент кода, реализующий этот алгоритм выглядит так:

int a[]={10,9,8,7,6,5,4,3,2,1};

const int MAX=sizeof(a)/sizeof(a[0]);

for (int j=0; j<MAX-1; j++)

fot (int k=j+1; MAX-1; k++)

if(a[j]>a[k])Swap(a[j],a[k]);

Функция Swap(a[j],a[k])осуществляет обмен элементов массива.

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

int I;

cout<<sizeof(int);

cout<<sizeof i;

Кроме того, мы используем функцию strcmp()

Итак, вот код шаблона

#define SIZEOF (array) sizeof(array)/ sizeof(array[0])

 

Задачи.

1. Напишите функцию min(), возвращающую меньший из двух своих аргументов. Например, min(3,4) должна возвратить 3, а min(‘c’,’a’) возвратить а.

2. Напишите код функции find(). Эта функция ищет объект в массиве. Она возвращает индекс найденного объекта если его удалось найти или -1, если заданный объект не найден. Пример для объекта типа int.

int find(int object, int *list, int size){...}

Параметр size задает количество элементов массива.

 

7.1 Обработка исключений

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

Возможно, что вы никогда не задумывались над тем, что операционной системе компьютера постоянно приходится решать задачи связанные с возникновением необычных ситуаций, и она решает их достаточно успешно. Каким образом операционной системе удается выходить из подобных ситуаций? Ответ простой - дело в том, что такие ситуации заранее предусмотрены. Они называются особыми или исключительными. Как только возникает исключительная ситуация устройство сигнализирует о том, что произошло нечто необычное. Это действие называется генерацией исключения (throwing an exception). После чего операционная система распознает тип ситуации и переходит к её обработке, т.е. к выполнению определенной инструкции заранее подготовленной разработчиками операционной системы. Например, всем приходилось указывать файл на дискете, в то время как дискета уже была снята с дисковода, но никогда после этого копьютер не выполнял команды на считывание или запись. Этого не происходит именно потому о чем мы сейчас сказали.

Приступая к написанию программы каждый человек надеется, что все события происходящие во время ее выполнения будут именно такими как он предполагает. Например, если программа обращается к файлу, то такой файл существует, если программа обращается к элементу массива, то такой элемент существует (хотя в C++ легко выйти за границу массива), и т.д. Очень часто такие предположения оказываются неверными, и тогда результат выполнения программы будет непредсказуем. Возможно, что вступит в действие обработчик событий операционной системы, как, например, часто бывает при делении на ноль, и программа аварийно завершится, но возможны и другие результаты, иногда связанные с потерей информации, зацикливанием и другими неприятными последствиями. Для того, чтобы избежать такого рода неприятностей в С++ существует понятие исключения (exception). Исключение это состояние, которое достигается программой при её выполнении. Такое состояние может быть вызвано как возникновением непредвиденной ситуации, так и заранее предусмотренными действиями программиста.

 

Исключения и их стандартная обработка

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



Поделиться:




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

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2016-02-16 Нарушение авторских прав и Нарушение персональных данных


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