Поможем написать учебную работу
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.

Предоплата всего

Подписываем
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
Предоплата всего
Подписываем
PAGE 23
Конструкторы и деструкторы
Класс может содержать любое количество функций - членов самого разнообразного назначения, но два типа функций занимают особое положение. Эти функции называются конструктором и деструктором.
Для многих объектов естественно требовать, чтобы они были инициированы (т.е. имели начальное значение, а не мусор) до начала их использования.
В С++ для упрощения процесса инициализации объектов предусмотрена специальная функция, называемая конструктором.
Конструктор это компонентная функция, вызываемая автоматически при создании объекта класса и выполняющая необходимые инициализирующие действия.
- инициализация данных класса - задание им начальных
значений программно или по умолчанию,
- открытие файлов,
3) Функция-конструктор не может возвращать результат, даже тип void не
допустим.
4) Функция автоматически вызывается при определении объекта, или при размещении в памяти объекта с помощью операции new.
< имя класса> ( список формальных параметров)
{ операторы тела конструктора }
Конструктор, как и любая компонентная функция, может быть определен и вне тела класса, имея в теле класса прототип.
class T {
…
public:
T ( список параметров) ;
…
} ;
T::T( список параметров) { тело конструктора}
компилятор автоматически выделяет под них память, хотя в этом случае
данные не инициализируются, и будут содержать мусор.
8) В определении класса могут присутствовать несколько конструкторов.
Деструктор - это функция, которая автоматически выполняется, когда экземпляр класса уничтожается.
( либо при выходе объекта за пределы своей области видимости, либо при уничтожении динамического объекта операцией delete).
Назначение выполнение завершающих действий (напр., закрытие файлов, установка видеосистемы в исходное состояние, написание каких-либо фраз и т.д.)
Деструктор используется, например, если объект локальный и ему динамически выделялась память, целесообразно, чтобы при выходе из блока, когда объект перестает существовать, память была возвращена системе. Желательно, чтобы память освобождалась автоматически, операцию освобождения памяти можно включить в деструктор.
Класс может иметь несколько конструкторов, но деструктор может быть только один.
Формат компонентной функции-деструктора
~ имя класса ( ) { операторы тела деструктора};
<имя объекта>. ~ <имя класса> ( );
При этом объект будет продолжать существовать, только выполняться те действия, которые записаны в теле деструктора.
Рассмотрим класс Men, несущий в себе данные о возрасте и имени индивидуума.
Пусть в этом классе присутствуют и конструктор и деструктор.
В конструкторе кроме инициализирующих действий имеется еще вывод контрольной строки.
Деструктор пусть также содержит вывод другой контрольной строки.
class Men {
char* name ;
int age;
public:
Men(char * n, int a) //встроенный конструктор
{ name = n; age = a;
cout<<name << “ - begin “<<endl;
}
void SetN (char*n) {name =n ; } // комп. функция для изменения данного
void SetA (int a) { age = a ;} // комп. функция для изменения другого данного
char* GetN ( ) { return name; } // компонентные функции
int GetA { return age; } //возвращающие значения данных
~Men ( ) { cout<< name<< “- end”<<endl;} // деструктор
#include <iostream.h>
#include<conio.h>
void main () {
/* создание экземпляра класса по имени:
конструктор автоматически вызывается при создании объекта, и т.к. наш конструктор требует двух параметров, то в строке создания объекта (или в строке вызова конструктора) необходимо указать оба параметра, которые и инициируют создаваемый объект m1 */
Men m1 (“Петров” , 34 ) ;
/* создается объект, параметры конструктора инициализируют
переменную m1 (объект) */
/* Men m1 ; - уже не верно!
т.к. в классе нет конструктора без параметров, т.е. в этом
случае нельзя создать неинициализированный объект * /
/* создание экземпляра класса с помощью указателя:
параметры конструктора инициализируют динамически выделенный участок памяти под объект типа Men*/
Men * m2 = new Men ( “Рощин ”, 25 ) ;
cout<<m1. GetN ( )<< “ “ << m1. GetA( ) << endl;
cout<< m2->GetN( ) << “ “ << m2-> GetA ( ) << endl;
getch( );
}
Результат:
Петров -begin
Рощин - begin
Петров 34
Рощин 25
Петров -end
Итак, конструктор вызывался автоматически дважды при создании объекта по имени и с помощью указателя.
Деструктор вызывался только один раз. Для объекта m2 деструктор не вызывался, т.к. мы выделили память “вручную” и так и не вернули ее системе, т.е. система не фиксирует уничтожение этого объекта при завершении программы.
Если перед getch() вставить строку
delete (m2);
то результат будет:
Петров -begin
Рощин - begin
Петров 34
Рощин 25
Рощин -end
Петров -end
Причем при выводе, как первого, так и второго результата программа останавливается на вызове getch( ), выводятся все строки результатов, кроме последней строки, после нажатия клавиши выводится и последняя строка.
Назначение конструктора инициализировать данные, однако данные могут приобретать значения и другим способом с помощью компонентных функций SetN( ) и SetA( ).
Конструктор мог бы инициализировать только одно данное объекта и выглядеть, напр., так
Men (char*n){ name =n;}
А возраст определялся бы каким-то другим способом.
Еще примеры конструктора с параметрами:
#include <iostream.h>
struct book {
char* name ;
float price;
book (char* newn, float newpr)
{ name=newn; price=newpr;};
};
void main()
{ book a1 ("Хождение по мукам",34.5);
cout<<a1.name;
}
#include <iostream.h>
#include <string.h>
struct goods {
char name [40];
float price;
goods (char* newn, float newpr)
{
// name=newn - ошибка (lvalue required )
strcpy(name,newn); price=newpr;};
};
void main()
{ goods b1 ("Шляпа",12.5);
cout<<b1.name;
}
Итак, конструктор автоматически вызывается при создании объекта, при этом объект инициализируется с помощью фактических параметров передаваемых в тело конструктора.
Заключение:
Экземпляры класса в программе могут создаваться статически и динамически.
1) Определение статического экземпляра класса:
<имя класса> <имя объекта> ( параметры конструктора);
Пустой список параметров не допустим, если в классе конструктор с параметрами
2) Определение массива статических экземпляров класса:
<имя класса> <имя массива> [размер массива] =
{ <имя класса>( параметры конструктора для 0-го экземпляра), …,
<имя класса> ( параметры конструктора для последнего экземпляра)
};
3) Определение динамического экземпляра класса:
<имя класса> * <имя указателя на объект> = new <имя класса> ( параметры конструктора);
4) Определение массива динамических экземпляров класса:
а)
<имя класса> * <имя массива указателей на объекты> [разм. массива] = { new <имя класса> ( параметры конструктора для 0-го экземпляра), …, new<имя класса>( параметры конструктора для последнего экземпляра) };
б)
cin>>n; // количество экземпляров
<имя класса>**<имя двойного указателя> = new <имя класса>*[n];
for( int i=0 ; i<n; i++)
<имя двойного указателя>[i] =new <имя класса> ( параметры
конструктора для i-го экземпляра);
Можно создавать безымянный объект при инициализации другого объекта
<имя класса > (фактические параметры конструктора); // нет имени
{
…
goods tip5 ( “Пальто” , 20000); //обычно
good tip6 = good ( “Шуба” , 100000);
// создается безымянный объект, значения которого используются, напр., в //инициализации вновь созданного объекта tip6
}
Конструктор с аргументами, задаваемыми по умолчанию
Этот конструктор позволяет при его вызове с параметрами инициализировать данные создаваемого объекта указанными значениями параметров.
Но возможен вызов конструктора (или создание объекта) и без параметров, при этом данные инициализируются значениями по умолчанию.
Для класса Men конструктор с умалчиваемыми значениями параметров:
Men (char*n = “ ”, int a =0)
// значения по умолчанию имя пробел, возраст -0
{name=n; age = a; }
/* Такой конструктор можно вызвать без параметров. Если такой конструктор вызвать без параметров, то создаваемый объект будет инициироваться значениями по умолчанию.
Вызов того же конструктора с параметрами будет инициировать данные создаваемого объекта значениями параметров */
void main ()
{ Men m1; // name = “ “ , age =0
Men m2 ( “ Иванов”, 45) // name =”Иванов”, age = 45
Men m3 (“Петров”) // name= “Петров” age=0
Men m4 (18) // ошибка!
Нельзя задавать параметр, перескакивая через умалчиваемое значение (это значение компилятором будет трактоваться как значение параметра n, и естественно будет сообщение об ошибке, т.к. это не строка).
Напр., если четыре параметра с умалчиваемыми значениями, можно вызывать конструктор:
Еще пример:
… struct mag
{ int a,b, c ;
mag ( int aa=1, int bb=2 , int cc=3) // конструктор
//с умалчиваемыми значениями параметров
{ a= aa; b=bb; c= cc; };
void shownumber ( void)
{cout << a <<b <<c ; } ;
};
mag one; // объект инициализируется значениями 1, 2 ,3
mag one1 (10); // объект инициализируется значениями 10, 2 ,3
mag one2 (10,20); // объект инициализируется значениями 10, 20 ,3
mag one3 (10,20,30) // объект инициализируется значениями 10, 20 ,30
Не обязательно иметь умалчиваемое значение для всех параметров, при этом параметры, не подлежащие инициализации умалчиваемыми значениями должны располагаться в начале списка, подлежащие в конце этого списка.
Men ( char*n, int a=0) { name = n; age = a; }
Men ( char*n = “ “ , int a) { name = n; age = a; } // ошибка!
Конструктор по умолчанию
Это разновидность конструктора без параметров (присутствует в классе в единственном экземпляре).
Присутствует, когда надо единообразно инициализировать данные, или инициализирующие действия вообще не связаны с данными.
Этот конструктор не имеет параметров.
class A {
int x , y ;
public:
A ( ) ;
...
};
A :: A ( ) // невстроенный конструктор по умолчанию
{ x =0 ; y = 0; // инициализация данных, может отсутствовать
... // другие инициализирующие действия
}
/* мог бы быть просто такой:
A :: A ( ) { } */
При наличии конструктора по умолчанию , предложение
A one ;
создает объект со значениями данных x=0 и y=0.
Если в конструкторе не было инициализации, то этот конструктор предоставляет возможность создавать неинициализированные объекты даже при наличии в определении класса еще одного конструктора с параметрами.
Конструктор по умолчанию схож при вызове с конструктором с умалчиваемыми значениями и при написании конструкции:
<Имя класса> < имя объекта> ;
компилятор обнаруживает двусмысленность и выдает сообщение об ошибке, т. к. не ясно какой конструктор вызывать.
При наличии в классе конструктора с параметрами, задаваемыми по умолчанию, объявлять конструктор по умолчанию нельзя!
Если в определении класса вообще нет конструктора, компилятор автоматически предоставляет конструктор по умолчанию следующего вида:
<Имя класса> ( ) { }
который и создает неинициализированные объекты.
Конструктор копирования
Назначение:
Операции копирования объектов выполняет конструктор копирования.
В большинстве случаев компилятор предоставляет нам конструктор копирования по умолчанию, обеспечивая правильное копирование.
Копирование по умолчанию:
class T {
int x, y ;
public:
T ( int tx , int ty ) { x = tx ; y = ty ; }
int GetX ( ) { return x; }
int GetY ( ) { return y; }
friend T sum ( T, T);
};
void Print ( T obj )
{ cout<<\n<<”x=”<<obj.GetX( ) <<” y=”<<obj.GetY( ); }
T sum ( T obj1 , T obj2 )
{ obj1. x + =obj2. x ;
obj1. y + =obj2. y ;
return obj1 ; }
#include <iostream.h>
void main () {
T e1 ( 1, 10); //создали объект, вызвав конструктор с параметрами
Print ( e1 ); // первое копирование
T e2 = e1; // второе копирование
Print ( e2 ); // третье копирование
T e3 = sum ( e1, e2 ) ; // четвертые копирования
Print (e3); // пятое копирование
}
// выведется:
// x= 1 y = 10
x= 1 y = 10
x= 2 y = 20
Определение конструктора копирования
Конструктор копирования должен иметь для данного класса такой вид:
1. T ( T obj ) { x = obj.x ; y = obj. y } // не верно!
Но тогда в сам конструктор копирования должен копироваться объект, т.е. должен вызываться сам конструктор копирования, в котором опять для копирования будет вызываться сам конструктор. Возникла бы бесконечная рекурсия.
Рекурсию можно избежать, если не копировать объект в конструктор, а использовать передачу параметра по ссылке:
2. T ( T & obj ) { x = obj.x ; y = obj. y }
И последнее необходимо ввести запрет на модификацию копируемого параметра для этого используется ключевое слово const:
3. T ( const T & obj ) { x = obj.x ; y = obj. y }
В общем случае третий вариант наиболее правильный (и так выглядит конструктор по умолчанию), однако структура конструкторов, такова, что в них отсутствуют действия, изменяющие копируемый объект. Поэтому в дальнейшем часто будем останавливаться на втором варианте.
Рассмотрим, как происходит копирование.
Как удостоверится, что
- при копировании объекта,
- при передачи объекта в функцию,
-при возвращении функцией объекта в некоторую
переменную (также объект)
вызывается конструктор копирования по умолчанию, или конструктор копирования явно определенный в классе?
Для того, чтобы зафиксировать факт вызова конструктора копирования, мы правильное копирование (в программе написанной выше, конструктор варианта 3 вызывается по умолчанию) заменим на копирование с приращением:
class T {
int x, y ;
public:
T ( int tx , int ty ) { x = tx ; y = ty ; }
T (const T & obj) { x = obj.x+1 ; y = obj. y +1 }
int GetX ( ) { return x; }
int GetY ( ) { return y; }
friend T sum ( T, T);
};
void Print ( T obj )
{ cout<<\n<<”x=”<<obj.GetX( ) <<” y=”<<obj.GetY( ); }
T sum ( T obj1 , T obj2 ) // хотя закрытые данные функция -друг
{ obj1. x + =obj2. x ; // имеет право обращаться к компонентам
obj1. y + =obj2. y ;
return obj1 ; }
#include <iostream.h>
void main () {
T e1 ( 1, 10); //создаем объект
Print ( e1 ); // первое копирование - приращение
T e2 = e1; // второе копирование
Print ( e2 ); // третье копирование
T e3 = sum ( e1, e2 ) ; // четвертые копирования
Print (e3); // пятое копирование
}
// выведется:
// x= 2 y = 11
x= 3 y = 12
x= 7 y = 25
в данные объекта obj передадутся значения x и y объекта e1 с
инкрементом, которые и будут выведены функцией (x = 2 y = 11)
инкрементированные значения x и y объекта e1 ( 2 и 11- значения
данных объекта e2).
переменную obj в функции Print. В obj передадутся значения x и y из
e2 с инкрементом они и будут выведены ( x= 3 y = 12).
4) Затем в локальные переменные obj1 obj2 функции sum копируются
данные объектов e1 e2 с инкрементом в obj1 - (2 11), в obj2 - ( 3 12).
Локальные объекты суммируются в функции. Результирующий объект
имеет значения данных ( 5 23 ).
Данные этого объекта копируются с инкрементом в данные объекта e3 в
выражении T e3 = sum (…); т.е. e3 получает данные ( 6 24 ).
5) При вызове Print(e3) , значения объекта e3 копируются в локальную
переменную obj в функции Print. В obj передадутся значения x и y из
e3 с инкрементом они и будут выведены ( x= 7 y = 25).
Как мы видели выше, копирование по умолчанию осуществляет правильное копирование и без определения конструктора копирования в классе. Свой конструктор копирования нужен не всегда.
Обязательное его определение в тех случаях, когда класс содержит поля, являющиеся указателями на динамические участки памяти.
#include <iostream.h>
#include <conio.h>
class My {
int* p;
public:
My ( int a) // встроенный конструктор с параметром
{ p= new int ; *p = a ; }
My ( const My & obj ) //конструктор копирования с инкрементом
{ p = new int ; *p = *obj.p+1 ; }
/* Для фиксации вызова конструктора копирование также проводится с
инкрементом.
Конструктор копирования, создавая копию объекта, сначала выделяет
память под данное- член нового объекта и затем присваивает ему
значение , взятое из объекта аргумента. Правильное присваивание
( без инкремента): *p = *obj.p;
*/
int Get ( ) { return * p } // возвращает значение по адресу указателя
~My ( ) { delete (p); } // деструктор память, выделенную под данное
// объекта освобождает
} ;
void Print ( My obj ) // функция вывода на экран самой переменной
{ cout<< \n << obj.Get ( ) ;}
void main ( )
{ My a1 (10) ; // создается объект, переменная инициализируется 10
My a2 = a1; // с помощью конструктора копирования создается объект
// a2, со значением переменной 11, т.к. в конструкторе
// инкремент
Print ( a1) ;
/* значение переменной объекта a1 копируется с инкрементом в
в переменную локального объекта obj функции (11 ) и это
значение выводится */
Print ( a2 ) ;
/* значение переменной объекта a2 копируется с инкрементом в
в переменную локального объекта obj функции (12 ) и это
значение выводится */
a1. ~My( ) ; // вызовом деструктора объект память a1 освобождается
Print ( a1) ; // выведется мусор
Print ( a2); // выведется 12
}
Результат:
11
12
7853
12
После вызова деструктора a1 освобождается динамическая память и в данном (*p) члене объекта a1 содержится теперь мусор (операция delete в процессе возврата в систему возвращаемой памяти затирает эту память).
Объект a2 остался неизменным. Так и должно быть.
Если мы удалим из определения класса конструктор копирования, результат работы программы следующий:
10
10
7853
7853
Уничтожение объекта a1 привело к уничтожению и его копии a2 , в которой находится теперь тот же мусор.
С другой стороны значения переменных теперь выводятся правильно, т.к. конструктор по умолчанию никаких инкрементов естественно не выполняет.
При отсутствии конструктора копирования программа будет использовать конструктор копирования по умолчанию следующего вида:
My ( My & obj ) { p = obj.p; }
В объекте a1 находится указатель на выделенное обычным конструктором поле памяти.
Побайтовое копирование объекта дает копирование указателя, т.е. в объект a2 заносится тот же самый указатель, оба объекта указывают на один и тот же участок памяти.
Объект a1
Данное (*p)
Объект
a2
При освобождении памяти в a1 ( или в a2 ) стирается наше единственное данное, т.е. и второй объект разрушается.
Правильным копированием при наличии в объекте динамического данного будет выделение для создаваемого при копировании объекта нового участка памяти.
Объект a1
Данное ( *p)
Объект a2
Данное (*p)
В этом случае объекты a1 и a2 будут указывать на разные поля. Уничтожение одного из них вызовом деструктора, никак не отразится на существовании другого. Именно это и выполняется в нашем конструкторе копирования.
Еще о конструкторах
Есть еще один способ инициализации объекта с помощью списка инициализаторов данных объекта.
Этот список помещается при описании конструктора между списком параметров и телом конструктора:
<имя класса> ( список параметров) : < список инициализаторов>
{ тело конструктора }
Пример :
Class A
{ int ii ; float ee ; char cc;
public : A( int i , float e , char c ) : ii ( 7),
ee( ii + i * e),
cc(c)
{ }
. . .
};
A a( 5 , 1.2 . f) ; // создается объект с компонентами a.ii =7 ,
// a.ee = 13 , a.cc=f
- Параметром конструктора не может быть его собственный класс, но
может быть ссылка на него.
- В классе может быть несколько конструкторов, но только один с
умалчиваемыми значениями параметров.
- Нельзя получить адрес конструктора.
- Если в определении класса нет конструктора, то компилятор автоматически предоставляет конструктор по умолчанию, который и создает неинициированный объект
<Имя класса> < имя объекта>;
Если есть хоть один конструктор с параметрами (но без значений по- умолчанию), для того чтобы иметь возможность создать неинициированный объект надо объявить в теле класса конструктор по умолчанию:
<Имя класса> ( ) { };
тогда используя конструкцию
<Имя класса> < имя объекта>;
можно объявить неинициированный объект.
Примеры:
1.
… сlass Book {
public :
char title[40];
char author[20];
float price;
Book ( char*atitle, char*aauthor, float aprice ); //прототип
//конструктора
~Book( ) ; // прототип деструктора
void show_book(void) { cout<<\n<< title<< “, “<<price ;}
} ;
Book::Book(char*atitle, char*aauthor, float aprice )
{ strcpy(title, atitle); strcpy(author, aauthor);
price = aprice; }
Book::~Book( )
{ cout <<\n<< “Уничтожение экземпляра:”<< title ;}
void main ( )
{ Book tip1 (“Turbo Pascal”, “B.B.Фаронов”, 60.0) ;
Book tip2(“Язык С++” , “В.В.Подбельский”,62.0 );
Tip1.show_book( );
Tip2.show_book( );
}
Turbo Pascal , 60.0
Язык С++ , 62.0
Уничтожение экземпляра : Язык С++
Уничтожение экземпляра : Turbo Pascal
char*ch // указатель на строку на символьный массив
int len // длина строки
public:
stroka( char*cch) // конструктор1
{ len = strlen(cch) ; ch=new char [len+1];
strcpy (ch,cch); } ;
stroka(int N=20) // конструктор 2
{ ch= new char[N+1}; len=0; ch[0]=\0}
int len_str(void) { return len;} // возвращает длину
//строки
char * string (void) {return ch;} // возвращает указатель на строку
void vivod ( ) // выводит данные
{ cout<< “строка: “ <<ch <<” , длина строки=”<<len;};
~stroka( ) // деструктор
{ delete []ch ;} ;
}
void main ( )
{ stroka S1( “Миру-мир”);
S1.vivod;}
Деструктор автоматически освободит память.
Деструктор идеальное место для всей общей работы по очистке, напр.: освобождение памяти, сохранение данных в файлах , закрытие файлов…
Компонентные данные
имя компонента
( если позволяет статус доступа)
имя объекта. имя компонента
указатель на объект -> имя компонента
В связи с этим отличием введена особая область видимости класс (наряду с файлом , блоком и функцией ).
Статические компоненты класса необходимо описывать и инициировать
вне определения класса как глобальные переменные.
Обращаться к ним можно до определения первого объекта класса с
помощью квалифицированного имени:
имя класса ::имя компонента
Если объекты объявлены, то к статическому компоненту можно
обращаться стандартно, и всякое изменение статического компонента в одном объекте становится видно во всех объектах.
Если статический элемент имеет статус private или protected к нему можно обращаться только из компонентных функций. Если надо обратиться до определения объектов, надо ввести статическую компонентную функцию, со спецификатором static, которую можно вызывать до определения объектов с помощью квалифицированного имени:
имя класса ::имя статической функции (параметры )
Пример:
Class cot
{ …;
static int N;// статический компонент
public: …
static void count(int value) { N = value} ;
};
int cot::N=0; // внешняя инициализация статического элемента
void main ( )
{ cot::count(500); …
//изменение статического компонента до объявления объектов
}
Определение указателя на компонентные данные класса:
тип данных(имя класса ::*имя указателя)
В определение можно включить и инициализацию
& имя класса :: имя компонента
int ( stroka::*plen)=&stroka::len;
// - не верно, т.к. len имеет статус private
При инициализации надо использовать общедоступные компоненты.
После объявления объекта к его компонентам следующий формат обращения через указатель:
имя объекта.* указатель на компонент данных;
Если определен указатель на объект формат обращения:
Указатель на объект-> *указатель на компонент данных ;
Определение указателей на компонентные функции
Тип возвращаемого результата ( имя класса ::*имя указателя на метод)
( спецификация параметров функции);
void (book::*fprt)(void) = & book::show_book ;
// определение указателя на компонентные функции класса book и
// инициализация указателя компонентной функцией show_book
И форматы обращения к методам через указатели:
Имя объекта.*указатель на метод(параметры);
Указатель на объект-> *указатель на метод ( параметры);
Пример:
book A (“C и C++”, “Б.И.Березин, 40.0); // определен объект
void (book::*fprt)(void) = & book::show_book ;
(A.*fptr) ( ) // выведет на печать значения объекта
Если в определении класса было бы несколько идентичных функций указатель fptr можно “настроить” на другие функции
Компонентные функции
Если определение функции полностью размещено в классе, то эта функция по умолчанию считается подставляемой, т.е. при каждом вызове код функций встраивается в точку вызова.
При внешнем определении в теле класса располагается прототип функции:
<Тип> <имя функции >(спецификация формальных параметров);
Вне тела класса компонентная функция определяется следующим образом:
<Тип ><имя класса>::<имя функции>(спецификация формальных
параметров)
{ тело функции };
Функция компонент класса имеет туже область видимости, что и класс.
Пример: Работа с графической библиотекой
При объявлении методов класса использовались (инкапсулировались) графические функции, прототипы которых находятся в заголовочном файле graphics.h
Все эти функции предоставляют возможности управления графическим экраном.
Стандартное состояние ПК соответствует работе экрана в текстовом режиме (25 строк по 80 символов в строке).
Если мы хотим использовать графические средства компьютера, необходимо инициировать графический режим работы дисплейного адаптера.
Для управления техническими средствами ПК имеются соответствующие программы, называемые драйверами.
Графический драйвер управляет дисплейным адаптером в графическом режиме.
Графические возможности адаптера определяются разрешением экрана (количеством точек экрана) и количеством цветов, которыми может светиться каждая точка.
Наиболее распространенные дисплейные адаптеры (CGA - Color Graphics Adapter, EGA Enhanced (усиленный) Graphics Adapter, VGA Video Graphics Array (графический видеомассив), SVGA и т. д. ) могут иметь несколько графических режимов работы. Для управления современными графическими адаптерами мы используем драйвер EGAVGA.BGI
Экран представляет собой совокупность светящихся точек - пикселей. Количество точек определяется монитором и режимом драйвера для работы с ним. Положение пикселя определяется его координатами по отношению к точке с координатами 0, 0 верхнему левому углу экрана.
Для инициализации графического режима адаптера используется вызов функции:
initgraph( тип графического драйвера, режим адаптера , путь к драйверу)
void initgraph (int far * graphdriver, int far * graphmode, char far * pathtodriver);
Для указания типа драйвера в файле имеются константы:
DETECT = 0 (режим автоопределения типа)
CGA =1
EGA=3
VGA=9
и т. д.
Аналогично имеются константы для определения моды адаптера.
Но если программа рассчитана на работу с любым адаптером, вызов функции можно производить с требованием автоопределения типа драйвера:
int dr = DETECT , // тип драйвера и
mod ; // режим работы адаптера
// определяются автоматически
//режим при этом выбирается с максимальным разрешением
initgraph ( &dr , &mod , "D:\\Borlandc\\BGI" );
// предполагается, что драйвер находится в каталоге BGI
#include <graphics.h>
#include <conio.h> //содержит прототип функции getch( )
// опишем класс
class point { //точка на экране дисплея
int x, y ; // собственные компонентные данные
public: // общедоступные компонентные функции
point (int xx=0 , int yy =0) ; // прототип конструктора
// с умалчиваемыми значениями
void show (void); // прототип функции изображения точки
void move ( int xo=0 , int yo =0) ; // прототип функции
// перемещения точки с умалчиваемыми значениями
private : // собственная функция класса
void hide ( ) ; // прототип функции, убирающей точку с экрана
} ;
//дадим внешнее определение методам класса
point::point( int xx=0, int yy=0 ) // определение конструктора
{ x = xx ; y = yy ; }
void point :: show (void)
{ putpixel (x, y , getcolor( ) ) ;} // int getcolor(void)
//возвращает номер цвета символов
void point :: hide(void)
{ putpixel (x, y , getbkcolor( )) ; }
void point ::move ( int xn=0, int yn=0 )
{ hide ( ) ;
x =xn ; y= yn ;
show ( ) ; }
void main ( )
{ // создается три объекта , три невидимых точки
point A ( 150, 70 );
point B ; // координаты по умолчанию равны x=0 и y=0
point D ( 400, 200);
//инициализация графики
int dr = DETECT , mod ;
initgraph ( & dr , & mod , “D :\\ borlandc\\ bgi “);
//установка цвета
setcolor(4) ;
A.show ( ) ; // показать на экране точку А
getch ( ) // подождать до нажатия клавиши
B.show ( ) ; // показать на экране точку В
getch ( ) ; // подождать до нажатия клавиши
D.show ( ) ; // показать на экране точку D
getch ( ) ; // подождать до нажатия клавиши
A.move ( ) ; // переместить точку А в начало координат
getch ( ) ; // подождать до нажатия клавиши
B.move(50, 60 ) ; // переместить точку B
getch ( ) ; // подождать до нажатия клавиши
closegraph( ) ;
}
При обращении к функции getch( ) программа останавливается и ждет ввода с клавиатуры любого символа, появляется возможность отследить смену изображений.
С этой же целью используется функция int kbhit (void) (прототип в файле conio.h ) .
while (! Kbhit() ); - задержка программы до нажатия клавиши.
Контрольные вопросы:
Указатель this
Каждый раз при создании объекта класса строится указатель, называемый this, и содержащий адрес этого объекта. Правильнее сказать , указатель определяется не в момент создания объекта , а в момент вызова любого из методов объекта.
В связи с неявным определением this является константным указателем, т.е. по умолчанию происходит определение:
имя класса *const this = адрес обрабатываемого объекта
При работе с компонентами класса можно использовать указатель this
Эквивалентно:
Class point { int x, y ; Class point { int x, y ;
public: public:
point( int xx=0, int yy=0) point( int xx=0, int yy=0)
{this-> x=xx ; this ->y =yy ;} ; { x=xx ; y =yy ;} ;
void print ( void) void print ( void)
{ cout<< this->x <<” “ << this->y;} ; {cout<< x <<” “ <<y;} ;
} }
В таком использовании нет никаких преимуществ.
Иногда используется при конфликте имен , когда имена формальных параметров функций совпадают с именами компонентов класса:
Class point { int x, y ; Class point { int x, y ;
public: public:
point( int x=0, int y=0) point( int x=0, int y=0)
{this-> x=x ; this ->y =y ;} ; { point::x=x ; point::y =y ;} ;
// используя this // используя квалифицированное имя
…
Часто в функциях- членах класса параметры функции имеют объект некоторого класса ( или указатель на объект) , а объект еще не существует. Для этого также используется указатель this, обозначающий указатель на тот объект, для которого вызывается данная функция .
Class A
{ int x, y ;
public:
A ( int xx=0, int yy =0){ x=xx ; y = yy; }
A func ( ) ;
…
}
A A :: func( ) { if ( x%2) x++; // функция x преобразующая в четное
return * this;
}
void main ()
{ A a1 (17, 55)
A a2 = a1 . func ();
}
/* Можно объявить так
A* A:: func ( )
{ …
return this} */
Чаще всего this используется при организации связанных списков, звеньями которых должны быть объекты класса и встает необходимость включать в связи указатель на тот объект, который в данный момент обрабатывается
Пример: Очередь
#include <iostream.h>
#include<stdio.h>
Class que
{ static que*first ; // указатель(адрес)первого элемента очереди
que*next ; // указатель на следующий элемент очереди
char bukva; //содержимое элемента очереди
public: // общедоступные функции
que(char c) { bukva = c } ; // конструктор
void add (void) ; // функция добавления элемента в очередь
static void print (void); // вывод содержимого очереди
}
// определения функций:
void que::add(void)
{ que* list= first ; // вспомогательный указатель устанавливается на
// начало очереди
que * uk ; // вспомогательный указатель для продвижения по
//очереди
while(list!=NULL) { uk = list ; list=list->next }//продвижение по очереди
if( uk!=NULL) {uk->next=this; }//присоединение в конец очереди
else first = this; // очередь пустая
this->next=NULL; }
void que::print (void)
{ que *list = first ; // устанавливаем на начало очереди
if ( list = = NULL) {cout << “ список пуст”; return; }
else cout<<”содержимое списка :”
while( list!=NULL)
{ cout<< list->bukva; list= list->next; }//выводим и продвигаемся по
// очереди
}
que * que :: first = NULL; //инициализация статического компонента
void main( )
{ //формируем объекты класса
que A( a) ; que B(b ) ; queC(c); queD(d);
que::print( ); // выводим фразу, что список пуст
A.add( ); B.add( ); C.add( ); D.add( );//включаем в список
// элементы
que::print( ); } //выводим список