C++多态理解及多态对象模型探索

来源:互联网 发布:电脑键盘记录软件 编辑:程序博客网 时间:2024/06/15 05:44

什么是多态? 
多态按字面的意思就是”多种状态”。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。在C++中过虚函数(Virtual Function) 实现的,使用的关键字是virtual。

多态分类 
1.静态多态,在编译期间完成。编译器会根据函数的参数来确定调用哪一个参数,如果有这个函数则调用它,否则直接报错。 
2.动态多态,在执行期间完成。在编译期间,判断对象的实际类型,根据实际类型,执行相应的方法。 
例如:

class Base
{};
class Derived
{};
int main()
{
Base* pb = new Base;//pb的静态类型是Base*,动态类型也是Base*
Derived *pd = pb;//pd的静态类型是Derived*,动态类型是Base*
return 0;
}

动态绑定条件 
(1)虚函数–子类必须重写虚函数,且要求函数名,函数的参数都要相同。 
(2)基类的指针或引用调用虚函数。

了解概念之后我们分析以下代码:

class Base
{
public:
virtual void Test1() //基类是虚函数,子类也虚函数
{
cout<<"Base:: Test1()"<<endl;
}

virtual void Test2(int a)//基类与子类函数参数不同
{
cout<<"Base:: Test2()"<<endl;
}

virtual void Test3()//基类是虚函数,子类不是虚函数
{
cout<<"Base:: Test3()"<<endl;
}

void Test4() //基类不是虚函数,子类是虚函数
{
cout<<"Base:: Test4()"<<endl;
}

virtual void Test5()//基类不含参数,子类有一个参数
{
cout<<"Base:: Test5()"<<endl;
}
};

class Derived:public Base
{
public:
virtual void Test1()
{
cout<<"Derived:: Test1()"<<endl;
}

virtual void Test2(char a)
{
cout<<"Derived:: Test2()"<<endl;
}

void Test3()
{
cout<<"Derived:: Test3()"<<endl;
}

virtual void Test4()
{
cout<<"Derived:: Test4()"<<endl;
}

virtual void Test5(int a)
{
cout<<"Base:: Test5()"<<endl;
}
};

void Test(Base& b)
{
b. Test1();
b. Test2(10);
b. Test2('z');

b. Test4();
b. Test5();
}

