- •29 Марта 2005 г., протокол № 5
- •Введение
- •1. Классы. Первое знакомство
- •2. Классы и функции
- •Указатель this
- •3. Наследование, виртуальные функции и полиморфизм
- •4. Массивы объектов, указатели и ссылки
- •5. Шаблоны и исключительные ситуации
- •6. Потоки и классы ввода/вывода
- •7. Статические и константные члены, локальные классы
- •Объектно-ориентированное
- •220050, Минск, пр. Независимости, 4.
- •220004, Минск, ул. Освобождения, 9.
5. Шаблоны и исключительные ситуации
Шаблоны функции
Шаблоны, которые также называют родовыми или параметризованными типами, позволяют конструировать семейства функций и классов. В отличие от механизма перегрузки, когда для каждого набора формальных параметров устанавливается своя функция, шаблон семейства функций определяется один раз, но при этом оно параметризуется. Параметризовать в шаблоне функции можно тип возвращаемого функцией значения и типы параметров, количество и порядок которых должны быть фиксированы. В определении шаблона употребляется служебное слово template. Для параметризации используется список формальных параметров шаблона, который заключается в угловые скобки <>. Каждый формальный параметр шаблона помечен служебным словом class, за которым следует имя параметра.
Шаблон семейства функций состоит из двух частей – заголовка шаблона и определения функции, в котором тип возвращаемого значения и типы параметров обозначаются именами параметров шаблона.
template <class Ttype> тип имя_функции(список аргументов)
{/*тело функции*/}
Здесь Ttype – фиктивный тип, или список типов, через запятую, который используется при объявлении аргументов, локальных переменных и возвращаемых значений функции. Компилятор заменит этот фиктивный тип на один из реальных и создаст соответственно несколько перегружаемых функций, которые являются ограниченными, поскольку выполняют одни и те же действия. Например:
#include <iostream.h>//пример 50
#include <string.h>
template <class X> int find(X object, X *list, int size) {
int i;
for(i=0; i<size; i++)
if(object == list[i]) return i;
return -1;
}
int main(){
int a[ ]={1, 2, 3, 4};
char *c="это проверка";
double d[ ]={1.1, 2.2, 3.3};
cout << find(3, a, 4) << endl;
cout << find('a', c, strlen(c))<< endl;
cout << find(0.0, d, 3);
return 0;
}
Компилятор автоматически создает три перегруженные функции find(), соответствующие типам передаваемых аргументов.
Приведем пример определения шаблона функций, вычисляющих абсолютные значения числовых величин разных типов:
template <class type> type abs (type x) { return x > 0 ? x: -x;}
В качестве еще одного примера рассмотрим шаблон семейства функций для обмена значений двух передаваемых им параметров.
template <class T> void swap (T* x, T* y){
T z = *x;
*x = *y;
*y = x; }
Здесь параметр T шаблона функций используется не только для спецификации формальных параметров, но и в теле определения функции, где он задает тип вспомогательной переменной z.
Если в программе присутствует приведенный ранее шаблон семейства функций swap() и появится последовательность операторов:
long k = 4, d = 8; swap (&k, &d);
то компилятор сформирует определение функции:
void swap (long* x, long* y){
long z = *x;
*x = *y;
*y = z; }
Затем будет выполнено обращение именно к этой функции, и значения переменных 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;
}
Параметры шаблона
Параметры шаблона являются формальными, а типы тех параметров, которые используются в конкретных обращениях к функции, служат фактическими параметрами шаблона. Имена параметров шаблона должны быть уникальными во всем его определении. В списке параметров шаблона функций их может быть несколько. Каждый из параметров должен начинаться со служебного слова class. Например, неверен заголовок:
template <class type1, type2, type3>
Недопустимо использовать в заголовке шаблона параметры с одинаковыми именами, например:
template <class t, class t, class t>
Все параметры шаблона функций должны быть обязательно использованы в спецификациях параметров определения функции. Будет ошибочным шаблон:
template <class A, class B, class C> B func (A n, C m) {B v; ... }
Применение параметра шаблона B в качестве типа возвращаемого функцией значения и для определения объекта v в теле функции недостаточно.
Шаблоны классов
Аналогично шаблонам функций определяется шаблон семейства классов:
template <список_параметров_шаблона> класс
В определении класса, входящего в шаблон, его имя является не именем отдельного класса, а параметризованным именем семейства классов.
С помощью шаблона класса можно создать класс, реализующий стек, очередь, дерево и т. д. для любых типов данных. Компилятор будет генерировать правильный тип объекта на основе типа, задаваемого при создании объекта. Объявление шаблона класса имеет вид:
template <class Ttype> class имя_класса {
// поля и функции класса
}
Например:
// простой родовой связанный список
#include <iostream.h>//пример 51
template <class data_t> class List {
data_t data;
List *next;
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;
}
С помощью простого объявления можно создать другой объект, например, для хранения целых:
List <int> int start(1);
В список можно поместить структуры или другие объекты.
Класс-шаблон может иметь больше одного родового типа данных:
template<class T1,class T2> class M {T1 a; T2 b;}
В качестве аргумента в общем случае может быть использовано константное выражение, однако выражение, содержащее переменные, использовать в качестве фактического параметра шаблона нельзя.
Исключительные ситуации
Под исключительной ситуацией, или исключением (exception), понимают прерывание нормального потока программного управления в ответ на непредвиденное или аварийное событие. Исключение может порождаться ошибками, такими, как деление числа на нуль или обращением к памяти по недействительному адресу. В качестве ответа на ошибку функция, в которой возникла ошибка, инициирует (возбуждает) исключение оператором throw, за которым следует значение. Это значение может бытъ константой, переменной или объектом и предназначено для передачи информации обработчику исключения об исключении.
Обработчик исключения начинается ключевым словом catch с объявлением в круглых скобках. Если тип, определенный в этом объявлении, совпадает с типом значения, данного в операторе throw, управление будет передано в блок, следующий за ключевым словом catch. В случае несовпадения типов, программа осуществляет поиск другого обработчика.
Для процедуры обработки исключения должен быть предусмотрен try-блок, в котором и происходит собственно инициализация исключения. Если исключение не инициализировано в try-блоке, программное управление проигнорирует catch-блок и непосредственно перейдет к первому оператору, находящемуся за catch-блоком.
try {/*блок try*/
}catch(type1 arg){/*блок catch*/}
//операторы
С блоком try может связываться несколько блоков catch. Выполняется тот блок catch, для которого тип данных соответствует типу возникшей исключительной ситуации. При этом ее значение присваивается аргументу в круглых скобках блока catch. Если ошибка имеет место внутри блока try, она может генерироваться с помощью throw.
#include <iostream.h>//пример 52
int main() {
try {
// начало блока try
cout << "Внутри блока try\n";
throw 10; // генерация ошибки 10
cout << "Это выполнено не будет\n";
}
catch (int i) { // перехват ошибки
cout << "Перехвачена ошибка номер: "<< i << "\n";
}
catch (char *s) { // перехват ошибки
cout << "Перехвачена ошибка номер: "<<s << "\n";
}
return 0;
}
Будет выведено:
Внутри блока try
Перехвачена ошибка: 10
Оператор throw генерирует ошибку, после чего управление передано блоку catch. Если заменить тип ошибки int на double, ошибка перехвачена не будет. Для перехвата всех исключительных ситуаций независимо от типа можно воспользоваться многоточием, которое соответствует любому типу данных.
catch(…) {/*тело*/}
Ключевое слово throw после заголовка функции используется для определения списка инициализированных исключений:
void funс( ) throw (except1, except2, except3, char*) { }
Функции, генерирующие исключения, вызываются из блока try. Описанная без оператора throw функция не может корректно для течения программы инициализировать исключение, а та, которая может это сделать, способна вызвать также исключение типа, производного от него. Блок catch, который перехватывает исключение для объектов, может перехватывать и исключения производных типов.
Если в функции инициализируется исключение, тип которого не совпадает с типом ни одного обработчика, то вызывается функция unexpected(), которая в свою очередь, вызывает terminate(), а terminate() – функцию abort() для завершения программы.
