搜索
bottom↓
回复: 48

[交流][微知识]模块的封装(二):C语言类的继承和派生

  [复制链接]

出0入296汤圆

发表于 2013-6-25 19:05:54 | 显示全部楼层 |阅读模式
本帖最后由 Gorgon_Meducer 于 2017-5-31 16:15 编辑


[交流][微知识]模块的封装(二):C语言类的继承和派生

      在模块的封装(一):C语言类的封装中,我们介绍了如何使用C语言的结构体来实现一个类的封装,并通过掩码结构体的方式实
现了类成员的保护。这一部分,我们将在此基础上介绍C语言类的继承和派生。其实继承和派生是同一个动作的两种不同角度的表述。
当我们继承了一个基类而创造了一个新类时,派生的概念就诞生了。派生当然是从基类派生的。派生出来的类当然继承了基类的东西。
继承和派生不是一对好基友,他们根本就是一个动作的两种不同说法,强调动作的起始点的时候,我们说这是从某某类继承来的;强调
动作的终点时,我们说派生出了某某类。——呼……真累……厄……不是阿……我不是唐僧。

      我们知道,类总是会提供一些方法,可以让我们很方便的使用,比如

  1.     window_t tWin = new_window();    //!< 创建一个新的window对象
  2.     tWin.show();                                 //!< 显示窗体
复制代码
显然,能够实现这一技术的必要手段就是将函数指针一起封装在结构体中。在C语言中,类的方法(method)是通过函数指针(或者函
数指针的集合)——我们叫做虚函数(表)来实现的。虚函数表同样可以单独存在,我们称之为interface。在C语言中,虚函书表是可以
直接通过封装了纯函数指针的结构体来实现的。如下面的代码所示:
  1. //! \name interface definition
  2. //! @{
  3. #define DEF_INTERFACE(__NAME,...)   \
  4.             typedef struct __NAME __NAME;\
  5.             __VA_ARGS__\
  6.             struct __NAME {

  7. #define END_DEF_INTERFACE(__NAME)   \
  8.             };
  9. //! @}
复制代码
例如,我们可以使用上面的宏来定义一个字节流的读写接口:

  1. DEF_INTERFACE(i_pipe_byte_t)
  2.     bool (*write)(uint8_t chByte);
  3.     bool (*read)(uint8_t *pchByte)
  4. END_DEF_INTERFACE(i_pipe_byte_t)
复制代码
这类接口非常适合定义一个模块的依赖型接口——比如,某一个数据帧解码的模块是依赖于对字节流的读写的,通过在该模块中使用这样一个接口,
并通过专门的接口注册函数,即可实现所谓的面向接口开发——将模块的逻辑实现与具体应用相关的数据流隔离开来。例如:

frame.c

  1. ...
  2. DEF_CLASS(frame_t)
  3.     i_pipe_byte_t tStream;     //!< 流接口
  4.     ...
  5. END_DEF_CLASS(frame_t)

  6. //! 接口注册函数
  7. bool frame_register_stream_interface(frame_t *ptFrame, i_pipe_byte_t tStream)
  8. {
  9.     //! 去除掩码结构体的保护
  10.     CLASS(frame_t) *ptF = (CLASS(frame_t) *)ptFrame;
  11.     //! 合法性检查
  12.     if (NULL == tStream.write || NULL == tStream.read || NULL == ptFrame ) {
  13.         return false;
  14.     }
  15.     ptF->tStream = tStream;    //!< 设置接口
  16.     return true;
  17. }
复制代码
frame.h

  1. ...
  2. EXTERN_CLASS(frame_t)
  3.     i_pipe_byte_t tStream;     //!< 流接口
  4.     ...
  5. END_EXTERN_CLASS(frame_t)

  6. //! 接口注册函数
  7. extern bool frame_register_stream_interface(frame_t *ptFrame, i_pipe_byte_t tStream);

  8. extern bool frame_init(frame_t *ptFrame);

