67358

Узагальнені класи

Лекция

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

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

Украинкский

2014-09-07

142.5 KB

0 чел.

Лекція № 21

Тема: Узагальнені класи

План

  1.  Поняття про узагальнені класи
  2.  Створення класу з одним узагальненим типом даних
  3.  Створення класу з двома узагальненими типами даних
  4.  Приклад створення узагальненого класу для організації безпечного масиву
  5.  Використання в узагальнених класах аргументів, що не є узагальненими типами

  1.    Поняття про узагальнені класи

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

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

   Загальний формат оголошення узагальненого класу має такий вигляд:

template <class tType> class ім'я_класу 

{

     // Тіло класу

};

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

    Створивши узагальнений клас, можна створити його конкретний примірник, використовуючи такий загальний формат:

ім'я_класу <тип> ім'я_об'єкту;

    У цьому записі елемент тип означає ім'я типу даних, які оброблятимуться примірником узагальненого класу. Функції-члени узагальненого класу автоматично є узагальненими. Тому програмісту не потрібно використовувати ключове слово template для безпосереднього визначення їх такими.

  1.  Створення класу з одним узагальненим типом даних

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

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

const int size=5;

 

  // Створення узагальненого класу queueClass.

template <class qType> class queueClass

{         qType array[size];

          int sloc, rloc;

    public:

          queueClass() { sloc = rloc = 0; }

          void Get(qType c);

          qType Put(); // Виведення з об'єкта значення

};

   // Занесення об'єкта в чергу.

template <class qType> void queueClass<qType>::Get(qType c)

{     if(sloc>=size)

                  { cout << "Черга заповнена" << endl; return; }

      sloc++;

      array[sloc] = c;

}

   // Вилучення об'єкта з черги.

template <class qType> qType queueClass<qType>::Put()

{     if(rloc == sloc)

                 { cout << "Черга порожня" << endl; return 0; }

      rloc++;

      return array[rloc];

}

void main()

{    queueClass<int> ObjA, ObjB;         // Створюємо дві черги для int-значень.

     ObjA.Get(10);

     ObjB.Get(19);

     ObjA.Get(20);

     ObjB.Get(1);

     cout << ObjA.Put() << " ";

     cout << ObjA.Put() << " ";

     cout << ObjB.Put() << " ";

     cout << ObjB.Put() << endl;

     queueClass<double> ObjC, ObjD;         // Створюємо дві черги для double-значень

     ObjC.Get(10.12);

     ObjD.Get(19.99);

     ObjC.Get(-20.0);

     ObjD.Get(0.986);

     cout << ObjC.Put() << " ";

     cout << ObjC.Put() << " ";

     cout << ObjD.Put() << " ";

     cout << ObjD.Put() << endl;

}

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

10 20 19 1

10.12 -20 19.99 0.986

    У цьому коді програми оголошення узагальненого класу є подібним до оголошення узагальненої функції. Тип даних, який зберігаються в черзі, узагальнений в оголошенні класу. Він невідомий доти, доки не буде оголошений об'єкт класу queueClass, який і визначить реальний тип даних. Після оголошення конкретного примірника класу queueClass компілятор автоматично згенерує всі функції та змінні, необхідні для оброблення реальних даних. У цьому прикладі оголошуються два різних типи черги: дві черги для зберігання цілих чисел і дві черги для значень типу double. Зверніть особливу увагу на ці оголошення:

queueClass<int> ObjA, ObjB;

queueClass<double> ObjC, ObjD;

  Зауважте, як вказується потрібний тип даних: він поміщається в кутові дужки. Змінюючи тип даних при створенні об'єктів класу queueClass, можна змінити тип даних, який зберігаються в черзі. Наприклад, використовуючи таке оголошення, можна створити ще одну чергу, яка міститиме показники на символи:

queueClass<char *> chrptrQ;

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

struct addrStruct

{       // Оголошення типу структури

     char name[40];

     char street[40];

     char city[30];

     char state[3];

     char zip[12];

};

  Тепер для того, щоби за допомогою узагальненого класу queueClass, можна було згенерувати чергу для зберігання об'єктів типу addrStruct, достатньо використовувати таке оголошення:

queueClass<addrStruct> obj;

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

  1.   Створення класу з двома узагальненими типами даних

   Шаблонний клас може мати декілька узагальнених типів даних. Для цього достатньо оголосити всі потрібні типи даних в template-специфікації у вигляді елементів списку, що розділяються між собою комами. Наприклад, у наведеному нижче коді програми створюється клас, який використовує два узагальнених типи даних.

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

template <class aType, class bType> class myClass

{           aType c;

            bType d;

     public:

            myClass(aType ObjA, bType ObjB) { c = ObjA; d = ObjB; }

            void showType()

                         {cout << "c= " << c << "; d= " << d << endl; }

};

void main()

{    myClass<int, double> ObjA(10, 0.23);

     myClass<char, char *> ObjB('x', "Це тест.");

     ObjA.showType();           // Відображення int- і double-значень

     ObjB.showType ();          // Відображення значень типу char і char *

}

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

c= 10; d= 0.23

c= x; d= Це тест.

   У цьому коді програми оголошується два види об'єктів. Об'єкт ObjA використовує дані типу int і double, а об'єкт ObjB – символ і показник на символ. Для цих ситуацій компілятор автоматично генерує дані та функції відповідного способу побудови об'єктів.

  1.   Приклад створення узагальненого класу для організації безпечного масиву

   Розглянемо ще одне застосування узагальненого класу. Як було показано в попередніх темах, можна перевантажувати оператор "[]", що дає змогу створювати власні реалізації масивів, у тому числі і "безпечні масиви", які забезпечують динамічну перевірку порушення його меж. Як уже зазначалося вище, у процесі виконання програми, написаній мовою програмування C++, можливий вихід за межі масиву без видачі повідомлення про помилку. Але, якщо створити клас, який би містив масив, і дати змогу доступ до цього масиву тільки через перевантажений оператор індексації ("[]"), то можна перехопити індекс, що відповідає адресі за межами адресного простору масиву.

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

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

const int size = 10;

template <class aType> class arrClass        // Оголошення класового типу

{           aType aMas[size];

      public:

             arrClass() { for(int i=0; i<size; i++) aMas[i] = i; }

             arrClass &operator[](int i);

};

     // Забезпечення контролю меж для класу aType.

template <class aType> aType &arrClass<aType>::operator[](int i)

{   if(i<0 || i> size-1)

       {   cout << "Значення індексу " << i << " за межами масиву" << endl;

            exit(1);

        }

     return aMas[i];

}

void main()

{    arrClass<int> ObjI;            // Масив int-значень

     arrClass<double> ObjD;        // Масив double-значень

     cout << "Масив int-значень: ";

     for(int i=0; i<size; i++) ObjI[i] = i;

     for(int i=0; i<size; i++) cout << ObjI[i] << " ";

     cout << endl;

     cout << "Масив double-значень: ";

     for(int i=0; i<size; i++) ObjD[i] = i/3.0;

     for(int i=0; i<size; i++) cout << ObjD[i] << " ";

     cout << endl;

     ObjI[12] = 100;           // Помилка, спроба вийти за межі масиву!

}

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

Масив int-значень: 0 1 2 3 4 5 6 7 8 9

Масив double-значень: 0 0.333333 0.666667 1 1.333333 1.666667 2 2.333333 2.666667 3

   У цьому коді програми спочатку створюється узагальнений тип безпечного масиву, а потім продемонстровано його використання шляхом побудови масиву цілих чисел і масиву double-значень. Як доводить цей приклад, одна з великих переваг узагальнених класів полягає у тому, що вони дають змогу тільки один раз написати програмний код, відлагодити його, а потім застосовувати його до даних будь-якого типу, не переписуючи його для кожного конкретного застосування.

  1.   Використання в узагальнених класах аргументів, що не є узагальненими типами

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

Приклад. Демонстрація механізму використання в узагальнених класах аргументів, що не є

                 узагальненими типами

   // Тут елемент int size – це аргумент, що не є узагальненими типом.

template <class aType, int size> class arrClass

{           aType aMas[size];              // В аргументі size передається розмір масиву.__

      public:

            arrClass() { for(int i=0; i<size; i++) aMas[i] = i; }

            arrClass &operator[](int i);

};

   // Забезпечення контролю меж для класу aType.

template <class aType, int size> aType & arrClass<aType, size>::operator[](int i)

