Файл: Методические указания по выполнению лабораторных и самостоятельных работ для обучающихся с применением дистанционных.pdf

ВУЗ: Не указан

Категория: Не указан

Дисциплина: Не указана

Добавлен: 12.01.2024

Просмотров: 78

Скачиваний: 2

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.

24
Максимальный балл за выполнение работы – 5, а минимальный проход- ной – 3. Если текущая оценка за выполненную работу ниже 3 баллов, то она должна быть переработана и отправлена на повторное рецензирование. Если ито- говое число баллов менее 3, то лабораторная работа считается незачтенной
(незачет).

25
3
САМОСТОЯТЕЛЬНАЯ РАБОТА № 2 «ШАБЛОНЫ»
Чужие слабости надо уважать.
Х/ф «Жмурки»
3.1
Цели и задачи работы
Цель работы – изучить особенности разработки и использования шаблонов функций в языке C++.
Для достижения поставленной цели необходимо решить следующие задачи:
 ознакомиться с представленными тестовыми примерами по разработке шаблонов функций в языке C++;
 разработать программные коды для реализации предписанных заданием шаблонных функций.
3.2
Краткие теоретические сведения
и демонстрационные примеры
3.2.1 Механизм шаблонов
Шаблоны позволяют давать обобщенные определения функций и классов, поэтому их часто называют «шаблонные функции» и «шаблонные классы».
Кроме того, используются термины «параметризованные функции» и «парамет- ризованные классы».
Шаблон функции представляет собой обобщенное определение функции, из которого компилятор автоматически создает представителя функции для задан- ного пользователем типа (или типов) данных. Когда компилятор по шаблону функции создает ее конкретную реализацию, то говорят о порожденной функции.
При создании шаблона функции используется ключевое слово template, за которым следуют один или несколько параметров (обобщенные имена типов данных, используемые внутри шаблона), заключенных в угловые скобки и раз- деленные между собой запятыми. Вместо ключевого слова class может также использоваться ключевое слово typename (однако это справедливо только для

26 шаблона функции, но не шаблона класса, поэтому использование слова class более универсально).
Синтаксис определения шаблонной функции (квадратные скобки сигнали- зируют о том, что заключенные в них параметры могут отсутствовать) имеет сле- дующий вид:
Заголовок
функции
template
return_type func_name (arguments)
Тело
функции
{
}
Параметры шаблона, следующие за ключевыми словами class (или typename), называют параметризованными типами (T1, T2, ...), имена кото- рых могут быть произвольными. Они информируют компилятор о том, что неко- торый (пока что неизвестный) тип данных используется в шаблоне в качестве параметра (в момент вызова шаблона на их место подставляются требуемые типы).
Более общее определение шаблона позволяет указывать параметры, кото- рые не относятся к параметризованным типам, их иногда называют идентифика- торы (I1, ..., Ik). Они могут быть указаны в любом месте в угловых скобках, но рекомендуется указывать их после параметров шаблонного типа.
Синтаксис общего определения шаблона функции:
1   2   3   4   5

Заголовок
функции
template
... , type Ik]>
return_type funcn_name (arguments)
Тело
функции
{
}
В результате шаблонные функции можно вызывать как обычные функции, например, как показано в следующем программном коде. Результат его работы приведен на рисунке 3.1.

27 1 #include using namespace std; template
// Шаблон для вычисления квадрата
T Pow(T x){ return x*x;
} template
// Шаблон перестановки элементов
T* Swap(int I1, int I2, T* t) {
// здесь параметры
намеренно расположены в нерекомендуемом порядке для
демонстрации такой функциональности
T tmp = t[I1]; t[I1] = t[I2]; t[I2] = tmp; return t;
} int main( ){ int n=10, i = 2, j = 5; double d = 10.21; float arr[5] = {1.1, 2.2, 3.3, 4.4, 5.5}; string ss = "Template"; char* aa = &ss[0]; cout<< "The value of the n = " << n << endl << "The square of the n = " << Pow(n) << endl<< "The value of d
= " << d << endl << "The square of d = " << Pow(d) << endl << "The initial string = " << aa << endl << "The modified string = " << Swap(i, j, aa) << endl << "The initial array = "; for(auto i: arr) cout << i << " ";
cout << endl << "The modified array = ";
2 3
4 5
6 7
8 9
10 11 12 13 14 15 16 17 18 19 20 21 22 23

28 24 float * parr = Swap(0,4,arr); for(auto i: arr) cout << i << " ";
return 0;
}
25 26 27 28
The value of the n = 10
The square of the n = 100
The value of d = 10.21
The square of d = 104.244
The initial string = Template
The modified string = Template
The initial array = 1.1 2.2 3.3 4.4 5.5
The modified array = 5.5 2.2 3.3 4.4 1.1
Рис. 3.1 – Результат работы шаблонных функций
В приведенном примере вызовы шаблонной функции Sqr() и Sqr() для n и d приводят к построению двух порожденных функций для параметри- зованных типов, в данном случае это int и double соответственно. Аналогич- ное происходит и с шаблонной функцией Swap() с параметризованными ти- пами char* и float*. Поэтому процесс построения компилятором порожден- ной функции иногда называют конкретизацией шаблонной функции.
Стоит обратить внимание на то, что в приведенном примере при вызове шаблонных функций явно не были указаны параметризованные типы, поскольку компилятор по типу данных аргументов самостоятельно «выбрал» требуемые ре- ализации. Однако это не всегда возможно, поэтому рекомендуется явно указывать параметризованные типы при вызове шаблонных функций. В рассмотренном примере это соответствует
Pow (n),
Pow (d),
Swap (i, j, aa) и Swap (i, j, aa).
Как и для обычных функций, можно создать прототип шаблона функции в виде его предварительного объявления. Например:
«template < class T> Т* Swap(int I1, int I2 , T* t);».

29
При этом рекомендуется и объявление, и реализацию шаблонной функции помещать в заголовочный файл.
Важно помнить, что каждый параметризованный тип, указанный в списке параметров, должен использоваться в теле функции. Например, объявление
«template Т2 func (T2);» приведет к ошибке во время компиляции, т. к. тип T1 не используется в теле функции. (Подробное описание шаблонов дано в учебном пособии по дисци- плине [3].)
3.2.2 Вариативные шаблоны
До этого момента были рассмотрены шаблоны, принимающие строго опре- делённое число параметров, однако иногда требуется, чтобы это число могло из- мениться в зависимости от контекста выполнения программы. Для этого исполь- зуются вариативные шаблоны.
Так, вариативный шаблон (англ. variadic tempate) – это шаблон функции
(или класса), который поддерживает произвольное число параметров функции.
Набор таких параметров называется пакетом параметров (parameter pack).
Есть два вида пакетов параметров: пакет параметров шаблона (template
parameter pack), представляющий любое число параметров шаблона, и пакет па- раметров функции (function parameter pack), представляющий любое число пара- метров функции.
Для указания, что шаблону или функции представлен пакет параметров, используется оператор многоточие (...). При этом он используется в двух кон- текстах. Так, если он помещен слева от имени параметра, то он обозначает пакет
параметров,а справа – распаковку пакета параметров на отдельные имена. По- этому положение многоточия очень важно:
1 template void func1(std::vector v1);
// v1 НЕ является
пакетом параметров функции
2

30 3 template
// v2 ЯВЛЯЕТСЯ пакетом
параметров функции
void func2(std::vector... v2); template
// v3 ЯВЛЯЕТСЯ пакетом параметров
функции
void func3(const T&... v3);
4 5
6
Здесь, в записях vector... v2 и const T&... v3, много- точия обозначают пакеты параметров v2 и v3 соответственно.
Одна из сложностей использования вариативных шаблонов заключается в работе с его параметрами. Привычные механизмы индексации ([]) здесь не ра- ботают. Поэтому рассмотрим распаковку пакета с использованием рекурсии.
Так, вариативные шаблоны функций (как и аналогичные вариативные шаб- лоны классов) могут устанавливать требование о том, что должен быть передан по крайней мере один параметр. По сути, нужно подсказать компилятору, что первый параметр функции – это тип данных, а остальная часть – пакет. Их опре- деление имеет следующий вид:
template return_type func_name(const T& first, const U& ... rest);
Здесь параметр first является обязательным, а rest, обобщающий воз- можные остальные параметры, нет. Отметим, что в функции могут передаваться большие объекты, поэтому рекомендуется ее аргументы передавать по ссылке
(T&) или, что еще лучше, по константной ссылке (const T& и const U&).
Следующий программный код демонстрирует работы вариативного шаб- лона функции. Для его работы требуется сначала переопределить функцию с од- ним параметром. (Если по логике программы надо, чтобы функция также рабо- тала и без параметров, то необходимо сделать еще одну перегрузку функции под этот случай.) Результат работы приведен на рисунке 3.2.

31 1 #include using namespace std; template void func(const T& val){
// функция для обработки случая
с одним параметром
cout << val << endl;
} template
// U — это пакет
параметров шаблона
void func(const T& first, const U& ... rest){
// rest -
это пакет параметров функции
cout << first << ", "; func(rest...);
// распаковка пакета параметров за
счет рекурсивных вызовов функции с одним параметром
} int main(){ func(100); func("first", 1); func(2 , "second", 2); func("third", 3);
}
2 3
4 5
6 7
8 9
10 11 12 13 14 15 16 17 first, 1 2, second, 2 third, 3
Рис. 3.2 – Результат работы вариативной шаблонной функции
Стоит отметить, что в вариативных шаблонах используется опера- тор sizeof...(), который отличается от привычного оператора sizeof().

32 1 template void func(T ... arg) { cout << sizeof...(T) << endl;
// число параметров
пакета шаблона
cout << sizeof...(args) << endl;
// число параметров
пакета функции
}
2 3
4 5
3.2.3 Недостатки шаблонов
Шаблоны предоставляют определенные выгоды при программировании, связанные с широкой применимостью кода и легким его сопровождением. Этот механизм позволяет решать те же задачи, для которых используется полимор- физм. С другой стороны, в отличие от макросов, они позволяют обеспечить без- опасное использование типов данных.
Однако с использованием шаблонов связаны и некоторые недостатки:
 программа содержит полный код для всех порожденных представителей шаблонного класса или функций;
 не для всех типов данных предусмотренная реализация класса или функ- ции оптимальна.
Преодоление второго недостатка возможно с помощью специализации шаблонов или использования концептов (начиная с C++ 20). Отметим, что спе- циализация шаблонов подробно рассмотрена в учебном пособии по дисциплине
[3], а изучение концептов отводится на самостоятельное изучение.
3.3
Задание на работу
Разработать шаблонную функцию и продемонстрировать корректность ее работы для типов данных int, double и char. При необходимости использо- вать вариативные шаблоны. Разрабатываемая функция должна выполнять с за- данной матрицей (двумерного массива) произвольного размера совместно два

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

34
4
ЛАБОРАТОРНАЯ РАБОТА № 2 «НАСЛЕДОВАНИЕ
И ВИРТУАЛЬНЫЕ ФУНКЦИИ»
Нам с вами надо работать конструктивнее.
Надо, так сказать, начинать уже поэтапно, планомерно ломать вот эти, устоявшиеся стереотипы. А нам их с вами досталось, так сказать, в наследство... не мне вам не говорить.
Х/ф «Какраки»
4.1
Цели и задачи работы
Цель работы – приобрести практические навыки по использованию меха- низмов наследования и виртуальных функций при разработке объектно-ориен- тированных программ.
Для достижения поставленной цели надо решить следующие главные за- дачи:
 ознакомиться с теоретическими сведениями;
 программно реализовать иерархию классов;
 выполнить тестирование программного кода;
 оформить отчет по проделанной работе.
4.2
Теоретические сведения
4.2.1 Простое наследование
Язык C++ позволяет классу наследовать члены (поля и методы) одного или нескольких других классов. При этом новый класс называют производным (или классом-потомком). Класс, члены которого наследуются, называется базовым
(родительским классом или классом-предком) для своего производного класса.
Наследование дает возможность абстрагировать некоторые общие черты поведения классов в одном базовом классе. Производные классы, наследуя это общее поведение, могут его несколько видоизменять, переопределяя некоторые методы базового класса, или дополнять, вводя новые поля и методы. В результате

35 определение производного класса может быть значительно сокращено, т. к. нужно определить только эти новые члены, отличающие его от производных классов.
Если у производного класса имеется всего один базовый класс, то говорят о простом (или одиночном) наследовании. Синтаксис объявления производного класса имеет следующий вид:
1 class <имя класса> : [<спецификатор доступа>] <имя базового класса> {
<тело класса>
};
2 3
Спецификатор доступа – это одно из ключевых слов private, protected или public. Он не является обязательным и по умолчанию прини- мает значение private для классов (public для структур). Рассмотрим пример:
1 #include using namespace std; class Base{
// базовый класс
public: intSetX(int _x=0){ return x=_x;
} intGetX( ) { return x;
} protected:
// обозначает, что
int x;
// данное поле доступно данному классу и
всем его потомкам
}; class Derived : public Base{
// производный класс
2 3
4 5
6 7
8 9
10 11 12 13 14

36 15
// требуемый код
}; int main(){ int x;
Derived ob;
// создание объекта производного класса
x = ob.GetX( );
// обращение через этот объект к
полю и методу базового класса
return 0;
}
16 17 18 19 20 21 22
Спецификатор доступа при наследовании определяет уровень доступа к членам базового класса, который получают члены производного класса. В таб- лице 4.1 описаны возможные варианты наследуемого доступа. Из таблицы сле- дует, что при наследовании, как правило, происходит понижение доступа к эле- ментам базового класса.
Таблица 4.1 – Спецификаторы доступа в рамках иерархии наследования
Доступ в базовом классе
Спецификатор наследуемого доступа
Доступ в производном классе public protected private public public protected
Нет доступа public protected private protected protected protected
Нет доступа public protected private private private private
Нет доступа
Стоит помнить, что не все члены базового класса могут наследоваться. Так, не подлежат наследованию: конструкторы, в том числе конструкторы копирова- ния; деструкторы; перегруженные операторы присваивания; дружественные функции.

37
4.2.2 Переопределение методов
В производном классе обычно добавляются новые поля и методы, отсут- ствующие в базовом классе. Кроме того, можно переопределять методы базового класса. При этом очевидно, что определения таких методов в базовом и произ- водном классах должны совпадать, как это сделано в следующем программном коде на примере геттеров.
1 #include using namespace std; class MyInt{ public:
MyInt(int _x) {x = _x;}
MyInt( ){x = 0;} intGetX( ){returnx;}
// геттер базового класса
voidSetX(int _x) {x = _x;}
// сеттер базового класса
protected: int x;
}; class IncMyInt : public MyInt{ public:
IncMyInt(int _x) : MyInt(_x) {} intGetX( ) {return ++x;}
// переопределенный геттер
базового класса
voidSetX(int _x) {x = _x+900;}
// переопределенный
сеттер базового класса
void ShowX( ) {cout << GetX() << ' ';}
}; int main( ){
IncMyInt* ptr; ptr = newIncMyInt(10);
// создание объекта в куче
2 3
4 5
6 7
8 9
10 11 12 13 14 15 16 17 18 19 20 21

38 22 ptr->ShowX( ); delete ptr;
// освобождение памяти
return 0;
}
23 24 25
Здесь создается объект класса IncMyInt при помощи конструктора
(x = 10). Далее метод ShowX() этого объекта использует для получения зна- чения поля x переопределенный вариант метода (геттера) базового класса
GetX()
. В результате в консоль будет выведено инкрементированное значение поля x (x = 11).
Для того чтобы вызывать метод базового класса, а не его переопределен- ный вариант, используется оператор разрешения области видимости (::):
«<имя_класса>::<имя_метода>».
Это дает возможность компилятору «видеть» за пределами текущей области ви- димости. Так, переопределенный метод может вызывать соответствующий ме- тод базового класса, а затем выполнять некоторую дополнительную работу (или наоборот). Например, в предыдущем примере можно было переопределить ме- тод GetX() с помощью следующего кода:
1 intIncMyInt::GetX(){
// метод производного класса,
являющийся переопределенным методом базового класса
intz ; z = MyInt::GetX{);
// вызов метода базового
класса
return ++z;
}
2 3
4 5

39
4.2.3 Конструкторы и деструкторы
Как уже упоминалось ранее, конструкторы не наследуются, поэтому про- изводный класс должен объявить свой конструктор или предоставить возмож- ность компилятору генерировать конструктор по умолчанию. Поскольку произ- водный класс должен унаследовать все члены базового класса, то при его опре- делении должна быть обеспечена инициализация унаследованных полей, причем она должна быть выполнена до инициализации полей производного класса, так как последние могут использовать значения первых. Поэтому при определении конструктора производного класса применяется следующая конструкция:
«<имя класса>([<список аргументов>]): <имя базового класса>([<список аргументов>]){
// тело конструктора про-
изводного класса
}
».
Так, часть аргументов, переданных конструктору производного класса, ис- пользуется в качестве аргументов конструктора базового класса. Затем в теле конструктора производного класса выполняется инициализация его полей.
В приведенном выше примере указанная конструкция использована для опреде- ления конструктора класса IncMyInt:
«IncMyInt (int _x): MyInt (_x) {}».
Здесь тело конструктора оставлено пустым, так как класс IncMyInt не имеет собственных полей. При этом аргументы конструктора этого производ- ного класса переданы конструктору базового класса для инициализации его поля x.
В случае если реализованный конструктор производного класса не имеет аргументов (конструктор по умолчанию), то при создании его объекта автомати- чески вызывается конструктор базового класса. После того как объект создан, конструктор базового класса становится недоступным.
В отношении деструкторов производных классов также действуют опреде- ленные правила. Так, деструктор производного класса должен вызываться раньше деструктора базового класса (иначе деструктор базового класса мог бы разрушить данные полей, которые используются и в производном классе). После

