回顾cpp-多态-接口类

封装->继承->多态

  • 普通虚函数 check
  • 虚析构函数 check
  • 纯虚函数 check
  • 抽象类 check
  • 接口类 check
  • RTTI
  • 异常处理
  • 隐藏 覆盖 check
  • 早绑定 晚绑定 check
  • 虚函数表 check

纯虚函数

类中,一个虚函数没有函数体,直接=0。称为纯虚函数。如下:

1
2
3
4
5
6
class Shape{
public:
Shape(){}
virtual double calcAera(){ return 0; }
virtual double calcPerimeter() = 0; // 纯虚函数
};

上一篇中提到,一个类中因为有虚函数,所以一定有虚函数表指针,当然也有虚函数表。如果类中虚函数是一个普通的虚函数,这个虚函数的地址存储在虚函数表中。如果是纯虚函数,则在虚函数表中存储0,即该函数没有实现,没有函数体,所以自然就没有地址。

抽象类

纯虚函数一定是某个类的成员函数。包含纯虚函数的类称作抽象类。

  1. 含有一个或多个纯虚函数的类叫做抽象类。

  2. 抽象类是不被允许实例化对象的。

  3. 抽象类的子类也可以是抽象类。

    Person类太抽象,所以设计成抽象类:

    1
    2
    3
    4
    5
    6
    class Person{
    public:
    Person(string name);
    virtual void work()=0;
    virtual void printInfo()=0;
    };

    Worker类作为子类还是抽象,仍然是抽象类:

    1
    2
    3
    4
    5
    6
    7
    8
    class Worker:public Person{
    public:
    Worker(string name);
    virtual void work()=0;
    virtual void printInfo(){ cout<<m_strName; };
    private:
    string m_strName;
    };
  4. 抽象类的子类,只有把抽象类中的所有纯虚函数都做了实现,那么这个子类才可以实例化对象:

    1
    2
    3
    4
    5
    6
    7
    8
    class Dustman:public Worker{
    public:
    Worker(string name);
    virtual void work(){ cout<<"Cleaning"; };
    virtual void printInfo(){ cout<<m_strName; };
    private:
    string m_strName;
    };

一个实例:
Person被设计为一个抽象类,它work可是什么不好说:

1
2
3
4
5
6
7
8
class Person{
public:
Person(std::string name){ m_strName=name; };
virtual void work()=0; // 纯虚函数
virtual ~Person(){}
private:
std::string m_strName;
};

Worker类还是抽象,具体做什么呢:

1
2
3
4
5
6
7
8
9
// 还是抽象类
class Worker:public Person{
public:
Worker(std::string name, int age):Person(name){
m_iAge=age;
};
private:
int m_iAge;
};

Dustman就具体了,做清洁的一类工人:

1
2
3
4
5
class Dustman:public Worker{
public:
Dustman(std::string name, int age):Worker(name, age){};
virtual void work(){ std::cout<<"cleaning"<<std::endl; }
};

接口类

只含有纯虚函数的类称为接口类。即这个类中没有任何数据成员,只有成员函数,而这仅有的成员函数又都是纯虚函数。
接口类长这样:

1
2
3
4
5
class Shape{
public:
virtual double calcAera()=0;
virtual double calcPerimeter()=0;
};

“只有纯虚函数”真的是只有纯虚函数。

使用时,接口类表达一种能力或协议。接口类要被继承,且所有纯虚函数要被实现。最常使用方式如下例:

两个接口类:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 饮食习惯
class Diet{
public:
virtual void breakfirst()=0;
virtual int numMeal()=0;
};

// 训练方式
class Training{
public:
virtual void method()=0;
virtual int timeOfTraining()=0;
};

一个普通类:

1
2
3
4
5
6
7
8
9
10
11
12
// ID 即姓名
class ID{
public:
ID(std::string name){
m_strName = name;
}
void printName(){
std::cout<<"candidate name is: "<<m_strName<<std::endl;
}
private:
std::string m_strName;
};

Fighter是一个更具体的类,分别继承上述两个接口和一个普通类,并且实现所有的纯虚函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Fighter: public ID, public Diet, public Training{
public:
Fighter(std::string name, int trainTime, int numMeal):ID(name){
num_meal = numMeal;
time_training = trainTime;
}

// 实现所有接口中的纯虚函数:
virtual int numMeal(){ return num_meal; }
virtual int timeOfTraining(){ return time_training; }
virtual void breakfirst(){ cout<<"breakfirst ture"<<endl; }
virtual void method(){ cout<<"scientific"<<endl; }

private:
int num_meal;
int time_training;
};

最近的子类(在本例中也是唯一的子类)Fighter 继承了ID, 表示FighterID类的姓名。而且继承了Diet, 表示Fighter有各自的饮食习惯。还继承了Training类, 表示Fighter有各自的训练方式。只是Fighter的饮食和训练方式要自己定义。

最终使用的是那个没有被继承的类Fighter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void match(Fighter* candidate1, Fighter* candidate2){
if (candidate1->timeOfTraining() > candidate2->timeOfTraining())
cout<<"candidate1 is more likely going to win"<<endl;
else
cout<<"candidate2 is more likely going to win"<<endl;
}

int main() {
// How to use API
// 应该实例化“没有被继承的类”
Fighter f1("Junhui", 5, 3);
Fighter f2("Uunnui", 6, 4);

f1.printName();
f2.printName();

match(&f1, &f2);

return 0;
}

该实例是接口类的常用形式。