抽象类是为了抽象和设计的目的而建立的,处于继承层次结构的上层。定义了纯虚函数的类就是抽象类。
具体类是能够建立对象的类。
抽象类的规定
(1)抽象类只能用作其他类的基类,不能建立抽象类对象。
(2)抽象类不能用作参数类型、函数返回类型或显式转换的类型。
(3)可以定义指向抽象类的指针和引用,此指针可以指向它的派生类,进而实现多态性。
#include<iostream>
using namespace std;
const double PI=3.14159;
class Shapes //抽象类
{
protected:
int x, y;
public:
virtual ~Shapes(){}; // 定义了纯虚函数,就需要定义纯虚析构函数
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;
}
输出:
矩形面积:50
圆面积:314.159
这个类有一个纯虚函数,所以它是抽象的。
如果只定义一个虚析构函数原型,而不给出函数体:
virtual ~Shapes();
会报链接错误。
所以必须提供虚析构函数的定义:
1
|
Shapes::~Shapes() { }
// 纯虚析构函数的定义
|
这个定义是必需的,因为虚析构函数工作的方式是:最底层的派生类的析构函数最先被调用,然后各个基类的析构函数被调用。这就是说,即使是抽象类,编译器也要产生对~Shapes的调用,所以要保证为它提供函数体。
如果不定义虚析构函数,即把
virtual ~Shapes(){};
这一行注释掉,链接器就会检测出来,报警告:
./virtual_fun.cpp:7: warning: 'class Shapes' has virtual functions but non-virtual destructor
./virtual_fun.cpp:17: warning: 'class Square' has virtual functions but non-virtual destructor
./virtual_fun.cpp:24: warning: 'class Circle' has virtual functions but non-virtual destructor
如果给出了虚析构函数的声明,但不定义,即不提供函数体(去掉两个花括号),那么报如下错误:
/tmp/ccmjPETa.o: In function `Shapes':
./virtual_fun.cpp:7: undefined reference to `vtable for Shapes'
/tmp/ccmjPETa.o: In function `~Square':
./virtual_fun.cpp:17: undefined reference to `Shapes::~Shapes()'
/tmp/ccmjPETa.o: In function `~Circle':
./virtual_fun.cpp:24: undefined reference to `Shapes::~Shapes()'
./virtual_fun.cpp:24: undefined reference to `Shapes::~Shapes()'
/tmp/ccmjPETa.o: In function `~Square':
./virtual_fun.cpp:17: undefined reference to `Shapes::~Shapes()'
/tmp/ccmjPETa.o:(.rodata._ZTI6Square[typeinfo for Square]+0x8): undefined reference to `typeinfo for Shapes'
/tmp/ccmjPETa.o:(.rodata._ZTI6Circle[typeinfo for Circle]+0x8): undefined reference to `typeinfo for Shapes'
collect2: ld returned 1 exit status
其中玩家类作为父类,剑士类、弓箭手类、魔法师类分别继承玩家类,作为子类。当我们开始游戏时,需要选择创建某一个类的对象,才能进行游戏。然而,我们的选择不应该是4个类,而应该只能在剑士类、弓箭手类或魔法师类中做出选择。因为,单纯的玩家类是抽象的、不完整的,任何一个玩家必须有一个确定的职业之后,才能确定他的具体技能。又比如学生类,它也是非常抽象的。让一个小学生、中学生或本科生学习,他们都有各自学习的内容。而一个抽象概念的学生,他却不知道该学些什么。
这时,我们必须要对玩家类或学生类作一些限制了。由于玩家类和学生类直接实例化而创建的对象都是抽象而没有意义的,所以我们希望玩家类和学生类只能用于被继承,而不能用于直接创建对象。在C++中,我们可以把只能用于被继承而不能直接创建对象的类设置为抽象类(Abstract Class)。
之所以要存在抽象类,最主要是因为它具有不确定因素。我们把那些类中的确存在,但是在父类中无法确定具体实现的成员函数称为纯虚函数。纯虚函数是一种特殊的虚函数,它只有声明,没有具体的定义。抽象类中至少存在一个纯虚函数;存在纯虚函数的类一定是抽象类。存在纯虚函数是成为抽象类的充要条件。
那么我们该如何定义一个纯虚函数呢?纯虚函数的声明有着特殊的语法格式:
virtual 返回值类型成员函数名(参数表)=0;
请注意,纯虚函数应该只有声明,没有具体的定义,即使给出了纯虚函数的定义也会被编译器忽略。下面我们就修改一下程序17.7.1,将学生类变成一个抽象类:(程序17.9)
//student.h
#include <iostream>
using namespace std;
class student//因为存在纯虚函数study,student类自动变成了抽象类
{
public:
student(char *n,int a,int h,int w);
student();
void set(char *n,int a,int h,int w);
char * sname();
int sage();
int sheight();
int sweight();
virtual void study()=0;//声明study为纯虚函数
protected:
char name[10];
int age;
int height;
int weight;
};
char * student::sname()
{
return name;
}
int student::sage()
{
return age;
}
int student::sheight()
{
return height;
}
int student::sweight()
{
return weight;
}
void student::set(char *n,int a,int h,int w)
{
int i;
for (i=0;n[i]!='\0';i++)
{
name[i]=n[i];
}
name[i]='\0';
age=a;
height=h;
weight=w;
return;
}
student::student(char *n,int a,int h,int w)
{
cout <<"Constructing a student with parameter..." <<endl;
set(n,a,h,w);
}
student::student()
{
cout <<"Constructing a student without parameter..." <<endl;
}
//undergraduate.h和pupil.h同程序17.7.1
//main.cpp
#include <iostream>
#include "undergraduate.h"
#include "pupil.h"
using namespace std;
int main()
{
Undergraduate s1;
/*student s2;//此时创建学生对象将会出现编译错误*/
Pupil s3;
student *sp=&s1;
s1.set("Tom",21,178,60);
sp->study();
sp=&s3;
s3.set("Mike",8,148,45);
sp->study();
return 0;
}
运行结果:
Constructing a student without parameter...
Constructing a student without parameter...
学习高等数学和大学英语。
学习语数外。
我们看到,设置了纯虚函数之后并不影响多态的实现,但是却将父类变成了抽象类,限制了父类对象的创建。有了抽象类之后,就不会再出现不确定职业的玩家、不确定身份的学生了。
http://blog.youkuaiyun.com/Slience_Perseverance/article/details/20536277
http://c.biancheng.net/cpp/biancheng/view/97.html