{   if(i<0 || i> size-1)

       {   cout << "Значення індексу " << i << " за межами масиву" << endl;

             exit(1);

        }

    return aMas[i];

}

void main()

{    arrClass<int, 10> ObjI;            // 10-елементний масив цілих чисел

     arrClass<double, 15> ObjD;         // 15-елементний масив double-значень

     cout << "Масив цілих чисел: ";

     for(int i=0; i<10; i++) ObjI[i] = i;

     for(int i=0; i<10; i++) cout << ObjI[i] << " ";

     cout << endl;

     cout << "Масив double-значень: ";

     for(int i=0; i<15; i++) ObjD[i] = i/3.0;

     for(int i=0; i<15; i++) cout << ObjD[i] << " ";

     cout << endl;

     ObjI[12] = 100;         // Помилка тривалості виконання!

}

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

Масив int-значень: 0 1 2 3 4 5 6 7 8 9

Масив double-значень: 0 0.333333 0.666667 1 1.333333 1.666667 2 2.333333 2.666667 3 3.333333

3.666667 4 4.333333 4.666667

   Розглянемо уважно template-специфікацію для класу aType. Зверніть увагу на те, що аргумент size оголошений з вказанням типу int. Цей параметр потім використовують в тілі класу aType для оголошення розміру масиву aMas. Хоча у початковому коді програми член size має вигляд "змінної", його значення відоме вже при компілюванні. Тому його можна успішно використовувати для встановлення розміру масиву. Окрім цього, значення "змінної" size використовують для контролю виходу за межі масиву в операторній функції operator[](). Зверніть також увагу на те, як у функції main() створюється масив цілих чисел і масив значень з плинною крапкою. При цьому розмір кожного з них визначається другим параметром template-специфікації.

   На тип параметрів, які не представляють типи, накладаються обмеження. У цьому випадку дозволено використовувати тільки цілочисельні типи, показники і посилання. Інші типи (наприклад, float) не допускаються. Аргументи, які передаються параметру, що не є узагальненими типом, повинні містити або цілочисельну константу, або показник, або посилання на глобальну функцію чи об'єкт. Таким чином, ці "нетипові" параметри необхідно розглядати як константи, оскільки їхні значення не можуть бути змінені. Наприклад, у тілі функції operator[]() така настанова недопустима:

size = 10;       // Помилка

   Оскільки параметри-"не типи" обробляються як константи, то їх можна використовувати для встановлення розміру масиву, що істотно полегшує роботу програмісту.

   Як показує наведений вище приклад створення безпечного масиву, використання "нетипових" параметрів дуже розширює сферу застосування шаблонних класів. І хоча інформація, яка передається через "нетиповий" аргумент, повинна бути відома при компілюванні, урахування цього обмеження непорівнянне з перевагами, пропонованими такими параметрами.

   

Тема: Використання в шаблонних класах аргументів за замовчуванням

   Шаблонний клас може за замовчуванням визначати аргумент, який відповідає узагальненому типу. Наприклад, внаслідок оголошення такої template-специфікації

