67157

Присвоєння об’єктів. Передача об’єктів функціям

Лекция

Информатика, кибернетика и программирование

Якщо два об'єкти мають однаковий тип (тобто обидва вони – об'єкти одного класу), то значення членів-даних одного об'єкта можна присвоїти іншому. Проте, для виконання операції присвоєння недостатньо, щоб два класи були фізично подібними; імена класів, об'єкти яких беруть участь в операції присвоєння, повинні збігатися.

Украинкский

2014-09-04

62.5 KB

5 чел.

Лекція № 6

Тема: Присвоєння об'єктів. Передача об'єктів функціям

План

  1.  Присвоєння об'єктів.
  2.  Передача об'єктів функціям.
  3.  Конструктори, деструктори при передачі об'єктів

  1.  Присвоєння об'єктів

    Якщо два об'єкти мають однаковий тип (тобто обидва вони – об'єкти одного класу), то значення членів-даних одного об'єкта можна присвоїти іншому. Проте, для виконання операції  присвоєння недостатньо, щоб два класи були фізично подібними; імена класів, об'єкти яких беруть участь в операції присвоєння, повинні збігатися. Якщо один об'єкт присвоюється іншому, то за замовчуванням дані першого об'єкта порозрядно копіюються у другий. Механізм присвоєння об'єктів продемонстровано у наведеному нижче коді програми.

Приклад1.  Демонстрація механізму присвоєння об'єктів 

class myClass

{            int a, b;

       public:

             myClass()   { a = b = 0; }

             void Set(int c, int d) { a = c; b = d; }

             void Show()   { cout << "a = " << a << "; b = " << b << endl; }

};

 

int main()

{     myClass ObjA, ObjB;           // Створення об'єктів класу

 

      ObjA.Set(10, 20);

      ObjB.Set(0, 0);

      cout << "Об'єкт ObjA до присвоєння:" << endl;

      ObjA.Show();

      cout << "Об'єкт ObjB до присвоєння:" << endl;

      ObjB.Show();

      cout << endl;

 

      ObjB = ObjA;          // Присвоюємо об'єкт ObjA об'єкту ObjB.

 

      cout << "Об'єкт ObjA після виконання операції присвоєння:" << endl;

      ObjA.Show();

      cout << "Об'єкт ObjB після виконання операції присвоєння:" << endl;

      ObjB.Show();

 

      getch(); return 0;

}

    Внаслідок виконання ця програма відображає на екрані такі результати:

Об'єкт ObjA до присвоєння:

a = 10; b = 20

Об'єкт ObjB до присвоєння:

a = 0; b = 0

Об'єкт ObjA після виконання операції присвоєння:

a = 10; b = 20

Об'єкт ObjB після виконання операції присвоєння:

a = 10; b = 20

    За замовчуванням усі значення членів-даних з одного об'єкта присвоюються іншому шляхом створення порозрядної копії. Але, як буде показано далі, оператор присвоєння можна перевантажити, визначивши власні операції присвоєння.

  1.  Особливості механізму передачі об'єктів функціям

    Об'єкт можна передати функції так само, як і змінну будь-якого іншого типу даних. Об'єкти передаються функціям шляхом використання звичайного С++-погодження про передачу параметрів за значенням. Згідно з цим погодженням, функції передається не сам об'єкт, а його копія. Це означає, що зміни, внесені в об'єкт-копію у процесі виконання функції, не роблять ніякого впливу на вхідний об'єкт, який використовується як аргумент для функції. Цей механізм продемонстровано у наведеному нижче коді програми.

Приклад 2.  Демонстрація механізму передачі об'єктів функціям 

class myClass

{            int c;

       public:

             myClass()     { c = 0; }

             void Set(int _c)    { c = _c; }

             void Show(char *s)  { cout << s << c << endl; }

};

 

void Fun(myClass obj)         // Визначення функції не члена класу

{     obj.Show("t2= ");           // Виведення числа 10.

      obj.Set(100);                  // Встановлює тільки локальну копію.

      obj.Show("t3= ");          // Виведення числа 100.

}

int main()

{     myClass Obj;          // Створення об'єкта класу

 

      Obj.Set(10);

      Obj.Show("t1= ");          // Виведення числа 10.

 

       Fun(Obj);            // Передача об'єкта функції не члена класу

 

       Obj.Show("t4= ");        // Як і раніше, виводиться 10, проте значення змінної "i" не змінилося

 

       getch(); return 0;

}

   Ось як виглядають результати виконання цієї програми.

t1= 10

t2= 10

t3= 100

