目录
什么是纯虚函数?
纯虚函数是指被标明为不具体实现的虚拟成员函数。当我们定义一个基类时,当遇到无法定义基类中虚函数的具体实现时,且其实现依赖于不同的派生类。
定义纯虚函数的一般格式为:virtual 返回类型 函数名(参数表)=0;
eg: virtual void fun(int n)=0;
如果某一种行为必须具体到一个对象时才知道这种行为是什么,那么就把它写为纯虚函数。如基类定影动物的叫声,但是针对动物这个类我们无法实现叫声这个函数,需要借助不同的对象即派生类来重写不同的叫声函数,那么我们就可以将基类中叫声函数设置为纯虚函数。
什么是抽象类?
拥有纯虚函数的类称为抽象类
对于抽象类我们需要明白以下几点:
- 抽象类是不能定义对象的,在实际开发中为了强调一个类是抽象类,可以将该类的构造函数说明为保护的访问权限
- 抽象类刻画了一组子类的操作接口的通用语义,这些语义也传给子类。一般而言,抽象类只描绘这组子类共同的操作接口,而完整的实现留给子类去完成
- 抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出。如果派生类没有重新定义纯虚函数,而派生类只是继承基类的纯虚函数,则这个派生类仍然还是一个抽象类。如果派生类中给出了纯虚函数的实现,则该派生类就不在是抽象类了,它是一个可以建立对象的具体类
抽象类的规定:
- 抽象类只能作为其他类的基类,不能建立抽象类对象
- 抽象类不能做参数类型,函数返回类型或显示转化的类型
- 可以定义抽象类的指针和引用,此指针或指针可以指向它的派生类。实现多态
实例:
我们定义形状类,派生出三角形,正方形,长方形,形状类中将面积,周长设置为纯虚函数设置为抽象类,不同的形状都有着不同的面积,周长计算方法,因此继承后改写方法
/*
shape area Girth--- 没法写
抽象类(含有纯虚函数的类 )---作为类族做上面基类出现,派生子类
如果派生出子类,在子类中没有重写基类中的纯虚函数,则当前子类也是抽象类
----在子类中(具体类),要将抽象基类中的所有纯虚函数都实现了
抽象类---不能定义对象,但是可以定义抽象类的指针或引用,指向或者引用具体类的对象
*/
class Shape
{
public:
virtual void Area() = 0; //纯虚函数
virtual void Grith() = 0;
//virtual void fn() = 0;
};
class Circle :public Shape
{
public:
Circle(int ra) :m_pi(3.14), m_ra(ra) {}
virtual void Area()
{
cout << "Circle area : " << m_pi * m_ra * m_ra << endl;
}
virtual void Grith()
{
cout << "Circle grith : " << 2 * m_pi * m_ra << endl;
}
private:
float m_pi;
int m_ra;
};
class Rectangle :public Shape
{
public:
Rectangle(int len, int wid) :m_length(len), m_width(wid) {}
virtual void Area()
{
cout << "Rectangle area : " << m_length * m_width << endl;
}
virtual void Grith()
{
cout << "Rectangle grith : " << 2 * (m_length + m_width) << endl;
}
private:
int m_length;
int m_width;
};
class Triangle :public Shape
{
public:
Triangle(int length) :m_length(length) {}
virtual void Area()
{
cout << "Triangle area : " << endl;
}
virtual void Grith()
{
cout << "Triangle grith : " << 3 * m_length << endl;
}
private:
int m_length;
};
void test(Shape* p)
{
p->Area();
p->Grith();
}
void main()
{
Shape a;//错误 抽象类无法实例化对象
Triangle t(2);
Circle c(2);
Rectangle r(5, 2);
test(&t);
test(&c);
test(&r);
}
虚析构
在C++多态的使用中,我们常常会将基类析构函数设置为虚析构。
virtual ~类名();
虚析构函数是为了避免内存泄露,而且是当子类中会有指针成员变量时才会使用得到的。也就说虚析构函数使得在删除指向子类对象的基类指针时可以调用子类的析构函数达到释放子类中堆内存的目的,而防止内存泄露的
对于下面的代码,我们用堆区开辟派生类对象初始化基类的实例化指针,新建一个类的实例,调用其父类构造,子类构造,然后进行析构
class A { public: A() { cout << "A" << endl; m_i = new int; } ~A() { cout << "~A" << endl; delete m_i; } private: int* m_i; }; class B :public A { public: B() { cout << "B" << endl; } ~B() { cout << "~B" << endl; } }; void main() { A* pb = new B; delete pb; }
然而我们会发现结果为:
A B ~A 并没有调用子类的析构,发生了内存泄漏 如果当前类有指针作为数据成员,必须要写析构函数 如果当前类被继承了,则析构函数写成virtual,为了实现多态,防止内存泄露
我们了解构造函数和析构函数不能被继承。基类的构造和析构满足的工作要求不可能完全适应派生类,因此需要重写构造,析构。
因为父类的析构器不是虚函数,所以父类在调用析构时,调用的就是父类的构造器,所以子类的析构器没有被调用。因为类B析构器没有被使用,所以~B(){}被优化掉了。
为了解决这种问题,我们就需要将父类的析构函数设置为虚析构
因为父类的析构函数是虚函数,且父类的指针指向了子类对象,所以父类在调用析构函数时,会调用子类的析构函数,编译器会在子类的析构函数中安插调用父类析构函数的代码,所以当子类对象的析构函数处理完后,父类的析构函数也会调用。这样一来,子类和父类的析构函数全都会被调用,析构完整。
virtual ~A() { cout << "~A" << endl; delete m_i; }
纯虚析构
对于上述例子也可以使用纯虚析构,达到相同的目的
class A
{
public:
A()
{
cout << "A" << endl;
m_i = new int;
}
virtual ~A() = 0;//属于抽象类,无法实例化对象
private:
int* m_i;
};
//类内声明,类外实现
A::~A()
{
cout << "~A" << endl;
// delete m_i;
}
class B :public A
{
public:
B() { cout << "B" << endl; }
~B() { cout << "~B" << endl; }
};
void main()
{
A* pb = new B;
delete pb;
}
- 如果是纯虚析构,该类属于抽象类,无法实例化对象
注意:
如果子类中没有堆区数据,可以不写虚析构或纯虚析构