почему у нас на самом деле есть виртуальные функции?

Я плохо знаком с C++.

Мог кто-либо говорить мне различие между переопределением метода и понятиями виртуальной функции в C++.

Функциональность виртуальных функций может быть переопределена в ее производных классах. Переопределение функции в производном классе называют функциональным переопределением.

почему у нас на самом деле есть виртуальные функции?

11
задан Vijay 11 April 2016 в 06:24
поделиться

10 ответов

Для лучшей переносимости используйте библиотеку boost filesystem . Используйте opendir ()/readdir () и friends для UNIX-систем.

-121--3808436-

Если вы хотите добавить шаблон, вы можете добавить что-то подобное в класс массива:

class Array
  def each_fl
    each_with_index do |x,i|
      yield [i==0 ? :first : (i==length-1 ? :last : :inner), x]
    end
  end
end

и затем в любом месте, где вам нужно, вы получите следующий синтаксис:

[1,2,3,4].each_fl do |t,x|
  case t
    when :first
      puts "first: #{x}"
    when :last
      puts "last: #{x}"
    else
      puts "otherwise: #{x}"
  end
end

для следующих выходных данных:

first: 1
otherwise: 2
otherwise: 3
last: 4
-121--1000511-

ABSTRACT

В этой статье мы обсуждаем виртуальные функции в C++. Нулевая часть объясняет, как объявляются и переопределяются виртуальные функции. Часть первая пытается (и, возможно, не удается) объяснить, как реализуются виртуальные функции. Часть вторая - это пример программы, в которой используются классы примеров, определенные в частях 0 и 1. Часть третья - классический пример животного, приведенный в каждой виртуальной функции - руководстве по полиморфизму.

PART ZERO

Метод класса называется virtual тогда и только тогда, когда он объявлен как таковой.

class my_base
{
public:
            void non_virtual_test() { cout << 4 << endl; } // non-virtual
    virtual void virtual_test()     { cout << 5 << endl; } // virtual
};

