67133

Поняття про функції - «друзі» класу

Лекция

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

Технологія об'єктно-орієнтованого програмування дає змогу організувати доступ до закритих членів класу функціями, які не є його членами. Для цього достатньо оголосити ці функції дружніми до цього класу. Щоб зробити функцію “другом” класу, потрібно помістити її прототип в public-розділ оголошення класу і попередити його ключовим словом friend.

Украинкский

2014-09-04

78.5 KB

2 чел.

Лекція № 5

Тема: Поняття про функції-"друзі" класу 

План

  1.  Поняття про функції-"друзі" класу
  2.  Особливості перевантаження конструкторів

  1.  Поняття про функції-"друзі" класу

    Технологія об'єктно-орієнтованого програмування дає змогу організувати доступ до закритих членів класу функціями, які не є його членами. Для цього достатньо  оголосити  ці  функції  дружніми  до  цього  класу. Щоб  зробити  функцію “другом” класу, потрібно помістити її прототип в public-розділ оголошення класу і попередити його ключовим словом  friend. Наприклад, наведений нижче фрагмент коду функції Fun() оголошується "другом" класу myClass:

class myClass       // Оголошення класового типу

{             //...

        public:

             friend void Fun(myClass obj);  // "Дружня" функція класу

             //...

};

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

Приклад 1. Демонстрація механізму використання "дружньої" функції для доступу до         

                   закритих членів класу

class myClass 

{               int a, b;

         public:

               myClass(int _a, int _b) { a = _a; b = _b; }

               friend int Sum(myClass obj); // Функція Sum() – "друг" класу myClass.

};

int Sum(myClass obj)                  // Функція Sum() не є членом ні одного класу.

{ //Оскільки функція Sum() – "друг"класу myClass,то вона має право на прямий доступ до його членів-даних a i b

          return obj.a + obj.b;

}

 

int main()

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

       cout << "Sum= " << Sum(Obj) << endl;

       getch(); return 0;

}

    У наведеному вище прикладі функція Sum() не є членом класу myClass. Проте вона має повний доступ до private-членів класу myClass. Зокрема, вона може безпосередньо використовувати значення obj.a і obj.b. Зверніть також увагу на те, що функція  Sum()  викликається  звичайним  способом,  тобто  без  прив'язування  до  імені об'єкта (і  без  використання  оператора "крапка"). Оскільки  вона  не  є  функцією-членом класу, то під час виклику її не потрібно кваліфікувати з вказанням  імені об'єкта (точніше, під час її викликуне можна задавати ім'я об'єкта.). Зазвичай "дружній" функції  як параметр передається  один або  декілька об'єктів класу, для яких вона є "другом". Робиться це так само як і у випадку передачі параметра функції Sum().

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

Приклад 2.  Демонстрація механізму використання "дружньої" функції для перевірки

                     статусу  кожного об'єкта

const int IDLE=0;

const int INUSE=1;

 

class bClass;          // Випереджувальне оголошення класу

 

class aClass

{              int status;         // IDLE, якщо повідомлення неактивне

                                       // INUSE, якщо повідомлення виведене на екран.

       public:

              void Set(int s) { status = s; }

              friend int Put(aClass obi, bClass obj);

};

 

class bClass

{             int status;          // IDLE, якщо повідомлення неактивне

                                       // INUSE, якщо повідомлення виведене на екран.

       public:

             void Set(int s) { status = s; }

             friend int Put(aClass obi, bClass obj);

};

 

// Функція Put() – "друг" для класів aClass і bClass.

int Put(aClass obi, bClass obj)

{       if(obi.status || obj.status) return 0;

                else

        return 1;

}

