继承(一)

C++很重要的一个特征就是代码重用。在C语言中重用代码的方式就是拷贝代码、修改代码。C++可以用继承或组合的方式来重用。通过组合或继承现有的的类来创建新类,而不是重新创建它们。
关于“继承”是什么,只要是有过面向对象的编程基础的应该都很容易理解,接下来会详细对其进行介绍,而上面提到了一个“组合”,那它的表现形式是咋样的呢?
定义一个A类:
此时有一个B类,它的功能跟A类的很相近,没必要重头到尾编写这些功能,所以可以去调用A类的方法来实现重用,如下:
实际上组合也就是将一个类作为另一类的对象成员,来达到复用代码的目的。另外一种复用代码的方式则就是继承了,下面来了解C++的继承:

  • 继承是使用已经编写好的类来创建新类,新的类具有原有类的所有属性和操作,也可以在原有类的基础上作一些修改和增补。
  • 新类称为派生类或子类,原有类称为基类或父类。
  • 派生类是基类的具体化。
  • 派生类的声明语法为:

    class 派生类名 : 继承方式  基类名

      {

               派生类新增成员的声明;

      };

下面用代码来具体使用一下:

#include <iostream>
using namespace std;

class Base {//声明基类
public:
    int x_;
protected:
    int y_;
private:
    int z_;
};


class PublicInherit : public Base {//声明派车类,公有继承基类
public:
    void Test(){
        x_ = 10;
        y_ = 20;
        z_ = 30;
    }
private:
    int a;
};

int main(void) {

    return 0;
}

以上代码没有什么意义,纯是学习语法,编译一下:

报错了,继承类无法使用基类的私有成员,关于这个修饰符跟java的类似,很容易理解。对于protected、public的区别,也差不多,前者只能在派生类中访问,而后者则可以在类的外部进行访问,如下:

#include <iostream>
using namespace std;

class Base {//声明基类
public:
    int x_;
protected:
    int y_;
private:
    int z_;
};


class PublicInherit : public Base {//声明派车类,公有继承基类
public:
    void Test(){
        x_ = 10;
        y_ = 20;
        //z_ = 30;    ERROR,派生类中无法访问基类的私有成员
    }
private:
    int a;
};

int main(void) {
    Base b;
    b.x_ = 20;//在类外部访问public成员
    return 0;
}

这时可以正常编译的,但是如果访问protected成员,则会报错:

编译:

对于这些修饰符有了初步的认识之后,下面来详细介绍一下它们:

  • 在关键字public后面声明,它们是类与外部的接口,任何外部函数都可以访问公有类型数据和函数。
  • 在关键字private后面声明,只允许本类中的函数访问,而类外部的任何函数都不能访问。
  • 在关键字protected后面声明,与private类似,其差别表现在继承与派生时对派生类的影响不同。

对于我们写的例子用的是公有继承:

跟java不一样的,它还有其它方式进行继承,下面用一张表来总结一下继承情况:

下面来使用一下私有继承的情况:

#include <iostream>
using namespace std;

class Base {//声明基类
public:
    int x_;
protected:
    int y_;
private:
    int z_;
};


class PublicInherit : public Base {//声明派车类,公有继承基类
public:
    void Test(){
        x_ = 10;
        y_ = 20;
        //z_ = 30;    ERROR,派生类中无法访问基类的私有成员
    }
private:
    int a;
};

class PrivateInherit : private Base {//声明派车类,私有继承基类
public:
    void Test(){
        x_ = 10;
        y_ = 20;
    }
private:
    int a;
};

int main(void) {
    
    return 0;
}

先看下私有继承,能否对public和protect成员进行访问:

正常编译,那如果以对象的形式能否访问呢?

那如果以对象的形式去访问呢,又会如何?

#include <iostream>
using namespace std;

class Base {//声明基类
public:
    int x_;
protected:
    int y_;
private:
    int z_;
};


class PublicInherit : public Base {//声明派车类,公有继承基类
public:
    void Test(){
        x_ = 10;
        y_ = 20;
        //z_ = 30;    ERROR,派生类中无法访问基类的私有成员
    }
private:
    int a;
};

class PrivateInherit : private Base {//声明派车类,私有继承基类
public:
    void Test(){
        x_ = 10;
        y_ = 20;
        //z_ = 30;    ERROR,派生类中无法访问基类的私有成员
    }
private:
    int a;
};

int main(void) {
    PrivateInherit pi;
    pi.x_ = 10;
    return 0;
}

编译:

经过私有继承之后,基类的公有成员也变为私有的了,无法在外部进行访问。

