c++继承详解之一——继承的三种方式、派生类的对象模型

  C++是OOP(Object Oriented Programming)语言,即面向对象编程语言。OOP的核心思想就是数据抽象(类的设计),继承和动态绑定。

  类展现了C++的封装特性,即将具体的实现过程隐藏,只向外暴露公有接口,即数据抽象,通过数据抽象,我们可以将类的接口与实现分离,(即设计类)。
  与C相比,类可以通过相互间的继承和派生形成层次结构,派生类继承了基类的数据结构和方法 [编译时]。
  使用继承,可以定义相似的类型并对其相似关系建模。
  继承体现了OOP中事物的普遍性和特殊性。
  OOP强调软件的可重用性(software reuseablility).C++提供类的继承机制,解决了软件重用的问题。
  而动态绑定即体现了多态的思想,在此不赘述,以后会专门写一篇关于多态的文章。

一、继承简介

  通过继承机制,可以利用已有的数据类型来定义新的数据类型。所定义的新的数据类型不仅拥有新定义的成员,而且还同时拥有旧的成员。
  我们称已存在的用来派生新类的类为基类,又称为父类。由已存在的类派生出的新类称为派生类,又称为子类。
  基类负责定义所有类共同拥有的成员,而每个派生类定义各自特有的成员。
  派生类必须通过使用类派生列表明确指出它是从哪个类继承而来,并说明它是公有继承,保护继承还是私有继承。
下面是继承可以完成的一些操作:

  1. 可以再已有类的基础上添加功能。
  2. 可以给类添加数据。比如说基类马,可以派生一个白马类,在内加入颜色属性:白色
  3. 可以修改类方法的行为。

      当然,以上都可以通过复制修改源代码来完成,但继承机制只需要提供新特性,甚至只看对外接口不用看源码就可以派生出类,添加新特性。
    本文规定Base表示基类名称,Derive表示派生类名称

单继承

类派生列表的格式为:
class Base
{};
class Derive:public Base//公有继承
{};
class Derive2:protected Base //保护继承
{};
class Derive3:private Base//私有继承
{};
//上面的代码中,Derive,Derive2,Derive3都继承自Base基类
//区别就是继承方式不同

《c++继承详解之一——继承的三种方式、派生类的对象模型》
注意上图的箭头,箭头方向表示继承方向,从派生类指向基类。
派生类是基类的具体化,而基类是派生类的抽象。
单继承的定义格式如下:

class<派生类名>:<继承方式><基类名>
{
<派生类新定义成员>
};

  其中,class是关键词,<派生类名>是新定义的一个类的名字,它是从<基类名>中派生的,并且按指定的<继承方式>派生的。
  <继承方式>常使用如下三种关键字给予表示:

  • public 表示公有继承;
  • private 表示私有继承;
  • protected 表示保护继承;

多继承

多继承的定义格式如下:

class<派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>,…
{
    <派生类新定义成员>
};

注意,在多继承时,如果省略继承方式,默认为private

class A
{
    int a;
    void a()
    {}
};
class B
{
    int b;
    void b()
    {}
};
class C :public A, public B//A,B都是公有继承
{
    int c;
    void c()
    {}
};
class C :public A, public B//A,B都是公有继承
{
    int c;
    void c()
    {}
};
class C :public A, B//A为公有继承,B为私有继承
//可以类比定义指针时int *p,b;
    int c;
    void c()
    {}
};

二、派生类的构成

  派生类分为两大部分,一部分是从基类继承而来的成员,如下图data_B,另一部分是在声明派生类是增加的部分,如下图的data_D。

class Base
{
    int data_B;
};
class Derive:public Base//用公有继承先举例
{
    int data_D;
};