int main()

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

     bClass ObjY;          // Створення об'єкта класу

 

     ObjX.Set(IDLE);            // IDLE = 0

     ObjY.Set(IDLE);

 

     if(Put(ObjX, ObjY)) cout << "Екран вільний" << endl;

             else

     cout << "Відображається повідомлення" << endl;

 

     ObjX.Set(INUSE); // INUSE = 1

 

     if(Put(ObjX, ObjY)) cout << "Екран вільний" << endl;

             else

     cout << "Відображається повідомлення" << endl;

 

     getch(); return 0;

}

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

                      Екран вільний.

                      Відображається повідомлення.

   

     Оскільки функція Put() є "другом" як для класу aClass, так і для класу bClass, то вона має доступ до  закритого члена status, визначеного в обох класах. Таким чином, стан об'єкта кожного класу одночасно можна перевірити всього одним зверненням до функції Put().

    Зверніть  увагу  на  те, що  у  цьому  коді  програми  використовується  випереджувальне оголошення для  класу  bClass. Потреба  у ньому пов'язана  з  тим, що  оголошення функції Put() у класі aClass використовує посилання на клас bClass ще до його оголошення. Щоб створити випереджувальне оголошення для будь-якого класу, достатньо зробити це так, як показано у наведеному вище коді програми.

    "Дружня" функція одного класу може бути членом  іншого класу. Переробимо  наведену  вище  програму  так, щоби  функція  Put()  стала  членом  класу  aClass. Зверніть увагу на використання оператора дозволу області видимості (або оператора дозволу контексту) під час оголошення функції Put() як "друга" класу bClass.

Приклад 3.  Демонстрація механізму використання функції – члена одного класу і  

                     одночасно "дружньої функції" – для іншого класу

 

const int IDLE=0;

const int INUSE=1;

 

class bClass;   // Випереджувальне оголошення класу

 

class aClass

{               int status;         // IDLE, якщо повідомлення неактивне

                                         // INUSE, якщо повідомлення виведене на екран.

         public:

                void Set(int s) { status = s; }

                int Put(bClass obj);   // тепер це член класу aClass

};

class bClass

{               int status;         // IDLE, якщо повідомлення неактивне

                                         // INUSE, якщо повідомлення виведене на екран.

         public:

               void Set(int s) { status = s; }

               friend int aClass::Put(bClass obj);               // "Дружня" функція класу

};

 

// Функція Put() -- член класу aClass і "друг" класу bClass.

int aClass::Put(bClass obj)

{      if(status || obj.status) return 0;

               else

        return 1;

}

 

int main()

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

        bClass ObjY;         // Створення об'єкта класу

 

        ObjX.Set(IDLE);  // IDLE = 0

        ObjY.Set(IDLE);

 

        if(ObjX.Put(ObjY)) cout << "Екран вільний" << endl;

               else

        cout << "Відображається повідомлення" << endl;

 

        ObjX.Set(INUSE);                // INUSE = 1

 

       if(ObjX.Put(ObjY)) cout << "Екран вільний" << endl;

               else

       cout << "Відображається повідомлення" << endl;

 

       getch(); return 0;

}

     Оскільки функція Put()  є членом класу  aClass,  то вона має прямий доступ до змінної status об'єктів типу aClass. Отже, як параметр необхідно передавати функції Put() тільки об'єкти типу bClass.

  1.  Особливості перевантаження конструкторів

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

потім подає звуковий сигнал. У наведеному нижче коді програми конструктор перевантажується тричі, надаючи тим самим можливість задавати час як у секундах (причому  або  числом,  або  рядком),  так  і  у  хвилинах  і  секундах (за  допомогою двох цілочисельних  значень). У цьому коді програми використовується  стандартна бібліотечна функція clock(), яка повертає кількість сигналів, прийнятих від системного годинника з моменту початку виконання програми. Прототип цієї функції має такий вигляд:

             clock_t clock();

Тип clock_t є різновидом довгого цілочисельного типу. Операція ділення значення, що повертається функцією clock(), на  значення CLOCKS_PER_SEC дає  змогу

перетворити отриманий результат у секунди. Як прототип для функції clock(), так і

визначення константи CLOCKS_PER_SEC належать заголовку <ctime>.

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

#include <ctime>     // Для використання системного часу і дати

using namespace std;    // Використання стандартного простору імен

 

class timerClass

{           int s;

      public:

