Ключевое слово virtual
может быть опущено при переопределении в производных классах. Если переопределенная функция в базовом классе является виртуальной, то предполагается, что переопределение также является виртуальным.
Этот вопрос хорошо освещен в этом вопросе: Является ли функция в C ++ автоматически виртуальной, если она переопределяет виртуальную функцию?
Ваш второй вопрос о значениях по умолчанию и виртуальных функциях. По сути, каждое переопределение может иметь другое значение по умолчанию. Однако обычно это не будет делать то, что вы ожидаете, поэтому мой совет: не смешивайте значения по умолчанию и виртуальные функции .
Независимо от того, используется функция базового класса по умолчанию или нет, она полностью не зависит от того, используется ли функция производного класса по умолчанию.
Основная идея заключается в том, что статический тип будет использоваться для поиска значения по умолчанию, если оно определено. Для виртуальных функций динамический тип будет использоваться для поиска вызываемой функции.
Поэтому, когда динамический и статический типы не совпадают, последуют неожиданные результаты.
, например
#include <iostream>
class A
{
public:
virtual void foo(int n = 1) { std::cout << "A::foo(" << n << ")" << std::endl; }
};
class B : public A
{
public:
virtual void foo(int n = 2) { std::cout << "B::foo(" << n << ")" << std::endl; }
};
int main()
{
A a;
B b;
a.foo(); // prints "A::foo(1)";
b.foo(); // prints "B::foo(2)";
A& ref = b;
ref.foo(); // prints "B::foo(1)";
}
Если все ваши переопределения имеют одно и то же значение по умолчанию, другое решение состоит в том, чтобы определить дополнительную функцию в базовом классе, которая не выполняет ничего, кроме вызова виртуальной функции с аргументом по умолчанию. То есть:
class A
{
public:
void defaultFoo() { foo(1); }
virtual void foo(int n) { .... }
};
Если ваши переопределения имеют разные значения по умолчанию, у вас есть два варианта:
- также делает виртуальный
defaultFoo()
, что может привести к неожиданным результатам, если производный класс перегружает один, но не другой.
- не использовать значения по умолчанию, но явно указывать используемое значение в каждом вызове.
Я предпочитаю последнее.