1.多态的概念
多态的概念:通俗的说,就是多种形态,去完成某一个行为,不同的对象就会产生不同的行为。
2.多态的定义和实现
2.1定义的构成条件
多态是不同继承关系的类对象,调用同一个函数,产生不同的行为。
2.2构成多态还需要两个条件:
1.基类中必须要有虚函数,派生类要对虚函数进行重写
2.必须通过基类的指针或者引用来调用虚函数
2.3重写的定义
1.基类的函数必须是虚函数
2.派生类的虚函数必须于需要重写的虚函数一致(返回值类型,函数名字,参数类型不需相同)
3.派生类的虚函数virtual可以加也可以不加
4.派生类的虚函数访问权限可以与基类的虚函数访问权限不同,但是基类虚函数的访问权限必须是public
2.4重写的两个例外:
1.协变
重写的虚函数返回值可以不同,但必须分别是基类的指针和派生类的指针,或者基类的引用和派生类的引用。
2.析构函数的重写问题
基类的析构函数如果是虚函数,派生类的析构函数就重写了基类的析构函数,但是这里它们的函数名不同,违背了重写的规则,但是编译器会析构函数名称做特殊的处理,让它们满足重写。所以多态中,析构函数最好写成虚函数。
重写,重载和重定义的对比:
重载:两个函数在同一作用域,函数名字相同
重写:两个函数分别在基类和派生类的作用域中,函数名/参数/返回值必须相同
重定义:两个函数分别在基类和派生类的作用中,函数名相同,两个基类和派生类的函数如果不构成重写就是重定义
3.抽象类
在虚函数后边写上=0,则这个函数就是纯虚函数,包含纯虚函数的类叫做抽象类,抽象类不能实例化出对象,派生类继承也不能实例化出对象,只有重写了纯虚函数,派生类才能实例化出对象。
4.多态的原理
4.1虚函数表
看代码:
class base
{
public:
virtual void fun1()
{
cout <<"fun1" << endl;
}
private:
int _b = 1;
};
int main()
{
base b;
cout << sizeof(base) << endl;
return 0;
}
这个base类的大小是多少?
运行结果我们发现:sizeof(b)=8;
原因就在于:除了_b成员,还多了一个_vfptrz放在对象的前面,对象中这个指针我们叫做虚函数指针。一个含有虚函数的类中至少都有一个虚函数表指针,因为虚函数的地址要放在虚函数表中,虚函数表也称虚表。
看代码:
class base
{
public:
virtual void fun1()
{
cout <<"base::fun1" << endl;
}
virtual void fun2()
{
cout <<"base::fun2" << endl;
}
private:
int _b = 1;
};
class derive :public base
{
public:
virtual void fun1()
{
cout << "derice::fun1" << endl;
}
private:
int _d = 2;
};
int main()
{
base b;
derive d;
cout << sizeof(b) << endl;
cout << sizeof(d) << endl;
return 0;
}
我们用derive继承base,然后重写fun1,增加一个普通函数fun2。
调式结果:
1.派生类对象中也有一个属于自己的虚表指针,d的对象由两部分构成,一部分是从base中继承的成员,另一部分是自己的成员。
2.基类的虚表指针和派生类的虚表指针是不一样的,由于派生类对基类进行了重写。
3.fun2在基类中是虚函数,在派生类的继承下来,放进了虚表。
4.派生类的虚表的生成总结:
a.先将基类的虚表拷贝一份到派生类虚表中
b.如果派生类重写了某个虚函数,同派生类覆盖掉基类的虚函数,
c.派生类自己的虚函数按其在派生类声明的次序增加到派生类虚表的最后
4.2多态的原理
class base
{
public:
virtual void fun()
{
cout << "base::fun" << endl;
}
};
class derive :public base
{
public:
virtual void fun()
{
cout << "derice::fun1" << endl;
}
};
void fun1(base &b)
{
b.fun();
}
int main()
{
base b;
fun1(b);
derive d;
fun1(d);
return 0;
}
我们得到的结果就是:传base类的对象,调用的是base类的虚函数,传derive类的对象,调用的是derive的虚函数。
这就体现了多态的定义:调用同一个函数却展现出不同的形态。
对代码多增加一个虚函数
class base
{
public:
virtual void fun()
{
cout << "base::fun" << endl;
}
virtual void fun1()
{
cout << "base::fun1" << endl;
}
};
class derive :public base
{
public:
virtual void fun()
{
cout << "derice::fun" << endl;
}
virtual void fun1()
{
cout << "derice::fun1" << endl;
}
};
void fun2(base &b)
{
b.fun();
b.fun1();
}
int main()
{
base b;
fun2(b);
derive d;
fun2(d);
return 0;
}
通过汇编语言我们可以很清楚的看出调用虚函数的过程到底发生了什么?
满足多态的函数调用,不是在编译时确定的,是运行起来到对象中取寻找的,而非多态的函数调用才是编译时确定好的。
4.3动态绑定和静态绑定
1.静态绑定就是在程序运行前就确定了程序的行为,也称静态多态
2.动态绑定是程序运行期间,根据自己拿到的类型确定程序的具体行为,调用具体的函数。
5.单继承和多继承关系的虚函数表
5.1单继承的虚函数表
typedef void(*pppp)();
class base
{
public:
virtual void fun1()
{
cout << "base::fun1" << endl;
}
virtual void fun2()
{
cout << "base::fun2" << endl;
}
private:
int _b = 1;
};
class derive :public base
{
public:
virtual void fun1()
{
cout << "derice::fun1" << endl;
}
virtual void fun4()
{
cout << "derice::fun4" << endl;
}
virtual void fun3()
{
cout << "derice::fun3" << endl;
}
private:
int _d = 2;
};
int main()
{
base b;
derive d;
pppp*pb = (pppp*)*(int*)&b;
cout << "虚表地址" << endl;
for (int i = 0; pb[i] != NULL; i++)
{
cout << "第" << i << "个虚函数地址是" << pb[i];
pppp test = pb[i];
test();
}
cout << endl;
pppp*pd = (pppp*)*(int*)&d;
for (int i = 0; pd[i] != NULL; i++)
{
cout << "第" << i << "个虚函数地址是" << pd[i];
pppp test = pd[i];
test();
}
cout << endl;
return 0;
}
打印得到虚函数表:
5.2多继承的虚函数表
typedef void(*pppp)();
class base1
{
public:
virtual void fun1()
{
cout << "base1::fun1" << endl;
}
virtual void fun2()
{
cout << "base1::fun2" << endl;
}
private:
int _b1 = 1;
};
class base2
{
public:
virtual void fun1()
{
cout << "base2::fun1" << endl;
}
virtual void fun2()
{
cout << "base2::fun2" << endl;
}
private:
int _b2 = 1;
};
class derive :public base1,public base2
{
public:
virtual void fun1()
{
cout << "dericv::fun1" << endl;
}
virtual void fun3()
{
cout << "dericv::fun3" << endl;
}
private:
int _d = 2;
};
int main()
{
derive d;
pppp*pb = (pppp*)*(int*)&d;
cout << "虚表地址" << endl;
for (int i = 0; pb[i] != NULL; i++)
{
cout << "第" << i << "个虚函数地址是" << pb[i];
pppp test = pb[i];
test();
}
cout <<"------------------------------------------" <<endl;
pppp*pd = (pppp*)*(int*)((char*)&d+sizeof(base1));
for (int i = 0; pd[i] != NULL; i++)
{
cout << "第" << i << "个虚函数地址是" << pd[i];
pppp test = pd[i];
test();
}
cout << endl;
return 0;
}
由上可以看出,多继承中派生类只要将虚函数重写,派生类的虚函数就会替换继承的基类的虚函数,但是,多继承中,派生类没有重写的虚函数会放在第一个继承基类的虚表当中。