如果一个函数被定义为虚函数,那么它到底是什么意思?这和纯虚函数是一样的吗?
在面向对象编程中,像 C++ 和 Object Pascal 这样的语言中,虚函数或虚方法是一种可继承和重写的函数或方法,用于实现动态分派。这个概念是面向对象编程中(运行时)多态性的一个重要部分。简而言之,虚函数定义了一个目标函数来执行,但目标函数在编译时可能是未知的。
与非虚函数不同,当虚函数被重写时,最终导出的版本将在类层次结构的所有级别中使用,而不仅仅是在创建它的级别上。因此,如果基类的某个方法调用了虚方法,则会使用在派生类中定义的版本,而不是在基类中定义的版本。
这与非虚函数形成对比,后者仍然可以在派生类中被重写,但“新”版本仅由派生类及以下级别使用,而不会更改基类的功能。
而另一方面..
一个纯虚函数或纯虚方法是一个虚函数,如果派生类不是抽象类,则必须由其实现。
当存在纯虚方法时,该类是“抽象的”,不能单独实例化。必须使用实现纯虚拟方法的派生类。纯虚函数在基类中根本没有定义,因此必须由派生类定义,否则该派生类也是抽象的,无法实例化。只有没有抽象方法的类才能被实例化。
虚函数提供了覆盖基类功能的方法,而纯虚函数则要求它这样做。
我想就Wikipedia上关于"虚方法"的定义发表一下评论,正如其他几位在这里所说的那样。【在回答写作时】Wikipedia将虚方法定义为可以在子类中被覆盖的方法。这是不正确的:任何方法,而不仅仅是虚方法,都可以在子类中被覆盖。虚方法提供的是多态性,也就是在运行时选择方法的最终派生重载的能力。
考虑以下代码:
#include <iostream>
using namespace std;
class Base {
public:
void NonVirtual() {
cout << "Base NonVirtual called.\n";
}
virtual void Virtual() {
cout << "Base Virtual called.\n";
}
};
class Derived : public Base {
public:
void NonVirtual() {
cout << "Derived NonVirtual called.\n";
}
void Virtual() {
cout << "Derived Virtual called.\n";
}
};
int main() {
Base* bBase = new Base();
Base* bDerived = new Derived();
bBase->NonVirtual();
bBase->Virtual();
bDerived->NonVirtual();
bDerived->Virtual();
}
这个程序的输出是什么?Base NonVirtual called.
Base Virtual called.
Base NonVirtual called.
Derived Virtual called.
派生类覆盖了基类的每个方法:不仅仅是虚函数,而且还包括非虚函数。
当你有一个指向Derived的Base指针(bDerived)时,调用NonVirtual会调用Base类的实现。这在编译时解决:编译器看到bDerived是一个Base*,NonVirtual不是虚函数,因此它在类Base上进行解析。
然而,调用Virtual会调用Derived类的实现。由于关键字virtual,方法的选择发生在运行时,而不是编译时。编译时发生的情况是,编译器看到这是一个Base*,它正在调用一个虚方法,因此它插入对vtable的调用,而不是class Base。这个vtable在运行时实例化,因此在运行时解析到最终派生的重写。
希望这不会太让人困惑。简而言之,任何方法都可以被重载,但只有虚方法才能给你多态性,也就是最终派生重写的运行时选择。然而,在实践中,重载非虚方法被认为是不好的习惯,很少使用,因此许多人(包括撰写维基百科文章的人)认为只有虚方法可以被重载。
Derived*
并进行相同的函数调用以加强观点表达。否则答案很好。 - Jeff Jonesvirtual关键字赋予C++支持多态性的能力。当你有一个指向某个类的对象的指针时,如:
class Animal
{
public:
virtual int GetNumberOfLegs() = 0;
};
class Duck : public Animal
{
public:
int GetNumberOfLegs() { return 2; }
};
class Horse : public Animal
{
public:
int GetNumberOfLegs() { return 4; }
};
void SomeFunction(Animal * pAnimal)
{
cout << pAnimal->GetNumberOfLegs();
}
在这个(愚蠢的)示例中,GetNumberOfLegs()函数根据调用它的对象的类返回适当的数量。Duck d;
SomeFunction(&d);
输出结果将为'2'。如果我们执行以下操作:
Horse h;
SomeFunction(&h);
这将输出'4'。我们无法这样做:
Animal a;
SomeFunction(&a);
由于GetNumberOfLegs()虚函数是纯虚函数,必须由派生类(子类)实现,因此它无法编译。
纯虚函数主要用于定义:
a) 抽象类
这些是基类,您必须从它们派生并实现纯虚函数。
b) 接口
这些是“空”的类,其中所有函数都是纯虚函数,因此您必须派生并实现所有函数。
在C++类中, virtual 是指定方法可以被子类覆盖(即由子类实现)的关键字。例如:
class Shape
{
public:
Shape();
virtual ~Shape();
std::string getName() // not overridable
{
return m_name;
}
void setName( const std::string& name ) // not overridable
{
m_name = name;
}
protected:
virtual void initShape() // overridable
{
setName("Generic Shape");
}
private:
std::string m_name;
};
在这种情况下,子类可以重写 initShape 函数来执行一些特定的工作:class Square : public Shape
{
public:
Square();
virtual ~Square();
protected:
virtual void initShape() // override the Shape::initShape function
{
setName("Square");
}
}
术语“纯虚拟(pure virtual)”指的是需要由子类实现但未由基类实现的虚函数。你可以使用关键字“virtual”并在方法声明末尾加上“= 0”来将方法标记为纯虚方法。因此,如果您想使Shape :: initShape成为纯虚方法,则应执行以下操作:class Shape
{
...
virtual void initShape() = 0; // pure virtual method
...
};
通过在类中添加一个纯虚方法,您可以使该类成为一个抽象基类,这对于将接口与实现分离非常有用。
m_name
。m_
代表什么意思? - Tqn"Virtual"意味着该方法可以在子类中被重写,但在基类中有一个可直接调用的实现。 "Pure virtual"意味着它是一个没有直接可调用实现的虚拟方法。这样的方法必须在继承层次结构中至少被重写一次--如果一个类有任何未实现的虚拟方法,则无法构造该类的对象,编译将失败。
@quark指出,纯虚拟方法可以有一个实现,但由于纯虚拟方法必须被重写,因此默认实现不能直接调用。以下是带有默认值的纯虚拟方法示例:
#include <cstdio>
class A {
public:
virtual void Hello() = 0;
};
void A::Hello() {
printf("A::Hello\n");
}
class B : public A {
public:
void Hello() {
printf("B::Hello\n");
A::Hello();
}
};
int main() {
/* Prints:
B::Hello
A::Hello
*/
B b;
b.Hello();
return 0;
}
根据评论,编译是否会失败取决于具体的编译器。至少在GCC 4.3.3中无法通过编译:class A {
public:
virtual void Hello() = 0;
};
int main()
{
A a;
return 0;
}
输出:
$ g++ -c virt.cpp
virt.cpp: In function ‘int main()’:
virt.cpp:8: error: cannot declare variable ‘a’ to be of abstract type ‘A’
virt.cpp:1: note: because the following virtual functions are pure within ‘A’:
virt.cpp:3: note: virtual void A::Hello()
A *a = new A
,但是仍然会出现相同的错误。
@Glen:我再次查看了你的评论,意识到自己误读了。我会更正帖子。 - John Millikin虚函数是在基类中声明并由派生类重新定义的成员函数。虚函数按继承顺序自上而下排列。如果派生类未重写虚函数,则使用其基类中定义的函数。
纯虚函数是指与基类相对无定义的函数。它在基类中没有实现。任何派生类必须重写此函数。
虚拟关键字如何工作?
假设Man是一个基类,Indian是从Man派生而来的。
Class Man
{
public:
virtual void do_work()
{}
}
Class Indian : public Man
{
public:
void do_work()
{}
}
Man *man;
man = new Indian();
man->do_work(); // Indian's do work is only called.
纯虚函数
尝试这段代码
#include <iostream>
using namespace std;
class aClassWithPureVirtualFunction
{
public:
virtual void sayHellow()=0;
};
class anotherClass:aClassWithPureVirtualFunction
{
public:
void sayHellow()
{
cout<<"hellow World";
}
};
int main()
{
//aClassWithPureVirtualFunction virtualObject;
/*
This not possible to create object of a class that contain pure virtual function
*/
anotherClass object;
object.sayHellow();
}
#include <iostream>
using namespace std;
class aClassWithPureVirtualFunction
{
public:
virtual void sayHellow()
{
cout<<"from base\n";
}
};
class anotherClass:public aClassWithPureVirtualFunction
{
public:
void sayHellow()
{
cout<<"from derived \n";
}
};
int main()
{
aClassWithPureVirtualFunction *baseObject=new aClassWithPureVirtualFunction;
baseObject->sayHellow();///call base one
baseObject=new anotherClass;
baseObject->sayHellow();////call the derived one!
}
在基类中,sayHello函数被标记为虚函数。这意味着编译器会尝试在派生类中搜索并实现该函数。如果未找到,则执行基类函数。谢谢。
pure
关键字,但是贝尔实验室即将发布C++的重要版本,在那个时候他的经理不允许添加。增加关键词是一件大事。 - quark