我们半世相逢,依旧少年如风。
多态
多态是C++面向对象的三大特性(封装,继承,多态)之一。它可以使同名函数在不同的环境下表现出不同的行为。
多态有两种:
静态多态,在编译阶段确定函数地址,包括函数重载和运算符重载,以复用函数名的方式实现。
动态多态,在运行阶段确定函数地址,实现是在基类中编写虚函数,在子类中重写虚函数,调用时可以定义父类的指针或者引用,来指向子类对象,指向那个子类对象就调用那个子类的虚函数,这就是我们常说的实现父类指针或者引用指向子类对象。
虚函数语法:virtual 返回值 函数名(参数列表){具体实现}。
#include <iostream>
using namespace std;
class Person
{
public:
virtual void printName()//使用virtual关键字将函数变为虚函数,编译器在编译的时候无法确定函数调用
{
cout << "此人姓名" << endl;
}
};
class Chinese:public Person
{
public:
void printName()//在子类中重写虚函数,可以省略virtual
//重写与重载不同,重写函数的返回值类型、函数名字、参数列表必须完全一致,重载仅要求同名
{
cout << "张三三" << endl;
}
};
class English:public Person
{
public:
void printName()
{
cout << "亚瑟" << endl;
}
};
//我们希望传入什么对象,那么就调用什么对象的函数
void DoPrintName(Person& p)
{
p.printName();
}
void test()
{
Chinese c1;
English e1;
DoPrintName(c1);//父类指针或引用可以指向子类对象,
DoPrintName(e1);
}
int main()
{
test();
system("pause");
return 0;
}
在上述案例中,Person类在通过virtual关键字定义printName函数时,类的内存大小为4,是因为使用virtual关键字定义函数时,会产生一个vfptr(虚函数指针)指向虚函数表,虚函数表中存放虚函数的地址。子类继承了父类的虚函数指针跟虚函数表,当子类中重写虚函数时,会将虚函数表中的该函数地址替换为子类中重新定义的虚函数地址。
纯虚函数和抽象类
通常情况下多态中,父类是实现并没有什么意义,主要是调用子类重写的内容,因而实在编程中可以将虚函数写为纯虚函数,含有纯虚函数的类也被称为抽象类,抽象类无法实例化对象也必须在子类中重写抽象类的纯虚函数,否则子类也属于抽象类。
纯虚函数语法:virtual 返回值类型 函数名 (参数列表)=0;
#include <iostream>
using namespace std;
class Person
{
public:
virtual void printName() = 0;//纯虚函数
};
class Chinese:public Person
{
public:
void printName()
{
cout << "张三三" << endl;
}
};
void test()
{
Person* p1 =new Chinese ;//父类的指针指向子类的对象
p1->printName();
delete p1;
}
int main()
{
test();
system("pause");
return 0;
}
虚析构和纯虚析构
多态使用是,如果子类的属性被开辟到了堆区,通过父类指针在释放时就无法调用子类的析构代码。可以通过将父类中的析构函数写为虚析构或者纯虚析构来解决此问题。
纯虚析构与纯虚函数不同,他有具体实现,主要是为了能够清理类在堆上开辟的内存。
含有纯虚函数的类也属于抽象类,无法实例化对象。
虚析构语法:virtual ~类名(){具体实现}。
纯析构语法:virtual ~类名()=0;
类名::~类名(){具体实现}。
#include <iostream>
using namespace std;
class Person
{
public:
virtual void printName() = 0;//纯虚函数
Person()
{
cout << "这是Person类的构造函数函数" << endl;
};
virtual ~Person()= 0;
};
Person::~Person()
{
cout << "这是Person的析构函数" << endl;
}
class Chinese:public Person
{
public:
Chinese(string name)
{
cout << "这是Chinese类的构造函数" << endl;
c_name = new string(name);
};
~Chinese()
{
cout << "这是chinese类的构造函数" << endl;
if (c_name != NULL)
{
delete c_name;
c_name = NULL;
}
}
void printName()
{
cout << "张三三" << endl;
}
string* c_name;
};
void test()
{
Person* p1 = new Chinese("Tom");//创建时需要传参,是因为Chinese的构造函数是有参的
p1->printName();
delete p1;
}
int main()
{
test();
system("pause");
return 0;
}
如果子类没有在堆区开辟数据,那么可以不写虚析构和纯虚析构。