8. Класове

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


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

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

- конструиране на обект:
string name;  
// конструира (създава, дефинира) низа name с дължина 0
Employee harry("Hacher, Harry", 350);
// дефинира обекта harry
Time t(10, 10, 5);
// дефинира момента t като 10:10:05       

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

- модифициране (променяне на данните) на обект:
harry.set_salary(380); // задава нова заплата 380 на обекта harry
t.add_seconds(20);    
// добавя 20 секунди към времето на обекта t
  

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


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

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

Пример: Чете се информация за компютри, съдържаща:
име на модел 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;
}

nkirov@cpp % c++ bestval.cpp
nkirov@cpp % ./a.out
Please enter the model name: OmniBook XE
5660
76Please enter the price: Please enter the score:
More data? (y/n) y
Please enter the model name: ACMA P300
1095
75Please enter the price: Please enter the score:
More data? (y/n) y
Please enter the model name: AMAX Poewrstation
1999
78Please enter the price: Please enter the score:
More data? (y/n) n
The best value is ACMA P300 Price: 1095 Score: 75

Множествата от променливи за име (name), цена (price) и оценка (score) описват един продукт и можем да ги обединим в един клас (Product).


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

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


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

// 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;                      /* скрита данна name */
   double price;                     /* скрита данна price */
   int score;                        /* скрита данна score */
};
 
/* ИЗПОЛЗВАНЕ НА КЛАСА */
int main()
{  Product best;       /* дефиниране на обектa best от класа */
   bool more = true;
 
   while (more)
   {  Product next;  
/* дефиниране на обектa next от класа */
      next.read();    /* извикване на член-функция 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();        /* извикване на член-функция 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";
}
/* КРАЙ на файла, съдържащ текста на програмата */ 

nkirov@cpp % c++ product2.cpp
nkirov@cpp % ./a.out
Please enter the model name: OmniBook XE
5660
76Please enter the price: Please enter the score:
More data? (y/n) y
Please enter the model name: ACMA P300
1095
75Please enter the price: Please enter the score:
More data? (y/n) y
Please enter the model name: AMAX Poewrstation
1999
78Please enter the price: Please enter the score:
More data? (y/n) n
The best value is ACMA P300 Price: 1095 Score: 75

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

Видове член-функции:

- set-функции (мутатори) - променят (стойността на) неявния си параметър (данните на обекта):
декларация:   void read();
извикване:   next.read();

- get-функции (функции за достъп) - не променят (стойността на) неявния си параметър (данните на обекта), обявяват се като константни функции.
декларация:   void print() const;
извикване:    best.print();  


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

Дефиницията на класа се състои от заглавие и две секции: публична (public), частна (private):
- В публичната секция (обикновено) се декларират член-функции на класа (интерфейса);
- В частната секция на класа (обикновено) са дефинирани данните за обектите от този клас.
Пример: Предполагаема дефиниция на класа Time. Интерфейсът на класа е този (3. Низове и обекти), но представянето на данните може да е друго (скрити данни от потребителя на класа).
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 (3. Низове и обекти) с два конструктора: по подразбиране (без параметри) и с два параметъра.
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; }. 
// данна name
 
    void Employee::set_salary(double new_salary)
    { salary = new_salary; 
// данна salary
    }

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

Пример: Класът Time, написан от Кай Хорстман (3. Низове и обекти).

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_hours() << "; "
    << "Leave: " << leave.get_hours() << "\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;  
}   
nkirov@cpp % c++ employee.cpp ccc_time.cpp
nkirov@cpp % ./a.out
Name: Harry; Salary: 1100; Arrive: 8; Leave: 16
New salary 1210
Name: Harry    Salary: 1210
nkirov@cpp %


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


Явен (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).

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