
- •Класс и атд
- •Классификация методов
- •Действия над объектами
- •Способы задания доступа
- •Область видимости класса
- •Указатель this
- •Функции-члены типа static и const
- •Перегрузка операторов
- •Доступные и недоступные для перегрузки операторы:
- •Перегрузка обычными функция, как компонентными
- •Примеры перегрузки операторов:
- •56. Параметрический полиморфизм: шаблонные классы и шаблонные функции - назначение, параметризованные типы данных, синтаксис и семантика.
56. Параметрический полиморфизм: шаблонные классы и шаблонные функции - назначение, параметризованные типы данных, синтаксис и семантика.
Полиморфизм — возможность объектов с одинаковой спецификацией иметь различную реализацию. Используя параметрический полиморфизм можно создавать универсальные базовые типы. В случае параметрического полиморфизма, функция реализуется для всех типов одинаково и таким образом функция реализована для произвольного типа. В параметрическом полиморфизме рассматриваются параметрические методы и типы.
Особенностью концепций шаблонов явл представление использующегося типа не в виде специфического (например, int), а с помощью названия, вместо которого м.б. подставлен любой тип. Объявление шаблона имеет вид:
template <шаблонные_аргументы> объявление,
где шаблонные аргументы – class идентификатор или объявление аргумента.
Шаблон функции позволяет определять семейство функций. Это семейство характеризуется общим алгоритмом, который может применяться к данным различных типов. При этом задание конкретного типа данных для очередного варианта функции обеспечивается специальной синтаксической конструкцией, называемой списком параметров шаблона функции. Объявление функции, которому предшествует список параметров шаблона, называется шаблоном функции.
Каждый параметр шаблона состоит из служебного слова class, за которым следует идентификатор. (В контексте объявления шаблона функции служебное слово class не несёт никакой особой смысловой нагрузки). В заголовке шаблона имена параметров шаблона должны быть уникальны.
Следом за заголовком шаблона располагается прототип или определение. Как известно, у прототипа и определения функции также имеется собственный заголовок. Этот заголовок состоит из спецификатора возвращаемого значения, имя функции и список параметров. Все до одного идентификаторы из заголовка шаблона обязаны входить в список параметров функции. В этом списке они играют роль спецификаторов типа.
Шаблонные классы
Шаблон класса дает обобщенное определение семейства классов, использующее произвольные типы или константы. Шаблон определяет элементы данных и элементы-функции класса. После определения шаблона класса вы можете предписать компилятору генерировать на его основе новый класс для конкретного типа или константы.
Определение: template <class T>
class ClassName;
Параметр-тип шаблона состоит из ключевого слова class или typename (в списке параметров они эквивалентны), за которым следует идентификатор. (Ключевое слово typename не поддерживается компиляторами, написанными до принятия стандарта C++) Оба ключевых слова обозначают, что последующее имя параметра относится к встроенному или определенному пользователем типу. Например, в приведенном выше определении шаблона ClassName имеется один параметр-тип T. Допустимым фактическим аргументом для T является любой встроенный или определенный пользователем тип, такой, как int, double, char*, complex или string.
У шаблона класса может быть несколько параметров-типов:
template <class T1, class T2, class T3>
class Container;
Однако ключевое слово class или typename должно предшествовать каждому. Следующее объявление ошибочно:
// ошибка: должно быть <typename T, class U> или
// <typename T, typename U>
template <typename T, U>
class collection;
57. Наследование и иерархия классов: реализация механизма повторного использования кода, полиморфная обработка родственных объектов – чистый полиморфизм, отношения наследования, построение производного класса специализацией базового, построение базового класса обобщением производного, иерархия классов, наследование интерфейса, наследование реализации, виртуальные функции, абстрактные базовые классы, множественное наследование, тестирование системы классов, связанных отношением наследования.
Наследование — это мощный механизм повторного использования кода. С помощью наследования может быть создана иерархия родственных типов, которые совместно используют код и интерфейсы.
Многие полезные типы являются вариантами других, и часто бывает утомительно создавать для каждого из них один и тот же код. Производный класс наследует описание базового класса; затем он может быть изменен добавлением новых членов, изменением существующих функций-членов и изменением прав доступа.
Элементы методологии объектно-ориентированного проектирования:
1. Выбор надлежащей совокупности типов.
2. Проектирование взаимосвязей между типами и применение наследования для использования общего кода.
3. Использование виртуальных функции для .полиморфной обработки родственных объектов.
Ключевое слово protected введено для того, чтобы сохранить сокрытие данных для членов, которые должны быть доступны из производного класса, но в других случаях действуют как закрытые (private). Это промежуточная форма доступа между public и private.
C++ поддерживает виртуальные функции-члены (virtual member function). Это функции, объявленные в базовом классе и переопределенные в производных классах.
Чистый полиморфизм основан на виртуальных функциях:
Чистый полиморфизм имеет место, когда одна и та же функция применяется к аргументам различных типов
В случае чистого полиморфизма имеется одна функция (тело кода) и несколько ее интерпретаций
Реализация чистого полиморфизма возможна только при наличии полиморфных переменных, а точнее полиморфных аргументо
Чистый полиморфизм позволяет реализовывать обобщенные алгоритмы
ООП позволяет организовать совокупности классов с помощью отношения наследования. При создании нового класса его можно объявить наследником одного или нескольких базовых классов. В этом случае наследник наследует все св-ва и поведения родительского класса это означает , что в классе наследнике определены все переменные и методы родительского класса. В классе наследнике можно определить новые св-ва и методы либо переопределить существующие методы базового класса.
Концептуально, производный класс является специализацией базового класса. Например, при наличии базового класса Animal, возможно наличие одного производного класса, который называется Mammal, и еще одного производного класса, который называется Reptile. Mammal является Animal, и Reptile является Animal, но каждый производный класс представляет разные специализации базового класса.
Класс можно сделать производным от существующего с использованием следующей формы:
class имя_класса :(public | protected | private) имя__базового_класса
{объявления членов};
Ключевое слово class как всегда можно заменить ключевым словом struct, если, подразумевается, что все члены открыты. Одним из аспектов производного класса является видимость (открытость) его членов-наследников. Ключевые слова public, protected и private используются для указания того, насколько члены базового класса будут доступны для производного.
Class Animal{…}
Class Mammal:public Animal{…}
Class Reptile:public Animal{…}
Множество связанных между собой классов обычно называют иерархией классов.
Наследование интерфейса (открытое наследование):
Использование в заголовке производного класса ключевого слова public, следующего за двоеточием, означает, что защищенные и открытые (protected и public) члены базового класса должны насл-ся как защищенные и откр-е члены производного. Закрытые члены базового класса недоступны производному классу.
Открытое наследование означает также, что производный класс является подтипом базового (например, аспирант является студентом, но студент не обязательно должен быть аспирантом).
Наследование реализации (закрытое наследование):
При закрытом наследовании мы повторно используем код базового класса, но не предполагаем рассматривать объекты производного класса как экземпляры базового. Мы будем называть закрытое наследование наследованием реализации. Оно оказывается полезным при построении схем отношений, классов в сложной программной системе. Поскольку закрытое наследование не создает иерархии типов, оно имеет более ограниченное применение, нежели открытое наследование.
Часто повторное использование кода — это все, что мы хотим от наследования.
Class Animal{…}
Class Mammal:private Animal{…}
Class Reptile:private Animal{…}
Виртуальные функции
Перегруженная функция-член вызывается с учетом алгоритма соответствия типов, в который входит правило соответствия неявного аргумента объекту данного типа класса. Все это известно на этапе компиляции и позволяет компилятору напрямую выбирать надлежащий член. Как станет очевидно, было бы неплохо динамически (на этапе выполнения) выбирать соответствующую функцию-член среди функций базового и производного классов. Ключевое слово virtual служит спецификатором функции, и как раз и предоставляет подобный механизм, но оно может применяться для изменения объявлений только функций-членов. Сочетание виртуальных функций и открытого наследования станет для нас наиболее обобщенным и гибким способом построения программного обеспечения. Это — форма чистого полиморфизма.
class В { public:
int i ;
virtual void print_i() const
{ cout << i « " внутри В" << endl; } };
class D : public В { public:
//тоже виртуальная void print_i() const
{ cout << i << " внутри D" << endl; } };
int main() {
В b;
B* pb = &b; //указывает на объект В
Dd;
d.i = 1 + (b.i = 1);
pb -> print_i(); //вызов B::print_i() pb = &d; //указывает на объект D
pb -> print_i(); //вызов D::print_i()
}
Вот что выведет эта программа:
1 внутри В
2 внутри D
Абстрактные базовые классы
Иерархия типов обычно имеет корневой класс, содержащий некоторое число виртуальных функций. Виртуальные функции обеспечивают динамическую типизацию. Виртуальные функции корневого класса часто являются фиктивными функциями. Они имеют пустое тело в корневом классе, но в производных классах им будет придан конкретный смысл. В C++ для этих целей введена чисто виртуальная функция (виртуальная функция, тело которой не определено). Она объявляется внутри класса следующим образом:
virtual прототип_функции = 0;
Чисто виртуальная функция используется для того, чтобы отложить выбор реализации функции. В терминологии ООП это называется отложенным методом (deferred method).
Класс, имеющий хотя бы одну чисто виртуальную функцию, называется абстрактным классом (abstract class). Полезно, чтобы корневой тип в иерархии классов был абстрактным классом. Он должен иметь основные общие свойства со своими производными классами, но сам не может использоваться для объявления объектов. Вместо этого он используется для объявления указателей, которые могут иметь доступ к подобъектам, произведенным из абстрактного класса.
Множественное наследование
Множественное наследование (multiple inheritance) делает возможным получение производного класса от более чем одного базового класса. Синтаксис заголовка класса расширяется, чтобы можно было использовать список базовых классов с атрибутами доступа. Например:
class student { //студент
};
class worker { //рабочий
};
class student_worker: public student, public worker {
..... //работающий студент
};
Наследование и проектирование
С одной стороны, наследование — это техника совместного использования кода. С другой стороны, оно отражает представления о решаемой задаче. Наследование выражает взаимосвязи между частями, на которые можно разбить моделируемую проблему. Открытое наследование большей части является выражением отношения ISA (быть экземпляром) между базовым и производным классами. Прямоугольник — это фигура. Такая концепция позволяет сделать фигуру суперклассом и перенести поведение, описываемое его открытыми функциями-членами, на другие объекты внутри иерархии типа. Другими словами, подклассы, полученные из этого суперкласса, используют общий с ним интерфейс.
Невозможно предложить во всех отношениях оптимальный проект. Проект — это всегда «торг» между различными целями, которые мы хотим достичь. Например, обобщенность часто противоречит эффективности. Применение иерархии классов, которая выражает ISA-отношения, помогает понять, как разделить код на разумные, логически самостоятельные части, но потенциально привносит неэффективность.
58. Разработка объектно-ориентированных программ: конструирование иерархий классов - принципы построения, рекомендации, примеры; создание и использование объектов во время исполнения программы, организация массивов объектов; использование конструкторов, вызывающих другие конструкторы, методика тестирования объектно-ориентированных программ.
Иерархия классов. Часто подразумевают гомоморфную иерархию— иерархия классов с одинаковым открытым интерфейсом, унаследованным от общего базового класса. Во главе гомоморфной иерархии классов всегда стоит абстрактный базовый класс, который определяет открытый интерфейс своих предков. Как правило, он является чисто абстрактным классом — то есть он не содержит ни одной переменной, а все его функции являются виртуальными.
Итеративный подход к классификации накл-ет соотв-ий отпечаток и на процедуру конструирования иерархии классов и объектов при разработке сложного программного обеспечения. На практике обычно за основу берется какая-то определенная структура классов, которую постепенно совершенствуют. Можно создать новый подкласс из уже существующих (вывод), или разделить большой класс на много маленьких (факторизация), или слить несколько существующих в один (композиция). Возможно, в процессе разработки будут найдены новые общие св-ва, ранее не замеченные, и можно будет опр-ть новые классы (абстракция).
Рекомендации по именованию классов и объектов:
Объекты следует называть существительными: theSensor или shape.
Классы следует называть обобщенными существительными: Sensors, Shapes.
Операции-модификаторы следует называть активными глаголами: Draw, moveLeft.
У операций-селекторов в имя должен включаться запрос или форма глагола "to be": extentOf, isOpen.
Подчеркивание и исп-е заглавных букв - на ваше усмотрение, постарайтесь лишь не противоречить сами себе.
Любому объекту требуется память и начальное зн-е. В С++ это обесп-ся с помю объявлений, явл одновременно опр-ми.
Массивы объектов
Объекты — это переменные, они имеют те же возм-ти и признаки, что и переменные любых др типов. Поэтому вполне доп-мо упаковывать объекты в массив. Синтаксис объявления массива объектов совершенно аналогичен тому, кот исп-ся для объявл-я массива переменных любого др типа. Доступ к массивам объектов аналогичен доступу к масс пер-х любого др типа языка C.
Пример:
class myclass
{private:
int iVal;
public:
void set_val(int val);
int get_val();};
void myclass::set_val(int val)
{ iVal=val;}
int myclass::get_val()
{return iVal;}
int main()
{myclass o[4]; //объявл массива объектов
myclass o[]={-2,1,3,5,-4}; //инициализация массива зн-ми
myclass o[3][2]={{ -2, 4},{ 1, 7},{ 3, -4}} //двумерн массив
return 0; }
Методика тестирования объектно-ориентированных программ:
На первом уровне проводится тестирование методов каждого класса программы, что соответствует этапу модульного тестирования.
На втором уровне тестируются методы класса, которые образуют контекст интеграционного тестирования каждого класса.
На третьем уровне протестированный класс включается в общий контекст (дерево классов) программного проекта. Здесь становится возможным отслеживать реакцию программы на внешние события.
59. Обработка исключений: запуск исключений, перезапуск исключений, выражения исключений, спецификация исключения, пробные блоки try, обработчики исключений catch, иерархии классов для управления исключительными ситуациями, обработка исключений как механизм восстановления после сбоев и как механизм передачи управления, модель завершения, используемая в С++.
Конструкция throw выражение возбуждает исключение. Запущенное с помощью throw выражение – это временный статический объект, существующий до тех пор, пока не будет произведен выход из обработчика исключения.
Использование throw без выражения перезапускает перехваченное исключение. Catch, который перезапускает исключение, не может заканчивать обработку существующего исключения. Он передает управление в ближайший окружающий блок try, где вызывается обработчик, способный отловить все еще существующее исключение.
Запущенное исключение «передает» информацию обработчикам. Часто обработчики не нуждаются в этой информации, но пользователь может пожелать, чтобы выводилась дополнительная информация, которую можно было бы использовать как подспорье в выборе надлежащих действий. В таком случае следует упаковать нужную информация в объект:
class vect_error {
private:Enum error {bounds, heap, other} e_type; //тип ошибки: границы, нет памяти, другое
Int ub, index, size; //верхняя граница, индекс, размер
public:vect_error (error, int, int); //
vect_error (error, int);
…};
Спецификации исключений. В определении функции можно указать, исключения какого типа она может выбрасывать. Спецификация исключений для функции:
<тип> FuncName(<список параметров>) throw([<тип>
[, <тип> ...]])
{<тело функции>}
Функция может выбрасывать только типы, перечисленные в списке после ключевого слова throw. Если этот список пустой, то функция вообще не должна выбрасывать никаких исключений.
Обработка непредвиденных исключений.. Если функция, снабженная спецификацией исключений, выбрасывает непредвиденное, т. е. не указанное в спецификации, исключение, вызывается функция unexpected () . По умолчанию последняя просто вызывает terminate () . Можно указать свою собственную функцию, которая должна активироваться при появлении непредвиденных исключений, вызвав set_unexpected (). Прототип ее находится в файле except.h:
typedef void (_RTLENTRY *unexpected_function)();
unexpected_function _RTLENTRY set_unexpected(unexpected_function);
Возвращается указатель на предыдущую функцию обработки. Процедура для непредвиденных исключений не должна возвращать управление. Она может либо аварийно завершить программу, либо выбросить исключение.
Основные синтаксические конструкции:
Общий синтаксис обработки исключения в C++ такой:
try { // Начало "пробного блока".
throw выражение; / / "Выбрасывание" исключения.
} catch(тип переменная) { // Заголовок обработчика для <типа>.
тело_обработчика) [catch ...] // Возможно, обработчики других типов.
Блок try. Ключевое слово try начинает пробный блок операторов, показывая, что данный блок может генерировать исключение:
try {
cout << "Входим в пробный блок..."<< end.1;
DangerousFunc(); // Вызов процедуры, способной генерировать исключение.
} // Конец try-блока.
Блоки try могут быть вложенными.
Блок catch. За пробным блоком следует один или несколько обработчиков исключения, начинающихся ключевым словом catch. За ним следует объявление исключения в круглых скобках, аналогичное формальному параметру функции:
try {}
catch(int. i) { // Перехватывает исключения типа int.}
catch(char* str) { // Перехватывает char*.}
catch (...) { // Перехватывает все остальное.
Если тип выброшенного в пробном блоке исключения совпадает или совместим с типом в объявлении некоторого обработчика, то данный обработчик перехватывает исключение. Если нет, то поиск подходящего обработчика продолжается далее.
Если пробный блок не генерировал никакого исключения, управление, по выходе из него, передается первому оператору, следующему за последним из обработчиков исключений.
Восст-е после ошибок главным образом касается написания корректных прог. Обр-ка искл-ий связана с восст-ем после сбоев. Кроме того, это механизм передачи упр-я. Любое восст-е после сбоя основано на передаче управления.
С++ использует модель завершения, которая заставляет завершиться текущий блок try. В этой модели можно либо повторить попытку, либо игнорировать исключение, либо подставить результат по умолчанию и продолжить.