Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Infa (1)

.pdf
Скачиваний:
5
Добавлен:
14.04.2015
Размер:
2.31 Mб
Скачать

Шаблоны и несколько типов

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

template<class Т, class T1> void array_sort(T array[], T1 elements)

{

// операторы

}

С помощью шаблона array_sort программа может создать функции которые сортируют маленькие массивы типа float (менее 128 элементов) и очень большие массивы типа int, используя следующие прототипы:

void

array_sort(float,

char);

void array_sort(int, long);

 

 

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

Функции-шаблоны создаются с использованием ключевого слова template (шаблон). Обычное значение слова «шаблон» достаточно полно отражает его использование в С++. Шаблон используется для создания каркаса функции, оставляя компилятору реализацию подробностей. Общая форма функции-шаблона имеет следующий вид:

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

{

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

}

Здесь птип является параметром-типом, «держателем места» (placeholder) для имени типа данных, которое используется функцией. Этот параметр-тип может быть использован в определении функции. Однако это только «держатель места», который будет автоматически заменен компилятором на фактический тип данных во время создания конкретной версии функции.

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

//пример шаблона функции

#include <iostream.h>

//шаблон функции

template <class X> void swap(X &a, X &b)

{

X temp; temp = a; a = b;

b = temp;

}

int main()

{

int i=10, j = 20;

float x=10.1, у= 23.3; char a='x', b='z';

cout << "Original i, j: " << i << ' ' << j << endl; cout << "Original x, y: " << x << ' ' << у << endl; cout << "Original a, b: " << a << ' ' << b << endl; swap(i, j); // обмен целых

swap(x, у); // обмен вещественных значений swap(a, b); // обмен символов

cout << "Swapped i, j : " << i << ' ' << j << endl; cout << "Swapped x, y: " << x << ' ' << у << endl; cout << "Swapped a, b: " << a << ' ' << b << endl; return 0;

}

Рассмотрим эту программу более внимательно. Строка

template <class X> void swap (X &а, X &b)

указывает компилятору, что создается шаблон. Здесь X — шаблон типа, используемый в качестве параметра-типа. Далее следует объявление функции swap() с использованием типа данных X для тех параметров, которые будут обмениваться значениями. В функции main() функция swap() вызывается с передачей ей данных трех различных типов: целых чисел, чисел с плавающей запятой и символов. Поскольку функция swap() является функцией-шаблоном, то компилятор автоматически создаст три разные версии функции swap() — одну для работы с целыми числами, другую для

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

Функции с двумя типами-шаблонами

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

#include <iostream.h>

template <class type1, class type2> void myfunc(type1 x, type2 y)

{

cout << X << ' ' << у << endl;

}

int main()

{

myfunc(10, "hi"); myfunc(0.23, 10L); return 0;

}

В этом примере типы-шаблоны type1 и type2 заменяются компилятором на типы int, char*, double и long соответственно, причем компилятор создает два различных экземпляра функции myfunc() в функции main(). Следует иметь в виду, что когда создается функция-шаблон, то по существу этим компилятору разрешается создавать столько различных версий этой функции, сколько необходимо, чтобы обрабатывать те типы данных, которые передаются этой функции при ее вызове.

Явная перегрузка функций-шаблонов

Хотя функция-шаблон перегружает себя по мере необходимости, также можно перегрузить ее явным образом. Если перегружается функция-шаблон, то перегруженная функция переопределяет функциюшаблон для того конкретного набора типов параметров, для которого создается перегруженная функция. В качестве примера рассмотрим следующую версию функции swap():

//переопределение функции-шаблона

#include <iostream.h>

template <class X> void swap(X &a, X &b)

{

X temp; temp = a; a = b;

b = temp;

}

//обобщенная версия swap()

void swap(int &af int &b)

{

int temp; temp = a; a = b;

b = temp;

cout << "Inside overloaded swap(int &, int &).\n";

}

int main()

{

int i=10, j=20;

float x=10 .1, y=23.3; char a='x', b='z';

cout << "Original i, j : " << i << ' ' << j << endl; cout << "Original x, y: " << x << ' ' << у << endl; cout << "Original a, b: " << a << ' ' << b << endl; swap(i, j); // вызов явно перегруженной swap () swap(x, у); // обмен вещественных значений swap(a, b); // обмен символов

cout << "Swapped i, j : " << i << ' ' << j << endl; cout << "Swapped x, y: " << x << ' ' << у << endl; cout << "Swapped a, b: " << a << ' ' << b << endl; return 0;

}

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

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

Ограничения на функции-шаблоны

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

#include <iostream.h> #include <math.h> void myfunc(int i)

{

cout << "value is: " << i << "\n";

}

void myfunc(double d)

{

double intpart; double fracpart;

fracpart = modf(d, &intpart);

cout << "Fractional part: " << fracpart; cout << "\n";

cout << "Integer part: " << intpart;

}

int main()

{

myfunc(1);

myfunc(12.2); return 0;

}

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

Классы-шаблоны

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

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

template < class птип> class имя_класса {

...

}

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

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

имя_класса <тип> объект;

Здесь тип является именем типа данных, с которыми будет оперировать данный класс.

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

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

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

//демонстрациякласса-шаблона stack #include <iostream.h>

const int SIZE = 100;

//созданиекласса-шаблона stack template <class SType> class stack { SType stck[SIZE];

int tos; public: stack(); ~stack();

void push(SType i); SType pop();

};

//функция-конструктор stack

template <class SType> stack<SType>::stack()

{

tos = 0;

cout<< "Stack Initialized\n";

}

/* функция-деструктор stack

This function is not required. It is included for illustration only. */ template <class SType> stack<SType>::~stack()

