Програмен език C++
Част 22

Изброен тип и логически тип
(enum and bool)

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

В тази лекция ще се занимаем с два типа - изброен тип и логически тип. Те обикновенно се разглеждат заедно в книгите по програмиране, защото логическият тип е един вид предефиниран изброен тип.

1. Специфициране на изброен тип (Specifying an Enumerated Type). Спецификацията започва с ключовата дума enum, последвана от името на типа и заградени в големи скоби имената на стойностите на типа, които са разделени със запетайки. Накрая спецификацията завършва с точка и запетая. Едни от най-разпространените примери са дните от седмицата и боите на картите за игра:

 enum days_of_week {Sun, Mon, Tue, Wed, Thu, Fri, Sat};

 enum Suit {clubs, diamonds, hearts, spades};

Типът се нарича изброен, защото всички стойности, които може да приема променлива от този тип, се изброяват в големите скоби. Т.е. променлива от тип Suit може да приема четирите стойности clubs,diamonds,hearts и spades. Тези четири имена не са имена на променливи! Те не могат да стоят от лявата страна на едно присвояване! Тези четири имена могат да се разглеждат като константи от целочислен тип, както ще бъде показано по-долу.

2. Създаване и използване на променливи от изброен тип (Creating and Using Enumerated Variables). Дефинирането на такива променливи от тип enum е като с всеки един тип в C++ - първо се изписва името на типа и след него се изписват имената на променливите, разделени със запетайка. Следният код дефинира няколко променливи от гореспецифицираните два типа.

days_of_week yesterday, today, tomorrow;

Suit suit_of_card1, suit_of_chosen_card;

След дефиниране на тези променливи те могат да се използват както всички променливи в C++.

yesterday = Sun;
today = Mon;
tomorrow = Tue;

if (yesterday == Thu)
  tomorrow = Sat;

suit_of_chosen_card = diamonds;
suit_of_card1 = spades;

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

enum days_of_week {Sun, Mon, Tue, Wed, Thu, Fri, Sat }; // It is OK!
days_of_week yesterday, today, tomorrow;                // It is OK!
yesterday = Easter                                      // It is NOT OK! What is Easter?
Mon = yesterday;                                        // ILLEGAL: Mon is a constant

2. Изброеният тип е целочислен тип. Нека разгледаме кода по-долу.

 enum days_of_week {Sun, Mon, Tue, Wed, Thu, Fri, Sat };
 cout << "Value of Sun = " << Sun << endl
      << "Value of Mon = " << Mon << endl
      << "Value of Tue = " << Tue << endl
      << "Value of Wed = " << Wed << endl
      << "Value of Thu = " << Thu << endl
      << "Value of Fri = " << Fri << endl
      << "Value of Sat = " << Sat << endl;

На екрана той дава следното:

Value of Sun = 0
Value of Mon = 1
Value of Tue = 2
Value of Wed = 3
Value of Thu = 4
Value of Fri = 5
Value of Sat = 6

Т.е. на първата стойност в средните скоби при спецификацията на изброеният тип days_of_week съответства нула, на втората единица и т.н. Тези стойности могат да се разглеждат като целочислени и за тях важат същите операции както и с целочисления тип. Но да не забравяме, че те не са променливи и не могат да стоят в лявата част на едно присвояване. Разгледайте следните програмни редове:

 enum days_of_week {Sun, Mon, Tue, Wed, Thu, Fri, Sat };

 days_of_week yesterday, today, tomorrow;
 today = Mon;

 yesterday = today - 1;
 tomorrow = today + 1;

 cout << "yesterday = " << yesterday << endl
      << "today = " << today << endl
      << "tomorrow = " << tomorrow << endl;

Те ще дадат следния изход на екрана

yesterday = 0
today = 1
tomorrow = 2

Целите числа, свързани със стойностите на типа могат да бъдат променени. Например следният код

 enum days_of_week {Sun = 7, Mon = 1, Tue, Wed, Thu, Fri, Sat };
 cout << "Value of Sun = " << Sun << endl
      << "Value of Mon = " << Mon << endl
      << "Value of Tue = " << Tue << endl
      << "Value of Wed = " << Wed << endl
      << "Value of Thu = " << Thu << endl
      << "Value of Fri = " << Fri << endl
      << "Value of Sat = " << Sat << endl;

 if (Sun < Mon)            // logical comparisson
   cout << "It is American day-of-the-week count!";
 else
   cout << "It is Bulgarian day-of-the-week count!";

присвоява 7 на Sun, 1 на Mon, 2 на  Tue и т.н. до 6 на Sat. Ето и изходът на екрана:

