一.多态原理
静态多态:函数重载
动态多态:函数重写 virtual关键字
静态联编:地址早绑定,编译阶段绑定好地址
动态联编:在运行阶段确定函数地址
多态:父类的引用或者指针指向子类对象
原理:对于一个只有成员函数的类 其大小为1 如果给成员函数添加virtual关键字,则大小变为4,这是由于该类内部出现一个指针 vfptr 虚函数表指针,指向虚函数表中储存的指向该类内的成员函数的地址,如果该子类没有声明与父类virtual函数相同的同名函数,或者声明了与父类中没加virtual的同名函数,构造函数中,子类的vfptr会拥有与父类的虚函数表相同的虚函数表 指向&Animal::speak,有子类继承该父类时,如果使用与父类中virtual声明的同名函数,叫做重写,重写必须返回值 参数 类型 顺序都相同 ,子类会将自己虚函数表中的父类函数地址改为自己的函数地址,vfptr会指向子类自己的虚函数表中该函数的地址 &Cat::speak()
Animal *animal = new Cat;
animal.speak(); 父类指针指向子类对象 调用子类重写后的函数
class Animal
{
public:
virtual void speak()
{
cout<<"Animal Speak"<<endl;
}
virtual void eat()
{
cout<<"Animal eat"<<endl;
}
};
class Cat:public Animal
{
public:
void speak()
{
cout<<"Cat Speak"<<endl;
}
//子类中是否加virtual没影响
virtual void eat()
{
cout<<"Cat eat"<<endl;
}
};
//如果存在继承关系,编译器允许进行类型转换
void doSpeak(Animal& animal)
{
animal.speak();
}
static void test01()
{
Cat cat;
// doSpeak(cat); //打印结果 Animal Speak 原因 调用doSpeak时 绑定好了Animal中speak函数的地址 属于早绑定,进行静态联编,编译阶段就确定好了地址
//如果想调用Cat的speak,不能提前绑定好函数的地址,需要在运行时再去确定函数地址 即动态联编,在父类的成员函数前加virtual关键字,即虚函数
//添加virtual后效果
doSpeak(cat); //打印 Cat Speak
//静态多态:函数重载
//动态多态:函数重载
//静态联编:地址早绑定,编译阶段绑定好地址
//动态联编:在运行阶段确定函数地址
//多态:父类的引用或者指针指向子类对象
}
二.多态案例:计算器
真正的开发中,有个原则:开闭原则。
对扩展开放,对修改关闭。
使用多态 利于后期扩展 结构性好 可读性高 但是效率会变低 结构变复杂。
利用多态 在需要加入新的功能时,只需要添加相应的类即可。
普通虚函数:类内声明,类内实现,子类可以选择实现或者不实现。
纯虚函数:只声明,不实现,子类必须实现
如果父类有纯虚函数 子类中必须实现纯虚函数,否则该子类也是个抽象类(不能实例化对象)
如果父类有纯虚函数,该父类不能实例化对象
这个类有了纯虚函数,通常称为抽象类。
class abstractCalculator
{
public:
void setval1(int a)
{
this->val1 = a;
}
void setval2(int b)
{
this->val2 = b;
}
// //虚函数
// virtual int getResult()
// {
// return 0;
// }
//如果父类有纯虚函数 子类中必须实现纯虚函数,否则该子类也是个抽象类(不能实例化对象)
//如果父类有纯虚函数,该父类不能实例化对象
//这个类有了纯虚函数,通常称为抽象类。
virtual int getResult() = 0;
int val1;
int val2;
};
class PulsCalculator:public abstractCalculator
{
public:
virtual int getResult()
{
return val1 + val2;
}
};
class SubCalculator:public abstractCalculator
{
public:
virtual int getResult()
{
return val1 - val2;
}
};
class subclass :public abstractCalculator
{
public:
int getResult()
{
return 0;
}
};
void test003()
{
abstractCalculator* puls = new PulsCalculator;
puls->setval1(10);
puls->setval2(20);
cout<<puls->getResult()<<endl;
delete puls;
puls = new SubCalculator;
puls->setval1(10);
puls->setval2(20);
cout<<puls->getResult()<<endl;
subclass subc; //如果该子类不实现父类的纯虚函数 则报错 所以必须在子类中做实现
//报错 含有纯虚函数的类不能实例化对象
// abstractCalculator aaa;
// abstractCalculator *aa= new abstractCalculator;
}
三.虚析构和纯虚析构
1.虚析构
普通析构是不会调用子类的析构的,所以在使用父类指针指向子类对象时,释放时,可能导致子类释放不干净。
class Animal1
{
public:
virtual void speak()
{
cout<<"animal speak"<<endl;
}
//普通析构是不会调用子类的析构的,所以在使用父类指针指向子类对象时,可能导致子类释放不干净
//利用虚析构解决 在虚构函数前加virtual
virtual ~Animal1()
{
cout<<"Animal1 xigou"<<endl;
}
};
class Cat1:public Animal1
{
public:
Cat1(const char* name)
{
this->m_Name = new char[strlen(name)+1];
strcpy(this->m_Name,name);
}
~Cat1()
{
cout<<"Cat1 xigou"<<endl;
if(this->m_Name != NULL)
{
delete[] m_Name;
this->m_Name = NULL;
}
}
virtual void speak()
{
cout<<"cat speak"<<endl;
}
char* m_Name;
};
void test004()
{
Animal1* animal = new Cat1("TOM");
animal->speak();
//查看调用了哪个析构
delete animal; //打印: Animal1 xigou
//父类不使用虚析构时,调用了Animal1类的析构函数,不会调用子类的析构函数,和普通函数相同,所以需要父类使用虚析构函数
}
2.纯虚析构
纯虚析构需要类内声明,类外实现
出现纯虚析构函数,该类也是抽象类,不可实例化
子类虚构函数必须实现,否则也是抽象类
使用父类指针指向子类对象时,如果父类使用纯虚析构函数,则在释放时,先析构子类,再析构父类。
//纯虚析构
class Animal2
{
public:
virtual void speak()
{
cout<<"animal speak"<<endl;
}
//纯虚析构
//纯虚析构需要类内声明,类外实现
//出现纯虚析构函数,该类也是抽象类,不可实例化
//类内声明纯虚析构
virtual ~Animal2() = 0;
};
//纯虚析构的类外实现
Animal2::~Animal2()
{
cout<<"Animal2 Pure virtual destructor"<<endl;
//Animal2的纯虚析构调用
}
class Cat2:public Animal2
{
public:
Cat2(const char* name)
{
this->m_Name = new char[strlen(name)+1];
strcpy(this->m_Name,name);
}
~Cat2()
{
cout<<"Cat1 xigou"<<endl;
if(this->m_Name != NULL)
{
delete[] m_Name;
this->m_Name = NULL;
}
}
virtual void speak()
{
cout<<"cat speak"<<endl;
}
char* m_Name;
};
void test005()
{
// //报错 出现纯虚析构函数,该类也是抽象类,不可实例化
// Animal2 an;
// Animal2 *ann;
Animal2* animal = new Cat2("TOM");
animal->speak();
//查看调用了哪个析构
delete animal; // 打印:Cat1 xigou Animal2 Pure virtual destructor
//先析构子类,再析构父类
}