复制代码
基于这样的模块,一个可能的外部使用方法是这样的:
app.c

  1. ...
  2. static bool serial_out(uint8_t chByte)
  3. {
  4.     ...
  5. }

  6. static bool serial_in(uint8_t *pchByte)
  7. {
  8.     ...
  9. }

  10. static frame_t s_tFrame;
  11. ...
  12. void app_init(void)
  13. {
  14.     //! 初始化
  15.     frame_init(&s_tFrame);   

  16.     //! 初始化接口
  17.     do {
  18.         i_pipe_byte_t tPipe = {&serial_out, &serial_in};
  19.         frame_register_stream_interface(&s_tFrame, tPipe);
  20.     } while(0);
  21. }
复制代码
像这个例子展示的这样,将接口直接封装在掩码结构体中的形式,我们并不能将其称为“实现(implement)了接口i_pipe_byte_t”,
这只是内部将虚函数(表)表作为了一个普通的成员而已,我们可以认为这是加入了private属性的,可重载的内部成员函数。下面,我们
将来介绍如何真正的“实现(implement)”指定的接口。首先,我们要借助下面的专门定义的宏:


  1. #define DECLARE_CLASS(__NAME)                   \
  2.      typedef union __NAME __NAME;               

  3. #define __DEF_CLASS(__NAME,...)                 \
  4.     /*typedef union __NAME __NAME;  */          \
  5.     typedef struct __##__NAME __##__NAME;       \
  6.     struct __##__NAME {                         \
  7.         __VA_ARGS__
  8. #define DEF_CLASS(__NAME, ...)      __DEF_CLASS(__NAME, __VA_ARGS__)

  9.          
  10. #define __END_DEF_CLASS(__NAME, ...)            \
  11.     };                                          \
  12.     union __NAME {                              \
  13.         __VA_ARGS__                             \
  14.         uint_fast8_t __NAME##__chMask[(sizeof(__##__NAME) + sizeof(uint_fast8_t) - 1) / sizeof(uint_fast8_t)];\
  15.     };
  16. #define END_DEF_CLASS(__NAME, ...)  __END_DEF_CLASS(__NAME, __VA_ARGS__)

  17. #define __EXTERN_CLASS_OBJ( __TYPE, __OBJ )         \
  18.             extern union {                          \
  19.                 CLASS(__TYPE) __##__OBJ;            \
  20.                 __TYPE   __OBJ;                     \
  21.             };
  22. #define EXTERN_CLASS_OBJ(__TYPE, __OBJ)             \
  23.             __EXTERN_CLASS_OBJ( __TYPE, __OBJ )


  24. #define __EXTERN_CLASS(__NAME,...)                  \
  25.     /*typedef union __NAME __NAME; */               \
  26.     union __NAME {                                  \
  27.         __VA_ARGS__                                 \
  28.         uint_fast8_t __NAME##__chMask[(sizeof(struct{\
  29.         __VA_ARGS__
  30. #define EXTERN_CLASS(__NAME, ...)   __EXTERN_CLASS(__NAME, __VA_ARGS__)

  31. #define END_EXTERN_CLASS(__NAME, ...)               \
  32.         }) + sizeof(uint_fast8_t) - 1) / sizeof(uint_fast8_t)];\
  33.     };

