[收集]c++抽象类、纯虚函数以及巧用纯虚析构函数实现接口类
来源:互联网 发布:手机一键php建站 编辑:程序博客网 时间:2024/06/06 05:18
在Java、C#中有关键词abstract指明抽象函数、抽象类,但是在C++中没有这个关键词,很显然,在C++也会需要只需要在基类声明某函数的情况,而不需要写具体的实现,那C++中是如何实现这一功能的,答案是纯虚函数。 含有纯虚函数的类是抽象类,不能生成对象,只能派生。他派生的类的纯虚函数没有被改写,那么它的派生类还是个抽象类。定义纯虚函数就是为了让基类不可实例化化,因为实例化这样的抽象数据结构本身并没有意义,或者给出实现也没有意义。
一. 纯虚函数
在许多情况下,在基类中不能给出有意义的虚函数定义,这时可以把它说明成纯虚函数,把它的定义留给派生类来做。定义纯虚函数的一般形式为:
class 类名{
virtual 返回值类型 函数名(参数表)= 0; // 后面的"= 0"是必须的,否则,就成虚函数了
};
纯虚函数是一个在基类中说明的虚函数,它在基类中没有定义,要求任何派生类都定义自己的版本。纯虚函数为各派生类提供一个公共界面。
从基类继承来的纯虚函数,在派生类中仍是虚函数。二. 抽象类
1. 如果一个类中至少有一个纯虚函数,那么这个类被称为抽象类(abstract class)。
抽象类中不仅包括纯虚函数,也可包括虚函数。抽象类中的纯虚函数可能是在抽象类中定义的,也可能是从它的抽象基类中继承下来且重定义的。2. 抽象类特点,即抽象类必须用作派生其他类的基类,而不能用于直接创建对象实例。
一个抽象类不可以用来创建对象,只能用来为派生类提供一个接口规范,派生类中必须重载基类中的纯虚函数,否则它仍将被看作一个抽象类。
3. 在effective c++上中提到,纯虚函数可以被实现(定义)(既然是纯虚函数,为什么还可以被实现呢?这样做有什么好处呢?下文中“巧用纯虚析构函数实现接口类”中将说明这一功能的目的。),但是,不能创建对象实例,这也体现了抽象类的概念。
三. 虚析构函数
虚析构函数: 在析构函数前面加上关键字virtual进行说明,称该析构函数为虚析构函数。虽然构造函数不能被声明为虚函数,但析构函数可以被声明为虚函数。
一般来说,如果一个类中定义了虚函数, 析构函数也应该定义为虚析构函数。
例如:
class B
{
virtual ~B(); //虚析构函数
…
};下面介绍一些实例:
#include <stdio.h>class Animal{public: Animal()//构造函数不能被声明为虚函数 {printf(" Animal construct! \n"); } virtual void shout() = 0; virtual void impl() = 0; virtual ~Animal() {printf(" Animal destory! \n");}; // 虚析构函数};void Animal::impl() // 纯虚函数也可以被实现。{ printf(" Animal: I can be implement! \n");}class Dog: public Animal{public: Dog() {printf(" Dog construct! \n"); } virtual void shout() // 必须要被实现,即使函数体是空的 { printf(" Dog: wang! wang! wang! \n"); } virtual void impl() { printf(" Dog: implement of Dog! \n"); } virtual ~Dog() {printf(" Dog destory! \n");}; // 虚析构函数};class Cat: public Animal{public: Cat() {printf(" Cat construct! \n"); } virtual void shout() // 必须要被实现,即使函数体是空的 { printf(" Cat: miao! miao! miao! \n"); } virtual void impl() { printf(" Cat: implement of Cat! \n"); } virtual ~Cat() {printf(" Cat destory! \n");}; // 虚析构函数};/*Animal f() // error, 抽象类不能作为返回类型{ }void display( Animal a) //error, 抽象类不能作为参数类型{ }*///ok,可以声明抽象类的引用Animal &display(Animal &a){ Dog d; Animal &p = d; return p; }void test_func(){ //Animal a; // error: 抽象类不能建立对象 Dog dog; //ok,可以声明抽象类的指针 Cat cat; //ok,可以声明抽象类的指针 printf("\n"); Animal *animal = &dog; animal->shout(); animal->impl(); printf("\n"); animal = &cat; animal->shout(); animal->impl(); printf("\n");}int main(){ test_func(); while(1); }//result:/*Animal construct!Dog construct!Animal construct!Cat construct!Dog: wang! wang! wang!Dog: implement of Dog!Cat: miao! miao! miao!Cat: implement of Cat!Cat destory!Animal destory!Dog destory!Animal destory!*/(YC:代码已调试无误)
四. 巧用纯虚析构函数实现接口类
c++不像java一样有纯接口类的语法,但我们可以通过一些手段实现相同的功能。
(1)能不能用“protected”实现接口类?
看如下代码:
#include <stdio.h>class A{protected:virtual ~A(){printf(" A: 析构函数 \n");}};class B : public A{public:virtual ~B(){printf(" B: 析构函数 \n");}};int _tmain(int argc, _TCHAR* argv[]){//A* p1 = new A; //error:[1]有问题//delete p1;B* p2 = new B; //ok:[2]没问题,输出结果为:delete p2; /* B: 析构函数 A: 析构函数*/(注意此处还是会调用A的析构函数的,不过编译没问题) //A* p3 = new B;//delete p3; //error:[3] 有问题return 0;}
通过在类中,将类的构造函数或者析构函数申明成protected ,可以有效防止类被实例话,要说实用的话,构造函数是protected更有用,肯定能保证类不会被实例化,而如果析构函数是protected的话,构造函数不是protected的话,还可能存在编译通过的漏洞,如下:
Case1:
class A{protected:A(){printf(" A: A() \n");}};int _tmain(int argc, _TCHAR* argv[]){A* p1 = new A; //编译不通过,无法访问protected构造函数delete p1;return 0;}
Case2:
class A{protected:~A(){printf(" A: ~A() \n");}};int _tmain(int argc, _TCHAR* argv[]){A* p1 = new A; //编译通过,此时因为仅仅是用到了A的构造函数,还不需要它的析构函数return 0;}(附:如果将main中改为:int _tmain(int argc, _TCHAR* argv[]){A a;return 0;}则编译出错,提示无法访问protected成员A::~A().两种情况出现差异的原因是什么?)
Case3:
class A{protected:~A(){printf(" A: ~A() \n");}};int _tmain(int argc, _TCHAR* argv[]){A* p1 = new A; delete p1; //编译失败,因为编译器发现A的析构函数是protectedreturn 0;}
所以,一种可行的办法貌似是这样的:
class A{protected:virtual ~A(){printf(" A: ~A() \n");}};class B : public A{};int _tmain(int argc, _TCHAR* argv[]){B* p =new B; //ok:这种情况下确实是可行的(YC:仔细看会发现这种情况同“(1)看如下代码”下面的代码中ok的情况相同)delete p;return 0;}
由于B public继承自A,所以其可以完全访问A的构造或析构函数,但是:
int _tmain(int argc, _TCHAR* argv[]){ A* p =new B; delete p; //error:由于p变成指向A的指针,字面上编译器需要知道A的析构函数,然后A的析构函数又是protected return 0;}
即便像这样B显示重载了A的析构函数:
class A{protected:virtual ~A(){printf(" A: ~A() \n");}};class B : public A{public:virtual ~B(){printf(" B: ~B() \n");}};int _tmain(int argc, _TCHAR* argv[]){A* p =new B;delete p; //error:也还是不行,因为重载是运行时的事情,在编译时编译器就认定了A的析构函数,结果无法访问return 0}
小结:
貌似用protected这样的方法并不是很恰当,虽然在遵守一定规则的情况下确实有他的实用价值,但并不是很通用(2)应该怎样实现接口类?
其实上面protected的思路是对的,无非是让父类无法实例化,那么为了让父类无法实例化,其实还有一个方法,使用纯虚函数。
class A{public: //这里就不用protected了 virtual ~A() = 0;};class B : public A{};int _tmain(int argc, _TCHAR* argv[]){ B* p =new B; delete p;//编译ok,链接error return 0;}
这样写貌似不错,以往大家都把类中的一般成员函数写成纯虚的,这次将析构函数写成纯虚的,更加增加通用性,编译也通过了,但就是在链接的时候出问题,报错说找不到A的析构函数的实现,很显然嘛,因为A的析构是纯虚的嘛。
那么如何修改上述代码可以达到既可以去除上述error,又可以让基类不能被实例化呢?如下所示:
class A{public://这里就不用protected了virtual ~A() = 0 //它虽然是个纯虚函数,但是也可以被实现{ //这个语法很好很强大(完全是为了实现其接口类而弄的语法吧)printf(" A: ~A() \n");}};class B : public A{};int _tmain(int argc, _TCHAR* argv[]){B* p =new B;delete p;A* p2 =new B;delete p2; //不用担心编译器报错了,因为此时A的析构函数是publicreturn 0;}//result:/* A: ~A() A: ~A()*/
如此终于大功告成了,注意,不能将构造函数替代上面的析构函数的用法,因为构造函数是不允许作为虚函数的。
补充:以上那个语法就真的只是为了这种情况而存在的,因为一般我们在虚类中申明的接口:
virtual foo()= 0;
virtual foo()= 0 {}
这两种写法是完全没有区别的,纯虚函数的默认实现,仅仅在它是析构函数中才有意义!!!
所以可以说,老外是完全为了这一个目的而发明了这种语法...
最终的接口类
classInterface{public: virtual ~Interface() = 0 {}};应该挺完美的了吧
[备注:内容多收集于网络~]
- [收集]c++抽象类、纯虚函数以及巧用纯虚析构函数实现接口类
- [收集]c++抽象类、纯虚函数以及巧用纯虚析构函数实现接口类
- [收集]c++抽象类、纯虚函数以及巧用纯虚析构函数实现接口类
- [收集]c++抽象类、纯虚函数以及巧用纯虚析构函数实现接口类
- c++抽象类、纯虚函数以及巧用纯虚析构函数实现接口类【转】
- 【c++】纯虚函数和抽象类
- C++:纯虚函数与抽象类
- [C++]纯虚函数和抽象类
- C++ 虚函数&纯虚函数&抽象类&接口&虚基类
- C++ 虚函数&纯虚函数&抽象类&接口&虚基类
- 虚函数 纯虚函数 抽象类 接口
- C++ 虚函数、纯虚函数、抽象类、接口、虚基类
- C++ 虚函数&纯虚函数&抽象类&接口&虚基类
- C++ 虚函数&纯虚函数&抽象类&接口&虚基类
- C++ 虚函数&纯虚函数&抽象类&接口&虚基类
- C++ 虚函数&纯虚函数&抽象类&接口&虚基类
- C++ 虚函数&纯虚函数&抽象类&接口&虚基类
- C++-虚函数,多态性,纯虚函数,抽象类
- 好文必须推荐---"怎样花两年时间去面试一个人”
- Maven远程部署web工程到Tomcat7中
- JSTL标签用法
- 使用Lua脚本为wireshark编写自定义通信协议解析器插件
- Android杂谈---各种Toast
- [收集]c++抽象类、纯虚函数以及巧用纯虚析构函数实现接口类
- EMC Documentum Explorer for android
- openfire开发文档
- 解析后台返回的xml文本 ![CDATA[]]形式
- Linux 下 MySQL 启动与关闭 说明
- IaaS PaaS和SaaS公司都做些什么
- 一些感悟和总结
- uva 620 Cellular Structure
- HOG(方向梯度直方图)