抽象类和接口
什么是抽象类
- 用来表示现实世界中的抽象概念
- 是一种只能定义类型,而不能产生对象的类
- 只能被子类继承,且抽象类的相关成员函数没有完整的体现,用来被子类重写.
比如图形(Shape)类, 就是一个抽象的概念,因为我们无法计算这个“图形”的面积,所以它的成员函数area()是空的。
而继承它的子类(矩形,圆形,三角形等)就可以去重写area()成员函数. 里面通过数学公式,计算出面积.
参考图形(Shape)类,代码如下:
class Shape
{
public:
double area()
{
return ;
}
};
既然Shape是个抽象的类,那就根本没有该类的对象,我们该如何避免他人使用Shape类创建对象呢?
答:
在C++中,通过纯虚函数来避免
- 纯虚函数只需要声明函数名,不用实现函数内容.通过子类去实现
- 当类中有纯虚函数时,该类就无法创建对象,因为纯虚函数里没有具体内容,所以这个类便成为了抽象类.
- 如果子类没有实现存虚函数,则子类也会成为抽象类
纯虚函数
纯虚函数需要在声明函数名前面加上virtual,在最后面加个=0;
比如:
class Shape
{
public:
virtual double area()=; //不需要实现函数内容
};
接口
当类满足下面条件,则称为接口
- 类中没有定义任何成员变量
- 所有的成员函数都是公有的,并且都是纯虚函数
- 接口是一种特殊的抽象类
举个例子
比如我们的蓝牙,可以打开,关闭,收发数据
而网卡也一样,可以打开,关闭,收发数据.
类似的还有串口等等
这些类都拥有同样的行为,只是内容不同,所以它们的父类Channel只需要构造纯虚函数,所以便被称为接口,该父类代码如下:
class Channel{
public
virtual bool open()=;
virtual bool close()=;
virtual bool send(char* buf,int len)=;
virtual bool recv(char* buf,int len)=;
};
多重继承
- 一个类可以继承于多个父类
- 子类拥有所有父类的成员变量和函数
- 子类对象可以当做任意父类对象使用
例如:
class Derived : public BaseA,
public BaseB,
public BaseC
{
//... ...
}
多重继承的问题1
当多个不同的父类指针指向同一个多重继承的子类时,可能拥有不同地址
比如:
#include <iostream> using namespace std; class BaseA
{
int ma;
public:
BaseA(int a)
{
ma = a;
}
int getA()
{
return ma;
}
}; class BaseB
{
int mb;
public:
BaseB(int b)
{
mb = b;
}
int getB()
{
return mb;
}
}; class Derived : public BaseA, public BaseB
{
int mc;
public:
Derived(int a, int b, int c) : BaseA(a), BaseB(b)
{
mc = c;
}
}; int main()
{
Derived d(, , );
BaseA* pa = &d;
BaseB* pb = &d; if((void *)pa==(void *)pb)
{
cout<<"true"<<endl;
}
else
{
cout<<"false"<<endl;
} cout << "&d= " << &d << endl;
cout << "pa= " << pa << endl;
cout << "pb= " << pb << endl;
}
运行打印:
false
&d= 0x28fefc
pa= 0x28fefc
pb= 0x28ff00
为什么,pa指针和pb指针都指向d对象,它们的地址却有所不同?
这是因为Derived d对象地址里依次存了两个不同的父类成员变量值,如下图所示:
从上图看到,其实pa和pb还是位于d对象地址里,只是指向的位置不同而已.所以在多重继承里,最好不要使用等号直接判断两个指针对象是否相等.
多重继承的问题2
多重继承可能产生冗余的成员
比如:
老师teacher类,学生student类都继承于people类
而有些老师,为了工作还要考博士学位,既是老师又是学生,所以同时继承于老师teacher类,学生student类,则该类的成员便会拥有两个people类成员,从而产生冗余
在工程中,如何正确使用多重继承
- 只继承一个父类和多个接口
- 由于接口只有存虚函数,从而避免了冗余的成员
- 在父类中提供equal()成员函数,
- 通过equal()成员函数来判断指针是否指向当前对象,使用dynamic_cast强制转换
例如:
#include <iostream>
using namespace std; class Base
{
protected:
int mi;
public:
Base(int i)
{
mi = i;
} int getI()
{
return mi;
} bool equal(Base* obj)
{
return (this == obj);
}
}; class Interface1
{
public:
virtual void add(int i) = ;
virtual void minus(int i) = ;
}; class Interface2
{
public:
virtual void multiply(int i) = ;
virtual void divide(int i) = ;
}; class Derived : public Base, public Interface1, public Interface2
{
public:
Derived(int i) : Base(i)
{
}
void add(int i)
{
mi += i;
}
void minus(int i)
{
mi -= i;
}
void multiply(int i)
{
mi *= i;
}
void divide(int i)
{
if( i != )
{
mi /= i;
}
}
}; int main()
{
Derived d();
Derived* p = &d;
Interface1* pInt1 = &d;
Interface2* pInt2 = &d; cout << "p->getI() = " << p->getI() << endl; //
pInt1->add();
pInt2->divide();
pInt1->minus();
pInt2->multiply(); cout << "p->getI() = " << p->getI() << endl; // cout << "pInt1 == p : " << p->equal(dynamic_cast<Base*>(pInt1)) << endl;
cout << "pInt2 == p : " << p->equal(dynamic_cast<Base*>(pInt2)) << endl;
cout << "&d == p : " << p->equal(dynamic_cast<Base*>(&d)) << endl; return ;
}
运行打印:
p->getI() =
p->getI() =
pInt1 == p :
pInt2 == p :
&d== p :
可以发现,使用dynamic_cast转换,判断出来的地址就是相等的.
以p->equal(dynamic_cast<Base*>(pInt1))为例,我们编译时,编译器就会去检查pInt1所在的地址,然后找到是d对象,通过d对象找到Base父类,从而去修正pInt1指针的地址.