C++抽象类小览

本篇csdn博客链接

##一、文章来由

virtual 方法和 virtual 类可以说是c++语言的一大特性,甚至有人说是c++语言的精髓,其实这么说也是有一定道理的,因为运行时多态在c++中体现淋漓尽致,而 virtual 就是为多态服务的。这也是一个一定要搞懂的c++问题,所以有了这篇文章。同时,我觉得这类底层问题不可能一文以蔽之,而且我也相信真正想搞懂这个问题的读者,不会只读我这一篇文章,所以只是小览,同时欢迎讨论和指正。

##二、引入原因

其实,引入纯虚函数的原因我在我另一篇文章虚函数与多态小览就有写,不过重要的话说三遍,还只是两遍呢,哈哈~~知其然也需要知其所以然

###2.1 定义

纯虚函数:纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“=0”。

抽象类:含有一个或以上纯虚函数的类,叫做抽象类,有的地方还有叫纯虚类。我的理解是关于定义,还是选择一个一定正确的说法,也就是选择抽象类来叫更好。

但是百度百科关于纯虚类的定义却是另一种说法:所有函数都是纯虚函数的类是纯虚类。纯虚类可以有成员变量。纯虚类不能实例化。

如: virtual void funtion()=0

###2.2 引入原因

1、为了方便使用多态特性,我们常常需要在基类中定义虚拟函数。

2、在很多情况下,基类本身生成对象是不合情理的。

例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。为了解决上述问题,引入了纯虚函数的概念,将函数定义为纯虚函数(方法:virtual ReturnType Function()= 0;),则编译器要求在派生类中必须予以重写以实现多态性。同时含有纯虚拟函数的类称为抽象类,它不能生成对象。这样就很好地解决了上述两个问题。

这样就清楚有抽象类的原因了吧~~

##三、抽象类的特性

###3.1 直接上特性

罗列的不一定有顺序,但是尽量罗列到,欢迎补充

1、首先说明:抽象类是区别于虚类的。虚类可以实例化,但是抽象类不能被实例化,也就永远没有对象。。。。。。T.T(永远没有对象,好惨!!)

2、如果子类没有实现纯虚函数,相当子类也有纯虚函数,所以子类也是抽象类。

3、其他类的定义与使用方式都与一般类差不多,大致有如下地方:

1)抽象类可以有成员变量 (可以) 2)抽象类可以有普通的成员函数 (可以) 3)抽象类可以有其他虚函数 (可以) 4)抽象类可不可以有带有参数的构造函数?(可以) 5)可不可以在抽象类的派生类的构造函数中显式调用抽象类的带参数构造函数 (可以)

###3.2、一篇文章对抽象类的描述

一篇文章中这样写道:

1、抽象类是一种特殊的类,它是为了抽象和设计的目的而建立的,它处于继承层次结构的较上层(而不是绝对的上层,也有可能是中层,甚至底层)。

2、在实际中为了强调一个类是抽象类,可将该类的构造函数(设置为protected) 说明为保护的访问控制权限。

文章还写道:

3、抽象类的主要作用是将有关的组织在一个继承层次结构中,由它来为它们提供一个公共的根 (其实不一定是根),相关的子类是从这个根派生出来的。

4、抽象类刻画了一组子类的操作接口的通用语义,这些语义也传给子类。一般而言,抽象类只描述这组子类共同的操作接口,而完整的实现留给子类。

5、抽象类只能作为基类来使用(大多数情况是其他类的基类,但是抽象类本身也有可能是子类)。

6、可以定义指向抽象类的指针和引用,此指针可以指向它的派生类,进而实现多态性。

##四、抽象类例子

作为接口使用的例子:

#include<iostream>
#include<string>
#include<vector>
#include<algorithm>
using namespace std;

class animal
{
protected:
	animal(){}
	virtual void  eat(const string name) =0;
};


class dog:public animal
{
public:
	vector<string> m_food;

	void eat(const string name);
	void push_back(const string name);
};
		 
void dog::eat(const string name)
{
	vector<string>::iterator iter=std::find(m_food.begin(),m_food.end(),name);
	if (m_food.end() !=iter)
	{
		cout<<"Dog eat "<<*iter<<endl;
	}
}

void dog::push_back(const string name)
{
	if (m_food.end() ==std::find(m_food.begin(),m_food.end(),name))
	{
		m_food.push_back(name);
	}
}
		 

int main(void)
{
	dog d;
	d.push_back("bone");
	d.eat("bone");

	return 0;
}

另一个好例子,多态的实例:

#include<iostream>
using namespace std;

const double PI=3.14159;

class Shapes   //抽象类
{
protected:
	int x, y;
public:
	void setvalue(int d, int w=0){x=d;y=w;}
	virtual void disp()=0;//纯虚函数
};

class Square:public Shapes
{
public:
	void disp(){
		cout<<"矩形面积:"<<x*y<<endl;
	}
};

class Circle:public Shapes{
public:
	void disp(){
		cout<<"圆面积:"<<PI*x*x<<endl;
	}
};

int main()
{
	Shapes *ptr[2]; //定义对象指针数组
	Square s1;
	Circle c1;
	ptr[0] = &s1;
	ptr[0]->setvalue(10, 5);
	ptr[0]->disp();
	ptr[1] = &c1;
	ptr[1]->setvalue(10);
	ptr[1]->disp();
	return 0;

}

##五、抽象类与虚函数表

有这么个问题:

我们知道C++中有虚函数的类会有一个对应的虚函数表,那么抽象类有虚表吗,如果有的话怎么调用纯虚函数?

直觉上来讲,应该是有的。可是既然是抽象类,说明其对象永远不会被创建,那么维护个虚表貌似也不是很必要了。

设计个程序来验证一下:

class VirtualBase
{
public:
    VirtualBase()
    {
        // call pure virtual function through a non virtual function in base class's constructor
        non_virtual_fun();
    }

    void non_virtual_fun()
    {
        virtual_fun();
    }

    virtual void virtual_fun() = 0;
};

class Derived: public VirtualBase
{
public:
    virtual void virtual_fun(){}
};

int main(int argc, const char* argv[])
{

    size_t size = sizeof(VirtualBase);

    Derived d;
}

分析:

(1)sizeof(VirtualBase)返回4,而不是代表空类的1,这说明其内部是有一个虚表指针,而有虚表指针,那么极有可能有一个虚表。

(2)通过上面的程序,我们在实例化Derived时可以调入VirtualBase的构造函数,此时可以在watch窗口中查看this指针,的确有一个虚表指针指向一个虚表。

这里写图片描述

在构造函数中通过一个非虚函数调用一个纯虚函数 - 虽然该纯虚函数在Derived中被实现,但此时还在基类的构造函数中,所以,纯虚函数被调用 - 出错

这里写图片描述

抽象类需要虚表,我能想到的一个原因是在多个派生类中override时,我们需要保证被改写的函数是在正确的偏移地址的,为了保证这个地址是正确的,事先准备一个模板还是比较重要的。

—END—


####参考文献

[1] http://blog.csdn.net/goondrift/article/details/19705797 [2] http://blog.csdn.net/Slience_Perseverance/article/details/20536277 [3] http://www.cnblogs.com/baiyanhuang/archive/2011/03/07/1976445.html