《c++继承详解之一——继承的三种方式、派生类的对象模型》
构造一个派生类需要做以下三部分工作:

  1. 从基类接受成员。

    派生类会把基类的全部成员(不包括构造函数和析构函数)接收过来,不能选择接收其中的一部分。

  2. 调整从基类接受的成员。

      在派生类中可以改变基类成员在派生类中的访问属性,这就是下面我们要将的指定继承方式来实现的。
    如果在派生类中声明了一个与基类成员相同名字的函数,派生类的新成员会覆盖基类的同名成员。

  3. 在声明派生类时增加的成员

多继承时派生类的构成

class A { 
    int a;
    void a()
    {}
};
class B { 
    int b;
    void b()
    {}
};
class C :public A, public B { 
    int c;
    void c()
    {}
};

《c++继承详解之一——继承的三种方式、派生类的对象模型》

其实多继承派生类的模型与继承列表类的顺序是有关的

class A { 
    int a;
    void a()
    {}
};
class B { 
    int b;
    void b()
    {}
};
class C :public B, public A { 
    int c;
    void c()
    {}
};

此时派生类的模型变为下图:
《c++继承详解之一——继承的三种方式、派生类的对象模型》

三、public,protected,private三种继承

  派生类可以继承定义在基类中的成员,但是派生类的成员函数不一定有权访问从基类继承而来的成员。
派生类将除了基类的构造函数和析构函数以外的所有成员都继承下来了。

  派生类有权访问哪些成员呢?
  那就是我们下面要说的三种继承方式决定的了

1.公有继承:

公有继承的格式为

class Derive:public Base
//public表示公有继承,如果不写继承方式

class Derive:Base
//默认私有继承

  公有继承时,基类的公用成员和保护成员在派生类中保持原有的访问属性,其私有成员仍为基类私有,即在派生类中不能访问,在类外也不能访问。
  私有成员体现了数据的封装性,如果基类的私有成员可以被派生类所访问,即破坏了基类的封装性,这就会失去C++的一个重要特性。

  大家看下面的代码

class Base
{
public:
    Base(int a = 0,int b = 0,int c = 0)
        :_pub(a)
        , _pro(b)
        , _pri(c)
    {
        cout << "Base()" << endl;
    }
    ~Base()
    {
        cout << "~Base()" << endl;
    }
    int _pub;
protected:
    int _pro;
private:
    int _pri;
};
class Derive :public Base
{
public:
    Derive()
    {}
    ~Derive()
    {}
    void Display()
    {
        cout << "_pri" << _pri << endl;//会报错
        //父类的是私有的
        //相当于在类外访问私有成员
        cout << "_pro" << _pro << endl;
        cout << "_pub" << _pub << endl;

        cout << "d_pri" << d_pri << endl;
        cout << "d_pro" << d_pri << endl;
        cout << "d_pub" << d_pri << endl;
    }
private:
    int d_pri;
protected:
    int d_pro;
public:
    int d_a;
};
int main()
{
    Derive a;
    a.Display();
    //会报错,编译不通过
}

在注释中我写出了运行结果,会报错。

Error   1   error C2248: 'Base::_pri' : cannot access private member declared in class 'Base'   e:\demo\继承\way\project1\project1\source.cpp 34  1   Project1

  为什么派生类不能访问基类的私有成员?
  答:因为派生类和基类是不同的类域,在不同类中访问相当于在类外访问私有成员。
  我们在派生类的成员函数中访问了父类的私有成员,相当于在类外访问私有成员,是错误的。

2.保护继承

  保护继承的特点是基类的所有公有成员和保护成员都成为派生类的保护成员,并且只能被它的派生类成员函数或友元访问,基类的私有成员仍然是私有的。

  对派生类而言,保护成员类似于公有成员,但对于外部而言,保护成员与私有成员类似。
  保护继承——原public,protected属性的成员继承后为protected属性,原private成员仍为private属性

