Програмен език C++
(съдържание)

Част 5. Функции на класа

В тази лекция ще се спрем на функциите на класа (member functions) като разгледаме подробно програмата, която дадохме като пример в предния материал. Става въпрос за елементарна програма за управление на щандове, продаващи хот-дог, взета от превъзходния курс на Роберт Лафоор [1].

1. Програмата hotdog1.cpp. В нея има един специфициран клас, HotDogStand, който има две целочислени променливи HotDogsOnHand и BunsOnHand (виж програмния код по-долу). Тези променливи отчитат броя на хот-доците и броя на хлебчетата в наличие: предвидено е обектите от този клас да не оперират директно с тях, затова те са след ключовата дума private. Това означава, че ако се създаде обект, например с програмния ред HotDogStand stand1;, то не можем да напишем cout << stand1.HotDogsOnHand; или stand1.BunsOnHand = 47;.

class HotDogStand                // class specification
{
  private:
   int HotDogsOnHand;         // hot dogs on hand
   int BunsOnHand;            // buns on hand
  public:
   void displayData()         // display hot dogs and buns
    {
      cout << "\n   Hot dogs = " << HotDogsOnHand;
      cout << "\n   Buns = " << BunsOnHand;
    }

    void SoldOneDog()          // record sale of one dog
    {
      --HotDogsOnHand;
      --BunsOnHand;
    }

    void initData()            // set initial quantities
    {
      cout << "\n   Enter hot dogs on hand: ";
      cin >> HotDogsOnHand;
      cout << "   Enter buns on hand: ";
      cin >> BunsOnHand;
    }
 };  // end of HotDogStand class
 

За да може да боравим с обектните променливи, класът има три функции - displayData(), SoldOneDog() и initData(). Редът на дефиниране на функциите в класа няма значение, затова ще ги разгледаме по реда на тяхната функционалност.

След като се създадат обекти, техните променливи не заемат някакви конкретни стойности. Ето затова тези променливи трябва да се инициализират, и това прави функцията initData() - виж спецификацията на обекта по-горе.

Втората функция, SoldOneDog(), отмята една продажба на хот-дог, т.е. намалява броя на хот-доците и броя на хлебчетата с едно.  Тук особенното е, че се използва т.н. намаляващ оператор (decrement operator ) --, който намалява стойността на променливата, която е негов аргумент, с единица: стигнахме до една нова тема - аритметичните оператори, на която ще се спрем по-късно в тази лекция.

Функцията displayData() показва стойността на двете обектни променливи на екрана. Операторите, както и някои специални символи в нея, бяха разгледани в предишната лекция Вход и изход.

Обърнете внимание, че и трите функции са след ключовата дума public, което позволява тяхното извикване с тези програмни редове в програмата:

   stand1.initData();
   stand2.initData();

   stand1.SoldOneDog();
   stand1.SoldOneDog();

   stand2.SoldOneDog();
   stand2.SoldOneDog();
   stand2.SoldOneDog();

   stand1.displayData();
   stand2.displayData();

Разбира се в програма има и някои допълнителни редове, които упътват потребителя какво става в момента, например

   // set initial data
   cout << "\nInitialize data for stand 1";
   stand1.initData();
   cout << "\nInitialize data for stand 2";
   stand2.initData();

Това, което трябва да се отбележи тук е т.н. точков оператор (dot operator), който стои между името на обекта (например stand1) и името на функцията на класа, която се извиква - например initData()в израза stand1.initData();. Формалното име на този оператор е class member access operator (тук авторът се затруднява с българския превод, а може би и на читателя ще му стане ясно защо научно-техническата литература трябва да се чете на английски!). Причина за името му е, че чрез него имаме достъп (access) до функциите или променливите на класа, които съставят неговите членове (members). По-същият начин може да се обърнем и към променливи на класа (т.е. на обектите), стига те да са след ключовата дума public, но това не се препоръчва - по-добре е да има функции за достъп и промяна на променливите на обектите, отколкото те директно да се променят.

