1.纯虚函数:
纯虚函数是一种特殊的虚函数,在基类中无法对虚函数给出有意义的实现,所以就叫做纯虚函数。
纯虚函数的作用:它的实现留给基类的派生类去做。
格式:
class <类名>
{
virtual<类型><函数名>(参数列表) = 0;
....
};
纯虚函数可以让类先具有一个操作名称,而没有操作内容,而后在基类的派生类中给出定义
引入原因:
- 为了方便使用多态特性,我们常常需要在基类中定义虚函数
- 很多情况下,基类本身生成对象是不合情理的。动物作为基类可以派生出企鹅,老鹰,狗等动物,但是动物本身生成对象明显不合常理
函数定义为纯虚函数,就可以解决上述问题。
2.抽象类:
凡是含有纯虚函数的类就叫抽象类,不能定义对象,只能为派生类用,可以定义指针与引用。
注意:
- 在派生类中要安全实现基类中的纯虚函数,要不然派生类也要变成抽象类
- 不能实例化对象
- 在派生类中实现纯虚函数后,定义抽象类对象的指针,并指向或引用子类对象
对于纯虚函数来说:
1.在定义的时候是不能定义实现部分的
2.在没有重新定义这种纯虚函数之前,是不能调用该纯虚函数的
抽象类的唯一用途是为派生类提供基类,纯虚函数的作用是作为派生类中的成员函数的基础,并实现动态多态性。
代码实现:
#include <iostream>
using namespace std;
//定义抽象基类
class Base
{
public:
//定义虚函数等于零,就是为纯虚函数,必须在派生类进行,可以看成一个派生类的接口,调用此接口时,调用相应派生类的方法
virtual void Fun() = 0;
};
class Derive : public Base
{
public:
/* 若不实现此函数,编译将会出错 */
void Fun()
{
cout << "Derive::Fun" << endl;
}
Derive()
{
cout << "Derive()" << endl;
}
~Derive()
{
cout << "~Derive()" << endl;
}
};
class Derive2: public Base
{
public:
/* 若不实现此函数,编译将会出错 */
void Fun()
{
cout << "Derive2::Fun" << endl;
}
Derive2()
{
cout << "Derive2()" << endl;
}
~Derive2()
{
cout << "~Derive2()" << endl;
}
};
void Show(Base& Base) {
Base.Fun();
}
int main()
{
//Base base;error,抽象类不可以实例化对象
Derive derive1;
Derive derive2;
Base* base1 = &derive1;
Base* base2 = &derive2;
base1->Fun();
base2->Fun();
return 0;
}
如果给抽象基类定义对象时,编译会报错:
结果如下:
3.单继承与多继承:
单继承:一个派生类只继承一个基类
多继承:一个类可以同时继承多个不同基类的行为和特征功能
多继承的格式:
//基类
class <类名>
{
};
//派生类
class <类名> :<继承方式> <基类名1>,<继承方式> <基类2>...
{
···
};
代码:
class Base1
{
public:
Base1() { cout << "Base1()" << endl; }
~Base1() { cout << "~Base1()" << endl; }
};
class Base2
{
public:
Base2() { cout << "Base2()" << endl; }
~Base2() { cout << "~Base2()" << endl; }
};
/*
** : 之后称为类派生表,表的顺序决定基类构造函数
** 调用的顺序,析构函数的调用顺序正好相反
*/
class Derive : public Base2, public Base1
{};
int main()
{
Derive derive;
return 0;
}
这段代码先声明Base2,在声明Base1而且运行没错。结果是Base2先构造,Base1在构造。
多继承会引发的问题:
- 二义性问题
- 菱形继承导致派生类持有间接基类的多份拷贝
4.二义性:
在上面的基类Base1和Base2中若存在相同的方法或成员变量,那么在派生类Derive中或使用Derive的对象时,若使用这个方法或成员变量时,那么编译器不知道需要调用Base1中的方法还是Base2中的方法或成员变量。
当然我们可以给方法添加作用域来解决这个问题,我们也可以通过在派生类Derive中重新定义这个方法来覆盖基类中的同名方法,从而使编译器能够正常工作
例子:
#include <iostream>
using namespace std;
class Base1
{
public:
Base1(int a = 10 ):ma(a)
{}
~Base1()
{}
void show()
{
cout << ma <<endl;
}
private:
int ma;
};
class Base2
{
public:
Base2(int b = 10 ):mb(b)
{}
~Base2()
{}
void show()
{
cout << mb <<endl;
}
private:
int mb;
};
//多继承
class Derive : public Base2,public Base1
{};
int main()
{
Derive derive;
derive.show();
return 0;
}
编译时发现存在二义性:
解决方法:就是为show函数加上作用域,即可正常运行:(2.在Derive派生类中重写Show方法,将隐藏基类方法。)
5.菱形继承:
- Derive1继承了Base类,它的成员有ma、mb、mc
- Derive2继承了Base类,它的成员有ma、mb、md
- Derive3继承了Derive1类和Derive2类,它的成员有ma、mb、mc、ma、mb、md
示意图:
Derive3由于多重继承,拿到了它的间接基类Base的两份数据拷贝
代码:
#include <iostream>
using namespace std;
class Base
{};
class Derive1 : public Base
{};
class Derive2 : public Base
{};
class Derive3 : public Derive1,public Derive2
{};
int main()
{
Derive3 derive3;
Base* base = &derive3;
return 0;
}
该代码在运行时,会出现错误 将派生类对象的地址赋给基类指针出现二义性
这种赋值将把基类指针设置为派生类对象中的基类指针的地址。但是现在Derive3中包含两个地址可选择,所以应该使用类型转换来指定对象:
Derive3 derive3;
Base* base = (Derive1*) &derive3;
Base* base1 = (Derive2*) &derive3;
这将使得使用基类指针来引用不同的对象(多态性)复杂化。
为了解决上述问题,C++引入了一种新技术——虚基类