多态分为静多态和动多态,静多态包括模板,函数重载,是在编译时期就已经确定的,动多态是在运行时确定。
体现在汇编上的差异(静多态 call 0x地址)(动多态 call eax)
class Abstract
{
public:
virtual void f() = 0{ cout << "over" << endl; };
virtual void g() = 0;
};
class Dev:public Abstract
{
public:
void f(){}
void g(){}
//virtual void r(){ cout << "Dev::r" << endl; }
};
/*
1.派生类会继承基类的虚表。
2.派生类构造时来确定对象中的虚表指针该指向哪
3.虚函数表在编译时期就有了,并且放在rodata中
4.纯虚函数可以有函数体,但没啥意义。
*/
//class Rt :public Dev
//{
//public:
// void r(){}
// void r(int x){}
//};
int main()
{
Dev v;
v.f();
//Rt t;
//t.r(1);
//Abstract as;
//Dev de;
//cout << sizeof(de) << endl;
return 0;
}
#if 0
//如果基类中该函数有默认参,发生多态时默认使用该默认参。
class Base
{
public:
static void f(){ cout << "Base f" << endl; }
static int c;
virtual void tr(int x = 10){ cout << x << endl; }
};
int Base::c = 10;
class Dev:public Base
{
public:
void g(int x)
{
//Base::f();
c = x;
}
void p(){ cout << c << endl; }
void tr(int x = 11){ cout << "Dev::"<<x << endl; }
};
int main()
{
Dev de;
de.g(11);
Dev de1;
de1.g(12);
//de.p();
//de1.p();
Base &p = de;
p.Base::tr();
//Base *p1 = &de;
//p1->tr();
//cout << sizeof(de) << endl;
//fun2(20);
return 0;
#endif
}
#if 0
class Base
{
public:
Base(){};
/*
virtual Base();
如果使用了virtual那么构造函数就要通过vptr找到vtable,
但是vptr是在构造函数中完成的,那么vtable就无法找到。
因此,不可以有虚构造函数。
*/
virtual ~Base(){ cout << "~Base" << endl; }
/*
当一个类作为基类时,尽可能的把它的析构写为virtual,
防止X类继承了该类的,那么X类中动态申请的内存就不能
得到释放,发生内存泄露。
如果确定不作为基类,就别写了,写了也多占内存。
*/
private:
int b;
};
class Dev:public Base
{
public:
Dev(){};
~Dev(){ cout << "~Dev" << endl; };
private:
int d;
};
int main()
{
//Dev v;
Base *p = new Dev();
delete p;
/*
动态绑定,分析对象类型,并调用它的析构函数。
因此,会先调用Dev析构。由于继承关系,再调用
Base的析构。
*/
return 0;
}
#endif
#if 0
//哪些函数不能实现为虚函数?
1.普通函数
/*
virtual void fun1){};
virtual关键字不允许使用在类外,因此普通函数不能实现为虚函数。
*/
2.内联函数
/*
inline virtual void fun();
内联函数在编译时期展开,与虚函数通过函数实现多态相背。
尴尬的是上面这样的定义,本应当是不对的。但编译通过了,
并且sizeof(Base)=4。 也就是编译器对我建议的显式line完
全不理视。(内联只是建议,决定权在编译器手里)
*/
3.静态函数
/*
virtual static void fun();
首先静态函数是可以被继承的,但它在编译时期就被确定了属
于哪个类是确定的,不会再更改。
因此,静态函数是不可以被virtual修饰进行重写,想发生多态
是不可能了。
*/
4.友元函数
/*
virtual friend void fun();
编译失败,友元函数不能被继承,非继承特性的函数
不允许被virtual修饰。
*/
};
#if 0
/*
理解分析虚基类
*/
class Base0{
public:
Base0(int var = 1):var0(var){}
int var0;
};
class Base1 :virtual public Base0{
public:
Base1():Base0(10){}
int var1;
};
class Base2 :virtual public Base0{
public:
Base2():Base0(20){}
int var2;
};
class Derived :public Base1, public Base2{
public:
Derived(){}
};
int main(){
Derived d;
cout << sizeof(d) << endl; //20
/*
内存布局
*******************
* ***************
* *Base1:vbptr **
* * var1 **
*******************
* *Base2:vbptr **
* * var2 **
* ***************
* var0 **
*******************
由于虚继承原因,内存布局产生了变动,为了每一个类能正确指到虚基类的数据
因此为每一个直接或间接基类产生一个vbptr,可以指向例如此例中的var0
结论:1.最远建立对象的构造中,虚基类的成员是直接通过虚基类的构造函数构造的,
尽管途中有其他对虚基类的构造有所指定,但不执行。
2.虚基类的构造先于任何一个非虚基类
3.vbptr表项
vbptr[0] 当前最近作用域的偏移 - vbptr的偏移(一般为0)
vbptr[1] 虚继类数据的起始偏移 - vbptr的偏移(可以正确指到数据)
vbptr[2] 还有数据的话就以此类推
4.vfptr表项
**************************************************
* RTTL(run time type information)运行时类型信息*
**************************************************
* 虚函数指针的偏移 (-8) *
**************************************************
vfptr--> * 虚函数的入口地址(第一个虚函数入口/地址) * *
**************************************************
* 还有虚函数的依次向下 *
**************************************************
虚表的写入时机 构造函数的执行之前
class Derived :virtual public Base
此时继承关系为虚拟基类继承,那么在使用是编译器会为Derived类
隐式加上一个vbptr。因为Derived有可能和其他类有共同基类,避免
重复,内存的浪费。用vbptr来指向他们共同的正确数据。
*/
return 0;
}
#endif
#if 0
/*
重载 隐藏 覆盖(复写)
*/
class Base
{
public:
void fun(){ cout << "Base::fun" << endl; }
virtual void fun(int i){}
};
class Dev : public Base
{
public:
void* fun(){ cout << "Dev::fun" << endl; return NULL; }
void fun(int i){}
};
int main()
{
Dev v;
v.fun()
return 0;
}
#endif