Передача аргументов в базовые конструкторы

Предположим, нужно перегрузить конструкторы, заданные по умолчанию в классах Mammal и Dog, таким образом, чтобы первый из них сразу присваивал новому объекту определенный возраст, а второй — породу. Как передать в конструктор класса Mammal значения возраста и веса животного? Что произойдет, если вес не будет установлен конструктором класса Mammal, зато его установит конструктор класса Dog?

Чтобы выполнить инициализацию базового класса, необходимо записать имя класса, после чего указать параметры, ожидаемые базовым классом, как показано в листинге 11.4.

Листинг 11.4. Перегрузка конструкторов в производных классах

1: //Листинг 11.4. Перегрузка конструкторов в производных классах

2:

3: #include <iostream.h>

4: enum BREED { GOLDEN, CAIRN, DANDIE, SHETLAND, D0BERMAN, LAB };

5:

6: class Mammal

7: {

8: public:

9: // Конструкторы

10: Mammal();

11: Mammal(int age);

12: ~Mammal();

13:

14: // Методы доступа

15: int GetAge() const { return itsAge; }

16: void SetAge(int age) { itsAge = age; }

17: int GetWeight() const { return itsWeight; }

18: void SetWeight(int weight) { itsWeight = weight; }

19:

20: //Другие методы

21: void Speak() const { cout << "Mammal sound!\n"; }

22: void Sleep() const { cout << "shhh. I'm sleeping.\n"; }

23:

24:

25: protected:

26: int itsAge;

27: int itsWeight;

28: };

29:

30: class Dog : public Mammal

31: {

32: public:

33:

34: // Конструкторы

35: Dog();

36: Dog(int age);

37: Dog(int age, int weight);

38: Dog(int age, BREED breed);

39: Dog(int age, int weight, BREED breed);

40: ~Dog();

41:

42: // Методы доступа

43: BREED GetBreed() const { return itsBreed; }

44: void SetBreed(BREED breed) { itsBreed = breed; }

45:

46: // Другие методы

47: void WagTail() const { cout << "Tail wagging,..\n"; }

48: void BegForFood() const { cout << "Begging for food...\n"; }

49:

50: private:

51: BREED itsBreed;

52: };

53:

54: Mammal::Mammal():

55: itsAge(1),

56: itsWeight(5)

57: {

58: cout << "Mammal constructor...\n";

59: }

60:

61: Mammal::Mammal(int age):

62: itsAge(age),

63: itsWeight(5)

64: {

65: cout << "Mammal(int) constructor...\n";

66: }

67:

68: Mammal::~Mammal()

69: {

70: cout << "Mammal destructor...\n";

71: }

72:

73: Dog::Dog();

74: Mammal(),

75: itsBreed(GOLDEN)

76: {

77: cout << "0og constructor...\n";

78: }

79:

80: Dog::Dog(int age):

81: Mammal(age),

82: itsBreed(GOLDEN)

83: {

84: cout << "Dog(int) constructor...\n";

85: }

86:

87: Dog::Dog(int age, int weight):

88: Mammal(age),

89: itsBreed(GOLDEN)

90: {

91: itsWeight = weight;

92: cout << "Dog(int, int) constructor...\n";

93: }

94:

95: Dog::Dog(int age, int weight, BREED breed):

96: Mammal(age),

97: itsBreed(breed)

98: {

99: itsWeight = weight;

100: cout << "Dog(int, int, BREED) constructor...\n";

101: }

102:

103: Dog::Dog(int age, BREEDbreed):

104: Mammal(age),

105: itsBreed(breed)

106: {

107: cout << "Dog(int, BREED) constructor...\n";

108: }

109:

110: Dog::~Dog()

111: {

112: cout << "Dog destructor...\n";

113: }

114: int main()

115: {

116: Dog fido;

117: Dog rover(5);

118: Dog buster(6,8);

119: Dog yorkie (3,GOLDEN);

120: Dog dobbie (4,20,DOBERMAN);

121: fido.Speak();

122: rover.WagTail();

123: cout << "Yorkie is " << yorkie.GetAge() << " years old\n";

124: cout << "Dobbie weighs ";

125: cout << dobbie.GetWeight() << " pounds\n";

126: return 0;

127: }

Примечание: Для удобства дальнейшего анализа строки вывода программы на экран пронумерованы.

Результат:

1: Mammal constructor...

2: Dog constructor...

3: Mammal(int) constructor...

4: Dog(int) constructor...

5: Mammal(int) constructor...

6: Dog(int, int) constructor...

7: Mammal(int) constructor...

8: Dog(int, BREED) constructor....

9: Mammal(int) constructor...

10: Dog(int, int, BREED) constructor...

11: Mammal sound!

12: Tail wagging...

13: Yorkie is 3 years old.

14: Dobbie weighs 20 pounds.

15: Dog destructor..,

16: Mammal destructor...

17: Dog destructor...

18: Mammal destructor...

19: Dog destructor...

20: Mammal destructor...

21: Dog destructor...

22: Mammal destructor...

23: Dog destructor, . .

24: Mammal destructor...

Анализ: В листинге 11.4 конструктор класса Mammal перегружен в строке 11 таким образом, чтобы принимать целочисленные значения возраста животного. В строках 61—66 происходит инициализация переменной itsAge значением 5, переданным в параметре конструктора.

В классе Dog в строках 35—39 создается пять перегруженных конструкторов. Первый — это конструктор, заданный по умолчанию. Второй принимает возраст и использует для этого тот же параметр, что и конструктор класса Mammal. Третий принимает возраст и вес, четвертый — возраст и породу, а пятый — возраст, вес и породу.