Value of Sun = 7
Value of Mon = 1
Value of Tue = 2
Value of Wed = 3
Value of Thu = 4
Value of Fri = 5
Value of Sat = 6
It is Bulgarian day-of-the-week count!

Забележете, че стойностите на Tue и т.н. до Sat не се указват изрично, а се подразбират от Mon = 1, че започват от 2 и нарастват с единица. Бъдете внимателни с тези стойности, защото например следният код (подобен на няколко от редовете по-горе) дава неочаквани резултати, в смисъл на получаване на целочислена стойност (в случая 1), която я няма сред стойностите на типа days_of_week.

 enum days_of_week {Sun, Mon = 2, Tue, Wed, Thu, Fri, Sat };

 days_of_week yesterday, today, tomorrow;
 today = Mon;

 // yesterday obtains a value of 1
 // which not among the values of days_of_week!!!
 yesterday = today - 1;
 tomorrow = today + 1;

 cout << "yesterday = " << yesterday << endl
      << "today = " << today << endl
      << "tomorrow = " << tomorrow << endl;

4. Две примерни програми, които боравят с изброен тип. И двете програми са взети от превъзходния курс на Лафоор [1], Първата програма моделира разместването на три карти и познаването на една от тях. В програмата се специфицира структура card, която моделира карта за игра. Дефинират се шест карти от типа card. Една от тях, temp, се използва при размяната на картите, другата, chosen, е избраната от нас, трета, prize, е тази, за която ще спечелим, а останалите три, card1, card2  и card3, са конкретни карти. Обърнете внимание как се разменят картите, например първа и втора: temp = card2; card2 = card1; card1 = temp;: това е много често срещана ситуация в програмирането, например при сортиране на елементите на масив.

// cardenum.cpp
// demonstrates enumerations

#include <iostream>
#include "conio.h"
using namespace std;

const int jack = 11;         //2 through 10 are unnamed integers
const int queen = 12;
const int king = 13;
const int ace = 14;
enum Suit { clubs, diamonds, hearts, spades };
////////////////////////////////////////////////////////////////
struct card
{
   int number;               //2 to 10, jack, queen, king, ace
   Suit suit;                //clubs, diamonds, hearts, spades
};
////////////////////////////////////////////////////////////////
int main()
{
   card temp, chosen, prize;                  //define cards
   int position;

   card card1 = { 7, clubs };                 //initialize card1
   cout << "Card 1 is the seven of clubs\n";

   card card2 = { jack, hearts };             //initialize card2
   cout << "Card 2 is the jack of hearts\n";

   card card3 = { ace, spades };              //initialize card3
   cout << "Card 3 is the ace of spades\n";

   prize = card3;               //copy this card, to remember it

   cout << "I'm swapping card 1 and card 3\n";
   temp = card3; card3 = card1; card1 = temp;

   cout << "I'm swapping card 2 and card 3\n";
   temp = card3; card3 = card2; card2 = temp;

   cout << "I'm swapping card 1 and card 2\n";
   temp = card2; card2 = card1; card1 = temp;

   cout << "Now, where (1, 2, or 3) is the ace of spades? ";
   cin >> position;

   switch (position)
   {
      case 1: chosen = card1; break;
      case 2: chosen = card2; break;
      case 3: chosen = card3; break;
   }

   if(chosen.number == prize.number &&          //compare cards
      chosen.suit == prize.suit)
     cout << "That's right!  You win!\n";
   else
      cout << "Sorry. You lose.\n";

   getch();
   return 0;
}

Ето е едно примерно изпълнение на тази програма:

Card 1 is the seven of clubs
Card 2 is the jack of hearts
Card 3 is the ace of spades
I'm swapping card 1 and card 3
I'm swapping card 2 and card 3
I'm swapping card 1 and card 2
Now, where (1, 2, or 3) is the ace of spades? 3
Sorry. You lose.

Втората програма използва масиви, класове и изброени типове. Картите вале, дама, поп и ас са дефинирани като числа, за да може автоматично да се оперира с тях в циклите. Класът card има две променливи, първата, number, показва коя е картата (от двойка до ас), а втората, suit, която е от изброен тип - боята на картата. В главната програма се дефинира масив от 52 карти, наречен deck, което на английски означава тесте (колода) от карти. Разгледайте програмата, а след нея ще може да прочете за някои особености в нея.

// cardaray.cpp
// cards as objects

#include <iostream.h>
#include <stdlib.h>          // for randomize(), rand
#include <time.h>            // for randomize()
#include <conio.h>           // for getche()

enum Suit { clubs, diamonds, hearts, spades };

