460
社区成员
![](https://csdnimg.cn/release/cmsfe/public/img/topic.427195d5.png)
![](https://csdnimg.cn/release/cmsfe/public/img/me.40a70ab0.png)
![](https://csdnimg.cn/release/cmsfe/public/img/task.87b52881.png)
![](https://csdnimg.cn/release/cmsfe/public/img/share-circle.3e0b7822.png)
我是荔园微风,作为一名在IT界整整25年的老兵,今天针对 Visual C++中的虚函数和纯虚函数的原理来聊聊。本文程序全部在Microsoft Visual Studio 2022上调试通过。
首先,先满足一下急性子的同学,因为有的同学是因为急于了解虚函数和纯虚函数才来看这篇帖子的,那你可以先这样理解:
这就是本人的学习方法和别人不一样的地方,我年轻时学C++始终不得要领,于是我把JAVA学会后再去学C++,就全都搞明白了。所以我的帖子都是用这种方法去学C++的。希望那些学C++很吃力的同学可以来借鉴我的这个学习方法。
好了,我们先来看一个程序,一个父亲和一个儿子,希望父亲的学习本领能被儿子继承下去。我们在儿子类中重新定义学习方法。我们希望如果对象是儿子,就调用 儿子类的学习方法,如果对象是父亲,那么就调用父亲类的学习方法。
#include <iostream>
using namespace std;
class Father
{
public:
void eat()
{
cout<<"eat"<<endl;
}
void run()
{
cout<<"run"<<endl;
}
void study()
{
cout<<"study"<<endl;
}
};
class Son : public Father
{
public:
void study()
{
cout<<"new study"<<endl;
}
};
void fn(Father *p)
{
p->study();
}
int main()
{
Father *p;
Son boy;
p=&boy;
fn(p);
return 0;
}
我们在儿子类中重新定义了study()方法,上一代人可能喜欢用做笔记的方法来学习,而新一代人总是有新的学习方法的吧,比如在设计好的教学游戏中学习,哈哈。接着定义了一个全局函数fn(),指向父亲类的指针作为 fn()函数的参数。在main()函数中,定义了一个儿子类的对象,将它的地址赋给了指向父亲类的指针变量 p,然后调用 fn()函数。当我们将儿子类的对象boy的地址直接赋给指向父亲类的指针变量,C+编译器居然不报错。这是因为儿子对象也是一个父亲对象,将儿子类型转换为父亲类型不用强制类型转换,C++编译器会自动进行这种转换。反过来,则不能把父亲对象看成是儿子对象,如果一个父亲对象确实是儿子对象,那么在程序中需要进行强制类型转换,这样编译才不会报错。
大家可以猜想一下上面程序运行的结果,输出的结果是“study”,为什么输出的结果不是“new study”呢?这是因为在我们将儿子类的对象fh的地址赋给p时,C++编译器进行了类型转换,此时C++编译器认为变量p保存的就是父亲对象的地址。当在 fn函数中执行p->study()时,调用的当然就是父亲对象的study函数。为了帮助大家更好地理解对象类型的转换,我们给出了儿子对象内存模型,如图所示:
当我们将儿子类的对象转换为父亲类型时,该对象就被认为是原对象整个内存模型的上半部分,也就是上图中的“父亲的对象所占内存”。当我们利用型转换后的对象指针去调用它的方法时,自然也就是调用它所在的内存中的方法。
现在我们在父亲类的study()方法前面加上一个virtual关键字,
#include <iostream>
using namespace std;
class Father
{
public:
void eat()
{
cout<<"eat"<<endl;
}
void run()
{
cout<<"run"<<endl;
}
virtual void study()
{
cout<<"study"<<endl;
}
};
class Son : public Father
{
public:
void study()
{
cout<<"new study"<<endl;
}
};
void fn(Father *p)
{
p->study();
}
int main()
{
Father *p;
Son boy;
p=&boy;
fn(p);
return 0;
}
用virtual关键字申明的函数叫作虚函数。运行这个程序,结果调用的是儿子类的学习方法。程序输出结果是“new study”。
这就是C++中的多态性。当C++编译器在编译的时候,发现父亲类的study()函数是虚函数,这个时候C++就会采用迟绑定(latebinding)技术。也就是编译时并不确定具体调用的函数,而是在运行时,依据对象的类型(在程序中,我们传递的儿子类对象的地址)来确认调用的是哪一个函数,这种能力就叫作C++的多态性。我们没有在study()函数前加virtual关键字时,C++编译器在编译时就确定了哪个函数被调用,这叫作早期绑定(early binding)。
下面我们将study()函数申明为纯虚函数,会了发生什么事呢?
#include <iostream>
using namespace std;
class Father
{
public:
void eat()
{
cout<<"eat"<<endl;
}
void run()
{
cout<<"run"<<endl;
}
virtual void study()=0;
};
class Son : public Father
{
public:
void study()
{
cout<<"new study"<<endl;
}
};
void fn(Father *p)
{
p->study();
}
int main()
{
Father *p;
Son boy;
p=&boy;
fn(p);
return 0;
}
纯虚函数是指被标明为不具体实现的虚成员函数(注意:纯虚函数也可以有函数体,但这种提供函数体的用法很少见)。纯虚函数可以让类先具有一个操作名称,而没有操作内容,让派生类在继承时再去具体地给出定义。含有纯虚函数的类叫作抽象类,这种类不能声明对象,只是作为基类为派生类服务。在派生类中必须完全实现基类的纯虚函数,否则,派生类也变成了抽象类,不能实例化对象。
文章来源: https://blog.csdn.net/wang2015cn/article/details/131426986
版权声明: 本文为博主原创文章,遵循CC 4.0 BY-SA 知识共享协议,转载请附上原文出处链接和本声明。