11. Класове

 
"Изпървом бил Еру, Единствения, когото в Арда наричат Илуватар;
и той сътворил Свещените Айнури, що били рожби на неговата
мисъл и съществували заедно с него още когато нямало
нищо друго."
                                                       Дж. Р. Р. Толкин, СИЛМАРИЛИОНЪТ
 
    В лекциите до тук за класовете са разгледани:
- конструиране на обект: Employee harry("Hacher, Harry", 350);
- модифициране на обект: harry.set_salary(380);
- извеждане на информация за обект: cout << harry.get_name();
Въведени са и основните понятия конструктор, член-функция и операция точка.


  Обособяване на класове.
    Ще разгледаме задача, която се решава без използване на клас и след това с дефиниране и използване на подходящ клас.
    Чете се информация за компютри, съдържаща:
име на модел name OmniBook XE
цена price 5660
оценка score 76
OmniBook XE
5660
76
ACMA P300
1095
75
AMAX Poewrstation
1999
78
Търси се продукта с най-голямо отношение оценка/цена.
** Първото програмно решение е без използване на класове.
// bangbuck.cpp
#include <iostream>
#include <string>
using namespace std;

int main()
{  string best_name = "";
   double best_price = 0;
   int best_score = 0;
   bool more = true;
   while (more)
   {  string next_name;
      double next_price;
      int next_score;
      cout << "Please enter the model name: ";
      getline(cin, next_name);
      cout << "Please enter the price: ";
      cin >> next_price;
      cout << "Please enter the score: ";
      cin >> next_score;
      string remainder; /* read remainder of line */
      getline(cin, remainder);
      if (next_price != 0)
      {  if (best_price == 0 or
            next_score/next_price > best_score/best_price)
         {  best_name = next_name;
            best_score = next_score;
            best_price = next_price;
         }
      }
      cout << "More data? (y/n) ";
      string answer;
      getline(cin, answer);
      if (answer != "y") more = false;
   }
   cout << "The best bang for the buck is " << best_name
      << " (Score: " << best_score
      << " Price: " << best_price << ")\n";
   return 0;
}

Please enter the model name: OmnoBook XE
Please enter the price: 5660
Please enter the score: 76
More data? (y/n) y
Please enter the model name: Junk PC
Please enter the price: 1230
Please enter the score: 51
More data? (y/n) n
The best bang for the buck is Junk PC (Score: 51 Price: 1230)

Интерфейс и капсулиране, членове-функции.
** Второ програмно решение на задачата с използване на класове.
// product2.cpp
#include <iostream>
#include <string>
using namespace std;

/* ДЕФИНИЦИЯ НА КЛАСА */
class Product  {
public:                 /* интерфейс, открита част */
/* декларации на член-функциите на класа */
   Product();                         /* създава нов продукт */
   void read();                       /* чете продукт        */
   bool is_better_than(Product b) const;/* сравнява продукти */
   void print() const;                /* отпечатва продукт   */
private:               /* капсулиране, скрита част */
   string name;                       /* скрити данни */
   double price;                      /* скрити данни */
   int score;                         /* скрити данни */
};
/* ИЗПОЛЗВАНЕ НА КЛАСА */
int main()
{  Product best;       /* дефиниране на обект от класа */
   bool more = true;
   while (more)
   {  Product next;
      next.read();    /* извикване на член-функция на класа */
      if (next.is_better_than(best)) best = next;
      cout << "More data? (y/n) ";
      string answer;
      getline(cin, answer);
      if (answer != "y") more = false;
   }
   cout << "The best bang for the buck is ";
   best.print();        /* извикване на член-функция на класа */
   return 0;
}
/* РЕАЛИЗАЦИЯ НА КЛАСА */
/* дефиниция на конструктор */
Product::Product()
{ price = 10000;
  score = 0;
}
/* дефиниция на член-функция мутатор (set-функция) */
void Product::read()
{  cout << "Please enter the model name: ";
   getline(cin, name);
   cout << "Please enter the price: ";
   cin >> price;
   cout << "Please enter the score: ";
   cin >> score;
   string remainder;
   getline(cin, remainder);
}
/* дефиниция на член-функции за достъп (get-функция) */
bool Product::is_better_than(Product b) const
{  if (price == 0) return false;
    if (b.price == 0) return true;
    if (score/price > b.score/b.price) return true;
    return false;
}
/* дефиниция на член-функции за достъп (get-функция) */
void Product::print() const
{ cout << name
       << " Price: " << price
       << " Score: " << score << "\n";
}
/* КРАЙ на файла, съдържащ текста на програмата */
** Неявен параметър на член-функция:
- set-функции (мутатори) - променят (стойността на) неявния си параметър;
    void read();
- get-функции (функции за достъп) - не променят (стойността на) неявния си параметър, могат да се обявят като константни функции.
    void print() const;  



