Введение в
классы.
Содержание статьи:
Вступление.
Основы.
Конструкторы и
деструкторы.
Массивы объектов.
Вступление.
к началу статьи
Объектно-ориентированное программирование появилось уже много лет
назад, но до сих пор занимает почетное место среди концепций написания
программ. Попробуем разобраться, что же в нем такого хорошего.
При процедурно-ориентированном подходе рано или поздно наступал момент,
когда функций становилось слишком много, настолько много что даже
создание заголовочных файлов не спасало положение, код становился
неуправляемым. После долгих поисков были сформулированы основные
принципы ООП (объектно-ориентированного программирования). Этот подход
позволяет разложить программу на отдельные блоки, таким образом, что
при написании кода программист будет оперировать логическими понятиями,
что значительно проще работы с голыми функциями. Также концепция ООП
позволяет сделать программу более понятной, например:
Основы.
к началу статьи
|
cVector3 v1,v2,v3; float alpha;
v1 = alpha*v1 + (1-alpha)*v2;
|
Это ведь намного
понятнее чем: |
cVector3 v1,v2,v3; float alpha;
v1 = Interpolation( v1 , v2 , alpha );
|
Хочу заметить только
что первый пример не
совсем честный, он скорее демонстрирует связку ООП + Полиморфизм,
нежели просто ООП, в остальном же все корректно.
Надеюсь, я Вас убедил, что классы – полезная штука? Если да,
то давайте создадим себе новое божество!
Сперва,
рассмотрим синтаксис класса (наиболее простой вариант):
|
class [name]{
[private:]
[члены класса]
public:
[члены класса] };
|
public и private
– это
спецификаторы доступа к членам класса (полям и методам). Их значение
станет понятно из примера: |
class banana{
int x;
public:
int y;
};
//где-то в программе
banana b;
int tmp;
tmp = b.x;
//ошибка – доступ этому полю запрещен спецификатором private
tmp = b.y;
//все нормально – y находится в секции public
|
Все поля в классе по
умолчанию имеют
спецификатор доступа private. Как правило, в private прячут те поля и
методы, которые не желательно показывать остальному миру. Если же Вы
что-то пишете для себя то необходимости в таком разграничении доступа
не возникает.
В качестве полей класса могут быть переменные любого типа, с одним
небольшим исключением: |
class apple{
public:
int x;
apple a1;//ошибка
apple &a2;//корректно
apple *a3;//корректно
};
|
Со ссылкой и
указателем компилятору все
понятно: они занимают фиксированное и известное ему количество байт,
поэтому проблем не возникает. А вот в строчке кода apple a; компилятор
распознает рекурсию. Смотрите сами: сколько байт занимает объект этого
класса? Размер целого + размеры ссылки и указателя + размер объекта
класса apple.Сколько занимает объект этого класса? Размер целого +
размеры ссылки и указателя + размер объекта класса apple… И
так до бесконечности.
Конструкторы
и деструкторы.
к началу статьи
При создании объекта, возникают ситуации, когда необходима первичная
инициализация. Отдельно метод нам вызывать лениво (да и проблематично
каждый раз это делать, можно и забыть), поэтому рассмотрим такое
понятие как конструктор. |
class cVector{
public:
float x,y;
float mul( void ){return( x * y );}
cVector( void );
};
//вот определение конструктора
cVector::cVector( void ){x = y = 1;}
|
Посмотрим что тут есть
нового. Первое что
бросается в глаза – float mul( void ){return( x * y );}, хотя
такое и допускается синтаксисом, не стоит этим злоупотреблять; мы же могли сделать так: |
//стоит отметить, что с каждым классом связывается пространство имен, //доступ к которму осуществляется посредством оператора :: void cVector::mul( void ){return( x * y );}
|
Следующая вещь,
которая бросается в глаза
– конструктор не возвращает значения. Вот почему: |
cVector v1; //в этот момент вызывается cVector::cVector( void );
|
Итак, если бы
конструктор и возвращал
какое-то значение, мы бы не смогли с ним ничего сделать - синтаксисом
не предусмотрено (имеется ввиду синтаксис объявления
объектов/переменных). Конструктор без параметров называется
конструктором по умолчанию.
Теперь, собственно, сама суть конструкторов: |
cVector v1; //в этот момент вызывается cVector::cVector( void ); if( v1.x == 1 )exit(777); //произойдет завершение программы т.к. поле v1.x //инициализировано конструктором
|
Если же мы хотим чтобы
объект
инициализировался какими-то определенными значениями, мы должны
добавить в класс перегруженный конструктор: |
class cVector{ public:
float x,y; float mul( void ){return( x * y );} cVector( void ); cVector( int , int ); }; cVector::cVector( int _x , int_y ){ x = _x; y = _y; } //где-то в программе cVector v1,v2(1,2); //теперь v2.x==1 v2.y==2
|
Заметьте, если Вы не
определите в классе
никаких конструкторов, то компилятор создаст конструктор по-умолчанию
самостоятельно. Если же вы определите какой-либо конструктор (даже если
это не будет конструктор по-умолчанию) то компилятор НИЧЕГО не будет
создавать (видимо решит что Вы достаточно взрослый). Ради эксперимента
затрите в предыдущем примере cVector( void ) и, посмотрите что Вам
скажет компилятор.
В завершении раздела поговорим о деструкторах. Деструктор - это метод
класса, который ничего не принимает в качестве параметра и ничего не
возвращает, т.к. вызывается неявно при выходе объекта из блока, в
котором он(объект) был определен (хотя существует возможность явного
вызова, но это совсем уж дурной тон, даже я себе этого не позволяю).
Деструктор определяется следующим образом: |
class banana{ public: //наличие тильды перед названием класса обязательно //все вместе и будет именем деструктора как метода
~banana(){} };
|
Вызвать деструктор
можно так (только не в
MSVC 7.0): |
banana b; b.~banana();//моветон
|
Есть еще один нюанс:
если у Вас есть
класс, полями которого являются объекты других классов, то при
уничтожении объекта, сначала вызывается его деструктор а потом
вызывается деструктор его полей в порядке обратном их объявлению (а
потом вызываются деструкторы классов от которых наш класс был
наследован начиная с последнего в списке наследования и заканчивая
первым в иерархии, но об этом мы поговорим когда серьезно займемся
наследованием).
Массивы
объектов
к началу статьи
Массив объектов определяется так же как массив любого другого типа.
Правда, появляются некоторые дополнительные условия на методы класса: |
class banana{ class int i; banana( void ){
printf("default constructor\n"); } banana( int i ){
printf("int param constructor %i\n",i); } ~banana(){
printf("dectructor %i\n",i); } };
int main( void ){
//1
banana arr1[5];
//2
banana arr2[5]={0,1,0,1,0};
//3
banana *ptr = new banana[ 5 ];
ptr[ 0 ].i = 1;
ptr[ 1 ].i = 2;
ptr[ 2 ].i = 3;
ptr[ 3 ].i = 4;
ptr[ 4 ].i = 5;
//4 delete [] ptr; ptr = NULL; getch(); return 0; }
|
Теперь некоторые
правила, которые
продемонстрированы в данном примере:
- Для
элементов массива, не проинициализированных явным образом, вызывается
конструктор по умолчанию.
- Элементы
массива можно инициализировать стандартными численными типами и тогда
вызывается соответствующий конструктор, при инициализации объектами
других типов вызов конструктора не будет производится вообще.
- При
выделении памяти, вызывается конструктор по умолчанию для каждого
объекта в выделенном блоке памяти.
При запуске программы, Вы увидите: |
default constructor default constructor default constructor default constructor default constructor int param constructor 0 int param constructor 1 int param constructor 0 int param constructor 1 int param constructor 0 default constructor default constructor default constructor default constructor default constructor
|
Как видите пять раз
вызвался конструктор
по умолчанию при объявлении вектора объектов. Пять раз вызвался
конструктор banana(int), при инициализации второго массива. Затем снова
пять раз вызвался конструктор по умолчанию при выделении память под 5
объектов. Теперь ради интереса поменяйте строку кода banana
arr2[5]={0,1,0,1,0}; на banana arr2[5]={0,1,0,arr1[1],arr[1]};.
Заметьте: при такой замене конструктор banana(int) вызовется только 3
раза!
Перед тем как закончить разговор о конструкторах я Вам подкину одну
интересную мысль. Все типы, даже стандартные представляются в VC7 как
КЛАССЫ! Например, такая инструкция будет вполне корректна: |
int i( 0 );
|
Вызывается конструктор
int::int(int&)! Также в объявлении класса можно писать |
class banana{ class int i;
banana( void ){
printf("default constructor\n"); } banana( int j ):i(j){} };
|
И никакого
“старомодного” присваивания…
Осталось только прокомментировать следующий кусок кода: |
banana *ptr = new banana[ 5 ]; delete [] ptr;
|
Инструкцией delete []
ptr мы удаляем блок
памяти, вызывая для каждого объекта, располагающегося в нем,
деструктор.
Вот, пожалуй,
и все что я хотел рассказать Вам об основах использования классов.
Впереди еще много интересных вещей, посвященных ООП и его реализации в
c++. Надеюсь, туториал Вам понравится, и Вы получите представление о
том что этот язык программирования обладает огромным инструментарием
для создания и реализации всех Ваших замыслов. Удачи!
к началу статьи
ЗЫ: Способ связи прежний: dodonov_a_a@inbox.ru
|