一 面向对象新需求
1 如果父类指针指向的是父类对象则调用父类中定义的函数,如果父类指针指向的是子类对象则调用子类中定义的重写函数
2 解决方案:
1)C++中通过virtual关键字对多态进行支持
2)使用virtual声明的函数被重写后即可展现多态特性,
3) 当在父类的函数声明中写了virtual关键字,子类中可写可不写。
二 实现多态的三个条件
1 要有继承
2 要有虚函数重写
3 用父类指针(父类引用)指向子类对象….
#include <iostream>
using namespace std;
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;
}
};
void PlayObj(HeroFighter *hf, EnemyFighter *ef)
{
//不写virtual关键字 是静态联编 C++编译器根据HeroFighter类型,去执行 这个类型的power函数 在编译器编译阶段就已经决定了函数的调用
//动态联编: 迟绑定: //在运行的时候,根据具体对象(具体的类型),执行不同对象的函数 ,表现成多态.
if (hf->power() > ef->attack()) //hf->power()函数调用会有多态发生
{
printf("主角win\n");
}
else
{
printf("主角挂掉\n");
}
}
void main()
{
HeroFighter hf;
AdvHeroFighter Advhf;
EnemyFighter ef;
AdvAdvHeroFighter advadvhf;
PlayObj(&hf, &ef);
PlayObj(&Advhf, &ef);
PlayObj(&advadvhf, &ef) ; //这个框架 能把我们后来人写的代码,给调用起来
cout<<"hello..."<<endl;
system("pause");
}
三多态理论基础 :静态联编和动态联编
1联编是指一个程序模块、代码之间互相关联的过程。
2 静态联编(static binding),是程序的匹配、连接在编译阶段实现,也称为早期匹配。重载函数使用静态联编。
3 动态联编是指程序联编推迟到运行时进行,所以又称为晚期联编(迟绑定)。
switch 语句和 if 语句是动态联编的例子。
四 虚析构函数
1 构造函数不能是虚函数。建立一个派生类对象时,必须从类层次的根开始,沿着继承路径逐个调用基类的构造函数。
2 析构函数可以是虚函数。虚析构函数用于指引delete运算符正确析构动态对象。
3 目的:通过父类指针,释放所有的子类资源。
#include <iostream>
using namespace std;
//虚析构函数
class A
{
public:
A()
{
p = new char[20];
strcpy(p, "obja");
printf("A()\n");
}
virtual ~A()//
{
delete [] p;
printf("~A()\n");
}
private:
char *p;
};
class B : public A
{
public:
B()
{
p = new char[20];
strcpy(p, "objb");
printf("B()\n");
}
~B()
{
delete [] p;
printf("~B()\n");
}
private:
char *p;
};
class C : public B
{
public:
C()
{
p = new char[20];
strcpy(p, "objc");
printf("C()\n");
}
~C()
{
delete [] p;
printf("~C()\n");
}
private:
char *p;
};
void howtodelete(A *base)
{
delete base; //virtual如果不定义,这句话不会表现成多态 这种属性
}
void main()
{
C *myC = new C; //new delete匹配
//delete myC; //直接通过子类对象释放资源 不需要写virtual
howtodelete(myC);
cout<<"hello..."<<endl;
system("pause");
return ;
}
五重载重写重定义
1重载
1) 必须在同一个类中进行。
2)子类无法重载父类函数,父类同名函数将被名称覆盖。
3)重载实在编译期间根据参数类型和个数决定函数调用;
2 重写
1)必须发生在父类和子类之间
2)父类与子类必须有完全相同的函数原型
3)使用virtual声明之后能够产生多态,不使用virtual关键字就变成了重定义
4) 多态实在运行期间根据具体对象的类型决定函数的调用。
六 多态原理探究
1 虚函数表在编译的时候就确定了,而类对象的虚函数指针vptr是在运行阶段确定的,这是实现多态的关键!
2实现原理 :
1) 当类中声明虚函数时,编译器会在类中生成一个虚函数表;
2)虚函数表是一个存储类成员函数指针的数据结构
3)虚函数表是由编译器自动生成与维护的
4)virtual成员函数会被编译器放入虚函数表中
5)存在虚函数时,每个对象中都有一个指向虚函数表的指针(vptr指针)
3 证明vptr指针的存在
#include <iostream>
using namespace std;
class Parent1
{
public:
Parent1(int a=0)
{
this->a = a;
}
void print()
{
cout<<"我是爹"<<endl;
}
private:
int a;
};
class Parent2
{
public:
Parent2(int a=0)
{
this->a = a;
}
virtual void print()
{
cout<<"我是爹"<<endl;
}
private:
int a;
};
void main()
{
printf("sizeof(Parent):%d sizeof(Parent2):%d \n", sizeof(Parent1), sizeof(Parent2));
cout<<"hello..."<<endl;
system("pause");
return ;
}
4 构造函数中调用虚函数能发生多态吗?不能
1)对象中的VPTR指针什么时候被初始化?
对象在创建的时,由编译器对VPTR指针进行初始化,
只有当对象的构造完全结束后VPTR的指向,才最终确定,
父类对象的VPTR指向父类虚函数表,
子类对象的VPTR指向子类虚函数表.
2) 子类对象的虚函数指针初始化是分步完成的:
a当执行父类构造函数时,子类的vptr指针指向父类的虚函数表
b当父类构造运行完毕,会把子类vptr指向子类的虚函数表
#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 能发生多态吗?
//c1.print();
cout<<"hello..."<<endl;
system("pause");
return ;
}