(Конечно, я предполагаю, что программист ранее не делал ничего подобного # define virtual .)

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

class my_derived : public my_base
{
public:
    void non_virtual_test() { cout << 6 << endl; } // overloaded
    void virtual_test()     { cout << 7 << endl; } // overriden
};

PART ONE

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

class my_base
{
//<vtable>
// The vtable is actually a bunch of member function pointers
protected:
    void (my_base::*virtual_test_ptr)();
//</vtable>

// The actual implementation of the virtual function
// is hidden from the rest of the program.
private:
    void virtual_test_impl() { cout << 5 << endl; }

// Initializing the real_virtual_test pointer in the vtable.
public:
    my_base() : virtual_test_ptr(&my_base::virtual_test_impl) {}

public:
    void non_virtual_test() { cout << 4 << endl; }
    // The interface of the virtual function is a wrapper
    // around the member function pointer.
    inline void virtual_test() { *virtual_test_ptr(); }
};

Когда компилятор обнаруживает, что класс переопределяет виртуальный метод, он заменяет связанную с ним запись в таблице vtable. Результат аналогичен тому, что было бы создано при компиляции этого кода:

class my_derived : public my_base
{
// The actual implementation of the virtual function
// is hidden from the rest of the program.
private:
    void virtual_test_impl() { cout << 7 << endl; }

// Initializing the real_virtual_test pointer in the vtable.
public:
    my_derived() : virtual_test_ptr(&my_derived::virtual_test_impl) {}

public:
    void non_virtual_test() { cout << 6 << endl; }
};

ЧАСТЬ ВТОРАЯ

Теперь, когда ясно, что виртуальные функции реализуются с помощью vtables, которые не являются ничем иным, как набором указателей функций, должно быть ясно, что этот код делает:

#include <iostream>

using namespace std;

class my_base
{
    public:
            void non_virtual_test() { cout << 4 << endl; }
    virtual void virtual_test()     { cout << 5 << endl; }
};

class my_derived : public my_base
{
public:
    void non_virtual_test() { cout << 6 << endl; }
    void virtual_test()     { cout << 7 << endl; }
}

int main()
{
    my_base* base_obj = new my_derived();

    // This outputs 4, since my_base::non_virtual_test() gets called,
    // not my_derived::non_virtual_test().
    base_obj->non_virtual_test();

    // This outputs 7, since the vtable pointer points to
    // my_derived::virtual_test(), not to my_base::virtual_test().
    base_obj->virtual_test();

    // We shall not forget
    // there was an object that was pointed by base_obj
    // who happily lived in the heap
    // until we killed it.
    delete base_obj;

    return 0;
}

ЧАСТЬ ТРЕТЬЯ

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

#include <iostream>

using namespace std;

class animal
{
public:
    virtual void say_something()
    { cout << "I don't know what to say." << endl
           << "Let's assume I can growl." << endl; }

    /* A more sophisticated version would use pure virtual functions:
     *
     * virtual void say_something() = 0;
     */
};

class dog : public animal
{
public:
    void say_something() { cout << "Barf, barf..." << endl; }
};

class cat : public animal
{
public:
    void say_something() { cout << "Meow, meow..." << endl; }
};

int main()
{
    animal *a1 = new dog();
    animal *a2 = new cat();
    a1->say_something();
    a2->say_something();
}
2
ответ дан 3 December 2019 в 02:01
поделиться

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

Базовая установка

В C ++ любой производный класс может быть передан функции, требующей объект базового класса. (См. Также Нарезка и LSP ). Дано:

struct Base_Virtual
{
  virtual void some_virtual_function();
};

struct Base_Nonvirtual
{
  void some_function();
};

void Function_A(Base_Virtual * p_virtual_base);
void Function_B(Base_Nonvirtual * p_non_virtual_base);

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

Объявлены две функции, которым требуются указатели на соответствующие базовые классы.

Производные классы

Давайте теперь проверим полиморфизм, особенно виртуальный и не виртуальный (методы переопределения). Структуры:

struct Derived_From_Virtual
: public Base_Virtual
{
  void some_virtual_function(); // overrides Base_Virtual::some_virtual_function()
};

struct Derived_From_Nonvirtual {{1 }}: public Base_Nonvirtual { void some_function (); }

Согласно языку C ++, я могу передать указатель на Derived_From_Virtual на Function_A , поскольку Derived_From_Virtual наследуется от Base_Virtual . Я также могу передать указатель на Derived_From_Nonvirtual в Function_B .

Разница между virtual и переопределением

Модификатор virtual в Base_Virtual сообщает компилятору, что Function_A будет использовать Derived_From_Virtual :: some_virtual_function () вместо метода в Base_Virtual .Это связано с тем, что метод является виртуальным , окончательное определение может находиться в будущем или производном классе . Фактическое определение говорит об использовании метода в самом производном классе, содержащем определение.

При передаче указателя на Derived_From_Nonvirtual в Function_B , компилятор проинструктирует функцию использовать метод базового класса, Base_Nonvirtual :: some_function () . Метод some_function () в производном классе является отдельным, не связанным с базовым классом методом.

Основное различие между виртуальным и переопределением связано с полиморфизмом.

9
ответ дан 3 December 2019 в 02:01
поделиться

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

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

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

Изучая C ++ больше, вы обнаружите, что наследование - это еще не все, чем его считают. Композиция и часто является лучшей альтернативой. Повеселись.

1
ответ дан 3 December 2019 в 02:01
поделиться

Это скорее продолжение комментариев из этого ответа , чем сам по себе ответ.

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

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

struct base {
   virtual void override() { std::cout << "base::override" << std::endl; }
   void not_override() { std::cout << "base::not_override" << std::endl; }
};
struct derived : base {
   void override() { std::cout << "derived::override" << std::endl; }
   void not_override() { std::cout << "derived::not_override" << std::endl; }
};
int main() {
   derived d;
   base & b = d;

   b.override();     // derived::override
   b.not_override(); // base::not_override
   d.not_override(); // derived::not_override
}

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

Другие проблемы

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

Виртуальные методы, которые не являются окончательным переопределением , а также скрытые методы могут быть вызваны при использовании полного имени. В случае виртуальных методов использование полного имени отключает механизм полиморфной отправки для вызова: d.base :: override () вызовет базовую реализацию, даже если есть другие переопределения в производных классах.

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

struct base {
   void f() {}
};
struct derived : base {
   void f(int) {}
};
int main() {
   derived d;
   // d.f() // error, derived::f requires an argument, base::f is hidden in this context
}

Как и в случае переопределяет , d.base :: f () вызывает базовую версию, а не потому, что она отключает полиморфизм - этого не происходит, поскольку метод не объявлен virtual он никогда не будет иметь полиморфного поведения - но поскольку полная квалификация сообщает компилятору, где находится метод, даже если он был скрыт другим методом в производном классе.

4
ответ дан 3 December 2019 в 02:01
поделиться

Виртуальная функция / метод - это просто функция, поведение которой может быть переопределено в пределах подкласса (или, в терминах C ++, производного класса) путем переопределения того, как работает функция (с использованием той же сигнатуры).

Представьте себе млекопитающее базового класса с функцией речи. Функция недействительна и просто показывает, как говорит млекопитающее. Когда вы унаследованы от этого класса, вы можете переопределить метод speak, чтобы собаки пели "Arf Arf!" и кошки кричат ​​«Мяу-Мяу».

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

Перегрузка функций означает создание функции с тем же именем, но с разными аргументами, то есть с другим числом и типом аргументов. Вот объяснение перегрузки в C ++ с сайта IBM :

Перегрузка (только C ++) Если вы укажете более одного определения для имени функции или оператора в той же области, вы перегрузили это имя функции или оператор. Перегруженные функции и операторы описаны в разделах «Функции перегрузки (только C ++)» и «Операторы перегрузки » (только C ++) соответственно.

Перегруженное объявление - это объявление, которое было объявлено с тем же именем, что и ранее объявленное объявление в той же области, , за исключением того, что оба объявления имеют разные типы.

Если вы вызываете перегруженное имя функции или оператора, компилятор определяет наиболее подходящее определение для использования, сравнивая типы аргументов , которые вы использовали для вызова функции или оператора, с {{1 }} типы параметров, указанные в определениях. Процесс выбора наиболее подходящей перегруженной функции или оператора называется разрешением перегрузки , как описано в разделе Разрешение перегрузки (только C ++).

Что касается полной рациональной причины ситуаций, в которых требуются виртуальные функции, то это сообщение в блоге дает хороший пример: http://nrecursions.blogspot.in/2015/06/so-why-do-we- need-virtual-functions.html

12
ответ дан 3 December 2019 в 02:01
поделиться

Посмотрите C++ FAQ lite, http://www.parashift.com/c++-faq-lite/. Это, вероятно, один из лучших ресурсов по C++ для начинающих. В нем подробно написано о виртуальных функциях и переопределении.

Лично я считаю C++ FAQ отличным источником информации при изучении C++. У других людей другое мнение, ваш пробег может отличаться

.
8
ответ дан 3 December 2019 в 02:01
поделиться

Если вы пришли из Java, то концепция виртуальных и не виртуальных функций-членов может показаться вам запутанной. Следует помнить, что методы Java соответствуют виртуальным функциям-членам в C++.

Вопрос не столько в том, зачем нам виртуальные функции, сколько в том, зачем нам невиртуальные? Я оправдываю их для себя (поправьте меня, если я ошибаюсь) тем, что они дешевле в реализации, поскольку вызовы к ним могут быть разрешены во время компиляции.

1
ответ дан 3 December 2019 в 02:01
поделиться

Разница просто используется, когда вы вызываете метод производного класса через указатель на объект базового класса. В этот момент, если вызываемый метод был переопределен в производном классе, вы получите выполнение метода базового класса, а если был виртуальным, то вы получите выполнение метода производного класса.

#include <iostream>

class A{
    public:
    virtual void getA() { std::cout << "A in base" << std::endl;};
};

class B : public A {
    public:
    void getA() { std::cout << "A in derived class" << std::endl;}
};

int main(int argc, char** argv)
{
    A a;
    B b;
    a.getA();
    b.getA();

    A* t = new B;
    t->getA();
}

Например: в этой программе t->getA() выводит "A в производном классе", но если бы в базовом классе A не было виртуального модификатора, то выводилось бы "A в базовом".

Надеюсь, это поможет.

-1
ответ дан 3 December 2019 в 02:01
поделиться

Классический пример - это программа рисования, где создается базовый класс Shape с виртуальной функцией draw(). Затем каждая из фигур (круг, прямоугольник, треугольник и т.д.) может быть создана как подкласс, каждый из которых реализует свою функцию draw() соответствующим образом, и основная программа рисования может хранить список фигур, каждая из которых будет выполнять соответствующую функцию draw(), даже если хранится только указатель на базовый класс Shape.

0
ответ дан 3 December 2019 в 02:01
поделиться

И вертолеты, и самолеты летают, но делают это по-разному - они оба являются экземплярами некоего гипотетического объекта Flyer. Вы можете попросить объект Flyer "летать", но Flyer - это просто интерфейс. Он ничего не знает о полете, кроме того, что он должен уметь летать.

Однако, если и вертолет, и самолет следуют интерфейсу флаера, чем если бы у него был объект аэродрома и вы дали ему флаер, все, что нужно на аэродроме, - это запросить летчиков на полет.

Например:

Airplace X=Airplane X("boeing 747");
Airfield::takeoff(&X);

Helicopter Y= Helicopter("Comache");
Airfield::takeof(&Y);

void Airfield::takeOff(Flyer * f)
{
     f->fly();
}

C ++ - это язык со строгой типобезопасностью, и такая функциональность (выполнение вызовов функций к производным классам косвенно через базовый класс) возможна только тогда, когда RTTI включен для иерархии объектов и квалифицируется виртуальная функция-член позволяет это сделать.

-1
ответ дан 3 December 2019 в 02:01
поделиться
Другие вопросы по тегам:

Похожие вопросы: