- 作者: 雪山肥鱼
- 时间:20210905 12:00
- 目的: 理解静态动态绑定概念
静态与动态绑定
//静态与d动态
class Base
{
public:
};
class Derive :public Base
{
public:
};
class Derive2 :public Base
{
public:
};
int main(int argc, char ** argv)
{
//静态类型, 对象定义的类型,编译期间是能确定好的,定义的是什么,就是什么
Base base;// 定义的类型就是Base
Derive derive;
Base * pBase;
Base *pbase2 = new Derive(); //类型也是确定的 是Base *
Base *pbase3 = new Derive2(); //类型也是确定的 是Base *
//动态类型,只有指针和引用才有此类做法,运行的时候才有
//pBase 类型 飘忽不变
pBase = pbase2;
pBase = pbase3;
return 0;
}
静态类型:编译期间就已经定了。可以理解为执行文件中就已经写死了。即使像也是静态绑定
Base *pbase2 = new Derive();
动态绑定,类型飘忽不定
函数的动静态绑定
普通成员函数
相比于上个例子中的代码,子类与父类增加了相同的成员函数
class Base
{
public:
void myfunc()
{
cout << "Base::myfun()" << endl;
}
};
class Derive :public Base
{
public:
void myfunc()
{
cout << "Derive::myfun()" << endl;
}
};
class Derive2 :public Base
{
public:
void myfunc()
{
cout << "Derive2::myfun()" << endl;
}
};
int main(int argc, char ** argv)
{
Derive derive;
Derive* pderive = &derive;
pderive->myfunc();//Derive::myfunc
Base*pbase = &derive;
pbase->myfunc();//Base::myfunc
return 0;
}
- 函数的静态绑定:
绑定的静态类型,所对应的函数或者属性依赖于对象的静态类型,发生在编译期间
普通函数均为静态绑定 - 函数的动态绑定
所对应的函数或者属性依赖于对象的动态类型,发生在运行期间
虚函数 - 动态绑定
例子中的myfun()是标准的普通成员函数,普通函数,到底调用哪个,取决于调用者的类型
代码规范: 不能在子类中重新定义一个基类中的普通成员函数
虚函数
增加虚函数代码
class Base
{
public:
void myfunc()
{
cout << "Base::myfun()" << endl;
}
virtual void myVirFunc() {
cout << "Base:: myVirFunc()" << endl;
}
};
class Derive :public Base
{
public:
void myfunc()
{
cout << "Derive::myfun()" << endl;
}
virtual void myVirFunc() {
cout << "Derive:: myVirFunc()" << endl;
}
};
class Derive2 :public Base
{
public:
void myfunc()
{
cout << "Derive2::myfun()" << endl;
}
virtual void myVirFunc() {
cout << "Derive2:: myVirFunc()" << endl;
}
};
int main(int argc, char ** argv)
{
Derive derive;
Derive* pderive = &derive;
Base*pbase = &derive;
Base base;
pderive->myVirFunc();//Derive
pbase->myVirFunc();//Derive
pbase = &base;
pbase->myVirFunc(); //Base
return 0;
}
非常明显的动态绑定。
虚函数中默认参数的坑
class Base
{
public:
void myfunc()
{
cout << "Base::myfun()" << endl;
}
virtual void myVirFunc(int value =1) {
cout << "Base:: myVirFunc(), value = " << value << endl;
}
};
class Derive :public Base
{
public:
void myfunc()
{
cout << "Derive::myfun()" << endl;
}
virtual void myVirFunc(int value =2) {
cout << "Derive:: myVirFunc(), value= " << value << endl;
}
};
int main(int argc, char ** argv)
{
Derive derive;
Derive* pderive = &derive;
Base*pbase = &derive;
Base base;
pderive->myVirFunc();//Derive default value = 2
pbase->myVirFunc();//Derive default value = 1;
pbase = &base;
pbase->myVirFunc(); //Base default value= 1;
return 0;
}
虚函数的默认参数是静态绑定的。生命的类型是谁,就用谁的虚函数默认参数。
谈多态
- 代码实现
调用虚函数,走虚函数表这条路,则是多态,如果不是,则非多态 - 表现形式
- 父类有虚函数,子类必有虚函数表,子类重写父类的虚函数
- 父类指针 or 引用 指向子类地址 or 子类对象
- 父类指针 or 引用 调用子类中重写的虚函数时,能看到多态,调用的实际是子类的虚函数。
多态注定要依靠虚函数的
class A
{
public:
virtual void myvirfun()
{
printf("A:myvirfun()");
}
};
class B:public A
{
public:
};
int main(int argc, char ** argv)
{
A *pa = new A();
pa->myvirfun();//多态
A a;
//进入反汇编 就2行代码
a.myvirfun();//非多态
A*ppa = &a;
ppa->myvirfun();//多态
B b;
b.myvirfun();
return 0;
}
注意:
A a;
a.myvirfun();
非多态,进入反汇编,则只能看到2行代码 调用的是 A::myvirfun();
虚函数的调用
小测试 虚函数位置
问: 虚函数i() 在虚函数表什么位置
class Base
{
public:
virtual void f()
{
cout << "Base::f()" << endl;
}
virtual void g()
{
cout << "Base::g()" << endl;
}
virtual void h()
{
cout << "Base::h()" << endl;
}
};
class Derive :public Base
{
public:
virtual void i()
{
cout << "Derive::self i()" << endl;
}
virtual void g()
{
cout << "Derive::g()" << endl;
}
void myselffunc(){}
};
int main(int argc, char**argv)
{
//一 单继承下的虚函数测试
//由反汇编测试可知,i 在子类虚函数表的最后。看图即可。
Derive myDerive;
Derive *pMyDerive = &myDerive;
pMyDerive->f();
pMyDerive->g();
pMyDerive->h();
pMyDerive->i();
return 0;
}
打断点,在反汇编中可以观察到,子类自己的虚函数i()在虚函数表的最后
虚函数调用代码(编译器视角)
class Base
{
public:
virtual void f()
{
cout << "Base::f()" << endl;
}
virtual void g()
{
cout << "Base::g()" << endl;
}
virtual void h()
{
cout << "Base::h()" << endl;
}
};
class Derive :public Base
{
public:
virtual void i()
{
cout << "Derive::self i()" << endl;
}
virtual void g()
{
cout << "Derive::g()" << endl;
}
void myselffunc(){}
};
int main(int argc, char**argv)
{
Base *pb = new Derive();
pb->g();
/*
(*pb->vptr[1])(pb); 1.传入this指针,2.虚函数表中每个虚函数的位置已经定死啦。
*/
//我们需要知道的是运行期间,pb调用哪个虚函数表
Derive myderive;
Base &mb = myderive;
mb.g();
return 0;
}
- 虚函数表中,每隔虚函数的位置都已经写死在可执行文件中
- 继承中,确认 pb 调用哪个虚函数表是关键(多继承中会用到)
vptr 的生成
vptr 编译时候生成,编译器的在构造函数中插入了给vptr 的赋值代码
所以构造函数不可以为虚函数,构造函数中就是要构造虚函数表的。把构造函数搞成虚函数毫无意义的。
子类中如果没有任何虚函数,不会复用父类的虚函数表,还会另起炉灶,弄一份一模一样的虚函数表
网友评论