文档章节

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

follitude
 follitude
发布于 2016/05/04 07:12
字数 1337
阅读 36
收藏 0

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

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

  • 派生类内部可以直接使用基类的 public 、protected 成员(包括变量和函数)

  • 使用派生类的对象,可以像访问派生类自己的成员一样访问基类的成员

  •  对于被派生类覆盖的基类的非虚函数,在派生类中可以通过基类名和域作用符(::)来访问

  • 当使用基类指针调用虚函数时,会调用指针指向的实际对象实现的函数,如果该对象未重载该虚函数,则沿继承层次,逐级回溯,直到找到一个实现

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

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

[cpp] view plain copy

  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 不同)。

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

[cpp] view plain copy

  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 文件中实现的函数:

[cpp] view plain copy

  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 函数族来分配内存块,我们没有机会来自动初始化结构体的成员,只能自己增加一个函数。如下面这样(略去头文件中的声明语句):

[cpp] view plain copy

  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() 调用返回的结构体指针,已经可以像类实例一样使用了:

[cpp] view plain copy

  1. struct base * b1 = new_base();  

  2. b1->func1(b1);  


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

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

 

[cpp] view plain copy

  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 来访问基类的成员(通过指针强制转换的话可以直接访问)。代码如下:

[cpp] view plain copy

  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 中,严格来讲,实际上不存在覆盖这种情况。即便定义了完全一样的函数指针,也没有关系,因为“包含”这种方式,已经从根本上分割了“基类”和“派生类”的成员,它们不在一个街区,不会冲突。

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

[cpp] view plain copy

  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 中实现类似虚函数的效果,实现多态。

网友评论:

new_derived();的实现没给出来啊,是不是这样啊?

[cpp] view plain copy

  1. struct derived * new_derived()    

  2. {    

  3.     struct derived * d = malloc(sizeof(struct derived));    

  4.     d->b = 0;  

  5.     d->func2 = derived_func2;    

  6.     d->parent = new_base();  

  7.     return d;    

  8. }   


本文转载自:http://blog.csdn.net/foruok/article/details/18325977

follitude
粉丝 6
博文 118
码字总数 4956
作品 0
浦东
私信 提问
C语言面向对象编程(四):面向接口编程

Java 中有 interface 关键字,C++ 中有抽象类或纯虚类可以与 interface 比拟,C 语言中也可以实现类似的特性。 接口和抽象类有什么区别? 很多编程书籍也经常说要面向接口编程,我的理解是,...

follitude
2016/05/04
68
0
1.2 面向对象语言的发展历史

Simula 面向对象技术最早是在编程语言Simula中提出的。1967年5月20日,在挪威奥斯陆郊外的小镇莉沙布举行的IFIP TC-2 工作会议上,挪威科学家Ole-Johan Dahl和Kristen Nygaard正式发布了Sim...

无寄语
2016/08/13
234
0
2017年10大主流编程语言最新排行榜出炉

2017-12-25 祈澈姑娘 前言 据美国科技公司Gizmodo报道,截至2014年9月24日,全世界采用IT操作系统的设备数量已经达到10亿台,IT推动中国移动互联网进入高速发展期,成为所有行业中发展前景最...

祈澈姑娘
2017/12/25
0
0
【OOP】go语言学习笔记(第3章)—面向对象编程

一个典型的类型系统包括: 基础类型: byte, int, bool, float等 复合类型:数组,结构体,指针 可以指向任意对象的类型(Any类型) 值语义和引用语义 面向对象,即所有具备面向对象特征的类型...

mickelfeng
2015/10/27
61
0
用C实现面向对象和模版编程

不需要C++,C语言照样可以面向对象编程,照样可以模版编程。下面是我写cfan库的一些经验。 模版编程 使用void*会迫使内存在堆上分配,模版编程能在不损失性能的情况下复用代码。 例如一个Poi...

chunquedong
2013/09/07
838
4

没有更多内容

加载失败,请刷新页面

加载更多

OpenStack 简介和几种安装方式总结

OpenStack :是一个由NASA和Rackspace合作研发并发起的,以Apache许可证授权的自由软件和开放源代码项目。项目目标是提供实施简单、可大规模扩展、丰富、标准统一的云计算管理平台。OpenSta...

小海bug
昨天
7
0
DDD(五)

1、引言 之前学习了解了DDD中实体这一概念,那么接下来需要了解的就是值对象、唯一标识。值对象,值就是数字1、2、3,字符串“1”,“2”,“3”,值时对象的特征,对象是一个事物的具体描述...

MrYuZixian
昨天
6
0
数据库中间件MyCat

什么是MyCat? 查看官网的介绍是这样说的 一个彻底开源的,面向企业应用开发的大数据库集群 支持事务、ACID、可以替代MySQL的加强版数据库 一个可以视为MySQL集群的企业级数据库,用来替代昂贵...

沉浮_
昨天
7
0
解决Mac下VSCode打开zsh乱码

1.乱码问题 iTerm2终端使用Zsh,并且配置Zsh主题,该主题主题需要安装字体来支持箭头效果,在iTerm2中设置这个字体,但是VSCode里这个箭头还是显示乱码。 iTerm2展示如下: VSCode展示如下: 2...

HelloDeveloper
昨天
9
0
常用物流快递单号查询接口种类及对接方法

目前快递查询接口有两种方式可以对接,一是和顺丰、圆通、中通、天天、韵达、德邦这些快递公司一一对接接口,二是和快递鸟这样第三方集成接口一次性对接多家常用快递。第一种耗费时间长,但是...

程序的小猿
昨天
11
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部