Главная станица
Курс лекций
Лабораторный практикум
Экзаменационные вопросы
Литература
Заочнику
Рабочая программа

 

 

Лабораторная работа №1. Класс «Двухмерный динамический массив».

Цель работы: Изучить методику создания и уничтожения двухмерных динамических массивов при помощи конструкторов с захватом динамической памяти и деструкторов для ее освобождения. Научиться работать с классом через функции-друзья этого класса.

Краткие теоретические сведения

Создание двухмерного динамического массива

Учитывая тот факт, что имя двухмерного массива, это указатель на указатель:

int  * * m;

двухмерный динамический массив создается и уничтожается за два шага. Например, нужно создать целочисленный массив m[3][4]


m = new  int * [3];       // Захват памяти для указателей – рис. Часть А
// Захват памяти для элементов – рис. Часть B
for ( int i=0; i<3; i++)  
m[i] = new  int [4];
…………………………………………………………………………..
// обнулили элементы
for ( i=0; i<3; i++)                 
for ( j=0; j<4; j++)              
m[i] [j] = 0;                  // или  *(*(m+i)+j) = 0;
………………………………………………………………………….
for ( i=0; i<3; i++)                  // Освобождение памяти
delete [ ] m[i];
delete [ ] m;
В динамической памяти была создана такая конструкция

 

Ука­
за­
тели

m[0]

®

m[0][0]

m[0][1]

m[0][2]

m[0][3]

m[0]

m[1][0]

m[1][1]

m[1][2]

m[1][3]

m[0]

m[2][0]

m[2][1]

m[2][2]

m[2][3]

                 (А)                                                 (В)


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

int **a;
puts(“\n Input n,m:”); 
scanf(“%d %d”,&n,%m);
a=(int **)calloc(n,sizeof(int*));                                   // Захват памяти
for(i=0; i<n; i++)
a[i]=(int *)calloc(m,sizeof(int));                      
. . .
for(i=0; i<n; i++)   free(a[i]);                           // Освобождение памяти
free(a);                                                          
getch();   

Краткая характеристика функции-друга класса

Механизм «функции - друзья класса» обеспечивает возможность доступа к любым элементам класса тем функциям, которые не входят в состав этого класса. Т.е. функция-друг класса - это обычная функция пользователя, которая благодаря данному механизму имеет полные права доступа ко всем без исключения элементам класса, не зависимо от их степени защищенности. Чтобы обычная функция стала другом класса, в заголовке ее необходимо указать friend.

Некоторые особенности:

1. Функцию-друга класса необходимо декларировать в описании этого класса. Декларация возможно в двух формах:

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

- в форме полного определения, в шаблоне класса приводится ее полный текст.

2. При обращении к Функции-другу не надо использовать операцию привязки (.) или (->).

3. Так как функция-друг - обычная функция, у нее отсутствует первый скрытый параметр this.

Для обращения элементам класса из функции-друга используют или ссылку или указатель что и иллюстрирует следующий пример

class X {
int a, b;
public:
void Print(void);                                  // Метод печати
friend void f1Set (X &);
friend void f2Set (X *);
};
void X :: Print (void) {
cout << “ a= “ << a << “ b= “ << b << endl;
}
// f1Set() и f2Set() - обычные функции, поэтому операция привязки не нужна
void f1Set (X &r) {
r.a = 1; r.b = 2;
}
void f2Set (X *p) {
p -> a = 3; r -> b = 4;
}
void main(void)  {
X  x1;                          // Установили значение
f1Set (x1);    x1.Print();
f2Set (&x1);  x1.Print();
}


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

void f2Set (X *p, int I, int j) {
p -> a = i; r -> b =j;
}


а обращение к ней:


f2Set (&x1, 5, 6);

 

Задание к лабораторной работе 1

Общая постановка. Пользовательский класс Array должен содержать конструктор с параметрами для создания  динамических целочисленных массивов (операция new или стандартная библиотечная функция calloc) и установки начальных значений их элементов: Array(…) (реальные размеры массива  - число строк и столбцов передается в конструктор через параметры);

  • Деструктор: ~ Х ();

  • Метод печати текущего состояния массива: void print(…);

  • Метод переустановки текущего состояния массива: void set(…);

  • Функция-друг, решающая поставленную задачу:friend void Run(…);

Код методов и функции-друга  – вне пространства определения класса

Программа иллюстрирует косвенный способ обращения к элементам массива.

 

ИНДИВИДУАЛЬНЫЕ ЗАДАНИЯ

Составить функцию Run  которая позволит выполнить следующие действия с двухмерными массивами (если задачу решить нельзя, то сообщит об этом).


1. Определить: сумму элементов в тех строках, которые не содержат отрицательных элементов;
2. Определить: количество строк, содержащих хотя бы один нулевой элемент;
3. Определить: номер первой из строк, не содержащих ни одного положительного элемента.
4. Определить: Найти номер первого из столбцов, не содержащих ни одного отрицательного элемента.
5. Определить: номер первого из столбцов, содержащих хотя бы один нулевой элемент.
6. Определить: номер первой из строк, содержащих хотя бы один положительный элемент.
7. Определить: количество строк, среднее арифметическое элементов которых меньше заданной величины.
8. Определить: Найти сумму модулей элементов, расположенных выше главной диагонали.
9. Определить: количество строк, не содержащих ни одного нулевого элемента;
10. Определить: максимальное из чисел, встречающихся в заданной матрице более одного раза.
11. Определить: количество столбцов,  не содержащих ни одного нулевого элемента.
12. Определить: произведение элементов в тех строках, которые не содержат отрицательных элементов;
13. Определить: сумму элементов в тех строках, которые содержат хотя бы один отрицательный элемент;
14. Определить: сумму элементов в тех столбцах, которые содержат хотя бы один отрицательный элемент

 

Контрольные вопросы
  1. Чем является имя двухмерного массива?

  2. Как косвенно обратиться к элементу двухмерного массива?

  3. Как отреагирует компилятор на такой оператор a[-3[-2]?

  4. Что представляет из себя функция друг класса?

  5. Какие существуют способы обращения к элементам класса из функции-друга?

 

 

Лабораторная работа №2. Класс «Динамическая строка» и перегрузка операций.

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

Краткие теоретические сведения

Напомним, что работа со строками в языке С реализована путем использования одномерных массивов типа char, т.е. строка символов – это одномерный массив типа char, заканчивающийся нулевым байтом. Нулевой байт – это байт, каждый бит которого равен нулю, при этом для нулевого байта определена символьная константа '\0' (признак окончания строки или нуль-терминатор). Поэтому, если строка должна состоять из k символов, то в описании массива необходимо указать размер k+1, а при ручном формировании строки в ее окончание нужно явно добавить признак ее окончания.


Операции над строками выполняются только через стандартные функции. Декларации функций для работы со строками размещены в  файле string.h. Вот некоторые из наиболее часто используемых:

1. Функция strcpy(S1, S2) - копирует содержимое строки S2 в строку S1.

2. Функция strcat(S1, S2) - присоединяет строку S2 к строке S1 и помещает ее в массив, где находилась строка S1, при этом строка S2 не изменяется. Нулевой байт, который завершал строку S1, заменяется первым символом строки S2.

3. Функция strcmp(S1, S2) сравнивает строки S1 и S2 и возвращает значение =0, если строки равны, т.е. содержит одно и то же число одинаковых символов; значение <0, если S1<S2;значение >0, если S1>S2.

4. Функция strlen(S) возвращает длину строки, т.е. количество символов, начиная с нулевого и до нуль-терминатора, нулевой байт не учитывается.

5. Функции преобразования строки S в число:
целое:  int atoi(S); длинное целое:  long atol(S); действительное:  double atof(S); при ошибке возвращает значение 0.

6. Функции преобразования числа V в строку S:
целое:  itoa(int V,char S,int kod); длинное целое:  ltoa(long V,char S,int kod); 2<=kod<=36, для отрицательных чисел kod=10.

Помимо традиционной декларации строки, например: char s1[51]; можно создавать динамические строки.

Пример создания с использованием библиотечной функции:

………………………………………………………….
char *s;
s=(char*)calloc(k,sizeof(char));// Захват памяти для строки длиной k
………………………………………………………….

Можно воспользоваться и операциями по захвату и освобождения динамической памяти.

Пример класса с конструктором для создания динамических строковых объектов и деструктором для их уничтожения после использования в программе. 

………………………………………………………………………………………
class String1 {                        
char *str;
public:
// Конструктор
String1 (char *s = “\0”) {                               
str = new char [strlen(s)+1];    
strcpy(str,s);                           
}
// Деструктор
~ String1 ()
{
delete str;  }
void Print(char *s1);
……………………………………………………………………………….
};
void main(void)
{
String1 s1(“Minsk”),               
String1 s2(“Moskow”),                      
s1 . Print (“S1”);
s2 . Print (“S2”);  . . . }

Получим:      

S1 : Minsk
S2 : Moskow

 

Перегрузка операций в С++(краткая характеристика)

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

Если компилятор обнаружил, что в программе есть перегрузка операций для объектов конкретного типа, введенных пользователем, то найдя эти операции компилятор анализирует их. И, если

- операнды имеют  встроенный тип данных, то будет заложена стандартная операция;

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

Операции для конкретного класса перегружаются или методом этого класса, или функцией-другом для этого класса  следующего :

«тип» operator@(список параметров с указанием их типа)

{   
код, определяющий смысл указанной операции,
}


@- символ перегружаемой операции

Т.к. эта операция @ - перегружается для объекта конкретного класса, а сам класс - это тип этого объекта, то «тип» - это ID (идентификатор) этого класса.

Отличия перегрузки операций при помощи метода класса или функции-друга следующие:

а) @a  унарная операция может быть перегружена методом без параметров и функцией-другом с одним параметром, т.к. у метода имеется первый скрытый от нас параметр (указатель this), который автоматически устанавливается на единственный операнд унарной операции, а функция-друг такого указателя не имеет, потому при перегрузке ее параметров, ее операнд и передают

б) a1 @ a2 - бинарная операция перегружается методом класса с одним параметром и функцией-другом с двумя параметрами, т.к. при ее перегрузке методом на первый операнд устанавливается скрытый указатель (this), а второй передаем через параметр; при перегрузке функцией-другом - первый операнд передается функции - через первый параметр, второй - через второй параметр.

Рассмотрим пример перегрузки (с использованием функции-друга) операции для класса, порождающего комплексные числа:

Класс, порождающий комплексные числа:

z1 = re1 + j * im1= (re1, im1) ;

т.е комплексное число – это пара вещественных чисел.

z2 = re2 + j * im2= (re2, im2) ;

Введем:

z3 = (z1 + z2) = re3 + j * im3= (re3, im3) ;

где:

re3 = re1 + re2;         im3= im1 + im2;

это и составит смысл перегрузки операции «+»

. . .
class Complex {
double re, im;
public:
Complex(double r, double i) {  // Обычный конструктор с параметрами
re = r; im = i;   
}
…………………………………………………….
…………………………………………………….
Декларация функции-друга типа operator
friend Complex operator+(Complex, Complex);
};        
// Определение функции
Complex operator+(Complex x, Complex y){
Complex z;
z.re = x.re + y.re;
z.im = x.im + y.im;
return (z);
}                                             // Перегрузили
……………………………………………………………………………..
………………………………………………………………………………
Участок основного кода, иллюстрирующий перегрузку операции «+»:
double a = 1.5, b = 2.6, c;
c = a+b;                   
// Компилятор нашел «+», анализирует данные - это обычные типы, сработает
// стандартная операция
cout << “ C = “ << endl;                     // На экране: С = 4.1
Complex z1(1, 2), z2(3, 4), z3(0, 0);
z3 = z1 + z2;
// сработает перегруженный «+»
cout << “ Z3 : “;                                 
z3.Print();   getch();                             // На экране: Z3 : Re = 4  Im = 6

 


Задание к лабораторной работе 2

Общая постановка. Пользовательский класс String должен содержать необходимые элементы-данные, которые создаются в динамической области памяти.

  • Конструктор для создания строк:String (…);

  • Деструктор:~String(); 

  • Метод ввода исходной строки: Set();

  • Метод печати:void print(…);

Код методов  – вне пространства определения класса

Программа иллюстрирует прямой и косвенный способы обращения к методам

 

ИНДИВИДУАЛЬНЫЕ ЗАДАНИЯ

Ввести с клавиатуры строку  символов S1. Признак окончания ввода строки - нажатие клавиши "Ввод". Программа должна содержать перегруженную операцию «=», использование которой скопирует S1 в S2 при следующих условиях:

1. без 2 первых и 2 последних символа;

2. без всех чисел, которые делятся на 2;

3. без всех цифр;

4. без всех a..z;

5. без всех A..Z;

6. без скобок всех видов;

7. подстроку до первого пробела;

8. подстроку в фигурных: «{ }» скобках;

