Тогда у меня снова вопрос. что-то вроде этого:
#include <iostream>
using namespace std;
class Base
{
public:
void foo()
{
cout<<"Base."<<endl;
}
};
class Derive:public Base
{
public:
void foo()
{
cout<<"Derive."<<endl;
}
};
int main()
{
Derive d;
Base *pb=&d; //attention here
pb->foo(); //ateention here
system("pause");
return 0;
}
И вывод «База». Тогда правила функций не работают, я запутался в этом, не могли бы вы помочь мне? Благодарю.
поскольку foo
не является виртуальной, вызываемая функция основана на статический тип (то есть тип, на который указывается указатель), а не динамический тип (тип объекта, на который в данный момент ссылается указатель).
Есть также несколько более сложных случаев, которые нужно рассмотреть. Один момент (на который некоторые другие ответы на самом деле несколько вводят в заблуждение) заключается в том, что это не просто функция название это имеет значение, но вся функция подписи. Например:
#include <iostream>
struct base {
virtual void foo() {
std::cout << "base::foo";
}
};
struct derived : base {
virtual void foo() const {
std::cout << "derived::foo";
}
};
int main(){
base *b = new derived;
b->foo();
}
Вот foo
квалифицируется как virtual
как в базовых, так и (казалось бы, избыточно) производных классах, но вызов b->foo()
еще распечатывает base::foo
,
const
добавлено в подпись derived::foo
означает, что он больше не соответствует подписи base::foo
поэтому вместо переопределения виртуальной функции мы по-прежнему получаем две отдельные функции с одинаковым именем, поэтому derived::foo
шкуры base::foo
, но не отменяет это. Несмотря на virtual
квалификация, мы получаем статическое связывание, так b->foo();
вызывает базовую функцию, а не производную, даже если b
указывает на объект derived
тип.
Как отметил Тони Д. в комментарии, C ++ 11 добавил новую складку в язык, чтобы предотвратить это. Если вы хотите переопределить функцию базового класса, вы можете добавить идентификатор override
к функции в производном классе:
struct derived : base {
virtual void foo() const override {
std::cout << "derived::foo";
}
};
При этом, если есть разница в сигнатуре функции (как в случаях, показанных здесь), компилятор выдаст сообщение об ошибке, предупреждающее вас о том, что derived::foo
помечен как override
, но на самом деле не переопределяет функцию из базового класса. Однако это было добавлено в C ++ 11, поэтому, если вы используете более старый компилятор, эта функция может быть не реализована (хотя, к счастью, компиляторы, которые ее не реализуют, быстро уходят в небытие).
Исправление подписи в базовом классе:
virtual void foo() const // ...
…позволит скомпилировать код и дать правильные результаты.
функция Base::foo
не является виртуальным. Он вызывается потому, что используется указатель на базовый класс.
Если вы измените код следующим образом:
class Base
{
public:
virtual void foo() // add virtual
{
cout<<"Base."<<endl;
}
};
вывод должен бытьПроизводный».
Вы видите это поведение, потому что foo()
не объявлен как virtual
в Base
, В C ++ функции-члены не являются виртуальными по умолчанию. Вы должны явно объявить функцию как virtual
для того, чтобы воспользоваться динамической диспетчеризацией и полиморфизмом.
Судя по названию вашего вопроса, я понимаю, что вы не до конца понимаете, когда функции скрываются, перегружаются и перезаписываются.
Пример кода 1:
struct Base
{
void foo()
{
}
};
struct Derive: public Base
{
void foo()
{
}
};
int main()
{
Derive d;
Base *pb=&d;
d.foo(); // Resolves to Derived::foo()
pb->foo(); // Resolves to Base::foo()
return 0;
}
Почему d.foo()
вызов Derived::foo()
а также pb->foo()
вызов Base::foo()
?
Ответ на этот вопрос заключается в шагах, которые компилятор предпринимает для разрешения этих привязок функций.
Дан объект типа T и имя функции f
, компилятор ищет функции с именем f
в T
, Если он находит только одну функцию с именем f
поиск функций на этом заканчивается. Если он находит более одной функции, он пытается разрешить перегрузку из набора функций, найденных в T
,
Если он не находит никаких функций с именем f
в T
а также T
имеет базовый класс, он пытается вышеуказанную логику в T
базовый класс. Если T
не имеет базовых классов, компилятор сообщает об ошибке.
Подойдя к объектам примера кода …
При обработке вызова функции d.foo()
компилятор ищет foo
в Derived
, Он находит там одно совпадение и останавливается. Так как Derived::foo()
это не virtual
функция, привязка выполняется во время компиляции. Во время выполнения, Derived::foo()
называется.
При обработке вызова функции pb->foo()
компилятор ищет foo
в Base
, Он находит там одно совпадение и останавливается. Так как Base::foo()
это не virtual
функция, привязка выполняется во время компиляции. Во время выполнения, Base::foo()
называется.
Пример кода 2:
struct Base
{
void foo(int i)
{
}
};
struct Derive: public Base
{
void foo()
{
}
};
int main()
{
Derive d;
Base *pb=&d;
d.foo(); // Resolves to Derived::foo()
d.foo(10); // Compiler error.
pb->foo(10); // Resolves to Base::foo(int)
pb->foo(); // Compiler error.
return 0;
}
Почему компилятор выдает здесь ошибки?
При обработке вызова функции d.foo(10)
компилятор ищет foo
в Derived
, Он находит там одно совпадение и останавливается. Он пытается использовать эту функцию, но подпись функции не соответствует вызывающему коду. Следовательно, это ошибка компилятора.
При обработке вызова функции pb->foo()
компилятор ищет foo
в Base
, Он находит там одно совпадение и останавливается. Он пытается использовать эту функцию, но подпись функции не соответствует вызывающему коду. Следовательно, это ошибка компилятора.
Как только компилятор найдет foo
в Derived
он не идет в поисках соответствия foo
в Base
,
В этом случае вы можете думать о Derived::foo
быть полностью скрытым Base::foo
,
Пример кода 3:
struct Base
{
void foo()
{
}
};
struct Derive: public Base
{
void foo()
{
}
void foo(int )
{
}
};
int main()
{
Derive d;
d.foo(); // Resolves to Derived::foo()
d.foo(10); // Resolves to Derived::foo(int)
Base *pb=&d;
pb->foo(); // Resolves to Base::foo()
pb->foo(10); // Compiler error.
return 0;
}
При обработке вызовов функции d.foo()
а также d.foo(10)
компилятор ищет foo
в Derived
, Он находит там пару совпадений и останавливается. Затем он пытается разрешить перегрузку. Он может найти соответствие для обеих версий. Так как ни один из Derived::foo()
с является virtual
функция, привязка выполняется во время компиляции.
При обработке вызовов функции bp->foo()
а также bp->foo(10)
компилятор ищет foo
в Base
, Он находит там пару совпадений и останавливается. Затем он пытается разрешить перегрузку. Он может найти соответствие для первых версий, но не для второй версии. Это генерирует ошибку для второго вызова.
Вот Derived::foo
не только спрятать Base::foo
но есть и две перегруженные версии Derived::foo
,
Пример кода 4:
struct Base
{
virtual void foo()
{
}
void foo(int)
{
}
};
struct Derive: public Base
{
void foo()
{
}
void foo(int )
{
}
};
int main()
{
Derive d;
d.foo(); // Resolves to Derived::foo()
// But Derived:foo() gets called at run time.
d.foo(10); // Resolves to Derived::foo(int)
// But Derived:foo(int) gets called at run time.
Base *pb=&d;
pb->foo(); // Resolves to Base::foo()
// But Derived:foo() gets called at run time due to
// function overwritting.
pb->foo(10); // Resolves to Base::foo(10)
// Base:foo(int) gets called at run time.
return 0;
}
Это включает в себя скрытие функций, перегрузку функций и перезапись функций.
Derived::foo
шкуры Base::foo
,
Derived::foo()
а также Derived::foo(int)
перегружены.
Base::foo()
а также Base::foo(int)
перегружены.
Base::foo()
перезаписывается Derived::foo()
,
Я надеюсь, что это прояснит некоторые ваши сомнения.
В этом коде нет перегрузки или перезаписи. Base::foo
называется, а не Derive::foo
потому что не было никакой спецификации программистом, чтобы использовать динамическое связывание для имени foo
, Если виртуальный спецификатор не предоставлено, компилятор находит функцию на основе статического типа объекта, на который он вызывается, а не на тип, на который он может указывать. Это называется статическим связыванием и выполняется во время компиляции.
Если используется виртуальный спецификатор, имя функции ищется во время выполнения и вызывается на основе типа времени выполнения объекта.
Кроме того, вашему базовому классу нужен виртуальный деструктор по той же причине, что и выше. Если у тебя есть Base
указатель класса, который указывает на Derive
удаление этого указателя вызовет только деструктор базового класса, а не базовый и производный.