
- •«Объектно-ориентированный анализ и проектирование»
- •1. Принципы ооп. Классы.
- •Описание классов.
- •Объекты класса.
- •Конструкторы
- •Конструктор копирования
- •Статические элементы класса
- •Дружественные функции и классы
- •Деструкторы
- •Перегрузка операций
- •Перегрузка унарных операций
- •Перегрузка бинарных операций
- •Перегрузка операции присваивания
- •Рекомендации по составу классов
- •2. Наследование
- •Ключи доступа
- •Простое наследование
- •Пример замещения функций (практикум)
- •Виртуальные методы
- •Множественное наследование
- •3. Отношения между классами. Диаграммы класссов на языке uml.
- •4. Шаблоны классов.
- •4.1. Определения шаблонов классов Queue и QueueItem
- •4.2. Конкретизация шаблона класса
- •4.3. Аргументы шаблона для параметров-констант
- •4.4. Функции-члены шаблонов классов
- •5. Обработка исключительных ситуаций
- •5.1. Общий механизм обработки исключений.
- •5.2 . Синтаксис исключений
- •5.3. Перехват исключений
- •5.4. Исключения в конструкторах и деструкторах
- •Vector(int n) // Конструктор
- •5.5. Список исключений функции.
- •6. Строки
- •Преобразование строк
- •7. Контейнерные классы
- •7.1. Векторы.
- •7.2. Двухсторонние очереди (deque).
- •7.3. Списки (List).
- •7.4. Стеки
- •7.5. Очереди (queue)
- •7.6. Очередь с приоритетами(priority_queue)
- •8. Ассоциативные контейнеры
- •8.1. Словари (map)
- •8.2. Словари с дубликатами (multimap)
- •8.3. Множества (set)
- •8.4. Множества с дубликатами (multiset)
- •8.5. Битовые множества (bitset)
4.1. Определения шаблонов классов Queue и QueueItem
Ниже представлено определение шаблона класса Queue. Оно помещено в заголовочный файл Queue.h вместе с определением шаблона QueueItem:
#ifndef QUEUE_H
#define QUEUE_H
// объявление QueueItem
template <class T> class QueueItem;
template <class Type>
class Queue {
public:
Queue() : front(0), last(0){}
~Queue();
Type& remove();
void add(const Type&);
bool is_empty() const {
return front == 0;
}
private:
QueueItem<Type> *front;
QueueItem<Type> *last;
};
#endif
При использовании имени Queue внутри определения шаблона класса Queue список параметров <Type> можно опускать. Однако пропуск списка параметров шаблона QueueItem в определении шаблона Queue недопустим. Так, объявление члена front является ошибкой:
template <class Type>
class Queue {
public:
// ...
private:
// ошибка: список параметров для QueueItem неизвестен
QueueItem *front;
}
Упражнение 1.1
Найдите ошибочные объявления (или пары объявлений) шаблонов классов:
(a) template <class Type>
class Container1;
template <class Type, int size>
class Container1;
(b) template <class T, U, class V>
class Container2;
(c) template <typename myT, class myT>
class Container3 {};
(d) template <class Type, int *pi>
class Container4;
(e) template <class Type, int val = 0>
class Container6;
template <class T = complex<double>, int v>
class Container6;
Упражнение 1.2
Следующее определение шаблона List некорректно. Как исправить ошибку?
template <class elemenType>
class ListItem;
template <class elemType>
class List {
public:
List<elemType>(): _at_front(0), _at_end(0), _current(0), _size(0)
{}
List<elemType>( const List<elemType> & );
List<elemType>& operator=( const List<elemType> & );
~List();
void insert(ListItem<elemType> *ptr, elemType value);
int remove( elemType value );
ListItem *find( elemType value );
void display( ofstream &os = cout );
int size() { return _size; }
private:
ListItem<elemType> *_at_front;
ListItem<elemType> *_at_end;
ListItem<elemType> *_current;
int _size;
};
4.2. Конкретизация шаблона класса
В определении шаблона указывается, как следует строить индивидуальные классы, если заданы один или более фактических типов или значений. По шаблону Queue автоматически генерируются экземпляры классов Queue с разными типами элементов. Например, если написать: Queue<int> qi; -то из обобщенного определения шаблона автоматически создается класс Queue для объектов типа int.
Генерация конкретного класса из обобщенного определения шаблона называется конкретизацией (или инстанцированием) шаблона. При такой конкретизации Queue для объектов типа int каждое вхождение параметра Type в определении шаблона заменяется на int, так что определение класса Queue принимает вид:
template <class int>
class Queue {
public:
Queue(): front(0), last(0){}
~Queue();
int& remove();
void add( const int & );
bool is_empty() const{
return front == 0;
}
private:
QueueItem<int> *front;
QueueItem<int> *last;
};
Чтобы создать класс Queue для объектов типа string, надо написать:
Queue<string> qs;
При этом каждое вхождение Type в определении шаблона будет заменено на string. Объекты qi и qs являются объектами автоматически созданных классов.
Каждый конкретизированный по одному и тому же шаблону экземпляр класса совершенно не зависит от всех остальных. Так, у Queue для типа int нет никаких прав доступа к неоткрытым членам того же класса для типа string.
Конкретизированный экземпляр шаблона будет иметь соответственно имя Queue<int> или Queue<string>. Части <int> и <string>, следующие за именем Queue, называются фактическими аргументами шаблона. Они должны быть заключены в угловые скобки и отделяться друг от друга запятыми. В имени конкретизируемого шаблона аргументы всегда должны задаваться явно. В отличие от аргументов шаблона функции (когда по параметру можно определить тип), аргументы шаблона класса никогда не выводятся из контекста.
Конкретизированный шаблон класса Queue можно использовать в программе всюду, где допустимо употребление типа обычного класса:
// типы возвращаемого значения и обоих параметров конкретизированы из
// шаблона класса Queue
extern Queue<complex<double>>;
Queue<complex<double>>& slianie(Queue<complex<double>>&, Queue<complex<double>>&);
Объекты класса, конкретизированного по шаблону Queue, объявляются и используются так же, как объекты обычных классов:
extern Queue<double> eqd;
Queue<int> *pqi = new Queue<int>;
Queue<int> aqi[1024];
// объявление шаблона функции
template <class Type>
void bar(Queue<Type> &, // ссылается на обобщенный шаблон
Queue<double> &); // ссылается на конкретизированный шаблон
Однако вне такого определения употребляются только конкретизированные экземпляры. Например, в теле обычной функции всегда надо задавать фактические аргументы шаблона Queue:
void foo2(Queue<int> &qi ){
Queue<int> *pqi = &qi;
…
}
Шаблон класса конкретизируется только тогда, когда имя объекта требует определения шаблона. Не всегда определение класса должно быть известно. Например, перед объявлением указателей и ссылок на класс его знать необязательно:
class Matrix;
Matrix *pm; // правильно: определение класса Matrix знать необязательно
void inverse( Matrix & ); // тоже правильно
Поэтому объявление указателей и ссылок на шаблон класса не приводит к его конкретизации. (Отметим, что в некоторых компиляторах, написанных до принятия стандарта C++, шаблон конкретизируется при первом упоминании имени класса в тексте программы.) Так, в функции foo2() объявляются указатель и ссылка на Queue, но это не вызывает конкретизации шаблона Queue.
Определение класса необходимо знать, когда определяется объект этого типа. В следующем примере определение obj1 ошибочно: чтобы выделить для него память, компилятору необходимо знать размер класса Matrix:
class Matrix;
Matrix obj1; // ошибка: класс Matrix не определен
class Matrix { ... };
Matrix obj2; // правильно
Таким образом, конкретизация происходит тогда, когда определяется объект класса, конкретизируемый по этому шаблону. Эта «точка» называется точкой конкретизации данного класса.
Если имеется указатель или ссылка на конкретизированный шаблон, то конкретизация производится в момент обращения к объекту, на который они ссылаются. В определенной выше функции foo2() класс Queue конкретизируется в следующих случаях: когда разыменовывается указатель pqi (ссылка qi используется для получения значения именуемого объекта) и когда pqi или qi употребляются для доступа к членам или функциям-членам этого класса:
void foo2(Queue<int> &qi)
{
Queue<int> *pqi = &qi;
//Queue конкретизируется в результате вызова функции-члена
pqi->add(255);
…
}
Определение Queue<int> становится известным компилятору еще до вызова функции-члена add() из foo2().
Напомним, что в определении шаблона класса Queue есть также ссылка на шаблон QueueItem:
template <class Type>
class Queue {
public:
// ...
private:
QueueItem<Type> *front;
QueueItem<Type> *last;
};
При конкретизации Queue типом int члены front и last становятся указателями на QueueItem. Следовательно, конкретизированный экземпляр Queue<int> ссылается на экземпляр QueueItem, конкретизированный типом int. Но поскольку соответствующие члены являются указателями, то QueueItem<int> конкретизируется лишь в момент их разыменования в функциях-членах класса Queue<int>.
Наш класс QueueItem служит вспомогательным средством для реализации класса Queue и не будет непосредственно употребляться в вызывающей программе. Поэтому пользовательская программа способна манипулировать только объектами Queue. Конкретизация шаблона QueueItem происходит лишь в момент конкретизации шаблона класса Queue или его членов. (Далее рассмотрим конкретизации членов шаблона класса.)
В зависимости от типов, которыми может конкретизироваться шаблон, при его определении надо учитывать некоторые нюансы. Почему, например, следующее определение конструктора класса QueueItem не подходит для конкретизации общего вида?
template <class Type> class …
Если конкретизация производится для объемного типа (скажем, Matrix), то накладные расходы могут стать неприемлемыми. Поэтому аргумент конструктора объявляется как ссылка на константный тип:
QueueItem( const Type & );
Следующее определение приемлемо, если у типа, для которого конкретизируется QueueItem, нет ассоциированного конструктора:
template <class Type>
class QueueItem {
// ...
public:
// потенциально неэффективно
QueueItem(const Type &t ) {
item = t; next = 0;
}
};
Если аргументом шаблона является тип класса с конструктором (например, string), то item инициализируется дважды! Конструктор по умолчанию string вызывается для инициализации item перед выполнением тела конструктора QueueItem. Затем для созданного объекта item производится почленное присваивание. Избежать такого можно с помощью явной инициализации item в списке инициализации членов внутри определения конструктора QueueItem:
template <class Type>
class QueueItem {
// ...
public:
//item инициализируется в списке инициализации членов конструктора
QueueItem( const Type &t )
: item(t) {next = 0;}
};