9. подстроку до первой круглой скобки;

10. подстроку после последнего пробела;

11. подстроку со второго пробела

12. без каждого 3-го символа

13. подстроку до последнего пробела

14. подстроку от последней цифры


На печать вывести исходную и преобразованную строки.

 

Контрольные вопросы


1. Как создать динамическую строку?

2. В чем заключаются особенности работы с строками в языке С?

3. Что такое «перегрузка операций» в языке С?

4. при помощи чего можно перегрузить операцию в языке С?

5. Какие отличия в перегрузке унарных и бинарных операциях?

 

 

 

Лабораторная работа №3. Иерархия классов. Механизм виртуальных функций

Цель работы: Изучить одну из базовых концепций ООП – наследование классов в С++, заключающуюся в построении цепочек классов, связанных иерархически. Познакомиться с механизмом виртуальных функций.

Краткие теоретические сведения

Наследование - такое соотношение между классами, когда один класс использует часть другого класса, добавляя этому классу нечто свое, и таким образом расширяя возможности этого класса. При этом первый класс называется базовый, который описывает  наиболее общие свойства ряда объектов, второй класс – производный.

Определение  производного класса имеет следующий синтаксис:

сlass ID_производного_класса : ID_базового_класса
{
код_производного_класса
};

Одна из особенностей порожденного класса – видимость унаследованных компонент базового класса со степенью защиты protected и public (атрибуты базового класса). Компоненты базового класса с ключевым словом private производному классу недоступны.

Производный класс может служить базовым классом для создания следующего, производного класса на более низком уровне иерархии классов.
Такое наследование называется простым, при котором производный класс имеет одного родителя. В С++ наследование может быть множественным. Множественное наследование позволяет одному классу обладать свойствами двух и более родительских классов.

Видимые компоненты базового класса со степенью защиты protected и public в производном классе становятся private, а значит не могут быть наследованы производными классами на более низких уровнях иерархии классов. Это связано с тем, что в заголовке производного класса перед ID базового класса по умолчанию компилятор указывает атрибут доступа private. Если же указать явно общедоступный атрибут:

сlass ID_производного_класса : public ID_базового_класса

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

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

Если конструктор базового класса имеет параметры для инициализации своих объектов, то конструктор производного класса обязан обеспечить передачу ему исходных данных. Для этой цели используется форма конструктора со списком инициализаторов, в который включаются идентификаторы конструкторов базового класса (явный вызов конструктора базового класса). При этом последние должны быть объявлены с атрибутом доступа как минимум protected.

Продемонстрируем данный механизм  на конкретном примере:

. . .
class B1 {                                          // базовый класс
protected:
int x;
public:
B1(int i) {конструктор с параметрами
x = i; }
};
class D: public B1  // Наследование с сохранением прав доступа
{         
protected:
int a, b;
public:
D(int i, int  j) : B1(i+3), a(i)// Конструктор со списком инициализации
{
b = j;
}
void Print(void) {
cout << “ a = “ << a << “ b = “ << b << endl;           
cout << “ x = “ << x << endl; 
}
};

void main(void)  {
D  d1(3, 4);
d1.Print();
}

На экране монитора:

a=3 b=4
х=6

На как правило, коды методов выносят из пространства класса. Тогда для предыдущего примера:

…………..
…………..
class D {
protected:
int a, b;
public:
D(int, int);
void Print(void);
};                                                       

D :: D(int i, int j) : B1(i+3), a(i)
{
b = j;
}
void D :: Print(void)
{
cout << “ a = “ << a << “ b = “ << b << endl;           
cout << “ x = “ << x << endl; 
}
…………………………..

 

Использование косвенной адресации с установкой указателей на базовый класс

Механизм косвенной адресации рассмотрим на конкретном примере.

……………………………………………..
class B
 {
public:
int x;
B() {                            // Конструктор по умолчанию
x = 4; }
};
class D : public B {                // Производный класс
public:
int y;
D()
{                                 // Конструктор по умолчанию
y = 5; }
};
void main(void)  {
                        d;                            // Конструктор класса D создает объект d
B *p;                          // Указатель установлен на базовый касс
p = &d;                       // Указатель p инициализируется адресом d
// косвенное обращение к объектам базового и производного классов
// «считываем их текущее состояние в переменные
int i = p -> x;              // Базовы класс виден напрямую
int j = ( ( D* ) p )p -> y;// Прямое преобразование указателя на D
// через переменные печатаем их текущее состояние
cout << “ x_i= “ << i << endl;         
                        cout << “ y_j= “ << j << endl;         
                        getch();
}


