У вас вопросы?
У нас ответы:) SamZan.net

членов самого разнообразного назначения но два типа функций занимают особое положение

Работа добавлена на сайт samzan.net: 2015-07-10

Поможем написать учебную работу

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

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

от 25%

Подписываем

договор

Выберите тип работы:

Скидка 25% при заказе до 11.4.2025

PAGE  23

 Конструкторы и деструкторы

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

 

       Для многих объектов естественно требовать, чтобы они были инициированы (т.е. имели начальное значение, а не мусор) до начала их использования.

        В  С++  для упрощения процесса инициализации объектов предусмотрена специальная функция, называемая конструктором.

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

  1.  Основное назначение – инициализация объектов.

      

-  инициализация данных класса - задание им начальных

        значений  программно или по умолчанию,

     - открытие файлов,

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

  1.  Имя конструктора должно совпадать с именем класса.

3) Функция-конструктор не может возвращать результат, даже тип void не

    допустим.

 

4) Функция автоматически вызывается при определении объекта, или при       размещении в памяти объекта с помощью операции new.

  1.  Формат определения конструктора в теле класса:

                 < имя класса> ( список формальных параметров)

                         { операторы тела конструктора }

   

        Конструктор, как и любая компонентная функция, может быть определен     и вне тела класса, имея в теле класса прототип.

       class T  {

                           …

                         public:

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

                          …

                        } ;

         T::T( список параметров) { тело конструктора}

  1.  Как правило, конструкторы объявляются в открытой части класса
  2.  Конструктор может отсутствовать, при создании экземпляров  класса

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

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

8) В определении класса могут присутствовать несколько конструкторов.

   

     Деструктор - это функция, которая автоматически выполняется, когда экземпляр класса уничтожается.

( либо при выходе объекта за пределы своей области видимости, либо при уничтожении динамического объекта операцией  delete).

 

       Назначение – выполнение завершающих действий (напр., закрытие файлов, установка видеосистемы в исходное состояние, написание каких-либо фраз   и т.д.)

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

        Класс может иметь несколько конструкторов, но деструктор может быть только один.

Формат компонентной функции-деструктора

          ~ имя класса ( ) { операторы тела деструктора};

 

  1.  Между тильдой и именем класса нет пробелов.
  2.  У деструктора нет типа результата даже void и нет параметров даже типа void.
  3.  Деструктор выполняется неявно, автоматически, как только объект уничтожается. Его, как правило, никогда не вызывают, но можно и вызывать явно, если он определен в классе

            <имя объекта>. ~ <имя класса> ( );

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

Рассмотрим класс 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, и естественно будет сообщение об ошибке, т.к. это не строка).

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

  1.  без параметров;
  2.  с указанием только первого параметра ( три параметра – значения по умолчанию);
  3.  с указанием двух первых параметров ( два последних – по умолчанию)
  4.  с указанием трех первых (  последний – по умолчанию)
  5.  с указанием четырех параметров

 

Еще пример:   

… 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.

 

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

 

        Конструктор по умолчанию схож при вызове с конструктором с умалчиваемыми значениями и при написании конструкции:

 <Имя класса>   < имя объекта> ;

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

    

     При наличии в классе конструктора с параметрами, задаваемыми по умолчанию, объявлять конструктор по умолчанию нельзя!

      Если в определении класса вообще нет конструктора, компилятор автоматически предоставляет конструктор по умолчанию следующего вида:

 

<Имя класса>  ( ) {  }

который и создает неинициализированные объекты.

Конструктор копирования

Назначение:

  1.  создать объект  полностью совпадающий с уже созданным;

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

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

     Операции копирования объектов выполняет конструктор копирования.

В большинстве случаев компилятор предоставляет нам конструктор копирования по умолчанию, обеспечивая правильное копирование.

Копирование по умолчанию:

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

  1.  При копировании  e1 в локальную переменную obj  в функции Print,

   в  данные объекта obj передадутся значения x и  y объекта  e1  с

   инкрементом,  которые  и    будут  выведены     функцией  (x = 2    y = 11)

  1.  При копировании в e1 в  e2 , в  объект e2 передадутся

    инкрементированные значения x и y объекта e1    ( 2   и 11- значения

    данных  объекта e2).

  1.  Затем при вызове Print(e2)  , значения объекта 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

 

  1.  class stroka  {

               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;}

Деструктор автоматически освободит память.

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

                             

                                    Компонентные данные

  1.  При обращении к  данным из компонентных функций используется только

имя компонента

  1.  При обращении к компонентам из операторов, выполняемых вне  класса

( если позволяет статус доступа)

                 имя объекта. имя компонента

         указатель на объект -> имя компонента

  1.  Данные класса (а также компонентные функции) не обязательно должны быть описаны  до первого обращения к ним в классе. Т.е. все компоненты класса видны во всех операторах его тела.

В связи с этим отличием введена особая область видимости – класс (наряду с файлом , блоком и  функцией ).

  1.  Каждый объект класса имеет свою копию данных класса, кроме    статических данных.  Статический компонент (static) не тиражируется,     существует в единственном экземпляре

   

    Статические компоненты   класса  необходимо описывать и   инициировать

    вне определения класса как глобальные переменные.

  

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

   помощью квалифицированного имени:

имя класса ::имя компонента

    

   Если      объекты      объявлены, то    к   статическому  компоненту   можно

 обращаться  стандартно, и  всякое  изменение  статического  компонента   в одном объекте становится видно во всех объектах.

   

Если статический элемент имеет статус 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); …   

                        //изменение статического компонента до объявления   объектов

                       }

  1.       Указатели на компоненты класса

Определение указателя на компонентные данные класса:

тип данных(имя класса ::*имя указателя)

В определение можно включить и инициализацию

               & имя класса :: имя компонента

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, EGAEnhanced (усиленный) Graphics Adapter,  VGAVideo 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() ); - задержка программы до нажатия клавиши.

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

  1.  В чем суть объектно-ориентированного подхода к программированию? Дать определение класса.

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

  1.  Обращение к компонентам объекта

  1.  Что такое статический элемент класса

  1.  Что такое друзья класса

  1.  Что такое конструкторы и деструкторы. Какие типы конструкторов вы знаете?

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

      

Указатель 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( ); }                    //выводим список   




1. статья А. Лиханова Послесловие М.html
2. . Причини та умови виникнення філософії
3.  Внешняя политика Людовика XIV
4. вступлением на престол Петра Первого
5. Метафоры интернета
6. Зачем логопед задаёт домашние задания
7. Общие условия организация работы следователя
8. Види води в земній корі
9. 1 Изучить общие сведения о занулении- назначение область его практического применения и основные принципы
10. темах центрального водоснабжения