           timerClass(char *t) { s = atoi(t); }                 // Задавання секунд у вигляді рядка

           timerClass(int t) { s = t; }             // Задавання секунд у вигляді цілого числа

           timerClass(int xv, int sec) { s = xv*60 + sec; }   // Час, який задається в хвилинах і секундах

           timerClass(int hod, int xv, int sec) { s = 60*(hod*60 + xv) + sec; }       // Час, який задається в  

                                                                                                                 годинах, хвилинах і секундах

 

           void Run();        // Таймер відліку часу

};

 

void timerClass::Run()

{     clock_t t1 = clock();

       while((clock()/CLOCKS_PER_SEC – t1/CLOCKS_PER_SEC)< s);

       cout << "\a"; // Подання звукового сигналу

}

 

int main()

{      timerClass ObjA(10), ObjB("20"), ObjC(1, 10), ObjD(0, 2, 8);

 

       ObjA.Run(); // Відлік 10 секунд

       ObjB.Run(); // Відлік 20 секунд

       ObjC.Run(); // Відлік 1 хвилини і 10 секунд

       ObjD.Run(); // відлік 0 годин 2 хвилини і 8 секунд

 

      getch(); return 0;

}

    При створенні у функції main() об'єктів ObjA, ObjB, ObjC i ObjD класу timerClass він надає члену даних s початкові  значення чотирма різними способами, що підтримуються перевантаженими функціями конструкторів. У кожному випадку викликається  той  конструктор,  який  відповідає  заданому  переліку  параметрів,  і  тому правильно ініціалізує "свій" об'єкт.

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

Тема: Особливості механізму динамічної ініціалізації конструктора 

    У мові програмування C++ як локальні, так і глобальні змінні можна ініціалізувати  у  процесі  виконання  програми. Цей  процес  іноді  називають  динамічною ініціалізацією. Дотепер у більшості настанов ініціалізації, використовувалися константи. Проте одну  і  ту ж

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

           int n = strlen(str);

          double arc = sin(theta);

          float d = 1.02 * pm/delta;

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

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

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

    Наприклад, у версії коду програми таймера для створення двох об'єктів ObjB і ObjC, яку наведено нижче, використовується динамічна ініціалізація конструктора.

Приклад. Демонстрація механізму динамічної ініціалізації конструктора 

#include <ctime>               // Для використання системного часу і дати

using namespace std;        // Використання стандартного простору імен

 

class timerClass

{           int s;

     public:

            timerClass(char *t) { s = atoi(t); }                 // Задавання секунд у вигляді рядка

            timerClass(int t) { s = t; }                // Задавання секунд у вигляді цілого числа

            timerClass(int xv, int sec) { s = xv*60 + sec; }  // Час, який задається в хвилинах і секундах  

            timerClass(int hod, int xv, int sec) { s = 60*(hod*60 + xv) + sec; }      // Час, який задається в  

                                                                                                                 годинах, хвилинах і секундах

            void Run(); // Таймер відліку часу

};

 

void timerClass::Run()

{      clock_t t1 = clock();

        while((clock()/CLOCKS_PER_SEC – t1/CLOCKS_PER_SEC)< s);

        cout << "\a"; // Подання звукового сигналу

}

 