复制代码
为了很好的说明上面宏的用法,我们以一个比较具体的例子来示范一下。这是一个通用的串行设备驱动的例子,这个例子的
意图是,为所有的类似USART,I2C,SPI这样的串行数据接口建立一个基类,随后,不同的外设都从该基类继承并派生出
属于自己的基类,比如USART类等等——这种方法是面向对象开发尤其是面向接口开发中非常典型的例子。首先,我们要
定义一个高度抽象的接口,该接口描述了我们是期待如何最简单的使用一个串行设备的,同时一起定义实现了该类的基类
serial_dev_t:
serial_device.h


  1. DECLARE_CLASS( serial_dev_t );

  2.         //! 这是我们定义的接口i_serial_t 这里的语法看起来似乎有点怪异,后面将介绍
  3.         DEF_INTERFACE( i_serial_t)     
  4.             fsm_rt_t (*write)(serial_dev_t *ptDev, uint8_t *pchStream, uint_fast16_t hwSize);    //!< i_serial_t 接口的write方法
  5.             fsm_rt_t (*read)(serial_dev_t *ptDev, uint8_t *pchStream, uint_fast16_t hwSize);     //!< i_serial_t 接口的read方法
  6.         END_DEF_INTERFACE( i_serial_t )

  7. //! 这是一个实现了接口i_serial_t的基类serial_dev_t
  8. EXTERN_CLASS( serial_dev_t, IMPLEMENT(i_serial_t) )
  9.     //! 类serial_dev_t的内部定义
  10.     ...
  11. END_EXTERN_CLASS_IMPLEMENT( serial_dev_t, IMPLEMENT(i_serial_t))
复制代码


如果不仔细看,这个例子似乎比较清楚了,一个基类serial_dev_t实现了接口i_serial_t。


注释:
值得注意的是这里有一个向前引用的问题,也就是 i_serial_t 使用到了还未定义的 serial_dev_t
如果你曾经定义过类似下面的结构体,你就知道蹊跷在哪里了,同时,你也就知道解决的
原理了:

  1. //! 一个无法编译通过的写法
  2. typedef struct {
  3.     ....
  4.     item_t *ptNext;
  5. }item_t;
复制代码
等效的正确写法如下:

  1. //! 前置声明的例子
  2. typedef struct item_t item_t;
  3. struct item_t {
  4.     ...
  5.     item_t *ptNext;
  6. };
复制代码
可见,前置声明是解决这类问题的关键,这里,下面的宏值得注意:

  1. #define DECLARE_CLASS(__NAME)                   \
  2.      typedef union __NAME __NAME;  
复制代码


以此为例,我来演示一下如何用参数宏实现方便的前置声明:

  1. #define DEF_FORWARD_LIST(__NAME)    \
  2.     typedef struct __NAME __NAME;\
  3.     struct __NAME {

  4. #define END_DEF_FORWARD_LIST(__NAME)  \
  5.     };
复制代码
使用的时候这样

  1. DEF_FORWARD_LIST(item_t)
  2.     ...
  3.     item_t *ptNext;
  4. END_DEF_FORWARD_LIST(item_t)
复制代码
这只解决了一个疑惑,另外一个疑惑就是为什么可以在参数宏里面插入另外一段代码?答案是一直可以,比如,我常这么干:

  1. # define SAFE_ATOM_CODE(...)     {\
  2.         istate_t tState = GET_GLOBAL_INTERRUPT_STATE();\
  3.         DISABLE_GLOBAL_INTERRUPT();\
  4.         __VA_ARGS__;\
  5.         SET_GLOBAL_INTERRUPT_STATE(tState);\
  6.     }
复制代码
这是原子操作的宏,使用的时候,只要在"..."的位置写程序就好了,例如:
adc.c

  1. ...
  2. static volatile uint16_t s_hwADCResult;
  3. ...
  4. ISR(ADC_vect)
  5. {
  6.     //! 获取ADC的值
  7.     s_hwADCResult = ADC0;
  8. }

  9. //! \brief 带原子保护的adc结果读取
  10. uint16_t get_adc_result(void)
  11. {
  12.     uint16_t hwResult;
  13.     SAFE_ATOM_CODE(
  14.         hwResult = s_hwResult;
  15.     )
  16.     return hwResult;
  17. }
复制代码
adc.h

  1. ...
  2. //! 可以随时安全的读取ADC的结果
  3. extern uint16_t get_adc_result(void);
  4. ...
复制代码
现在看来在参数宏里面插入大段大段的代码根本不是问题。