Обратите внимание, что в строке 74 конструктор по умолчанию класса Dog вызывает конструктор по умолчанию класса Mammal. Хотя в этом нет необходимости, но данная запись лишний раз документирует намерение вызвать именно базовый конструктор, не содержащий параметров. Базовый конструктор будет вызван в любом случае, но в данной строке это было сделано явно.

В строках 80—85 выполняется конструктор класса Dog, который принимает одно целочисленное значение. Во время инициализации (строки 81 и 82) возраст принимается из базового класса в виде параметра, после чего присваивается значение породы.

Другой конструктор класса Dog выполняется в строках 87—93. Этот конструктор принимает два параметра. Первое значение вновь инициализируется обращением к соответствующему конструктору базового класса, тогда как второе берется из переменной базового класса itsWeight самим конструктором класса Dog. Обратите внимание, что присвоение значения переменной базового класса не может осуществляться на стадии инициализации конструктора произведенного класса. Поскольку в классе Mammal нет конструктора, присваивающего значение этой переменной, то присвоение значения должно выполняться в теле конструктора класса Dog.

Самостоятельно проанализируйте работу остальных конструкторов в программе, чтобы закрепить полученные знания. Обращайте внимание, какие переменные можно инициализировать одновременно с инициализацией конструктора, а в каких случаях инициализацию следует выполнять в теле конструктора.

Для удобства анализа работы программы строки вывода были пронумерованы. Первые две строки вывода соответствуют инициализации объекта Fido с помощью конструкторов, заданных по умолчанию.

Строки 3 и 4 соответствуют созданию объекта rover, а строки 5 и 6 — объекта buster. Обратите внимание, что в последнем случае из конструктора класса Dog с двумя целочисленными параметрами происходит вызов конструктора класса Mammal, содержащего один целочисленный параметр.

После создания всех объектов программа использует их и наконец выходит за область видимости этих объектов. Удаление каждого объекта сопровождается обращением к деструктору класса Dog, после чего следует обращение к деструктору класса Mammal.

Замещение функций

Объект класса Dog имеет доступ ко всем функциям-членам класса Mammal, а также к любой функции-члену, чье объявление добавлено в класс Dog, например к функции WagTaill(). Но кроме этого, базовые функции могут быть замещены в производном классе. Под замещением базовой функции понимают изменение ее выполнения в производном классе для объектов, созданных в этом классе.

Если в производном классе создается функция с таким же возвратом и сигнатурой как и в базовом классе, но выполняемая особым образом, то имеет место замещение метода.

В случае замещения функций должно сохраняться соответствие между типом возврата и сигнатурой функций в базовом классе. Под сигнатурой понимают установки, заданные в прототипе функции, включая ее имя, список параметров и, в случае использования, ключевое слово const.

В листинге 11.5 показано замещение в классе Dog функции Speak(), объявленной в классе Mammal. Для экономии места знакомые по предыдущим листингам объявления методов доступа в этом примере были опущены.

Листинг 11.5. Замещение метода базового класса в производном классе

1: //Листинг 11.5. Замещение метода базового класса в производном классе

2:

3: #include <iostream.h>

4: enum BREED { GOLDEN, CAIRN, DANDIE, SHETLAND, DOBERMAN, LAB };

5:

6: class Mammal

7: {

8: public:

9: // Конструкторы

10: Mammal() { cout << "Mammal constructor...\n"; }

11: ~Mammal() { cout << "Mammal destructor...\n"; }

12:

13: //Другие методы

14: void Speak()const { cout << "Mammal sound!\n"; }

15: void Sleep()const { cout << "shhh. I'm sleeping.\n"; }

16:

17:

18: protected:

19: int itsAge;

20: int itsWeight;

21: };

22:

23: class Dog : public Mammal

24: {

25: public:

26:

27: // Конструкторы

28: Dog(){ cout << "Dog constructor...\n"; }

29: ~Dog(){ cout << "Dog destructor...\n"; }

30:

31: // Другие методы

32: void WagTail() const { cout << "Tail wagging...\n"; }

33: void BegForFood() const { cout << "Begging for food...\n"; }

34: void Speak() const { cout << "Woof!\n"; }

35:

36: private:

37: BREED itsBreed;

38: };

39:

40: int main()

41: {

42: Mammal bigAnimal;

43: Dog fido;

44: bigAnimal.Speak();

45: fido.Speak();

46: return 0;

47: }

Результат:

Mammal constructor...

Mammal constructor...

Dog constructor...

Mammal sound!

Woof!

Dog destructor...

Mammal destructor...

Mammal destructor...

Анализ: В строке 34 в классе Dog происходит замещение метода базового класса

Speak(), в результате чего в случае вызова этой функции объектом класса Dog на экран выводится Woof!. В строке 42 создается объект bigAnimal класса Mammal, в результате чего вызывается конструктор класса Mammal и на экране появляется первая строка. В строке 43 создается объект Fido класса Dog, что сопровождается последовательным вызовом сначала конструктора класса Mammal, а затем конструктора класса Dog. Соответственно на экран выводится еще две строки.

В строке 44 объект класса Mammal вызывает метод Speak(), а в строке 45 уже объект класса Dog обращается к этому методу. На экран при этом выводится разная информация, так как метод Speak() в классе Dog замещен. Наконец выполнение программы выходит за область видимости объектов и для их удаления вызываются соответствующие пары деструкторов.

Перегрузка или замещение

Эти схожиеподходы приводят почти к одинаковым результатам. При перегрузке метода создается несколько вариантов этогометода с одним и тем же именем, но с разными сигнатурами. При замещении в производном классе используется метод с тем же именем и сигнатурой, что и в базовом классе, но с изменениями в теле функции.

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