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


 

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

29564. Правило наименьших издержек 138.5 KB
  Правило наименьших издержек.5 Правило наименьших издержек это условие согласно которому издержки минимизируются в том случае когда последний доллар марка рубль и так далее затраченный на каждый ресурс дает одинаковую отдачу одинаковый предельный продукт. Правило наименьших издержек обеспечивает равновесие положения производителя. В этом положении достигается оптимальная комбинация факторов производства обеспечивающая максимизацию издержек.
29565. Трансакционные издержки 73 KB
  Трансакционные издержки По материалам курсовой Определений понятия трансакционные издержки множество каждый из ученых пытается выделить какуюлибо специфичную сторону данных видов затрат однако в целом обобщая можно сказать что трансакционные издержки представляют собой определенные затраты затраты ресурсов времени на взаимоотношения с внешним окружением. Можно сказать что трансакционные издержки – это цена оплачиваемая экономической системой за несовершенства и провалы рынка и провалы государства. То есть...
29566. Конкурентные и неконкурентные рынки 69.5 KB
  Принимая решение он рассматривает два его следствия: Эффект объема производства. Эффект цены. Если эффект объема производства больше чем эффект цены владелец колодца увеличит предложение воды. Если эффект цены превышает эффект объема производитель откажется от планов увеличения предложения.
29567. Понятия «неопределенность» и «риск». Предпосылки поведения потребителя в условиях неопределенности 365.5 KB
  Понятия неопределенность и риск. Неопределенность как условие риска Неопределенность – одно из центральных понятий в современной теории и практике управления. Неопределенность выступает необходимым и достаточным условием риска в принятии решений. Как отмечается в этимологическом словаре Фасмера термины риск рисковать происходят от греческого rysicon – утес скала; отсюда рисковать – значит взбираться на скалу или лавировать между скалами.
29568. Теория игр в выборе потребителя. Динамические игры. Координационные игры 487.5 KB
  Динамические игры. Координационные игры. думаю главное самое основное рассказать у теории игр большой математический аппарат который нет смысла сейчас изучать главное передать суть теории применительно к выбору потребителя и к решениям принимаемым на предприятиях в условиях олигополии стратегии равновесия выигрыши. Из лекции Бодрова у него только про статические игры: Теория игр анализирует принятие решений экономическими субъектами называемыми в соответствии с установившейся традицией игроками в ситуациях когда на результат...
29569. Эластичность спроса. Эластичность спроса относительно дохода 73 KB
  Эластичность спроса. Эластичность спроса относительно цены показывает относительное изменение объема спроса под влиянием изменения цены на один процент. Оно вызывает значительное изменение величины спроса. Рост цен автомобиля Вольво на 10 рублей практически не ощутим для покупателей этой автомашины поэтому изменение цены и величины спроса дается в формуле эластичности не абсолютно а относительно: EPD =  Q Q : P P  = Q в P в  3.
29570. Потребительское поведение и выбор потребителя 117.5 KB
  Полезность блага utility of good – это способность экономического блага удовлетворять одну или несколько человеческих потребностей. была выявлена закономерность: потребляемые последовательно части какоголибо блага обладают убывающей полезностью для потребителя. Это означает что любому бесконечно малому увеличению количества блага Q соответствует прирост общей полезности totl utility – TU см. Хотя общая полезность с увеличением количества благ постепенно возрастает предельная полезность mrginl utility MU каждой дополнительной...
29571. Кривая цена-потребеление 55 KB
  Однако при этом не учитываются два важных обстоятельства: цены товаров и доход потребителей. Если I доход потребителя Px цена блага X Py цена блага Y а X и Y составляют соответственно купленные количества благ то уравнение бюджетного ограничения можно записать следующим образом: I = Px X PY Y или в более привычном виде: Y = I Py – Px Py  X где –Px Py – угловой коэффициент бюджетной линии который измеряет наклон этой линии к оси абсцисс. При X = 0 Y = I Py то есть весь доход потребителя расходуется на благо Y....