int main()

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

     ObjA.Run();

     char str[80];

 

     cout << "Введіть кількість секунд: ";

     cin >> str;

     timerClass ObjB(str);    // Динамічна ініціалізація конструктора

     ObjB.Run();

 

     int xv, sec;

     cout << "Введіть хвилини і секунди: "; cin >> xv >> sec;

     timerClass ObjC(xv, sec);        // Динамічна ініціалізація конструктора

     ObjC.Run();

 

     int hod;

     cout << "Введіть години, хвилини і секунди: "; cin >> hod >> xv >> sec;

     timerClass ObjD(hod, xv, sec);        // Динамічна ініціалізація конструктора

     ObjD.Run();

 

     getch(); return 0;

}

    Як  бачимо,  об'єкт  ObjA  створюється,  використовуючи  механізм  ініціалізації цілочисельної константи. Проте основою для побудови об'єктів ObjB  і ObjC слугує інформація,  яка  вводиться  користувачем.  Оскільки  для  об'єкта  ObjB  користувач вводить рядок, то є сенс перевантажити конструктор timerClass() для прийняття рядкової змінної. Об'єкт ObjC також створюється у процесі виконання програми з використанням  даних,  які  вводяться  користувачем. Оскільки  у  цьому  випадку  час вводиться у вигляді хвилин  і секунд, то для побудови об'єкта ObjC логічно використовувати формат конструктора, що приймає два цілочисельні аргументи. Аналогічно створюється об'єкт ObjD, для якого час вводиться у вигляді годин, хвилин і секунд,  тобто  використовується  формат  конструктора,  що  приймає  три  цілочисельних аргументи. Важко не погодитися з тим, що наявність декількох форматів ініціалізації конструктора позбавляє програміста від виконання додаткових перетворень під час ініціалізації членів-даних об'єктів.

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


 

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

39713. Понятие маркетинга, цели, принципы и функции маркетинга 17.15 KB
  Производственная: организация производственных товаров организация материальнотехнического снабжения управление качеством и конкурентоспособностью продукции. Сбытовая: организация системы товародвижения организация системы формирования спроса и стимулирования сбыта и системы продаж реклама товарная политика организация сервиса ценовая политика. Управления и контроля: организационные принципы управления и контроля планирование и информационное обеспечение маркетинга организация системы коммуникаций на предприятии управление рисками.
39714. Этапы развития маркетинга 17.05 KB
  Так в это время спрос намного превышает предложение и поэтому любой производитель может продать свой товар важную роль играет количество товара а не его качество. В определенный момент монополия конкретного товара становится тормозом развития своего рынка поэтому либо вмешивается государство антимонопольная политика либо фирма вынуждена переориентировать свою деятельность препятствуя падению покупательского спроса. Задачей производителя было произвести как можно больше товара и как можно изощреннее его продать. Все это привело к...
39715. Стратегическое планирование 15.63 KB
  Не используя преимущества стратегического планирования организации в целом и отдельные люди будут лишены четкого способа оценки цели и направления корпоративного предприятия. Процесс стратегического планирования обеспечивает основу для управления членами организации.Система стратегического планирования дает возможность акционерам и менеджменту компаний определиться с направлением и темпом развития бизнеса очертить глобальные тенденции рынка понять какие организационные и структурные изменения должны произойти в компании чтобы она стала...
39716. Основные компоненты стратегического планирования 15.71 KB
  В нем также раскрываются потенциальные препятствия с которыми предстоит столкнуться компании и пути их преодоления. Каждый хороший стратегический план должен содержать заявление о миссии компании. Оно должно быть кратким лаконичным объяснять причину существования компании и того чего она пытается добиться. Цели организации представляют собой пути посредством которых вы планируете выполнить миссию компании.
39717. Процесс управления маркетингом: планирование, организация, мотивация и контроль 19.66 KB
  Разработка комплекса маркетинга разработка товаров; установление цен на товары; методы распространения товаров; стимулирование сбыта товаров 4. Стратегия маркетинга – это генеральная программа маркетинговой деятельности на целевых рынках. Она включает главные направления маркетинговой деятельности фирмы и инструментарий комплекса маркетинга маркетингмикс с помощью которого разрабатывают и осуществляют маркетинговые мероприятия для достижения поставленных целей. Стратегия маркетинга показывает с каким продуктом на какие рынки с каким...
39718. Маркетинговый план 16.39 KB
  Анализ достигнутого уровня: историческая справка; ситуационный анализ; анализ продаж; анализ привлекательности отрасли; плановые ожидания прогнозы. В анализе достигнутого уровня предприятия дается историческая справка о его состоянии и деятельности и приводятся основные показатели деятельности предприятия в динамике. Дается описание рынка относительно его размера основных сегментов и потребностей покупателей проводятся анализ факторов среды обзор основных товаров приводятся перечень конкурентов и их оценка показаны каналы...