int main()
{
Base b;
Derived d;
Test(b);
Test(d);
return 0;

通过对以上代码的测试我们总结出以下结论

(1)如果是动态绑定,则调用基类虚函数; 
(2)如果基类不是虚函数,而子类是虚函数,则调用调用基类函数; 
(3)如果基类是虚函数,子类不是虚函数,优先调用子类函数; 
(3)如果子类和基类都是虚函数但是他们的参数部分不相同,比如基类是一个参数,而子类两个参数,以基类函数为准,否则报错。

多态继承问题研究

单继承原理 
通过查看内存窗口我们发现对象的首地址存放的是一个指针,指针指向了一张虚表,虚表中存放了虚函数的地址。 
单继承模型图


单继承模型
class B
{
public:
virtual void Test()
{
cout<<"B::Test()"<<endl;
}
int _b;
};
class C: public B
{
virtual void Test()
{
cout<<"C::Test()"<<endl;
}
virtual void Test1()
{
cout<<"C::Test1()"<<endl;
}
int _c;
};
typedef void (*pVtr)();
void Print(B& b1)
{
pVtr* pvtf = (pVtr*)*(int*)&b1;
while(*pvtf)
{
cout<<hex<<(*pvtf)<<endl;
pvtf++;
}
}

int main()
{
B b1;
C c1;
Print(b1);
Print(c1);
return 0;
}

多态对象模型—多继承

对象模型内存图


class B{public:virtual void FunTest1()  //在派生类中被重写{cout<<"B::FunTest1()"<<endl;}virtual void FunTest2(){cout<<"B::FunTest2()"<<endl;}int _b;};class C{public:virtual void FunTest3()  //在派生类中被重写{cout<<"C::FunTest3()"<<endl;}virtual void FunTest4(){cout<<"C::FunTest4()"<<endl;}int _c;};class D : public B, public C{public:virtual void FunTest1(){cout<<"D::FunTest1()"<<endl;}virtual void FunTest3(){cout<<"D::FunTest3()"<<endl;}virtual void FunTest5(){cout<<"D::FunTest5()"<<endl;}int _d;};void Test(B& b, D& d){b.FunTest1();d.FunTest1();d.FunTest3();d.FunTest5();}int main(){B b1;D d1;cout<<sizeof(D)<<endl;Test(b1, d1);return 0;}

菱形继承

对象模型内存图


class B{public:virtual void FunTest1()  //在派生类中被重写{cout<<"B::FunTest1()"<<endl;}virtual void FunTest2(){cout<<"B::FunTest2()"<<endl;}int _b;};class C1 :/* virtual*/ public B   //加上virtual后就是菱形继承{public:virtual void FunTest3()  //在派生类中被重写{cout<<"C1::FunTest3()"<<endl;}virtual void FunTest4(){cout<<"C1::FunTest4()"<<endl;}int _c1;};class C2 : /*virtual*/ public B     //加上virtual后就是菱形继承{public:virtual void FunTest5() //在派生类中被重写{cout<<"C2::FunTest5()"<<endl;}virtual void FunTest6(){cout<<"C2::FunTest6()"<<endl;}int _c2;};class D : public C1, public C2{public:virtual void FunTest1(){cout<<"D::FunTest1()"<<endl;}virtual void FunTest3(){cout<<"D::FunTest3()"<<endl;}virtual void FunTest5(){cout<<"D::FunTest5()"<<endl;}virtual void FunTest7(){cout<<"D::FunTest57()"<<endl;}int _d;};void Test(B& b, D& d){b.FunTest1();d.FunTest1();d.FunTest3();d.FunTest5();//b.}int main(){B b1;D d1;//C c1;cout<<sizeof(D)<<endl;Test(b1, d1);//Test(c1, d1);return 0;}

在菱形继承继承中存在二义性问题,因此我们引入了菱形虚拟继承,这解决了菱形继承中的的二义性问题

菱形虚拟继承

对象模型内存图


这里测试代码只需要在菱形继承代码中加上两个virtual关键字即可。


函数重载,同名隐藏,重写三者之间的对比

1.函数重载:在同一个作用域,函数名称相同,参数列表不同(参数类型不同,参数个数不同,或者参数的次序不同),函数的返回值可以相同也可以不同; 
2.同名隐藏:只要函数名相同即可,函数的作用域不相同,一个在基类,一个在派生类,在基类和派生类中国只要不构成重写就是同名隐藏。 
3.重写:作用域不同,分别在派生类和基类,函数名相同,参数相同,返回值相同(协变除外),基类的函数必须包含有virtual这个关键字
 

哪些成员函数不可作为虚函数??

常见的不不能声明为虚函数的有:普通函数(非成员函数);静态成员函数;内联成员函数;构造函数;友元函数。

1.为什么C++不支持普通函数为虚函数?

普通函数(非成员函数)只能被overload,不能被override,声明为虚函数也没有什么意思,因此编译器会在编译时邦定函数。

多态的运行期行为体现在虚函数上,虚函数通过继承方式来体现出多态作用,顶层

函数不属于成员函数,是不能被继承的

2.为什么C++不支持构造函数为虚函数?

这个原因很简单,主要是从语义上考虑,所以不支持。因为构造函数本来就是为了明确初始化对象成员才产生的,然而virtual function主要是为了再不完全了解细节的情况下也能正确处理对象。另外,virtual函数是在不同类型的对象产生不同的动作,现在对象还没有产生,如何使用virtual函数来完成你想完成的动作。(这不就是典型的悖论)

1)构造函数不能被继承,因而不能声明为virtual函数

(2)构造函数一般是用来初始化对象,只有在一个对象生成之后,才能发挥多态

作用,如果将构造函数声明为virtual函数,则表现为在对象还没有生成的情

况下酒使用了多态机制,因而是行不通的。

3.为什么C++不支持内联成员函数为虚函数?

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

inline函数和virtual函数有着本质的区别,inline函数是在程序被编译时就展开,在函数调用处用整个函数体去替换,而virtual函数是在运行期才能够确定如何去调用的,因而inline函数体现的是一种编译期机制,virtual函数体现的是一种运行期机制。此外,一切virtual函数都不可能是inline函数。

4.为什么C++不支持静态成员函数为虚函数?

这也很简单,静态成员函数对于每个类来说只有一份代码,所有的对象都共享这一份代码,他也没有要动态邦定的必要性。不能被继承,只属于该类。

5.为什么C++不支持友元函数为虚函数?

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

如何获取虚表??

可以通过函数指针来获取一张虚表,在一个含有虚函数的类中,都会有一个指针,这个指针指向一张虚表,通过访问虚拟地址并进行指针偏移我们可以访问对应的虚函数

class B  //基类
{
public:
virtual void Test()
{
cout<<"B::Test()"<<endl;
}
int _b;
};
class C//:/*public B*/
{
virtual void Test()
{
cout<<"C::Test()"<<endl;
}
int _c;
};

class D:public C
{
virtual void Test1()
{
cout<<"D::Test()"<<endl;
}
int _d;
};
//void (*)&b1;
typedef void (*pVtr)(); //定义一个函数指针,通过函数指针拿到虚表地址
void Print(B& b1) //打印B类中虚表地址
{
pVtr* pvtf = (pVtr*)*(int*)&b1;
while(*pvtf)
{
cout<<hex<<(*pvtf)<<endl;
pvtf++;
}
}
void Print(C& c1) //打印C类出虚表中的地址
{
pVtr* pvtf = (pVtr*)*(int*)&c1;
while(*pvtf)
{
cout<<hex<<(*pvtf)<<endl;
pvtf++;
}
}
int main()
{
B b1;
C c1;
D d1;
//pVtr* pvtf = (pVtr*)*(int*)&b1;
Print(b1);
Print(c1);
Print(d1);
return 0;
}

总结: 
(1)虚表是所有实例所共用的。 
(2)每一个派生类中都有一张虚表,虚表中的内容包含了基类的一份拷贝。 
(3)派生类如果重写基类的虚函数,并且派生类对象调用的虚函数是重写后的(覆盖了基类中的虚函数); 
(4)如果派生类不重写虚函数,则虚表中的内容和基类中的内容相同; 
(5)派生类的自己的虚函数续接到基类虚表(拷贝之后的)的后面,并且根据自己定义的顺序依次放到后面。

原创粉丝点击