Конструктори по подразбиране и с параметри.
** Конструкторът инициализира (задава начални стойности на) данните на класа. Няма тип и не връща стойност.
** Предефиниране на функции - две функции в една програма на С++ могат да имат еднакви имена. За да бъдат различими, те трябва да имат различен брой параметри или/и параметрите им да са от различен тип.
class Employee {
public:
/* конструктор без параметри */
   Employee();
/* конструктор с параметри */
   Employee(string emp_name, double init_salary);
   void set_salary(double new_salary);
   string get_name() const;
   double get_salary() const;
   void print() const;
private:
   string name;
   double salary;
};
/* дефиниция на канструктора с параметри */
Employee::Employee(string emp_name, double init_salary)
{ name = emp_name;
  salary = init_salary;  }

* Полета с данни - обекти от други класове.
class Employee {
public:
   Employee(string n, double sal, int arr, int leav);
   ...
private:
   string name;
   double salary;
   Time arrive;
   Time leave;
};

Employee::Employee(string n, double sal, int arr, int leav)
{ name = n;
  salary = sal;
  arrive = Time(arr, 0, 0);
  leave = Time(leav, 0, 0);
}
Класът Employee съдържа  членове-данни, които са обекти от клас Time.


Достъп до полетата с данни.
** Само членовете-функции имат достъп до скритите полета с данни.
    string Employee::get_name() const
    { return name;
    }
    void Employee::set_salary(double new_salary)
    { salary = new_salary;
    }
** Явни и неявни параметри на функции.
    Отпечатване на името и заплатата на служител, реализирано с три различни функции:
-- член-функция с използване на достъп до скритите данни:
   void Employee::print() const
   { cout << "Name: " << name << "    "
          << "Salary: " << salary << "\n";
   }
-- член-функция с използване на член-функции от същия клас:
   void Employee::print() const
   { cout << "Name: " << get_name() << "    "
          << "Salary: " << get_salary() << "\n";
   }
-- външна за класа функция:
   void print(Employee const &emp)
   { cout << "Name: " << emp.get_name() << "    "
          << "Salary: " << emp.get_salary() << "\n";
   }



Обектно-ориентирано проектиране.
    Процесът от описание на задачата до реализацията на С++, използвайки обектно-ориентиран подход:
1. Обособяване на обекти и класове.
2. Дефиниране на интерфейс за всеки от тези класове.
3. Реализиране на интерфейса.
4. Комбиниране на обектите, за да се реши задачата.
    Ще илюстрираме този процес с пример: Игра, която обучава децата да познават часовника.
    Играта се състои в следното. Генерира се случайно време, рисува се циферблат с това време и се пита играчът колко е часът. Играчът има право на два опита, преди играта да покаже правилното време. Когато той познае правилния отговар, точките му се увеличават с 1. Има 4 нива на трудност. Ниво 1 учи на кръглите часове, ниво 2 - на 15-минутни интервали, ниво 3 - на 5 минутни интервали и ниво 4 - с точност до минута. Когато играчът постигне 5 точки на едно ниво, играта преминава на следващото ниво. В началото играта пита за името на играча и желано начално ниво. След всеки кръг тя пита играча желае ли да играе още. Играта свършва, когато той отговори отрицателно.
** Обособяване на класовете.
    Търсят се съществителните във формулировката на задачата - те са кандидатите за класове: играч (Player), часовник (Clock), време (Time), игра (Game), кръг (Round), ниво (Level).
** Определяне на интерфейс.
    Определя се какви действия трябва да бъдат извършвани с обекти от възможните класове.
Часовникът трябва да се нарисува и да му се зададе време.
Clock
 draw
 set_time
Времето трябва да може да се задава с точност до минута; да се отчитат часовете и минутите и да се сравняват показаното на часовника и отговора на играча.
Time
  Time(hours, minutes)
 get_hours
 get_minutes
  is_same_as(Time)
Играчът има име, ниво и точки. Името и нивото му се задават в началото на играта. Точките му се увеличават по време на играта и след набиране на 5 точки се увеличава и нивото му. Ще трябва да се знае и текущото ниво на играча.
Player
  Player(name, initial_level)
 increment_score
 get_level
Игра: 
получаване на информация за играча 
do
{ изиграване на кръг 
   питане за продължаване на играта 
}
while (играчът иска да продължи)
Game
  play
 read_player_information
 play_round

** Реализиране на интерфейс.
* Клас Clock:
class Clock  {
public:
   Clock();
   Clock(Point c, double r);
   void set_time(Time t);
   void draw() const;
private:
   Time current_time;
   Point center;
   double radius;
};
void Clock::set_time(Time t)
{  current_time = t;
}
    Рисуване на часовник:
- рисуване на кръг;
- рисуване на часовите деления;
- рисуване на минутните деления;
- рисуване на часовата стрелка;
- рисуване на минутната стрелка.
    Рисува часово или минутно деление:
void Clock::draw_tick(double angle, double length) const
{  double alpha = PI/2 - 6*angle*PI/180;
   Point from(center.get_x() + cos(alpha)*radius*(1-length),
              center.get_y() + sin(alpha)*radius*(1-length));
   Point to(center.get_x() + cos(alpha)*radius,
            center.get_y() + sin(alpha)*radius);
   cwin << Line(from, to);
}
    Рисува стрелка:
void Clock::draw_hand(double angle, double length) const
{  double alpha = PI/2 - 6*angle*PI/180;
   Point from = center;
   Point to(center.get_x() + cos(alpha)*radius*length,
            center.get_y() + sin(alpha)*radius*length);
   cwin << Line(from, to);
}
Рисува циферблат:
void Clock::draw() const
{  cwin << Circle(center, radius);
   int i;
   const double HOUR_TICK_LENGTH = 0.2;
   const double MINUTE_TICK_LENGTH = 0.1;
   const double HOUR_HAND_LENGTH = 0.6;
   const double MINUTE_HAND_LENGTH = 0.75;
   for (i = 0; i < 12; i++)
   {  draw_tick(i * 5, HOUR_TICK_LENGTH);
      int j;
      for (j = 1; j <= 4; j++)
         draw_tick(i * 5 + j, MINUTE_TICK_LENGTH);
   }
   draw_hand(current_time.get_minutes(), MINUTE_HAND_LENGTH);
   draw_hand((current_time.get_hours() +
      current_time.get_minutes() / 60.0) * 5, HOUR_HAND_LENGTH);
}
* За времето ще използваме готовия клас Time.
* Клас Player:
class Player  {
public:
   Player();
   Player(string player_name, int initial_level);
   void increment_score();
   int get_level() const;
   string get_name() const;
private:
   string name;
   int score;
   int level;
};
Player::Player(string player_name, int initial_level)
{  name = player_name;
   level = initial_level;
   score = 0;
}
int Player::get_level() const
{ return level;
}
string Player::get_name() const
{ return name;
}
void Player::increment_score()
{  score++;
   if (score % 5 == 0 and level < 4) level++;
}
* Клас Game:
class Game {
public:
   Game();
   void play();
   void read_player_information();
   void play_round();
private:
   Player player;
};
Game::Game()
{ rand_seed();
}
void Game::play()
{ read_player_information();
  string response;
  do
  { play_round();
    response =
    cwin.get_string("Do you want to play again? (y/n)");
  } while (response == "y");
}
void Game::read_player_information()
{  string name = cwin.get_string("What is your name?");
   int initial_level;
   do
   { initial_level =
     cwin.get_int("At what level do you want to start?(1-4)");
   } while (initial_level < 1 or initial_level > 4);
   player = Player(name, initial_level);
}
    Изиграване на кръг:
- генериране на случайно време
- показване на времето
- получаване на отговор
- ако отговорът не е правилен, отново получаване на отговор
- ако отговорът е правилен - поздравления и увеличаване на точките
    Генериране на случайно време:
Time Game::random_time()
{  int level = player.get_level();
   int minutes;
   if (level == 1) minutes = 0;
   else if (level == 2) minutes = 15 * rand_int(0, 3);
   else if (level == 3) minutes = 5 * rand_int(0, 11);
   else minutes = rand_int(0, 59);
   int hours = rand_int(1, 12);
   return Time(hours, minutes, 0);
}
    Получаване на отговор:
Time Game::get_guess()
{  int hours;
   do
   {  hours = cwin.get_int("Please enter hours: (1-12)");
   } while (hours < 1 or hours > 12);
   int minutes;
   do
   {  minutes = cwin.get_int("Please enter minutes: (0-59)");
   } while (minutes < 0 or minutes > 59);

   return Time(hours, minutes, 0);
}
    Изиграване на кръг:
void Game::play_round()
{  cwin.clear();
   Time t = random_time();
   const double CLOCK_RADIUS = 5;
   Clock clock(Point(0, 0), CLOCK_RADIUS);
   clock.set_time(t);
   clock.draw();
   Time guess = get_guess();
   if (t.seconds_from(guess) != 0) guess = get_guess();
   string text;
   if (t.seconds_from(guess) == 0)
   {  text = "Congratulations, " + player.get_name()
         + "! That is correct.";
      player.increment_score();
   }
   else
      text = "Sorry, " + player.get_name()
         + "! That is not correct.";
   cwin << Message(Point(-CLOCK_RADIUS, CLOCK_RADIUS + 1),text);
}
* Главна програма:
int main()
{  Game clock_game;
   clock_game.play();
   return 0;
}
    Цялата програма е записавна във файла clock.cpp.