1.多态的原理
虚函数表
class Base
{
public:virtual void Func1(){cout << "Func1()" << endl;}
private:int _b = 1;
};
b对象是8bytes,除了_b成员,还多一个__vfptr放在对象的前面(注意有些 平台可能会放到对象的最后面,这个跟平台有关),对象中的这个指针我们叫做虚函数表指针(v代 表virtual,f代表function)。
一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数 的地址要被放到虚函数表中,虚函数表也简称虚表
class Base
{
public:virtual void Func1(){cout << "Base::Func1()" << endl;}virtual void Func2(){cout << "Base::Func2()" << endl;}void Func3(){cout << "Base::Func3()" << endl;}
private:int _b = 1;
};int main()
{Base b;//Derive d;printf("%p\n",&Base::Func1);printf("%p\n", &Base::Func2);printf("%p\n", &Base::Func3);return 0;
}
通过printf可以观察到函数的地址
对于c++来说 普通全局函数通过函数名就可以取到地址 但是类成员函数 除了要指定类域外 还要& 才能取地址 但是cin >> operator >> 重载中会由关于函数名的一些函数 导致无法正常取地址 所以这里使用 printf来取地址
虚函数是放在虚表当中的 那么普通的全局函数又是放在哪儿的呢?
1. 静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也称为静态多态, 比如:函数重载
2. 动态绑定又称后期绑定(晚绑定),是在程序运行期间,根据具体拿到的类型确定程序的具体 行为,调用具体的函数,也称为动态多态。
通常称普通函数为静态绑定 虚函数为动态绑定
首先先看一段代码
class Base
{
public:virtual void Func1(){cout << "Base::Func1()" << endl;}void Func3(){cout << "Base::Func3()" << endl;}
private:int _b = 1;
};
class Derive : public Base
{
public:virtual void Func1(){cout << "Derive::Func1()" << endl;}
private:int _d = 2;
};
void func1(Base*p)
{p->Func1();p->Func3();
}
int main()
{Base b;Derive d;func1(&b);func1(&d);return 0;
}
一个类中所有的虚函数都会存在各自的虚表当中 子类中如果发生了重写 那么其对应部分虚表位置的函数也会由子类中的同名函数发生替换
多态是看指向的对象是谁 如果是b 那就会进入Base 中去通过虚表指针去找到Base类的虚表 在虚表中找到对应的函数去调用 而如果是d 虽然指针是Base类型 但是确实从Derive类中的Base部分虚表去找对应的函数调用 由于该函数发生重写 已经不再试Base:: 而是变成了Derive类域中的同名函数 所以在调用时也是调用的是子类中的同名函数 产生多态效果 (多态的原理)
而这些找的过程都是在运行时发生的 所以叫做动态绑定
而对于没有重写的普通成员函数 和 全局函数 这些都是在编译时就已经确定的 所以称作静态绑定
虚函数表本质是一个存虚函数指针的指针数组
虚表存的是虚函数指针,不是虚函数
虚函数和普通函数一样的,都是存在代码段的,
虚函数指正是存在对象中的
动态多态 :就是类中的多态
静态多态: 函数重载 函数模版
int main()
{int i = 0;double d = 1.1;cout << i;cout << d;return 0;
}
函数重载 一种静态多态
int main()
{int a = 0;int b = 21;double c = 0.2;double d = 1.6;swap(a,b);swap(c,d);return 0;
}
函数模版也属于一种静态多态
多继承中指针偏移问题?下面说法正确的是( )
class Base1 { public: int _b1; };
class Base2 { public: int _b2; };
class Derive : public Base1, public Base2 { public: int _d; };
int main(){Derive d;Base1* p1 = &d;Base2* p2 = &d;Derive* p3 = &d;return 0;
}
A:p1 == p2 == p3 B:p1 < p2 < p3 C:p1 == p3 != p2 D:p1 != p2 != p3
这里有一个小技巧 那就是对于子类来说 一定是在地址的下面的 而上面放着的是父类部分 而多继承的话 最先继承的部分的地址会在首位 也就是上面 所以p3和p1指向的地址都是首位 都是相同只不过是两者根据类型大小不同所以取到的内容不同 而p2是通过切片中的指针偏移所以取得是中间的位置的地址 所以选C