class Base
{
public:
    Base(int a = 0,int b = 0,int c = 0)
        :_pub(a)
        , _pro(b)
        , _pri(c)
    {
        cout << "Base()" << endl;
    }
    ~Base()
    {
        cout << "~Base()" << endl;
    }
    int _pub;
protected:
    int _pro;
private:
    int _pri;
};
class Derive :protected Base
{
public:
    Derive()
    {}
    ~Derive()
    {}
    void Display()
    {
        cout << "_pri" << _pri << endl;//会报错
        //父类的是私有的
        //相当于在类外访问私有成员
        cout << "_pro" << _pro << endl;
        cout << "_pub" << _pub << endl;
        //_pro,_pub均为protected属性,可以类内访问

        cout << "d_pri" << d_pri << endl;
        cout << "d_pro" << d_pri << endl;
        cout << "d_pub" << d_pri << endl;
    }
private:
    int d_pri;
protected:
    int d_pro;
public:
    int d_a;
};
int main()
{
    Derive a;
    a.Display();
    a._pro = 0;
    a._pub = 0;
    //上面两句均会报错,因为_pro,_pub均为protected属性的成员
    //不能在类外访问
}

3.私有继承

  私有继承即所有基类成员均变成派生类的私有成员,基类的私有成员仍然不能在派生类中访问。

class Base
{
public:
    Base(int a = 0,int b = 0,int c = 0)
        :_pub(a)
        , _pro(b)
        , _pri(c)
    {
        cout << "Base()" << endl;
    }
    ~Base()
    {
        cout << "~Base()" << endl;
    }
    int _pub;
protected:
    int _pro;
private:
    int _pri;
};
class Derive :private Base
{
public:
    Derive()
    {}
    ~Derive()
    {}
    void Display()
    {
        cout << "_pri" << _pri << endl;//会报错
        //父类的是私有的
        //相当于在类外访问私有成员
        cout << "_pro" << _pro << endl;
        cout << "_pub" << _pub << endl;
        //_pro,_pub均为protected属性,可以类内访问

        cout << "d_pri" << d_pri << endl;
        cout << "d_pro" << d_pri << endl;
        cout << "d_pub" << d_pri << endl;
    }
private:
    int d_pri;
protected:
    int d_pro;
public:
    int d_a;
};
int main()
{
    Derive a;
    a.Display();
    a._pro = 0;
    a._pub = 0;
    //上面两句均会报错,因为_pro,_pub均为private属性的成员
    //不能在类外访问
}

  三种继承方式都介绍完毕后,肯定会有一个问题,那就是派生类无论怎样都无法访问基类的私有成员,那么我们要想知道私有成员的状态应该怎么办呢?
我们可以在基类定义非私有的show函数,然后在派生类调用它即可

class Base
{
    public:
    Base()
    {

    }

    ~Base()
    {

    }
    void Show()
    {
        cout<<"_pri"<<_pri<<endl;
        cout<<"_pro"<<_pri<<endl;
        cout<<"_pub"<<_pri<<endl;
    }
    private:
    int _pri;
    protected:
    int _pro;
    public:
    int _pub;
};
class Derive:protected Base
{
    public:
    Derive()
    {}
    ~Derive()
    {}
    void Display()
    {
        Show();//即可知道基类所有成员的状态
        //因为show函数是非私有的
        //所以派生类无论是哪种继承方式继承的,都可以调用show函数
        cout<<"d_pri"<<d_pri<<endl;
        cout<<"d_pro"<<d_pri<<endl;
        cout<<"d_pub"<<d_pri<<endl;
    }
    private:
    int d_pri;
    protected:
    int d_pro;
    public:
    int d_a;
};
int main()
{
    Derive a;
    a.Display();
    //运行时,会打印出基类的成员状态
}

四、友元函数、静态成员函数的继承

  相信看完上面的介绍,会对继承有一些了解了。
  那么基类的友元函数能不能被派生类继承呢?

1.友元函数

  答案是:不能!
  友元只是能访问指定类的私有和保护成员的自定义函数,不是被指定类的成员,自然不能继承。
使用友元类时注意:

(1) 友元关系不能被继承。
(2) 友元关系是单向的,不具有交换性。

  若类B是类A的友元,类A不一定是类B的友元,要看在类中是否有相应的声明。
