Использование конструкторов с параметрами при наследовании

Рассмотрим два класса: базовый и производный с конструкторами.

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

Наши рекомендации