Menu
Coddy logo textTech

Переопределение методов

Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy — урок 51 из 104.

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

Чтобы переопределить метод, просто определите метод в производном классе с тем же самым именем, типом возвращаемого значения и параметрами:

class Animal {
public:
    void speak() {
        std::cout << "Some sound" << std::endl;
    }
};

class Dog : public Animal {
public:
    void speak() {
        std::cout << "Woof!" << std::endl;
    }
};

Dog d;
d.speak();  // Вывод: Woof!

Версия производного класса скрывает версию базового класса. Тем не менее, вы все еще можете получить доступ к методу базового класса, используя оператор разрешения области видимости:

class Dog : public Animal {
public:
    void speak() {
        Animal::speak();  // Вызов версии базового класса
        std::cout << "Woof!" << std::endl;
    }
};

У этой базовой формы переопределения есть важное ограничение. Когда вы обращаетесь к производному объекту через указатель или ссылку на базовый класс, вместо этого вызывается версия базового класса:

Dog d;
Animal* ptr = &d;
ptr->speak();  // Вывод: Some sound (не Woof!)

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

challenge icon

Задание

Легко

Давайте создадим систему уведомлений, которая демонстрирует переопределение методов в действии. Вы создадите базовый класс Notifier и два специализированных уведомления, которые настраивают способ доставки сообщений.

Вы организуете свой код в трех файлах:

  • Notifier.h: Определите базовый класс Notifier с:
    • Защищенным членом std::string recipient
    • Конструктором, который принимает имя получателя и сохраняет его
    • Публичным методом send(const std::string& message), который выводит: Sending to <recipient>: <message>
  • EmailNotifier.h: Определите класс EmailNotifier, который публично наследуется от Notifier:
    • Конструктор, который принимает получателя и передает его в базовый класс
    • Переопределите метод send(), чтобы сначала вызвать версию базового класса с помощью Notifier::send(), а затем вывести: [Email sent successfully]
  • main.cpp: Считайте два входных значения (каждое на отдельной строке):
    1. Имя получателя (строка)
    2. Содержание сообщения (строка)

    Определите класс SMSNotifier непосредственно в main.cpp, который публично наследуется от Notifier:

    • Конструктор, который принимает получателя и передает его в базовый класс
    • Переопределите метод send(), чтобы вывести: SMS to <recipient>: <message> (полностью заменяя базовое поведение без его вызова)

    Создайте по одному объекту каждого типа уведомления (базовый Notifier, EmailNotifier и SMSNotifier), используя одного и того же получателя. Вызовите send() для каждого с введенным сообщением, выводя разделительную линию между ними:

    --- Base Notifier ---
    <output>
    --- Email Notifier ---
    <output>
    --- SMS Notifier ---
    <output>

Например, при входных данных Alice и Hello there!:

--- Base Notifier ---
Sending to Alice: Hello there!
--- Email Notifier ---
Sending to Alice: Hello there!
[Email sent successfully]
--- SMS Notifier ---
SMS to Alice: Hello there!

Обратите внимание, как EmailNotifier расширяет базовое поведение, сначала вызывая родительский метод, в то время как SMSNotifier полностью заменяет его собственной реализацией. Не забудьте про guard-инструкции заголовков и включение соответствующих заголовочных файлов в каждый файл.

Шпаргалка

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

Базовый синтаксис переопределения:

class Animal {
public:
    void speak() {
        std::cout << "Some sound" << std::endl;
    }
};

class Dog : public Animal {
public:
    void speak() {
        std::cout << "Woof!" << std::endl;
    }
};

Dog d;
d.speak();  // Output: Woof!

Метод производного класса скрывает версию базового класса. Чтобы вызвать метод базового класса из производного класса, используйте оператор разрешения области видимости:

class Dog : public Animal {
public:
    void speak() {
        Animal::speak();  // Вызов версии базового класса
        std::cout << "Woof!" << std::endl;
    }
};

Важное ограничение: При доступе к производному объекту через указатель или ссылку базового класса вызывается версия базового класса:

Dog d;
Animal* ptr = &d;
ptr->speak();  // Output: Some sound (not Woof!)

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

Попробуйте сами

#include <iostream>
#include <string>
#include "Notifier.h"
#include "EmailNotifier.h"

using namespace std;

// TODO: Определите здесь класс SMSNotifier, который публично наследуется от Notifier
// - Конструктор, который передает recipient в базовый класс
// - Переопределите send(), чтобы вывести: SMS to <recipient>: <message>
//   (полностью замените базовое поведение, не вызывайте родительский send())

int main() {
    string recipient;
    string message;
    
    getline(cin, recipient);
    getline(cin, message);
    
    // TODO: Создайте объект Notifier с recipient
    // Выведите "--- Base Notifier ---"
    // Вызовите send() с message
    
    // TODO: Создайте объект EmailNotifier с recipient
    // Выведите "--- Email Notifier ---"
    // Вызовите send() с message
    
    // TODO: Создайте объект SMSNotifier с recipient
    // Выведите "--- SMS Notifier ---"
    // Вызовите send() с message
    
    return 0;
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование