相关文章推荐
玩篮球的火锅  ·  在Windows ...·  10 月前    · 
不开心的椰子  ·  Can someone help me? ...·  1 年前    · 
低调的镜子  ·  Excel ...·  1 年前    · 
重情义的青椒  ·  MOUSE DOESN'T WORK ...·  1 年前    · 
叛逆的领结  ·  透射电镜 - 知乎·  1 年前    · 

在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:
		printf(" A: A()  \n");
int _tmain(int argc, _TCHAR* argv[])
	A* p1 = new A;                //编译不通过,无法访问protected构造函数
	delete p1;
	return 0;

Case2:

class A
protected:
		printf(" A: ~A()  \n");
int _tmain(int argc, _TCHAR* argv[])
	A* p1 = new A;                //编译通过,此时因为仅仅是用到了A的构造函数,还不需要它的析构函数
	return 0;
(附:如果将main中改为:
int _tmain(int argc, _TCHAR* argv[])
	return 0;
则编译出错,提示无法访问protected成员A::~A().两种情况出现差异的原因是什么?

Case3:

class A
protected:
		printf(" A: ~A()  \n");
int _tmain(int argc, _TCHAR* argv[])
	A* p1 = new A;                
	delete p1;                //编译失败,因为编译器发现A的析构函数是protected
	return 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的析构函数是public
	return 0;
//result:
 A: ~A()
 A: ~A()

如此终于大功告成了,注意,不能将构造函数替代上面的析构函数的用法,因为构造函数是不允许作为虚函数的

补充:以上那个语法就真的只是为了这种情况而存在的,因为一般我们在虚类中申明的接口:

virtual foo()= 0;

virtual foo()= 0 {}

这两种写法是完全没有区别的纯虚函数的默认实现,仅仅在它是析构函数中才有意义!!!

所以可以说,老外是完全为了这一个目的而发明了这种语法...

最终的接口类

classInterface
public:       
    virtual ~Interface() = 0 {}
应该挺完美的了吧
[备注:内容多收集于网络~]
1、函数声明如下: virtual void funtion1()=0; 函数一定没有定义,函数用来规范派生类的行为,即接口。包含函数的类是抽象类,抽象类不能定义实例,但可以声明指向实现抽象类的具体类的指针或引用。 2、函数声明如下:virtual ReturnType FunctionName(Parameter);函数必须实现,如果不实现,编译器将报错,错误提示 一、C++中            1、函数(virtual)     第一次引入函数的基类时,必须在类声明中指定virtual关键字。如果函数的定义放在类的外面,则不能再次指定关键字virtual。假设有下面的类层次: class A public: virtual void foo() { cout << "A::foo() is called" << endl;}//定 一、抽象类作为方法参数 今后开发中,抽象类作为方法参数的情况也很多见。当遇到方法参数为抽象类类型时,要传入一个实现抽象类所有抽象方法的子类对象。如下代码演示: //抽象类 abstract class Person{ public abstract void show(); class Student extends Person{ @Overrid... 转载:https://blog.csdn.net/hackbuteer1/article/details/7558868 首先:强调一个概念。定义一个函数函数,不代表函数为不被实现函数。他定义为函数的英文为了网求允许用基类的指针来调用子类的这个函数定义一个函数函数,才代表函数没有被实现。定义函数是为了实现一个接口,起到一个规范的作用,规范继承这个类的程序员必须实现这个函数.1,... 将父类的方法标记为方法,就是在父类方法的返回值前加virtual关键字,表示这个方法可以被子类重写 子类重写父类方法:在子类的方法的返回值前加override关键字,当然子类也可以不重写,或者是用new隐藏 父类中用virtual修饰的方法,可以在子类重写该的方... 含有函数的基类是不能用来定义对象的。 函数没有实现部分,不能产生对象,所以含有函数的类是抽象类。 抽象类是一种特殊的类,它是为了抽象和设计的目的而存在的,它处于继承层次结的较上层。 抽象类是不能定义对象的,在实际中为了强调一个类是抽象类,可将该类的函数说明为保护的访问控制权限。 将有关的组织在一个继承层次中,由它来为它们提供一个公有的根,相关的子类是从这个根派生出来的。 注意 :子类如果不重写父类中的函数