• 深入浅出MFC 笔记一 三个基本原则 不及格的程序员


    晴天霹雳

    我们渐渐接触问题的核心。上述C++ 性质使真实生活经验的确在计算机语言中仿真了出 来,但是万里无云的日子里却出现了一个晴天霹雳:

    如果你以一个「基础类别之指针」 指向一个「衍生类别之对象」,那么经由此指针,你就只能够调用基础类别(而不是衍 生类别)所定义的函数。

    因此:

    CSales aSales("侯俊杰")
    CSales* pSales; CWage* pWager; pSales = &aSales; pWager = &aSales;

    // 以「基础类别之指针」指向「衍生类别之对象」

    pWager->setSales(800.0); // 错误(编译器会检测出来),

    // 因为CWage 并没有定义setSales 函数。

    pSales->setSales(800.0); // 正确,

    调用CSales::setSales 函数。 虽然pSales 和pWager 指向同一个对象,但却因指针的原始类型而使两者之间有了差异。

    延续此例,我们看另一种情况:

    pWager->computePay(); // 调用

    CWage::computePay()

    pSales->computePay(); // 调用CSales::computePay()

    虽然pSales 和pWager 实际上都指向CSales 对象,但是两者调用的computePay 却不相同。到底调用到哪个函数,必须视指针的原始类型而定,与指针实际所指之对象无关。

    三个结论

    我们得到了三个结论:

    1. 如果你以一个「基础类别之指针」指向「衍生类别之对象」,那么经由该指针 你只能够调用基础类别所定义的函数。

    2. 如果你以一个「衍生类别之指针」指向一个「基础类别之对象」,你必须先做 明显的转型动作(explicit cast)。这种作法很危险,不符合真实生活经验,在 程序设计上也会带给程序员困惑。

    3. 如果基础类别和衍生类别都定义了「相同名称之成员函数」,那么透过对象指针调用成员函数时,到底调用到哪一个函数,必须视该指针的原始型别而定, 而不是视指针实际所指之对象的型别而定。这与第1点其实意义相通。

    CBase* pBase;

    CDerived* pDeri;

    不论你把这两个指针指向何方,由于它们的原始类型, 使它们在调用同名的CommFunc() 时有着无可改变的宿命:

    • pBase->CommFunc() 永远是指 CBase::CommFunc

    • pDeri->CommFunc() 永远是指 CDerived::CommFunc


    Object slicing 与虚拟函数 我要在这里说明虚拟函数另一个极重要的行为模式。假设有三个类别,阶层关系如下:

    CObject        virtual void Serialize()

    CDocument virtual void Serialize()

    CMyDoc   virtual void Serialize()

    以程序表现如下:
    #0001 #include <iostream.h>
    #0002
    #0003 class CObject
    #0004 {
    #0005 public:
    #0006 virtual void Serialize() { cout << "CObject::Serialize() \n\n"; }
    #0007 };
    #0008
    #0009 class CDocument : public CObject
    #0010 {
    #0011 public:
    #0012 int m_data1;
    #0013 void func() { cout << "CDocument::func()" << endl;
    #0014 Serialize();
    #0015 }
    #0016
    #0017 virtual void Serialize() { cout << "CDocument::Serialize() \n\n"; }
    #0018 };
    #0019
    #0020 class CMyDoc : public CDocument
    #0021 {
    #0022 public:
    #0023 int m_data2;
    #0024 virtual void Serialize() { cout << "CMyDoc::Serialize() \n\n"; }
    #0025 };
    #0026 //---------------------------------------------------------------
    #0027 void main()
    #0028 {
    #0029 CMyDoc mydoc;
    #0030 CMyDoc* pmydoc = new CMyDoc;
    #0031
    #0032 cout << "#1 testing" << endl;
    #0033 mydoc.func();
    #0034
    #0035 cout << "#2 testing" << endl;
    #0036 ((CDocument*)(&mydoc))->func();
    #0037
    #0038 cout << "#3 testing" << endl;
    #0039 pmydoc->func();
    #0040
    #0041 cout << "#4 testing" << endl;
    #0042 ((CDocument)mydoc).func();
    #0043 }

    由于CMyDoc 自己没有func 函数,而它继承了CDocument 的所有成员,所以main 之中 的四个调用动作毫无问题都是调用CDocument::func。

    但,CDocument::func 中所调用的 Serialize 是哪一个类别的成员函数呢?如果它是一般(non-virtual)函数,毫无问题应该 是CDocument::Serialize。

    但因为这是个虚拟函数,情况便有不同。

    以下是执行结果:

    #1 testing  //mydoc.func();

    CDocument::func()

    CMyDoc::Serialize()

    #2 testing //((CDocument*)(&mydoc))->func();

    CDocument::func()

    CMyDoc::Serialize()

    #3 testing //pmydoc->func();

    CDocument::func()

    CMyDoc::Serialize()

    #4 testing    //((CDocument)mydoc).func();

    CDocument::func()

    CDocument::Serialize() <-- 注意

    前三个测试都符合我们对虚拟函数的期望:既然衍生类别已经改写了虚拟函数Serialize, 那么理当调用衍生类别之Serialize 函数。

    这种行为模式非常频繁地出现在application framework 身上。后续当我追踪MFC 源代码时,遇此情况会再次提醒你。

    第四项测试结果则有点出乎意料之外。你知道,衍生对象通常都比基础对象大(我是指 内存空间),因为衍生对象不但继承其基础类别的成员,又有自己的成员。

    那么所谓 的upcasting(向上强制转型): (CDocument)mydoc,将会造成对象的内容被切割(object slicing):

    当我们调用: ((CDocument)mydoc).func(); mydoc 已经是一个被切割得剩下半条命的对象,而func 内部调用虚拟函数Serialize;后 者将使用的「mydoc 的虚拟函数指针」虽然存在,它的值是什么呢?你是不是隐隐觉得 有什么大灾难要发生? 幸运的是,由于((CDocument)mydoc).func() 是个传值而非传址动作,编译器以所谓 的拷贝构造式(copy constructor)把CDocument 对象内容复制了一份,使得mydoc 的 vtable 内容与CDocument 对象的vtable 相同。本例虽没有明显做出一个拷贝构造式, 编译器会自动为你合成一个。 说这么多,总结就是,经过所谓的data slicing,本例的mydoc 真正变成了一个完完全全

  • 相关阅读:
    LinkedList源码解析
    HashMap源码解析
    HashMap和Hashtable区别
    arcgis api for js 之网络分析服务发布
    arcgis api for js 之发布要素服务
    arcis api for js 值 3.17 本地部署
    ArcGIS 产品体系结构
    layui select下拉框选项不显示
    windows10企业版2016长期服务版激活
    PHP常见的输出语句
  • 原文地址:https://www.cnblogs.com/ioriwellings/p/16022411.html
Copyright © 2020-2023  润新知