8. Класове

План:
Използване на класове и обекти
Обособяване на класове
Капсулиране на данните
Член-функции
Конструктори по подразбиране и с параметри
Достъп до полетата с данни
Сравняване на член-функции с обикновени функции


** Използване на класове и обекти (преговор)

В лекциите до тук за класовете са разгледани:

- конструиране на обект:
string name;
Employee harry("Hacher, Harry", 350);
Time t(10, 10, 5);

- извеждане на информация за обект:
cout << name.length();
cout << harry.get_name();
cout << t.hours();

- модифициране на обект:
harry.set_salary(380);
t.add_seconds(20);
  

Въведени са и основните понятия конструктор, член-функция и операция точка.


** Обособяване на класове

Ще разгледаме задача, която се решава без използване на клас и след това с дефиниране и използване на подходящ клас.

Пример: Чете се информация за компютри, съдържаща:
име на модел name OmniBook XE
цена price 5660
оценка score 76
OmniBook XE
5660
76
ACMA P300
1095
75
AMAX Poewrstation
1999
78
Търси се продукта с най-голямо отношение оценка/цена.

Първо програмно решение - без използване на класове.
// bestval.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_price = next_price;
                       best_score = next_score;
         }

      }
      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;
}

Множествата от променливи за име, цена и оценка описват един продукт и можем да ги обединим в един клас.


** Интерфейс и капсулиране, членове-функции.

Интерфейсът на класа се състои от всички членове-функции, които ще обработват обектите от този клас.
За нашия пример:


** Второ програмно решение на задачата с използване на класове.

// 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;  


** Капсулиране на данните

Дефиницията на класа се състои от заглавие и две секции: публична (public), частна (private):
- В публичната секция се декларират член-функции на класа (интерфейса);
- В частната секция на класа обикновено са дефинирани данните за обектите от този клас.

Само конструкторът на класа и член-функциите на класа имат достъп до частните членове на класа (данните).

Скриването на данните (от външни функции) се нарича капсулиране (encapsulation).
Капсулирането на данните налага индиректно използване на данните от външни за класа функции - чрез публичните член-функции, което предпазва данните от повреда (невалидни данни).

Пример: Предполагаема дефиниция на класа Time. Интерфейсът на класа е този, но представянето на данните е предполагаемо (скрити данни).
class Time {
public:
Time();
Time(int h, int m, int s);
void add_seconds(long s);

long seconds_from(Time t) const;
int get_seconds() const;
int get_minutes() const;
int get_hours() const;
private:
int hours; /* conjecture */
int minutes; /* conjecture */
int seconds; /* conjecture */
};
Ако имаше пряк достъп до данните, то можеше да се получи невалидно време, например
Time liftoff(19, 30, 0);
/* liftoff is delayed by six hours */
/* won't compile, but suppose it did */
liftoff.hours = liftoff.hours + 6;
Получава се 25:30:00!

В класа Time има функция add_seconds, която добавя секунди и пресмята вярно часа, минутите и секундите.

** Член-функции

Неявен и явен параметри на член-функция.
Формални и фактически параметри на член-функция.
Пример: Член-функция за сравняване на продукти.
bool Product::is_better_than(Product b) const
{ if (b.price == 0) return false;
if (price == 0) return true;
return score / price > b.score / b.price;
}
if (next.is_better_than(best)) ...
Неявен формален параметър - текущия обект; явен формален параметър Product b.
Неявен фактически параметър - next; явен факически параметър - best.

** Конструктори по подразбиране и с параметри

* Конструкторът инициализира (задава начални стойности на) данните на класа. Няма тип и не връща стойност.
* Всеки клас може да има няколко конструктора (напр. клас Time).
* Предефиниране на функции - две функции в една програма на С++ могат да имат еднакви имена, но за да бъдат различими, те трябва да имат различен брой параметри или/и параметрите им да са от различен тип.

Пример: Клас Employee с два конструктора: по подразбиране (без параметри) и с два параметъра.

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; 
}

* Полета с данни - обекти от други класове.

Пример: Към класа Employee добавяме още данни - времето на пристигане на работа и времето на тръгване от работа.
 
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 съдържа  членове-данни arrive и leave, които са обекти от клас Time.
Начални стойности на тези обекти се задават от конструкторите на съответните класове.


** Достъп до полетата с данни

* Само членовете-функции имат достъп до скритите полета с данни (в секция private).

    string Employee::get_name() const
    { return name; }
 
    void Employee::set_salary(double new_salary)
    { salary = new_salary;
    }

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

Пример: Класът Time, написан от Кай Хорстман.

class Time {
public:
Time();
Time(int h, int m, int s);
void add_seconds(long s);

long seconds_from(Time t) const;
int get_seconds() const;
int get_minutes() const;
int get_hours() const;
private:
long time_in_secs;
};

Реализациите (дефинициите) на член-функциите също са скрити за потребителя на класа.

Пример:
Дефиниции на член-функции на класа Time.

Time::Time(int hour, int min, int sec)
{
time_in_secs = 60 * 60 * hour + 60 * min + sec;
}

int Time::get_minutes() const
{
return
(time_in_secs / 60) % 60;
}

int Time::seconds_from(Time t) const
{
return time_in_secs - t.time_in_secs;
}

** Сравняване на член-функции с обикновени функции.
 
Примери: Отпечатване на името и заплатата на служител (обект от клас Employee), реализирано с три различни функции:
 
-- член-функция с използване на достъп до скритите данни name и salary:
   void Employee::print() const
   { cout << "Name: " << name << "    "
          << "Salary: " << salary << "\n";
   }

-- член-функция с използване на член-функции (за достъп) от същия клас get_name и get_salary:
   void Employee::print() const
   { cout << "Name: " << get_name() << "    "
          << "Salary: " << get_salary() << "\n";
   }

-- външна за класа функция с параметър константен псевдоним от клас Employee:
   void print(const Employee &emp)
   { cout << "Name: " << emp.get_name() << "    "
          << "Salary: " << emp.get_salary() << "\n";
   } 

Пример: Разширена реализация на клас Employee с две реализации на функция raise_salary за увеличаване на заплатата на служител - член-функция на класа Employee е обикновена функция с параметър от тип Employee.

// employee.cpp
#include <iostream>
using namespace std;
#include <ccc_time.h>

class Employee {
public:
   Employee(string employee_name, double initial_salary,
            int arrive_hour, int leave_hour);

   void set_salary(double new_salary);
   void raise_salary(double percent);

   string get_name() const;
   double get_salary() const;
   void print() const;
private:
   string name;
   double salary;
   Time arrive;
   Time leave;

};

Employee::Employee(string employee_name, double initial_salary,
   int arrive_hour, int leave_hour)
{  name = employee_name;
   salary = initial_salary;
   arrive = Time(arrive_hour, 0, 0);
   leave = Time(leave_hour, 0, 0);

}

void Employee::set_salary(double new_salary)
{  salary = new_salary; 
}    

string Employee::get_name() const
{  return name;
}

double Employee::get_salary() const
{  return salary;
}

void Employee::raise_salary(double percent)
{  salary = salary * (1 + percent / 100);
}


void raise_salary(Employee& e, double percent)
{  double new_salary = e.get_salary() * (1 + percent / 100);
   e.set_salary(new_salary);
}


void Employee::print() const
{  cout << "Name: " << get_name() << "; "
        << "Salary: " << get_salary() << "; "
    << "Arrive: " << arrive.get_hour() << "; "
    << "Leave: " << leave.get_hour() << "\n";
}


void print(const Employee &emp)
{ cout << "Name: " << emp.get_name() << "    "
    << "Salary: " << emp.get_salary() << "\n";
}


int main()
{ Employee harry("Harry", 1000, 8, 16);
  harry.raise_salary(10);
  harry.print();
  raise_salary(harry, 10);
  cout << "New salary " << harry.get_salary() << endl;
  print(harry);
  return 0;  
}   


Таблица за явни и неявни параметри на функция.


Явен (explicit) параметър Неявен (implicit) параметър
параметър-променлива
(не се променя)
По подразбиране
Пример:

void print(Employee);
print(harry); //
параметър harry
Използва се const
Пример:
void Employee::print() const;
harry.print();
// параметър harry
параметър-псевдоним
(може да се промени)

Използва се &
Пример:
void raiseSalary(Employee& e, double p);
raiseSalary(harry, 10); // параметър harry

По подразбиране
Пример:
void Employee::raiseSalary(double p);
harry.
raiseSalary(10);// параметър harry


** Разделяне програмата на:
-- заглавен файл (product.h),
-- дефиниции на функциите на класа (product.cpp),
-- използване на обекти от класа (prodtest.cpp).

Разделна компилация (за упражненията).