Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Программа ГЭ_спец_2012 ответы light.doc
Скачиваний:
31
Добавлен:
15.11.2019
Размер:
3.71 Mб
Скачать
  1. Параметрический полиморфизм: шаблонные классы и шаблонные функции - назначение, параметризованные типы данных, синтаксис и семантика.

Параметрический полиморфизм

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

В С++ ключевое слово template используется для обеспечения параметрического полиморфизма (parametric polymorphism). Он позволяет применять один и тот же код к разным типам, причем тип является параметром тела кода. Параметрический поли¬морфизм — форма обобщенного программирования. Многие из наших классов ис¬пользовались для хранения данных конкретного типа. Данные обрабатывались од¬ним и тем же способом независимо от типа. Определения шаблонных классов и шаблонных функций делают возможным повторное использование кода простым, безопасным с точки зрения типов образом, что позволяет компилятору автоматизи¬ровать процесс инстанцирования (instantiation) типа. Оно выполняется, когда факти¬ческий тип заменяет тип-параметр, присутствующий в коде шаблона.

Полиморфизм: способность принимать различные формы

Синтаксис объявления класса предваряется конструкцией:

template <class идентификатор>

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

Наследование (inheritance) — это механизм получения нового класса из существую¬щего. Существующий класс может быть дополнен или изменен для создания произ¬водного класса. Наследование — это мощный механизм повторного использования кода. С помощью наследования может быть создана иерархия родственных типов, которые совместно используют код и интерфейсы.

Производный класс наследует описание базового класса; затем он может быть изменен добавлением новых членов, изменением существующих функций-членов и изменением прав доступа. C++ поддерживает виртуальные функции-члены (virtual member function). Это функции, объявленные в базовом классе и переопределенные в производных классах. Иерархия классов, которая определена открытым наследованием, создает родствен¬ный набор пользовательских типов, на все объекты которых может указывать указа¬тель базового класса. Получая доступ к виртуальной функции с помощью этого указателя, C++ выбирает надлежащее определение функции на этапе выполнения. Объект, на который направлен указатель, должен нести в себе информацию о типе, так чтобы его можно было распознать динамически; в этом заключается характерная особенность кода на C++. Каждый объект «знает», как на него можно воздействовать. Такая форма полиморфизма называется чистым полиморфизмом (pure polymorphism).

Производный класс

Класс можно сделать производным от существующего с использованием следующей формы:

class имя_класса :

(public I protected I private) имя__базового_класса

{объявления членов};

Производный класс является модификацией базового класса; он наследует защи¬щенные и открытые члены базового класса. Не могут наследоваться только конст¬рукторы, деструктор базового класса и любые функции-члены operator= ().

Виртуальные функции

Перегруженная функция-член вызывается с учетом алгоритма соответствия типов, в который входит правило соответствия неявного аргумента объекту данного типа класса. Все это известно на этапе компиляции и позволяет компилятору напрямую вы¬бирать надлежащий член. Ключевое слово virtual служит спецификатором функции, и как раз и предоставляет подобный механизм, но оно может применяться для измене¬ния объявлений только функций-членов. Сочетание виртуальных функций и откры¬того наследования станет для нас наиболее обобщенным и гибким способом построе¬ния программного обеспечения. Это — форма чистого полиморфизма.

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

Абстрактные базовые классы

Иерархия типов обычно имеет корневой класс, содержащий некоторое число вирту¬альных функций. Виртуальные функции обеспечивают динамическую типизацию. Виртуальные функции корневого класса часто являются фиктивными функциями. Они имеют пустое тело в корневом классе, но в производных классах им будет при¬дан конкретный смысл. В C++ для этих целей введена чисто виртуальная функция (pure virtual function). Чисто виртуальная функция — это виртуальная функция, тело которой не определено. Она объявляется внутри класса следующим образом:

virtual прототип_функции = 0;

Чисто виртуальная функция используется для того, чтобы отложить выбор реализа¬ции функции. В терминологии ООП это называется отложенным методом (deferred method).

Класс, имеющий хотя бы одну чисто виртуальную функцию, называется абстрак¬тным классом (abstract class).

Множественное наследование

До сих пор примеры в тексте требовали лишь одиночного наследования (single inheritance): класс производится от единственного базового класса. Это может привести к цепочке

наследований, когда класс В производится от класса А, класс С — от класса В.....

