C++之区分接口继承和实现继承

一.结论:

接口继承与实现继承不同。在公有继承下,派生类总是继承基类接口

  • 纯虚拟函数指定 仅有接口被继承。
  • 虚拟函数指定接口继承加上缺省实现继承
  • 非虚拟函数指定接口继承加上强制实现继承

二.说明

什么意思呢,请看下面的例子!

Shape类中包含纯虚函数、虚函数和非虚函数,这些函数由不同的类进行公有继承

// class sample 1class Shape { // 抽象类public:    virtual void draw() const = 0;  // 纯虚函数    virtual void error(const std::string& msg) {  // 虚函数        std::cout << "Shape::error()" << std::endl;    }    int objectID() const {  // 非虚函数        std::cout << "Shape::objectID()" << std::endl;        return 0;    }};void Shape::draw() const{    std::cout << "Shape::draw()" << std::endl;}class Rectangle: public Shape {    virtual void draw() const { // 继承抽象类,须实现        std::cout << "Rectangle::draw()" << std::endl;    }        virtual void error(const std::string& msg) { // 实现自己的error接口        std::cout << "Rectangle::error()" << std::endl;    }};class Ellipse: public Shape {    virtual void draw() const {  // 继承抽象类,须实现        std::cout << "Ellipse::draw()" << std::endl;    }};

2.1纯虚函数

对于基类的纯虚函数来说有如下两个特性:

  • 所有继承者都必须重新声明
  • 抽象类中没有其具体的定义

由此可以看出声明一个纯虚拟函数的目的是使 派生类仅继承一个函数的接口

使用时需注意:

  • 抽象基类Shape不能有实例
  • 派生类如果使用纯虚函数,需要实现其功能
  • 派生类可以调用基类的纯虚函数,但是需要使用基类名的作用域调用(需要基类实现定义)

如下:

// main sample 1int main(){    // Shape *s1 = new Shape; // 错误,抽象类不能有实例    Shape *s2 = new Rectangle; // 正确    s2->draw();   // 正确,调用Rectangle::draw()        Shape *s3 = new Ellipse; // 正确    s3->draw();   // 正确,Ellipse::draw()            s2->Shape::draw(); // 正确,调用Shape::draw()        s2->Shape::draw(); // 正确, 调用Shape::draw()        delete s1;    delete s2;    return 0;}

运行结果如下:

Rectangle::draw()Ellipse::draw()Shape::draw()Shape::draw()

2.2虚函数

看上去派生类可以调用基类的纯虚函数没有什么用处,但是可以为虚函数(非纯虚函数)提供安全使用的机制,如下:

// main sample 2int main(){    Shape *s2 = new Rectangle; // 正确    s2->error("s2");   // 正确,调用Rectangle::error()        Shape *s3 = new Ellipse; // 正确    s3->error("s2");   // 正确,Shape::error()        delete s1;    delete s2;    return 0;}

输出结果:

Rectangle::error()Shape::error()

咋一看这里没有什么问题,但实际上这里隐藏了一个巨大的风险,如果这里Shape类的draw函数是虚函数(非纯虚)且实现为二维平面图形的画法,如果有一个3维的正方体公有继承Shape,单独实现draw自然是没有问题,但是如果忘了实现而直接调用呢,则可以正常运行,但是画出来的结果可能千差万别,这便是由于缺省实现导致的

2.2.1 解决办法

解决办法有多种,常用安全的解决办法如下:

// class sample 2class Shape { // 抽象类public:    virtual void draw() const = 0;  // 纯虚函数};void Shape::draw() const{    std::cout << "Shape::draw()" << std::endl;}class Rectangle: public Shape {    virtual void draw() const {        Shape::draw();  // 复用父类实现    }};class Ellipse: public Shape {    virtual void draw() const {        Shape::draw();  // 复用父类实现    }};class Cubic: public Shape {    virtual void draw() const {  // 自身实现,缺省则会报错        std::cout << "Cubic::draw()" << std::endl;    }};

2.3非虚函数

对于非虚函数来说,声明一个非虚拟函数的目的是使派生类既继承一个函数的接口,又继承一个强制的实现。

// main sample 3 use class sample 1int main(){    Shape *s2 = new Rectangle; // 正确    s2->objectID();   // 正确,调用Shape::objectID()        Shape *s3 = new Ellipse; // 正确    s3->objectID();   // 正确,Shape::objectID()        delete s1;    delete s2;    return 0;}

输出结果:

Shape::objectID()Shape::objectID()

对于派生类实现基类同名函数时,则在派生类中所有该基类的同名函数均不可见,所以派生类绝不应该重定义基类的非虚函数


备注:

欢迎大家多多指教,喜欢的大家可以点击关注本账号,后续将不定期更新C++相关的内容

发表评论
留言与评论(共有 0 条评论) “”
   
验证码:

相关文章

推荐文章