我正在学习C ++,并且刚开始使用虚函数。
从我阅读的内容(在书中和在线上)中,虚函数是基类中的函数,您可以在派生类中重写这些函数。
但是在本书的前面,当学习基本继承时,我无需使用即可在派生类中覆盖基本函数virtual
。
那我在这里想念什么?我知道虚函数还有很多,这似乎很重要,所以我想弄清楚到底是什么。我只是无法在网上找到直接的答案。
我正在学习C ++,并且刚开始使用虚函数。
从我阅读的内容(在书中和在线上)中,虚函数是基类中的函数,您可以在派生类中重写这些函数。
但是在本书的前面,当学习基本继承时,我无需使用即可在派生类中覆盖基本函数virtual
。
那我在这里想念什么?我知道虚函数还有很多,这似乎很重要,所以我想弄清楚到底是什么。我只是无法在网上找到直接的答案。
Answers:
这是我不仅了解什么virtual
功能,还了解为什么需要它们的方式:
假设您有以下两个类:
class Animal
{
public:
void eat() { std::cout << "I'm eating generic food."; }
};
class Cat : public Animal
{
public:
void eat() { std::cout << "I'm eating a rat."; }
};
在您的主要职能中:
Animal *animal = new Animal;
Cat *cat = new Cat;
animal->eat(); // Outputs: "I'm eating generic food."
cat->eat(); // Outputs: "I'm eating a rat."
到目前为止一切顺利,对吗?动物不吃普通食物,猫不吃老鼠virtual
。
让我们现在对其进行一些更改,以eat()
通过中间函数(仅对于此示例而言是一个琐碎的函数)进行调用:
// This can go at the top of the main.cpp file
void func(Animal *xyz) { xyz->eat(); }
现在我们的主要功能是:
Animal *animal = new Animal;
Cat *cat = new Cat;
func(animal); // Outputs: "I'm eating generic food."
func(cat); // Outputs: "I'm eating generic food."
呃...我们把一只猫送进了func()
,但它不会吃老鼠。您是否应该超载,func()
所以需要一个Cat*
?如果您必须从Animal衍生出更多动物,则它们都需要它们自己的func()
。
解决方案是eat()
从Animal
类中创建一个虚函数:
class Animal
{
public:
virtual void eat() { std::cout << "I'm eating generic food."; }
};
class Cat : public Animal
{
public:
void eat() { std::cout << "I'm eating a rat."; }
};
主要:
func(animal); // Outputs: "I'm eating generic food."
func(cat); // Outputs: "I'm eating a rat."
做完了
virtual
引入了动态绑定与静态绑定,是的,如果您来自Java之类的语言,这很奇怪。
没有“虚拟”,您将获得“早期约束力”。使用哪种方法的实现是在编译时根据调用的指针类型决定的。
使用“虚拟”,您将获得“后期绑定”。在运行时,将根据所指向对象的类型(最初将其构造为何种对象)来决定使用哪种方法。根据指向该对象的指针的类型,这不一定是您想要的。
class Base
{
public:
void Method1 () { std::cout << "Base::Method1" << std::endl; }
virtual void Method2 () { std::cout << "Base::Method2" << std::endl; }
};
class Derived : public Base
{
public:
void Method1 () { std::cout << "Derived::Method1" << std::endl; }
void Method2 () { std::cout << "Derived::Method2" << std::endl; }
};
Base* obj = new Derived ();
// Note - constructed as Derived, but pointer stored as Base*
obj->Method1 (); // Prints "Base::Method1"
obj->Method2 (); // Prints "Derived::Method2"
编辑 -看到这个问题。
另外- 本教程介绍了C ++中的早期和晚期绑定。
main
函数指针等对衍生隐式强制转换为指针到基地(更专业的内容隐式转换为更一般的内容)。反之亦然,您需要一个明确的强制转换,通常是dynamic_cast
。其他-非常容易发生不确定的行为,因此请确保您知道自己在做什么。据我所知,这甚至在C ++ 98之前都没有改变。
您需要至少1级继承和一个低级继承来演示它。这是一个非常简单的示例:
class Animal
{
public:
// turn the following virtual modifier on/off to see what happens
//virtual
std::string Says() { return "?"; }
};
class Dog: public Animal
{
public: std::string Says() { return "Woof"; }
};
void test()
{
Dog* d = new Dog();
Animal* a = d; // refer to Dog instance with Animal pointer
std::cout << d->Says(); // always Woof
std::cout << a->Says(); // Woof or ?, depends on virtual
}
您需要使用虚拟方法来进行安全向下转换,简化和简洁。
这就是虚拟方法的作用:它们使用显然简单明了的代码安全地向下转换,避免了您本来会更复杂和冗长的代码中不安全的手动转换。
以下代码是有意的“不正确”。它没有将value
方法声明为virtual
,因此会产生意外的“错误”结果,即0:
#include <iostream>
using namespace std;
class Expression
{
public:
auto value() const
-> double
{ return 0.0; } // This should never be invoked, really.
};
class Number
: public Expression
{
private:
double number_;
public:
auto value() const
-> double
{ return number_; } // This is OK.
Number( double const number )
: Expression()
, number_( number )
{}
};
class Sum
: public Expression
{
private:
Expression const* a_;
Expression const* b_;
public:
auto value() const
-> double
{ return a_->value() + b_->value(); } // Uhm, bad! Very bad!
Sum( Expression const* const a, Expression const* const b )
: Expression()
, a_( a )
, b_( b )
{}
};
auto main() -> int
{
Number const a( 3.14 );
Number const b( 2.72 );
Number const c( 1.0 );
Sum const sum_ab( &a, &b );
Sum const sum( &sum_ab, &c );
cout << sum.value() << endl;
}
在注释为“坏”的行中,该Expression::value
方法被调用,因为静态已知类型(在编译时已知的类型)为Expression
,并且该value
方法不是虚拟的。
声明value
为virtual
静态已知类型Expression
可确保每次调用都将检查该对象的实际类型,并value
为该动态类型调用相关的实现:
#include <iostream>
using namespace std;
class Expression
{
public:
virtual
auto value() const -> double
= 0;
};
class Number
: public Expression
{
private:
double number_;
public:
auto value() const -> double
override
{ return number_; }
Number( double const number )
: Expression()
, number_( number )
{}
};
class Sum
: public Expression
{
private:
Expression const* a_;
Expression const* b_;
public:
auto value() const -> double
override
{ return a_->value() + b_->value(); } // Dynamic binding, OK!
Sum( Expression const* const a, Expression const* const b )
: Expression()
, a_( a )
, b_( b )
{}
};
auto main() -> int
{
Number const a( 3.14 );
Number const b( 2.72 );
Number const c( 1.0 );
Sum const sum_ab( &a, &b );
Sum const sum( &sum_ab, &c );
cout << sum.value() << endl;
}
这里的输出是6.86
应有的,因为虚拟方法被虚拟地调用。这也称为调用的动态绑定。进行一些检查,找到对象的实际动态类型,然后调用该动态类型的相关方法实现。
相关的实现是最具体(最派生)的类中的一种。
请注意,此处未标记派生类中的方法实现,virtual
而是标记了override
。它们可以被标记,virtual
但是它们是自动虚拟的。该override
关键字确保如果有没有在一些基础类这样的虚拟方法,那么你会得到一个错误(这是可取的)。
没有virtual
人就必须实现动态绑定的“ 自己动手做”版本。通常,这就是不安全的手动下降,复杂性和冗长性。
对于单个函数,如此处所示,将函数指针存储在对象中并通过该函数指针进行调用就足够了,但是即使如此,它仍然涉及一些不安全的贬低,复杂性和冗长性:
#include <iostream>
using namespace std;
class Expression
{
protected:
typedef auto Value_func( Expression const* ) -> double;
Value_func* value_func_;
public:
auto value() const
-> double
{ return value_func_( this ); }
Expression(): value_func_( nullptr ) {} // Like a pure virtual.
};
class Number
: public Expression
{
private:
double number_;
static
auto specific_value_func( Expression const* expr )
-> double
{ return static_cast<Number const*>( expr )->number_; }
public:
Number( double const number )
: Expression()
, number_( number )
{ value_func_ = &Number::specific_value_func; }
};
class Sum
: public Expression
{
private:
Expression const* a_;
Expression const* b_;
static
auto specific_value_func( Expression const* expr )
-> double
{
auto const p_self = static_cast<Sum const*>( expr );
return p_self->a_->value() + p_self->b_->value();
}
public:
Sum( Expression const* const a, Expression const* const b )
: Expression()
, a_( a )
, b_( b )
{ value_func_ = &Sum::specific_value_func; }
};
auto main() -> int
{
Number const a( 3.14 );
Number const b( 2.72 );
Number const c( 1.0 );
Sum const sum_ab( &a, &b );
Sum const sum( &sum_ab, &c );
cout << sum.value() << endl;
}
一种看待这种情况的积极方法是,如果遇到上述不安全的向下转换,复杂性和冗长性,那么通常使用一种或多种虚拟方法确实可以提供帮助。
虚函数用于支持运行时多态性。
也就是说,virtual关键字告诉编译器不要在编译时决定(函数绑定),而是将其推迟到运行时。”
您可以通过virtual
在基类声明中的关键字前面使函数虚拟化。例如,
class Base
{
virtual void func();
}
当基类具有虚拟成员函数时,从基类继承的任何类都可以使用完全相同的原型重新定义该函数,即只能重新定义功能,而不能重新定义该函数的接口。
class Derive : public Base
{
void func();
}
基类指针可用于指向基类对象以及派生类对象。
如果基类是Base
,派生类是Der
,您可以拥有一个Base *p
实际指向实例的指针Der
。当你打电话p->foo();
,如果foo
是不虚,那么Base
的的版本,它执行,无视事实,p
实际上指向Der
。如果foo 是虚拟的,则p->foo()
在foo
充分考虑指向项的实际类的情况下执行的“最末尾”覆盖。因此,虚拟与非虚拟之间的区别实际上非常关键:前者允许运行时多态,即OO编程的核心概念,而后者则不允许。
需要虚拟功能的说明[易于理解]
#include<iostream>
using namespace std;
class A{
public:
void show(){
cout << " Hello from Class A";
}
};
class B :public A{
public:
void show(){
cout << " Hello from Class B";
}
};
int main(){
A *a1 = new B; // Create a base class pointer and assign address of derived object.
a1->show();
}
输出将是:
Hello from Class A.
但是具有虚函数:
#include<iostream>
using namespace std;
class A{
public:
virtual void show(){
cout << " Hello from Class A";
}
};
class B :public A{
public:
virtual void show(){
cout << " Hello from Class B";
}
};
int main(){
A *a1 = new B;
a1->show();
}
输出将是:
Hello from Class B.
因此,使用虚函数可以实现运行时多态。
我想添加虚拟函数的另一种用法,尽管它使用与上述答案相同的概念,但我想值得一提。
虚拟销毁器
考虑下面的程序,而不将基类的析构函数声明为虚函数;Cat的内存可能无法清除。
class Animal {
public:
~Animal() {
cout << "Deleting an Animal" << endl;
}
};
class Cat:public Animal {
public:
~Cat() {
cout << "Deleting an Animal name Cat" << endl;
}
};
int main() {
Animal *a = new Cat();
delete a;
return 0;
}
输出:
Deleting an Animal
class Animal {
public:
virtual ~Animal() {
cout << "Deleting an Animal" << endl;
}
};
class Cat:public Animal {
public:
~Cat(){
cout << "Deleting an Animal name Cat" << endl;
}
};
int main() {
Animal *a = new Cat();
delete a;
return 0;
}
输出:
Deleting an Animal name Cat Deleting an Animal
without declaring Base class destructor as virtual; memory for Cat may not be cleaned up.
比那更糟。通过基本指针/引用删除派生对象是纯粹的未定义行为。因此,不仅仅是一些内存可能泄漏。而是,程序的格式不正确,因此编译器可能会将其转换为任何内容:碰巧可以正常工作,什么都不做的机器代码,从鼻子上召唤恶魔的机器等等。这就是为什么如果程序设计成这样一种某些用户可以通过基本引用删除派生实例的方式,该基本对象必须具有虚拟析构函数
您必须区分重载和重载。如果没有virtual
关键字,则只能重载基类的方法。这只不过意味着躲藏。假设您有两个都实现的基类Base
和派生类。现在,您有一个指向的实例的指针。调用它时,您可以观察到不同之处:如果该方法是虚拟的,则将使用的实现,如果缺少该方法,则将选择from的版本。最佳实践是永远不要从基类重载方法。使方法成为非虚拟方法是其作者告诉您的方法,即不打算在子类中对其进行扩展。Specialized
void foo()
Base
Specialized
foo()
virtual
Specialized
Base
virtual
你就不会超载。你在影子。如果基类B
具有一个或多个函数foo
,并且派生类D
定义了一个foo
名称,则将所有这些-s foo
隐藏foo
在中B
。它们是B::foo
使用范围解析来达到的。要将B::foo
功能提升D
为重载,必须使用using B::foo
。
为什么我们需要C ++中的虚拟方法?
在Bjarne Stroustrup C ++编程:原理和实践(14.3)中:
虚函数提供了在基类中定义函数并在用户调用基类函数时在派生类中具有相同名称和类型的函数的功能。这通常被称为运行时多态性,动态调度或运行时调度,因为所调用的函数是在运行时根据所使用对象的类型确定的。
为了处理虚拟呼叫,需要一个或多个与派生对象 3有关的数据。通常要做的方法是添加函数表的地址。该表通常称为虚拟表或虚拟功能表,其地址通常称为虚拟指针。每个虚拟函数在虚拟表中都有一个插槽。根据调用者的对象(派生的)类型,虚拟函数依次调用相应的覆盖。
1.继承,运行时多态性和封装的使用是面向对象编程的最常见定义。
2.在运行时,您不能使用其他语言功能对功能进行编码,使其速度更快或占用更少的内存。Bjarne Stroustrup C ++编程:原理和实践。(14.3.1)。
3.当我们调用包含虚函数的基类时,可以说出哪个函数真正被调用。
我以对话的形式得到了更好的阅读答案:
为什么我们需要虚拟功能?
由于多态性。
什么是多态?
基本指针也可以指向派生类型对象的事实。
这种对多态的定义如何导致对虚函数的需求?
好吧,通过早期绑定。
什么是早期绑定?
C ++中的早期绑定(编译时绑定)意味着在执行程序之前已修复函数调用。
所以...?
因此,如果将基本类型用作函数的参数,则编译器将仅识别基本接口,并且如果使用派生类中的任何参数调用该函数,它将被切掉,这不是您想要的。
如果这不是我们想要发生的事情,为什么允许这样做?
因为我们需要多态!
那么,多态性有什么好处呢?
您可以将基本类型指针用作单个函数的参数,然后在程序的运行时,可以使用该单个引用取消访问每个派生类型接口(例如其成员函数)而没有任何问题。基本指针。
我仍然不知道虚拟函数对...有什么好处!这是我的第一个问题!
好吧,这是因为您过早提出了您的问题!
为什么我们需要虚拟功能?
假设您使用基本指针调用了一个函数,该指针具有其派生类之一中对象的地址。正如我们在上面讨论的那样,在运行时,该指针已被取消引用,到目前为止,到目前为止,我们仍然希望执行“来自派生类”的方法(==成员函数)!但是,在基类中已经定义了相同的方法(一个具有相同的标头),那么为什么您的程序要费心选择另一个方法?换句话说,您如何将这种情况与我们以前通常看到的情况区分开来?
简短的答案是“在基类中有一个虚拟成员函数”,而更长的答案是,“在这一步,如果程序在基类中看到了一个虚函数,它就知道(意识到)您正在尝试使用多态”,然后转到派生类(使用v-table,这是后期绑定的一种形式)来找到另一种方法具有相同标头,但预期实现方式不同的方法。
为什么要采用不同的实现方式?
你的指关节!去读一本好书!
OK,等等,等等,为什么当他/她可以简单地使用派生类型指针时,为什么还要麻烦使用基本指针呢?您是法官,这一切值得头痛吗?查看以下两个片段:
// 1:
Parent* p1 = &boy;
p1 -> task();
Parent* p2 = &girl;
p2 -> task();
// 2:
Boy* p1 = &boy;
p1 -> task();
Girl* p2 = &girl;
p2 -> task();
好的,尽管我认为1仍然比2好,但是您可以写1:
// 1:
Parent* p1 = &boy;
p1 -> task();
p1 = &girl;
p1 -> task();
而且,您应该知道,这只是到目前为止我已经向您解释的所有内容的虚构用法。取而代之的是,例如,假设您的程序中有一个函数分别使用每个派生类中的方法(getMonthBenefit())的情况:
double totalMonthBenefit = 0;
std::vector<CentralShop*> mainShop = { &shop1, &shop2, &shop3, &shop4, &shop5, &shop6};
for(CentralShop* x : mainShop){
totalMonthBenefit += x -> getMonthBenefit();
}
现在,尝试重新编写此内容,不要头疼!
double totalMonthBenefit=0;
Shop1* branch1 = &shop1;
Shop2* branch2 = &shop2;
Shop3* branch3 = &shop3;
Shop4* branch4 = &shop4;
Shop5* branch5 = &shop5;
Shop6* branch6 = &shop6;
totalMonthBenefit += branch1 -> getMonthBenefit();
totalMonthBenefit += branch2 -> getMonthBenefit();
totalMonthBenefit += branch3 -> getMonthBenefit();
totalMonthBenefit += branch4 -> getMonthBenefit();
totalMonthBenefit += branch5 -> getMonthBenefit();
totalMonthBenefit += branch6 -> getMonthBenefit();
实际上,这可能也是一个人为的例子!
当您在基类中有一个函数时,可以在派生类中Redefine
或Override
它中进行操作。
重新定义方法:派生类中提供了基类方法的新实现。不便利Dynamic binding
。
覆盖方法:
派生类中基类的Redefining
avirtual method
。虚方法促进了动态绑定。
所以当你说:
但是在本书的前面,当学习基本继承时,我能够在不使用“虚拟”的情况下覆盖派生类中的基本方法。
您没有覆盖它,因为基类中的方法不是虚拟的,而是您在重新定义它
如果您了解潜在的机制,它将很有帮助。C ++规范了C程序员使用的一些编码技术,用“覆盖”代替了“类”-具有公共头段的结构将用于处理不同类型但具有一些公共数据或操作的对象。通常,覆盖图的基本结构(公共部分)具有指向功能表的指针,该功能表针对每种对象类型指向一组不同的例程。C ++做同样的事情,但隐藏了机制,即C ++ ptr->func(...)
中func是虚拟的,就像C一样。(*ptr->func_table[func_num])(ptr,...)
,其中派生类之间的变化是func_table的内容。[非虚拟方法ptr-> func()仅转换为mangled_func(ptr,..)。]
这样做的结果是,您只需要了解基类即可调用派生类的方法,即,如果例程了解类A,则可以将其传递给派生类B指针,则所调用的虚拟方法将是那些B而不是A的值,因为您浏览了功能表B所指向的位置。
关键字virtual告诉编译器不应执行早期绑定。相反,它应该自动安装执行后期绑定所需的所有机制。为此,典型的编译器1为每个包含虚拟函数的类创建一个表(称为VTABLE)。编译器将该特定类的虚拟函数的地址放入VTABLE中。在每个具有虚函数的类中,它秘密地放置一个称为vpointer(缩写为VPTR)的指针,该指针指向该对象的VTABLE。当您通过基类指针进行虚拟函数调用时,编译器会悄悄地插入代码以获取VPTR,并在VTABLE中查找函数地址,从而调用正确的函数并导致后期绑定。
此链接中的更多详细信息 http://cplusplusinterviews.blogspot.sg/2015/04/virtual-mechanism.html
所述虚拟关键字强制编译器来接在所限定的方法实现对象的类,而不是在指针的类。
Shape *shape = new Triangle();
cout << shape->getName();
在上面的示例中,默认情况下将调用Shape :: getName,除非在基类Shape中将getName()定义为虚拟的。这迫使编译器在Triangle类而不是Shape类中寻找getName()实现。
该虚拟表是其中编译器跟踪子类的各种虚拟-方法实现的机制。这也被称为动态调度,并有是与它相关的一些开销。
最后,为什么在C ++中甚至需要虚拟,为什么不使其成为Java中的默认行为呢?
为什么我们需要虚拟功能?
虚拟函数避免了不必要的类型转换问题,当我们可以使用派生类指针来调用派生类中特定的函数时,我们中的某些人可能会争论为什么需要虚拟函数!答案是-它使大型系统中的整个继承概念无效开发中,非常需要具有单个指针基类对象的对象。
让我们比较下面两个简单的程序,以了解虚拟功能的重要性:
没有虚拟功能的程序:
#include <iostream>
using namespace std;
class father
{
public: void get_age() {cout << "Fathers age is 50 years" << endl;}
};
class son: public father
{
public : void get_age() { cout << "son`s age is 26 years" << endl;}
};
int main(){
father *p_father = new father;
son *p_son = new son;
p_father->get_age();
p_father = p_son;
p_father->get_age();
p_son->get_age();
return 0;
}
输出:
Fathers age is 50 years
Fathers age is 50 years
son`s age is 26 years
具有虚拟功能的程序:
#include <iostream>
using namespace std;
class father
{
public:
virtual void get_age() {cout << "Fathers age is 50 years" << endl;}
};
class son: public father
{
public : void get_age() { cout << "son`s age is 26 years" << endl;}
};
int main(){
father *p_father = new father;
son *p_son = new son;
p_father->get_age();
p_father = p_son;
p_father->get_age();
p_son->get_age();
return 0;
}
输出:
Fathers age is 50 years
son`s age is 26 years
son`s age is 26 years
通过仔细分析两个输出,可以了解虚拟功能的重要性。
在C ++中,需要虚拟方法来实现多态,更准确地说是子类型化或子类型多态如果应用Wikipedia的定义。
维基百科,子类型,2019年1月9日:在编程语言理论中,子类型(也称为子类型多态或包含多态)是类型多态的一种形式,其中子类型是与某种概念相关的另一数据类型(超类型)相关的数据类型。可替换性的含义是指被编写为对超类型的元素进行操作的程序元素(通常是子例程或函数)也可以对子类型的元素进行操作。
注意:子类型表示基类,子类型表示继承的类。
如果您有一个指向基类的指针,则该方法的调用(被声明为virtual)将被分派到所创建对象的实际类的方法中。这就是C ++实现子类型多态的方法。
对于方法上的每个“虚拟”修饰符,C ++编译器通常在声明该方法的类的vtable中创建一个条目。这就是常见的C ++编译器如何实现动态调度。
范例程式码
#include <iostream>
using namespace std;
class Animal {
public:
virtual void MakeTypicalNoise() = 0; // no implementation needed, for abstract classes
virtual ~Animal(){};
};
class Cat : public Animal {
public:
virtual void MakeTypicalNoise()
{
cout << "Meow!" << endl;
}
};
class Dog : public Animal {
public:
virtual void MakeTypicalNoise() { // needs to be virtual, if subtype polymorphism is also needed for Dogs
cout << "Woof!" << endl;
}
};
class Doberman : public Dog {
public:
virtual void MakeTypicalNoise() {
cout << "Woo, woo, woow!";
cout << " ... ";
Dog::MakeTypicalNoise();
}
};
int main() {
Animal* apObject[] = { new Cat(), new Dog(), new Doberman() };
const int cnAnimals = sizeof(apObject)/sizeof(Animal*);
for ( int i = 0; i < cnAnimals; i++ ) {
apObject[i]->MakeTypicalNoise();
}
for ( int i = 0; i < cnAnimals; i++ ) {
delete apObject[i];
}
return 0;
}
示例代码的输出
Meow!
Woof!
Woo, woo, woow! ... Woof!
UML类图的代码示例
这是一个完整的示例,说明了为什么使用虚拟方法。
#include <iostream>
using namespace std;
class Basic
{
public:
virtual void Test1()
{
cout << "Test1 from Basic." << endl;
}
virtual ~Basic(){};
};
class VariantA : public Basic
{
public:
void Test1()
{
cout << "Test1 from VariantA." << endl;
}
};
class VariantB : public Basic
{
public:
void Test1()
{
cout << "Test1 from VariantB." << endl;
}
};
int main()
{
Basic *object;
VariantA *vobjectA = new VariantA();
VariantB *vobjectB = new VariantB();
object=(Basic *) vobjectA;
object->Test1();
object=(Basic *) vobjectB;
object->Test1();
delete vobjectA;
delete vobjectB;
return 0;
}
关于效率,虚拟功能的效率略低于早期绑定功能。
“这种虚拟调用机制几乎可以与“常规函数调用”机制一样高效(25%之内。它的空间开销是具有虚拟功能的类的每个对象中的一个指针,每个此类类具有一个vtbl” [ A Bjarne Stroustrup 的C ++之行 ]
if(param1>param2) return cst;
在某些情况下编译器可以将整个函数调用减少为常量)。
界面设计中使用虚拟方法。例如,在Windows中,有一个名为IUnknown的界面,如下所示:
interface IUnknown {
virtual HRESULT QueryInterface (REFIID riid, void **ppvObject) = 0;
virtual ULONG AddRef () = 0;
virtual ULONG Release () = 0;
};
这些方法留给接口用户来实现。它们对于创建和销毁某些必须继承IUnknown的对象至关重要。在这种情况下,运行时会意识到这三种方法,并希望在调用它们时将其实现。因此,从某种意义上说,它们充当对象本身与使用该对象的对象之间的契约。
the run-time is aware of the three methods and expects them to be implemented
由于它们是纯虚拟的,因此无法创建的实例IUnknown
,因此所有子类都必须实现所有此类方法才能进行编译。没有实现它们而不只是在运行时发现它们的危险(当然,显然可以错误地实现它们!)。哇,今天,我#define
用单词来学习Windows sa宏interface
,大概是因为他们的用户不能(A)看到I
名称中的前缀或(B)看到类以查看它是一个接口。gh
这是前两个答案的C ++代码的合并版本。
#include <iostream>
#include <string>
using namespace std;
class Animal
{
public:
#ifdef VIRTUAL
virtual string says() { return "??"; }
#else
string says() { return "??"; }
#endif
};
class Dog: public Animal
{
public:
string says() { return "woof"; }
};
string func(Animal *a)
{
return a->says();
}
int main()
{
Animal *a = new Animal();
Dog *d = new Dog();
Animal *ad = d;
cout << "Animal a says\t\t" << a->says() << endl;
cout << "Dog d says\t\t" << d->says() << endl;
cout << "Animal dog ad says\t" << ad->says() << endl;
cout << "func(a) :\t\t" << func(a) << endl;
cout << "func(d) :\t\t" << func(d) << endl;
cout << "func(ad):\t\t" << func(ad)<< endl;
}
两种不同的结果是:
如果没有#define virtual,它将在编译时绑定。Animal * ad和func(Animal *)都指向Animal的say()方法。
$ g++ virtual.cpp -o virtual
$ ./virtual
Animal a says ??
Dog d says woof
Animal dog ad says ??
func(a) : ??
func(d) : ??
func(ad): ??
使用#define virtual,它将在运行时绑定。Dog * d,Animal * ad和func(Animal *)指向/引用Dog的say()方法,因为Dog是它们的对象类型。除非未定义[Dog's say()“ woof”]方法,否则它将是在类树中首先搜索的方法,即派生类可能会覆盖其基类[Animal's say()]的方法。
$ g++ virtual.cpp -D VIRTUAL -o virtual
$ ./virtual
Animal a says ??
Dog d says woof
Animal dog ad says woof
func(a) : ??
func(d) : woof
func(ad): woof
有趣的是,Python中的所有类属性(数据和方法)实际上都是虚拟的。由于所有对象都是在运行时动态创建的,因此不需要类型声明或关键字virtual。以下是Python版本的代码:
class Animal:
def says(self):
return "??"
class Dog(Animal):
def says(self):
return "woof"
def func(a):
return a.says()
if __name__ == "__main__":
a = Animal()
d = Dog()
ad = d # dynamic typing by assignment
print("Animal a says\t\t{}".format(a.says()))
print("Dog d says\t\t{}".format(d.says()))
print("Animal dog ad says\t{}".format(ad.says()))
print("func(a) :\t\t{}".format(func(a)))
print("func(d) :\t\t{}".format(func(d)))
print("func(ad):\t\t{}".format(func(ad)))
输出为:
Animal a says ??
Dog d says woof
Animal dog ad says woof
func(a) : ??
func(d) : woof
func(ad): woof
与C ++的虚拟定义相同。请注意,d和ad是引用/指向同一Dog实例的两个不同的指针变量。表达式(ad是d)返回True,并且它们的值是相同的< main .Dog object at 0xb79f72cc>。
最重要的是,虚拟功能使生活更轻松。让我们使用M Perry的一些想法,并描述如果我们没有虚拟函数而只能使用成员函数指针,将会发生什么情况。在没有虚函数的正常估计中,我们有:
class base {
public:
void helloWorld() { std::cout << "Hello World!"; }
};
class derived: public base {
public:
void helloWorld() { std::cout << "Greetings World!"; }
};
int main () {
base hwOne;
derived hwTwo = new derived();
base->helloWorld(); //prints "Hello World!"
derived->helloWorld(); //prints "Hello World!"
好的,这就是我们所知道的。现在,让我们尝试使用成员函数指针做到这一点:
#include <iostream>
using namespace std;
class base {
public:
void helloWorld() { std::cout << "Hello World!"; }
};
class derived : public base {
public:
void displayHWDerived(void(derived::*hwbase)()) { (this->*hwbase)(); }
void(derived::*hwBase)();
void helloWorld() { std::cout << "Greetings World!"; }
};
int main()
{
base* b = new base(); //Create base object
b->helloWorld(); // Hello World!
void(derived::*hwBase)() = &derived::helloWorld; //create derived member
function pointer to base function
derived* d = new derived(); //Create derived object.
d->displayHWDerived(hwBase); //Greetings World!
char ch;
cin >> ch;
}
尽管我们可以使用成员函数指针来做一些事情,但是它们不像虚函数那样灵活。在类中使用成员函数指针是很棘手的。至少在我的实践中,几乎总是必须在主函数中或从成员函数内部调用成员函数指针,如上例所示。
另一方面,虚拟函数虽然可能会有一些函数指针开销,但确实可以大大简化事情。
编辑:还有另一种方法与eddietree类似:c ++虚拟函数与成员函数指针(性能比较)。