Einführung in die grundlegenden virtuellen Funktionsklassen im Zusammenhang mit Vererbung und Polymorphismus in C++

巴扎黑
Freigeben: 2017-09-11 11:28:47
Original
1395 Leute haben es durchsucht

Dieser Artikel führt Sie hauptsächlich in die grundlegenden virtuellen Funktionsklassen der Vererbung und des Polymorphismus ein. Der Artikel stellt sie anhand von Beispielcodes ausführlich vor Freunde, bitte folgen Sie dem Herausgeber, um gemeinsam zu lernen.

Vorwort

Dieser Artikel stellt Ihnen hauptsächlich den relevanten Inhalt über die grundlegenden virtuellen Funktionsklassen der Vererbung und des Polymorphismus in C++ vor. Bitte teilen Sie ihn Für alle. Bitte werfen Sie einen Blick auf die ausführliche Einführung.

Virtuelle Funktionsklasse

Bei der Vererbung erwähnen wir oft die virtuelle Vererbung, das virtuelle If Das Schlüsselwort virtual wird vor einer Mitgliedsfunktion einer Funktionsklasse hinzugefügt. Die Mitgliedsfunktion wird als virtuelle Funktion bezeichnet. Sie kann viele heikle Probleme bei der Vererbung lösen und ist für die Polymorphie noch wichtiger Ohne es gibt keinen Polymorphismus, daher ist dieser Wissenspunkt sehr wichtig, und die später eingeführte virtuelle Funktionstabelle ist äußerst wichtig. Sie müssen ihn nun sorgfältig verstehen. Beginnen wir nun mit der Konzeptualisierung virtueller Funktionen und führen Sie ein anderes Konzept ein, nämlich das Umschreiben. Überschreiben), wenn in einer untergeordneten Klasse eine virtuelle Funktion definiert wird, die genau mit der übergeordneten Klasse übereinstimmt, wird gesagt, dass die Funktion der Unterklasse die virtuelle Funktion der übergeordneten Klasse überschreibt (auch Überschreiben genannt). Lassen Sie mich zunächst die virtuelle Funktionstabelle erwähnen. Wie später erläutert wird, besteht das Umschreiben darin, alle überschriebenen Funktionsadressen der übergeordneten Klasse in der virtuellen Funktionstabelle in der Unterklasse in die Adressen der Unterklassenfunktionen zu ändern.

Reine virtuelle Funktion

Schreiben Sie =0 nach dem formalen Parameter der Mitgliedsfunktion, dann ist die Mitgliedsfunktion eine rein virtuelle Funktion. Klassen, die rein virtuelle Funktionen enthalten, werden abstrakte Klassen (auch Schnittstellenklassen genannt) genannt.

Abstrakte Klassen können keine Objekte instanziieren. Erst nachdem eine rein virtuelle Funktion in einer abgeleiteten Klasse neu definiert wurde, kann die abgeleitete Klasse ein Objekt instanziieren.

Sehen Sie sich ein Beispiel an:


class Person 
{ 
  virtual void Display () = 0; // 纯虚函数 
protected : 
  string _name ;   // 姓名 
}; 
 
class Student : public Person 
{};
Nach dem Login kopieren

Fassen Sie zunächst das Konzept zusammen:

1 Wiederverwendung Um Polymorphismus durch Schreiben einer virtuellen Funktion einer Basisklasse zu implementieren, müssen Funktionsname, Parameterliste und Rückgabewert genau gleich sein. (Außer Kovarianz)

2. Eine virtuelle Funktion ist in der Basisklasse definiert und die Funktion behält immer die Eigenschaften einer virtuellen Funktion in der abgeleiteten Klasse bei.

3. Nur Mitgliedsfunktionen einer Klasse können als virtuelle Funktionen definiert werden.

4. Statische Memberfunktionen können nicht als virtuelle Funktionen definiert werden.

5. Wenn Sie eine virtuelle Funktion außerhalb der Klasse definieren, können Sie virtuelle Funktionen nur hinzufügen, wenn Sie die Funktion deklarieren.

6. Rufen Sie keine virtuellen Funktionen im Konstruktor und Destruktor auf, da das Objekt unvollständig ist und es zu undefiniertem Verhalten kommen kann.

7. Am besten deklarieren Sie den Destruktor der Basisklasse als virtuelle Funktion. (Warum? Darüber hinaus ist der Destruktor etwas Besonderes, da sich der Name des Destruktors der abgeleiteten Klasse vom Namen der Basisklasse unterscheidet, aber eine Überschreibung darstellt. Dies liegt daran, dass der Compiler eine spezielle Verarbeitung durchgeführt hat.)