На экране монитора увидите:

x_i=4
y_j=5

 

Механизм виртуальных функций

Механизм виртуальных функций  - одна из основных концепций объектно-ориентированного программирования. Данный механизм предполагает использование идеи «один интерфейс – множество методов реализации». Эта идея заключается в том, что базовый класс обеспечивает для производных классов все элементы, которые эти классы могут использовать непосредственно, а также содержит набор функций, которые производные классы должны реализовать путем их переопределения (создание собственного кода функции в производном классе, которые позволяет решить поставленную перед производным классом задачу ).

Виртуальная функция – это функция, объявленная с ключевым словом virtual в базовом классе и переопределенная в одном или нескольких производных от этого классах. Обязательное требование: заголовок функции должен быть точно такой же, как в базовом классе (и имя и список параметров должны бать одинаковые). Тогда при создании объекта или базового или одного из производных классов, компилятор определяет какую из функций требуется вызвать, основываясь на типе (ID класса) объекта.

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

Такой процесс  в С++ получил название «позднее связывание».

Как правило, на практике в базовом классе приводят только прототип виртуального метода, который определяет общий интерфейс, указывающий - с какими данными необходимо работать. А в производном классе приводят полные коды, которые определяют способы обработки указанных данных.

Если требование полного сохранения заголовка виртуального метода в производном классе нарушено, то компилятор, обнаружив это, механизм виртуальных функций  проигнорирует и произведет на этапе компиляции его перегрузку .

 

Задание к лабораторной работе 3

Общая постановка. Программа должна содержать:

  • Базовый класс Х, включающий два  элемента х1, х2 типа int,

  • конструктор с параметрами для создания объектов в динамической области памяти,

  • деструктор,

  • виртуальные методы просмотра текущего состояния и переустановки объектов базового класса в новое состояние.

  • Производный класс У, включающий один элемент у типа int ,

  • конструктор с параметрами и  списком инициализаторов, передающий данные конструктору базового класса,

  • переопределенные методы просмотра текущего состояния объектов и их переустановки в новое состояние.

 

ИНДИВИДУАЛЬНЫЕ ЗАДАНИЯ

Создать в производном классе метод Run, определяющий:

  1. Сумму компонент классов

  2. Произведение компонент классов

  3. Сумму квадратов компонент классов

  4. Значение  х1+х2 – у

  5. Значение (х1+х2)/у

  6. Значение (х1+х2)*у

  7. Значение х1*у+х2

  8. Значение х1+х2*у

  9. Произведение квадратов компонент класса

  10. Значение х1*х2+у

  11. Значение х1*х2/у

  12. Значение х1*х2-у

  13. Значение (x1-x2)*y

  14. Значение (x1-x2)/y

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

 

Контрольные вопросы

1. Что такое «наследование» и иерархия классов.

2. Какие элементы базового класса видны из производного. Как управлять степенью их защиты.

3. Какое наследование называют множественным.

4. Указатели и иерархия классов

5. Поясните механизм виртуальных функций.

 

 

 

Лабораторная работа №4. Шаблоны классов

Цель работы: Изучить приемы создания и использования шаблонов классов.

Краткие теоретические сведения

Механизм шаблонов С++ - это средство построения обобщенных определений функций и классов, которые не зависят от используемых типов данных.

Этот механизм позволяет сократить трудоемкость создания программ, т.к. повышает лаконичность текста, т.е. использование шаблонов избавляете от необходимости дублировать коды классов и функций для разных типов данных.

Компилятор по заданному типу аргументов, на основе описания шаблона автоматически создает соответствующие экземпляры классов и функций, которые называются «представители» конкретных классов и функций.

Шаблоны класса в отличие от шаблона функции позволяют параметризовать, т.е. использовать в качестве параметров, не только типы элементов данных, но и константы разных типов

Синтаксис определения шаблона класса следующий:

template

< список параметров  шаблона >

обычное описание структуры класса;

При этом список параметров шаблона не может быть пустым. Элементы в списке разделяются запятыми.

Внутри пространства класса параметры шаблона должны быть хотя бы один раз упомянуты.

В список параметров могут входить два вида параметров:

1) типизированные параметры; начинаются со слова

class идентификатор