在看代码,对一个类来说,是否实现接口,以及实现几个接口其实是不确定的,例如这个例子中我们实现了一个接口:

  1. EXTERN_CLASS(example_t, IMPLEMENT( i_serial_t ))
  2. ....
  3. END_EXTERN_CLASS(example_t, IMPLEMENT( i_serial_t ) )
复制代码

那么如何在DEF_CLASS和EXTERN_CLASS中体现这种对不确定性的支持呢?

显然,这时候变长参数就成了关键,幸好C99位我们提供了这个便利,直接在参数宏里面加入“...”在宏本体里面用
__VA_ARGS__就可以代表“...”的内容了。

经过这样的解释,回头再去看前面的类定义,根本不算什么。^_^
那么一个类实现(implement)了某个接口,这有神马意义呢? 意义如下,我们就可以像正常类那么使用接口提供的方法了:

  1. //! 假设我们获取了一个名叫“usart0”的串行设备
  2. serial_dev_t *ptDev = get_serial_device("usart0");

  3. uint8_t chString[] = "Hello World!";

  4. //! 我们就可以访问这个对象的方法,比如发送字符串
  5. while ( fsm_rt_cpl !=
  6.     ptDev->write(ptDev, chString, sizeof(chString))
  7. );
  8. //! 当然这个对象仍然是被掩码结构体保护的,因为ptDev的另外一个可见的成员是ptDev->chMask,你懂的
复制代码
接下来,我们要处理的问题就是继承和派生……唉,绕了这么大的圈子,才切入本文的重点。记得有个谚语的全文叫做“博士卖驴,
下笔千言,离题万里,未有驴子……”
要实现继承和派生,只要借助下面这个装模作样的宏就可以了。


  1. //! \brief macro for inheritance

  2. #define INHERIT_EX(__TYPE, __NAME)  \
  3.             union {                 \
  4.                 __TYPE  __NAME;     \
  5.                 __TYPE;             \
  6.             };

  7. /*! \note When deriving a new class from a base class, you should use INHERIT
  8. *        other than IMPLEMENT, although they looks the same now.
  9. */
  10. #define __INHERIT(__TYPE)           INHERIT_EX(__TYPE, base__##__TYPE)
  11. #define INHERIT(__TYPE)             __INHERIT(__TYPE)

  12. /*! \note You can only use IMPLEMENT when defining INTERFACE. For Implement
  13. *        interface when defining CLASS, you should use DEF_CLASS_IMPLEMENT
  14. *        instead.
  15. */
  16. #define __IMPLEMENT(__INTERFACE)    INHERIT_EX(__INTERFACE, base__##__INTERFACE)
  17. #define IMPLEMENT(__INTERFACE)      __IMPLEMENT(__INTERFACE)
复制代码

是的,它不过是把基类作为新类(结构体)的第一个元素,并起了一个好听的名字叫base。“尼玛坑爹了吧?”没错,其实就是这
样,没什么复杂的,所以我们可以很容易的从serial_dev_t继承并为usart派生一个类出来:
usart.h

  1. #include "serial_device.h"
  2. ...
  3. EXTERN_CLASS(usart_t, INHERIT(serial_dev_t) )

  4.     uint8_t chName[20];                        //!< 保存名字,比如"USART0"
  5.     usart_reg_t *ptRegisters;                  //!< 指向设备寄存器
  6.     ...

  7. END_EXTERN_CLASS(usart_t , INHERIT(serial_dev_t))

  8. //! \brief 当然要提供一个函数来返回基类咯
  9. extern serial_dev_t *usart_get_base(usart_t *ptUSART);
复制代码

完成了这些,关于OOC格式上的表面工作,基本上就介绍完毕了。格式毕竟是表面工作,学会这些并不能让你的代码面向对象,
最多时看起来很高档。真正关键的是给自己建立面向对象的思维模式和训练自己相应的开发方法,这就需要你去看那些介绍面向
对象方法的书了,比如面向对象的思想啊,设计模式阿,UML建模阿。还是那句老话,如果你不知道怎么入门,看《UML+OOPC》。

