1.仮想関数はどのように見つかりますか
最初に単一継承を見て、次のコードをコンパイルします。
#include<iostream>
#include<string.h>
using namespace std;
class Grandam
{
public:
virtual void introduce_self()
{
cout << "I am grandam." << endl;
}
};
class Mother:public Grandam
{
public:
virtual void introduce_self()
{
cout << "I am mother." << endl;
}
};
class Daughter :public Mother
{
public:
void introduce_self()
{
cout << "I am daughter." << endl;
}
};
int main()
{
Grandam* ptr;
Grandam g;
Mother m;
Mother m1;
Daughter d;
typedef void(*introduce_self)();
ptr = &g;
ptr->introduce_self();
ptr = &m;
ptr->introduce_self();
ptr = &m1;
ptr = &d;
ptr->introduce_self();
introduce_self* p = (introduce_self*)(*(int*)(&g));
(*p)();
system("pause");
return 0;
}
結果を次の図に示します。
4番目の結果は、基本クラスGrandamの仮想関数が呼び出されることです。
コード内:
typedef void(*introduce_self)(); //对函数进行重命名
introduce_self* p = (introduce_self*)(*(int*)(&g)); //定义一个函数指针,并让它指向对象内存的首地址里存放的地址(即虚表指针或地址)
(*p)(); //对虚表调用该函数
因为虚表里存放的是虚函数的地址,所以对函数指针进行解引用,即就是调用该虚函数。
したがって、私たちは知っています:
只要存在虚函数,那么被基类指针或被基类对象引用的对象中调用该虚函数时,该被调用对象的首地址存放的是虚表指针_vfptr(地址),而虚表_vfptr中存放的是虚函数地址。
2.基本クラスと派生クラスが仮想テーブルを共有するかどうか
次 の図に示すように、f10を押してメモリに格納されているコンテンツの変更を確認することで、シングルステップでデバッグすることもできます。
1.基本クラスGrandamオブジェクトgの関数introduce_self()を呼び出すときのメモリを次の図に示します。
2.派生クラスMotherオブジェクトmの関数introduce_self()を呼び出すときのメモリを次の図に示します。
3.派生クラスMotherオブジェクトmの関数introduce_self()を呼び出すときのメモリを次の図に示します。
4.派生クラスDaughterオブジェクトdの関数introduce_self()が呼び出されたときのメモリを次の図に示します。
概要:3つのメモリの比較によると、異なるオブジェクトアドレスに格納されている仮想テーブルポインタは異なり、同じタイプのオブジェクトは仮想テーブルを共有していることがわかります。
3.基本クラスと派生クラスが仮想関数を呼び出す順序
1.単一継承、コードは次のとおりです。
#include<iostream>
#include<string.h>
using namespace std;
class Grandam
{
public:
void introduce_self1()
{
cout << "1: I am grandam." << endl;
}
void introduce_self2()
{
cout << "2: I am grandam." << endl;
}
virtual void introduce_self3()
{
cout << "3: I am grandam." << endl;
}
virtual void introduce_self4()
{
cout << "4: I am grandam." << endl;
}
};
class Daughter :public Grandam
{
public:
void introduce_self2()
{
cout << "2: I am daughter." << endl;
}
virtual void introduce_self5()
{
cout << "5: I am daughter." << endl;
}
virtual void introduce_self1()
{
cout << "1: I am daughter." << endl;
}
virtual void introduce_self4()
{
cout << "4: I am daughter." << endl;
}
};
int main()
{
Grandam g;
Daughter d;
Daughter* ptr = &d;
typedef void(*intro)();
intro* pi = (intro*)(*((int*)(&d)));
while (*pi)
{
(*pi)();
(int *)pi++;
}
return 0;
}
**次のコードは、仮想テーブル内の仮想関数の呼び出しです。注意事項は次のとおりです。
typedef void(*intro)();
intro* pi = (intro*)(*((int*)(&d)));
while (*pi)
{
(*pi)();
(int *)pi++;
}
注:これは、派生クラスオブジェクトdの仮想テーブルへの呼び出しです。これはVS 2012で実行されます。仮想テーブルの最後のアドレスは00000000であるため、このwhileループとして記述できます。コンパイラが異なれば、書き込み方法も異なります。 。
操作の結果は次のとおりです。
要約すると、我々は、下のことを見ることができ、単一継承:
オブジェクトのアドレスに格納された仮想テーブルポインタに従って:仮想テーブルに格納されている仮想関数のアドレスのため、仮想関数を呼び出すために:
1)最初に基本クラスの仮想関数を呼び出し、基本クラスで宣言された順序でそれらを呼び出します。
2)基本クラスの仮想関数が呼び出された後、派生クラスの仮想関数を呼び出し、それを基本クラス関数の後ろに置きます。
3)派生クラスの仮想関数が呼び出されると、派生クラスでの宣言順に呼び出されます。
2.多重継承は次のとおりです。
#include<iostream>
#include<string.h>
using namespace std;
class Grandam
{
public:
void introduce_self1()
{
cout << "1: I am grandam." << endl;
}
void introduce_self2()
{
cout << "2: I am grandam." << endl;
}
virtual void introduce_self3()
{
cout << "3: I am grandam." << endl;
}
virtual void introduce_self4()
{
cout << "4: I am grandam." << endl;
}
};
class Mother
{
public:
virtual void introduce_self1()
{
cout << "1: I am mother." << endl;
}
virtual void introduce_self2()
{
cout << "2: I am mother." << endl;
}
};
class Daughter :public Grandam , public Mother
{
public:
void introduce_self2()
{
cout << "2: I am daughter." << endl;
}
virtual void introduce_self5()
{
cout << "5: I am daughter." << endl;
}
void introduce_self1()
{
cout << "1: I am daughter." << endl;
}
virtual void introduce_self4()
{
cout << "4: I am daughter." << endl;
}
};
int main()
{
Grandam g;
Mother m;
Daughter d;
Mother& m1 = d;
Grandam& g1 = d;
typedef void(*pfun)();
pfun* pg1 = (pfun*)(*(int*)(&g1));
while (*pg1)
{
(*pg1)();
(int*)pg1++;
}
pfun* pm1 = (pfun*)(*(int*)(&m1));
while (*pm1)
{
(*pm1)();
(int*)pm1++;
}
return 0;
}
結果は次のとおりです。
要約:多重継承の場合、派生クラスの仮想関数は、最初の基本クラスの仮想関数が呼び出された後に呼び出されます。