C++ 的 “继承”

来源:互联网 发布:淘宝用户年龄分布 编辑:程序博客网 时间:2024/05/17 07:05

        首先,“继承”二字,可以用生活中的例子来理解,即:继承上一版本的功能,并继续发展下一版本的功能。它在C++里面可以使面向对象的程序设计代码复用,从而使C++更贴合面向对象这个方向。

继承的概念:

        继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能。这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能。这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。

继承的五大特点:

1. 继承关系&访问限定符

2. 派生类的6个默认成员函数

3. 赋值兼容规则

4. 单继承&多继承&菱形继承

继承的格式:

        class DeriveClassName : acess_label BaseClassName

          DeriveClassName是继承类的名字,后面acess_label是继承的权限,BaseClassName是被继承的类的名字,这里面,继承的权限又分为public、protected、private。public继承不改变被继承类中的任何变量和函数的权限,protected会改变基类中public类型变量或函数的权限,使之变为protected的权限,只可以在派生类中访问,这样继承的话,就不能通过派生类去访问基类中的public型的变量或者参数了,假如一定要访问,则必须在前面加上作用域限定符“ :: ”。private继承会将基类中所有的变量或者函数变为派生类不可见的。所有派生中无法访问。

         注意:在继承时不加继承权限,C++编译器会默认为“private”。

关于三种继承的关系与作用可以参加下面这张表:


继承时的同名隐藏:

        若在基类和派生类中存在同名的变量或者函数,则在用派生类访问这个变量或者函数时, 会使用派生类中的,而放弃使用基类中的,但在继承的时候,派生类也继承了基类中这个同名的函数或变量,只是因为和派生类中存在同名, 所以在派生类中就“隐藏”了基类中的元素,但是也可以通过基类的作用域限定符对基类中的该元素进行访问。

派生类中空间的分布:

        派生类的空间中各个单元的分布模型为: 最上方是基类成员,基类成员的分布又按声明定义次序分布,接下来是派生类中的变量,同样,派生类中变量也是按声明定义次序排列。

可以参照下面代码的实现:

#include <iostream>using namespace std;   class Base{public:int _b1;int _b2;};class Derive :public Base{public:int _d1;int _d2;};int main(){Derive D;D._b1 = 1;D._b2 = 2;D._d1 = 3;D._d2 = 4;system("pause");return 0;}

        如上图,我们可以在简单的赋值后从内存中看出空间的分配。

构造函数&析构函数:

        在创建一个派生类的对象时,会先“调用”派生类的构造函数,但是在初始化列表位置,会去调用基类的构造函数,所以在调用上看,是先调用派生类,但在执行上来看,是会先执行完基类的构造函数。一半情况下,不给构造函数也可以,但是基类中带有参数的构造函数时,必须在派生类中显式的给出构造函数。

        析构时,会先释放派生类中的空间,在释放基类的空间,具体方式和构造函数类似,都是先进入派生类的析构,在进入基类的。

几个继承时的注意事项:

        1.派生类中对象可以给基类对象赋值,反之则不可。

         正确形式: base dereive;

        2.基类的指针或引用可以指向派生类的对象。

         正确形式:Base& base =derive; 

        3.基类中的友缘函数,派生类不可继承。

        4.基类中的静态成员派生类可以继承。

        5.继承对个基类时,要在每个基类之前都加上继承权限。

菱形继承:


上面是菱形继承的图解

而菱形继承内部的空间用如下代码和内存可以清楚的看出:

#include <iostream>using namespace std;   class B{public:int _b;};class C1 :public B{public:int _c1;};class C2 :public B{public:int _c2;};class D :public C1, public C2{public:int _d;};int main(){D d;d.C1::_b = 1;d.C1::_c1 = 2;d.C2::_b = 3;d.C2::_c2 = 4;d._d = 5;system("pause");return 0;}


我们可以看到大概是这样的:


而在菱形继承中,还有菱形虚拟继承,实现方式如下:

#include <iostream>using namespace std;   class B{public:int _b;};class C1 :virtual public B{public:int _c1;};class C2 :virtual public B{public:int _c2;};class D :public C1, public C2{public:int _d;};int main(){D d;d._b = 1;d._c1 = 2;d._c2 = 3;d._d = 4;system("pause");return 0;}

        可以发现,只是在C1、C2继承B时在继承权限前面加了“virtual”,而在main函数中的实现中。在访问_b时也没有使用作用域限定符,我们可以再来看看此时得内存:


我们可以看到多了两个地址,那么这两个地址是干嘛的呢,我们可以在内存里面看看:

经过看内存我们可以发现里面有两个int类空间,前8个字节保存的是全0,第一个地址的后8个字节保存了一个数字14(十六进制),第二个地址的后8个字节保存的是0c,我们可以发现,保存的是当前的地址对于基类中长远_b的偏移量。如下图所示:


这样可以保证更高效,安全的访问派生类中的各个成员,也对于基类的指针或者引用可以指向派生类的对象做了一个很好的方式。


0 0
原创粉丝点击