打完收工,谢谢~

阿莫论坛20周年了!感谢大家的支持与爱护!!

月入3000的是反美的。收入3万是亲美的。收入30万是移民美国的。收入300万是取得绿卡后回国,教唆那些3000来反美的!

出0入296汤圆

 楼主| 发表于 2013-6-25 19:06:37 | 显示全部楼层
本帖最后由 Gorgon_Meducer 于 2013-10-24 10:32 编辑


一个绘图无关的 窗体系统的例子


window.h


  1. //! \name 矩形区域
  2. //! @{
  3. typedef struct {
  4.     int32_t nTop;
  5.     int32_t nLeft;
  6.     int32_t nHeigh;
  7.     int32_t nWidth;
  8. } rect_t;
  9. //! @}



  10. //! \brief 定义一个window类,它是一切的起点,也是一个抽象类abstract class,实现了接口i_win_t
  11. EXTERN_CLASS_IMPLEMENT(window_t,i_win_t,
  12.     //! \name 基于接口开发的window当然要有对应的接口咯,这也是未来覆盖(override)的关键
  13.     DEF_INTERFACE(i_win_t,
  14.         fsm_rt_t win_method_t(window_t *ptWin);     //!< window类里面的会用到的虚函数原型
  15.         fsm_rt_t win_msg_process(window_t *ptWin, msg_t tMSG);    //!< 消息处理函数
  16.      )
  17.         win_method_t    *fnRefresh;                   //!< 一个虚函数,用来刷新窗体的,你懂的
  18.         win_msg_process *fnMSGProcess;            //!< 一个虚函数 窗体怎么能没有消息处理函数呢,这可是事件驱动的关键阿
  19.     END_DEF_INTERFACE
  20. )
  21.     rect_t    tRegion;                                            //!< window的位置和大小
  22.     bool      bIsPressed;                                        //!< 是否是被按下的效果
  23. END_EXTERN_CLASS_IMPLEMENT(window_t,i_win_t)

复制代码
control.h


  1. //! \name 一个抽象类,它继承了基类window_t
  2. EXTERN_CLASS(control_t) INHERIT(window_t)

  3.     bool bIsTableStop;    //!< Table键是否能停在这个控件上
  4.     bool bIsActivated;      //!< 当前控件是否处于激活状态
  5.     i_control_t *ptPrevious;
  6.     i_control_t *ptNext;

  7. END_EXTERN_CLASS(control_t)

复制代码

出0入0汤圆

发表于 2013-6-25 19:15:57 | 显示全部楼层
傻孩子的东西是好。。。只是,我是菜鸟。。。一直看不懂这种写法...

出0入0汤圆

发表于 2013-6-25 19:53:54 | 显示全部楼层
坐等更新

出0入0汤圆

发表于 2013-6-25 20:13:45 | 显示全部楼层
这样的方式适合大型的工程吧,可贵的是代码可以重复利用,只是我等菜鸟没学会,看起来也费劲。

出0入0汤圆

发表于 2013-6-25 20:22:23 | 显示全部楼层
既然是类建议把结构体取个别名叫class,傻孩子师傅,别来无样哦

出0入296汤圆

 楼主| 发表于 2013-6-25 23:11:18 | 显示全部楼层
liuhengpeng123 发表于 2013-6-25 20:22
既然是类建议把结构体取个别名叫class,傻孩子师傅,别来无样哦

我一般把一个.c看作一个类……

出0入0汤圆

发表于 2013-6-25 23:13:45 | 显示全部楼层

              

出0入296汤圆

 楼主| 发表于 2013-6-26 11:32:19 | 显示全部楼层
更新完毕,打完收工~谢谢捧场
欢迎拍砖

