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() побітова копія об'єкта не створюється. Передача об'єкта  за посиланням – типове вирішення описаної вище проблеми, але тільки у випадках, коли утворена ситуація дає змогу прийняти таке рішення, що  буває  далеко не  завжди. На щастя,  є  більш  загальне  рішення: можна створити власну версію конструктора копії об'єкта. Це дасть змогу точно визначити,  як  саме  потрібно  створювати  побітову  копію  об'єкта  і  тим  самим  уникнути описаних вище проблем. Але перед тим, як займемося конструктором копії, є сенс

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


 

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

12716. Создание модели детали типа вал в SolidWork 2006 и чертежа вала в Компас 3D v8 477.5 KB
  Практическая работа №7. Тема:Создание модели детали типа вал в SolidWork 2006 и чертежа вала в Компас 3D v8.Цель: Научиться сохранять созданные в SolidWorks модели в промежуточном формате импортировать их в Компас и создавать чертежи деталей. Необходимое оборудование и материа...
12717. Создание чертежа детали типа Корпус2 в программе компас 3D 221 KB
  Практическая работа №8. Тема:Создание чертежа детали типа Корпус2 в программе компас 3D.Цель: Создание чертежа детали типа корпус с применением модели корпусной детали созданной в практической работе № 3. Необходимое оборудование и материалы: ПК персональный
12718. Организация производственного процесса во времени 171.5 KB
  Лабораторная работа №1 по дисциплине Организация производства и менеджмент: Организация производственного процесса во времени Вариант №6 1.ОРГАНИЗАЦИЯ ПРОИЗВОДСТВЕННОГО ПРОЦЕССА ВО ВРЕМЕНИ. Тема: Производственный цикл изготовления изделий и его виды. ...
12719. ПОТОЧНЫЕ МЕТОДЫ ОРГАНИЗАЦИИ ПРОИЗВОДСТВА 194 KB
  Лабораторная работа №2 по дисциплине Организация производства и менеджмент: ПОТОЧНЫЕ МЕТОДЫ ОРГАНИЗАЦИИ ПРОИЗВОДСТВА Вариант №6 Расчет и построение стандартпланов работы однопредметных поточных линий. Задача 2.1. Определить длину сборочного конвейера ...
12720. Расчет основных календарно-плановых нормативов при оперативном планировании в серийном производстве 113.5 KB
  Лабораторная работа №3 по дисциплине Тема: Расчет основных календарноплановых нормативов при оперативном планировании в серийном производстве. Функциональная задача оперативного планирования в серийном производстве заключается в установлении основных календ...
12721. Экономическое обоснование выбора вариантов технологических процессов 132.5 KB
  Лабораторная работа №4 по дисциплине Организация производства и менеджмент: Экономическое обоснование выбора вариантов технологических процессов Вариант №6 ЗАДАЧА Произвести выбор наиболее экономического варианта операции механической обработки. Имеет
12722. Организация производства в машиностроении. Экономика и организация производства 183.5 KB
  Лабораторная работа №1 Организация производства в машиностроении и Экономика и организация производства Вариант №2 Задача 1.1 Построить графики технологического цикла сборки партии интегральных схем в количестве 4 штук при последовательном параллел...
12723. Циклы в VB .NET 12.43 KB
  Циклы 1.В VB .NET как практически во всех языках программирования существуют циклы конструкции позволяющие выполнять операции заданное количество раз или продолжать пока выполняется или наоборот не выполняется некоторое логическое условие. По сравнению с прежними
12724. Циклы с заданным числом повторений 54.5 KB
  Лабораторная работа № 16 Тема: Циклы с заданным числом повторений Общие сведения Циклом с заданным числом повторений называется процедура в которой вычислительные операции выполняются многократно заданное число раз. Циклы этого типа называются циклами типа €œДО€...