40 того как деструктор производного класса выполнит свою часть работы по уни- чтожению объекта, вызывается деструктор базового класса. Причем вся работа по организации соответствующего вызова возлагается на компилятор.
4.2.4 Виртуальные методы
Работа с объектами класса чаще всего производится через указатели. Ука- зателю на базовый класс («MyInt *p;») можно присвоить адрес объекта лю- бого производного от него класса («р = new IncMyInt;»).
Продемонстрируем сказанное на примере следующего кода, в котором ис- пользованы описанные выше базовый класс MyInt и производный класс
IncMyInt
, не имеющие виртуальных методов. Результат работы этого про- граммного кода приведен на рисунке 4.1.
1 int main( ){
MyInt*p;
// указатель на базовый класс
p = new MyInt(0);
cout<
GetX() <// метод базового класса,
здесь результат 0
p->SetX(111);
// метод базового класса
cout<
GetX() <// метод базового класса,
здесь результат 111
p = newIncMyInt(100);
// здесь указатель ссылается на
производный класс
cout<
GetX() <// метод базового класса,
здесь результат 100
p->SetX(222); cout<
GetX() <// метод базового класса,
здесь результат 222
static_cast(p)->SetX(33333333);
//
(в стиле языка С – (IncMyInt* p)->SetX(33333333);) метод
ПРОИЗВОДНОГО класса
2 3
4 5
6 7
8 9
10 11

41 12 cout<
GetX() <// метод базового класса,
здесь результат 33334233
cout<(p)->GetX() <//метод ПРОИЗВОДНОГО класса, здесь результат 33334234
return 0;
}
13 14 15
Дадим некоторые пояснения по представленному коду. Для некоторого класса вызов методов через указатель происходит в соответствии с типом, ис- пользованным при создании этого указателя, а не фактическим (присвоенным) типом, поэтому при выполнении оператора разыменования (строки 4–6, а также
8–10 и 12) вызываются методы класса MyInt, а не класса IncMyInt. Это свя- зано с тем, что ссылки на методы устанавливаются во время компоновки про- граммы. Такой процесс основан на механизме раннего связывания. Тогда, чтобы вызвать методы класса IncMyInt, следует использовать явное приведение типа указателя (строки 11 и 13).
0 111 100 222 33334233 33334234
Рис. 4.1 – Результат работы без использования виртуальных методов
Механизм раннего связывания не всегда может быть корректно применим, поскольку в разное время работы программы (по задумке программиста) указа- тель может ссылаться на разные классы в иерархии наследования. Тогда, напри- мер, во время компиляции программы конкретный класс может быть неизвестен.
В качестве примера можно привести функцию, параметром которой является указатель на базовый класс. Тогда во время выполнения программы в функцию мо- жет быть передан указатель на один из производных классов. Другой пример –

42 это связный список указателей на различные классы, с которым требуется рабо- тать единообразно. Поэтому в C++ реализован также механизм позднего связы- вания, используемый, когда задание ссылок (через инициализацию указателя) на метод происходит не на этапе компиляции, а на этапе выполнения программы в зависимости от конкретного типа объекта, вызвавшего метод. Этот механизм ре- ализован с помощью виртуальных методов (иногда называемых виртуальными функциями).
Метод, который может быть переопределен в каждом производном классе так, что конкретная его реализация будет определяться во время исполнения про- граммы, называется виртуальным. Другими словами, виртуальный метод – это метод, который гарантирует, что для объекта класса будет вызван требуемый ме- тод независимо от того, какое выражение используется для осуществления его вызова. Для объявления виртуального метода используется ключевое слово virtual
, например:
«virtual void draw(int, int, int, int);».
Основные правила использования виртуальных методов:
1. Если в базовом классе метод объявлен как виртуальный, то метод, опре- деленный в производном классе с тем же именем и набором аргументов, также автоматически становится виртуальным, а с отличающимся набором аргумен- тов – обычным.
2. Виртуальные методы наследуются, поэтому переопределять их в произ- водном классе следует только при необходимости задания отличающихся дей- ствий. При этом спецификаторы доступа при переопределении изменять запре- щено.
3. Если виртуальный метод переопределен в производном классе, то объ- екты этого класса могут получить доступ к методу базового класса с помощью операции расширения области видимости (::).
4. Если в классе есть виртуальный метод, то, как минимум, он должен быть определен как чисто виртуальный (т. е. иметь только прототип), о чем сигнали- зирует признак «= 0», используемый вместо тела метода:

43
«virtual void method(int) = 0;».
Чисто виртуальный метод должен быть переопределен в каждом производном классе, минимально как виртуальный.
5. Виртуальный метод нельзя объявлять статическим (ключевое слово static), но можно – дружественным (ключевое слово friend).
Таким образом, виртуальным называется метод, ссылка (инициализация ука- зателя) на который определяется на этапе выполнения программы, т. е. по факту вызова метода (слово virtual следует понимать как «фактический»).
Для демонстрации работы виртуальных методов модернизируем про- граммный код класса MyInt, определив его метод GetX() как виртуальный
(строка 5). Тогда результат работы (рис. 4.2) будет значительно отличаться от представленного на рисунке 4.1, так как после инициализации указателя адресом объекта производного класса IncMyInt будет вызываться его метод GetX(), что позволяет отказаться от явного приведения типа, использованного ранее.
1 class MyInt{ public:
MyInt(int _x) {x = _x;}
MyInt( ){x = 0;} virtualintGetX( ){returnx;}
// геттер базового класса
voidSetX(int _x) {x = _x;}
// сеттер базового класса
protected: int x;
};
2 3
4 5
6 7
8 9
Рис. 4.2 – Результат работы с использованием виртуальных методов

44
Кратко поясним, как работает механизм позднего связывания. Для каждого класса (не объекта!), содержащего хотя бы один виртуальный метод, компилятор создает таблицу виртуальных методов (vtbl), в которую для каждого виртуаль- ного метода записан его адрес памяти. Адреса методов содержатся в таблице в порядке их объявления в классах. Адрес любого виртуального метода имеет в таблице vtbl одно и то же смещение для каждого класса в пределах иерархии.
Каждый объект класса содержит скрытое дополнительное поле, содержащее ссылку на таблицу vtbl и называемое vptr. Оно инициализируется конструк- тором при создании объекта (для этого компилятор добавляет в начало тела кон- структора соответствующие инструкции). На этапе компиляции ссылки на вир- туальные методы заменяются на обращения к таблице vtbl через поле vptr объекта, а при выполнении программы при обращении к методу его адрес выби- рается из таблицы. В результате вызов виртуального метода, в отличие от обыч- ных методов и функций, выполняется через дополнительный этап получения его адреса из таблицы vtbl. Очевидно, что это ведет к повышению затрат памяти и снижению быстродействия программы. Поэтому, работая с небольшим клас- сом, не являющимся базовым для других классов, использовать виртуальные функции не целесообразно.
Для того чтобы гарантировать правильное освобождение памяти под дина- мически созданные объекты, рекомендуется деструкторы классов делать вирту- альными, т. к. это позволяет гарантировать, что в любой момент времени будет выбран деструктор, соответствующий фактическому типу объекта. Деструктор передает операции delete размер объекта. Если удаляемый объект является эк- земпляром производного класса, то в нем не определен виртуальный деструктор и передаваемый размер объекта может оказаться некорректным.
Далее укажем особенности использования виртуальных деструкторов. Так, при удалении объекта производного класса, на который ссылается указатель ба- зового класса, если деструктор объявлен как виртуальный, будет вызван деструк- тор соответствующего производного класса. Затем деструктор производного

