我正在学习C++,我刚刚开始学习虚拟函数。
从我(在书中和网上)读到的内容来看,虚拟函数是基类中的函数,可以在派生类中重写。
但在本书的早些时候,当我学习基本继承时,我能够在派生类中重写基函数,而不使用虚函数。
那么我在这里错过了什么?我知道虚拟函数还有很多,它似乎很重要,所以我想清楚它到底是什么。我只是在网上找不到一个直截了当的答案。
我正在学习C++,我刚刚开始学习虚拟函数。
从我(在书中和网上)读到的内容来看,虚拟函数是基类中的函数,可以在派生类中重写。
但在本书的早些时候,当我学习基本继承时,我能够在派生类中重写基函数,而不使用虚函数。
那么我在这里错过了什么?我知道虚拟函数还有很多,它似乎很重要,所以我想清楚它到底是什么。我只是在网上找不到一个直截了当的答案。
当前回答
虚拟函数用于支持运行时多态性。
也就是说,virtual关键字告诉编译器不要在编译时做出(函数绑定的)决定,而是推迟到运行时”。
您可以通过在函数的基类声明中的关键字virtual之前使其成为虚拟函数。例如类基础{虚虚函数();}当基类具有虚拟成员函数时,从基类继承的任何类都可以使用完全相同的原型重新定义该函数,即只能重新定义功能,而不能重新定义函数的接口。类派生:公共基{void函数();}基类指针可用于指向基类对象和派生类对象。当使用基类指针调用虚拟函数时,编译器在运行时决定要调用函数的哪个版本(即基类版本或重写的派生类版本)。这被称为运行时多态性。
其他回答
我想添加虚拟函数的另一种用法,尽管它使用了与上述答案相同的概念,但我认为它值得一提。
虚拟析构函数
考虑下面的这个程序,不要将基类析构函数声明为virtual;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;
}
输出:
删除动物
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;
}
输出:
删除动物名称猫删除动物
虚拟函数用于支持运行时多态性。
也就是说,virtual关键字告诉编译器不要在编译时做出(函数绑定的)决定,而是推迟到运行时”。
您可以通过在函数的基类声明中的关键字virtual之前使其成为虚拟函数。例如类基础{虚虚函数();}当基类具有虚拟成员函数时,从基类继承的任何类都可以使用完全相同的原型重新定义该函数,即只能重新定义功能,而不能重新定义函数的接口。类派生:公共基{void函数();}基类指针可用于指向基类对象和派生类对象。当使用基类指针调用虚拟函数时,编译器在运行时决定要调用函数的哪个版本(即基类版本或重写的派生类版本)。这被称为运行时多态性。
为什么我们需要C++中的虚拟方法?
快速回答:
它为我们提供了面向对象编程所需的“要素”之一。
在Bjarne Stroustrup C++编程:原理与实践中,(14.3):
虚拟函数提供了在基类中定义函数的能力,并在用户调用基类函数时在派生类中具有相同名称和类型的函数。这通常称为运行时多态性、动态调度或运行时调度,因为调用的函数是在运行时根据所使用的对象类型确定的。
如果您需要虚拟函数调用2,这是最快、更有效的实现。
为了处理虚拟调用,需要一条或多条与派生对象3相关的数据。通常的做法是添加函数表的地址。该表通常称为虚拟表或虚拟函数表,其地址通常称为虚指针。每个虚拟函数在虚拟表中都有一个槽。根据调用者的对象(派生)类型,虚拟函数依次调用相应的重写。
1.使用继承、运行时多态性和封装是面向对象编程的最常见定义。
2.您不能在运行时使用其他语言功能在备选方案中进行选择,从而使功能更快或使用更少的内存。Bjarne Stroustrup C++编程:原理与实践。(14.3.1).
3.当我们调用包含虚拟函数的基类时,可以判断哪个函数真正被调用。
底线是,虚拟功能使生活更轻松。让我们使用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;
}
虽然我们可以用成员函数指针做一些事情,但它们不如虚拟函数灵活。在类中使用成员函数指针是很棘手的;至少在我的实践中,成员函数指针几乎总是必须在主函数中或从成员函数中调用,如上面的示例所示。
另一方面,虚拟函数虽然可能有一些函数指针开销,但确实大大简化了事情。
EDIT:还有一种方法与eddietree类似:c++虚拟函数与成员函数指针(性能比较)。
当基类中有函数时,可以在派生类中重新定义或重写它。
重新定义方法:派生类中给出了基类方法的新实现。不便于动态绑定。
重写方法:在派生类中重新定义基类的虚拟方法。虚拟方法有助于动态绑定。
所以当你说:
但在书的早些时候,当我了解基本遗传时能够重写派生类中的基方法,而不使用“虚拟”。
因为基类中的方法不是虚拟的,所以您不是在重写它,而是在重新定义它