问题引出
如果子类定义了与父类中原型相同的函数会发生什么?
函数重写
在子类中定义与父类中原型相同的函数
函数重写只发生在父类与子类之间#include<iostream>
using namespace std;
class Parent
{
public:
void print()
{
cout << "Parent:print() do..." << endl;
}
};
class Child : public Parent
{
public:
void print()
{
cout << "Child:print() do..." << endl;
}
};
int main()
{
Child child;
Parent *p = NULL;
p = &child;
p->print(); //parent
child.print(); //child
child.Parent::print(); //parent
system("pause");
return 0;
}
默认情况下子类中重写的函数将隐藏父类中的函数
通过作用域分辨符::可以访问到父类中被隐藏的函数面向对象新需求
编译器的做法不是我们期望的
根据实际的对象类型来判断重写函数的调用
如果父类指针指向的是父类对象则调用父类中定义的函数
如果父类指针指向的是子类对象则调用子类中定义的重写函数
解决方案
Ø C++中通过virtual关键字对多态进行支持
Ø 使用virtual声明的函数被重写后即可展现多态特性
#include <iostream>
using namespace std;
class Parent
{
public:
Parent(int a)
{
this->a = a;
cout<<"Parent a"<<a<<endl;
}
virtual void print() //子类的和父类的函数名字一样
{
cout<<"Parent 打印 a:"<<a<<endl;
}
protected:
private:
int a ;
};
class Child : public Parent
{
public:
Child(int b) : Parent(10)
{
this->b = b;
cout<<"Child b"<<b<<endl;
}
virtual void print() //virtual 父类写了virtual,子类可写 可不写
{
cout<<"Child 打印 b:"<<b<<endl;
}
protected:
private:
int b;
};
void howToPrint(Parent *base)
{
base->print(); //一种调用语句 有多种表现形态...
}
void howToPrint2(Parent &base)
{
base.print();
}
void main()
{
Parent *base = NULL;
Parent p1(20);
Child c1(30);
base = &p1;
base->print(); //执行父类的打印函数
base = &c1;
base->print(); //执行谁的函数 ? //面向对象新需求
{
Parent &base2 = p1;
base2.print();
Parent &base3 = c1; //base3是c1 的别名
base3.print();
}
//函数调用
howToPrint(&p1);
howToPrint(&c1);
howToPrint2(p1);
howToPrint2(c1);
cout<<"hello..."<<endl;
system("pause");
return ;
}
#include <iostream>
using namespace std;
//HeroFighter AdvHeroFighter EnemyFighter
class HeroFighter
{
public:
virtual int power() //C++会对这个函数特殊处理
{
return 10;
}
};
class EnemyFighter
{
public:
int attack()
{
return 15;
}
};
class AdvHeroFighter : public HeroFighter
{
public:
virtual int power()
{
return 20;
}
};
class AdvAdvHeroFighter : public HeroFighter
{
public:
virtual int power()
{
return 30;
}
};
//多态威力
//1 PlayObj给对象搭建舞台 看成一个框架
//15:20
void PlayObj(HeroFighter *hf, EnemyFighter *ef)
{
//不写virtual关键字 是静态联编 C++编译器根据HeroFighter类型,去执行 这个类型的power函数 在编译器编译阶段就已经决定了函数的调用
//动态联编: 迟绑定: //在运行的时候,根据具体对象(具体的类型),执行不同对象的函数 ,表现成多态.
if (hf->power() > ef->attack()) //hf->power()函数调用会有多态发生
{
printf("主角win\n");
}
else
{
printf("主角挂掉\n");
}
}
//多态的思想
//面向对象3大概念
//封装: 突破c函数的概念....用类做函数参数的时候,可以使用对象的属性 和对象的方法
//继承: A B 代码复用
//多态 : 可以使用未来...
//多态很重要
//实现多态的三个条件
//C语言 间接赋值 是指针存在的最大意义
//是c语言的特有的现象 (1 定义两个变量 2 建立关联 3 *p在被调用函数中去间接的修改实参的值)
//实现多态的三个条件
//1 要有继承
//2 要有虚函数重写
//3 用父类指针(父类引用)指向子类对象....
void main()
{
HeroFighter hf;
AdvHeroFighter Advhf;
EnemyFighter ef;
AdvAdvHeroFighter advadvhf;
PlayObj(&hf, &ef);
PlayObj(&Advhf, &ef);
PlayObj(&advadvhf, &ef) ; //这个框架 能把我们后来人写的代码,给调用起来
cout<<"hello..."<<endl;
system("pause");
}
void main1401()
{
HeroFighter hf;
AdvHeroFighter Advhf;
EnemyFighter ef;
if (hf.power() > ef.attack())
{
printf("主角win\n");
}
else
{
printf("主角挂掉\n");
}
if (Advhf.power() > ef.attack())
{
printf("Adv 主角win\n");
}
else
{
printf("Adv 主角挂掉\n");
}
cout<<"hello..."<<endl;
system("pause");
return ;
}
多态的理论基础
静态联编和动态联编
1、联编是指一个程序模块、代码之间互相关联的过程。
2、静态联编(static binding),是程序的匹配、连接在编译阶段实现,也称为早期匹配。
重载函数使用静态联编。
3、动态联编是指程序联编推迟到运行时进行,所以又称为晚期联编(迟绑定)。
switch 语句和 if 语句是动态联编的例子。
理论联系实际
1、C++与C相同,是静态编译型语言
2、在编译时,编译器自动根据指针的类型判断指向的是一个什么样的对象;所以编译器认为父类指针指向的是父类对象。
3、由于程序没有运行,所以不可能知道父类指针指向的具体是父类对象还是子类对象
从程序安全的角度,编译器假设父类指针只指向父类对象,因此编译的结果为调用父类的成员函数。这种特性就是静态联编。理论知识:
Ø 当类中声明虚函数时,编译器会在类中生成一个虚函数表
Ø 虚函数表是一个存储类成员函数指针的数据结构
Ø 虚函数表是由编译器自动生成与维护的
Ø virtual成员函数会被编译器放入虚函数表中
Ø 当存在虚函数时,每个对象中都有一个指向虚函数表的指针(C++编译器给父类对象、子类对象提前布局vptr指针;当进行howToPrint(Parent *base)函数是,C++编译器不需要区分子类对象或者父类对象,只需要再base指针中,找vptr指针即可。)
Ø VPTR一般作为类对象的第一个成员
多态的实现原理
C++中多态的实现原理
当类中声明虚函数时,编译器会在类中生成一个虚函数表
虚函数表是一个存储类成员函数指针的数据结构
虚函数表是由编译器自动生成与维护的
virtual成员函数会被编译器放入虚函数表中
存在虚函数时,每个对象中都有一个指向虚函数表的指针(vptr指针)说明1:
通过虚函数表指针VPTR调用重写函数是在程序运行时进行的,因此需要通过寻址操作才能确定真正应该调用的函数。而普通成员函数是在编译时就确定了调用的函数。在效率上,虚函数的效率要低很多。
说明2:
出于效率考虑,没有必要将所有成员函数都声明为虚函数说明3 :C++编译器,执行run函数,不需要区分是子类对象还是父类对象
构造函数中能调用虚函数,实现多态吗
1)对象中的VPTR指针什么时候被初始化?
对象在创建的时,由编译器对VPTR指针进行初始化
只有当对象的构造完全结束后VPTR的指向才最终确定
父类对象的VPTR指向父类虚函数表
子类对象的VPTR指向子类虚函数表
2)分析过程
画图分析
#include <iostream>
using namespace std;
//构造函数中调用虚函数能发生多态吗?
class Parent
{
public:
Parent(int a=0)
{
this->a = a;
print();
}
virtual void print()
{
cout<<"我是爹"<<endl;
}
private:
int a;
};
class Child : public Parent
{
public:
Child(int a = 0, int b=0):Parent(a)
{
this->b = b;
print();
}
virtual void print()
{
cout<<"我是儿子"<<endl;
}
private:
int b;
};
void HowToPlay(Parent *base)
{
base->print(); //有多态发生 //2 动手脚
}
void main()
{
Child c1; //定义一个子类对象 ,在这个过程中,在父类构造函数中调用虚函数print 是调用父类print()还是子类print()?(此程序所要呈现的问题)
//c1.print();
cout<<"hello..."<<endl;
system("pause");
return ;
}