出0入0汤圆

发表于 2013-6-26 11:38:12 | 显示全部楼层
收藏了谢谢

出200入0汤圆

发表于 2013-6-26 11:39:03 | 显示全部楼层
记号,一会看看

出0入0汤圆

发表于 2013-6-26 11:44:02 | 显示全部楼层
Gorgon_Meducer 发表于 2013-6-25 23:11
我一般把一个.c看作一个类……

我也是这样,哈哈。。各种底层都封装好

出0入0汤圆

发表于 2013-6-26 12:03:03 | 显示全部楼层
菜鸟,有点看不懂啊

出0入0汤圆

发表于 2013-6-26 12:04:13 | 显示全部楼层
做个标记,等有空的时候再来细细品读。

出0入0汤圆

发表于 2013-7-8 14:59:23 | 显示全部楼层
当年的C++就是这么来的吧

出0入0汤圆

发表于 2013-8-14 08:03:03 来自手机 | 显示全部楼层
mark……
顶一个…

出0入0汤圆

发表于 2013-9-3 20:37:45 | 显示全部楼层
算是学过了,虽然懂的不是很透彻

出0入0汤圆

发表于 2013-9-30 02:14:56 | 显示全部楼层
匿名结构体可以实现简单的继承。老师这个是不是OIOIC那种思路呢?

出0入296汤圆

 楼主| 发表于 2013-10-24 10:31:17 | 显示全部楼层
Jach_cc 发表于 2013-9-30 02:14
匿名结构体可以实现简单的继承。老师这个是不是OIOIC那种思路呢?

OIOIC是什么?

出0入0汤圆

发表于 2013-11-14 09:47:57 | 显示全部楼层
写的好啊

出0入0汤圆

发表于 2013-12-11 11:01:05 | 显示全部楼层
顶一个,慕名而来

出0入0汤圆

发表于 2013-12-11 11:16:13 | 显示全部楼层
没看懂,楼主能再扩展点其他资料吗? 以方便学习

出0入0汤圆

发表于 2013-12-11 11:18:30 | 显示全部楼层
弱弱问下,用IAR写ARM的能实现吗,比如STM32的?

出0入296汤圆

 楼主| 发表于 2013-12-11 11:32:38 | 显示全部楼层
passage110 发表于 2013-12-11 11:18
弱弱问下,用IAR写ARM的能实现吗,比如STM32的?

我一直使用IAR写ARM程序,能实现,与芯片无关。

出0入0汤圆

发表于 2013-12-11 14:27:13 | 显示全部楼层
Gorgon_Meducer 发表于 2013-12-11 11:32
我一直使用IAR写ARM程序,能实现,与芯片无关。

刚下了本书UML+00PC的在结合你的看看,顶你不错

出0入0汤圆

发表于 2014-1-3 18:02:03 | 显示全部楼层
比较早的帖子了,但是知识对我来说还是新的。Mark。

出0入0汤圆

发表于 2014-1-12 08:49:14 来自手机 | 显示全部楼层
比较好,交流写的不错。

出0入0汤圆

发表于 2014-3-26 17:13:42 | 显示全部楼层
make              

出0入0汤圆

发表于 2014-9-3 13:45:49 | 显示全部楼层
多谢多谢,正苦恼怎么写出好的风格的代码来着

出0入0汤圆

发表于 2014-9-4 04:17:52 来自手机 | 显示全部楼层
mark研究下。

出0入0汤圆

发表于 2014-9-12 17:13:05 | 显示全部楼层
没看太懂,留个记号慢慢研究!

出0入0汤圆

发表于 2014-10-22 13:48:43 | 显示全部楼层
mark
不错的资料,下来要慢慢研究

出0入0汤圆

发表于 2014-12-21 22:46:39 | 显示全部楼层
这么好的帖子就盖了30多层,我也来一层要不怎么获得房产。最近看你的接口宏整晕了,回来补补课