компилятор при создании экземпляров класса заменит его на конкретный тип данных;

2) нетипированные  параметры шаблона:

cтандартный тип числовых данных
Идентификатор

Таким образом, типированные параметры – это фиктивные имена типов данных, входящих в класс. Нетипированные параметры – это поименованные типы числовых констант. При этом им при декларировании можно присваивать умалчиваемые значения.

Каждый параметр является локальным в рамках пространства  класса.

В описании класса хотя бы один раз необходимо упомянуть ID типированных и нетипированных параметров, конкретные значения для которых будут переданы в момент создания объекта этого класса через список аргументов, который указывается через запятые в  треугольных скобках сразу после  ID класса:

ID класса < список аргументов > ID объектов;

Здесь, как и ранее: ID объектов - записанные через запятые (например, a,b,c) идентификаторы объектов, которые создает данный класс. При этом, в  списке аргументов, каждому типированному параметру шаблона должен соответствовать известный конкретный тип данных, а каждому нетипированному параметру - константное выражение соответствуючего типа. Таким образом, между списком параметров шаблона и списком аргументов должно быть абсолютное соответствие по количеству, порядку их следования и типам. Если нетипированные  параметры имеют умалчиваемые значения - их располагают в списке
последними.

Пример с шаблоном класса, конструктор которого порождает объекты с двумя параметриованными полями.

. . .
template <class T1>
class X {
protected:
T1 a, b;
public:
X(T1 i, T1 j) {                       // Конструктор
a = i; b = j;
cout << “\n Object created, size = ” << sizeof(T1) << endl;
}
void Print(void) {
cout << “  a = ” << a << “  b = ” << b << endl;
}
};
void main(void) {
X < int >  x1(2, 3);                  //  целочисленные
x1.Print();                    // На экране: Object created, size = 2
getch();                        //                      a = 2  b = 3
X < float >  x2(0.5, 1.2);         // вещественные       
x2.Print();                    // На экране: Object created, size = 4
getch();                        //                      a = 0.5  b = 1.2
}

Методы шаблона класса можно определять вне его пространства. В этом случае, формат их определения будет следующим:

 

template

< список параметров шаблона >

тип результата

ID класса

< перечень через «,» ID из списка параметров шаблона >

 

::

ID метода

(список параметров метода)

 

{
код метода                
}

В вышеприведенном примере вынесем за пределы пространства класса код конструктора:

. . .
template <class T1>
class X {
protected:
T1 a, b;
public:
X(T1, T1);
void Print(void) {
cout << “  a = ” << a << “  b = ” << b << endl;
}
};
//  определение кострук вне состава класса
template <class T1> X <T1> ::  X(T1 i, T1 j) {                                 
a = i; b = j;
cout << “ Object created, size = ” << sizeof(T1) << endl;
}
void main(void) {        
// код предыдущего примера
……………………………………………….
}

 

Задание к лабораторной работе 4

Общая постановка. Дано: число N и последовательность a1, a2, … aN

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

 

ИНДИВИДУАЛЬНЫЕ ЗАДАНИЯ

Вариант 1. a1, (a1+a2), … ,(a1+a2+…+aN)

Вариант 2. (a1*a1), (a1*a2), …, (a1*aN)

Вариант 3. |a1|, |a1+a2|, …, |a1+a2+…aN|

Вариант 4. a1, -a1*a2, +a1*a2*a3, … ,(-1)N*a1*a2*…aN

Вариант 5. -a1, +a2, -a3, … , (-1)N*aN

Вариант 6. (a1+1), (a2+2) , (a3+3), …, (aN+N)

Вариант 7. a1*1, a2*2, a3*3,     , aN*N

Вариант 8. a1*a2, a2*a3, … , aN-1*aN

Вариант 9. a1/1, a2/2, a3/3, …,aN/N

Вариант 10. (a1+a2), (a2+a3),… ,(aN-1+aN)

Вариант 11. (a1+a2+a3), (a2+a3+a4), (a3+a4+a5), … (aN-2+aN-1+aN)

Вариант 12. (N+a1), ( N-1+a2),   ,(1+aN)

Вариант 13. (N*a1), ( (N-1)*a2),   ,(1*aN)

Вариант 14. a1/N, a2/N, … ,aN/1

 