а класс N — от класса М. В результате N завершает цепь, имея в основе А, В,..., М. Однако эта цепочка не должна быть замкнутой — класс не может быть предком само¬го себя.

  1. Разработка объектно-ориентированных программ: конструирование иерархий классов - принципы построения, рекомендации, примеры; создание и использование объектов во время исполнения программы, организация массивов объектов; использование конструкторов, вызывающих другие конструкторы, методика тестирования объектно-ориентированных программ.

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

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

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

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

Рекомендации:

• Объекты следует называть существительными: 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;

}

// Подключаем библиотеку стандартного ввода/вывода

#include <iostream>

using namespace std;

// Начало программы

int main()

{

// Определяем переменные (объекты)

myclass o[4];

int i;

// Устанавливаем значения внутренних переменных для каждого

// объекта

for(i=0;i<4;i++)o[i].set_val(i);

// Выводим значения значения хранимые объектами

for(i=0;i<4;i++)cout << o[i].get_val();

cout << "\n";

return 0;

}

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

Пример:

// Начало определения класса

class myclass

{

// Определение закрытых переменных и функций

private:

int iVal;

// Определение открытых переменных и функций

public:

myclass(int val);

void set_val(int val);

int get_val();

};

void myclass::myclass(int val)

{

iVal=val;

}

void myclass::set_val(int val)

{

iVal=val;

}

int myclass::get_val()

{

return iVal;

}

// Подключаем библиотеку стандартного ввода/вывода

#include <iostream>

using namespace std;

// Начало программы

int main()

{

// Определяем переменные (объекты)

myclass o[]={-2,1,3,5,-4};

int i;

// Выводим значения значения хранимые объектами

for(i=0;i<5;i++)cout << o[i].get_val();

cout << "\n";

return 0;

}

Как и при использовании стандартных типов, в языке C++ имеется возможность

создания многомерных массивов объектов.

Пример:

// Начало определения класса

class myclass

{

// Определение закрытых переменных и функций

private:

int iVal;

// Определение открытых переменных и функций

public:

myclass(int val);

void set_val(int val);

int get_val();

};

void myclass::myclass(int val)

{

iVal=val;

}

void myclass::set_val(int val)

{

iVal=val;

}

int myclass::get_val()

{

return iVal;

}

// Подключаем библиотеку стандартного ввода/вывода

#include <iostream>

using namespace std;

// Начало программы

int main()

{

// Определяем переменные (объекты)

myclass o[3][2]=

{

{ -2, 4},

{ 1, 7},

{ 3, -4}

}

int i,j;

// Выводим значения значения хранимые объектами

for(i=0;i<3;i++)

{

for(j=0;j<2;j++)cout << o[i][j].get_val();

cout << "\n";

}

return 0;

}

Указатели на объекты

Поскольку объекты это практически те же самые структуры, то и синтаксис обращения к членам класса им аналогичен, то есть, обращение к членам класса происходит по средствам стрелки ->. Адресная арифметика, используемая при работе с объектами аналогична адресной арифметики для любого типа данных языков C/C++.

Пример:

// Начало определения класса

class myclass

{

// Определение закрытых переменных и функций

private:

int iVal;

// Определение открытых переменных и функций

public:

myclass(int val);

void set_val(int val);

int get_val();

};

void myclass::myclass(int val)

{

iVal=val;

}

void myclass::set_val(int val)

{

iVal=val;

}

int myclass::get_val()

{

return iVal;

}

// Подключаем библиотеку стандартного ввода/вывода

#include <iostream>

using namespace std;

// Начало программы

int main()

{

// Определяем переменные (объекты)

myclass o[]={-2,1,3,5,-4};

myclass *p;

int i;

// Выводим значения значения хранимые объектами

p=&o;

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

cout << p->get_val();

p++;

cout << "\n";

return 0;

}

Методика тестирования объектно-ориентированных программ:

На первом уровне проводится тестирование методов каждого класса программы, что соответствует этапу модульного тестирования.

На втором уровне тестируются методы класса, которые образуют контекст интеграционного тестирования каждого класса.

На третьем уровне протестированный класс включается в общий контекст (дерево классов) программного проекта. Здесь становится возможным отслеживать реакцию программы на внешние события.

  1. Обработка исключений: запуск исключений, перезапуск исключений, выражения исключений, спецификация исключения, пробные блоки 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.

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

Стандартные исключения предоставляются компиляторами С++ и поставщиками библиотек. Например, компилятором Borland запускается исключение типа xalloc если оператору new не удается выделить место в свободной памяти. Исключения стандартной библиотеки наследуются от базового класса exception.

Восстановление после ошибок главным образом касается написания корректных программ. Обработка исключений связана с восстановлением после сбоев. Кроме того, это механизм передачи управления. Любое восстановление после сбоя основано на передаче управления.

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