Разбира се, преди обектите да се инициализират и използват те трябва да се създадат. Както споменахме в най-първите лекции, създаването на обекти не се отличава от декларирането на променливи от кой да е от предефинираните типове. В програмата за продажба на хот-дог, това става в първите редове на функцията main(), като се създават два обекта - stand1 и stand2.

   // create two hot-dog stand objects
   HotDogStand stand1;
   HotDogStand stand2;

Обръщането към функция на класа или, по-формално, извикването на функция на класа с даден обект се нарича изпращане на съобщения към обектите (sending messages to objects),

2. Главната функция на програмата, main(). Всяка програма на C++ (както и тези на C) трябва да има главна функция, която се изписва в програмата така: main(). Изпълнението на програмата започва от първия ред в тялото на тази функция. В програмата за продажба на хотдоци главната функция е дефинирана да не връща никаква стойност - void main() Но тази дефиниция може да се направи и така - int main(), което означава, че след изпълнение на програмата тя (програмата или по-точно нейната главна функция) връща числена стойност. Обикновено тази числена стойност показва дали програмата е работила без грешка или някаква грешка е прекъснала нейното изпълнение. Това е така, защото в зората на операционните системи (от DOS 3.00 до DOS 6.22) програмите можеха да се стартират в т.н. сериен режим, който се описваше в серийни файлове (batch files) с разширение .BAT, и тогава тази върната от main() стойност може да се обработва от серийния файл.

Ако искате да използвате вашата програма в серийни файлове или да я стартирате с набор от параметри след нея, то може да дефинирате главната функция по следния начин:

int main(int argc, char * argv[])

където argc е целочислено число, показващо броя на параметрите (command-line arguments) след името на програмата, argv  е масив от показатели към стрингове (array of pointers to strings, char *[]). Първият елемент на този масив, argv[0], е пълното име на директорията, от която програмата се стартира (full path name of the program being run), argv[1] е първият стринг подаден към програмата, argv[2] - вторият и т.н. argv[argc - 1] е последният, а argv[argc] съдържа NULL.

Например, ако вашата програма се казва deletefiles и се стартира в директорията d:\test_progs по този начин

deletefiles test1.txt test2.doc "names of students.doc"

то променливите, които се връщат от главната функция са:

argc = 4
argv[0] = d:\test_progs
argv[1] = test1.txt
argv[2] = test2.doc
argv[3] = names of students.doc
argv[4] = NULL

Стартирането на функция със списък от аргументи и тяхната обработка от нея ще бъдат разгледани в следващите материали от курса.

3. Аритметичните оператори (Arithmetic operators). Вече видяхме един аритметичен оператор в програмата, която разгледахме.  В таблица 1 са дадени седем аритметични оператора.

Таблица 1. Аритметичните оператори
--------------------------------------------------------------------------------
Оператор (Operator)    Действие (Purpose)            Примери (Examples)
--------------------------------------------------------------------------------
  +                    Събиране (Addition)           var1 + var2
  -                    Изваждане (Subtraction)       fvar1 - 2.43
  *                    Умножение (Multiplication)    2 * pi
  /                    Делене (Division)             14 / 4
  %                    Остатък (Remainder)           14 % 4
  ++                   Увеличаващ оператор           count++
  --                   Намаляващ оператор            --days
--------------------------------------------------------------------------------

Първите пет оператора са бинарни (binary operators ), т.е. действат на два операнда (operands), както е показано в примерите.

!Типът на резултата зависи от типа на операндите, но имайте предвид, че дясната част на един аритметичен израз се изчислява и на нея се предава даден тип, независимо от лявата част: това твърдение, което се споменава между другото в ръководствата на съответния компилатор е довело до много мои грешки и разочарования в ранния ми програмистки период. Например следните редове дават "учудващ" резултат за начинаещите програмисти.

  float ratio;
  int a = 6, b = 4;
  ratio = a / b;

За стойността на ratio  се получава 1, вместо 1.5, въпреки че ratio е променлива от тип float. В този случай в дясно на израза ratio = a / b; стоят две променливи от тип int и операторът делене / действа като целочислено делене. По същият начин примерът от таблицата 14 / 4 ще даде 3, вместо очакваните 3.5: това може да се провери с програмния ред cout  << 14 / 4 << endl;.

