多态
虚函数
虚函数就是在类的成员函数声明前加virtual,该成员函数就变成了虚函数。一旦一个类中有虚函数,编译器就会为该类生成虚函数表。
虚函数表中一个元素记录一个虚函数的地址,使用该类构造对象时,对象前4(8)个字节记录虚函数表的首地址。
/02-虚函数和虚函数表/
#include <iostream>
using namespace std;
class Animal{
public:
//成员函数
virtual void show()//虚函数
{
cout<<"Animal show"<<endl;
}
void run()
{
cout<<"Animal run"<<endl;
}
virtual void eat()
{
cout<<"Animal eat"<<endl;
}
};
class Dog:public Animal{
public:
//重写虚函数
virtual void show()//虚函数
{
cout<<"Dog show"<<endl;
}
virtual void eat()
{
cout<<"Dog eat bones"<<endl;
}
};
class Cat:public Animal{
public:
//重写虚函数
virtual void show()//虚函数
{
cout<<"Cat show"<<endl;
}
virtual void eat()
{
cout<<"Cat eat fish"<<endl;
}
};
typedef void (*pfunc_t)();//为函数指针类型起别名
int main()
{
//没有成员变量的类类型对象所占空间大小是1
//cout<<sizeof(Animal)<<endl;
Animal an;
//long addr = *(long *)&an;//取出对象前8个字节的数据
//pfunc_t *pt = (pfunc_t *)addr;//转化为虚函数表首地址类型
pfunc_t *pt = *(pfunc_t **)&an;//取出对象前8个字节的数据 --- 虚函数表的地址
//调用虚函数
pt[0]();//an.show();
pt[1]();//an.eat();
return 0;
}
如果父类中有虚函数,子类中可以对虚函数进行重写(overwrite),子类会继承父类的虚函数表,但是重写的虚函数会覆盖父类中对应虚函数在虚函数表中的位置。
/02-虚函数和虚函数表/
#include <iostream>
using namespace std;
class Animal{
public:
//成员函数
virtual void show()//虚函数
{
cout<<"Animal show"<<endl;
}
void run()
{
cout<<"Animal run"<<endl;
}
virtual void eat()
{
cout<<"Animal eat"<<endl;
}
};
class Dog:public Animal{
public:
//重写虚函数
virtual void show()//虚函数
{
cout<<"Dog show"<<endl;
}
virtual void eat()
{
cout<<"Dog eat bones"<<endl;
}
};
typedef void (*pfunc_t)();//为函数指针类型起别名
int main()
{
Dog dg;
pt = *(pfunc_t **)&dg;//取出对象前8个字节的数据 --- 虚函数表的地址
//调用虚函数
pt[0]();//an===>show();
pt[1]();//dg===>eat();
return 0;
}
如果父类的虚函数在子类中被重写,则可以使用父类类型记录子类对象,此时调用虚函数会去调用子类中虚函数的实现,而不调用父类中的原虚函数,普通的成员函数没有这个特点。
/02-虚函数和虚函数表/
#include <iostream>
using namespace std;
class Animal{
public:
//成员函数
virtual void show()//虚函数
{
cout<<"Animal show"<<endl;
}
void run()
{
cout<<"Animal run"<<endl;
}
virtual void eat()
{
cout<<"Animal eat"<<endl;
}
};
class Dog:public Animal{
public:
//重写虚函数
virtual void show()//虚函数
{
cout<<"Dog show"<<endl;
}
virtual void eat()
{
cout<<"Dog eat bones"<<endl;
}
};
typedef void (*pfunc_t)();//为函数指针类型起别名
int main()
{
//有虚函数,父类类型记录子类对象
Animal *pa = new Dog; //可以会有指向问题,不建议这样使用
pa->show(); //有虚函数就可以这样写
pa->run();
pa->eat();
delete pa;
return 0;
}
使用虚函数可以实现用父类类型记录子类对象,可以通过父类型的 指针/引用 访问子类中对应的接口,大大提高编程的灵活性(动态绑定),这种语法就叫多态。(简单的是父类指向子类)
#include <iostream>
using namespace std;
class Animal{
public:
//虚析构
virtual ~Animal()
{
cout<<"~Animal"<<endl;
}
//成员函数
virtual void show()//虚函数
{
cout<<"Animal show"<<endl;
}
void run()
{
cout<<"Animal run"<<endl;
}
virtual void eat()
{
cout<<"Animal eat"<<endl;
}
};
class Dog:public Animal{
public:
virtual ~Dog()
{
cout<<"~Dog"<<endl;
}
//重写虚函数
virtual void show()//虚函数
{
cout<<"Dog show"<<endl;
}
void run()//名字隐藏
{
cout<<"Dog run"<<endl;
}
virtual void eat()
{
cout<<"Dog eat bones"<<endl;
}
};
class Cat:public Animal{
public:
virtual ~Cat()
{
cout<<"~Cat"<<endl;
}
//重写虚函数
virtual void show()//虚函数
{
cout<<"Cat show"<<endl;
}
virtual void eat()
{
cout<<"Cat eat fish"<<endl;
}
};
//传入一个Animal对象 ----- 多态
void animal_gogo(Animal *p)
{
p->show();
p->eat();
}
int main()
{
Animal *pa = new Animal;
//pa->show();
//pa->run();
//pa->eat();
animal_gogo(pa); //动态接口绑定
delete pa;
//有虚函数,父类类型记录子类对象
pa = new Dog;
//pa->show();
//pa->run();
//pa->eat();
animal_gogo(pa);
delete pa;
pa = new Cat;
//pa->show();
//pa->run();
//pa->eat();
animal_gogo(pa);
delete pa;
//Dog dg;
//Animal &ra = dg;
//ra.show(); //这样也是多态
//ra.Animal::show();
return 0;
}
几种重名机制的处理
函数重载(overload)
名字隐藏(namehide)
虚函数重写(overwrite)
函数重载:在同一作用域,函数名相同,参数列表不同的函数构成重载关系 名字隐藏:子类中出现与父类中同名的成员,子类中父类的同名成员会被隐藏 虚函数重写:子类中重写父类的虚函数,父类的虚函数在子类的虚函数表中将被覆盖(覆盖是虚函数表)
多态的总结
通过父类 指针/引用 记录子类对象,调用虚函数时体现的是子类中虚函数的实现。
1.继承是多态的基础 2.虚函数是实现多态的关键 3.虚函数重写是实现多态的必要条件
练习:
实现一个类Phone,有成员price和虚函数show,func,继承产生SmartPhone,重写其虚函数,SmartPhone继承产生IPhone,重写其虚函数。
实现一个全局函数void Use_Phone(Phone &p),参数时Phone的引用,在函数中传递不同对象调用该函数。
#include <iostream>
using namespace std;
class Phone {
public:
//虚析构
Phone(double p = 0.0) :price(p)
{
}
//成员函数
virtual void show()//虚函数
{
cout << "Phone show" << endl;
}
virtual void func()//虚函数
{
cout << "Phone 打电话" << endl;
}
double get_pricr() {
return this->price;
}
private:
double price;
};
class SmartPhone :public Phone {
public:
SmartPhone(double p = 0.0) :Phone(p)
{
}
//重写虚函数
virtual void show()//虚函数
{
cout << "SmartPhone show" << endl;
}
virtual void func()//虚函数
{
cout << "SmartPhone 打电话,听音乐" << endl;
}
};
class IPhone :public SmartPhone {
public:
IPhone(double p = 0.0) :SmartPhone(p)
{
}
//重写虚函数
virtual void show()//虚函数
{
cout << "IPhone show" << endl;
}
virtual void func()//虚函数
{
cout << "IPhone 打电话,听音乐,刷抖音,Wechat" << endl;
}
};
//传入一个Animal对象 ----- 多态
void usePhone(Phone &p){
p.show();
p.func();
}
int main()
{
Phone nokia(300);
SmartPhone xiaomi(2000);
IPhone iphone13(6000);
usePhone(nokia);
usePhone(xiaomi);
usePhone(iphone13);
return 0;
}
另一种写法
但是这个方法在delate时会调用析构父类的方法,引入虚析构加上关键字
#include <iostream>
using namespace std;
class Phone {
public:
Phone(double p = 0.0) :price(p)
{
}
//成员函数
virtual void show()//虚函数
{
cout << "Phone show " << this->get_price() << endl;
}
virtual void func()//虚函数
{
cout << "Phone 打电话" << endl;
}
double get_price() {
return this->price;
}
private:
double price;
};
class SmartPhone :public Phone {
public:
SmartPhone(double p = 0.0) :Phone(p)
{
}
//重写虚函数
virtual void show()//虚函数
{
cout << "SmartPhone show " << this->get_price() << endl;
}
virtual void func()//虚函数
{
cout << "SmartPhone 打电话,听音乐" << endl;
}
};
class IPhone :public SmartPhone {
public:
IPhone(double p = 0.0) :SmartPhone(p)
{
}
//重写虚函数
virtual void show()//虚函数
{
cout << "IPhone show " << this->get_price() << endl;
}
virtual void func()//虚函数
{
cout << "IPhone 打电话,听音乐,刷抖音,Wechat" << endl;
}
};
//传入一个Animal对象 ----- 多态
void usePhone(Phone* p) {
p->show();
p->func();
}
int main()
{
Phone* nokia = new Phone(300);
usePhone(nokia); //动态接口绑定
delete nokia;
SmartPhone* xiami = new SmartPhone(1000);
usePhone(xiami); //动态接口绑定
delete xiami;
IPhone* iphone13 = new IPhone(6000);
usePhone(iphone13); //动态接口绑定
delete iphone13; //这个方法会调用父类的方法
return 0;
}
虚析构
如果父类类型指向子类对象,当释放对象时,默认调用父类的析构函数而不是子类的析构函数。如果在多态中希望根据对象的具体类型调用其析构函数,需要将析构函数写成虚析构(在析构函数前加virtual)。
类中本身有析构函数,同时也有虚函数时必须将析构函数写成虚析构。
#include <iostream>
/* employer */
using namespace std;
class Phone {
public:
//虚析构
/*
~Phone()
{
cout<< " ~Phone show " << endl;
}
*/
virtual ~Phone()
{
cout << " ~Phone show " << endl;
}
Phone(double p = 0.0) :price(p)
{
}
//成员函数
virtual void show()//虚函数
{
cout << "Phone show " << this->get_price() << endl;
}
virtual void func()//虚函数
{
cout << "Phone 打电话" << endl;
}
double get_price() {
return this->price;
}
private:
double price;
};
class SmartPhone :public Phone {
public:
SmartPhone(double p = 0.0) :Phone(p)
{
}
/*
SmartPhone() {
cout << " ~SmartPhone show " << endl;
}
*/
virtual ~SmartPhone() {
cout << " ~SmartPhone show " << endl;
}
//重写虚函数
virtual void show()//虚函数
{
cout << "SmartPhone show " << this->get_price() << endl;
}
virtual void func()//虚函数
{
cout << "SmartPhone 打电话,听音乐" << endl;
}
};
class IPhone :public SmartPhone {
public:
IPhone(double p = 0.0) :SmartPhone(p)
{
}
/*
~IPhone()
{
cout << " ~IPhone show " << endl;
}
*/
virtual ~IPhone()
{
cout << " ~IPhone show " << endl;
}
//重写虚函数
virtual void show()//虚函数
{
cout << "IPhone show " << this->get_price() << endl;
}
virtual void func()//虚函数
{
cout << "IPhone 打电话,听音乐,刷抖音,Wechat" << endl;
}
};
//传入一个Animal对象 ----- 多态
void usePhone(Phone* p) {
p->show();
p->func();
}
int main()
{
Phone* nokia = new Phone(300);
usePhone(nokia); //动态接口绑定
delete nokia;
SmartPhone* xiami = new SmartPhone(1000);
usePhone(xiami); //动态接口绑定
delete xiami;
IPhone* iphone13 = new IPhone(6000);
usePhone(iphone13); //动态接口绑定
delete iphone13; //这个方法会调用父类的方法
return 0;
}
纯虚函数和抽象类
纯虚函数
用virtual修饰,没有语句体,只有声明和(=0)的成员函数
语法:
class A{ //父类 public: virtual void show()=0;//纯虚函数 }
如果类中有纯虚函数,该类不能实例化对象,这种类叫抽象类。
抽象类的应用
抽象类的作用不是用来构造对象,而是用作父类(基类)继承产生子类
子类在继承抽象父类时,如果没有实现父类中所有的纯虚函数,那么子类仍然是一个抽象类。
如果一个类中所有的成员函数都是纯虚函数,那么该类就叫纯抽象类。(只有实现父类,才能用,不然会报错)
一个项目中的抽象类的设计属于框架设计的一部分。用来写实现
C++中类和实现的分离
C++中使用类来组织代码,类的声明写在头文件,类的声明包括成员变量的声明,成员函数的声明,成员变量的声明无需修改,类中的函数只保留声明语句。
类的函数实现写在配对的源文件中,实现类中的函数时应该指定该函数属于哪个类。
头文件(xxx.h xxx.hpp) class 类名{ 成员变量的声明; 成员函数的声明; 构造函数,析构函数,拷贝构造函数的声明; }; 源文件(xxx.cpp) 成员函数的实现; 构造函数,析构函数,拷贝构造函数的实现;
//函数参数的默认值要写到声明中(头文件) //初始化参数列表写到实现中(源文件)
练习:
将employer.cpp拆分为源文件和头文件的分离形式。
#include <cstring>
#include "employer.hpp"
Object::Object(const char *s)
{
cout<<"Object()"<<endl;
if(s){
this->p = new char[strlen(s)+1];
strcpy(this->p, s);
}
else{
this->p = new char[10];
memset(this->p,0,10);
}
}
//拷贝构造
Object::Object(const Object &o)
{
cout<<"Object(const Object &o)"<<endl;
if(strlen(o.p)){
this->p = new char[strlen(o.p)+1];
strcpy(this->p, o.p);
}
else{//空串
this->p = new char[10];
memset(this->p,0,10);
}
}
Object::~Object()
{
cout<<"~Object()"<<endl;
delete[] this->p;
}
//获取
const char *Object::get_p()
{
return this->p;
}
//普通人类
Person::Person(const char *s,string name):Object(s),p_name(name)
{
cout<<"Person()"<<endl;
}
//获取
string Person::get_pname()
{
return this->p_name;
}
//公司类
Comp::Comp(const char *s,string name):Object(s),c_name(name)
{
cout<<"Comp()"<<endl;
}
//获取
string Comp::get_cname()
{
return this->c_name;
}
//员工类
Employer::Employer(const char *s,string pname,string cname,double salary):
Object(s),Person(s,pname),Comp(s,cname),salary(salary)
{
cout<<"Employer()"<<endl;
}
void Employer::show()
{
cout<<this->get_pname()<<":"<<this->get_cname()<<":"<<
this->get_p()<<":"<<this->salary<<endl;
}