C语言面向对象编程(二):继承详解

在 C 语言面向对象编程(一)里说到继承,这里再详细说一下。

    C++ 中的继承,从派生类与基类的关系来看(出于对比 C 与 C++,只说公有继承):

 

  • 派生类内部可以直接使用基类的 public 、protected 成员(包括变量和函数)
  • 使用派生类的对象,可以像访问派生类自己的成员一样访问基类的成员
  •  对于被派生类覆盖的基类的非虚函数,在派生类中可以通过基类名和域作用符(::)来访问
  • 当使用基类指针调用虚函数时,会调用指针指向的实际对象实现的函数,如果该对象未重载该虚函数,则沿继承层次,逐级回溯,直到找到一个实现

    上面的几个特点,我们在 C 语言中能否全部实现呢?我觉得可以实现类似的特性,但在使用方法上会有些区别。后面我们一个一个来说,在此之前呢,先说继承的基本实现。

 

    先看 C 语言中通过“包含”模拟实现继承的简单代码框架:

 

 
  1. struct base{

  2. int a;

  3. };

  4.  
  5. struct derived{

  6. struct base parent;

  7. int b;

  8. };

  9.  
  10. struct derived_2{

  11. struct derived parent;

  12. int b;

  13. };


    上面的示例只有数据成员,函数成员其实是个指针,可以看作数据成员。 C 中的 struct 没有访问控制,默认都是公有访问(与 java 不同)。

 

    下面是带成员函数的结构体:

 

 
  1. struct base {

  2. int a;

  3. void (*func1)(struct base *_this);

  4. };

  5.  
  6. struct derived {

  7. struct base parent;

  8. int b;

  9. void (*func2)(struct derived* _this;

  10. };


    为了像 C++ 中一样通过类实例来访问成员函数,必须将结构体内的函数指针的第一个参数定义为自身的指针,在调用时传入函数指针所属的结构体实例。这是因为 C 语言中不存在像 C++ 中那样的 this 指针,如果我们不显式地通过参数提供,那么在函数内部就无法访问结构体实例的其它成员。

 

    下面是在 c 文件中实现的函数:

 

 
  1. static void base_func1(struct base *_this)

  2. {

  3. printf("this is base::func1\n");

  4. }

  5. static void derived_func2(struct derived *_this)

  6. {

  7. printf("this is derived::func2\n");

  8. }


    C++ 的 new 操作符会调用构造函数,对类实例进行初始化。 C 语言中只有 malloc 函数族来分配内存块,我们没有机会来自动初始化结构体的成员,只能自己增加一个函数。如下面这样(略去头文件中的声明语句):

 

 

 
  1. struct base * new_base()

  2. {

  3. struct base * b = malloc(sizeof(struct base));

  4. b->a = 0;

  5. b->func1 = base_func1;

  6. return b;

  7. }


    好的,构造函数有了。通过 new_base() 调用返回的结构体指针,已经可以像类实例一样使用了:

 

 

 
  1. struct base * b1 = new_base();

  2. b1->func1(b1);


    到这里我们已经知道如何在 C 语言中实现一个基本的“类”了。接下来一一来看前面提到的几点。

 

   第一点,派生类内部可以直接使用基类的 public 、protected 成员(包括变量和函数)。具体到上面的例子,我们可以在 derived_func2 中访问基类 base 的成员 a 和 func1 ,没有任何问题,只不过是显式通过 derived 的第一个成员 parent 来访问:

 

 
  1. static void derived_func2(struct derived *_this)

  2. {

  3. printf("this is derived::func2, base::a = %d\n", _this->parent.a);

  4. _this->parent.func1(&_this->parent);

  5. }

 

 

    第二点,使用派生类的对象,可以像访问派生类自己的成员一样访问基类的成员。这个有点变化,还是只能通过派生类实例的第一个成员 parent 来访问基类的成员(通过指针强制转换的话可以直接访问)。代码如下:

 

 
  1. struct derived d;

  2. printf("base::a = %d\n",d.parent.a);

  3.  
  4. struct derived *p = new_derived();

  5. ((struct base *)p)->func1(p);

 

    第三点,对于被派生类覆盖的基类的非虚函数,在派生类中可以通过基类名和域作用符(::)来访问。其实通过前两点,我们已经熟悉了在 C 中访问“基类”成员的方法,总是要通过“派生类”包含的放在结构体第一个位置的基类类型的成员变量来访问。所以在 C 中,严格来讲,实际上不存在覆盖这种情况。即便定义了完全一样的函数指针,也没有关系,因为“包含”这种方式,已经从根本上分割了“基类”和“派生类”的成员,它们不在一个街区,不会冲突。

    下面是一个所谓覆盖的例子:

 

 
  1. struct base{

  2. int a;

  3. int (*func)(struct base * b);

  4. };

  5.  
  6. struct derived {

  7. struct base b;

  8. int (*func)(struct derived *d);

  9. };

  10.  
  11. /* usage */

  12. struct derived * d = new_derived();

  13. d->func(d);

  14. d->b.func((struct base*)d);

    如上面的代码所示,不存在名字覆盖问题。

 

 

    第四点,虚函数。虚函数是 C++ 里面最有意义的一个特性,是多态的基础,要想讲明白比较困难,我们接下来专门写一篇文章讲述如何在 C 中实现类似虚函数的效果,实现多态。

 

    回顾一下:

 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值