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

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


 

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

75453. Состав таблицы реляционной БД 23 KB
  Состав таблицы реляционной БД Реляционная база данных это совокупность отношений содержащих всю информацию которая должна храниться в БД. Иначе говоря в каждой позиции таблицы на пересечении строки и столбца всегда имеется в точности одно значение или ничего. Строки таблицы обязательно отличаются друг от друга хотя бы единственным значением что позволяет однозначно идентифицировать любую строку такой таблицы. Столбцам таблицы однозначно присваиваются имена и в каждом из них размещаются однородные значения данных даты фамилии целые...
75454. Организация бухучета в системе 1С 28 KB
  Организация бухучета в системе 1С На крупных предприятиях бухгалтерский учет организуется по двухуровневой системе управления управленческий и финансовый учет. Сметы нормативы калькуляции оптимальные соотношения затрат и результатов объекты управленческого учета. Информация управленческого учета имеет четко выраженную внутреннюю направленность. Информация финансового учета широко используется внешними потребителями инвесторами кредиторами и другими организациями и предприятиями.
75455. Назначение и цель анализа безубыточности в ИС Project Expert 23.5 KB
  Назначение и цель анализа безубыточности в ИС Project Expert Целью анализа безубыточности является выяснение влияния объема сбыта на уровень издержек и прибыли. Анализ даёт возможность решать ряд важных задач управления и планирования работы предприятия: формирование оптимальной номенклатуры изделий обоснование производственной программы определение стратегии и тактики ценообразования вычисление точки безубыточности производства Анализ безубыточности базируется на следующих предпосылках т. При соблюдении перечисленных условий легко...
75456. Понятие и содержание метаданных системы 1С: Предприятие 99 KB
  Понятие и содержание метаданных системы 1С: Предприятие Метаданные данные о данных совокупность объектов метаданных настроенных на хранение и обработку информации о хозяйственной деятельности конкретного предприятия. Формально объекты метаданных объединяются в виде дерева метаданных которое появляется при открытии окна Конфигурация Конфигуратора системы рис. Дерево метаданных Наряду с понятием метаданные используется термин структура метаданных. Данный термин более точно отражает суть метаданных как сложной структуры...
75457. Технология «клиент-сервер» для распределенных БД 103.5 KB
  Информационную основу системы клиент-сервер составляет распределенная база данных которая хранится на одном или нескольких серверах и с запросами к которой обращаются клиенты. Беглый обзор научной и профессиональной литературы показывает что о вычислениях...
75458. Последовательность разработки инвестиционного проекта в ИС Project Expert 24 KB
  Project Expert - одна из самых известных программ для составления бизнес-планов, она практически полностью автоматизирует составление бизнес-плана инвестиционного проекта...
75459. Реконфигурация системы БУ (1С) 21.5 KB
  Реконфигурация системы БУ 1С Системы комплексной автоматизации бухгалтерского учета потенциально способны решать любые задачи по всем разделам бухгалтерского учета. Такая функциональность обеспечена возможностью реконфигурации типовой модели учета реализованной в базовой версии например в программе 1C: Бухгалтерия 7. Таким образом помимо задач бухгалтерского учета стали автоматизироваться задачи оперативного управления. Например программы 1C: Бухгалтерия и 1C: Торговля и Склад рассматриваемые в комплексе являются системой...
75460. Этапы проектирования реляционных баз данных 30.5 KB
  Анализ предметной области заключается в получении от пользователя неструктурированных описаний прикладных задач базы данных выработке четкого определения и классификации элементов рассматриваемой предметной области. На основе собранной информации строится вербальная модель предметной области. Концептуальное проектирование состоит в формализации вербальной модели предметной области путем формирования ее концептуальной модели в виде схемы сущностьсвязь либо ERсхемы...
75461. Основные показатели эффективности и период расчета в ИС Project Expert 47.5 KB
  Отчет о прибылях и убытках отражает операционную деятельность предприятия здесь и далее под операционной деятельностью понимается процесс производства и сбыта продукции или услуг за определенные периоды времени месяц квартал год. Балансовая ведомость в отличие от Отчета о прибылях и убытках отражает финансовое состояние предприятия определенный момент времени...