对于保护继承就不测试了,其结果可以从表中知道。

  • class Base {};
  • struct D1 : Base {};  // 对于结构体而言,默认就是公有继承
  • class D2 : Base {};  // 对于类而言,默认就是私有继承

  • 我们将类的公有成员函数称为接口。
  • 公有继承,基类的公有成员函数在派生类中仍然是公有的,换句话说是基类的接口成为了派生类的接口,因而将它称为接口继承。
  • 实现继承,对于私有、保护继承,派生类不继承基类的接口。派生类将不再支持基类的公有接口,它希望能重用基类的实现而已,因而将它称为实现继承。

  •  对基类的数据成员的重定义。

    这时重定义一下x_:

    #include <iostream>
    using namespace std;
    
    class Base {//声明基类
    public:
        Base() : x_(0) {
    
        }
        int getBaseX() const {
            return x_;
        }
        int x_;
    };
    
    class Derived : public Base {
    public:
        Derived() : x_(0) {
    
        }
        int getDerivedX() const {
            return x_;
        }
        int x_;//重定义了x_
    };
    
    int main(void) {
        Derived d;
        d.x_ = 10;
        cout<<d.getBaseX()<<endl;
        cout<<d.getDerivedX()<<endl;
        return 0;
    }

    这时打印一下看改变的是:

    当然是自身重定义的x_喽。

  • 对基类成员函数的重定义分为两种
    ①、overwrite【注意:它不是重载(overload),重载是作用域相同才可以,也就是在同一个类中发生的】
      a、与基类完全相同
      b、与基类成员函数名相同,参数不同
    #include <iostream>
    using namespace std;
    
    class Base {//声明基类
    public:
        Base() : x_(0) {
    
        }
        int getBaseX() const {
            return x_;
        }
        void show() {
            cout<<"Base::show ..."<<endl;
        }
        int x_;
    };
    
    class Derived : public Base {
    public:
        Derived() : x_(0) {
    
        }
        int getDerivedX() const {
            return x_;
        }
        int x_;//重定义了x_
    };
    
    int main(void) {
        Derived d;
        d.x_ = 10;
        cout<<d.getBaseX()<<endl;
        cout<<d.getDerivedX()<<endl;
    
        d.show();//派生类没有重定义show函数,所以调用的肯定是基类的show了
    
        return 0;
    }

    编译运行:

    接下来派生类重写一下show函数:

    #include <iostream>
    using namespace std;
    
    class Base {//声明基类
    public:
        Base() : x_(0) {
    
        }
        int getBaseX() const {
            return x_;
        }
        void show() {
            cout<<"Base::show ..."<<endl;
        }
        int x_;
    };
    
    class Derived : public Base {
    public:
        Derived() : x_(0) {
    
        }
        int getDerivedX() const {
            return x_;
        }
        void show(int n) {
            cout<<"Derived::show ..."<<n<<endl;
        }
        int x_;//重定义了x_
    };
    
    int main(void) {
        Derived d;
        d.x_ = 10;
        cout<<d.getBaseX()<<endl;
        cout<<d.getDerivedX()<<endl;
    
        d.show();
    
        return 0;
    }

    编译:

    这说明基类的无参的show函数被隐藏了,这是带参数的重写,另外也有不带参数的重写,如下:

    编译运行:

    那如果想访问基类的show()方法,可以这样做:

    编译运行:

    同样的,如果想访问被重写的父类成员也一样:



    ②、override【覆盖,需要虚函数才可以,虚函数之后再学习,先了解一下】

  • 无论是继承与组合本质上都是把子对象放在新类型中,两者都是使用构造函数的初始化列表去构造这些子对象。
    也就是说明继承与组合的内存模型是一样的,下面用代码来说明下:
    #include <iostream>
    using namespace std;
    
    class Base {//声明基类
    public:
        Base() : x_(0) {
    
        }
        int getBaseX() const {
            return x_;
        }
        void show() {
            cout<<"Base::show ..."<<endl;
        }
        int x_;
    };
    
    class Derived : public Base {
    public:
        Derived() : x_(0) {
    
        }
        int getDerivedX() const {
            return x_;
        }
        void show(int n) {
            cout<<"Derived::show ..."<<n<<endl;
        }
        void show() {
            cout<<"Derived::show ..."<<endl;
        }
        int x_;//重定义了x_
    };
    
    int main(void) {
        Derived d;
        d.x_ = 10;
        d.Base::x_ = 20;
        cout<<d.getBaseX()<<endl;
        cout<<d.getDerivedX()<<endl;
    
        d.show();
        d.Base::show();
    
        cout<<sizeof(Derived)<<endl;//打印一下派生类的大小,实际上包含两个int类型,一个是基类的,一个是自身的
    
        return 0;
    }

    下面用组合来看一下:
    #include <iostream>
    using namespace std;
    
    class Base {//声明基类
    public:
        Base() : x_(0) {
    
        }
        int getBaseX() const {
            return x_;
        }
        void show() {
            cout<<"Base::show ..."<<endl;
        }
        int x_;
    };
    
    class Derived : public Base {
    public:
        Derived() : x_(0) {
    
        }
        int getDerivedX() const {
            return x_;
        }
        void show(int n) {
            cout<<"Derived::show ..."<<n<<endl;
        }
        void show() {
            cout<<"Derived::show ..."<<endl;
        }
        int x_;//重定义了x_
    };
    
    class Test {
    public:
        Base b_;//组合关系
        int x_;
    };
    
    int main(void) {
        Derived d;
        d.x_ = 10;
        d.Base::x_ = 20;
        cout<<d.getBaseX()<<endl;
        cout<<d.getDerivedX()<<endl;
    
        d.show();
        d.Base::show();
    
        cout<<sizeof(Derived)<<endl;
        cout<<sizeof(Test)<<endl;
    
        return 0;
    }

  • 组合通中是在希望新类内部具有已存在的类的功能时使用,而不是希望已存在类作为它的接口。组合通过嵌入一个对象以实现新类的功能,而新类用户看到的是新定义的接口,而不是来自老类的接口。(has-a)
    举个现实中的例子来理解:
    一部汽车(它有行驶的功能)有引擎(它有启动、停止、加速、减速的功能)和轮胎(它有滚动的功能),而汽车是利用引擎和轮胎来实现实驶的功能,所以汽车与引擎和轮胎是组合关系。
  • 如果希望新类与已存在的类有相同的接口(在这基础上可以增加自己的成员)。这时候需要用继承,也称为子类型化。(is-a)【实际上它是LSP(Liskov Substitution Principle里氏代换原则),用它可以检验继承的质量,这个在之后会学习到,了解一下~】

 

posted on 2016-05-29 21:22  cexo  阅读(221)  评论(0编辑  收藏  举报

导航