Контрольные вопросы
  1. В чем в С++ заключается механизм шаблонов классов и функций.

  2. Каков общий формат шаблона класса

  3. Какие вы знаете виды параметров шаблона класса

  4. Как создать конкретный экземпляр класса использую шаблон класса

  5. Как определить метод вне пространства  шаблона класса

 

 

 

Лабораторная работа №5. Обработка исключительных ситуаций

Цель работы: освоить использование функций обработки исключительных ситуаций, их формат и особенности.

Краткие теоретические сведения

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

Средства обработки ошибочных ситуаций позволяют передать обработку исключений из кода, в котором возникло исключение,   некоторому другому программному блоку, который выполнит в данном случае некоторые определенные действия. Таким образом, основная идея данного механизма состоит в том, что функция проекта, которая обнаружила непредвиденную ошибочную ситуацию, которую она не знает, как решить, генерирует сообщение об этом (бросок исключения). А система вызывает по этому сообщению программный модуль, который перехватит исключение  и отреагирует на возникшее нештатное событие. Такой программный модуль называют «обработчик» или перехватчик исключительных ситуаций. И в случае возникновения исключения  в его обработчик передаётся произвольное количество информации с  контролем ее типа. Эта информация и является характеристикой возникшей нештатной ситуации.


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


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


try        - проба испытания;


catch    - перехватить (обработать);


throw   - бросать.


Кратко рассмотрим их назначение.


try  - открывает блок кода, в котором может произойти ошибка; это обычный составной оператор:

try
{
код
};

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


Операция броска throw имеет следующий формат:


throw  выражение;


где - «выражение» определяет тип информации, которая и описывает исключение (например, конкретные типы данных).


catch - сам обработчик исключения, который перехватывает информацию:

catch ( тип параметр)
{
код 
}

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

Код определяет те действия, которые надо выполнить при возникновении данной конкретной ситуации. В С++ используют несколько форм обработчиков. Такой обработчик получил название параметризованный специализированный перехватчик

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

В этом случае их необходимо расположить сразу же за контролирующим блоком последовательно друг за другом.

Кроме того, запрещены переходы, как извне в обработчик, так и между обработчиками.

Можно воспользоваться универсальным или абсолютным обработчиком:

catch ( . . . )
{
код
}

где (...) - означают способность данного перехватчика обрабатывать информацию любого типа. Такой обработчик располагают последним в пакете специализированных обработчиков. Тогда, если исключение не будет перехвачено специализированными обработчиками, то буде выполнен последний - универсальный.

В случае не возникновения исключения,  набор обработчиков будет обойден, т.е. проигнорирован.

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

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

Блоки try, как составные блоки могут быть вложены:

try {
...
try
{
... 
}
...
}

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


#include <iostream.h>
#include <conio.h>

float divide(float, float);

void main(void)
{
float a, b, result;
cout<<" input divident, divisor:"<<endl;
cin>>a>>b;
try{
result=divide(a,b);
cout<<"Normal Work"<<endl;
cout<<"Divident, divisor: "<<a<<"  "<<b<<endl;
cout<<"Ansver is: "<<result<<endl;
getch();
}
catch(float z)
{
cout<<"Division by zero..."<<endl;
cout<<"Divisor="<<z<<endl;
getch();
}
}

float divide(float a1, float b1)
{
if (b1==0) throw b1;
return a1/b1;
}

Результат выполнения программы:

1.
input divident, divisor:
1 2
Normal Work
Divident, divisor: 1  2
Ansver is: 0.5
Press any key to continue

2.
input divident, divisor:
1 0
Division by zero...
Divisor=0
Press any key to continue

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

Задание к лабораторной работе 5

Общая постановка. Даны два выражения Z1 и Z1. Написать функции для вычисления этих выражений с организацией обнаружения нештатной  ситуации (деление на ноль) и ее обработки. Передача аргументов в функции – по ссылкам.

В случае успеха значения Z1 и Z1 будут приблизительно одинаковыми.

ИНДИВИДУАЛЬНЫЕ ЗАДАНИЯ

1. .

2. .

3. .

4. .

5. .

6. .

7. .

8. .

9.

10. .

11. .

12. .

13. .
14. .

15. .

 

Контрольные вопросы

1.  Что называют исключением?

2. Что такое «блок с контролем?

3. Дайте характеристику обработчику исключений. Какие бывают виды обработчиков?

4. Какие правила налагаются на соотношения между блоком контроля и обработчиками

5. Чем отличается вызов обработчика от вызова обычной функции

 

 

 
Hosted by uCoz