t4= 10

    Як підтверджують ці результати, модифікування об'єкта obj у функції Fun() не впливає на об'єкт Obj у функції main().

  1.  Конструктори, деструктори при передачі об'єктів

    Хоча передача функціям нескладних об'єктів як аргументів – достатньо проста процедура, проте при цьому можуть відбуватися непередбачені події, які мають відношення до конструкторів і деструкторів. Щоб розібратися у цьому, розглянемо такий код програми.

Приклад 3.  Демонстрація механізму використання конструкторів, деструкторів при

                     передачі об'єктів

class myClass

{            int n;

        public:

              myClass(int _n)  { n = _n; cout << "Створення об'єкта" << endl; }

              ~myClass()   { cout << "Руйнування об'єкта" << endl; }

              int Put()   { return n; }

};

 

void Get(myClass obj)

{      cout << "n= " << obj.Put() << endl;

}

int main()

{      myClass ObjA(10);       // Створення об'єкта класу

 

       Get(ObjA);

 

       getch(); return 0;

}

     Внаслідок виконання ця програма відображає на екрані такі результати:

Створення об'єкта

n= 10

Руйнування об'єкта

Руйнування об'єкта

    Як  бачимо,  тут  здійснюється  одне  звернення  до функції  конструктора (при створенні об'єкта ObjA), але чомусь відбувається два звернення до функції деструктора. Давайте з'ясуємо, у чому тут проблема.

    При передачі об'єкта функції створюється його копія (і ця копія стає параметром  у  функції).  Створення  копії  означає  появу  нового  об'єкта.  Коли  виконання функції завершується, копія аргумента (тобто параметр) руйнується. Тут виникає відразу два запитання. По-перше, чи викликається конструктор об'єкта при створенні копії? По-друге, чи викликається деструктор об'єкта під час руйнування копії? Відповіді на ці запитання можуть здивувати Вас. Оскільки під час виклику функції створюється копія аргумента, то звичайний конструктор об'єкта не викликається. Натомість викликається конструктор копії об'єкта, який визначає, як має створюватися копія об'єкта. Але, якщо в класі безпосередньо не визначено конструктор копії, то мова програмування C++ надає його за замовчуванням. Конструктор копії за замовчуванням створює побітову (тобто однакову) копію об'єкта. Оскільки звичайний конструктор використовують для ініціалізації тільки деяких даних об'єкта, то він не повинен викликатися для створення копії вже наявного об'єкта. Такий виклик змінив би його вміст. При передачі об'єкта функції потрібно використовувати поточний стан об'єкта, а не його початковий стан. Однак, коли функція завершує свою роботу, то руйнується копія об'єкта, яка використовується  як  аргумент,  для  чого  викликається  деструктор  цього  об'єкта. Потреба виклику деструктора пов'язана з виходом об'єкта з області видимості його функцією, у якій він використовується. Саме тому попередня програма виводила два звернення перед зверненням до деструктора. Перше відбулося при виході з області видимості параметра функції Put(), а друге – під час руйнування об'єкта ObjA у функції main() після завершення роботи коду програми.

Отже,  коли  об'єкт передається функції  як  аргумент,  звичайний  конструктор не викликається. Замість нього викликається конструктор копії, який за замовчуванням створює побітову (тобто, однакову) копію цього об'єкта. Але коли ця побітова копія об'єкта руйнується (зазвичай при виході за межі області видимості його після завершення роботи функції), то обов'язково викликається деструктор.

Тема: Потенційні проблеми, які виникають при передачі об'єктів

    Як зазначалося вище, об'єкти передаються функціям "за значенням", тобто за допомогою  звичайного С++-механізму передачі параметрів,  який  теоретично  захищає аргумент  і  ізолює його від параметра, що приймається. Незважаючи на ці обставини, тут все-таки можливий побічний ефект або навіть загроза для "життя" об'єкта,  який  використовується  як  аргумент. Наприклад,  якщо  оригінальний  об'єкт, який потім використовується як аргумент, вимагає динамічного виділення області пам'яті та  звільняє цю пам'ять шляхом його руйнування, то локальна копія цього об'єкта під час виклику деструктора звільнить ту ж саму область пам'яті, яка була виділена оригінальному об'єкту. Поява такої ситуації стає потенційною проблемою,  оскільки  оригінальний  об'єкт  все ще  використовує  цю (вже  звільнену) область  пам'яті. Описана  ситуація  робить  оригінальний  об'єкт "збитковим"  і,  по суті, непридатним  для  використання. Для  розуміння  сказаного  вище  розглянемо таку навчальну програму.

Приклад.  Демонстрація механізму появи проблеми, яка виникає при передачі об'єктів

                 функціям, у яких динамічно виділяється та звільняється область пам'яті

class myClass

{            int *p;

       public:

             myClass(int c)

                {      p = new int; *p = c;

                        cout << "Виділення p-пам'яті звичайним конструктором" << endl;

                }

             ~myClass()

                {     delete p;

                       cout << "Звільнення p-пам'яті" << endl;

                 }

             int Put() { return *p; }

};

 

// У процесі виконання цієї функції якраз і виникає проблема.

void Get(myClass obj)              // Звичайна передача об'єкта

{

       cout << "*p= " << obj.Put() << endl;

}

 

int main()

{       myClass ObjA(10);  // Створення об'єкта класу

 

        Get(ObjA);

        getch(); return 0;

}

    Ось як виглядають результати виконання цієї програми.

Виділення p-пам'яті звичайним конструктором.

*p= 10

Звільнення p-пам'яті.

Звільнення p-пам'яті.

    Ця програма містить принципову помилку,  а  саме: при  створенні  у функції main() об'єкта ObjA виділяється область пам'яті, адреса якої присвоюється показнику ObjA.р. При передачі функції Get() об'єкт ObjA побітово копіюється в параметр obj. Це означає, що обидва об'єкти (ObjA і obj) матимуть однакове значення для показника р.  Іншими словами, в обох об'єктах (в оригіналі та його копії) член даних р вказуватиме на  одну  і  ту  саму  динамічно  виділену  область пам'яті. Після  завершення роботи функції Get() об'єкт obj руйнується за допомогою деструктора. Деструктор звільняє область пам'яті, яка адресується показником obj.р. Але ж ця (вже звільнена) область пам'яті –  та ж сама область, на яку все ще вказує член даних (початкового об'єкта) ObjA.р! Тобто, як на перший погляд – виникає серйозна помилка. Насправді  справи йдуть ще  гірше. Після  завершення  роботи  коду програми руйнується об'єкт ObjA  і динамічно виділена (ще під час його створення) пам'ять звільняється повторно. Йдеться про те, що звільнення однієї і тієї ж самої області динамічно  виділеної пам'яті удруге  вважається невизначеною операцією,  яка,  як правило (залежно від того, яка система динамічного розподілу пам'яті реалізована), спричиняє непоправну помилку. Одним  із шляхів вирішення проблеми, пов'язаної з руйнуванням (ще потрібних) даних деструктором об'єкта, який є параметром функції, полягає не в передачі самого об'єкта, а в передачі показника на нього або посилання. У цьому випадку копія об'єкта не створюється; отже, після завершення роботи функції деструктор  не  викликається. Ось  як  виглядає,  наприклад,  один  із  способів  виправлення попереднього коду програми.