45 класса вызовет деструктор базового класса, и объект будет корректно удален (вся занятая память будет освобождена), иначе будет вызван только деструктор базо- вого класса, что в конечном счете приведет к утечке памяти. Для демонстрации сказанного модернизируем показанный выше программный код, добавив де- структоры в классы. Результат работы кода приведен на рисунке 4.3.
1 #include using namespace std; class MyInt{ public:
MyInt(int _x) {x = _x;}
MyInt( ){x = 0;} virtualintGetX( ){returnx;}
// геттер базового
класса
voidSetX(int _x){x=_x;}
// сеттер базового класса
MyInt( ){cout<< "destructor of base class Myint"
<// virtual MyInt( ){cout<< "destructor of base class
Myint" <
protected: int x;
}; class IncMyInt : public MyInt{ public:
IncMyInt(int _x) : MyInt(_x) {} intGetX( ) {return ++x;}
// переопределенный геттер
базового класса
voidSetX(int _x) {x = _x+900
;}// переопределенный
сеттер базового класса
void ShowX( ) {cout<2 3
4 5
6 7
8 9
10 11 12 13 14 15 16 17 18 19

46 20 IncMyInt(){cout<< "destructor of derived class
IncMyint" <}; int main( ){
MyInt* ptr = new IncMyInt(10); delete ptr; return 0;
}
21 22 23 24 25 26
Так как объявленный в производном классе IncMyInt деструктор не яв- ляется виртуальным, то при освобождении памяти (строка 24) вызывается только деструктор базового класса MyInt. При этом очевидно, что память под объект производного класса не освобождается (утечка памяти). destructor of base class Myint
Рис. 4.3 – Результат удаления только объекта базового класса
Если объявить деструктор базового класса виртуальным (закомментиро- вать строку 9 и раскомментировать строку 10), то деструктор производного класса также станет виртуальным. Тогда при освобождении памяти сначала от- работает деструктор производного класса, а затем базового (рис. 4.4). (При этом можно не использовать явное удаление объекта, т. к. компилятор сделает это са- мостоятельно, а результат будет один и тот же.) destructor of derived class IncMyint destructor of base class Myint
Рис. 4.4 – Результат удаления объектов базового и производного классов
Основные правила использования виртуальных методов:
 виртуальные методы следует использовать только при организации наследования, т. е. при наличии базового и производных классов;
 если в базовом классе имеются виртуальные методы, то следует исполь- зовать виртуальные деструкторы;

47
 запрещено создавать виртуальные конструкторы.
Дополнительно отметим, что механизм виртуальных методов работает только при использовании указателей или ссылок на объекты классов. Объект класса, определенный через указатель или ссылку и имеющий виртуальные ме- тоды, называется полиморфным. Полиморфизм здесь проявляется в том, что с помощью обращения к одному и тому же методу в зависимости от типа объ- екта, на который ссылается указатель в каждый момент времени, могут выпол- няться различные действия.
1   2   3   4   5


4.2.5 Абстрактные классы
Класс, содержащий хотя бы один чисто виртуальный метод, называется аб- страктным. Абстрактные классы предназначены для выявления (определения) общих понятий, которые предполагается конкретизировать в производных клас- сах. Абстрактный класс может использоваться только в качестве базового для других классов.
Общие правила работы с абстрактными классами:
 абстрактный класс не может использоваться в качестве типов аргумен- тов функций и методов;
 абстрактный класс не может использоваться в качестве типов возвраща- емых значений функций и методов;
 явное преобразование типа объекта к абстрактному классу запрещено;
 создавать объект абстрактного класса запрещено;
 создавать указатель или ссылку на абстрактный класс разрешено;
 объявлять деструктор абстрактного класса чисто виртуальным запре- щено, т. к. после вызова деструктора производного класса должен быть вызван деструктор базового класса, поэтому последний не может быть чисто виртуаль- ным. Тогда решение данной проблемы надо возложить на компилятор, используя следующее определение этого деструктора:
«virtual

MyInt( ) = default;».

48
Здесь ключевое слово default указывает компилятору, что он должен са- мостоятельно генерировать и использовать соответствующий деструктор.
Продемонстрируем сказанное на примере модернизированного программ- ного кода, где базовый класс объявлен как абстрактный. Результат его работы приведен на рисунке 4.5.
1 #include using namespace std; class MyInt{
// абстрактный класс
public:
// virtual MyInt( )=0; // ошибка компиляции, т. к.
запрещено создавать виртуальные конструкторы
virtual int GetX()=0;
// чисто виртуальный метод
virtual void SetX(int)=0;
//чисто виртуальный метод
virtual void ShowX()=0;
//чисто виртуальный метод
virtual MyInt()=default; protected: int x;
}; class IncMyInt : public MyInt{ public:
IncMyInt(int _x) {x = _x;} int GetX( ) {return ++x;}
// переопределенный
геттер базового класса
void SetX(int _x) {x = _x+900;}
// переопределенный
сеттер базового класса
void ShowX( ) {cout << GetX() << ' ';}
IncMyInt(){cout << "destructor of derived class
IncMyint" << endl;}
};
2 3
4 5
6 7
8 9
10 11 12 13 14 15 16 17 18 19 20

49 21 int main( ){
// MyInt* obj = new MyInt(0); // ошибка компиляции,
т. к. создавать объект абстрактного класса запрещено
// MyInt objBase(0); // ошибка компиляции, т. к.
создавать объект абстрактного класса запрещено
IncMyInt* pDerived = new IncMyInt(0);
// нет ошибки,
динамическое создание объекта производного класса
IncMyInt Derived(10);
// нет ошибки, статическое
создание объекта производного класса
cout << pDerived->GetX() << endl; cout << Derived.GetX() << endl; pDerived->SetX(123);
Derived.SetX(12345); cout << pDerived->GetX() << endl; cout << Derived.GetX() << endl; return 0;
}
22 23 24 25 26 27 28 29 30 31 32 33 1
11 1024 13246 destructor of derived class IncMyint
Рис. 4.5 – Результат работы при использовании абстрактного класса
Важно помнить, что можно создавать метод (функцию), аргументом кото- рого является указатель на абстрактный класс. Тогда при выполнении про- граммы в качестве этого аргумента можно передавать указатель на объект лю- бого производного класса. В результате, это позволяет создавать полиморфные методы, работающие с объектами любого класса в пределах одной иерархии наследования.

50
4.3
Программное и методическое обеспечение
При выполнении работы необходимо использовать программное и методи- ческое обеспечение из подразд. 2.3.
4.4
Задание для выполнения работы
Согласно индивидуальному заданию, не используя контейнеры библио-
теки шаблонов STL, следует разработать проект, в котором реализована иерар- хия наследования классов. Классы должны содержать конструкторы и деструк- торы, методы ввода и вывода информации на экран, а также предписанный зада- нием метод. При этом базовый класс должен быть определен как абстрактный, а предписанный метод должен быть определен как чисто виртуальный в базовом классе и переопределен в производных. При разработке производных классов их определения и реализации должны содержаться в раздельных файлах. Абстракт- ный класс должен быть помещен в отдельный файл. В отдельный файл также должна быть помещена функция main(), с помощью которой должна быть про- демонстрирована корректная работа разработанных классов.
4.5
Порядок выполнения работы
При выполнении работынеобходимо:
1. Создать проект (подразд. 2.3) с именем, состоящим из имени студента и номера учебной группы (например, Sidorov_169_1) и содержащим файлы с программным кодом, согласно индивидуальному заданию из подразд. 4.4 с учетом требований из подразд. 4.7. (Объявления классов поместить в заголо- вочные файлы *.h, а их реализацию – в исполняемые *.cpp. Имена этих фай- лов должны совпадать с именами разрабатываемых классов. Базовый класс по- местить в один заголовочный файл. Функция main(), создание объектов клас- сов и работа с ними должны быть помещены в файл main.cpp.)
2. Все элементы программного кода именовать согласно рекомендациям из приложения В учебного пособия по дисциплине [3].

51 3. При реализации методов классов предусмотреть обработку ошибочных ситуаций в части вводимой информации: индекс k выходит за рамки массива; индекс k меньше нуля или больше размера массива; размер массива меньше или равен нулю и др. (при необходимости).
4. Выполнить тестирование работы созданного проекта. Для этого в функ- ции main() создать объекты разработанных производных классов (с помощью считывания данных из консоли и при необходимости вывода в консоль) и проде- монстрировать работу переопределенных методов. При этом должно быть выпол- нено тестирование всех ветвлений работы методов класса как при корректном, так и при некорректном задании требуемых параметров (аргументов методов).
5. Оформить отчет по проделанной работе согласно требованиям из под- разд. 4.6.
4.6
Требования к оформлению отчета
Структура отчета должна включать:
 титульный лист;
 цель работы;
 основную часть (описательная часть, пояснения, результаты тестирования);
 выводы по работе;
 список использованной литературы;
 приложения, содержащие раздельные листинги файлов с программным кодом созданного проекта.
При оформлении текстовой части отчета следует руководствоваться тре- бованиями Образовательного стандарта ТУСУР 01-2021 «Работы студенческие по направлениям подготовки и специальностям технического профиля. Общие требования и правила оформления» (приказ ректора от 25.11.2021)
(
https://regulations.tusur.ru/documents/70
).
Отчет по работе должен включать:
 архив (RAR или ZIP) с программной частью лабораторной работы.
В архиве должны находиться все исходные файлы, включая файлы проектов

52 используемой среды разработки, а также файлы, необходимые для запуска про- граммы (исполняемый файл, файлы с входными данными и т. п.). Программный код должен быть построчно комментирован;
 документ с отчетом по лабораторной работе в формате DOC, DOCX,
RTF или PDF;
 рецензия на предыдущий вариант работы, если она сдается повторно по- сле исправления замечаний.
4.7
Варианты индивидуальных заданий
Работа выполняется согласно варианту индивидуального задания
(табл. 4.2), выбор которого осуществляется по следующей формуле:
V = NK div 100, где V – искомый номер варианта;
N – общее количество вариантов;
K – код варианта (индивидуальный код студента); div – целочисленное деление.
При V = 0 выбирается максимальный вариант.
Параметры, не указанные в таблице 4.2, но необходимые для работы клас- сов, выбираются на усмотрение обучающегося.
Таблица 4.2 – Варианты индивидуальных заданий
Вариант
Задание
1
Разработать абстрактный базовый класс фигура с виртуальным ме- тодом площадь и производные классы прямоугольник, квад- рат и прямоугольный треугольник со своими реализациями этого метода
2
Разработать абстрактный базовый класс с виртуальным методом норма и производные классы комплексное_число (с веще- ственной и мнимой частями типа double), вектор (10 элементов типа double) и матрица (размер 22, элементы типа double) со своими реализациями этого метода. Для реализации использовать абсолютное значение в квадрате, квадратный корень из суммы квад- ратов элементов и максимальную сумму из абсолютных значений элементов по строкам соответственно

53
Продолжение таблицы 4.2
Вариант
Задание
3
Разработать абстрактный базовый класс Фигура с виртуальным ме- тодом периметр и производные классы круг, прямоугольник и четырехугольник со своими реализациями этого метода
4
Разработать абстрактный базовый класс вектор (одномерный мас- сив произвольной длины) с виртуальным методом сумма (сумма элементов вектора) и производные классы вектор_целых_чисел
(int*), вектор_чисел_float (float*) и вектор_чи- сел_double (double*) со своими реализациями этого метода
5
Разработать абстрактный базовый класс поиск с виртуальным ме- тодом проверка_наличия введенного символа в объекте и про- изводные классы символ (char), строка (char *) и матрица
(char **) со своими реализациями этого метода
6
Разработать абстрактный базовый класс уравнение с виртуальным методом нахождения значения у по заданному значению х и произ- водные классы линейное_уравнение, квадратное_уравне- ние и кубическая_парабола со своими реализациями этого ме- тода
7
Разработать абстрактный базовый класс число с виртуальным ме- тодом изменение_знака_числа и производные классы це- лое_число (int), вещественное_число (double) и ком- плексное_число (float, float) со своими реализациями этого метода
8
Разработать абстрактный базовый класс прогрессия, состоящий из произвольного числа членов n, с виртуальным методом сумма_прогрессии и производные классы арифметиче- ская_прогрессия и геометрическая_прогрессия со сво- ими реализациями этого метода. Классы также должны содержать значения первых членов прогрессий a
0
, а также разность d (для арифметической) и постоянное отношение r (для геометрической).
Арифметическая прогрессия задается как a
j
= a
0
+ jd, где j = 1, 2, …,
n–1, а ее сумма вычисляется как s
n
= n(a
0
+ a
n–1
) / 2.
Геометрическая прогрессия задается как a
j
= a
0
r
j
, j = 1, 2, …n – 1, а ее сумма вычисляется как s
n
= (a
n–1
ra
0
) / (r – 1)
9
Разработать абстрактный базовый класс матрица (двумерный мас- сив произвольного порядка) с виртуальным методом поиск_мак- симального_значения и производные классы матрица_це- лых_чисел (int**), матрица_символов (char **) и мат- рица_вещественных_чисел (double**) со своими реализаци- ями этого метода

54
Окончание таблицы 4.2
Вариант
Задание
10
Разработать абстрактный базовый класс фигура с виртуальным ме- тодом объем и производные классы параллелепипед, тетра- эдр, шар со своими реализациями этого метода. Для вычисления объема параллелепипеда использовать формулу V = xyz (x, y и z – это стороны параллелепипеда); объема тетраэдра – V = √2a
3
/12 (a – сто- рона тетраэдра); объема шара – V = 4πR
3
/3 (R – радиус шара)
11
Разработать абстрактный базовый класс студент с виртуальным методом печать_информации и производные классы со своими реализациями этого метода: студент_младших_курсов (ФИО, номер учебной группы, факультет, средняя оценка); студент_ба- калавриата (ФИО, направление подготовки); студент_маги- стратуры (ФИО, тема диссертации, ФИО научного руководителя)
12
Разработать абстрактный базовый класс матрица (двумерный мас- сив произвольного порядка) с виртуальным методом по- иск_наиболее_часто_встречаемого_элемента и произ- водные классы матрица_целых_чисел (int**), мат- рица_символов (char**) и матрица_вещественных_чисел
(double**) со своими реализациями этого метода
13
Создать абстрактный базовый класс человек с виртуальным мето- дом печати_личных_данных и производные классы со своими реализациями этого метода: мужчина (ФИО, возраст, должность, статус, имя жены), женщина (ФИО, возраст, статус, имена мужа и детей) и ребенок (ФИО, возраст, имена матери и отца)
14
Разработать абстрактный базовый класс вектор (одномерный мас- сив произвольной длины) с виртуальным методом сорти- ровка_элементов_по_возрастанию и производные классы вектор_целых_чисел
(int*), вектор_чисел_float
(float*) и вектор_чисел_double (double*) со своими реа- лизациями этого метода
15
Разработать абстрактный базовый класс фигура с виртуальным ме- тодом площадь и производные классы круг, равнобедренный треугольник, овал со своими реализациями этого метода

55
4.8
Система оценивания
Для оценивания работы используется следующая балльная система (разде- ление программного кода на файлы является обязательным и дополнительно не оценивается, а каждый выполненный не в полной мере пункт оценивается как
0,5 балла):
 Разработаны классы согласно общему и индивидуальному заданиям
(подразд. 4.4 и 4.7) – 1 балл.
 Выполнено именование элементов программного кода (согласно реко- мендациям из приложения В учебного пособия по дисциплине [3]) и его коммен- тирование – 1 балл.
 Разработаны проверки корректного ввода данных и корректной работы кода – 1 балл.
 Выполнена демонстрация корректной работы и реализации всех пере- определенных методов производных классов – 1 балл.
 Оформление отчета выполнено согласно ОС ТУСУР – 1 балл.
Максимальный балл за выполнение работы – 5, а минимальный проход- ной – 3. Если текущая оценка за выполненную работу ниже 3 баллов, то она должна быть переработана и отправлена на повторное рецензирование. Если ито- говое число баллов менее 3, то лабораторная работа считается незачтенной
(незачет).

56
СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ
Не мы такие, жизнь такая.
Х/ф «Бумер»
1. Буч, Г. Объектно-ориентированное проектирование с примерами приме- нения / Г. Буч. – М. : Конкорд, 1992. – 516 c.
2. GDB
Online
Debugger
[Электронный ресурс]. –
URL: https://www.onlinegdb.com
(дата обращения: 01.06.2022).
3. Демаков А. В. Объектно-ориентированное программирование : учеб. по- собие / А. В. Демаков, А. А. Квасников, С. П. Куксенко. – Томск : Эль Контент,
2022. – 190 с.

57
ПРИЛОЖЕНИЕ А
(обязательное)
Пользовательский проект
– Э-эй! Я знаю, что вы здесь! Я прям слышу, как вы метаболизируете кисло- род и выбрасываете углекислый газ...
Т/с «Теория большого взрыва»
В приложении приведены программные коды пользовательского проекта, состоящего из файлов array.h и array.cpp и main.cpp, демонстрирую- щего работу разработанного класса array.
// файл array.cpp
1 #ifndef ARRAY_H
#define ARRAY_H class CDynamicArray{ public:
// объявление открытых членов класса
// методы класса
CDynamicArray();
//конструктор; вызывается, когда
создаётся объект класса
CDynamicArray();
//деструктор; вызывается, когда
уничтожается объект класса
void CreateArray(int);
// создание массива
void SetValue(int, int);
// установка элемента
массива
void FillArrayRandValues();
// заполнение массива
случайными числами
void DisplayArray();
// вывод элементов массива
private:
// объявление закрытых членов класса
int nCount;
// размер массива, если массив пуст,
то nCount=0
int *arr;
// указатель на массив
2 3
4 5
6 7
8 9
10 11 12 13 14

58 15 };
// class
#endif
// ARRAY_H
16
// файл array.cpp
1 #include "array.h"
#include using namespace std;
// реализация методов класса
CDynamicArray::CDynamicArray(){ nCount = 0;
// начальные значения полей класса:
пустой массив
arr = nullptr;
}
CDynamicArray::CDynamicArray(){ delete [] arr;
// освобождение памяти под массив
} void CDynamicArray::CreateArray(int N){ if (N > 0){
// если новый размер массива задан
корректно, то создается массив
if (arr != nullptr)
// если массив существовал, то
он аннигилируется
delete [] arr; arr = new int[N]; nCount = N; cout<< "-- An array is created! --" << endl< } else cout << "-- Error, the size of array must be > 0!
-- " << endl << endl;
} void CDynamicArray::SetValue(int k, int value){
2 3
4 5
6 7
8 9
10 11 12 13 14 15 16 17 18 19 20 21 22 23

59 24 if(nCount == 0){ cout<< endl << "-- Error, an array not created! --"
<< endl << endl; return;
} if (k >= 0 && k < nCount){
// если элемент с индексом
k существует, то меняется его значение
arr[k] = value;
DisplayArray();
} else cout << endl << "-- Error, the index of array is incorrect! --" << endl << endl;
} void CDynamicArray::FillArrayRandValues(){ if(nCount > 0){
// если массив создан
for(int i = 0; i < nCount; i++) arr[i] = rand() % 100;
// использование
генератора случайных чисел
DisplayArray();
} else cout<< endl << "-- An array not created! --" << endl << endl;
} void CDynamicArray::DisplayArray(){ if(nCount > 0){
1   2   3   4   5

//если массив создан
cout << "-----------------------" << endl; for(auto i = 0; i < nCount; i ++) cout << arr[i] << " ";
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48

60 49 cout< } else cout<<"-- An array not created! --" <}
50 51 52 53
// файл main.cpp
1 #include
#include "array.h" using namespace std;
int main(){
CDynamicArray ar;
//создаётся объект класса
int key; do{ cout << "-- Сhoose a work option --" << endl; cout <<"1 - the mode of creating an array"<> key;
// ввод требуемого режима
cout << endl; switch(key){ case 1: int n; cout<<"Enter the Number of array elements:"; cin >> n;
2 3
4 5
6 7
8 9
10 11 12 13 14 15 16 17 18 19 20 21

61 22 ar.CreateArray(n); break; case 2: int k, val; cout << "Enter the Number of the array element: "; cin >> k; cout << "Enter the Value of the array element: "; cin >> val; ar.SetValue(k, val); break; case 3: ar.FillArrayRandValues(); break; case 4: ar.DisplayArray(); break;
}
// switch
}
// do
while(key != 5); return 0;
// аннигиляция объекта, автоматический
вызов деструктора
}
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
Пример результата работы пользовательского проекта показан на ри- сунке A.1. Так, сначала создается массив (режим 1), состоящий из 5 элементов, потом он заполняется (режим 3), далее меняется его последний элемент
(режим 2) и затем он дополнительно выводится на печать (режим 4), после чего следует выход из программы (режим 5). Пример работы при некорректном зада- нии параметров показан на рисунке А.2.

62
-- Сhoose a work option --
1 - the mode of creating an array
2 - the mode of changing array elements
3 - the mode of filling an array with random numbers
4 - the mode of displaying array elements
5 - the output mode
Your choice: 1
Enter the Number of array elements:5
-- An array is created! --
-- Сhoose a work option --
1 - the mode of creating an array
2 - the mode of changing array elements
3 - the mode of filling an array with random numbers
4 - the mode of displaying array elements
5 - the output mode
Your choice: 3
------------------------
83 86 77 15 93
------------------------
-- Сhoose a work option --
1 - the mode of creating an array
2 - the mode of changing array elements
3 - the mode of filling an array with random numbers
4 - the mode of displaying array elements
5 - the output mode
Your choice: 2
Enter the Number of the array element: 4
Enter the Value of the array element: 1313
------------------------
83 86 77 15 1313
------------------------
-- Сhoose a work option --
1 - the mode of creating an array
2 - the mode of changing array elements
3 - the mode of filling an array with random numbers
4 - the mode of displaying array elements
5 - the output mode
Your choice: 4
------------------------
83 86 77 15 1313
------------------------
-- Сhoose a work option --
1 - the mode of creating an array
2 - the mode of changing array elements
3 - the mode of filling an array with random numbers
4 - the mode of displaying array elements
5 - the output mode
Your choice: 5
...Program finished with exit code 0
Press ENTER to exit console.
Рис. A.1 – Пример результата работы в GDB online Debugger при корректных параметрах


63
-- Сhoose a work option --
1 - the mode of creating an array
2 - the mode of changing array elements
3 - the mode of filling an array with random numbers
4 - the mode of displaying array elements
5 - the output mode
Your choice: 3
-- An array not created! --
-- Сhoose a work option --
1 - the mode of creating an array
2 - the mode of changing array elements
3 - the mode of filling an array with random numbers
4 - the mode of displaying array elements
5 - the output mode
Your choice: 2
Enter the Number of the array element: -1
Enter the Value of the array element: 1313
-- Error, an array not created! --
-- Сhoose a work option --
1 - the mode of creating an array
2 - the mode of changing array elements
3 - the mode of filling an array with random numbers
4 - the mode of displaying array elements
5 - the output mode
Your choice: 2
Enter the Number of the array element: 5
Enter the Value of the array element: 1313
-- Error, an array not created! --
-- Сhoose a work option --
1 - the mode of creating an array
2 - the mode of changing array elements
3 - the mode of filling an array with random numbers
4 - the mode of displaying array elements
5 - the output mode
Your choice: 4
-- An array not created! --
-- Сhoose a work option --
1 - the mode of creating an array
2 - the mode of changing array elements
3 - the mode of filling an array with random numbers
4 - the mode of displaying array elements
5 - the output mode
Your choice: 5
...Program finished with exit code 0
Press ENTER to exit console.
Рис. A.2 – Пример результата работы в GDB online Debugger при некорректных параметрах