const int jack = 11;         // from 2 to 10 are
const int queen = 12;        // integers without names
const int king = 13;
const int ace = 14;

class card
{
   private:
      int number;            // 2 to 10, jack, queen, king, ace
      Suit suit;             // clubs, diamonds, hearts, spades

   public:
      void init(int n, Suit s)     // initialize card
      { suit = s; number = n; }

      void display()               // display the card
      {
         if (number >= 2 && number <= 10)
            cout << number;
         else
            switch(number)
            {
               case jack:  cout << 'J'; break;
               case queen: cout << 'Q'; break;
               case king:  cout << 'K'; break;
               case ace:   cout << 'A'; break;
            }
         switch(suit)
         {
            case clubs:    cout << char(5); break;
            case diamonds: cout << char(4); break;
            case hearts:   cout << char(3); break;
            case spades:   cout << char(6); break;
         }
   }  // end display()
}; // end class card

void main()
{
   card deck[52];                       // deck of cards
   int j = 0;                           // counts thru deck
   int num;                             // card number

   cout << endl;

   for (num = 2; num <= 14; num++)      // for each number
   {
      deck[j].init(num, clubs);         // set club
      deck[j + 13].init(num, diamonds); // set diamond
      deck[j + 26].init(num, hearts);   // set heart
      deck[j++ +39].init(num, spades);  // set spade
   }

   cout << "\nOrdered deck:\n";
   for (j = 0; j < 52; j++)             // display ordered deck
   {
      deck[j].display();
      cout << "  ";
      if( !( (j+1) % 13) )              // newline every 13 cards
         cout << endl;
   }

   randomize();                         // seed random number generator
   for (j = 0; j < 52; j++)             // for each card in the deck,
   {
      int k = random(52);               // pick another card at random
      card temp = deck[j];              // and swap them
      deck[j] = deck[k];
      deck[k] = temp;
   }

   cout << "\nShuffled deck:\n";
   for (j = 0; j < 52; j++)             // display shuffled deck
   {
      deck[j].display();
      cout << "  ";
      if ( !( (j + 1) % 13) )           // newline every 13 cards
         cout << endl;
   }

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

}  //end main

Най-труден за разбиране е първият цикъл, където картите се инициализират така както са подредени по боя и стойност. Инициализацията става с помощта на функцията init(int n, Suit s) на класа card. Обърнете внимание, че в цикъла се променят две променливи - num и j. По този начин картите се подреждат по бои, от спатия до пика, и във всяка една боя те нарастват от двойка до ас.

Вторият цикъл разпечатва картите на екрана, третия ги размества, а последния разпечатва вече размесените карти на екрана. Непознати функции тук са randomize() и random. Първата инициализира генератора на случайни числа (като обикновено се използва компютърния час) за да може серията от генерирани случайни числа да бъде всеки път различна при всяко едно изпълнение на програмата. Втората функция връща неотрицателно цяло число по-малко от аргумента си, в случая 52 - не забравяйте, че картите са номерирани от 0 до 51, а е от 1 до 52.

Във функцията display() на класа card се използва превръщането на цели числа (които са номерата на съответните ASCII символи) в тип char за да се разпечатат символите на боите спатия, каро, купа и пика. Това превръщане в символа за спатията изглежда така: char(6) .

5. Логически тип (Boolean type). В началото на спецификацията на C++ е нямало логически тип така както, например, в ПАСКАЛ има тип boolean. Този проблем се решава, както се запознахме в част 6, като нулата има значение на лъжа, а всички цели числа, различни от нея - значението на истина. Разбира се, програмистът може да дефинира изброен тип с две стойности по следния начин:

enum boolean (false, true);  // false is 0, true is 1

и да работи с променливи от този тип, например:

boolean flag;

if (alpha < 10)
   flag = false;

В следващите спецификации на C++ е определен тип bool, кой е логически тип и който се съпровожда с две предефинирани стойности (predefined literal valuestrue и false. Следният код показва, че тези стойности могат да се превръщат в цели числа, а именно true е 1, а false  - 0. Следният код го демонстрира:

 bool boolvar = true;
   cout << "true = " << boolvar << endl;

 boolvar = false;
   cout << "false = " << boolvar << endl;

Като дава на екрана следното:

true = 1
false = 0

В следващата лекция ще се върнем отново на функциите на класовете и в няколко леции ще се занимаем с тяхното предефиниране (Overloading), аргументи по подразбиране (Default Arguments) и референтни аргументи (Reference Arguments).

.
(съдържание)
.
Литература
.
[1] Robert Lafore; C++ Interactive Course. Waite Group Press, Macmillan Computer Publishing, 1996.

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

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

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