Приклад.  Демонстрація механізму вирішення проблеми при передачі об'єктів функціям, у

                яких динамічно виділяється та звільняється область пам'яті

class myClass

{              int *p;

        public:

              myClass(int c)

                 {     p = new int; *p = c;

                         cout << "Виділення p-пам'яті звичайним конструктором" << endl;

                  }

              ~myClass();

                 {     delete p; cout << "Звільнення p-пам'яті" << endl;

                  }

              int Put()  { return *p; }

};

 

// Ця функція НЕ створює проблем.

void Get(myClass &obj)      // Передача об'єкта за посиланням

{        cout << "*p= " << obj.Put() << endl;

}

 

int main()

{       myClass ObjA(10);  // Створення об'єкта класу

 

       Get(ObjA);

        getch(); return 0;

}

    Оскільки об'єкт obj тепер передається за посиланням, то побітова копія аргумента не створюється, а отже, об'єкт не виходить з області видимості після завершення роботи функції Get(). Результати виконання цієї версії коду програми виглядають набагато краще від попередніх:

Виділення p-пам'яті звичайним конструктором

*p= 10

Звільнення p-пам'яті

    Як бачимо, тут деструктор викликається тільки один раз, оскільки при передачі за посиланням аргумента функції Get() побітова копія об'єкта не створюється. Передача об'єкта  за посиланням – типове вирішення описаної вище проблеми, але тільки у випадках, коли утворена ситуація дає змогу прийняти таке рішення, що  буває  далеко не  завжди. На щастя,  є  більш  загальне  рішення: можна створити власну версію конструктора копії об'єкта. Це дасть змогу точно визначити,  як  саме  потрібно  створювати  побітову  копію  об'єкта  і  тим  самим  уникнути описаних вище проблем. Але перед тим, як займемося конструктором копії, є сенс

розглянути ще одну ситуацію, у вирішенні якої ми також можемо отримати певний виграш завдяки створенню конструктора копії.


 

А также другие работы, которые могут Вас заинтересовать

