Другим способом реализации полиморфизма в языке C++ служит перегрузка операторов. Например, в C++ можно использовать операторы << и >> для выполнения операций ввода/вывода. Это оказывается возможным благодаря тому, что в заголовочном файле iostream.h эти операторы перегружены. Когда оператор перегружен, он приобретает дополнительное значение в зависимости от своего использования. Тем не менее, он сохраняет возможность своего использования в определенном ранее смысле.
В общем случае можно перегружать операторы C++, определяя, что они означают применительно к специфическому классу. В качестве примера снова рассмотрим класс queue, представленный ранее в этой главе. Можно перегрузить оператор + по отношению к объектам типа queue таким образом, что он добавляет содержимое одного объекта queue к другому. Тем не менее, оператор + сохраняет свое прежнее значение оператора сложения по отношению к другим типам данных.
Поскольку перегрузка операторов фактически представляет собой более сложную задачу, чем перегрузка функций, мы отложим ее рассмотрение до соответствующей главы.
Наследование является одной из главных особенностей объектно-ориентированного программирования. В C++ наследование поддерживается за счет того, что одному классу разрешается при своем объявлении включать в себя другой класс. Наследование позволяет построить иерархию классов от более общего к более частным. Этот процесс включает в себя определение базового класса, определяющего общие качества всех объектов, которые будут выведены затем из базового класса. Базовый класс представляет собой наиболее общее описание. Выведенные из базового класса классы обычно называют производными классами. Производные классы включают в себя все черты базового класса и, кроме того, могут добавлять новые качества, характерные именно для данного производного класса. Чтобы продемонстрировать, как работает этот процесс, в следующем примере созданы классы, классифицирующие различные типы средств передвижения.
В качестве начального рассмотрим класс, названный road_vehicle (дорожное средство передвижения), который служит очень широким определением средств передвижения по дорогам. Он хранит информацию о числе колес движущегося средства и о числе пассажиров, которые он может вмещать:
class road_vehicle
{
int wheels;
int passengers;
public:
void set_wheels(int num);
int get_wheels();
void set_pass(int num);
int get_pass();
};
Теперь можно использовать это определение дорожного средства передвижения для того, чтобы определить другие, более конкретные типы. Например, в следующем фрагменте кода определен класс truck (грузовик), используя класс road_vehicle:
class truck: public road_vehicle
{
int cargo;
public:
void set_cargo(int size);
int get_cargo();
void show();
};
Обратим внимание, каким образом наследуется класс road_vehicle. Общая форма записи наследования имеет следующий вид
class имя_нового_класса: доступ наследуемый_класс
{
//тело нового класса
}
Здесь использование доступа не обязательно, но если оно используется, то должно принимать значение public, protected или private. Более подробно данная опция обсуждается в дальнейшем. Доступ public означает, что все члены класса-предшественника сохраняют свои спецификаторы доступа во всех производных классах. Поэтому в данном примере члены класса truck имеют доступ к функциям-членам класса road_vehicle так, как если бы эти функции-члены были объявлены внутри класса truck. Однако функции-члены класса truck не имеют доступа к частным членам класса road_vehicle, а именно числу колес и пассажиров.
Следующая программа иллюстрирует наследование с помощью создания двух подклассов класса road_vehicle: truck и automobile:
#include <iostream.h>
class road_vehicle
{
int wheels;
int passengers;
public:
void set_wheels(int num);
int get_wheels();
void set_pass(int num);
int get_pass ();
};
class truck: public road_vehicle
{
int cargo;
public:
void set_cargo(int size);
int get_cargo();
void show();
};
enum type{car, van, wagon};
class automobile: public road_vehicle
{
enum type car_type;
public:
void set_type(enum type t);
enum type get_type();
void show();
};
void road vehicle::set_wheels(int num)
{
wheels = num;
}
int road_vehicle::get_wheels()
{
return wheels;
}
void road_vehicle::set_pass(int num)
{
passengers = num;
}
int road_vehicle::get_pass()
{
return passengers;
}
void truck::set_cargo(int num)
{
cargo = num;
}
int truck::get_cargo()
{
return cargo;
}
void truck::show()
{
cout << "Wheels: " << get_wheels() << "\n";
cout << "Passengers: " << get_pass() << "\n";
cout << "Cargo capacity: " << cargo << "\n";
}
void automobile::set_type(enum type t)
{
car_type = t;
}
enum type automobile::get_type()
{
return car_type;
}
void automobile::show()
{
cout << "Wheels: " << get_wheels() << “\n”;
cout << "Passengers: " << get_pass() << "\n";
cout << "Type: ";
switch(get_type())
{
case van: cout << "Van\n";
break;
case car: cout << "Car\n";
break;
case wagon: cout << "Wagon\n";
break;
}
}
int main()
{
truck t1, t2;
automobile c;
t1.set_wheels(18);
t1.set_pass(2);
t1.set_cargo(3200);
t2.set_wheels(6);
t2.set_pass(3);
t2.set_cargo(1200);
t1.show();
с.set_wheels(4);
с.set_pass(6);
с.set_type(van);
с.show();
return 0;
}
Наибольшим достоинством наследования служит возможность создания классификации типов данных, которая может быть затем включена в конкретные классы. Таким образом, каждый объект выражает в точности те свои черты, которые определяют его место в классификации.
Обратим внимание, что классы truck и automobile включают функции-члены с одинаковым именем show(), которые служат для вывода информации об объекте. Это еще один аспект полиморфизма. Поскольку каждая функция show() относится к своему собственному классу, компилятор может легко установить, какую из них вызывать в конкретной ситуации.