首先给出一段代码:
class MainFrameWnd :public WindowImplBase{
public:
static const LPCTSTR kClassName;
static const LPCTSTR kMainWndFrame;
public:
MainFrameWnd();
protected:
virtual CDuiString GetSkinFolder() override; //获取皮肤文件的目录,如果有多层目录,可以在这里设置
virtual CDuiString GetSkinFile() override; //获取皮肤文件xml的名字
virtual LPCTSTR GetWindowClassName(void) const override; //获取窗口的名字 就是类名 class name
virtual DuiLib::UILIB_RESOURCETYPE GetResourceType() const override; //获取资源的类型,打包的zip资源
virtual LPCTSTR GetResourceID() const override; //如果是zip资源,返回资源id
virtual void InitWindow() override; //窗口初始化函数
virtual void Notify(TNotifyUI& msg) override; //通知时间处理函数
virtual LRESULT OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); //关闭进程函数
virtual LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) override;
protected:
CButtonUI* min_btn_;
CButtonUI* max_btn_;
CButtonUI* close_btn_;
CButtonUI* restore_btn_;
CButtonUI* mul_btn_;
//增加一个list列表的控件
CListUI* list_ui_;
CDialogBuilder builder_;
SecondFrameWnd* second_frame_wnd_;
};
窗口子类MainFrameWnd继承WindowImplBase基类,并且重写了以上函数。首先了解C++中的多态。
在C++中,当派生类重写(override)了基类的成员函数时,基类对象调用基类成员函数时,不会调用派生类重写的成员函数。它将始终调用基类的成员函数。
静态绑定:这是因为在C++中,函数调用的绑定(Binding)是静态的,也就是说在编译时就已经确定了。当基类的成员函数被调用时,编译器会根据调用对象的静态类型(也就是声明类型)来确定调用哪个函数,而不是根据运行时对象的实际类型。
下面是一个示例来说明这一点:
#include
class Base {
public:
void print() {
std::cout << "Base::print()" << std::endl;
}
};
class Derived : public Base {
public:
void print() {
std::cout << "Derived::print()" << std::endl;
}
};
int main() {
Base baseObj;
Derived derivedObj;
Base* ptr1 = &baseObj;
Base* ptr2 = &derivedObj;
ptr1->print(); // Output: Base::print()
ptr2->print(); // Output: Base::print()
return 0;
}
在上面的例子中,派生类Derived重写了基类Base的成员函数print。然后我们创建了一个基类对象baseObj和一个派生类对象derivedObj。接着,我们用两个不同的、基类指针ptr1和ptr2分别指向这两个对象。
当我们调用ptr1->print()时,由于ptr1的静态类型是Base*,编译器会选择调用基类Base的print函数,输出是**“Base::print()”**。
当我们调用ptr2->print()时,由于ptr2的静态类型是Base*,同样编译器会选择调用基类Base的print函数,输出仍然是**“Base::print()”,而不是派生类Derived的print**函数。
如果你希望基类对象调用派生类的重写函数,需要将基类的成员函数声明为virtual,这样会启用运行时多态性,使得函数调用在运行时动态绑定到正确的函数。
在C++中,如果派生类重写了基类的虚拟成员函数(即在基类中声明为virtual的成员函数),当基类对象通过指针或引用调用基类的成员函数时,将会调用派生类重写的成员函数,而不是基类的成员函数。
这是因为虚函数机制(virtual function mechanism)在运行时实现了动态绑定(dynamic binding)或后期绑定(late binding)。当基类的成员函数声明为虚拟函数时,编译器将生成用于动态调度的虚函数表(virtual function table),用于在运行时确定要调用的正确函数。
下面是一个示例来说明这一点:
#include
class Base {
public:
virtual void print() {
std::cout << "Base::print()" << std::endl;
}
};
class Derived : public Base {
public:
void print() override {
std::cout << "Derived::print()" << std::endl;
}
};
int main() {
Base baseObj;
Derived derivedObj;
Base* ptr1 = &baseObj;
Base* ptr2 = &derivedObj;
ptr1->print(); // Output: Base::print()
ptr2->print(); // Output: Derived::print()
return 0;
}
在上述示例中,基类Base的成员函数print被声明为虚函数。派生类Derived重写了该虚函数。
当我们通过指针ptr1调用ptr1->print()时,由于ptr1的静态类型是Base*,但它指向了基类对象,所以在运行时会调用基类的虚函数Base::print()。
当我们通过指针ptr2调用ptr2->print()时,由于ptr2的静态类型是Base*,但它指向了派生类对象,所以在运行时会调用派生类的重写函数Derived::print()。
因此,在这种情况下,基类调用基类的成员函数时,如果派生类重写了该函数并且该函数被声明为虚函数,将会调用派生类的成员函数。这就是C++中的运行时多态性的体现。
在C++中,多态性(polymorphism)是面向对象编程的一个重要特性。它允许使用基类的指针或引用来引用派生类的对象,并根据实际对象类型调用正确的成员函数。多态性通过动态绑定(dynamic binding)和静态绑定(static binding)来实现。
1、静态绑定(Static Binding):
静态绑定发生在编译时,根据指针或引用的静态类型(声明类型)来确定调用哪个成员函数。在静态绑定中,成员函数的调用是根据编译器在编译时期就决定的,不会考虑实际对象的类型。静态绑定适用于非虚函数和静态函数。
示例代码:
#include
class Base {
public:
void print() {
std::cout << "Base::print()" << std::endl;
}
};
class Derived : public Base {
public:
void print() {
std::cout << "Derived::print()" << std::endl;
}
};
int main() {
Base baseObj;
Derived derivedObj;
Base* ptr1 = &baseObj;
Base* ptr2 = &derivedObj;
ptr1->print(); // Output: Base::print()
ptr2->print(); // Output: Base::print()
return 0;
}
在上面的例子中,基类Base和派生类Derived都有一个名为print的成员函数。当通过基类指针ptr1和ptr2调用print函数时,由于静态类型(声明类型)都是Base*,所以在编译时决定调用的是基类的print函数。因此,静态绑定会选择根据指针或引用的静态类型来调用相应的函数。
示例代码:
#include
class Base {
public:
virtual void print() {
std::cout << "Base::print()" << std::endl;
}
};
class Derived : public Base {
public:
void print() override {
std::cout << "Derived::print()" << std::endl;
}
};
int main() {
Base baseObj;
Derived derivedObj;
Base* ptr1 = &baseObj;
Base* ptr2 = &derivedObj;
ptr1->print(); // Output: Base::print()
ptr2->print(); // Output: Derived::print()
return 0;
}
在上述示例中,基类Base和派生类Derived都有一个名为print的成员函数,并且在基类中将该函数声明为虚函数。当通过基类指针ptr1和ptr2调用print函数时,由于虚函数的特性,虚函数表中的指定根据指针指向的实际对象类型来确定调用的是基类的虚函数还是派生类的重写函数。 因此,在运行时会根据对象的实际类型进行动态绑定,选择调用相应的函数。
综上所述,动态绑定和静态绑定是多态性的两个关键概念。动态绑定通过虚函数实现,在运行时根据对象的实际类型进行函数调用;静态绑定则是根据指针或引用的静态类型在编译时决定函数调用。多态性的使用可以提高代码的灵活性和可扩展性。
在**C++**中,实现多态性的条件是通过使用虚函数(Virtual Function)和基类指针或引用来引用派生类对象。多态性在面向对象编程中是一种重要的特性,它允许在运行时动态地选择调用适当的成员函数,以提供更灵活和可扩展的代码。
为了实现多态性,必须满足以下两个条件:
class Base {
public:
virtual void print() {
// ...
}
};
Base* ptr = new Derived(); // 或者 Base& ref = derivedObj;
ptr->print(); // 调用派生类中的重写函数
在C++11及其后续标准中,override是一个用于修饰虚函数的关键字。它的作用是明确表示一个函数是对基类中的虚函数的重写(覆盖)。使用override关键字可以帮助开发者在编码过程中更清晰地表达意图,提高代码的可读性和可维护性。
主要作用如下:
明确重写:在派生类中重写基类的虚函数时,使用override关键字可以明确告诉编译器,这个函数是有意重写基类的虚函数。这样一来,如果由于拼写错误或函数签名不匹配等原因导致重写失败,编译器会给出错误提示,帮助开发者及时发现潜在问题。
编译时检查:使用override关键字后,编译器会在编译时检查派生类中的函数是否真正重写了基类的虚函数。如果没有成功重写,编译器会报错,避免运行时的意外行为。
文档和维护:在代码中使用override关键字,可以提供清晰的文档说明,表明某个函数是意图重写虚函数。这样,其他开发者在阅读代码时能够更好地理解程序设计意图,有助于代码的维护和后续开发。
使用示例:
class Base {
public:
virtual void foo() {
// ...
}
};
class Derived : public Base {
public:
void foo() override { // 使用override关键字明确重写虚函数
// ...
}
};
在上述示例中,派生类Derived使用了override关键字来重写基类Base中的虚函数foo()。这样一来,编译器会在编译时检查是否成功重写,并在需要时给出相应的错误提示。使用override关键字可以提高代码的可靠性和可维护性,是良好的**C++**编程实践。
在DuiLib中,子类窗口重写了基类窗口WindowImplBase的成员函数时,子类窗口的成员函数不需要子类直接调用也能运行的原因是,DuiLib使用了虚函数和消息映射来实现窗口的消息分发和处理。
虚函数:
在DuiLib中,WindowImplBase中的成员函数经常被声明为虚函数,例如InitWindow、HandleMessage、Notify等。当子类窗口重写这些虚函数时,它们会自动成为虚函数,并实现了动态绑定。这意味着在运行时,根据实际对象的类型来调用正确的成员函数,而不是根据指针或引用的静态类型。因此,DuiLib会在适当的时候调用子类窗口重写的成员函数,无需子类直接调用。
消息映射:
DuiLib使用消息映射(Message Mapping)来将不同的消息映射到相应的成员函数。当窗口接收到消息时,DuiLib会根据消息映射表来查找对应的成员函数,并自动调用。子类窗口重写基类的成员函数,实际上是将子类的函数添加到消息映射表中,这样就能够在接收到对应消息时自动调用子类的成员函数。
综合起来,DuiLib中子类窗口重写基类窗口的成员函数之所以不需要子类直接调用也能运行,是因为使用了虚函数和消息映射的机制。 虚函数保证了在运行时根据实际对象类型调用正确的成员函数,消息映射则负责在窗口接收到消息时自动调用相应的成员函数,使得子类窗口的重写函数能够被正确地触发和执行。
在DuiLib中,子类窗口重写了基类窗口WindowImplBase的成员函数时,这些重写的成员函数会在特定的时机被调用。以下是一些常见的情况和对应的调用时机:
InitWindow:
子类重写的InitWindow函数在窗口初始化时被调用。在这个函数中,你可以进行窗口的初始化设置,例如设置窗口的标题、大小、样式等。
HandleMessage:
子类重写的HandleMessage函数在窗口接收到消息时被调用。DuiLib的消息循环会调用窗口的HandleMessage函数来处理各种消息,包括窗口的创建、大小调整、鼠标事件、键盘事件等。你可以根据消息的类型和参数在这个函数中编写相应的处理逻辑。
Notify:
子类重写的Notify函数在窗口接收到通知消息时被调用。通知消息是由窗口内的控件生成的,用于通知窗口发生的事件,例如按钮点击、菜单选择等。在Notify函数中,你可以根据通知消息的类型和参数执行相应的操作。
OnTimer:
子类重写的OnTimer函数在窗口的定时器事件触发时被调用。你可以在这个函数中实现定时器事件的处理逻辑,例如更新窗口内容、执行定时任务等。
需要注意的是,以上只是一些常见的情况,具体调用时机还取决于窗口的使用方式和触发条件。在DuiLib中,窗口的生命周期由框架控制,它会根据窗口的状态和用户操作来调用相应的成员函数。在子类窗口中重写基类窗口的成员函数,可以通过在适当的时机提供自定义的处理逻辑,以满足特定的业务需求。
虚函数和虚函数表是实现C++中动态多态性(Dynamic Polymorphism)的关键概念。
class Base {
public:
virtual void func() {
// Base class implementation
}
};
class Derived : public Base {
public:
void func() override {
// Derived class implementation
}
};
当一个类包含虚函数时,编译器会在对象的内存布局中添加一个指向虚函数表的指针,通常称为虚指针(vptr)。这个虚指针指向类的虚函数表,使得在运行时能够动态绑定到正确的虚函数。
在派生类中重写虚函数时,虚函数表会被更新,指向派生类的重写函数。这样在运行时,通过虚指针找到正确的虚函数表,并调用相应的函数。
注意:虚函数表的具体实现是由编译器和操作系统决定的,不同的编译器和平台可能会有不同的实现方式,但其原理和功能是相同的。
虚函数和虚函数表是C++中实现动态多态性的核心机制。它们使得在运行时根据对象的实际类型动态绑定到正确的函数,实现了多态性的特性。这样可以提高代码的灵活性和可扩展性,使得派生类能够根据自己的需求重写基类的虚函数,并在运行时调用正确的重写函数。