8. Obwohl „operator=“ als virtuelle Funktion definiert werden kann, ist es am besten, „operator=“ nicht als virtuelle Funktion zu definieren, da dies bei Verwendung leicht zu Verwirrung führen kann.


Sie fragen sich vielleicht, warum dies das obige Konzept ist. Die Antworten auf diese Inhalte finden Sie im folgenden Wissen ~ Okay, dann ist unser heutiger Protagonist Virtuelle FunktionAussehen!!!

Was ist eine virtuelle Funktionstabelle? Wir können das herausfinden, indem wir ein Programm schreiben und ein Überwachungsfenster anpassen.

Das Folgende ist eine Klasse mit virtuellen Funktionen:


#include<iostream> 
#include<windows.h> 
using namespacestd; 
 
class Base 
{ 
public: 
   virtual void func1() 
   {} 
 
   virtual void func2() 
   {} 
 
private: 
   inta; 
}; 
 
void Test1() 
{ 
   Base b1; 
} 
 
int main() 
 
{ 
   Test1(); 
   system("pause"); 
   return0; 
}
Nach dem Login kopieren

Wir klicken nun auf das Überwachungsfenster von b1

Hier gibt es einen _vfptr, und dieser _vfptr zeigt auf unseren Protagonisten, die virtuelle Funktionstabelle. Jeder wird sofort wissen, dass selbst unsere rautenförmige Vererbungstabelle für virtuelle Funktionen unterschiedliche Formen haben wird, unabhängig davon, ob es sich um eine Einzelvererbung oder eine Mehrfachvererbung handelt.

Untersuchen wir das Speicherlayout der Einzelvererbung

Schauen Sie sich den folgenden Code genau an :


#include<iostream> 
#include<windows.h> 
using namespace std; 
 
 
class Base 
{ 
public: 
   virtual void func1() 
   { 
     cout<< "Base::func1"<< endl; 
   } 
 
   virtual void func2() 
   { 
     cout<< "Base::func2"<< endl; 
   } 
 
private: 
   inta; 
}; 
 
class Derive:public Base 
{ 
public: 
   virtual void func1() 
   { 
     cout<< "Derive::func1"<< endl; 
   } 
 
   virtual void func3() 
   { 
     cout<< "Derive::func3"<< endl; 
   } 
 
   virtual void func4() 
   { 
     cout<< "Derive::func4"<< endl; 
   } 
 
private: 
   int b; 
};
Nach dem Login kopieren

Was wird unserer Meinung nach in der virtuellen Tabelle der Derive-Klasse enthalten sein?

Zuerst schreibt das fun1() der Unterklasse das fun1() der übergeordneten Klasse um. Die virtuelle Tabelle speichert das fun1() der Unterklasse und dann das fun2() der übergeordneten Klasse , die Unterklasse Die fun3() und fun4() sind alle virtuelle Funktionen, daher gibt es 4 Elemente in der virtuellen Tabelle, nämlich das fun1() der Unterklasse, das fun2() der Elternklasse, das fun3() der Unterklasse und das der Unterklasse fun4(). Dann rufen wir das Überwachungsfenster auf, um zu sehen, ob das, was wir denken, richtig ist?

我预计应该是看到fun1() ,fun2() ,fun3() ,fun4()的虚函数表,但是呢这里监视窗口只有两个fun1() , fun2() ,难道我们错了????

这里并不是这样的,只有自己靠得住,我觉得这里的编译器有问题,那我们就得自己探索一下了。 但是在探索之前我们必须来实现一个可以打印虚函数表的函数。


typedef void(*FUNC)(void); 
void PrintVTable(int* VTable) 
{ 
   cout<< " 虚表地址"<<VTable<< endl; 
 
   for(inti = 0;VTable[i] != 0; ++i) 
   { 
     printf(" 第%d个虚函数地址 :0X%x,->", i,VTable[i]); 
     FUNC f = (FUNC)VTable[i]; 
     f(); 
   } 
 
   cout<< endl; 
} 
 
 
int main() 
{ 
   Derive d1; 
   PrintVTable((int*)(*(int*)(&d1))); 
   system("pause"); 
   return0; 
}
Nach dem Login kopieren

下图来说一下他的缘由:


我们来使用这个函数,该函数代码如下:


//单继承 
class Base 
{ 
public: 
 virtual void func1() 
 { 
  cout << "Base::func1" << endl; 
 } 
 
 virtual void func2() 
 { 
  cout << "Base::func2" << endl; 
 } 
 
private: 
 int a; 
}; 
 
class Derive :public Base 
{ 
public: 
 virtual void func1() 
 { 
  cout << "Derive::func1" << endl; 
 } 
 
 virtual void func3() 
 { 
  cout << "Derive::func3" << endl; 
 } 
 
 virtual void func4() 
 { 
  cout << "Derive::func4" << endl; 
 } 
 
private: 
 int b; 
}; 
typedef void(*FUNC)(void); 
void PrintVTable(int* VTable) 
{ 
   cout<< " 虚表地址"<<VTable<< endl; 
  
   for(inti = 0;VTable[i] != 0; ++i) 
   { 
     printf(" 第%d个虚函数地址 :0X%x,->", i,VTable[i]); 
     FUNC f = (FUNC)VTable[i]; 
     f(); 
   } 
  
   cout<< endl; 
} 
  
  
int main() 
{ 
   Derive d1; 
   PrintVTable((int*)(*(int*)(&d1))); //重点 
   system("pause"); 
   return0; 
}
Nach dem Login kopieren

这里我就要讲讲这个传参了,注意这里的传参不好理解,应当细细的"品味".


PrintVTable((int*)(*(int*)(&d1)));
Nach dem Login kopieren

首先我们肯定要拿到d1的首地址,把它强转成int*,让他读取到前4个字节的内容(也就是指向虚表的地址),再然后对那个地址解引用,我们已经拿到虚表的首地址的内容(虚表里面存储的第一个函数的地址)了,但是此时这个变量的类型解引用后是int,不能够传入函数,所以我们再对他进行一个int*的强制类型转换,这样我们就传入参数了,开始函数执行了,我们一切都是在可控的情况下使用强转,使用强转你必须要特别清楚的知道内存的分布结构。

最后我们来看看输出结果:



Einführung in die grundlegenden virtuellen Funktionsklassen im Zusammenhang mit Vererbung und Polymorphismus in C++到底打印的对不对呢? 我们验证一下:

这里我们通过&d1的首地址找到虚表的地址,然后访问地址查看虚表的内容,验证我们自己写的这个函数是正确的。(这里VS还有一个bug,当你第一次打印虚表时程序可能会崩溃,不要担心你重新生成解决方案,再运行一次就可以了。因为当你第一次打印是你虚表最后一个地方可能没有放0,所以你就有可能停不下来然后崩溃。)我们可以看到d1的虚表并不是监视器里面打印的那个样子的,所以有时候VS也会有bug,不要太相信别人,还是自己靠得住。哈哈哈,臭美一下~

我们来研究一下多继承的内存格局

探究完了单继承,我们来看看多继承,我们还是通过代码调试的方法来探究对象模型

看如下代码:


class Base1 
{ 
public: 
 virtual void func1() 
 { 
  cout << "Base1::func1" << endl; 
 } 
 
 virtual void func2() 
 { 
  cout << "Base1::func2" << endl; 
 } 
 
private: 
 int b1; 
}; 
 
class Base2 
{ 
public: 
 virtual void func1() 
 { 
  cout << "Base2::func1" << endl; 
 } 
 
 virtual void func2() 
 { 
  cout << "Base2::func2" << endl; 
 } 
 
private: 
 int b2; 
}; 
 
 
class Derive : public Base1, public Base2 
{ 
public: 
 virtual void func1() 
 { 
  cout << "Derive::func1" << endl; 
 } 
 
 virtual void func3() 
 { 
  cout << "Derive::func3" << endl; 
 } 
 
private: 
 int d1; 
}; 
 
typedef void(*FUNC) (); 
void PrintVTable(int* VTable) 
{ 
 cout << " 虚表地址>" << VTable << endl; 
 
 for (int i = 0; VTable[i] != 0; ++i) 
 { 
  printf(" 第%d个虚函数地址 :0X%x,->", i, VTable[i]); 
  FUNC f = (FUNC)VTable[i]; 
  f(); 
 } 
 cout << endl; 
} 
 
 
void Test1() 
{ 
 Derive d1; 
 //Base2虚函数表在对象Base1后面 
 int* VTable = (int*)(*(int*)&d1); 
 PrintVTable(VTable); 
 int* VTable2 = (int *)(*((int*)&d1 + sizeof (Base1) / 4)); 
 PrintVTable(VTable2); 
} 
int main() 
{ 
 Test1(); 
 system("pause"); 
 return 0; 
}
Nach dem Login kopieren

现在我们现在知道会有两个虚函数表,分别是Base1和Base2的虚函数表,但是呢!我们的子类里的fun3()函数怎么办?它是放在Base1里还是Base2里还是自己开辟一个虚函数表呢?我们先调一下监视窗口:


监视窗口又不靠谱了。。。。完全没有找到fun3().那我们直接看打印出来的虚函数表。


现在很清楚了,fun3()在Base1的虚函数表中,而Base1是先继承的类,好了现在我们记住这个结论,当涉及多继承时,子类的虚函数会存在先继承的那个类的虚函数表里。记住了!

我们现在来看多继承的对象模型:


现在我们来结束一下上面我列的那么多概念现在我来逐一的解释为什么要这样.

1.为什么静态成员函数不能定义为虚函数?

因为静态成员函数它是一个大家共享的一个资源,但是这个静态成员函数没有this指针,而且虚函数变只有对象才能能调到,但是静态成员函数不需要对象就可以调用,所以这里是有冲突的.

2.为什么不要在构造函数和析构函数里面调用虚函数?

构造函数当中不适合用虚函数的原因是:在构造对象的过程中,还没有为“虚函数表”分配内存。所以,这个调用也是违背先实例化后调用的准则析构函数当中不适用虚函数的原因是:一般析构函数先析构子类的,当你在父类中调用一个重写的fun()函数,虚函数表里面就是子类的fun()函数,这时候已经子类已经析构了,当你调用的时候就会调用不到.

现在我在写最后一个知识点,为什么尽量最好把基类的析构函数声明为虚函数??

现在我们再来写一个例子,我们都知道平时正常的实例化对象然后再释放是没有一点问题的,但是现在我这里举一个特例:

我们都知道父类的指针可以指向子类,现在呢我们我们用一个父类的指针new一个子类的对象。


//多态 析构函数 
class Base 
{ 
public: 
 virtual void func1() 
 { 
  cout << "Base::func1" << endl; 
 } 
 
 virtual void func2() 
 { 
  cout << "Base::func2" << endl; 
 } 
 
 virtual ~Base() 
 { 
  cout << "~Base" << endl; 
 } 
 
private: 
 int a; 
}; 
 
class Derive :public Base 
{ 
public: 
 virtual void func1() 
 { 
  cout << "Derive::func1" << endl; 
 } 
 virtual ~Derive() 
 { 
  cout << "~Derive"<< endl; 
 } 
private: 
 int b; 
}; 
 
void Test1() 
{ 
 Base* q = new Derive; 
 delete q; 
} 
int main() 
{ 
 Test1(); 
 system("pause"); 
 return 0; 
}
Nach dem Login kopieren

这里面可能会有下一篇要说的多态,所以可能理解起来会费劲一点。

注意这里我先让父类的析构函数不为虚函数(去掉virtual),我们看看输出结果:


这里它没有调用子类的析构函数,因为他是一个父类类型指针,所以它只能调用父类的析构函数,无权访问子类的析构函数,这种调用方法会导致内存泄漏,所以这里就是有缺陷的,但是C++是不会允许自己有缺陷,他就会想办法解决这个问题,这里就运用到了我们下次要讲的多态。现在我们让加上为父类析构函数加上virtual,让它变回虚函数,我们再运行一次程序的:


诶! 子类的虚函数又被调用了,这里发生了什么呢??  来我们老方法打开监视窗口。


刚刚这种情况就是多态,多态性可以简单地概括为“一个接口,多种方法”,程序在运行时才决定调用的函数,它是面向对象编程领域的核心概念。这个我们下一个博客专门会总结多态.

当然虚函数的知识点远远没有这么一点,这里可能只是冰山一角,比如说菱形继承的虚函数表是什么样?然后菱形虚拟继承又是什么样子呢? 这些等我总结一下会专门写一个博客来讨论菱形继承。虚函数表我们应该已经知道是什么东西了,也知道单继承和多继承中它的应用,这些应该就足够了,这些其实都是都是为你让你更好的理解继承和多态,当然你一定到分清楚重写,重定义,重载的他们分别的含义是什么. 这一块可能有点绕,但是我们必须要掌握.

Das obige ist der detaillierte Inhalt vonEinführung in die grundlegenden virtuellen Funktionsklassen im Zusammenhang mit Vererbung und Polymorphismus in C++. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Verwandte Etiketten:
Quelle:php.cn
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage
Über uns Haftungsausschluss Sitemap
Chinesische PHP-Website:Online-PHP-Schulung für das Gemeinwohl,Helfen Sie PHP-Lernenden, sich schnell weiterzuentwickeln!