36178. Налоги налогообложение 255.5 KB
  Принцип справедливости утверждающий всеобщность обложения и равномерность распределения налога между гражданами соразмерно их доходам которыми они пользуются под покровительством и защитой государства. Принцип экономии заключающийся в сокращении издержек взимания налога и рационализации системы налогообложения. Все последующие функциональные элементы налога объекты налогообложения ставки льготы и др. Расчет налога производится непосредственно от налоговой базы.
36179. Теория финансов 212 KB
  Страховая защита и страховые фонды Страхование создание специальными организациями фондов в денежной форме за счет страховых взносов юридических и физических лиц и предназначенных для реального возмещения только участникам их создания ущерба возникающего в результате наступления стихийных событий и несчастных случаев. страхование предусматривает перераспределение ущерба как в пространстве так и во времени; страховые взносы мобилизованные страховщиком имеют возвратный характер. Страхование подразделяется на три отрасли...
36180. Финансы организаций 210.5 KB
  Распределительная функция обеспечивает: образование целевых фондов денежных средств и поддержание рациональной структуры капитала; образование фонда возмещения который обеспечивает покрытие затрат на простое воспроизводство; формирование фонда оплаты труда возмещение затрат труда который состоит из двух частей: включаемой в затраты по производству и реализации продукции товаров работ услуг и образуемой за счет прибыли организации. Принцип самоокупаемости и самофинансирования означает полную окупаемость затрат на производство и...
36181. Финансовый менеджмент малого бизнеса 42.5 KB
  Основные этапы жизненного цикла малого предприятия; 1 этап разработка технологии и создание коммерческой схемы товара. В этот периодвыручка практически нулевая а денежные потоки предприятия отрицательные. Для этого необходимо определить посильные для предприятия темпыприроста оборота.
36182. Финансовый менеджмент. Учебное пособие 222.5 KB
  Потапова финансовый менеджмент Информационное обеспечение и финансовый анализ деятельности компании Учебнометодическое пособие Минск 2004 УДК 658. Информационное обеспечение и финансовый анализ деятельности компании: Учеб. Дается классификация финансовых коэффициентов характеризующих ликвидность деловую активность платежеспособность и рыночную активность компании. 2004 Частный институт управления и предпринимательства 2004 Информационное обеспечение и финансовый анализ деятельности компании Лекция I.
36183. Организация деятельность коммерческих банков 637 KB
  Банковская деятельность отличается достаточно жесткой правовой регламентацией банковских операций и постоянным обновлением законодательства по их проведению. Поэтому особое внимание в работе уделено происшедшим в последние годы изменениям в осуществлении операций, например, по предоставлению банковского кредита, в подходах при оценке и управлению банковскими рисками, расчетам нормативов безопасного функционирования.
36184. Конструкции и материалы самонесущих и навесных наружных стен 18.67 KB
  Помимо различных дифференциаций можно выделить 2 типа стен: cамонесущие навесные Самонесущие – стены опирающиеся на фундамент и несущие нагрузку от собственного веса включая нагрузку от балконов эркеров парапетов и других элементов стены по всей высоте но не воспринимающие нагрузки от других частей здания. В соответствии со строительной системой каждый тип стены содержит несколько видов конструкций: бетонные стены из монолитного бетона крупных блоков или панелей; каменные стены ручной кладки стены из каменных блоков и панелей;...
36185. Конструкции и материалы самонесущих и навесных наружных стен. Их особенности 16.22 KB
  Стена отделяет помещение от внешнего пространства наружные стены или от других помещений внутренние стены выполняя тем самым ограждающую функцию. Помимо различных дифференциаций можно выделить 2 типа стен: cамонесущие навесные Самонесущие – стены опирающиеся на фундамент и несущие нагрузку от собственного веса включая нагрузку от балконов эркеров парапетов и других элементов стены по всей высоте но не воспринимающие нагрузки от других частей здания. В соответствии со строительной системой каждый тип стены содержит несколько...
36186. Балконы, лоджии и эркеры 23.87 KB
  Устройство балконов лоджий и эркеров повышает комфортность жилых и общественных помещений и в то же время обогащает пластику фасадов зданий. Лоджии в отличие от балконов по боковым сторонам ограждены стенами и могут быть как встроенными в объем здания так и выносными. ОГРАЖДЕНИЯ БАЛКОНОВ Могут выполняться из различных материалов: непрозрачного стекла пластиков древесных материалов волнистой листовой стали на каркасе и т. КОНСТРУКТИВНЫЕ РЕШЕНИЯ Конструктивное решение балконов зависит от схемы опирания балконной плиты консольное...