{

cout << "Stack Destroyed\n";

}

// помещениеобъектавстек

template <class SType> void stack<SType>::push(SType i)

{

if (tos==SIZE) {

cout << "Stack is full. \n"; return;

}

stck[tos] = i; tos++;

}

// извлечениеобъектаизстека

template <class SType> SType stack<SType>::pop()

{

if(tos==0) {

cout << "Stack underflow.\n"; return 0;

}

tos --;

return stck[tos];

}

int main()

{

stack<int> a; // созданиецелочисленногостека stack<double> b; // созданиевещественногостека stack<char>с; //созданиесимвольногостека

int i;

//использованиецелогоивещественногостеков a.push (1);

b.push (99.3); a.push(2); b.push(-12.23);

cout << a.pop() << " "; cout << a.pop() << " "; cout << b.pop() << " "; cout << b.pop() << "\n";

//демонстрациясимвольногостека

for (i=0; i<10; i++) с.push ( (char) 'A'+i); for (i=0; i<10; i+ + ) cout << c.pop(); cout << "\n";

return 0;

}

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

stack<int> а; // создание целочисленного стека stack<double> b; // создание вещественного стека stack<char> с; // создание символьного стека

Обратим внимание, каким образом нужный тип данных подставляется в угловые скобки. Изменяя тип данных, указываемый при создании объектов класса stack, одновременно изменяется тип данных, хранящихся в стеке. Например, можно создать другой стек, хранящий указатели на символы:

stack<char *> chrptrstck;

Также можно создать стек, содержащий определенный тип данных. Например, можно хранить адреса, используя структуру:

struct addr { char name[40]; char street[40]; char city[30]; char state[3]; char zip[12];

}

Далее можно использовать класс stack для создания стека, в котором хранятся объекты типа addr:

stack<addr> obj;

Пример с двумя типами-шаблонами

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

/* данный пример использует два шаблона при определении класса

*/

#include <iostream.h>

template <class Type1, class Type2> class myclass

{

Type1 i; Type2 j; public:

myclass (Type1 a, Type2 b) { i = a; j = b; } void show () { cout << i << ' ' << j << '\n'; } };

int main()

{

myclass<int, double> ob1(10, 0.23); myclass<char, char *> ob2('Х', "This is a test"); ob1.show(); // вывод int, double

ob2.show(); // вывод char, char * return 0;

}

Эта программа выдаст следующий результат на экран:

10 0.23

X This is a test

В программе объявляются два типа объектов. Объект ob1 использует типы данных integer и double. Объект ob2 использует типы данных char и char*. В обоих случаях компилятор автоматически генерирует необходимые данные и функции в соответствии с типом данных, передаваемых конструктору в качестве аргументов.

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

12) Исключения в C++.

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

возможности по непосредственной обработке исключений. Комитет по разработке стандартов С++ предоставил очень простую, но мощную форму обработки исключений.

Темные дни С

Типичная функция, написанная на С, выглядит примерно так:

long DoSomething()

{

long *a, c; FILE *b;

a = malloc(sizeof(long) * 10); if (a == NULL)

return 1;

b = fopen("something.bah", "rb"); if (b == NULL) {

free(a); return 2;

}

fread(a, sizeof(long), 10, b); if (a[0] != 0x10) {

free(a);

fclose(b); return 3;

}

fclose(b);

c = a[1];

free(a);

return c;

}

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

Try-catch-throw

Давайте же разберем основы обработки исключений в С++. Чтобы комфортно работать с исключениями в С++ вам нужно знать лишь три ключевых слова:

try (пытаться) - начало блока исключений;

catch (поймать) - начало блока, "ловящего" исключение;

throw (бросить) - ключевое слово, "создающее" ("возбуждающее") исключение.

Атеперь пример, демонстрирующий, как применить то, что вы узнали:

void func()

{

try

{

throw 1;

}

catch(int a)

{

cout<< "Caught exception number: " << a << endl;

return;

}

cout<< "No exception detected!" <<endl;

return;

}

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

Caught exception number: 1

Теперь закоментируйте строку throw 1; и функция выдаст такой результат:

No exception detected!

Как видите все очень просто, но если это применить с умом, такой подход покажется вам очень мощным средством обработки ошибок. Catch может "ловить" любой тип данных, так же как и throw может "кинуть" данные любого типа. Т.е. throw AnyClass(); будет правильно работать, так же как и catch (AnyClass &d) {};.

Как уже было сказано, catch может "ловить" данные любого типа, но вовсе не обязательно при это указывать переменную. Т.е. прекрасно будет работать что-нибудь типа этого:

catch(dumbclass) { }

так же, как и

catch(dumbclass&) { }

Так же можно "поймать" и все исключения:

catch(...) { }

Троеточие в этом случае показывает, что будут пойманы все исключения. При таком подходе нельзя указать имя переменной. В случае, если "кидаются" данные нестандартного типа (экземпляры определенных вами классов, структур и т.д.), лучше "ловить" их по ссылке, иначе вся "кидаемая" переменная будет скопирована в стек вместо того, чтобы просто передать указатель на нее. Если кидаются данные нескольких типов и вы хотите поймать конкретную переменную (вернее, переменную конкретного типа), то можно использовать несколько блоков catch, ловящих "свой" тип данных:

 

try {

 

 

throw 1;

 

 

// throw 'a';

 

 

}

 

 

catch (long b) {

 

 

cout<< "поймантип long:

" << b << endl;

 

}

 

 

catch (char b) {

 

 

cout<< "поймантип char:

" << b << endl;

 

}"

 

 

 

 

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]