1. Наследяване и полиморфизъм

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


Класове (преговор)


Производни класове

Наследяването е механизъм за разширяване на вече съществуващи класове.
Новият (производен) клас наследява съществуващия, като в него се добавят нови членове-функции и полета с данни.
Данните на базовия клас се съдържат във всеки обект от производния клас.
class Employee {
public:
   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;
};
class Manager : public Employee {
public:
   Manager(string n, double sal, string dept);
   string get_department() const;
   void print() const;
private:
   string department;
};
 
Обект от производния клас е обект и от базовия клас; обратното не е вярно.
Manager nino("Nino", 670, "Computer Science");
nino.set_salary(1200);
cout << nino.get_department();
Manager
Employee
string name;
double salary;

string department;

nino
Employee
"Nino"
670

"Computer Science"

Employee - базов клас, Manager - производен клас
- членовете-данни на базовият клас са членове-данни и на производния клас (всеки обект от производния клас съдържа данните на базовия клас);
- функциите на производният клас нямат достъп  до частните членове на базовия клас.


** Дефиниции на функции в производния клас

Конструкторът на производния клас има две задачи:
- да инициализира полетата с данни, дефинирани в производния клас;
- да инициализира обекта от базовия клас, който се  съдържа в обекта от производния клас.
 
Manager::Manager(string n, double sal, string dept)

          : Employee(n, sal)
  { department = dept;  }

Ако в базовия и в производния класове има функции с едно и също име (напр. print()), то викането на член-функция на базовия клас от функция на производния клас става с помощта на операция "принадлежност към клас" (::).
 
void  Manager::print()

  {
    Employee::print();

    cout << department;
  }

// manager.cpp
#include <iostream>
#include <string>

using namespace std;

class Employee {
public:
    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;
};

class Manager : public Employee {
public:
    Manager(string n, double sal, string dept);
    string get_department() const;
    void print() const;
private:
    string department;
};

int main()
{
    Employee harry("Harry", 500);
    harry.set_salary(550);
    harry.print();
    cout << harry.get_salary() << endl;
   
    Manager nino("Nino", 670, "Computer Science");
    nino.set_salary(1200);
    nino.print();
    cout << nino.get_salary() << " "
        << nino.get_department() << endl;
   
    return 0;
}

Employee::Employee(string emp_name, double init_salary)
{
    name = emp_name;
    salary = init_salary;
}

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::print() const
{
    cout << get_name() << " " << salary << endl;
}

Manager::Manager(string n, double sal, string dept)
: Employee(n, sal)
{
    department = dept;
}

string Manager::get_department() const
{
    return department;
}

void  Manager::print() const
{
    Employee::print();
    cout << department << endl;
}

Harry 550
550
Nino 1200
Computer Science
1200 Computer Science


Пример.  Да се изобразят няколко часовника, които показват часа в различни градове по света.

Базовият клас Clock показва локалното време по два начина - military и am/pm.

В САЩ има два формата за записване на време с разлика в часовете (два вида часовници):
- am/pm
- денонощието започва с 12 am (полунощ) следват 1-11 am, 12 pm (обяд) и 1-11 pm.
- military - часовете са от 00 до 23.
 // clock1.cpp 

#include <iostream> #include <iomanip> #include <string> using namespace std; #include "ccc_time.h" class Clock { public: /** Constructs a clock that can tell the local time. @param use_military true if the clock uses military format (21:05) and
flase if the clock uses "am/pm" format (9:05 pm)
*/ Clock(bool use_military); /** Gets the location of this clock. @return the location */ string get_location() const; /** Gets the hours of this clock. @return the hours, in military or am/pm format */ int get_hours() const; /** Gets the minutes of this clock. @return the minutes */ int get_minutes() const; /** Checks whether this clock uses miltary format. @return true if miltary format */ bool is_military() const; private: bool military; }; Clock::Clock(bool use_military) { military = use_military; } string Clock::get_location() const { return "Local"; } int Clock::get_hours() const { Time now; int hours = now.get_hours(); if (military) return hours; if (hours == 0) return 12; else if (hours > 12) return hours - 12; else return hours; } int Clock::get_minutes() const { Time now; return now.get_minutes(); } bool Clock::is_military() const { return military; } int main() { Clock clock1(true); Clock clock2(false); bool more = true; while (more) { cout << "Military time: " << clock1.get_hours() << ":" << setw(2) << setfill('0')
<< clock1.get_minutes()
<< setfill(' ') << "\n"; cout << "am/pm time: " << clock2.get_hours() << ":" << setw(2) << setfill('0')
<< clock2.get_minutes()
<< setfill(' ') << "\n"; cout << "Try again? (y/n) "; string input; getline(cin, input); if (input != "y") more = false; } return 0;
}
nkirov@cpp % c++ clocks1.cpp ccc_time.cpp
nkirov@cpp % ./a.out
Military time is 9:33
am/pm time is 9:33
Try again? (y/n) y
Military time is 9:33
am/pm time is 9:33
Try again? (y/n) n
nkirov@cpp %