Следващият оператор, %,  дава остатъка при деленето на две целочислени числа и формално се нарича modulus operator. В примера в таблицата той дава 2, което е остатък на деленето на 14 на 4: 14 = 3* 4 + 2.

Последните два оператора от таблица са унарни оператора (unary operators), защото те оперират върху един операнд. В примерите, count++ ще увеличи брояча (count) с едно, а --days - ще намали броя на дните (days) с едно. И двата оператора имат две форми - като представка (prefix) - отпред на операнда, или като надставка (postfix) - отзад на операнда. Има голяма разлика между тях. Първата се нарича preincrement (++days) или predecrement (--days), а втората - postincrement (days++) или postdecrement (days--). Разликата между pre и post е, че при pre първо се увеличава или намалява стойността на операнда, а после се извършват последващите операции, докато при post първо се извършват операциите в израза, а после се увеличава или намалява стойността на операнда. Например тези редове

  int k = 5, l = 5, m = 5;

  cout << "======  Prefix form =============== " << endl;
  k = ++l - 2;

  cout << "k = " << k << endl;
  cout << "l = " << l << endl;
  cout << "m = " << m << endl;

  cout << "======  Postfix form ============== " << endl;
  k = m++ - 2;
  cout << "k = " << k << endl;
  cout << "l = " << l << endl;
  cout << "m = " << m << endl;

ще дадат следните резултати на екрана

======  Prefix form =======
k = 4
l = 6
m = 5
======  Postfix form ======
k = 3
l = 6
m = 6

При префиксната форма, първо l се повишава с единица и става равно на 6, след това от него се изважда две и това се присвоява на k. , т.е. k е равно на 4. Докато при постфиксната форма, първо от m се изважда две и това се присвоява на k. , т.е. k е равно на 3,  и чак след това m се повишава с единица и става равно на 6.

4. Предимство (precedence) при изпълнение на операторите. В току що дадения пример видяхме някои разлики в това, кой оператор първо се изпълнява. За аритметичните оператори в C++ важат както и в другите програмни езици правила за предимство на изпълнение. За седемте оператора, дадени в таблица 1, важи следното:

Първо се изпълняват префиксните намаляващи или увеличаващи оператори
След това умножението, делението и операторите за остатък
След това събирането и изваждането
След това постфиксните намаляващи или увеличаващи оператори

Разбира се този ред се изпълнява от ляво на дясно, т.е. 12 / 6 / 2 ще даде 1, а не 4, което 4 би се получило от 12 / (6 / 2). Както видяхме токущо, редът на изпълнение може да се променя със кръгли скоби: обърнете внимание, че заграждането с повече на брой скоби не променя техния вид - те винаги са кръгли, ( и ), за разлика от училищните правила, където първо се използват кръгли (), после правоъгълни [], и накрая големи {} скоби.

5. Тип на резултата. Типът на резултата се определя от набор от правила, наречени "обикновени аритметични преобразования на типа" (set of rules known as ‘‘the usual arithmetic conversions’’). Главната цел на тези правила е да се получи "най-големият" тип операнди. Например ако имаме

  int k = 5;
  float r, s = 17.5;

  r = s + k;

то резултатът s + k ще е от тип float, който резултат после се присвоява на r, тъй като този тип допуска най-големи стойности от всички типове в дясно на аритметичния израз. Няма да се спираме подробно на тези правила, които може да намерите в ръководството на Вашия компилатор на C++. Само обърнете внимание отново, че типът на дясната част на един (аритметичен) израз се оценява независимо от типа на лявата част! Прочетете отново тази забележка, която беше дадена по-горе!

С това завършваме предварителния поглед върху класовете и обектите. Запознахме се с това що е това клас, пременлива на обект, функция на клас, както и някои предефинирани типове от данни и входа и изход от програмата. Редица термини, които бяха въведени в тази и предишната лекция са дефинирани в този материал.

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

Литература

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

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

-
[ това е материал от брой 12 от октомври 2007 г. на списание "Коснос" www.kosnos.com ]

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