接口描述了类的行为和功能,而无需完成类的特定实现
C++接口时通过抽象类实现的,设计抽象类的目的,是为了给其他类提供一个可以继承的适当的基类.抽象类本类不能被用于实例化对象,只能作为接口使用
注意:
如果试图实例化一个抽象类的对象,会导致编译错误
因此,如果一个抽象类的派生类需要被实例化(建立对象),则必须对每个继承来的纯虚函数进行函数体实现.
如果没有在派生类中重写所有纯虚函数,就尝试实例化派生类的对象,也会导致编译错误,这是因为如果派生类没有实现父类的纯虚函数,则派生类变为抽象类
抽象类基类为派生自抽象基类的派生类提供了约束条件,即派生类必须要实现继承自抽象基类中的纯虚函数,否则此派生类不可进行实例化,且派生类将继承为抽象派生类
纯虚函数是一个在 基类中声明的虚函数,它在该基类中没有定义具体的函数体(操作内容),要求派生类根据实际需要定义自己的版本,设计多层次类继承关系时用到。把某个方法声明为一个抽象方法等于告诉编译器,这个方法必不可少,但目前在基类中还不能为它提供实现
纯虚函数的标准格式:
virtual 函数类型 函数名(参数表)=0;
class Pet
{
public:
virtual void func()=0;//这便是声明了一个纯虚函数 也就是在虚函数尾部加上" =0 " 一个虚函数便被声明成为了一个纯虚函数
// 等于0表示该函数仅声明而没有函数体
};
注意:一旦类中有了纯虚函数,这个类便被称为 抽象类,且此类不可被实例化(不可建立类对象实例)
例如:
int main()
{
Pet pet;//报错!带有纯虚函数的类称为抽象类,不可实例化
}
抽象类
抽象类只能作为基类使用,无法定义抽象类对象实例,这是因为抽象类中包含了没有定义的纯虚函数,在C++中,我们把只能用于被继承而不能直接创建对象的类称之为抽象类,这种基类不能直接生成对象,而只有被继承后,并重写其虚函数后,才能使用
当抽象类的派生类实现了继承而来的纯虚函数后,才能实例化对象
之所以要存在抽象类,最主要是因为它具有不确定因素,我们把那些类中的确存在,但是在父类中无法确定具体实现的成员函数称为虚函数。虚函数是一种特殊的函数,在抽象类中只有声明,没有具体的定义
抽象类和纯虚函数的关系
抽象类中至少存在一个纯虚函数,存在纯虚函数的类一定是抽象类,存在纯虚函数是成为抽象类的充要条件
让我们借助现实生活中的例子来理解这一点。让我们说我们有一个基类Animal,它会睡觉,发出声音等等。现在我只考虑这两个行为并创建一个具有两个函数sleep()和sound()的基类Animal
此时我们有猫和狗两种动物需要被赋予发出声音的属性,我们知道动物的声音是不同的,猫说“喵”,狗说“汪”,那么我在Animal类中为函数sound()提供了什么实现?这样做的唯一和正确的方法是使这个函数纯粹抽象,这样我就不需要在Animal类中给出实现但是所有继承Animal的类必须为此函数提供实现。这样我确保所有动物都有声音,而且它们有独特的声音
程序示例:
#include
using namespace std;
//todonew与delete动态分配内存,与用指针调用对象 通过对象的不同调用不同的同名虚函数
class Pet//声明纯虚函数sound 后Pet类变为抽象类(接口)
{
public:
Pet(string thename);
void sleep();
virtual void sound()=0;//声明纯虚函数sound (并未进行函数实现 函数实现放在派生类中)
//注意:
//todo1.继承自抽象基类Pet的子类必须全部实现基类中的所有纯虚函数
//todo2.抽象基类Pet不可进行实例化
protected:
string name;
};
class Cat :public Pet
{
public:
Cat(string thename);
void climb();
void sound();
};
class Dog :public Pet
{
public:
Dog(string thename);
void jump();
void sound();
};
Pet::Pet(string thename)//todo基类构造器(抽象类也有构造函数)
{
name = thename;
}
void Pet::sleep()
{
cout << name << "正在睡大觉\n";
}
void Pet::sound()
{
cout << name << "动物发声\n";
}
Cat::Cat(string thename) :Pet(thename)//派生类Cat构造函数
{
}
void Cat::climb()
{
cout << name << "正在爬树\n";
}
void Cat::sound()//派生类虚函数
{
// Pet::sound();//todo如果需要调用基类中的play()函数 在原本的play()函数的基础上加上覆盖上的子类play()函数
cout << name << "喵喵喵!\n";
}
Dog::Dog(string thename) :Pet(thename)//派生类Dog构造函数
{
}
void Dog::jump()
{
cout << name << "跳过了栅栏\n";
}
void Dog::sound()//派生类虚函数
{
// Pet::sound();
cout << name << "汪汪汪!\n";
}
void func(Pet* x)
{
x->sound();
}
int main()
{
// Pet pet;//todo用带有抽象方法(纯虚函数)的抽象类Pet无法实例化对象
//todo创建指向子类实例的基类指针和引用来调用纯虚函数
Pet* cat = new Cat("猫");
Pet* dog = new Dog("狗");
//todo创建对象实例来调用纯虚函数
Cat cat2("对象实例调用 猫");
cat2.sound();
func(cat);
func(dog);
return 0;
}
本例中定义了三个类,它们的继承关系为:Animal-->Cat
和Animal-->Dog
Animal是一个抽象类,也是最顶层的基类,在 Animal类中定义了一个纯虚函数sound()
,在Cat
类中,实现了sound()
函数。所谓实现,就是定义了纯虚函数的函数体,抽象类Animal虽然不能实例化,但它为派生类提供了约束条件,派生类必须要实现这个函数,完成动物发声功能,否则就不能对派生类进行实例化
在实际开发中,你可以定义一个抽象基类,只完成部分功能,未完成的功能交给派生类去实现(谁派生谁实现). 这部分未完成的功能,往往是基类不需要的,或者在基类中无法实现的,虽然抽象基类没有完成,但是却强制要求派生类完成,这就是抽象基类的“霸王条款”
运行结果:
总结:
1.我们已经看到任何具有纯虚函数的类都是抽象类
2.抽象类基类不可建立实例
3.抽象类派生出的子类需将继承的纯虚函数全部进行实例化,才能建立其实例
4.抽象类可以有构造函数
5.如果派生类没有实现父类的纯虚函数,则派生类变为抽象类,即不可建立其实例
6.抽象基类除了约束派生类的功能,还可以实现多态,可以创建指向子类的实例的抽象基类的指针和引用
7.只有类中的虚函数才能被声明为纯虚函数,普通成员函数和顶层函数均不能声明为纯虚函数