Использование конструкторов с параметрами при наследовании
Рассмотрим два класса: базовый и производный с конструкторами.
class Base // базовый класс
{ private: int count; // закрытый элемент "счетчик"
public: // открытые методы базового класса
Base() {count=0;} // конструктор базового класса
void Setcount (int n) // метод установки счетчика
{count=n;}
int Getcount () // метод возврата значения счетчика
{ return count;}
};
class Derived : public Base // производный класс с доступом к Base
{ public: // открытые методы Derived
Derived() : Base() {} // конструктор производного класса
void Changecount (int n) // метод изменения счетчика
{ Setcount (Getcount ()+n);}
};
Покажем взаимосвязи классов Base и Derived. Класс Derived есть производный от класса Base с открытым доступом, сохраняющим статус доступа к элементам класса Base.
Производный класс наследует count из базового класса. Но так как count закрыт в Base, в Derived нельзя получить к нему непосредственный доступ, например,
void Changecount (int n) { count += n;} // ???
Этот оператор вызовет сообщение об ошибке при компиляции, поэтому необходимо использовать наследуемые методы: Setcount, Getcount.
В отличие от других элементов базового класса конструкторы и деструкторы никогда не наследуются. Обычно в производном классе имеется конструктор, если он есть и в базовом классе. В таком случае конструктор производного класса должен вызывать конструктор базового класса. В строке
Derived(): Base() { } объявляется конструктор производного класса и вызывается конструктор базового класса. Нельзя вызвать конструкторы базового класса в операторах. В нашем примере тело конструктора пустое, но там могут быть и операторы, например:
Derived (): Base() { cout<< "Объект инициализирован" << endl; }
Конструкторы не обязаны быть встраиваемыми. Например, производный класс объявлен так:
class Derived : public Base
{ public:
Derived(); // прототип
. . .
};
Реализация конструктора может иметь вид:
Derived::Derived() // заголовок конструктора производного класса
: Base() // вызов конструктора базового класса
{ операторы конструктора} // тело конструктора
Конструкторы производных классов всегда сначала вызывают конструктор базового класса, чтобы убедиться, что наследуемые элементы данных правильно созданы и инициализированы. В данном случае конструктор базового класса устанавливает закрытый элемент count=0. Если базовый класс, в свою очередь, является производным, то процесс вызова конструкторов продолжается по всей иерархии. Если в некотором классе Х конструктор не определен, то С++ создает конструктор по умолчанию вида: Х::Х(), то есть конструктор без аргументов. Если конструктор производного класса в явном виде не вызывает один из конструкторов своих базовых классов, то инициализируется конструктор по умолчанию (без аргументов).
Производный класс (Derived) может не иметь конструктор, если конструкторы базовых классов (Base) не имеют аргументов. Если же конструктор базового класса имеет аргументы, то каждый производный класс должен иметь конструктор с аргументами для того, чтобы передать их в базовые классы по иерархии. Чтобы передать аргументы в базовый класс, нужно определить их после объявления конструктора производного класса по форме:
Derived::Derived (список аргументов): // заголовок конструктора Derived
Base1 (список аргументов), ... , BaseN (список аргументов)
{ тело конструктора Derived ;},
где двоеточие (:) отделяет заголовок конструктора производного класса от вызовов конструкторов базовых классов с их списками аргументов. Список аргументов базового класса может состоять из констант, глобальных параметров и/или параметров для конструктора производного класса. Объект инициализируется во время выполнения программы, поэтому можно использовать переменные.
Рассмотрим следующую задачу. Основным элементом графического изображения является отдельная точка на экране монитора (пиксел). Информацию, определяющую точку, можно разделить на два типа: 1-й – описывает положение точки на экране, то есть ее координаты; 2-й – задает ее состояние (цвет, видимость). Первое более важно, так как, не зная координат, невозможно изобразить точку.
Таким образом, нужно создать более общий базовый класс с именем, например, Location, который должен содержать данные о координатах точки (х, у), а производный класс, например, Point наследует все то, что имеет класс Location и, кроме того, добавляет специфическую информацию о точке.
Пример 28.
Рассмотрим программу простого наследования и использования конструкторов с параметрами в задаче установки пиксела с заданным цветом в точке экрана с координатами (х, у) в графическом режиме. Создаются базовый класс Location для задания координат точки экрана и производный класс для изображения точки на экране по схеме Location <- Point.
#include<iostream.h>
#include<conio.h>
#include<graphics.h> // доступ к библиотеке графики
const char* path="c:\\borlandc\\bgi"; // путь к функциям библиотеки bgi
class Location // базовый класс
{ protected: int x, y; // защищенные элементы для доступа из Point
public: // открытые методы класса Location:
Location (int Initx, int Inity); // прототип конструктора
int getx() // метод возврата элемента х
{ cout<<"x="<<x<<endl; return x; }
int gety() // метод возврата элемента у
{ cout<<"y="<<y<<endl; return y; }
void show_mess() // метод вывода сообщения
{ cout<<"Нажмите любую клавишу"; }
};
// Класс производный от Location, сохраняющий режимы доступа (public)
class Point: public Location
{ int color; // закрытый элемент класса Point
public: // открытые методы класса Point
Point (int Initx, int Inity, int Initc); // прототип конструктора Point
void putpixel() // метод вывода пиксела на экран
{ ::putpixel(x,y,color); } // операция :: устраняет неоднозначность вызова
// метода и библиотечной функции с таким же именем
};
// Описание конструктора базового класса:
Location::Location (int Initx, int Inity) // заголовок конструктора
{ x=Initx; y=Inity; // инициализация элементов класса
cout<<"Работает конструктор Location\n";
}
// :Описание конструктора производного класса
Point :: Point (int Initx, int Inity, int Initc): // заголовок конструктора Point
Location(Initx,Inity) // вызов конструктора базового класса
{ color=Initc; cout<<"Работает конструктор Point\n"; // тело конструктора
}
void main() // главная функция
{ int gr=DETECT, gm; // данные графического режима
initgraph(&gr, &gm, path); // инициализация графического режима
Point mypoint(300,200,4); // создание и инициализация объекта (точка)
mypoint.putpixel(); // изображение точки на экрана
mypoint.getx(); // получение координаты х
mypoint.gety(); // получение координаты у
mypoint.show_mess(); // вывод сообщения
getch(); // задержка экрана результатов
closegraph(); // закрытие графического режима
}
Результаты программы:
Работает конструктор Location
Работает конструктор Point
x=300
y=200
Нажмите любую клавишу
На экране по заданным координатам (300, 200) должна быть цветная точка.
Пример 29.
Рассмотрим еще один модельный пример иерархии простого наследования классов: X <- Y <- Z. Класс Х задает переменную х, класс Y — переменную у, класс Z — переменную z=x*y. Каждый класс содержит функцию show(), которая различается по полным именам X::show() и т.д. На этом примере можно экспериментировать, изменяя режим доступа к переменным х, у, модификатор доступа при наследовании классов.
В программе показана передача режима доступа к элементам классов при простом наследовании с использованием конструкторов с параметрами и функций с одинаковыми именами в базовом и производных классах.
#include<iostream.h>
#include<conio.h>
class X // базовый класс X для классов Y, Z
{ protected: int x; // защищенный элемент х, доступный в Y, Z
public: // прототипы открытых методов класса X:
X(int i); // конструктор с аргументом класса X
~X(); // деструктор класса X
int getx() { return x;} // встроенная функция возврата x
void putx (int i) {x=i;} // встроенная функция задания x
void show(); // функция вывода на экран для класса Х
};
// Класс Y производный от Х с сохранением режимов доступа к классу X:
class Y: public X
{ protected: int y; // защищенная переменная, доступная в Z
public: // прототипы открытых методов класса Y:
Y(int i, int j); // конструктор с аргументами класса Y
~Y(); // деструктор класса Y
int gety () { return y;} // встроенная функция возврата y
void puty (int i) { y=i;} // встроенная функция задания y
void show(); // функция вывода на экран для класса Y
};
// Класс Z производный от Y и Х имеет доступ к переменным Х::x и Y::у:
class Z: public Y
{ protected: int z; // защищенная переменная z
public: // прототипы методов класса Z:
Z(int i, int j); // конструктор с аргументами класса Z
~Z(); // деструктор класса Z
void makez(); // функция вычисления z
void show(); // функция вывода на экран для класса Z
};
// Описание методов:
X::X(int i) // конструктор с аргументом класса X
{ x=i; cout<<"Конструктор X\n";}
X::~X() // деструктор класса X
{ cout<<"Деструктор X\n";}
void X::show() // функция вывода на экран для класса Х
{ cout<<"x="<<x<<endl;}
Y::Y(int i, int j): X(i) // конструктор Y передает аргумент i классу X
{ y=j; cout<<"Конструктор Y\n";}
Y::~Y() // деструктор класса Y
{ cout<<"Деструктор Y\n";}
void Y::show() // функция вывода на экран класса для Y
{ cout<<"x="<<x<<" y="<<y<<endl;}
Z::Z(int i, int j): Y(i,j) // конструктор Z передает аргументы i, j классу Y
{ cout<<"Конструктор Z\n";}
Z::~Z() // деструктор класса Z
{ cout<<"Деструктор Z\n";}
void Z::show() // функция вывода на экран класса для Z
{ cout"x="<<x<<" y="<<y<<" z="<<z<<endl;}
void Z::makez() // функция вычисления переменной z
{ z=x * y;} // с использованием переменных из X,Y
void main() // главная функция
{ clrscr(); // чистка экрана
Z zobj(3, 5);; // создан и инициирован объект класса Z
zobj.makez(); // вычисление z=x*y
zobj.show(); // вызов функции вывода для класса Z
zobj.Y::show(); // вызов функции вывода для класса Y !
zobj.X::show(); // вызов функции вывода для класса X !
zobj.putx(7); // изменение переменной х в классе Х
zobj.puty(9); // изменение переменной у в классе Y
zobj.makez(); // вычисление z=x*y
zobj.show(); // вызов функции вывода для класса Z
zobj.Y::show(); // вызов функции вывода для класса Y !
zobj.X::show(); // вызов функции вывода для класса X !
getch(); // задержка экрана результатов
}
Результаты программы:
Конструктор X
Конструктор Y
Конструктор Z
x=3 y=5 z=15
x=3 y=5
x=3
x=7 y=9 z=63
x=7 y=9
x=7
Деструктор Z
Деструктор Y
Деструктор X
Конструкторы и деструкторы классов выводят сообщения, демонстрируя их вызовы при наследовании классов. Следует обратить внимание на передачу параметров конструкторами классов: от производного своему базовому по цепочке Z <- Y<- X.
Попытка просто изменить режим доступа при наследовании с public на private без каких-либо изменений в программе может вызвать ошибки компиляции. Для корректного изменения режимов доступа необходимо использовать функции getx(), gety(), которые в предыдущем примере не использовались. Можно попробовать изменить режим доступа к переменным x, y на private.
Множественное наследование
В рассмотренном выше примере классы X и Y по сути равноправны, но при использовании простого наследования класс Y отличается от класса X числом параметров конструктора. Избавится от неравноправия классов можно, используя множественное наследование. Суть его в том, что производный класс может быть наследником нескольких базовых классов, например, для классов X, Y, Z по схеме наследования (X, Y) <- Z, которая в программе может принимать вид:
class Z : public X, public Y
{ тело класса Z };
Пример 30.
Изменим программу примера 29 в соответствии со схемой множественного наследования (X, Y) <- Z.
#include<iostream.h>
#include<conio.h>
class X // базовый класс X для класса Z
{ protected: int x; // защищенный элемент х, доступный в Z
public: // прототипы открытых методов класса X:
X(int i); // конструктор с параметром класса X
~X(); // деструктор класса X
int getx () { return x; } // встроенная функция возврата x
void putx (int i) { x=i; } // встроенная функция задания x
void show(); // функция вывода на экран для класса Х
};
class Y // базовый класс Y для класса Z
{ protected: int y; // защищенная переменная, доступная в Z
public: // прототипы открытых методов класса Y:
Y(int i); // конструктор с параметром класса Y
~Y(); // деструктор класса Y
int gety () { return y; } // встроенная функция возврата y
void puty (int i) { y=i; } // встроенная функция задания y
void show(); // функция вывода на экран для класса Y
};
// Класс Z производный от Х и Y имеет доступ к переменным Х::x и Y::у:
class Z : public Х, public Y
{ protected: int z; // защищенная переменная z
public: // прототипы открытых методов класса Z:
Z(int i, int j); // конструктор с параметрами класса Z
~Z(); // деструктор класса Z
void makez (); // функция вычисления z
void show(); // функция вывода на экран для класса Z
};
// Описание методов:
X::X(int i) // конструктор с параметром класса X
{ x = i; cout<<"Конструктор X\n"; } // инициализация элемента х
X::~X() // деструктор класса X
{ cout<<"Деструктор X\n"; }
void X::show() // функция вывода на экран класса Х
{ cout<<" x = "<< x <<endl; }
Y::Y(int i): // конструктор с параметром класса Y
{ y = i; cout<<"Конструктор Y\n"; } // инициализация элемента у
Y::~Y() // деструктор класса Y
{ cout<<"Деструктор Y\n"; }
void Y::show() // функция вывода на экран класса Y
{ cout<<" y = "<< y <<endl; }
Z::Z(int i, int j):X(i), Y(j) // конструктор Z с параметрами i, j для классов X, Y
{ cout<<"Конструктор Z\n"; }
Z::~Z() // деструктор для класса Z
{ cout<<"Деструктор Z\n"; }
void Z::show() // функция вывода на экран класса Z
{ cout<<" z = "<< z <<endl; }
void Z::makez() // функция вычисления переменной z
{ z=x * y; } // с использованием переменных из X,Y
void main() // главная функция
{ clrscr(); // чистка экрана
Z zobj(3, 5);; // создан и инициирован объект класса Z
zobj.makez(); // вычисление z=x*y
zobj.show(); // вызов функции вывода для класса Z
zobj. X::show(); // вызов функции вывода для класса X !
zobj. Y::show(); // вызов функции вывода для класса Y !
zobj.putx(7); // изменение переменной х в классе Х
zobj.puty(9); // изменение переменной у в классе Y
zobj.makez(); // вычисление z=x*y
zobj.show(); // вызов функции вывода для класса Z
zobj. X::show(); // вызов функции вывода для класса X !
zobj. Y::show(); // вызов функции вывода для класса Y !
getch(); // задержка экрана результатов
}
Результаты программы:
Конструктор X
Конструктор Y
Конструктор Z
z=15
x=3
y=5
z=63
x=7
y=9
Деструктор Z
Деструктор Y
Деструктор X