类型兼容性原则-重写
重写:发生在继承关系,父类和子类都有相同的函数原型(成员覆盖)
重载:同名不同作用(重载的函数原型是不用)
相同点:名字相同
虚函数
语法: virtual<类型> 成员函数(<参数列表>)
必须是基类中成员函数,且当基类的某个成员函数定义为虚函数的时候
他所有的派生类中与基类虚函数相同(函数原型相同)的函数都是虚函数
基类中声明过virtual,派生类中不需要再声明(不用再加virtual)
*通常情况下,基类的某个成员函数被定义为虚函数时,要在派生类中对他重新定义也就是重写,否则这样的虚函数时无意义的。
多态成立的三个条件
1、要有继承
2、要有虚函数
3、要用基类的指针或者时引用指向派生类对象;
如何抑制多态的实现,加域解析附::
#include <iostream>
using namespace std;
class student
{
public:
virtual void show()
{
cout<<"student show"<<endl;
}
};
class Graduate:public student
{
public:
void show()
{
cout<<"Graduate show"<<endl;
}
};
int main()
{
student S,*ptr;//当没有virtual时,早期联编后ptr指针已经确定为student类型,不管后面做什么操作,ptr->show()永远是student show。不管ptr指向哪个派生类,都不会改变为派生类版本。解决方法就是在基类中加入virtual。
Graduate G;
ptr=&S;
ptr->show();
ptr=&G;
ptr->show();
G.show();
return 0;
}
运行结果:
没有virtual时:
student show
student show
Graduate show
加了virtual后:
student show
Graduate show
Graduate show
静态联编和动态联编
1、联编是指一个程序模块、代码之间互相关联的过程。
2、静态联编(static binding),是程序的匹配、连接在编译阶段实现,也称为早期匹配。
重载函数使用静态联编。
3、动态联编是指程序联编推迟到运行时进行,所以又称为晚期联编(迟绑定)。
switch 语句和 if 语句是动态联编的例子。
4、理论联系实际
1、C++与C相同,是静态编译型语言
2、在编译时,编译器自动根据指针的类型判断指向的是一个什么样的对象;所以编译器认为父类指针指向的是父类对象。
3、由于程序没有运行,所以不可能知道父类指针指向的具体是父类对象还是子类对象
从程序安全的角度,编译器假设父类指针只指向父类对象,因此编译的结果为调用父类的成员函数。这种特性就是静态联编。
多态
多态原理:
1、虚函数是在运行时候完成,所以需要寻址
普通函数在编译时就确定了需要调用的函数
2、寻址(时间)+虚函数表(空间)虚函数是要消耗更多的时间和空间
处于效率考虑,谨慎添加虚函数
#include <iostream>
using namespace std;
class Cshape
{
protected:
int x;
int y;
public:
Cshape(int x,int y):x(x),y(y){};
virtual double get_area()
{
return 0;
}
/*虚函数表(virtual声明会额外申请一块存储空间)
VPTR指针指向这个虚函数表
CShape | double get_V()
BALL | double get_V() (存的是地址)
Rectangle | double get_V()*/
};
class Ball:public Cshape
{
protected:
int r;
public:
Ball(int r,int x,int y):Cshape(x,y)
{
this->r=r;
}
double get_area()
{
return 3.14*r*r;
}
};
class Rectangle:public Cshape
{
protected:
double z;
public:
Rectangle(int z,int x,int y):Cshape(x,y)
{
this->z=z;
}
double get_area()
{
return x*y*z;
}
};
void func(Cshape& ptr)//通过函数接口实现多态(常用)更简练看着也舒服
{
cout<<ptr.get_area()<<endl;
}
int main()
{
Ball b1(2,0,5.0);
func(b1); // CShape &p = b1
Rectangle r1(3.0,4.0,5.0);
func(r1); // CShape &p = r1
return 0;
}
构造函数中调用虚函数
这是没有意义的!
为什么多态无效:
因为构造的顺序是先有基类
在构造基类的时候,派生类还没有开始构造,自然就没有函数重写,也就没有多态
虚析构!!
重点!
何时使用:当将基类指针或引用 new运算符指向派生类实例时候
作用:为了在释放派生类实例时能够调用派生类的析构函数,
必须将析构函数设置为虚函数
int main()
{
Base* ptr = new Derived;//这一步其实包含了好几步,1、Base *ptr 2、Derived d 3、ptr= &d;
delete ptr;//ptr是一个基类的指针,在释放时编译器只会释放ptr,但是d却没有释放。用虚析构函数就可以指引编译器将所有涉及的对象去调用响应的析构函数;
return 0;
}
不要用父类指针指向子类数组
#include <iostream>
using namespace std;
class Test6A
{
protected:
int a;
public:
Test6A(){}
virtual void print ()
{
cout << "base" << endl;
}
};
class Test6B:public Test6A
{
protected:
int b;
public:
Test6B(int b)
{
this->b = b;
}
void print()
{
cout << "b:" << b << endl;
}
};
int main()
{
Test6B b[5] = {Test6B(1),Test6B(2),Test6B(3),Test6B(4),Test6B(5)};
Test6B *pb = b;
Test6A *p = b; //父类指针指向子类数组
pb->print();
p->print();
pb++; //Test6B *
p++; //Test6A * 指针运算是按照指针类型操作的
//他们俩所占空间不一样,所以指针运算的步长不一致
//如果父类指针指向子类数组 会出现段错误
cout << pb<< endl;
cout << p << endl; //两个地址不一致
pb->print();
p->print();
cout << pb<< endl;
cout << p << endl;
delete[] p;
return 0;
}
纯虚函数与抽象类
为什么会有抽象类:
像animal,Shape,Clothes。。。有些基类往往都有些抽象的概念
作用:
func(),提供统一接口,可以将接口和实现分开抽象
抽象类使用规则:
1、抽象类只能做基类,不能建立实例(不可以创建对象)
2、抽象类不能做参数类型/返回值类型/显示类型转换
3、可以声明抽象类的指针/引用,仅仅时用来指向派生类,实现多态
如何定义:
!!不可以直接定义
间接:
(很不常用)1、将该类所有构造函数设置为private/protected
(常用) 2、在该类声明纯虚函数,他就是抽象类
virtual<返回值类型> 函数名 (<参数列表>)=0;!!!后面的=0不能少;
纯虚函数使用规则:
1、声明纯虚函数时,不可以定义纯虚函数实现部分;
因此在重写纯虚函数之前不能调用;
2、纯函数后面的=0没有实际意义,可以理解为仅仅是个标志
3、在定义具有春旭函数的类的派生类时,必须对纯虚函数重写!
关于多态的小练习
要求实现英雄和皮肤和技能三个机制,设置了两个英雄各三个皮肤。要求可以选择英雄和皮肤并显示,最后输入qwe后打印响应技能
skin.h
#ifndef SKIN_H_INCLUDED
#define SKIN_H_INCLUDED
#include <iostream>
using namespace std;
class Skin
{
protected:
char* name;
public:
char* get_name(){return name;};
virtual void show_name()=0;
};
class tanglang_skin1:public Skin
{
public:
tanglang_skin1()
{
name = "霸天异形";
}
void show_name()
{
cout<<"你选择的是"<<"霸天异形"<<endl;
}
};
class tanglang_skin2:public Skin
{
public:
tanglang_skin2()
{
name = "沙之守护";
}
void show_name()
{
cout<<"你选择的是"<<"沙之守护"<<endl;
}
};
class tanglang_skin3:public Skin
{
public:
tanglang_skin3()
{
name = "死亡绽放";
}
void show_name()
{
cout<<"你选择的是"<<"死亡绽放"<<endl;
}
};
class shizigou_skin1:public Skin
{
public:
shizigou_skin1()
{
name = "铁血猎人";
}
void show_name()
{
cout<<"你选择的是"<<"铁血猎人"<<endl;
}
};
class shizigou_skin2:public Skin
{
public:
shizigou_skin2()
{
name = "暗黑武士";
}
void show_name()
{
cout<<"你选择的是"<<"暗黑武士"<<endl;
}
};
class shizigou_skin3:public Skin
{
public:
shizigou_skin3()
{
name = "霸天战士";
}
void show_name()
{
cout<<"你选择的是"<<"霸天战士"<<endl;
}
};
#endif // SKIN_H_INCLUDED
hero.h
#ifndef HERO_H_INCLUDED
#define HERO_H_INCLUDED
#include "skin.h"
class Hero
{
protected:
char* name;
Skin* sk;
public:
char* get_name(){return name;};
Skin* get_skin(){return sk;};
virtual void show_skin()=0;
virtual void set_skin(int cmd)=0;
virtual void show_name()=0;
virtual void show()=0;
virtual void skill1()=0;
virtual void skill2()=0;
virtual void skill3()=0;
};
class tanglang:public Hero
{
public:
tanglang()
{
name = "螳螂";
sk = NULL;
}
void show_skin()
{
cout<<"请选择皮肤:"<<endl;
cout<<"1.霸天异形"<<endl;
cout<<"2.沙之守护"<<endl;
cout<<"3.死亡绽放"<<endl;
}
void show_name()
{
cout<<"你选择的是 "<<name<<endl;
}
void show()
{
cout<<"你选择的是 "<<name<<endl;
cout<<"你选择的皮肤是 "<<sk->get_name()<<endl;
}
void set_skin(int cmd)
{
switch(cmd)
{
case 1:
sk = new tanglang_skin1;
break;
case 2:
sk = new tanglang_skin2;
break;
case 3:
sk = new tanglang_skin3;
break;
}
}
void skill1()
{
cout<<"品尝恐惧"<<endl;
}
void skill2()
{
cout<<"虚空突刺"<<endl;
}
void skill3()
{
cout<<"跃击"<<endl;
}
};
class shizigou:public Hero
{
public:
shizigou()
{
name = "狮子狗";
sk = NULL;
}
void show_name()
{
cout<<"你选择的是 "<<name<<endl;
}
void show()
{
cout<<"你选择的是 "<<name<<endl;
cout<<"你选择的皮肤是 "<<sk->get_name()<<endl;
}
void show_skin()
{
cout<<"请选择皮肤:"<<endl;
cout<<"1.铁血猎人"<<endl;
cout<<"2.暗黑武士"<<endl;
cout<<"3.霸天战士"<<endl;
}
void set_skin(int cmd)
{
switch(cmd)
{
case 1:
sk = new shizigou_skin1;
break;
case 2:
sk = new shizigou_skin2;
break;
case 3:
sk = new shizigou_skin3;
break;
}
}
void skill1()
{
cout<<"残忍无情"<<endl;
}
void skill2()
{
cout<<"战争咆哮"<<endl;
}
void skill3()
{
cout<<"套索打击"<<endl;
}
};
#endif // HERO_H_INCLUDED
main.cpp
#include <iostream>
#include <cstring>
#include <conio.h>
#include "hero.h"
#include "skin.h"
using namespace std;
Hero* Set_hero()
{
Hero* hero;
int cmd;
cout<<"请选择英雄"<<endl;
cin>>cmd;
switch(cmd){
case 1:
hero = new tanglang;
break;
case 2:
hero = new shizigou;
break;
}
hero->show_name();
return hero;
}
void Set_skin(Hero* hero)
{
hero->show_skin();
int cmd;
cin>>cmd;
hero->set_skin(cmd);
hero->get_skin()->show_name();
}
void Game_Start(Hero* hero)
{
cout<<"------------------------"<<endl;
hero->show();
cout<<"-------游戏开始!-------"<<endl;
char skill;
while(1)
{
skill=_getch();
switch( skill ){
case 'q':
hero->skill1();
break;
case 'w':
hero->skill2();
break;
case 'e':
hero->skill3();
break;
}
}
}
int main()
{
cout<<"请选择英雄"<<endl;
cout<<"1、螳螂"<<endl;
cout<<"2、狮子狗"<<endl;
Hero * hero = Set_hero();
Set_skin(hero);
Game_Start(hero);
return 0;
}
运行结果: