Програмен език C++

Част 16

Масивите като променливи в класовете
(Arrays as Instance Data)

(съдържание)

Масивите точно като другите типове данни могат да бъдат използвани като променливи на класовете. Нека започнем с един пример, който е взет от книгата на Лафоор [1].

1. Класът "служител" (The employee Class). Това е клас, който моделира данните, които се пазят за един служител в едно учреждение. С този клас ще бъде илюстрирана голяма част от следващия материал - наследяване на класовете, конструктори и деструктори и т.н.

За описанието на един служител в една професионална програма са необходими много полета, но тук ние ще се задоволим само с неговите имена - до 20 символа и неговия служебен номер. Оригиналната програма, взета от курса на Лафоор [1], с малки изменения е дадена по-долу.

// employ1.cpp
// models an employee
// uses array of type char for name

#include <iostream.h>
#include <conio.h>         // for getche()

class employee
{
   private:
      char name[20];       // name (20 chars max)
      int n;               // length of name
      unsigned long serial_number;
   public:
      void input()         // get data from user
      {
         char ch;
         n = 0;
         cout << "   Enter name: ";
         do
         {
            ch = getche();      // get one char at a time
            name[n] = ch;       // store in “name” array
            ++n;
         } while (ch != '\r');  // quit if “Enter” key

         cout << "\n   Enter serial number: ";
         cin >> serial_number;
      }

      void output()             // display employee data
      {
         cout << "   Name = ";

         for (int j = 0; j < n; ++j) // display one character
            cout << name[j];         // at a time
         cout << "\n   Serial number = " << serial_number;
      }
};  // class employee

void main()
{
   employee e1, e2;

   cout << "Enter employee 1 data" << endl;
   e1.input();
   cout << "Enter employee 2 data" << endl;
   e2.input();

   cout << "\nEmployee 1" << endl;
   e1.output();
   cout << "\nEmployee 2" << endl;
   e2.output();
 

   // stop the flow on the monitor
   getch();

}  //end main

Името на служителя се описва с масива name, който има максимална дължина от 20 символа. Дължина на името се пази в целочислената променлива n, а служебният номер - в serial_number.

Класът има две функции - input() и output(). В първата функция единствено непозната е библиотечната C функция getche(). Това е функция, която чете един символ от клавиатурата и го показва на екрана, за разлика от функцията getch(),  която използвахме в много от досегашните програми, и която функция не показва прочетения символ на екрана. Причината да се използва C функция е, че в C++ вкарването на само един символ е свързано с натискането на клавиша <Enter>, а това би значело в нашия случай да натискаме този клавиш след всяка една буква на името.

Другата особеност във функцията input() е цикълът do-while, който завършва своето изпълнение, ако вкараният символ има пореден номер 13 в ASCII таблицата. Това е символът, който се получава ако натиснем клавиша <Enter> и с условието while (ch != '\r') спираме въвеждането на буквите на името на служителя.

В главната функция се създават две променливи от класа employee и се вкарват данните за тях, които после се показват на екрана.

2. Префиксни и посфиксни оператори за нарастване и намаляване. (Postfix Increment and Decrement Operators).В лекция 5 се запознахме с тези оператори и сега ще използваме тези знания да съкратим с един ред функцията input(): казват, че допълнително и кодът става по-прегледен и разбираем. В префиксната си форма, например ++n, първо се увеличава (или намалява при --n) числото, а после се извършва следващата операция с него. Т.е. вместо двата реда

  name[n] = ch;         // store in “name” array
  ++n;

ако напишем

  name[++n] = ch;       // store in “name” array

ще имаме логическа грешка, защото при започване на четенето на символите n е било нула, и когато вкарваме първия символ ще имаме увеличение на n с единица и тогава следва присвояване на ch към name[1], т.е. масивът не започва от 0, както е прието в C++. Освен това ще е възможно вкарването на само 19 символа от името, да не говорим че при показване на името на екрана ще имаме трудности с name[0] , което е с неопределена стойност. Затова единственият ред трябва да използва постфиксен оператор:

name[n++] = ch;         // store in “name” array

3. Класът Стек (The Stack Class). Масивите са удобни за бързо обръщение към техните елементи с индексна променлива, защото техните елементи имат един и същ размер (и то предварително известен) и така може лесно да се изчисли мястото в паметта на съответния елемент. Съществуват и други конструкции за данни, които имат своите предимства и недостатъци. Една такава конструкция е стекът - той беше споменат в лекция 14, но там беше в контекста на паметта, в която се създават локалните променливи. Другото му значение е конструкция (структура), в която се вкарват данни и достъп има до последната вкарана данна - вижте втория смисъл на stack. Аналог по функционалност на стека е купчина чинии: (на английски куп(чина) е stack) - виж двете картинки по-долу.
 

...

Последната поставена чиния в купчината е първата, която може да се вземе. Това е реализация на принципа - "Последен вътре, първи вън" или на английски LIFO, for Last In First Out.

Разбира се, ние няма да използваме поредицата (sequence) от стандартната библиотеката, която работи на принципа "Последен вътре, първи вън", а ще си направим свой собствен клас, който имитира тази поредица. Стекът и опашката (queue), както и някои други конструкции (програмни структури) се изучават в курсовете по програмиране за напреднали и има много техни програмни реализации - с класове и без класове - но ние ще използваме програмата от курса на Лафоор [1].

Дефиницията на класа е дадена по-долу:

class Stack                   // a stack holds up to 20 ints
{
   private:
      int st[20];             // integers are stored in array
      int top;                // index of last item pushed

   public:
      void init()             // initialize index
      {
         top = -1;
      }

      void push(int var)      // place an item on the stack
      {
         st[++top] = var;
      }

      int pop()              // remove an item from the stack
      {
         return st[top--];
      }
};

Вкараните стойности се пазят в масив от 20 елемента с име st, а целочислената променлива top показва коя е текущата стойност в стека: в смисъл, кой елемент от масива е със стойност, вкарана преди малко - идеята е да имаме достъп само до този същия елемент. Стекът има нужда от инициализация, която се извършва с функцията init() и която се състои в приравняване на променливата top на -1. Вкарването на стойности става с процедурата push. С присвояването st[++top] = var се извършват две неща: първо променливата top нараства с единица и така сочи към следващия елемент на масива st, и второ, променливата var се присвоява на елемента на масива st, който е посочен от новата стойност на top. Функцията pop() връща последния вкаран в стека елемент с помощта на твърдението return st[top--]: забележете, че първо се връща текущия елемент, а после указателят top се намалява с единица и така той сочи към предишно вкарания елемент. Разбира се, няма защита от четене след връщане на всички елементи (например, опит за четене на st[-1]), ето защо потребителят трябва да се погрижи за изкарване на толкова елементи от стека (или не повече от тях), колкото са вкарани.

Ето и една програма от курса на Лафоор [1], която използва този клас.

// stack1.cpp
// class models a stack

#include <iostream.h>
#include <conio.h>            // for getche()

class Stack                   // a stack holds up to 20 ints
{
   private:
      int st[20];             // integers are stored in array
      int top;                // index of last item pushed

   public:
      void init()             // initialize index
      {
         top = -1;
      }

      void push(int var)      // place an item on the stack
      {
         st[++top] = var;
      }

      int pop()               // remove an item from the stack
      {
         return st[top--];
      }
};

void main()
{
   Stack s1;                  // create a stack object

   s1.init();                 // initialize it
   s1.push(11);               // push 3 items onto stack
   s1.push(12);
   s1.push(13);

   cout << s1.pop() << endl;   // pop 3 items and display them
   cout << s1.pop() << endl;
   cout << s1.pop() << endl;

   // stop the flow on the monitor
   getch();

}  //end main

В главната функция се създава една променлива s1 от класа Stack. Тя се инициализира с твърдението s1.init() и последователно в нея се вкарват числата 11, 12 и 13 чрез използване на функцията s1.push() в следващите три реда. А следващите три програмни реда използват функцията s1.pop() за да изкарат числата от стека. Обърнете внимание, че редът на излизане на елементите от стека (и на екрана) е следният:

13
12
11

т.е. напълно обратен на реда, по който бяха вкарани.

Разбира се, това бе една елементарна илюстрация на програмната структура (конструкция) стек, която за програмиста наподобява функционалността на стек. Въпреки наличието на масив като променлива на класа, програмистът борави само с трите функции на класа, без да се обръща директно към масива st. Казано с други думи класът Stack опакова (wraps) масива и осигурява интерфейс към него. Това е обща черта на класовете в ООП - те създават нов интерфейс (връзка) между програмиста и данните. Много добър пример за това опаковане е използването на класовете за осигуряване на интерфейс към API функциите на Windows (Windows Application Program Interface functions) с множество от лесни за употреба класове и техни функции.

Накрая ще споменем, че така дефинираният обект има нужда от инициализация, която се извършва с функцията init(). Тази функция винаги трябва да се извиква след създаване на обект от класа Stack. На практика, тя е недодялана версия на т.н. конструктор. За разлика от конструктора, който се изпълнява винаги, когато се създава обект от даден клас (например с декларацията Stack s1;),  програмистът трябва да се погрижи да не изпусне извикването на функцията init() след създаване на обекта с Stack s1;. В следващите лекции, когато се запознаем с конструкторите, функцията init() ще бъде заменена от конструктор.

.
(съдържание)

Литература

[1] Robert Lafore; C++ Interactive Course. Waite Group Press, Macmillan Computer Publishing, 1996.

Автор: Проф. Процесор Аритметиков

.
[ това е материал от брой 19 от май 2008 г. на списание "Коснос" www.kosnos.com ]

Keywords: С++,  OOP programming , C++ , Classes , Inheritance , Reusability , Creating New Data Types , Polymorphism and Overloading
Ключови думи: клас , обект, обектно ориентирано програмиране , полиморфизъм switch if else ?