出0入0汤圆

发表于 2014-12-24 14:54:35 | 显示全部楼层
楼上是疯子。。。

出0入0汤圆

发表于 2015-9-7 15:55:35 | 显示全部楼层
说实话没看懂,哎,差距太大了。

出0入296汤圆

 楼主| 发表于 2015-9-8 13:47:34 | 显示全部楼层
pplive 发表于 2015-9-7 15:55
说实话没看懂,哎,差距太大了。

具体从什么地方开始看不懂呢?说出来,我看看能不能帮帮你。

出0入0汤圆

发表于 2015-9-8 14:26:20 | 显示全部楼层
Gorgon_Meducer 发表于 2015-9-8 13:47
具体从什么地方开始看不懂呢?说出来,我看看能不能帮帮你。

谢谢大师,目前感觉是自己学习不够,充电中……
等有所感悟的时候一定再向您请教。

出0入296汤圆

 楼主| 发表于 2015-9-9 10:54:04 | 显示全部楼层
pplive 发表于 2015-9-8 14:26
谢谢大师,目前感觉是自己学习不够,充电中……
等有所感悟的时候一定再向您请教。 ...

约定好了哦~

出0入0汤圆

发表于 2016-3-9 15:17:29 | 显示全部楼层
最重要的还是后面这段话:
真正关键的是给自己建立面向对象的思维模式和训练自己相应的开发方法,这就需要你去看那些介绍面向
对象方法的书了,比如面向对象的思想啊,设计模式阿,UML建模阿。还是那句老话,如果你不知道怎么入门,看《UML+OOPC》。

出0入0汤圆

发表于 2016-3-9 17:11:42 | 显示全部楼层
mark,前段时间使用libjpeg了解到c也能写面向对象的代码,回头研究下

出0入0汤圆

发表于 2016-4-20 14:44:33 | 显示全部楼层
收藏了谢谢

出0入0汤圆

发表于 2018-6-29 15:29:22 | 显示全部楼层
程序写到一定阶段,就会开始思考这些问题了

出0入0汤圆

发表于 2018-7-15 23:02:56 | 显示全部楼层
为什么不考虑用c++,mdk 和 iar 都支持c++

出0入296汤圆

 楼主| 发表于 2018-7-16 20:57:14 | 显示全部楼层
日日♂夜夜 发表于 2018-7-15 23:02
为什么不考虑用c++,mdk 和 iar 都支持c++

因为有很多应用场景,我们并不想用全部的OO特性,而只根据“实用主义”思想,模拟
一些OO的特性到C中来。你可以认为这是一种非常轻量级的OO实践。如果你的系统涉
及到大量的OO开发,那的确推荐直接用C++。

出0入0汤圆

发表于 2018-9-7 13:20:38 | 显示全部楼层
期待更多OOP讲解

出0入0汤圆

发表于 2021-1-19 00:12:02 | 显示全部楼层
慢慢研究

出0入296汤圆

 楼主| 发表于 2021-1-21 18:57:39 | 显示全部楼层

已经过时……

出0入4汤圆

发表于 2021-4-1 19:01:16 | 显示全部楼层
#define INHERIT_EX(__TYPE, __NAME)  \
            union {                 \
                __TYPE  __NAME;     \
                __TYPE;             \
            };

union
{
    i_serial_t base__i_serial_t;
    i_serial_t;
};
为啥后缀多个i_serial_t呀

出0入296汤圆

 楼主| 发表于 2021-4-5 22:11:07 | 显示全部楼层
kyq_linux 发表于 2021-4-1 19:01
#define INHERIT_EX(__TYPE, __NAME)  \
            union {                 \
                __TYPE   ...

后面这个就是点金之笔。用的是微软扩展。具体看这里:

https://www.amobbs.com/thread-5582609-1-1.html


https://gcc.gnu.org/onlinedocs/gcc/Unnamed-Fields.html
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-23 23:50

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表