面向对象三要素:数据抽象(封装)、继承、动态绑定(多态)
数据抽象(封装)能帮助我们将对象的具体实现与对象所能执行的操作(接口)分离开来。
继承使我们可以更容易地定义与其他类相似但不完全相同的新类。
动态绑定(多态)让我们在使用这些彼此相似的类编写程序时,可以在一定程度上忽略掉它们的区别。
1、继承
基类和派生类通过继承构成一种层次关系。
基类位于根部,它负责定义在层次关系中所有类共同拥有的成员;派生类由基类派生而来,每个派生类定义各自特有的成员。
首先定义基类Quote:
class Quote
{
public:
std::string isbn() const;
virtual double net_price(std::size_t n) const;
};
其次定义派生类Bulk_quote:
class Bulk_quote : public Quote//Bulk_quote继承了Quote
{
public:
double net_price(std::size_t) const override;
};
注意到我们在基类函数net_price声明之前增加了关键字virtual指明它是一个“虚函数”。
虚函数表明该函数在基类和派生类中都要用到,但是行为不同。
2、动态绑定
动态绑定使我们可以用同一段代码处理基类(Quote)和派生类(Bulk_quote)的对象。
例子,定义一个函数,形参为基类(Quote):
double print_total(ostream &os, const Quote &item, size_t n)
{
double ret = item.net_price(n);//根据传入的实参是基类(Quote)还是派生类(Bulk_quote)决定调用哪一个net_price函数
//其他代码
};
由于print_total函数的形参是Quote类型的,因此我们既能使用基类对象,也能使用派生类对象调用它:
//basic的类型是Quote;bulk的类型是Bulk_quote
print_total(cout, basic, 20);//调用Quote的net_price
print_total(cout, bulk, 20);//调用Bulk_quote的net_price
使用基类对象和派生类对象调用print_total函数会分别调用基类和派生类中的虚函数net_price,也就是说,具体我们调用哪一个虚函数net_price是在函数运行过程中通过判断实参数基类对象还是派生类对象来决定的,所以动态绑定也被称作运行时绑定。
在C++语言中,当我们使用基类的引用(或指针)调用一个虚函数时将发生动态绑定。
举例如下:
// Example program
#include <iostream>
#include <string>
using namespace std;
class A
{
public:
A() = default;
A(double j)
{
degree = j;
}
virtual void show()
{
cout<<"A:"<<degree<<endl;
}
protected:
double degree;
};
class B:public A
{
public:
B() = default;
B(double deg,double Bde):A(deg), Bdegree(Bde) { }
virtual void show()
{
cout<<"A:"<<degree<<endl;
cout<<"B:"<<Bdegree<<endl;
}
private:
double Bdegree;
};
void f1(A m)//在编译时静态绑定,已经确定调用的是A类型中的show函数
{
m.show();
}
void f2(A& m)//在运行时根据传入实参的类型才能确定调用哪一个show
{
m.show();
}
int main()
{
<span style="white-space:pre"> </span>A a(3.14);
<span style="white-space:pre"> </span>B b(4.1,5.1);
f1(a);
f1(b);
f2(a);
f2(b);
while(1);
return 0;
}
输出结果为:
A:3.14
A:4.1
A:3.14
A:4.1
B:5.1
其中第1、2行是静态绑定,执行的是类A中的函数;而第3、4行是动态绑定,第3行输入是A类对象,因此调用A中的函数,而第4行输入的是B类对象,因此调用B中的函数。