Производният клас TravelClock представя часовник, който показва часа в друг град по света. 

 
В производния клас TravelClock са добавени две полета с данни (променливи) - location и time_difference.

Времето на друго място, се получава, като към местното време се добави разликата, получена от различните часови зони.
Например
TravelClock clock("London", -2);
cout << "The time in " << clock.get_location() << " is "
<< clock.get_hours() << ":" << clock.get_minutes();
Обект от TravelClock (производния клас) има 3 разлики с обект от Clock:
- двете полета с данни:  location и time_difference;
- функцията get_hours добавя разликата в часовите зони;
- функцията get_location връща името на мястото (а не низа "Local").
class TravelClock : public Clock {
public:
TravelClock(bool mil, string loc, double off);
int get_hours() const;
string get_location() const;
private:
string location;
int time_difference;
};


** Конструктор на базовия и производния клас

Конструкторът на производния клас има две задачи:
- да инициализира обекта от базовия клас, който се  съдържа в производния клас;
- да инициализира полетата с данни (дефинирани в производния клас).
TravelClock::TravelClock(bool mil, string loc, int diff)
: Clock(mil)
{ location = loc;
time_difference = diff;
while (time_difference < 0)
time_difference = time_difference + 24;
};
Ако не се извика (явно) конструктора на базовия клас, обектът от производния клас се създава, като автоматично (неявно) се вика конструктора по подразбиране на базовия клас.
Ако такъв конструктор няма (синтактична грешка), то задължително трябва да се вика явно конструктора на базовия клас.

Забележка: Когато дефинираме клас и не дефинираме конструктор, то автоматично се дефинира конструктор по подразбиране с празен блок. Ако обаче дефинираме поне един конструктор, то този конструктор с празен блок не се дефинира автоматично.


** Член функции в базовия и производния клас

Нека B::f е функция в базовия клас. Производният D клас може да:

- разшири функцията B::f като дефинира D::f  в производния клас, използвайки дефиницията в базовия клас.
Например TravelClock::get_hours е разширение на Clock::get_hours;

- замести функцията B::f с нова D::f, дефинирана в производния клас и независима от нея.
Например TravelClock::get_location замества Clock::get_location;

- наследи функцията B::f от базовия клас, без да я дефинира отново.
Например TravelClock наследява Clock::get_minutes и Clock::is_military.

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

int TravelClock::get_hours() const
{ . . .
   if (is_military())  /* clock uses military time */
          return (h + time_difference) % 24;
  . . .
}

Когато в базовия и в производния клас има функции с едно и също име (напр. get_hours()), то за да извикаме функцията от базовия клас в блок на функция от производния клас, трябва да използваме операция "принадлежност към клас" ::, за да избегнем рекурсията.
int TravelClock::get_hours() const
{ . . .
int h = Clock::get_hours(); /* calls base-class function */
. . .
}


** Данните на базовия и производния клас

Данните на базовия клас (напр. military) не са достъпни от член-функциите на производния клас.
 // clock2.cpp

#include
<iostream>
#include <iomanip>
#include <string>

using namespace std;

#include "ccc_time.h"

class Clock {
public:
Clock(bool use_military);
string get_location() const;
int get_hours() const;
int get_minutes() const;
bool is_military() const;
private:
bool military;
};

Clock::Clock(bool use_military)
{ military = use_military; }

string Clock::get_location() const
{ return "Local"; }

int Clock::get_hours() const
{ Time now;
int hours = now.get_hours();
if (military) return hours;
if (hours == 0) return 12;
else if (hours > 12) return hours - 12;
else return hours;
}

int Clock::get_minutes() const
{ Time now;
return now.get_minutes();
}

bool Clock::is_military() const
{ return military; }

class TravelClock : public Clock {
public:
/**
Constructs a travel clock that can tell the time
at a specified location
@param mil true if the clock uses military format
@param loc the location
@param diff the time difference from the local time
*/
TravelClock(bool mil, string loc, int diff);
string get_location() const;
int get_hours() const;
private:
string location;
int time_difference;
};

TravelClock::TravelClock(bool mil, string loc, int diff)
: Clock(mil)
{ location = loc;
time_difference = diff;
while (time_difference < 0)
time_difference = time_difference + 24;
}

string TravelClock::get_location() const
{ return location; }

int TravelClock::get_hours() const
{ int h = Clock::get_hours();
if (is_military())
return (h + time_difference) % 24;
else
{ h = (h + time_difference) % 12;
if (h == 0) return 12;
else return h;
}
}

int main()
{
Clock clock1(true);
cout << clock1.get_location() << " time: "
<< clock1.get_hours() << ":"
<< setw(2) << setfill('0')
<< clock1.get_minutes()
<< setfill(' ') << "\n";


TravelClock clock2(true, "Rome", -1);
cout << clock2.get_location() << " time: "
<< clock2.get_hours() << ":"
<< setw(2) << setfill('0')
<< clock2.get_minutes()
<< setfill(' ') << "\n";


TravelClock clock3(false, "Tokyo", 5);
cout << clock3.get_location() << " time: "
<< clock3.get_hours() << ":"
<< setw(2) << setfill('0')
<< clock3.get_minutes()
<< setfill(' ') << "\n";

return 0;
}
Local time is 9:36
Rome time is 8:36
Tokyo time is 2:36


