目錄
什么是多态
1、对象类型
2、静态多态与动态多态
静态多态
动态多态
什么是虚函数
析构函数与虚函数
静态类型函数与虚函数
友元函数与虚函数
内联成员函数与虚函数
析构函数总结:
总结:
首頁 後端開發 C#.Net教程 靜態多態與動態多型態以及虛函數相關

靜態多態與動態多型態以及虛函數相關

Apr 19, 2017 am 11:30 AM

什么是多态

从字面上理解就是多种形态的意思。而多态一词最初源自希腊语,其含义便是“多种形式”,意思是是具有多种形式或形态的情形,在C++语言中多态有着更广泛的含义。在C++ primer一书中把具有继承关系的多个类型称为多态类型,因为我们能使用这些类型的“多种形式”而无须在意它们的差异。百度百科上提到在面向对象语言中,接口的多种不同的实现方式即为多态。引用Charlie Calverts对多态的描述——多态性是允许你将父对象设置成为一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。多态性在Object Pascal和C++中都是通过虚函数实现的。
只从概念描述是无法深刻清晰的理解它的,下面我们就具体分析一下。

1、对象类型

这里所说的对象类型可以用下面的图来体现:我们通过代码举例说明一下


 1 class Derived1:public Base 2 {}; 3 class Derived2:public Base 4 {}: 5 int main() 6 { 7      Derived1* p1 = new Derived1; 8      Base = p1; 9      Derived2* p2 = new Derived1;10      Base = p2;11      return = p212 }
登入後複製

静态类型在编译时总是已知的,他是变量声明时的类型或表达式生成的类型;动态类型则是变量或表达式表示的内存中的对象的类型。动态类型直到运行时才可知道。

2、静态多态与动态多态

静态多态和动态多态的区别其实用下面的图就可以体现: 

静态多态

静态多态也称为静态绑定或早绑。编译器在编译期间完成的, 编译器根据函数实参的类型(可能会进行隐式类型转换) , 可推断出要调用那个函数, 如果有对应的函数就调用该函数, 否则出现编译错误。


 1 int Add(int left,int right) 2 { 3      return left + right; 4 } 5 float Add(float left, float right) 6 { 7      return left + right; 8 } 9 int main()10 {   
11     cout<<Add(1,2)<<endl;  //调用int Add()函数12     cout<<Add(1.34f,3.21f)<<endl; //调float Add()函数13     return 0;14 }
登入後複製

这里我把重载归为了静态多态。重载的实现是:编译器根据函数不同的参数表,对同名函数的名称做修饰,然后这些同名函数就成了不同的函数(至少对于编译器来说是这样的)。函数的调用,在编译器间就已经确定了,是静态的(记住:是静态)。也就是说,它们的地址在编译期就绑定了(早绑定)。正是由于重载的这种性质,也有结论认为:重载只是一种语言特性,与多态无关,与面向对象也无关。基于面向对象来说重载的概念并不属于“面向对象编程”。但是多态性又是一个比较广泛的概念。这里为了便于理解先不去深度分析它们的区别,等多态的其它部分具体分析完毕,我们再来说。

动态多态

动态绑定: 在程序执行期间(非编译期) 判断所引 用对象的实际类型, 根据其实际类型调用相应的方法。使用virtual关键字修饰类的成员 函数时, 指明该函数为虚函数, 派生类需要重新实现, 编译器将实现动态绑定。


              FunTest1(         cout <<  <<       FunTest2(         cout <<  <<        FunTest3(         cout <<  <<        FunTest4(         cout <<  <<    CDerived :              FunTest1(         cout <<  <<        FunTest2(         cout <<  <<       FunTest3( _iTest1) { cout <<  <<        FunTest4( _iTest1,          cout <<  <<        CBase* pBase =      pBase->FunTest1(     pBase->FunTest2(     pBase->FunTest3(     pBase->FunTest4(        }
登入後複製

当我们使用基类的指针或引用调用基类中定义的一个函数时时,我们并不知道该函数真正的对象是什么类型,因为他可能是一个基类的对象,也可能是一个派生类的对象。如果该函数是虚函数,则直到运行时才会知道到及执行哪个版本,判断的依据是引用或指针所绑定的对象的真实类型。

析构函数、静态类型函数、友元函数、内联函数与虚函数


        CTest&  =( CTest&       *     fri end voi d FunTestFri end() ;
12 }
登入後複製

什么是虚函数

其实在前面的虚拟继承中我们已经用到了虚函数这个概念,在那里我们是为了解决菱形普通继承中访问二义性的问题,但在多态中,他有更大的作用。百度百科中对虚函数是这么说的:在某基类中声明为 virtual 并在一个或多个派生类中被重新定义的成员函数,用法格式为:virtual 函数返回类型 函数名(参数表) {函数体};实现多态性,通过指向派生类的基类指针或引用,访问派生类中同名覆盖成员函数。形象的解释为“求同存异”,它的作用就是实现多态性。

简单地说,那些被virtual关键字修饰的成员函数,就是虚函数。虚函数的作用,用专业术语来解释就是实现多态性(Polymorphism),多态性是将接口与实现进行分离;用形象的语言来解释就是实现以共同的方法,但因个体差异,而采用不同的策略。


 1 class A 2 { 3     public: 4         virtual void print(){cout<<"This is A"<<endl;} 5 }; 6 class B : public A 7 { 8     public: 9     void print(){cout<<"ThisisB"<<endl;}10 };11 int main()12 {13     A a;14     B b;15     A *p1 = &a;16     A *p2 = &b;17     p1->print();18     p2->print();19     return 0;20 }
登入後複製

毫无疑问,class A的成员函数print()已经成了虚函数,那么class B的print()成了虚函数了吗?回答是Yes,我们只需在把基类的成员函数设为virtual,其派生类的相应的函数也会自动变为虚函数。所以,class B的print()也成了虚函数。那么对于在派生类的相应函数前是否需要用virtual关键字修饰,那就是你自己的问题了(语法上可加可不加,不加的话编译器会自动加上,但为了阅读方便和规范性,建议加上)。

运行代码,输出的结果是This is A和This is B。

总结:指向基类的指针在操作它的多态类对象时,会根据不同的类对象,调用其相应的函数,这个函数就是虚函数。

析构函数与虚函数

当在析构函数前面加virtual关键字时报错:,我们来分析一下原因。

1、虚函数的执行依赖于虚函数表。而虚函数表在构造函数中进行初始化工作,即初始化vptr,让他指向正确的虚函数表。而在构造对象期间,虚函数表还没有被初 始化,将无法进行。

2、构造一个对象的时候,必须知道对象的实际类型,而虚函数行为是在运行期间确定实际类型的。而在构造一个对象时,由于对象还未构造成功。编译器无法知道对象 的实际类型,是该类本身,还是该类的一个派生类,或是更深层次的派生类。无法确定。

虚函数的意思就是开启动态绑定,程序会根据对象的动态类型来选择要调用的方法。然而在构造函数运行的时候,这个对象的动态类型还不完整,没有办法确定它到底是什么类型,故构造函数不能动态绑定。(动态绑定是根据对象的动态类型而不是函数名,在调用构造函数之前,这个对象根本就不存在,它怎么动态绑定?)
编译器在调用基类的构造函数的时候并不知道你要构造的是一个基类的对象还是一个派生类的对象。

静态类型函数与虚函数

当我们在静态类型函数前加virtual关键字时报错:分析:

1、 static成员不属于任何类对象或类实例,所以即使给此函数加上virutal也是没有任何意义的。

2、static函数没有this指针,并且不会进入虚函数表的。当通过指针或者引用调用时根本无法把this指针传递给static函数,从而无法体现出多态。静态成员函数与普通成员函数的差别就在于缺少this指针,没有这个this指针自然也就无从知道name是哪一个对象的成员了。

友元函数与虚函数

当我们在友元函数前加virtual关键字时报错:

 因为C++不支持友元函数的继承,对于没有继承特性的函数没有虚函数的说法。

内联成员函数与虚函数

内联函数就是为了在代码中直接展开,减少函数调用花费的代价,虚函数是为了在继承后对象能够准确的执行自己的动作,这是不可能统一的。(再说了,inline函数在编译时被展开,虚函数在运行时才能动态的邦定函数)

赋值运算符的重载与虚函数

当我们把赋值运算符的重载定义为虚函数时编译可以通过,但是一般不建议这么做虽然可以将operator=定义为虚函数, 但使用时容易混淆。

1、无法给派生类的自有成员赋值;

2、调用虚函数要进行查虚表等一系列操作,效率下降。

析构函数与虚函数

析构函数设为虚函数的作用:在类的继承中,如果有基类指针指向派生类,那么用基类指针delete时,如果不定义成虚函数,派生类中派生的那部分无法析构。


 1 #include <stdafx.h> 2 #include <stdio.h> 3 class A 4 { 5     public: 6     A(); 7     virtual~A(); 8 }; 9     A::A()10     {}11     A::~A()12     {13     printf("Delete class APn");14     }15 class B : public A16 {17     public:18     B();19     ~B();20 };21     B::B()22     { }23     B::~B()24     {25     printf("Delete class BPn");26     }27 int main(int argc, char* argv[])28 {29     A *b=new B;30     delete b;31     return 0;32 }
登入後複製

输出结果为:Delete class B     Delete class A
如果把A的virtual去掉:那就变成了Delete class A也就是说不会删除派生类里的剩余部分内容,也即不调用派生类的虚函数

析构函数总结:

1. 如果我们定义了一个构造函数,编译器就不会再为我们生成默认构造函数了。
2. 编译器生成的析构函数是非虚的,除非是一个子类,其父类有个虚析构,此时的函数虚特性来自父类。
3. 有虚函数的类,几乎可以确定要有个虚析构函数。
4. 如果一个类不可能是基类就不要申明析构函数为虚函数,虚函数是要耗费空间的。
5. 析构函数的异常退出会导致析构不完全,从而有内存泄露。最好是提供一个管理类,在管理类中提供一个方法来析构,调用者再根据这个方法的结果决定下一步的操作。
6. 在构造函数不要调用虚函数。在基类构造的时候,虚函数是非虚,不会走到派生类中,既是采用的静态绑定。显然的是:当我们构造一个子类的对象时,先调用基类的构造函数,构造子类中基类部分,子类还没有构造,还没有初始化,如果在基类的构造中调用虚函数,如果可以的话就是调用一个还没有被初始化的对象,那是很危险的,所以C++中是不可以在构造父类对象部分的时候调用子类的虚函数实现。但是不是说你不可以那么写程序,你这么写,编译器也不会报错。只是你如果这么写的话编译器不会给你调用子类的实现,而是还是调用基类的实现。
7.在析构函数中也不要调用虚函数。在析构的时候会首先调用子类的析构函数,析构掉对象中的子类部分,然后在调用基类的析构函数析构基类部分,如果在基类的析构函数里面调用虚函数,会导致其调用已经析构了的子类对象里面的函数,这是非常危险的。
8. 记得在写派生类的拷贝函数时,调用基类的拷贝函数拷贝基类的部分。

总结:

1、 派生类重写基类的虚函数实现多态, 要求函数名 、 参数列表、 返回值完全相同。 (协变除外)。

2、 基类中定义了 虚函数, 在派生类中该函数始终保持虚函数的特性。

3、 只 有类的非静态成员 函数才能定义为虚函数, 静态成员 函数不能定义为虚函数。

4、 如果在类外定义虚函数, 只 能在声明函数时加virtual关键字, 定义时不用加。

5、 构造函数不能定义为虚函数, 虽然可以将operator=定义为虚函数, 但最好不要这么做, 使用时容易混淆。

6、 不要在构造函数和析构函数中调用虚函数, 在构造函数和析构函数中, 对象是不完整的, 可能会出现未定义的行为。

 7、 最好将基类的析构函数声明为虚函数。 ( 因为派生类的析构函数跟基类的析构函数名称不一样, 但是构成覆盖, 这里编译器做了特殊处理)

8、 虚表是所有类对象实例共用的。

以上是靜態多態與動態多型態以及虛函數相關的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

C++虛擬函數表與多態實現,如何避免記憶體浪費 C++虛擬函數表與多態實現,如何避免記憶體浪費 May 31, 2024 pm 07:03 PM

虛基底類別可最佳化虛擬函數表記憶體開銷,透過允許從多個基底類別繼承而無需建立額外虛函數表。在最佳化後程式碼中,形狀基底類別不再有虛擬函數表,圓和矩形類別共享同一個虛擬函數表,從而減少了記憶體消耗。

深入解析C語言中static關鍵字的作用與用法 深入解析C語言中static關鍵字的作用與用法 Feb 20, 2024 pm 04:30 PM

深入解析C語言中static關鍵字的功能和用法在C語言中,static是一種非常重要的關鍵字,它可以被用於函數、變數和資料類型的定義。使用static關鍵字可以改變物件的連結屬性、作用域和生命週期,以下就來詳細解析一下static關鍵字在C語言中的作用和用法。 static變數與函數:在函數內部使用static關鍵字定義的變數稱為靜態變量,它具有全域生命週

MySQL中如何實作資料的多態儲存與多維查詢? MySQL中如何實作資料的多態儲存與多維查詢? Jul 31, 2023 pm 09:12 PM

MySQL中如何實作資料的多態儲存與多維查詢?在實際應用開發中,資料的多態儲存和多維查詢是一個非常常見的需求。 MySQL作為常用的關聯式資料庫管理系統,提供了多種實作多態儲存和多維查詢的方式。本文將介紹使用MySQL實作資料的多態儲存和多維查詢的方法,並提供對應的程式碼範例,幫助讀者快速了解和使用。一、多態儲存多態儲存是指將不同類型的資料儲存在同一個欄位中的技

如何在PHP中使用多態性和繼承來處理資料類型 如何在PHP中使用多態性和繼承來處理資料類型 Jul 15, 2023 pm 07:41 PM

如何在PHP中使用多態性和繼承來處理資料類型引言:在PHP中,多型和繼承是兩個重要的物件導向程式設計(OOP)概念。透過使用多型和繼承,我們可以更靈活地處理不同的資料類型。本文將介紹如何在PHP中使用多態性和繼承來處理資料類型,並透過程式碼範例展示它們的實際應用。一、繼承的基本概念繼承是物件導向程式設計中的重要概念,它允許我們建立一個類,該類別可以繼承父類別的屬性和方法

繼承、多型與介面:PHP物件導向的三大特性 繼承、多型與介面:PHP物件導向的三大特性 May 11, 2023 pm 03:45 PM

PHP是一種伺服器端程式語言,自PHP5之後開始支援物件導向程式設計(OOP)。 OOP的核心思想是將資料和行為封裝在物件中,以提高程式的可維護性和可擴展性。在PHP中,物件導向程式設計具有三大特性:繼承、多型與介面。一、繼承繼承是指一個類別可以從另一個類別繼承屬性和方法。被繼承的類別稱為父類別或基底類,繼承的類別稱為子類別或衍生類別。子類別可以透過繼承來獲得父類別中的屬性和方法,並且可

'PHP物件導向程式設計入門:從概念到實踐” 'PHP物件導向程式設計入門:從概念到實踐” Feb 25, 2024 pm 09:04 PM

什麼是物件導向程式設計?物件導向程式設計(OOP)是一種程式設計範式,它將現實世界中的實體抽象化為類,並使用物件來表示這些實體。類別定義了物件的屬性和行為,而物件則實例化了類別。 OOP的主要優點在於它可以使程式碼更易於理解、維護和重複使用。 OOP的基本概念OOP的主要概念包括類別、物件、屬性和方法。類別是物件的藍圖,它定義了物件的屬性和行為。物件是類別的實例,它具有類別的所有屬性和行為。屬性是物件的特徵,它可以儲存資料。方法是物件的函數,它可以對物件的資料進行操作。 OOP的優點OOP的主要優點包括:可重複使用性:OOP可以讓程式碼更

PHP中私有靜態方法的作用及應用場景 PHP中私有靜態方法的作用及應用場景 Mar 23, 2024 am 10:18 AM

PHP中私有靜態方法的作用及應用場景在PHP程式設計中,私有靜態方法是一種特殊的方法類型,它只能在定義它的類別內部訪問,外部無法直接呼叫。私有靜態方法通常用於類別的內部邏輯實現,提供了一種封裝和隱藏細節的方式,同時又具有靜態方法的特性,可以在不實例化類別物件的情況下被呼叫。以下將探討私有靜態方法的作用及應用場景,並提供具體的程式碼範例。作用:封裝與隱藏實作細節:私有靜態

PHP中的多型與派發機制的關係 PHP中的多型與派發機制的關係 Jul 07, 2023 pm 05:45 PM

PHP中的多型態與派發機制的關係在物件導向程式設計中,多型是一種強大的概念,它允許不同的物件對同一訊息做出不同的回應。 PHP作為一門強大的開發語言,也支援多態性,與之緊密相關的是派發機制。本文將透過程式碼範例來探討PHP中的多態與派發機制的關係。首先,我們來了解什麼是多態。多態是指物件能夠根據自己的實際型別來呼叫對應的方法。透過使用多態,程式可以根據具體對象

See all articles