(3)友元关系不具有传递性。若类B是类A的友元,类C是B的友元,类C不一定是类A的友元,同样要看类中是否有相应的申明
注意事项:

1.友元可以访问类的私有成员。
2.友元只能出现在类定义内部,友元声明可以在类中的任何地方,一般放在类定义的开始或结尾。
3.友元可以是普通的非成员函数,或前面定义的其他类的成员函数,或整个类。
4.类必须将重载函数集中每一个希望设为友元的函数都声明为友元。
5.友元关系不能继承,基类的友元对派生类的成员没有特殊的访问权限。如果基类被授予友元关系,则只有基类具有特殊的访问权限。该基类的派生类不能访问授予友元关系的类。

class Base
{
    friend void fun();
public:
    Base(int data = 0)
        :b(data)
    {
        cout << "Base()" << endl;
    }
    ~Base()
    {
        cout << "~Base()" << endl;
    }
private:
    int b;
};

class Derive :public Base
{
public:
    Derive(int data = 0)
        :d(data)
    {
        cout << "Derive()" << endl;
    }
    ~Derive()
    {
        cout << "~Derive()" << endl; 
    }
private:
    int d;
};

int main()
{
    Derive d;
    d.fun();
    return 0;
}

编译后出错:

Error   1   error C2039: 'fun' : is not a member of 'Derive'    e:\demo\project1\project1\source.cpp    40  1   Project1
    2   IntelliSense: class "Derive" has no member "fun"    e:\DEMO\Project1\Source.cpp 40  4   Project1

编译器报错fun()函数不是派生类Derive的成员函数,即友元函数并没有被继承。

2.静态成员及静态成员函数能不能被继承

  了解这个问题之前,我们先确定一件事情,那就是,在基类和派生类中,静态成员和静态成员函数共用同一段空间。
  我们在以前的学习过程中都知道,类的模型是所有对象的数据成员单独存储,但是所有成员函数和静态成员是共用一段空间的
例如:

class Test { 
    public:
    int a;
    int b;
    int c;
    void fun()
    {}
    static int s;
};
int main()
{
    Test A,B,C;
    return 0;
}

上面的代码对应下图:
《c++继承详解之一——继承的三种方式、派生类的对象模型》
即,ABC各自有各自的数据段,但是成员函数和静态成员的内存是公有的。
我在这里通过下面的代码进行了认证:

#include <iostream>
using namespace std;
class Base
{
    friend void fun();
public:
    Base(int data = 0)
        :b(data)
    {
        cout << "Base()" << endl;
    }
    ~Base()
    {
        cout << "~Base()" << endl;
    }
    static void show()
    {
        cout << "static show()" << endl;
    }
    void add()
    {
        a++;
    }
    static int a;
private:
    int b;
};
int Base::a = 0;
class Derive :public Base
{
public:
    Derive(int data = 0)
        :d(data)
    {
        cout << "Derive()" << endl;
    }
    ~Derive()
    {
        cout << "~Derive()" << endl; 
    }
    void Add()
    {
        a++;
    }
private:
    int d;
};

int main()
{
    Derive d;
    d.show();
    d.a = 2;
    d.add();
    d.Add();
    cout << d.a << endl;
    return 0;
}   

运行结果如下:
《c++继承详解之一——继承的三种方式、派生类的对象模型》

现在准确得到了,基类与派生类的静态成员函数与静态成员是共用一段空间的,即静态成员和静态成员函数是可以继承的。

所以,我们可以得出以下结论:

  • 父类的static变量和函数在派生类中依然可用,但是受访问性控制(比如,父类的private域中的就不可访问)。而且对static变量来说,派生类和父类中的static变量是共用空间的,这点在利用static变量进行引用计数的时候要特别注意。
  • 派生类的friend函数可以访问派生类本身的一切变量,包括从父类继承下来的protected域中的变量。但是对父类来说,他并不是friend的。
    原文作者:长着胡萝卜须的栗子
    原文地址: https://blog.csdn.net/lixungogogo/article/details/51118524
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