惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Tenable Blog
月光博客
月光博客
雷峰网
雷峰网
WordPress大学
WordPress大学
博客园 - 司徒正美
Last Week in AI
Last Week in AI
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
V
Visual Studio Blog
H
Help Net Security
Engineering at Meta
Engineering at Meta
Google DeepMind News
Google DeepMind News
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
S
Security @ Cisco Blogs
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
爱范儿
爱范儿
W
WeLiveSecurity
J
Java Code Geeks
Forbes - Security
Forbes - Security
H
Hacker News: Front Page
T
Threatpost
The Cloudflare Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
N
Netflix TechBlog - Medium
Latest news
Latest news
V2EX - 技术
V2EX - 技术
小众软件
小众软件
T
The Blog of Author Tim Ferriss
A
Arctic Wolf
B
Blog RSS Feed
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
I
InfoQ
C
Check Point Blog
N
News | PayPal Newsroom
Cyberwarzone
Cyberwarzone
V
V2EX
TaoSecurity Blog
TaoSecurity Blog
P
Privacy & Cybersecurity Law Blog
Microsoft Security Blog
Microsoft Security Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
D
DataBreaches.Net
F
Fortinet All Blogs
阮一峰的网络日志
阮一峰的网络日志
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
IT之家
IT之家
K
Kaspersky official blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Google DeepMind News
Google DeepMind News
C
CXSECURITY Database RSS Feed - CXSecurity.com
www.infosecurity-magazine.com
www.infosecurity-magazine.com

博客园 - 新西兰程序员

C++ 构造函数中的初始化参数列表initializer list C++中的线程thread,以及C++11中的std::atomic学习 Leetcode56 Merge Intervals 合并区间 -- C++实现 C++中的内存对齐 C++中的static关键字使用以及全局变量 C++17中的结构化绑定Structured Binding C++中的函数参数是指针类型, 是按值传递 C++中异常处理机制中的栈展开stack unwinding C++实现链表反转 Linux中的性能分析工具 perf 来分析C++性能 C++17中新建一个类时,编译器默认生成的类成员函数 QT中的元对象系统 Leetcode 114 - 二叉树展开为链表 Leetcode65 有效数字 - 判断给定的字符串是否是一个有效数字 LettCode2289-Steps to Make Array Non-descreasing C#中的委托详解 C++中的std::function C++中的仿函数Functor C++中的const和constexpr异同比较 C++中的左值和右值,以及右值引用,移动语义 C++中传递参数是指针类型以及传入参数是指针的指针(**)详解 C++中GetTickCount函数学习 C++中以类的成员函数作为Windows callback函数需要设置成static函数 C#中的System.Security.SecureString学习 C++中的悬挂指针和野指针 C++中四种不同的对象生存方式(in stack, in heap, global, local static) C#中使用Parallel类来进行多线程并发编程 String类型转LPCTSTR -----理解C++中的字符串类型转换 C++中的虚函数和虚函数表 数据结构 - 栈的学习
C++中基类指针指向派生类对象
新西兰程序员 · 2024-04-02 · via 博客园 - 新西兰程序员

在看深入浅出MFC这本书时,谈到了C++中基类指针指向派生类对象的情况. 先说结论:

如果你以一个【基类之指针】指向派生类对象, 那么经由该指针(这个基类指针)只能够调用基类中所定义的函数,而不能调用派生类中定义而基类中不存在的函数

我们来看个例子

#include <string.h>
class CEmployee
{
private:
char m_name[30];
public:
CEmployee();
CEmployee(const char* em) { strcpy(m_name,em);}
float computePay(){return 0.0;}

}
class CSales : public CEmployee
{
private:
float m_sale;
public:
CSales(const char* nm) : CEmployee(nm) {m_sale = 0.0;}
void setSales(float sale) {m_sale = sale;}
float computePay(){return 100.00;}
}

// 我们现在来看基类指针
CSales testSale("吴棉"); //派生类对象
CEmployee* pEmployee; //基类指针
CSales* pSale; //派生类指针
pSale = &testSale; //派生类指针指向自己的(派生类)对象
pEmployee = &testSale; //基类指针指向派生类对象

pEmployee->setSales(300.0); //这个语句会报错,pEmployee是基类指针,CEmployee中并没有setSales函数
pSale->setSales(300.0); //正确,这里会成功调用CSales的setSales函数

这里要特别注意,也就是说,虽然基类指针pEmployee和派生类指针pSale都指向同一个对象(派生类), 但却因为指针的原始类型不一样而使得两者之间有了差异,我们再看:
pEmployee->computePay(); //调用CEmployee::computePay()
pSale->computePay();//调用CSales::computePay()