template <class aType=int> class myClass { // ...

використовуватиметься тип int, якщо при створенні об'єкта класу myClass відсутнє задавання будь-якого типу.

   Для аргументів, які не представляють тип в template-специфікації, також дозволяється задавати значення за замовчуванням. Вони використовуються у випадку, якщо під час реалізації класу значення для такого аргументу безпосередньо не вказане. Аргументи за замовчуванням для "нетипових" параметрів задаються за допомогою синтаксису, аналогічного використовуваному під час задавання аргументів за замовчуванням для параметрів функцій.

    Розглянемо ще одну версію класу безпечного масиву, у якому використовуються аргументи за замовчуванням як для типу даних, так і для розміру масиву.

Приклад. Демонстрація механізму використання шаблонних аргументів за 

                  замовчуванням

     // Тут параметр aType за замовчуванням приймає тип int, a параметр

     // size за замовчуванням встановлюється таким, що дорівнює 10.

template <class aType=int, int size=10> class arrClass

{   aType aMas[size];         // Через параметр size передається розмір масиву.

public:

     arrClass() { for(int i=0; i<size; i++) aMas[i] = i; }

     arrClass &operator[](int i);

};

     // Забезпечення контролю меж для класу aType.

template <class aType, int size> aType &arrClass<aType, size>::operator[](int i)

{    if(i<0 || i> size-1)

       {   cout << "Значення індексу " << i << " за межами масиву" << endl;

            exit(1);

        }

return aMas[i];

}

void main()

{    arrClass<int, 100> intMas; // 100-елементний масив цілих чисел

     arrClass<double> doubleMas; // 10-елементний масив double-значень

             // (розмір масиву встановлено за замовчуванням)

      arrClass<> defMas; // 10-елементний масив int-значень

       // (розмір і тип int встановлені за замовчуванням)

      cout << "Масив цілих чисел: ";

      for(int i=0; i<100; i++) intMas[i] = i;

      for(int i=0; i<100; i++) cout << intMas[i] << " ";

      cout << endl;

      cout << "Масив double-значень: ";

      for(int i=0; i<10; i++) doubleMas[i] = i/3.0;

      for(int i=0; i<10; i++) cout << doubleMas[i] << " ";

      cout << endl;

      cout << "Масив за замовчуванням: ";

      for(int i=0; i<10; i++) defMas[i] = i;

      for(int i=0; i<10; i++) cout << defMas[i] << " ";

      cout << endl;

}

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

Масив int-значень: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ... 99

Масив double-значень: 0 0.333333 0.666667 1 1.333333 1.666667 2 2.333333 2.666667 3

Масив за замовчуванням: 0 1 2 3 4 5 6 7 8 9

   Зверніть особливу увагу на цей рядок:

template <class aType=int, int size=10> class arrClass {

    У цьому записі параметр aType за замовчуванням замінюється типом int, а параметр size за замовчуванням встановлюється таким, що дорівнює числу 10. Як показано у наведеному вище коді програми, об'єкти класу aType можна створити трьома способами:

● шляхом безпосереднього задавання як типу, так і розміру масиву;

● задавши безпосередньо тільки тип масиву, при цьому його розмір за замовчуванням встановлюється таким, що дорівнює 10 елементам;

● взагалі без задавання типу і розміру масиву, при цьому він за замовчуванням зберігатиме     елементи типу int, а його розмір за замовчуванням встановлюється таким, що дорівнює 10.

    Використання аргументів за замовчуванням (особливо типів) робить шаблонні класи ще гнучкішими. Тип, що використовується за замовчуванням, можна передбачити для найбільш вживаного типу даних, даючи змогу при цьому користувачу Ваших класів задавати потрібний тип даних.

Тема: Явні спеціалізації класів

    Подібно до шаблонних функцій можна створювати і спеціалізації узагальнених класів. Для цього використовується конструкція template<>, яка працює за аналогією із безпосередньо заданими спеціалізаціями функцій. Розглянемо такий приклад.

Приклад. Демонстрація безпосередньо заданої спеціалізації класів

template <class tType> class myClass

{           tType x;

     public:

            myClass(tType ObjA)

                    { cout << "У тілі узагальненого класу myClass" << endl; x = ObjA; }

            tType getX() { return x; }

};

  // Безпосередня спеціалізація для типу int.

template <> class myClass<int>

{           int x;

      public:

            myClass(int a) { cout << "У тілі спеціалізації myClass<int>" << endl; x = a * a; }

            int getX() { return x; }

};

void main()

{     myClass<double> ObjD(10.1);

      myClass<int> ObjI(5);

      cout << "int: " << ObjI.getX() << endl;

}

 

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

У тілі узагальненого класу myClass.

double: 10.1

У тілі спеціалізації myClass<int>.

int: 25

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

template <> class myClass<int> {

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

    Безпосередня спеціалізація класів розширює діапазон застосування узагальнених класів, оскільки вона дає змогу легко обробляти один або два спеціальні випадки, залишаючи всю решту варіантів для автоматичного оброблення компілятором. Але, якщо Ви побачите, що у Вас створюється дуже багато спеціалізацій, то тоді, можливо, краще взагалі відмовитися від створення шаблонного класу.


 

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

54658. Основные требования безопасности ремонтно-наладочных работ в действующих электроустановках 49.5 KB
  Снимать предупреждающие и запрещающие плакаты можно только после оформления записи в журнале об окончании ремонта СИЗ резиновые галоши резиновые коврики головные уборы спецодежда Ремонтные работы на кабельных линиях электропередач ОПФ при ремонте кабельных линий: возможность повреждения кабелей находящихся под напряжением при их раскопках; разрывы в кабельных линиях обычно в муфтах при недопустимом натяжении; опасность...
54659. Классификация зданий по взрывопожарной опасности 38 KB
  Пожароопасная категория В – помещения в которых находятся горючие трудногорючие жидкости твёрдые материалы; склады для хранения бумаги текстильных трикотажных обувных товаров. Пожароопасная категория Г – помещения где находятся негорючие вещества и материалы в горячем раскалённом состоянии; процесс обработки которых сопровождается выделением лучистого тепла искр и пламени; связанные со сжиганием жидкого твёрдого газообразного топлива. Классификация пожароопасных зон Пожароопасная зона – пространство внутри помещения в...
54660. Общие сведения о гидроприводе 139.5 KB
  Гидросистемы бывают: для подачи жидкости отсутствуют устройства преобразующие энергию жидкости в механическую работу системы водоснабжения зданий охлаждения смазывания машин – класс разомкнутых гидросистем движение жидкости за счет работы насоса; гидравлические приводы – совокупность устройств предназначенных для передачи механической энергии преобразования движения посредством рабочей жидкости – класс замкнутых гидросистем. К ним относят: насосы – гидромашины...
54661. Общие сведения об объемных насосах 1.21 MB
  Объемные насосы по характеру движения рабочего органа: возвратнопоступательные – рабочая камера относительно корпуса неподвижна; имеются впускной и выпускной клапаны для соединения рабочей камеры с полостями всасывания и нагнетания; роторные – рабочая камера подвижна клапаны отсутствуют. Возвратнопоступательные насосы По способу привода: прямодействующие – за счет возвратнопоступательного воздействия непосредственно на вытеснитель простейший насос с ручным приводом; вальные – за счет вращения ведущего вала преобразуемое в...
54662. Физические основы функционирования пневмосистем 792 KB
  Физические основы функционирования пневмосистем продолжение Термодинамические процессы – процессы в двигателях установках компрессорах протекающие при постоянных отдельных параметрах рабочего тела или при переменных всех параметрах. Равновесные термодинамические процессы – процессы проходящие при бесконечно малых перепадах давлений и температур при этом во всех точках термодинамической системы в любой момент времени параметры состояния одинаковы. Неравновесные необратимые термодинамические процессы – процессы проходящие...
54663. Физические основы функционирования пневмосистем 1.66 MB
  В конце адиабатного процесса цилиндр сообщается с холодильником точка D и рабочее тело изотермически сжимается по линии D T = const; давление возрастает объем уменьшается. Знак больше относится к неравновесным процессам; знак равно к равновесным. Получим уравнение изменения энтропии для произвольного термодинамического процесса. T – Sдиаграмма изохорного процесса характеризует тепло процесса.
54664. Компрессоры 339.5 KB
  Компрессоры по принципу действия: а динамические лопастного типа – энергия сообщается потоку газа за счет того что рабочие органы компрессора оказывают силовое воздействие на газ находящийся в его проточной части; их называют турбокомпрессорами – применяют при высокой производительности но невысоком давлении 10  15 атм. Рабочие камеры компрессора образуются поверхностью ротора стенками корпуса пластинами 3 которые свободно перемещаются в пазах ротора и центробежной силой прижимаются к корпусу компрессора. За счет эксцентриситета...
54665. Пневматические двигатели 5.3 MB
  Для осуществления рабочего хода полость C соединяется с атмосферой; канал 4 полости B – перекрывают. Давление в полости C падает; поршень двигается вправо. Как только поршень открывает отверстие m, резко возрастает движущая сила, т.к. сжатый воздух с давлением pвх действует на всю площадь поршня.
54666. Классификация приводов, схемы 1.54 MB
  Классификация приводов схемы Автоматизированный привод самодействующий привод выполняющий работу с частичным участием человека. Автоматический привод – самодействующий привод выполняющий работу без участия человека. Приводы по виду энергии: электрический привод в котором источником механических движений в оборудовании является электродвигатель; пневматический – привод в котором энергия сжатого воздуха или газа пневмодвигателем преобразуется в механическую;...