Полиморфизъм

Дефинираме вектор от часовници (обекти от базовия клас) и използваме цикъл за показване на часа.
vector<Clock> clocks(3);
/* populate clocks */
clocks[0] = Clock(true);
clocks[1] = TravelClock(true, "Rome", -1);
clocks[2] = TravelClock(false, "Tokyo", 5);

for (int i = 0; i < clocks.size(); i++)
cout << clocks[i].get_location() << " time: "
<< clocks[i].get_hours() << ":"
<< setw(2) << setfill('0')
<< clocks[i].get_minutes()
<< setfill(' ') << "\n";
[ clocks2a.cpp

Local time is 21:15
Local time is 21:15
Local time is 9:15

Защо?

- Тъй като обект от производния клас е обект и от базовия клас, то можем да използваме операция присвояване с ляв аргумент обект от (базовия клас) Clock и  десен аргумент - обект от (производния клас) TravelClock (обратното не е вярно).
- Ефектът обаче е отрязване на даните (при операция присвояване), дефинирани в производния клас, т.е. на location и time_difference.

Можем да използваме указатели към базовия клас за елементи на вектора, тъй като указател от базов клас може съдържа адрес на обект от производен клас (обратното не е вярно).

Бележка: Указателите от различни класове имат една и съща дължина (няма отрязване).
vector<Clock*> clocks(3);
/* populate clocks */
clocks[0] = new Clock(true);
clocks[1] = new TravelClock(true, "Rome", -1);
clocks[2] = new TravelClock(false, "Tokyo", 5);

for (int i = 0; i < clocks.size(); i++)
cout << clocks[i]->get_location() << " time: "
<< clocks[i]->get_hours() << ":"
<< setw(2) << setfill('0')
<< clocks[i]->get_minutes()
<< setfill(' ') << "\n";
Векторът clocks съдържа колекция от различни часовници - указатели към обекти от различни класове. Такава колекция се нарича полиморфна.
[ clocks2b.cpp

Резултатът от новия вариант е същия:
Local time is 21:15
Local time is 21:15
Local time is 9:15

Защо?

Указателите са дефинирани от базовия клас (Clock) и съответно с тях се вика функция от същия клас.

Това свързване на обект (неявен параметър, напр. clocks[i]) и член-функция (напр. get_location()) като аргументи на операция стрелка, се нарича статично свързване (clocks[i]->get_location()) .

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

Например:

-- clocks[0]->get_location()
- clocks[0] е указател от базовия клас Clock, съдържа адрес на обект от базовия клас Clock(true) и get_location() е член-функция от базовия клас Clock;

-- clocks[1]->get_location() - clocks[1] е указател от базовия клас Clock, съдържа адрес на обект от производния клас TravelClock(true, "Rome", -1) и get_location() е член-функция от производния клас TravelClock.

За такова динамично свързване е необходимо функциите да се обяват като виртуални.
class Clock  {
public:
Clock(bool use_military);
virtual string get_location() const;
virtual int get_hours() const;
int get_minutes() const;
bool is_military() const;
private:
. . .
};
Програмата с динамично свързване:
 // clocks3.cpp
#include
<iostream> #include <iomanip> #include <string> #include <vector> using namespace std; #include "ccc_time.h" class Clock { public: Clock(bool use_military); virtual string get_location() const; virtual int get_hours() const; int get_minutes() const; bool is_military() const; private: bool military; }; Clock::Clock(bool use_military) { military = use_military; } string Clock::get_location() const { return "Local"; } int Clock::get_hours() const { Time now; int hours = now.get_hours(); if (military) return hours; if (hours == 0) return 12; else if (hours > 12) return hours - 12; else return hours; } int Clock::get_minutes() const { Time now; return now.get_minutes(); } bool Clock::is_military() const { return military; } class TravelClock : public Clock { public: TravelClock(bool mil, string loc, int diff); string get_location() const; int get_hours() const; private: string location; int time_difference; }; TravelClock::TravelClock(bool mil, string loc, int diff) : Clock(mil) { location = loc; time_difference = diff; while (time_difference < 0)
time_difference = time_difference + 24; } string TravelClock::get_location() const { return location; } int TravelClock::get_hours() const { int h = Clock::get_hours(); if (is_military()) return (h + time_difference) % 24; else { h = (h + time_difference) % 12; if (h == 0) return 12; else return h; } } int main() { vector<Clock*> clocks(3); clocks[0] = new Clock(true); clocks[1] = new TravelClock(true, "Rome", -1); clocks[2] = new TravelClock(false, "Tokyo", 5); for (int i = 0; i < clocks.size(); i++) { cout << clocks[i]->get_location() << " time: "
<< clocks[i]->get_hours() << ":" << setw(2) << setfill('0')
<< clocks[i]->get_minutes()
<< setfill(' ') << "\n"; } return 0; }
Local time is 18:07
Rome time is 17:07
Tokyo time is 11:07