这里可以看到,虽然基类指针pEmployee和派生类指针pSale都指向同一个对象(派生类)CSales,但是它们调用的computePay方法时,调用的却是不同的类的方法。到底应该调用哪个函数,必须视指针的原始类型而定,而与指针实际所指的对象无关


结论:

1.  如果你以一个[基类之指针] 指向 [派生类对象], 那么经由该指针你只能调用基类所定义的函数

2. 如果基类和派生类都定义了【相同名称的成员函数】,那么通过对象指针调用该相同名称的成员函数时,到底调的是基类还是派生类中的成员函数,必须由该指针的原始类型决定,而不是由指针实际所指向的对象的类型来定的

显然,现实写代码中经常会出现相反的情况,也就是说我们经常需要一个【基类指针】指向不同的【派生类对象】,但该指针调用的是每个派生类自己的同名函数(这个函数也在基类中存在)

怎么才能实现这个功能么?=》 这里就需要用到虚函数了

 我们需要把这个方法定义成虚函数,这样,就可以做到【基类指针】指向不同的 【派生类对象】时,该基类指针调用的是每个派生类自己的函数,而不是基类中的同名函数

Example:

#include <string.h>

class CEmployee
{
   private:
      char m_name[30];
   public:
      CEmployee();
      CEmployee(const char* em) { strcpy(m_name,em);}
      virtual float computePay() {return 0.00;}
   
}
class CSales : public CEmployee
{
   private:
       float m_sale;
   public:
       CSales(const char* nm) : CEmployee(nm) {m_sale = 0.0;}
       void setSales(float sale) {m_sale = sale;}
       virtual float computePay(return 100.00);
}

class CManager : public CEmployee
{
public:
CManager(const char* nm) : CEmployee(nm) {}
virtual float computePay(return 300.00);
}

// 我们现在来看基类指针 CSales testSale("吴棉"); //派生类对象
CManager testManager("张三"); // 派生类对象

CEmployee* pEmployee; //基类指针 pEmployee = &testSale; //基类指针指向派生类对象CSale pEmployee->computePay(); //调用CSale::computePay(), 这里虽然pEmployee是基类指针,但这里指向的不再是基类CEmployee的函数computePay,而是派生类CSales的函数computePay,原因就是我们把函数computePay定义成了虚函数 pEmployee = &testManager; //基类指针指向派生类对象CManager
pEmployee->computePay();
//调用CManager::computePay(), 这里虽然pEmployee是基类指针,但这里指向的不再是基类CEmployee的函数computePay,而是派生类CManager的函数computePay,原因就是我们把函数computePay定义成了虚函数
 

 上面的代码中, 

pEmployee->computePay(); C++编译器在编译时是无法判断它调用的是基类函数还是派生类函数的,必须在运行时才能评估之,这个我们称为后期联编late binding 或者动态联编 dynamic binding
而如果不是虚函数,比如最开始,computePay是non-virtual函数,那么它在编译时期就会转换为一个固定地址的调用了,这个我们称为 前期联编early binding或静态联编satic binding

纯虚函数和抽象类

上面提到了虚函数,我们来看一下
class CShape
{
    public:
        virtual void display() {}
}

这里CShape是个基类,它本身在现实世界中没有实际意义,也不会存在display这个动作。但是为了在它的派生类比如正方形,圆形,长方形等中绘图显示,各个派生类中需要继承实现这个函数,所以我们不得不在基类CShape中加上这么一个虚函数display, 上面,我们把它定义成了一个空函数,什么都不做. 但其实这样是不高明的,因为这个函数根本就不应该被调用(CSharp是个基类,它在现实世界中没有实际意义,也就不会去调用它的display方法), 所以我们最好是根本就不定义它. 但是我们又必须保留一块空间(spaceholder)给它,也就是说不定义但却需要保留一块空间,C++中怎么做到了 =》 纯虚函数, C++提供了所谓的纯虚函数:

class CShape
{
    public:
        virtual void display() = 0; //注意 "=0"
}

纯虚函数不需要定义其实际动作,它的存在只是为了能够在派生类中被重新定义, 只是为了提供一个多态接口。 

特别注意: 拥有纯虚函数的类,就是一种抽象类,它是不能够被实例化的(instantiate).  也就是说,你不能根据它产生一个对象 (我们不能说去产生一个形状为 "Shape"的物体)

如果你实例化抽象类,会产生如下错误的编译信息

error: illegal attempt to instantiate abstract class

 关于抽象类,有一点要注意: 比如现在我们写一个类CCircle, 继承基类(抽象类) CShape, 如果在CCircle中我们没有去改写CShape中的纯虚函数,那么CCircle本身也就成为了一个拥有纯虚函数的类,于是它也成了抽象类