mirror of
https://github.com/Akagi201/lw_oopc.git
synced 2024-12-24 23:48:53 +08:00
convert word to markdown
This commit is contained in:
parent
f2a6d3aa73
commit
fd11258b8e
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
||||
doc/UML+OOPC嵌入式C语言开发精讲.pdf filter=lfs diff=lfs merge=lfs -text
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -16,3 +16,6 @@
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
|
||||
# CMake
|
||||
build
|
||||
|
20
CMakeLists.txt
Normal file
20
CMakeLists.txt
Normal file
@ -0,0 +1,20 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(lw_oopc VERSION 1.2)
|
||||
add_library(${PROJECT_NAME} lw_oopc.c lw_oopc.h)
|
||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
|
||||
$<INSTALL_INTERFACE:include>
|
||||
)
|
||||
install(FILES lw_oopc.h DESTINATION include)
|
||||
install(EXPORT ${PROJECT_NAME}Targets
|
||||
FILE ${PROJECT_NAME}Config.cmake
|
||||
DESTINATION lib/cmake/${PROJECT_NAME}
|
||||
)
|
||||
install(TARGETS ${PROJECT_NAME}
|
||||
EXPORT ${PROJECT_NAME}Targets
|
||||
PUBLIC_HEADER DESTINATION include
|
||||
ARCHIVE DESTINATION lib
|
||||
LIBRARY DESTINATION lib
|
||||
RUNTIME DESTINATION bin
|
||||
)
|
46
README.md
46
README.md
@ -1,31 +1,31 @@
|
||||
LW_OOPC升级版本发布说明
|
||||
=========================
|
||||
# `LW_OOPC` 升级版本发布说明
|
||||
|
||||
本人在金永华对于高焕堂的lw_oopc的源码的基础上进行了一些简单的修改和调试. 使得lw_oopc能够用在实际的嵌入式项目中, 作为一个很好的框架传播开来.
|
||||
本人在金永华对于高焕堂的 `lw_oopc` 的源码的基础上进行了一些简单的修改和调试。使得 `lw_oopc` 能够用在实际的嵌入式项目中,作为一个很好的框架传播开来。
|
||||
|
||||
主要改进有:
|
||||
1. 修正了一些小错误, 使得编译通过.
|
||||
2. 调整了一些宏的位置, 使得配置接口只在lw_oopc.h中.
|
||||
3. 经过测试可以直接在*nix下编译运行, 删掉多余的VS工程文件.
|
||||
主要改进有:
|
||||
|
||||
我的联系方式:
|
||||
* 博客: <http://akagi201.org>
|
||||
1. 修正了一些小错误,使得编译通过。
|
||||
2. 调整了一些宏的位置,使得配置接口只在 lw_oopc.h 中。
|
||||
3. 经过测试可以直接在*nix 下编译运行,删掉多余的 VS 工程文件。
|
||||
|
||||
我的联系方式:
|
||||
|
||||
* 博客:<https://akjong.com>
|
||||
* E-mail: <akagi201@gmail.com>
|
||||
|
||||
## 以下为金永华(loveyfore@sohu.com)在他的repo上的一些说明.
|
||||
## 以下为金永华 <loveyfore@sohu.com> 在他的 repo 上的一些说明
|
||||
|
||||
* 在高焕堂先生的著作《UML+OOPC嵌入式C语言开发精讲》中介绍了高先生及其MISOO团队创作的lw_oopc宏.
|
||||
* 在研读这套宏的过程中, 我发现高先生提供的这套宏还过于简陋, 不能够优雅地支持面向接口编程.
|
||||
* 经过认真钻研, 我对原有的这套宏进行了扩充和改良, 得到了lw_oopc宏的升级改进版本.
|
||||
* 改良后的这套宏,能够很好地支持面向对象编程,能够支持面向接口编程(甚至支持复杂的多重继承).
|
||||
* 在高焕堂先生的著作《UML+OOPC 嵌入式 C 语言开发精讲》中介绍了高先生及其 MISOO 团队创作的 lw_oopc 宏。
|
||||
* 在研读这套宏的过程中,我发现高先生提供的这套宏还过于简陋,不能够优雅地支持面向接口编程。
|
||||
* 经过认真钻研,我对原有的这套宏进行了扩充和改良,得到了 lw_oopc 宏的升级改进版本。
|
||||
* 改良后的这套宏,能够很好地支持面向对象编程,能够支持面向接口编程 (甚至支持复杂的多重继承).
|
||||
|
||||
- 经邮件与高焕堂先生联系,征得高先生同意,以LGPL协议开源,感谢高先生以及MISOO团队的贡献!
|
||||
> 经邮件与高焕堂先生联系,征得高先生同意,以 LGPL 协议开源,感谢高先生以及 MISOO 团队的贡献!
|
||||
|
||||
## 原有高焕堂先生及其 MISOO 团队创作的宏 (总共 6 个宏),清单如下
|
||||
|
||||
### 原有高焕堂先生及其MISOO团队创作的宏(总共6个宏),清单如下:
|
||||
|
||||
高焕堂及MISOO创作的宏 | 是否存在问题? | 是否修改?
|
||||
---- | ---- | -----
|
||||
高焕堂及 MISOO 创作的宏 | 是否存在问题?| 是否修改?
|
||||
---- | ---- | -----
|
||||
INTERFACE | 没有问题 | 否
|
||||
CLASS | 无法支持继承 | 是
|
||||
CTOR | 对申请不到内存的情况未保护 | 是
|
||||
@ -33,17 +33,17 @@ END_CTOR | 没有问题 | 否
|
||||
FUNCTION_SETTING | 没有问题 | 否
|
||||
IMPLEMENTS | 没有问题 | 否
|
||||
|
||||
## 为了更好的支持面向对象以及面向接口编程,金永华增加了 14 个宏
|
||||
|
||||
### 为了更好的支持面向对象以及面向接口编程,金永华增加了14个宏:
|
||||
创作的宏 | 创作目的()为了解决什么问题?)
|
||||
创作的宏 | 创作目的 () 为了解决什么问题?
|
||||
---- | ----
|
||||
DTOR / END_DTOR | 为了支持析构函数的概念
|
||||
ABS_CLASS | 为了支持抽象类的概念
|
||||
ABS_CTOR / END_ABS_CTOR | 为了支持可继承的抽象类的构造函数
|
||||
EXTENDS | 为了让熟悉Java的人容易理解(与IMPLEMENTS宏等同)
|
||||
EXTENDS | 为了让熟悉 Java 的人容易理解 (与 IMPLEMENTS 宏等同)
|
||||
SUPER_CTOR | 为了支持子类调用父类的构造函数
|
||||
SUPER_PTR / SUPER_PTR_2 / SUPER_PTR_3 | 为了支持向上转型
|
||||
SUB_PTR / SUB_PTR_2 / SUB_PTR_3 | 为了支持向下转型
|
||||
INHERIT_FROM | 为了支持访问直接父类的数据成员
|
||||
|
||||
* 希望这套宏,能够真正帮助到想用C语言写出面向对象代码的C程序员们!
|
||||
* 希望这套宏,能够真正帮助到想用 C 语言写出面向对象代码的 C 程序员们!
|
||||
|
4
demo/Animal/CMakeLists.txt
Normal file
4
demo/Animal/CMakeLists.txt
Normal file
@ -0,0 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(Animal)
|
||||
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} SOURCE)
|
||||
add_executable(${PROJECT_NAME} ${SOURCE})
|
@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2008,2009,2010 by Tom Kao & MISOO Team & Yonghua Jin. All rights reserved.
|
||||
// Copyright (C) 2008,2009,2010 by Tom Kao & MISOO Team & Yonghua Jin. All rights reserved.
|
||||
// Released under the terms of the GNU Library or Lesser General Public License (LGPL).
|
||||
// Author: Tom Kao(中文名:高焕堂),MISOO团队,Yonghua Jin(中文名:金永华)
|
||||
//
|
||||
@ -151,7 +151,7 @@ void type##_ctor(type* cthis) {
|
||||
|
||||
#define EXTENDS(type) struct type type
|
||||
|
||||
#define SUPER_PTR(cthis, father) ((father*)(&(cthis->father)))
|
||||
#define SUPER_PTR(cthis, father) ((father*)(&((cthis)->father)))
|
||||
|
||||
#define SUPER_PTR_2(cthis, father, grandfather) \
|
||||
SUPER_PTR(SUPER_PTR(cthis, father), grandfather)
|
||||
@ -171,6 +171,6 @@ void type##_ctor(type* cthis) {
|
||||
#define SUB_PTR_3(selfptr, self, child, grandchild, greatgrandchild) \
|
||||
SUB_PTR(SUB_PTR_2(selfptr, self, child, grandchild), grandchild, greatgrandchild)
|
||||
|
||||
#define INHERIT_FROM(father, cthis, field) cthis->father.field
|
||||
#define INHERIT_FROM(father, cthis, field) (cthis)->father.field
|
||||
|
||||
#endif
|
||||
|
7
demo/CMakeLists.txt
Normal file
7
demo/CMakeLists.txt
Normal file
@ -0,0 +1,7 @@
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(demo)
|
||||
add_compile_definitions(-D_CRT_SECURE_NO_WARNINGS)
|
||||
add_subdirectory(Animal)
|
||||
add_subdirectory(Expr)
|
||||
add_subdirectory(expr-advance)
|
4
demo/Expr/CMakeLists.txt
Normal file
4
demo/Expr/CMakeLists.txt
Normal file
@ -0,0 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(Expr)
|
||||
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} SOURCE)
|
||||
add_executable(${PROJECT_NAME} ${SOURCE})
|
@ -1,6 +1,6 @@
|
||||
// Copyright (C) 2008,2009,2010 by Tom Kao & MISOO Team & Yonghua Jin. All rights reserved.
|
||||
// Copyright (C) 2008,2009,2010 by Tom Kao & MISOO Team & Yonghua Jin. All rights reserved.
|
||||
// Released under the terms of the GNU Library or Lesser General Public License (LGPL).
|
||||
// Author: Tom Kao(中文名:高焕堂),MISOO团队,Yonghua Jin(中文名:金永华)
|
||||
// Author: Tom Kao(中文名:高焕堂),MISOO 团队,Yonghua Jin(中文名:金永华)
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
@ -151,7 +151,7 @@ void type##_ctor(type* cthis) {
|
||||
|
||||
#define EXTENDS(type) struct type type
|
||||
|
||||
#define SUPER_PTR(cthis, father) ((father*)(&(cthis->father)))
|
||||
#define SUPER_PTR(cthis, father) ((father*)(&((cthis)->father)))
|
||||
|
||||
#define SUPER_PTR_2(cthis, father, grandfather) \
|
||||
SUPER_PTR(SUPER_PTR(cthis, father), grandfather)
|
||||
@ -171,6 +171,6 @@ void type##_ctor(type* cthis) {
|
||||
#define SUB_PTR_3(selfptr, self, child, grandchild, greatgrandchild) \
|
||||
SUB_PTR(SUB_PTR_2(selfptr, self, child, grandchild), grandchild, greatgrandchild)
|
||||
|
||||
#define INHERIT_FROM(father, cthis, field) cthis->father.field
|
||||
#define INHERIT_FROM(father, cthis, field) (cthis)->father.field
|
||||
|
||||
#endif
|
||||
|
@ -1,10 +1,11 @@
|
||||
# 例子说明
|
||||
|
||||
### 例子说明
|
||||
* animal - <轻量级的面向对象 C 语言编程框架介绍> 中第一个例子。
|
||||
* expr - <轻量级的面向对象 C 语言编程框架介绍> 中第二个例子。
|
||||
* expr-advance - LW_OOPC 的高级功能 (内存泄露诊断功能) 的支持演示。
|
||||
|
||||
* animal - <轻量级的面向对象C语言编程框架介绍> 中第一个例子.
|
||||
* expr - <轻量级的面向对象C语言编程框架介绍> 中第二个例子.
|
||||
* expr-advance - LW_OOPC的高级功能(内存泄露诊断功能)的支持演示.
|
||||
|
||||
### 编译说明
|
||||
## 编译说明
|
||||
|
||||
```sh
|
||||
gcc *.c
|
||||
```
|
||||
|
4
demo/expr-advance/CMakeLists.txt
Normal file
4
demo/expr-advance/CMakeLists.txt
Normal file
@ -0,0 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
project(ExprAdvance)
|
||||
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} SOURCE)
|
||||
add_executable(${PROJECT_NAME} ${SOURCE})
|
@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2008,2009,2010 by Tom Kao & MISOO Team & Yonghua Jin. All rights reserved.
|
||||
// Copyright (C) 2008,2009,2010 by Tom Kao & MISOO Team & Yonghua Jin. All rights reserved.
|
||||
// Released under the terms of the GNU Library or Lesser General Public License (LGPL).
|
||||
// Author: Tom Kao(中文名:高焕堂),MISOO团队,Yonghua Jin(中文名:金永华)
|
||||
//
|
||||
@ -151,7 +151,7 @@ void type##_ctor(type* cthis) {
|
||||
|
||||
#define EXTENDS(type) struct type type
|
||||
|
||||
#define SUPER_PTR(cthis, father) ((father*)(&(cthis->father)))
|
||||
#define SUPER_PTR(cthis, father) ((father*)(&((cthis)->father)))
|
||||
|
||||
#define SUPER_PTR_2(cthis, father, grandfather) \
|
||||
SUPER_PTR(SUPER_PTR(cthis, father), grandfather)
|
||||
@ -171,6 +171,6 @@ void type##_ctor(type* cthis) {
|
||||
#define SUB_PTR_3(selfptr, self, child, grandchild, greatgrandchild) \
|
||||
SUB_PTR(SUB_PTR_2(selfptr, self, child, grandchild), grandchild, greatgrandchild)
|
||||
|
||||
#define INHERIT_FROM(father, cthis, field) cthis->father.field
|
||||
#define INHERIT_FROM(father, cthis, field) (cthis)->father.field
|
||||
|
||||
#endif
|
||||
|
@ -1,21 +1,22 @@
|
||||
LW_OOPC 宏配置及使用指南
|
||||
========================
|
||||
|
||||
LW_OOPC是一套轻量级的面向对象C语言编程框架. 它是一套C语言的宏, 总共1个.h文件(如果需要内存泄漏和调试打印支持, 需要增加1个.c文件(lw_oopc.c, 约145行)). 20个宏, 约130行代码, 非常的轻量级, 但却很好的支持了很多面向对象的特性, 比如继承, 多态. 可以优美的实现面向接口编程.
|
||||
LW_OOPC 是一套轻量级的面向对象 C 语言编程框架。它是一套 C 语言的宏,总共 1 个.h 文件 (如果需要内存泄漏和调试打印支持,需要增加 1 个.c 文件 (lw_oopc.c,约 145 行)). 20 个宏,约 130 行代码,非常的轻量级,但却很好的支持了很多面向对象的特性,比如继承,多态。可以优美的实现面向接口编程。
|
||||
|
||||
注意, 这里特别强调一下, 使用LW_OOPC的前提是: 在C语言下. 如果您所在的团队已经在使用C++, 那么LW_OOPC对于这种情形是没有价值的. 也就是说, LW_OOPC希望能够帮助到那些懂OO的程序员, 即便是在用C语言编程, 依然能够编写出面向对象的程序.
|
||||
注意,这里特别强调一下,使用 LW_OOPC 的前提是:在 C 语言下。如果您所在的团队已经在使用 C++,那么 LW_OOPC 对于这种情形是没有价值的。也就是说,LW_OOPC 希望能够帮助到那些懂 OO 的程序员,即便是在用 C 语言编程,依然能够编写出面向对象的程序。
|
||||
|
||||
言归正传, 本文将对LW_OOPC的配置和使用方法进行讲解, 并对这些宏逐个进行细致讲解. 期望本文能给希望在实践中应用LW_OOPC的C程序员带来帮助.
|
||||
言归正传,本文将对 LW_OOPC 的配置和使用方法进行讲解,并对这些宏逐个进行细致讲解。期望本文能给希望在实践中应用 LW_OOPC 的 C 程序员带来帮助。
|
||||
|
||||
LW_OOPC当前版本共有两个文件: lw_oopc.h和lw_oopc.c. LW_OOPC的使用非常简单, 只需要将这两个文件加入工程即可. 常规情况下, 建议用户同时使用上述两个文件, 因为借助lw_oopc.c, 我们可以监测到内存泄漏, 通过打开调试开关, 我们能够观察内存分配和释放的调试打印信息, 这将有助于我们除错, 减少调试的时间. 如果你不需要监测内存泄漏(如准备发布程序), 此时, 你并不需要lw_oopc.c, 而只需要lw_oopc.h即可.
|
||||
LW_OOPC 当前版本共有两个文件:lw_oopc.h 和 lw_oopc.c. LW_OOPC 的使用非常简单,只需要将这两个文件加入工程即可。常规情况下,建议用户同时使用上述两个文件,因为借助 lw_oopc.c,我们可以监测到内存泄漏,通过打开调试开关,我们能够观察内存分配和释放的调试打印信息,这将有助于我们除错,减少调试的时间。如果你不需要监测内存泄漏 (如准备发布程序),此时,你并不需要 lw_oopc.c,而只需要 lw_oopc.h 即可。
|
||||
|
||||
### LW_OOPC配置
|
||||
### LW_OOPC 配置
|
||||
|
||||
在 lw_oopc.h 中,有这么几行代码:
|
||||
|
||||
在lw_oopc.h中, 有这么几行代码:
|
||||
```c
|
||||
// 配置宏(两种配置选其一):
|
||||
// LW_OOPC_USE_STDDEF_OFFSETOF 表示使用C标准定义的offsetof
|
||||
// LW_OOPC_USE_USER_DEFINED_OFFSETOF 表示使用用户自定义的lw_oopc_offsetof宏
|
||||
// 配置宏 (两种配置选其一):
|
||||
// LW_OOPC_USE_STDDEF_OFFSETOF 表示使用 C 标准定义的 offsetof
|
||||
// LW_OOPC_USE_USER_DEFINED_OFFSETOF 表示使用用户自定义的 lw_oopc_offsetof 宏
|
||||
#define LW_OOPC_USE_STDDEF_OFFSETOF
|
||||
//#define LW_OOPC_USE_USER_DEFINED_OFFSETOF
|
||||
|
||||
@ -23,33 +24,33 @@ LW_OOPC当前版本共有两个文件: lw_oopc.h和lw_oopc.c. LW_OOPC的使用
|
||||
//#define LW_OOPC_SUPPORT_MEMORY_LEAK_DETECTOR
|
||||
```
|
||||
|
||||
从上边的注释, 我们可以看出, LW_OOPC需要使用offsetof宏, 如果你的开发环境能够支持C标准定义的offsetof宏, 那么什么都不需要动. 如果你的开发环境不能支持C标准定义的offsetof宏, 那么可以选择使用用户自定义的lw_oopc_offsetof宏(如果你的开发环境连用户自定义的offsetof宏都不支持, 在这种情形下, LW_OOPC将无法很好的支持多态特性, 很遗憾, 你只能与LW_OOPC失之交臂).
|
||||
从上边的注释,我们可以看出,LW_OOPC 需要使用 offsetof 宏,如果你的开发环境能够支持 C 标准定义的 offsetof 宏,那么什么都不需要动。如果你的开发环境不能支持 C 标准定义的 offsetof 宏,那么可以选择使用用户自定义的 lw_oopc_offsetof 宏 (如果你的开发环境连用户自定义的 offsetof 宏都不支持,在这种情形下,LW_OOPC 将无法很好的支持多态特性,很遗憾,你只能与 LW_OOPC 失之交臂).
|
||||
|
||||
关于LW_OOPC对内存泄露检测以及调试打印的支持, 我们将在LW_OOPC高级配置部分进行详细讲解.
|
||||
关于 LW_OOPC 对内存泄露检测以及调试打印的支持,我们将在 LW_OOPC 高级配置部分进行详细讲解。
|
||||
|
||||
### LW_OOPC宏说明
|
||||
### LW_OOPC 宏说明
|
||||
|
||||
1. INTERFACE
|
||||
|
||||
INTERFACE用于声明接口, 譬如:
|
||||
INTERFACE 用于声明接口,譬如:
|
||||
|
||||
```C
|
||||
INTERFACE(IMoveable)
|
||||
{
|
||||
void (*move)(IMoveable* t); // Move行为
|
||||
void (*move)(IMoveable* t); // Move 行为
|
||||
};
|
||||
```
|
||||
|
||||
在LW_OOPC中, 声明接口, 抽象类和具体类的方法成员比较特殊, 均是函数指针类型的成员. 事实上, LW_OOPC正是借助了函数指针的特性, 完成了多态功能的模拟.
|
||||
在 LW_OOPC 中,声明接口,抽象类和具体类的方法成员比较特殊,均是函数指针类型的成员。事实上,LW_OOPC 正是借助了函数指针的特性,完成了多态功能的模拟。
|
||||
|
||||
2. ABS_CLASS
|
||||
|
||||
ABS_CLASS用于声明抽象类, 譬如:
|
||||
ABS_CLASS 用于声明抽象类,譬如:
|
||||
|
||||
```C
|
||||
ABS_CLASS(Animal)
|
||||
{
|
||||
char name[128]; // 动物的昵称(假设小于128个字符)
|
||||
char name[128]; // 动物的昵称 (假设小于 128 个字符)
|
||||
int age; // 动物的年龄
|
||||
|
||||
void (*setName)(Animal* t, const char* name); // 设置动物的昵称
|
||||
@ -63,40 +64,40 @@ ABS_CLASS(Animal)
|
||||
|
||||
3. CLASS
|
||||
|
||||
CLASS用于声明具体类, 譬如:
|
||||
CLASS 用于声明具体类,譬如:
|
||||
|
||||
```C
|
||||
CLASS(Fish)
|
||||
{
|
||||
EXTENDS(Animal); // 继承Animal抽象类
|
||||
IMPLEMENTS(IMoveable); // 实现IMoveable接口
|
||||
EXTENDS(Animal); // 继承 Animal 抽象类
|
||||
IMPLEMENTS(IMoveable); // 实现 IMoveable 接口
|
||||
|
||||
void (*init)(Fish* t, const char* name, int age); // 初始化昵称和年龄
|
||||
};
|
||||
```
|
||||
|
||||
在该例中, 我们声明了Fish类, 并让该类继承Animal抽象类, 并且实现IMoveable接口.
|
||||
在该例中,我们声明了 Fish 类,并让该类继承 Animal 抽象类,并且实现 IMoveable 接口。
|
||||
|
||||
4. EXTENDS 和 IMPLEMENTS
|
||||
|
||||
在介绍CLASS宏的时候, 我们在代码中看到有两个宏: EXTENDS和IMPLEMENTS, 如果你查看lw_oopc.h的源码, 你将会发现他们是一模一样的:
|
||||
在介绍 CLASS 宏的时候,我们在代码中看到有两个宏:EXTENDS 和 IMPLEMENTS,如果你查看 lw_oopc.h 的源码,你将会发现他们是一模一样的:
|
||||
|
||||
```C
|
||||
#define IMPLEMENTS(type) struct type type
|
||||
#define EXTENDS(type) struct type type
|
||||
```
|
||||
|
||||
之所以同时提供继承和实现关键字, 仅仅是为了让熟悉Java的人更加容易理解LW_OOPC宏. (注意, 在LW_OOPC中, 建议将继承和实现声明写在结构体的开头, 把继承和实现声明摆在显眼的位置, 有助于阅读代码的人更好的理解代码).
|
||||
之所以同时提供继承和实现关键字,仅仅是为了让熟悉 Java 的人更加容易理解 LW_OOPC 宏。(注意,在 LW_OOPC 中,建议将继承和实现声明写在结构体的开头,把继承和实现声明摆在显眼的位置,有助于阅读代码的人更好的理解代码).
|
||||
|
||||
5. ABS_CTOR 和 END_ABS_CTOR
|
||||
|
||||
ABS_CTOR和END_ABS_CTOR用于定义抽象类的构造函数, 例如:
|
||||
ABS_CTOR 和 END_ABS_CTOR 用于定义抽象类的构造函数,例如:
|
||||
|
||||
```C
|
||||
/* 设置动物的昵称 */
|
||||
void Animal_setName(Animal* t, const char* name)
|
||||
{
|
||||
// 这里假定name不会超过128个字符,为简化示例代码,不做保护(产品代码中不要这样写)
|
||||
// 这里假定 name 不会超过 128 个字符,为简化示例代码,不做保护(产品代码中不要这样写)
|
||||
strcpy(t->name, name);
|
||||
}
|
||||
/* 设置动物的年龄 */
|
||||
@ -107,7 +108,7 @@ void Animal_setAge(Animal* t, int age)
|
||||
/* 动物和我们打招呼 */
|
||||
void Animal_sayHello(Animal* t)
|
||||
{
|
||||
printf("Hello! 我是%s,今年%d岁了!\n", t->name, t->age);
|
||||
printf("Hello!我是%s,今年%d岁了!\n", t->name, t->age);
|
||||
}
|
||||
/* 初始化动物的昵称和年龄 */
|
||||
void Animal_init(Animal* t, const char* name, int age)
|
||||
@ -124,23 +125,23 @@ FUNCTION_SETTING(init, Animal_init);
|
||||
END_ABS_CTOR
|
||||
```
|
||||
|
||||
前面, 我们声明Animal是一个抽象类, 对应的构造函数定义需要使用ABS_CTOR和END_ABS_CTOR. ABS_CTOR是Abstract Constructor的缩写.
|
||||
前面,我们声明 Animal 是一个抽象类,对应的构造函数定义需要使用 ABS_CTOR 和 END_ABS_CTOR. ABS_CTOR 是 Abstract Constructor 的缩写。
|
||||
|
||||
6. FUNCTION_SETTING
|
||||
|
||||
在介绍ABS_CTOR和END_ABS_CTOR宏的时候, 我们在代码中又发现一个陌生的宏:
|
||||
FUNCTION_SETTING, 这个宏在LW_OOPC中的地位非同凡响, 没有它, LW_OOPC就不可能存在. LW_OOPC中的CTOR系列宏(CTOR/END_CTOR, ABS_CTOR/END_ABS_CTOR)除了给对象(在C语言中是struct)分配内存, 然后, 最重要的一个步骤是为结构体中的函数指针成员赋值. 这一过程, 也可以称为函数绑定(有点类似C++中的动态联编). 函数绑定的过程由FUNCTION_SETTING宏来完成.
|
||||
我们来看看FUNCTION_SETTING宏是如何实现的:
|
||||
在介绍 ABS_CTOR 和 END_ABS_CTOR 宏的时候,我们在代码中又发现一个陌生的宏:
|
||||
FUNCTION_SETTING,这个宏在 LW_OOPC 中的地位非同凡响,没有它,LW_OOPC 就不可能存在。LW_OOPC 中的 CTOR 系列宏 (CTOR/END_CTOR, ABS_CTOR/END_ABS_CTOR) 除了给对象 (在 C 语言中是 struct) 分配内存,然后,最重要的一个步骤是为结构体中的函数指针成员赋值。这一过程,也可以称为函数绑定 (有点类似 C++ 中的动态联编). 函数绑定的过程由 FUNCTION_SETTING 宏来完成。
|
||||
我们来看看 FUNCTION_SETTING 宏是如何实现的:
|
||||
|
||||
```C
|
||||
#define FUNCTION_SETTING(f1, f2) cthis->f1 = f2;
|
||||
```
|
||||
|
||||
看到这里, 想必读者应该会心一笑了. :)
|
||||
看到这里,想必读者应该会心一笑了。:)
|
||||
|
||||
7. CTOR 和 END_CTOR
|
||||
|
||||
CTOR和END_CTOR用于定义具体类的构造函数, 例如:
|
||||
CTOR 和 END_CTOR 用于定义具体类的构造函数,例如:
|
||||
|
||||
```C
|
||||
/* 鱼的吃行为 */
|
||||
@ -174,74 +175,74 @@ FUNCTION_SETTING(IMoveable.move, Fish_move);
|
||||
FUNCTION_SETTING(init, Fish_init);
|
||||
END_CTOR
|
||||
```
|
||||
从代码上看, CTOR/END_CTOR 与 ABS_CTOR/END_ABS_CTOR 的使用方式完全相同. 的确是, 不过, 背后, 这两对宏的实现方式略有差异, 建议有兴趣的读者, 认真研究一下LW_OOPC的源码. 这里, 简单地说明如下: 我们希望明确区分抽象类和具体类的概念, 抽象类是不可以创建对象的, 而具体类则可以. 前面, 我们声明了Animal是抽象类, Fish类是具体类. 那么, 我们希望:
|
||||
从代码上看,CTOR/END_CTOR 与 ABS_CTOR/END_ABS_CTOR 的使用方式完全相同。的确是,不过,背后,这两对宏的实现方式略有差异,建议有兴趣的读者,认真研究一下 LW_OOPC 的源码。这里,简单地说明如下:我们希望明确区分抽象类和具体类的概念,抽象类是不可以创建对象的,而具体类则可以。前面,我们声明了 Animal 是抽象类,Fish 类是具体类。那么,我们希望:
|
||||
|
||||
```C
|
||||
Animal* animal = Animal_new(); // 不允许这样写!
|
||||
Fish* fish = Fish_new(); // 允许这样写!
|
||||
Animal* animal = Animal_new(); // 不允许这样写!
|
||||
Fish* fish = Fish_new(); // 允许这样写!
|
||||
```
|
||||
|
||||
8. SUPER_CTOR
|
||||
|
||||
在讲解CTOR/END_CTOR宏的时候, 又出现一个陌生的宏: SUPER_CTOR. 它的功能与Java中的super关键字非常类似.
|
||||
在讲解 CTOR/END_CTOR 宏的时候,又出现一个陌生的宏:SUPER_CTOR. 它的功能与 Java 中的 super 关键字非常类似。
|
||||
|
||||
```C
|
||||
SUPER_CTOR(Animal);
|
||||
```
|
||||
|
||||
意为: 调用Animal类的构造函数.(建议将SUPER_CTOR写在"构造函数"体的开头).
|
||||
意为:调用 Animal 类的构造函数.(建议将 SUPER_CTOR 写在"构造函数"体的开头).
|
||||
|
||||
9. DTOR 和 END_DTOR
|
||||
|
||||
DTOR和END_DTOR用于定义“析构函数”, 例如:
|
||||
DTOR 和 END_DTOR 用于定义“析构函数”,例如:
|
||||
|
||||
```C
|
||||
// Expr_node的析构函数(DTOR/END_DTOR用于实现析构函数语义)
|
||||
// Expr_node 的析构函数 (DTOR/END_DTOR 用于实现析构函数语义)
|
||||
DTOR(Expr_node)
|
||||
if (--cthis->use == 0) // 递减引用计数,如果计数为0,释放自己
|
||||
if (--cthis->use == 0) // 递减引用计数,如果计数为 0,释放自己
|
||||
{
|
||||
cthis->finalize(cthis); // 释放内存之前先清理资源(其他需要释放的对象)
|
||||
cthis->finalize(cthis); // 释放内存之前先清理资源 (其他需要释放的对象)
|
||||
lw_oopc_free(cthis);
|
||||
}
|
||||
END_DTOR
|
||||
```
|
||||
|
||||
这里, 特别说明一点, 为了模拟C++中的this指针, 我们允许用户在ABS_CTOR/END_ABS_CTOR, CTOR/END_CTOR, DTOR/END_DTOR定义块中可以直接使用cthis.
|
||||
这里,特别说明一点,为了模拟 C++ 中的 this 指针,我们允许用户在 ABS_CTOR/END_ABS_CTOR, CTOR/END_CTOR, DTOR/END_DTOR 定义块中可以直接使用 cthis.
|
||||
|
||||
10. SUPER_PTR
|
||||
|
||||
SUPER_PTR用于"向上转型", 将对象指针向上转型成直接父类或者直接接口:
|
||||
SUPER_PTR 用于"向上转型",将对象指针向上转型成直接父类或者直接接口:
|
||||
|
||||
```C
|
||||
Fish* fish = Fish_new(); // 创建鱼对象
|
||||
|
||||
// 初始化鱼对象的昵称为: 小鲤鱼,年龄为: 1岁
|
||||
// 初始化鱼对象的昵称为:小鲤鱼,年龄为: 1 岁
|
||||
fish->init(fish, "小鲤鱼", 1);
|
||||
|
||||
// 将fish指针转型为Animal类型指针, 并赋值给animals数组的第一个成员
|
||||
// 将 fish 指针转型为 Animal 类型指针,并赋值给 animals 数组的第一个成员
|
||||
Animal* animal = SUPER_PTR(fish, Animal);
|
||||
|
||||
// 将fish指针转型为IMoveable接口类型指针, 并赋值给moveObjs数组的第一个成员
|
||||
// 将 fish 指针转型为 IMoveable 接口类型指针,并赋值给 moveObjs 数组的第一个成员
|
||||
IMoveable* moveFish = SUPER_PTR(fish, IMoveable);
|
||||
```
|
||||
|
||||
这里, 直接父类很容易理解, 直接接口, 呵呵, 暂且认为我是首创的吧. 我们再来看一下Fish类的声明代码:
|
||||
这里,直接父类很容易理解,直接接口,呵呵,暂且认为我是首创的吧。我们再来看一下 Fish 类的声明代码:
|
||||
|
||||
```C
|
||||
CLASS(Fish)
|
||||
{
|
||||
EXTENDS(Animal); // 继承Animal抽象类
|
||||
IMPLEMENTS(IMoveable); // 实现IMoveable接口
|
||||
EXTENDS(Animal); // 继承 Animal 抽象类
|
||||
IMPLEMENTS(IMoveable); // 实现 IMoveable 接口
|
||||
|
||||
void (*init)(Fish* t, const char* name, int age); // 初始化昵称和年龄
|
||||
};
|
||||
```
|
||||
|
||||
对Fish类来讲, IMoveable就是它的直接接口.
|
||||
对 Fish 类来讲,IMoveable 就是它的直接接口。
|
||||
|
||||
11. SUPER_PTR_2 和 SUPER_PTR_3
|
||||
|
||||
SUPER_PTR_2和SUPER_PTR_3是SUPER_PTR的高级版本, 它们的作用与SUPER_PTR是完全类似的, 都是向上转型. 只不过, SUPER_PTR_2是向上转两次, SUPER_PTR_3是向上转三次. 也就是说, SUPER_PTR_2用于将自身的指针转型为爷爷辈指针, SUPER_PTR_3用于将自身的指针转型为曾祖辈指针. 看看SUPER_PTR_2和SUPER_PTR_3的代码:
|
||||
SUPER_PTR_2 和 SUPER_PTR_3 是 SUPER_PTR 的高级版本,它们的作用与 SUPER_PTR 是完全类似的,都是向上转型。只不过,SUPER_PTR_2 是向上转两次,SUPER_PTR_3 是向上转三次。也就是说,SUPER_PTR_2 用于将自身的指针转型为爷爷辈指针,SUPER_PTR_3 用于将自身的指针转型为曾祖辈指针。看看 SUPER_PTR_2 和 SUPER_PTR_3 的代码:
|
||||
|
||||
```C
|
||||
#define SUPER_PTR_2(cthis, father, grandfather) \
|
||||
@ -251,27 +252,27 @@ SUPER_PTR_2和SUPER_PTR_3是SUPER_PTR的高级版本, 它们的作用与SUPER_PT
|
||||
SUPER_PTR(SUPER_PTR_2(cthis, father, grandfather), greatgrandfather)
|
||||
```
|
||||
|
||||
看到了吧, SUPER_PTR_2其实是两次SUPER_PTR的叠加. SUPER_PTR_3是三次SUPER_PTR的叠加. 由于转型两次或者转型三次, 会使得程序过于复杂, 所以, 建议大家合理组织类的继承关系, 尽力避免使用二次转型和三次转型.
|
||||
看到了吧,SUPER_PTR_2 其实是两次 SUPER_PTR 的叠加。SUPER_PTR_3 是三次 SUPER_PTR 的叠加。由于转型两次或者转型三次,会使得程序过于复杂,所以,建议大家合理组织类的继承关系,尽力避免使用二次转型和三次转型。
|
||||
|
||||
12. SUB_PTR
|
||||
|
||||
SUB_PTR用于"向下转型", 将父类指针向下转型成子类:
|
||||
SUB_PTR 用于"向下转型",将父类指针向下转型成子类:
|
||||
|
||||
```C
|
||||
/* 鱼的吃行为*/
|
||||
void Fish_eat(Animal* t)
|
||||
{
|
||||
Fish* fish = SUB_PTR(t, Animal, Fish);
|
||||
…… // 这里可以访问Fish类的成员
|
||||
…… // 这里可以访问 Fish 类的成员
|
||||
printf("鱼吃水草!\n");
|
||||
}
|
||||
```
|
||||
|
||||
eat方法是Animal的一个方法, Fish类覆写了该方法, 注意, 由于该方法的第一个参数类型是: Animal*, 在实现Fish类的eat方法Fish_eat时, 如果想要访问到Fish类的成员, 需要将第一个参数向下转型成Fish*, 这就是SUB_PTR所完成的事情.
|
||||
eat 方法是 Animal 的一个方法,Fish 类覆写了该方法,注意,由于该方法的第一个参数类型是:Animal*,在实现 Fish 类的 eat 方法 Fish_eat 时,如果想要访问到 Fish 类的成员,需要将第一个参数向下转型成 Fish*,这就是 SUB_PTR 所完成的事情。
|
||||
|
||||
13. SUB_PTR_2 和 SUB_PTR_3
|
||||
|
||||
SUB_PTR_2和SUB_PTR_3是SUB_PTR的高级版本, 它们的作用与SUB_PTR是完全类似的, 都是向下转型. 只不过, SUB_PTR_2是向下转两次, SUB_PTR_3是向下转三次. 也就是说, SUB_PTR_2用于将自身的指针转型为孙子辈指针, SUB_PTR_3用于将自身的指针转型为曾孙辈指针. 看看SUB_PTR_2和SUB_PTR_3的代码:
|
||||
SUB_PTR_2 和 SUB_PTR_3 是 SUB_PTR 的高级版本,它们的作用与 SUB_PTR 是完全类似的,都是向下转型。只不过,SUB_PTR_2 是向下转两次,SUB_PTR_3 是向下转三次。也就是说,SUB_PTR_2 用于将自身的指针转型为孙子辈指针,SUB_PTR_3 用于将自身的指针转型为曾孙辈指针。看看 SUB_PTR_2 和 SUB_PTR_3 的代码:
|
||||
|
||||
```C
|
||||
#define SUB_PTR_2(selfptr, self, child, grandchild) \
|
||||
@ -281,44 +282,44 @@ SUB_PTR_2和SUB_PTR_3是SUB_PTR的高级版本, 它们的作用与SUB_PTR是完
|
||||
SUB_PTR(SUB_PTR_2(selfptr, self, child, grandchild), grandchild, greatgrandchild)
|
||||
```
|
||||
|
||||
看到了吧, SUB_PTR_2其实是两次SUB_PTR的叠加. SUB_PTR_3是三次SUB_PTR的叠加. 由于转型两次或者转型三次, 会使得程序过于复杂, 所以, 建议大家合理组织类的继承关系, 尽力避免使用二次转型和三次转型.
|
||||
看到了吧,SUB_PTR_2 其实是两次 SUB_PTR 的叠加。SUB_PTR_3 是三次 SUB_PTR 的叠加。由于转型两次或者转型三次,会使得程序过于复杂,所以,建议大家合理组织类的继承关系,尽力避免使用二次转型和三次转型。
|
||||
|
||||
14. INHERIT_FROM
|
||||
|
||||
INHERIT_FROM用于访问直接父类的成员, 例如:
|
||||
INHERIT_FROM 用于访问直接父类的成员,例如:
|
||||
|
||||
```C
|
||||
Dog* dog = Dog_new(); // 创建狗对象
|
||||
|
||||
// 初始化狗对象的昵称为: 牧羊犬, 年龄为: 2岁
|
||||
// 初始化狗对象的昵称为:牧羊犬,年龄为: 2 岁
|
||||
dog->init(dog, "牧羊犬", 2);
|
||||
INHERIT_FROM(Animal, dog, age) = 3; // 把牧羊犬的年龄修改为3岁
|
||||
printf("狗的年龄是:%d岁!\n", INHERIT_FROM(Animal, dog, age)); // 打印狗的年龄
|
||||
INHERIT_FROM(Animal, dog, age) = 3; // 把牧羊犬的年龄修改为 3 岁
|
||||
printf("狗的年龄是:%d岁!\n", INHERIT_FROM(Animal, dog, age)); // 打印狗的年龄
|
||||
```
|
||||
|
||||
注意, LW_OOPC的上一个版本, 我们同时提供了INHERIT_FROM_2和INHERIT_FROM_3这两个宏, INHERIT_FROM_2用于访问爷爷辈的成员, INHERIT_FROM_3用于访问曾祖辈的成员. 我们认为应当尽量避免使用INHERIT_FROM_2和INHERIT_FROM_3宏, 因为, 这会导致类的继承关系中存在严重的数据耦合(自身类可以直接访问爷爷辈, 甚至曾祖父辈的成员), 这将导致程序难于理解, 难于维护. 因此, 在当前版本中, 删除了INHERIT_FROM_2和INHERIT_FROM_3宏, 仅仅保留INHERIT_FROM. 一般情况下, 我们可以通过更加合理的函数封装, 让当前类通过祖先类提供的方法间接地访问祖先类的成员. 如果确实要在当前类中直接访问爷爷辈甚至曾祖辈的成员, 我们可以先通过SUPER_PTR_2和SUPER_PTR_3将当前对象的指针转型为对应的祖先类指针, 然后再通过其指针访问其成员.
|
||||
注意,LW_OOPC 的上一个版本,我们同时提供了 INHERIT_FROM_2 和 INHERIT_FROM_3 这两个宏,INHERIT_FROM_2 用于访问爷爷辈的成员,INHERIT_FROM_3 用于访问曾祖辈的成员。我们认为应当尽量避免使用 INHERIT_FROM_2 和 INHERIT_FROM_3 宏,因为,这会导致类的继承关系中存在严重的数据耦合 (自身类可以直接访问爷爷辈,甚至曾祖父辈的成员),这将导致程序难于理解,难于维护。因此,在当前版本中,删除了 INHERIT_FROM_2 和 INHERIT_FROM_3 宏,仅仅保留 INHERIT_FROM. 一般情况下,我们可以通过更加合理的函数封装,让当前类通过祖先类提供的方法间接地访问祖先类的成员。如果确实要在当前类中直接访问爷爷辈甚至曾祖辈的成员,我们可以先通过 SUPER_PTR_2 和 SUPER_PTR_3 将当前对象的指针转型为对应的祖先类指针,然后再通过其指针访问其成员。
|
||||
|
||||
写到这里, LW_OOPC所有的宏都介绍完毕了. 下面, 我们介绍LW_OOPC对内存泄漏和调试信息打印的支持.
|
||||
写到这里,LW_OOPC 所有的宏都介绍完毕了。下面,我们介绍 LW_OOPC 对内存泄漏和调试信息打印的支持。
|
||||
|
||||
### LW_OOPC高级配置
|
||||
### LW_OOPC 高级配置
|
||||
|
||||
缺省情况下, LW_OOPC不支持内存泄漏检测, 如果需要支持, 只要将lw_oopc.h文件中
|
||||
缺省情况下,LW_OOPC 不支持内存泄漏检测,如果需要支持,只要将 lw_oopc.h 文件中
|
||||
|
||||
```C
|
||||
//#define LW_OOPC_SUPPORT_MEMORY_LEAK_DETECTOR 去掉行注释符即可
|
||||
```
|
||||
|
||||
一旦你决定让LW_OOPC支持内存泄露检测, 那么, 你必须同时将lw_oopc.c加入工程.
|
||||
缺省情况下, LW_OOPC不支持调试信息打印, 如果需要支持, 只要将lw_oopc.h文件中
|
||||
一旦你决定让 LW_OOPC 支持内存泄露检测,那么,你必须同时将 lw_oopc.c 加入工程。
|
||||
缺省情况下,LW_OOPC 不支持调试信息打印,如果需要支持,只要将 lw_oopc.h 文件中
|
||||
|
||||
```C
|
||||
//#define LW_OOPC_PRINT_DEBUG_INFO 去掉行注释符即可
|
||||
```
|
||||
|
||||
### 实例(demo/expr-advance)
|
||||
### 实例 (demo/expr-advance)
|
||||
|
||||
将lw_oopc.h中LW_OOPC_SUPPORT_MEMORY_LEAK_DETECTOR宏打开.
|
||||
main.c的内容, 一开始是这样的:
|
||||
将 lw_oopc.h 中 LW_OOPC_SUPPORT_MEMORY_LEAK_DETECTOR 宏打开。
|
||||
main.c 的内容,一开始是这样的:
|
||||
|
||||
```C
|
||||
#include <stdio.h>
|
||||
@ -349,7 +350,7 @@ int main() {
|
||||
}
|
||||
```
|
||||
|
||||
得到编译错误:
|
||||
得到编译错误:
|
||||
|
||||
```bash
|
||||
liuboyf1@ipc:~/data/custom/aklw_oopc/demo/ExprAdvance$ gcc *.c
|
||||
@ -368,7 +369,7 @@ In file included from main.c:2:0:
|
||||
expr.h:16:1: note: declared here
|
||||
```
|
||||
|
||||
为了支持内存泄漏的检测, 我们需要给每个"构造函数"传入文件名和行号参数. LW_OOPC已经为我们准备了一个宏: lw_oopc_file_line. 这里, 这个宏违反了编程规范(本来宏应该是全部采用大写字母), 不过, 这里之所以采用小写, 是故意希望用户产生一种错觉, 让用户以为lw_oopc_file_line是一个特殊的实参, 该实参包含了文件和行号的信息. 看看修正编译错误后的代码:
|
||||
为了支持内存泄漏的检测,我们需要给每个"构造函数"传入文件名和行号参数。LW_OOPC 已经为我们准备了一个宏:lw_oopc_file_line. 这里,这个宏违反了编程规范 (本来宏应该是全部采用大写字母),不过,这里之所以采用小写,是故意希望用户产生一种错觉,让用户以为 lw_oopc_file_line 是一个特殊的实参,该实参包含了文件和行号的信息。看看修正编译错误后的代码:
|
||||
|
||||
```C
|
||||
#include <stdio.h>
|
||||
@ -399,16 +400,16 @@ int main() {
|
||||
|
||||
看看
|
||||
Expr* expr1 = Expr_new(lw_oopc_file_line);
|
||||
相比Expr* expr1 = Expr_new();
|
||||
相比 Expr* expr1 = Expr_new();
|
||||
多了一个"实参": lw_oopc_file_line.
|
||||
|
||||
仅仅增加了一个参数, 我们并不能检测到该程序是否有内存泄漏. 我们还得在main函数的最后一条语句: return 0; 之前加一条语句:
|
||||
仅仅增加了一个参数,我们并不能检测到该程序是否有内存泄漏。我们还得在 main 函数的最后一条语句:return 0; 之前加一条语句:
|
||||
|
||||
``` C
|
||||
lw_oopc_report();
|
||||
```
|
||||
|
||||
运行结果:
|
||||
运行结果:
|
||||
|
||||
```bash
|
||||
liuboyf1@ipc:~/data/custom/aklw_oopc/demo/ExprAdvance$ ./a.out
|
||||
@ -416,20 +417,20 @@ liuboyf1@ipc:~/data/custom/aklw_oopc/demo/ExprAdvance$ ./a.out
|
||||
lw_oopc: no memory leak.
|
||||
```
|
||||
|
||||
看到lw_oopc: no memory leak.这句话了吧. 这表明我们的程序没有内存泄漏. 另外, LW_OOPC还会生成memory_detector_result.txt文件, 如下图所示:
|
||||
看到 lw_oopc: no memory leak.这句话了吧。这表明我们的程序没有内存泄漏。另外,LW_OOPC 还会生成 memory_detector_result.txt 文件,如下图所示:
|
||||
|
||||
```bash
|
||||
liuboyf1@ipc:~/data/custom/aklw_oopc/demo/ExprAdvance$ cat memory_detector_result.txt
|
||||
lw_oopc: no memory leak.
|
||||
```
|
||||
|
||||
我们试着注释掉一句代码, 如下所示:
|
||||
我们试着注释掉一句代码,如下所示:
|
||||
|
||||
```C
|
||||
//Expr_delete(expr1);
|
||||
```
|
||||
|
||||
再编译和运行一下, 我们看到我们的程序有内存泄漏:
|
||||
再编译和运行一下,我们看到我们的程序有内存泄漏:
|
||||
|
||||
```bash
|
||||
liuboyf1@ipc:~/data/custom/aklw_oopc/demo/ExprAdvance$ ./a.out
|
||||
@ -447,8 +448,8 @@ memory leak in: 0x1ab6650, size: 80, file: expr.c, line: 36
|
||||
memory leak in: 0x1ab6010, size: 80, file: main.c, line: 5
|
||||
```
|
||||
|
||||
前面的例子, 如果都是假设想要在堆上创建对象. 有一种情形, 我们一直没有涉及: 如果我们想让代码在堆栈上创建对象, 代码该如何写?
|
||||
很简单, 创建对象:
|
||||
前面的例子,如果都是假设想要在堆上创建对象。有一种情形,我们一直没有涉及:如果我们想让代码在堆栈上创建对象,代码该如何写?
|
||||
很简单,创建对象:
|
||||
|
||||
```C
|
||||
Expr expr;
|
||||
@ -461,4 +462,4 @@ Expr_ctor(&expr);
|
||||
Expr_dtor(&expr);
|
||||
```
|
||||
|
||||
到这里, 本文该结束了, 希望本文已经将LW_OOPC的配置以及所有的宏讲明白了. :)如果你看了这篇文章, 还有疑虑, 可以查看lw_oopc.h和lw_oopc.c源文件. 源码面前, 了无秘密.:) 如果你看了源码, 还有疑问, 可以与我联系.
|
||||
到这里,本文该结束了,希望本文已经将 LW_OOPC 的配置以及所有的宏讲明白了。:) 如果你看了这篇文章,还有疑虑,可以查看 lw_oopc.h 和 lw_oopc.c 源文件。源码面前,了无秘密.:) 如果你看了源码,还有疑问,可以与我联系。
|
||||
|
Binary file not shown.
@ -1,45 +1,45 @@
|
||||
|
||||
轻量级的面向对象C语言编程框架LW_OOPC介绍
|
||||
轻量级的面向对象 C 语言编程框架 LW_OOPC 介绍
|
||||
========================================
|
||||
|
||||
### 摘要
|
||||
|
||||
本文介绍一种轻量级的面向对象的C语言编程框架: LW_OOPC. LW_OOPC是Light-Weight Object-Oriented Programming in(with) C的缩写, 总共一个.h文件, 20个宏, 约130行代码, 非常的轻量级, 但却很好的支持了很多面向对象的特性, 比如继承, 多态. 可以优美的实现面向接口编程. 这个框架是由台湾的高焕堂先生以及他的MISOO团队首创, 之后由我继续改进优化, 最后, 经高焕堂同意以LGPL协议开源(开源网址参见后文).
|
||||
本文介绍一种轻量级的面向对象的 C 语言编程框架: LW_OOPC. LW_OOPC 是 Light-Weight Object-Oriented Programming in(with) C 的缩写,总共一个.h 文件,20 个宏,约 130 行代码,非常的轻量级,但却很好的支持了很多面向对象的特性,比如继承,多态。可以优美的实现面向接口编程. 这个框架是由台湾的高焕堂先生以及他的 MISOO 团队首创,之后由我继续改进优化,最后,经高焕堂同意以 LGPL 协议开源 (开源网址参见后文).
|
||||
|
||||
用C语言实现OO? 我没听错吗? 这听起来真是太疯狂了!... 大家都知道, C++支持了面向对象和面向泛型编程, 比C要更强大些. 那么, 为什么要在C语言中实践面向对象呢? 为什么不直接使用C++呢?
|
||||
用 C 语言实现 OO?我没听错吗?这听起来真是太疯狂了!... 大家都知道,C++ 支持了面向对象和面向泛型编程,比 C 要更强大些。那么,为什么要在 C 语言中实践面向对象呢?为什么不直接使用 C++ 呢?
|
||||
|
||||
### 为什么要用面向对象?
|
||||
### 为什么要用面向对象?
|
||||
|
||||
面向过程方式开发的系统, 代码复杂, 耦合性强, 难以维护. 随着我们所要解决的问题越来越复杂, 代码也变得越来越复杂, 越来越难以掌控. 而面向对象改变了程序员的思维方式, 以更加符合客观世界的方式来认识世界, 通过合理的运用抽象, 封装, 继承和多态, 更好的组织程序, 从而很好地应对这种复杂性.
|
||||
面向过程方式开发的系统,代码复杂,耦合性强,难以维护。随着我们所要解决的问题越来越复杂,代码也变得越来越复杂,越来越难以掌控。而面向对象改变了程序员的思维方式,以更加符合客观世界的方式来认识世界,通过合理的运用抽象,封装,继承和多态,更好的组织程序,从而很好地应对这种复杂性。
|
||||
|
||||
### 为什么不直接使用C++?
|
||||
### 为什么不直接使用 C++?
|
||||
|
||||
C和C++之争由来已久, 可能要持续到它们中的一种去世^_^. C语言以其简洁明快, 功能强大的特点, 深得开发人员的喜爱, 尤其是在嵌入式开发领域, C语言更是占据了绝对老大的地位. 在我看来, 语言只是工具, 作为程序员, 我们要做的是: 选择合适的语言, 解决恰当的问题. 我们要尊重事实, 考虑开发环境(软硬件环境), 考虑团队成员的水平, 从商用工程的角度讲, 选择团队成员擅长的语言进行开发, 风险要小很多.
|
||||
C和 C++ 之争由来已久,可能要持续到它们中的一种去世^_^. C 语言以其简洁明快,功能强大的特点,深得开发人员的喜爱,尤其是在嵌入式开发领域,C 语言更是占据了绝对老大的地位。在我看来,语言只是工具,作为程序员,我们要做的是:选择合适的语言,解决恰当的问题。我们要尊重事实,考虑开发环境 (软硬件环境),考虑团队成员的水平,从商用工程的角度讲,选择团队成员擅长的语言进行开发,风险要小很多。
|
||||
|
||||
一些从Java/C#转到C的程序员们, 无法从面向对象切换到面向过程, 但又必须与C语言同事们在遗留的C系统上开发软件, 他们有时会非常困惑: C语言是面向过程的编程语言, 如何实践面向对象, 甚至面向接口编程呢? 此时, 就非常需要在C语言中实现面向对象的手段, 而LW_OOPC正是应对这一难题的解决之道.
|
||||
一些从 Java/C#转到 C 的程序员们,无法从面向对象切换到面向过程,但又必须与 C 语言同事们在遗留的 C 系统上开发软件,他们有时会非常困惑: C 语言是面向过程的编程语言,如何实践面向对象,甚至面向接口编程呢?此时,就非常需要在 C 语言中实现面向对象的手段,而 LW_OOPC 正是应对这一难题的解决之道。
|
||||
|
||||
### LW_OOPC是什么?
|
||||
### LW_OOPC 是什么?
|
||||
|
||||
简而言之: LW_OOPC是一套C语言的宏, 总共1个.h文件(如果需要内存泄漏检测支持以及调试打印支持,那么还需要1个.c文件(w_oopc.c,约145行)), 20个宏, 约130行代码. LW_OOPC是一种C语言编程框架, 用于支持在C语言中进行面向对象编程.
|
||||
简而言之: LW_OOPC 是一套 C 语言的宏,总共 1 个.h 文件 (如果需要内存泄漏检测支持以及调试打印支持,那么还需要 1 个.c 文件 (w_oopc.c,约 145 行)), 20 个宏,约 130 行代码. LW_OOPC 是一种 C 语言编程框架,用于支持在 C 语言中进行面向对象编程。
|
||||
|
||||
### LW_OOPC宏介绍
|
||||
### LW_OOPC 宏介绍
|
||||
|
||||
下面, 先通过一个简单的示例来展示LW_OOPC这套宏的使用方法. 我们要创建这样一些对象: 动物(Animal), 鱼(Fish), 狗(Dog), 车子(Car). 显然, 鱼和狗都属于动物, 都会动. 车子也会动, 但是车子不是动物. 会动是这些对象的共同特征, 但是, 显然它们不属于一个家族. 因此, 我们首先考虑抽象出一个接口(IMoveable), 以描述会动这一行为特征:
|
||||
下面,先通过一个简单的示例来展示 LW_OOPC 这套宏的使用方法。我们要创建这样一些对象: 动物 (Animal),鱼 (Fish),狗 (Dog),车子 (Car). 显然,鱼和狗都属于动物,都会动。车子也会动,但是车子不是动物。会动是这些对象的共同特征,但是,显然它们不属于一个家族。因此,我们首先考虑抽象出一个接口 (IMoveable),以描述会动这一行为特征:
|
||||
|
||||
```C
|
||||
INTERFACE(IMoveable)
|
||||
{
|
||||
void (*move)(IMoveable* t); // Move行为
|
||||
void (*move)(IMoveable* t); // Move 行为
|
||||
};
|
||||
```
|
||||
|
||||
INTERFACE宏用于定义接口, 其成员(方法)均是函数指针类型.
|
||||
然后, 我们分析Animal, 它应该是抽象类还是接口呢? 动物都会吃, 都需要呼吸, 如果仅仅考虑这两个特征, 显然可以把Animal定为接口. 不过, 这里, 为了展示抽象类在LW_OOPC中如何应用. 我们让Animal拥有昵称和年龄属性, 并且, 让动物和我们打招呼(sayHello方法), 但, 我们不允许用户直接创建Animal对象, 所以, 这里把Animal定为抽象类:
|
||||
INTERFACE 宏用于定义接口,其成员 (方法) 均是函数指针类型。
|
||||
然后,我们分析 Animal,它应该是抽象类还是接口呢?动物都会吃,都需要呼吸,如果仅仅考虑这两个特征,显然可以把 Animal 定为接口。不过,这里,为了展示抽象类在 LW_OOPC 中如何应用。我们让 Animal 拥有昵称和年龄属性,并且,让动物和我们打招呼 (sayHello 方法),但,我们不允许用户直接创建 Animal 对象,所以,这里把 Animal 定为抽象类:
|
||||
|
||||
```C
|
||||
ABS_CLASS(Animal)
|
||||
{
|
||||
char name[128]; // 动物的昵称(假设小于128个字符)
|
||||
char name[128]; // 动物的昵称 (假设小于 128 个字符)
|
||||
int age; // 动物的年龄
|
||||
|
||||
void (*setName)(Animal* t, const char* name); // 设置动物的昵称
|
||||
@ -51,42 +51,42 @@ ABS_CLASS(Animal)
|
||||
};
|
||||
```
|
||||
|
||||
ABS_CLASS宏用于定义抽象类, 允许有成员属性. 代码的含义参见代码注释. 紧接着, 我们来定义Fish和Dog类, 它们都继承动物, 然后还实现了IMoveable接口:
|
||||
ABS_CLASS 宏用于定义抽象类,允许有成员属性。代码的含义参见代码注释. 紧接着,我们来定义 Fish 和 Dog 类,它们都继承动物,然后还实现了 IMoveable 接口:
|
||||
|
||||
```C
|
||||
CLASS(Fish)
|
||||
{
|
||||
EXTENDS(Animal); // 继承Animal抽象类
|
||||
IMPLEMENTS(IMoveable); // 实现IMoveable接口
|
||||
EXTENDS(Animal); // 继承 Animal 抽象类
|
||||
IMPLEMENTS(IMoveable); // 实现 IMoveable 接口
|
||||
|
||||
void (*init)(Fish* t, const char* name, int age); // 初始化昵称和年龄
|
||||
};
|
||||
|
||||
CLASS(Dog)
|
||||
{
|
||||
EXTENDS(Animal); // 继承Animal抽象类
|
||||
IMPLEMENTS(IMoveable); // 实现IMoveable接口
|
||||
EXTENDS(Animal); // 继承 Animal 抽象类
|
||||
IMPLEMENTS(IMoveable); // 实现 IMoveable 接口
|
||||
|
||||
void(*init)(Dog* t, const char* name, int age); // 初始化昵称和年龄
|
||||
};
|
||||
```
|
||||
|
||||
为了让Fish对象或Dog对象在创建之后, 能够很方便地初始化昵称和年龄, Fish和Dog类均提供了init方法.下面, 我们来定义Car,车子不是动物, 但可以Move, 因此, 让Car实现IMoveable接口即可:
|
||||
为了让 Fish 对象或 Dog 对象在创建之后,能够很方便地初始化昵称和年龄,Fish 和 Dog 类均提供了 init 方法。下面,我们来定义 Car,车子不是动物,但可以 Move,因此,让 Car 实现 IMoveable 接口即可:
|
||||
|
||||
```C
|
||||
CLASS(Car)
|
||||
{
|
||||
IMPLEMENTS(IMoveable); // 实现IMoveable接口(车子不是动物,但可以Move)
|
||||
IMPLEMENTS(IMoveable); // 实现 IMoveable 接口 (车子不是动物,但可以 Move)
|
||||
};
|
||||
```
|
||||
|
||||
接口, 抽象类, 具体类的定义都已经完成了. 下面, 我们开始实现它们. 接口是不需要实现的, 所以IMoveable没有对应的实现代码. Animal是抽象动物接口, 是半成品, 所以需要提供半成品的实现:
|
||||
接口,抽象类,具体类的定义都已经完成了。下面,我们开始实现它们。接口是不需要实现的,所以 IMoveable 没有对应的实现代码. Animal 是抽象动物接口,是半成品,所以需要提供半成品的实现:
|
||||
|
||||
```C
|
||||
/* 设置动物的昵称*/
|
||||
void Animal_setName(Animal* t, const char* name)
|
||||
{
|
||||
// 这里假定name不会超过128个字符, 为简化示例代码, 不做保护(产品代码中不要这样写)
|
||||
// 这里假定 name 不会超过 128 个字符,为简化示例代码,不做保护 (产品代码中不要这样写)
|
||||
strcpy(t->name, name);
|
||||
}
|
||||
/* 设置动物的年龄*/
|
||||
@ -97,7 +97,7 @@ void Animal_setAge(Animal* t, int age)
|
||||
/* 动物和我们打招呼*/
|
||||
void Animal_sayHello(Animal* t)
|
||||
{
|
||||
printf("Hello! 我是%s,今年%d岁了!\n", t->name, t->age);
|
||||
printf("Hello!我是%s,今年%d岁了!\n", t->name, t->age);
|
||||
}
|
||||
/* 初始化动物的昵称和年龄*/
|
||||
void Animal_init(Animal* t, const char* name, int age)
|
||||
@ -114,24 +114,24 @@ FUNCTION_SETTING(init, Animal_init);
|
||||
END_ABS_CTOR
|
||||
```
|
||||
|
||||
这里出现了几个新的宏, 我们逐个进行讲解. ABS_CTOR表示抽象类的定义开始, ABS_CTOR(Animal)的含义是Animal抽象类的"构造函数"开始. 在C语言里边其实是没有C++中的构造函数的概念的. LW_OOPC中的CTOR系列宏(CTOR/END_CTOR, ABS_CTOR/END_ABS_CTOR)除了给对象(在C语言中是struct实例)分配内存, 然后, 紧接着要为结构体中的函数指针成员赋值, 这一过程, 也可以称为函数绑定(有点类似C++中的动态联编). 函数绑定的过程由FUNCTION_SETTING宏来完成.
|
||||
对于Fish和Dog类的实现, 与Animal基本上是类似的, 除了将ABS_CTOR换成了CTOR, 直接参见代码:
|
||||
这里出现了几个新的宏,我们逐个进行讲解. ABS_CTOR 表示抽象类的定义开始,ABS_CTOR(Animal) 的含义是 Animal 抽象类的"构造函数"开始。在 C 语言里边其实是没有 C++ 中的构造函数的概念的. LW_OOPC 中的 CTOR 系列宏 (CTOR/END_CTOR, ABS_CTOR/END_ABS_CTOR) 除了给对象 (在 C 语言中是 struct 实例) 分配内存,然后,紧接着要为结构体中的函数指针成员赋值,这一过程,也可以称为函数绑定 (有点类似 C++ 中的动态联编). 函数绑定的过程由 FUNCTION_SETTING 宏来完成。
|
||||
对于 Fish 和 Dog 类的实现,与 Animal 基本上是类似的,除了将 ABS_CTOR 换成了 CTOR,直接参见代码:
|
||||
|
||||
```C
|
||||
/* 鱼的吃行为 */
|
||||
void Fish_eat(Animal* t)
|
||||
{
|
||||
printf("鱼吃水草!\n");
|
||||
printf("鱼吃水草!\n");
|
||||
}
|
||||
/* 鱼的呼吸行为 */
|
||||
void Fish_breathe(Animal* t)
|
||||
{
|
||||
printf("鱼用鳃呼吸!\n");
|
||||
printf("鱼用鳃呼吸!\n");
|
||||
}
|
||||
/* 鱼的移动行为 */
|
||||
void Fish_move(IMoveable* t)
|
||||
{
|
||||
printf("鱼在水里游!\n");
|
||||
printf("鱼在水里游!\n");
|
||||
}
|
||||
/* 初始化鱼的昵称和年龄 */
|
||||
void Fish_init(Fish* t, const char* name, int age)
|
||||
@ -150,22 +150,22 @@ FUNCTION_SETTING(init, Fish_init);
|
||||
END_CTOR
|
||||
```
|
||||
|
||||
上面是Fish的实现, 下面看Dog的实现:
|
||||
上面是 Fish 的实现,下面看 Dog 的实现:
|
||||
|
||||
/* 狗的吃行为 */
|
||||
void Dog_eat(Animal* t)
|
||||
{
|
||||
printf("狗吃骨头!\n");
|
||||
printf("狗吃骨头!\n");
|
||||
}
|
||||
/* 狗的呼吸行为 */
|
||||
void Dog_breathe(Animal* t)
|
||||
{
|
||||
printf("狗用肺呼吸!\n");
|
||||
printf("狗用肺呼吸!\n");
|
||||
}
|
||||
/* 狗的移动行为 */
|
||||
void Dog_move(IMoveable* t)
|
||||
{
|
||||
printf("狗在地上跑!\n");
|
||||
printf("狗在地上跑!\n");
|
||||
}
|
||||
/* 初始化狗的昵称和年龄 */
|
||||
void Dog_init(Dog* t, const char* name, int age)
|
||||
@ -183,13 +183,13 @@ FUNCTION_SETTING(IMoveable.move, Dog_move);
|
||||
FUNCTION_SETTING(init, Dog_init);
|
||||
END_CTOR
|
||||
|
||||
细心的朋友可能已经注意到了, 这里又有一个陌生的宏: SUPER_CTOR未介绍. 这个宏是提供给子类用的, 用于调用其直接父类的构造函数(类似Java语言中的super()调用, 在这里, 其实质是要先调用父类的函数绑定过程, 再调用自身的函数绑定过程), 类似Java那样, SUPER_CTOR如果要出现, 需要是ABS_CTOR或者CTOR下面紧跟的第一条语句.
|
||||
最后, 我们把Car类也实现了:
|
||||
细心的朋友可能已经注意到了,这里又有一个陌生的宏: SUPER_CTOR 未介绍。这个宏是提供给子类用的,用于调用其直接父类的构造函数 (类似 Java 语言中的 super() 调用,在这里,其实质是要先调用父类的函数绑定过程,再调用自身的函数绑定过程),类似 Java 那样,SUPER_CTOR 如果要出现,需要是 ABS_CTOR 或者 CTOR 下面紧跟的第一条语句。
|
||||
最后,我们把 Car 类也实现了:
|
||||
|
||||
```C
|
||||
void Car_move(IMoveable* t)
|
||||
{
|
||||
printf("汽车在开动!\n");
|
||||
printf("汽车在开动!\n");
|
||||
}
|
||||
|
||||
CTOR(Car)
|
||||
@ -197,7 +197,7 @@ FUNCTION_SETTING(IMoveable.move, Car_move);
|
||||
END_CTOR
|
||||
```
|
||||
|
||||
下面, 我们实现main方法, 以展示LW_OOPC的威力:
|
||||
下面,我们实现 main 方法,以展示 LW_OOPC 的威力:
|
||||
|
||||
```C
|
||||
#include "animal.h"
|
||||
@ -208,31 +208,31 @@ int main()
|
||||
Dog* dog = Dog_new(); // 创建狗对象
|
||||
Car* car = Car_new(); // 创建车子对象
|
||||
|
||||
Animal* animals[2] = { 0 }; // 初始化动物容器(这里是Animal指针数组)
|
||||
IMoveable* moveObjs[3] = { 0 }; // 初始化可移动物体容器(这里是IMoveable指针数组)
|
||||
Animal* animals[2] = { 0 }; // 初始化动物容器 (这里是 Animal 指针数组)
|
||||
IMoveable* moveObjs[3] = { 0 }; // 初始化可移动物体容器 (这里是 IMoveable 指针数组)
|
||||
|
||||
int i = 0; // i和j是循环变量
|
||||
int i = 0; // i 和 j 是循环变量
|
||||
int j = 0;
|
||||
|
||||
// 初始化鱼对象的昵称为:小鲤鱼,年龄为:1岁
|
||||
// 初始化鱼对象的昵称为:小鲤鱼,年龄为:1 岁
|
||||
fish->init(fish, "小鲤鱼", 1);
|
||||
|
||||
// 将fish指针转型为Animal类型指针,并赋值给animals数组的第一个成员
|
||||
// 将 fish 指针转型为 Animal 类型指针,并赋值给 animals 数组的第一个成员
|
||||
animals[0] = SUPER_PTR(fish, Animal);
|
||||
|
||||
// 初始化狗对象的昵称为:牧羊犬,年龄为:2岁
|
||||
// 初始化狗对象的昵称为:牧羊犬,年龄为:2 岁
|
||||
dog->init(dog, "牧羊犬", 2);
|
||||
|
||||
// 将dog指针转型为Animal类型指针,并赋值给animals数组的第二个成员
|
||||
// 将 dog 指针转型为 Animal 类型指针,并赋值给 animals 数组的第二个成员
|
||||
animals[1] = SUPER_PTR(dog, Animal);
|
||||
|
||||
// 将fish指针转型为IMoveable接口类型指针,并赋值给moveOjbs数组的第一个成员
|
||||
// 将 fish 指针转型为 IMoveable 接口类型指针,并赋值给 moveOjbs 数组的第一个成员
|
||||
moveObjs[0] = SUPER_PTR(fish, IMoveable);
|
||||
|
||||
// 将dog指针转型为IMoveable接口类型指针,并赋值给moveOjbs数组的第二个成员
|
||||
// 将 dog 指针转型为 IMoveable 接口类型指针,并赋值给 moveOjbs 数组的第二个成员
|
||||
moveObjs[1] = SUPER_PTR(dog, IMoveable);
|
||||
|
||||
// 将car指针转型为IMoveable接口类型指针,并赋值给moveOjbs数组的第三个成员
|
||||
// 将 car 指针转型为 IMoveable 接口类型指针,并赋值给 moveOjbs 数组的第三个成员
|
||||
moveObjs[2] = SUPER_PTR(car, IMoveable);
|
||||
|
||||
// 循环打印动物容器内的动物信息
|
||||
@ -259,23 +259,24 @@ int main()
|
||||
}
|
||||
```
|
||||
|
||||
从上边的代码中, 我们惊喜地发现, 在C语言中, 借助LW_OOPC, 我们实现了将不同的动物(Fish和Dog对象)装入Animal容器, 然后可以用完全相同的方式调用Animal的方法(比如eat和breathe方法), 而实际调用的是具体的实现类(Fish和Dog)的对应方法. 这正是面向对象中的多态的概念. 同样, 我们可以将Fish对象, Dog对象, 以及Car对象均视为可移动物体, 均装入IMoveable容器, 然后用完全相同的方式调用IMoveable接口的move方法. 看到了吗? 借助LW_OOPC, 在C语言下我们竟然可以轻松地实现面向对象和面向接口编程!
|
||||
从上边的代码中,我们惊喜地发现,在 C 语言中,借助 LW_OOPC,我们实现了将不同的动物 (Fish 和 Dog 对象) 装入 Animal 容器,然后可以用完全相同的方式调用 Animal 的方法 (比如 eat 和 breathe 方法),而实际调用的是具体的实现类 (Fish 和 Dog) 的对应方法。这正是面向对象中的多态的概念. 同样,我们可以将 Fish 对象,Dog 对象,以及 Car 对象均视为可移动物体,均装入 IMoveable 容器,然后用完全相同的方式调用 IMoveable 接口的 move 方法。看到了吗?借助 LW_OOPC,在 C 语言下我们竟然可以轻松地实现面向对象和面向接口编程!
|
||||
|
||||
下面, 再举一个稍微复杂的例子, 它的覆盖面是足够全面的, 足以一瞥面向对象编程的3个要素: 数据抽象, 继承和多态. 通过这个例子, 我们期望展现出LW_OOPC在遭遇问题本身比较复杂的情形下, 是如何从容应对的, 以加深读者对LW_OOPC的认识. (备注: 该问题来自<C++沉思录>第八章的例子, 有兴趣的读者可以对照参阅).
|
||||
下面,再举一个稍微复杂的例子,它的覆盖面是足够全面的,足以一瞥面向对象编程的 3 个要素:数据抽象,继承和多态。通过这个例子,我们期望展现出 LW_OOPC 在遭遇问题本身比较复杂的情形下,是如何从容应对的,以加深读者对 LW_OOPC 的认识. (备注:该问题来自<C++ 沉思录>第八章的例子,有兴趣的读者可以对照参阅).
|
||||
|
||||
### 问题描述
|
||||
|
||||
此程序涉及的内容是用来表示算术表达式的树. 例如, 表达式(-5) * (3 + 4)对应的树为:
|
||||
此程序涉及的内容是用来表示算术表达式的树。例如,表达式 (-5) * (3 + 4) 对应的树为:
|
||||
![math-tree](http://akagi201.qiniudn.com/math-tree.png)
|
||||
|
||||
一个表达式树包括代表常数, 一元运算符和二元运算符的节点. 这样的树结构在编译器和计算器程序中都可能用到. 我们希望能通过调用合适的函数来创建这样的树, 然后打印该树的完整括号化形式. 例如, 我们希望打印
|
||||
一个表达式树包括代表常数,一元运算符和二元运算符的节点。这样的树结构在编译器和计算器程序中都可能用到. 我们希望能通过调用合适的函数来创建这样的树,然后打印该树的完整括号化形式。例如,我们希望打印
|
||||
|
||||
```
|
||||
((-5)*(3+4))
|
||||
(((-5)*(3+4))*((-5)*(3+4)))
|
||||
```
|
||||
|
||||
作为输出. 此外, 我们不想为这些表达式的表示形式操心, 更不想关心有关它们内存分配和回收的事宜.
|
||||
这个程序所做的事情在很多需要处理复杂输入的大型程序中是很典型的, 例如编译器, 编辑器, CAD/CAM系统等. 此类程序中通常要花费很大的精力来处理类似树, 图和类似的数据结构. 这些程序的开发者永远需要面对诸如内存分配, 灵活性和效率之类的问题. 面向对象技术可以把这些问题局部化, 从而确保今后发生的一系列变化不会要求整个程序中的其他各个部分随之做相应调整.
|
||||
作为输出。此外,我们不想为这些表达式的表示形式操心,更不想关心有关它们内存分配和回收的事宜。
|
||||
这个程序所做的事情在很多需要处理复杂输入的大型程序中是很典型的,例如编译器,编辑器,CAD/CAM 系统等。此类程序中通常要花费很大的精力来处理类似树,图和类似的数据结构。这些程序的开发者永远需要面对诸如内存分配,灵活性和效率之类的问题。面向对象技术可以把这些问题局部化,从而确保今后发生的一系列变化不会要求整个程序中的其他各个部分随之做相应调整。
|
||||
|
||||
```C
|
||||
#include <stdio.h>
|
||||
@ -309,8 +310,8 @@ int main()
|
||||
|
||||
### 解决方案
|
||||
|
||||
通过考查这个树结构, 会发现这里有3种节点. 一种表示整数表达式, 包含一个整数值, 无子节点. 另外两个分别表示一元表达式和二元表达式, 包含一个操作符, 分别有一个或两个子节点. 我们希望打印各种节点, 但是具体方式需要视要打印节点的类型而定. 这就是动态绑定的用武之地了: 我们可以定义一个虚函数(print)来指明应当如何打印各种节点. 动态绑定将会负责在运行时基于打印节点的实际类型调用正确的函数.
|
||||
首先, 我们抽象出"节点"的概念, 抽象类的名字定为Expr_node. 它提供了打印的抽象接口, 所有的实际节点类型均从它派生:
|
||||
通过考查这个树结构,会发现这里有 3 种节点。一种表示整数表达式,包含一个整数值,无子节点。另外两个分别表示一元表达式和二元表达式,包含一个操作符,分别有一个或两个子节点。我们希望打印各种节点,但是具体方式需要视要打印节点的类型而定。这就是动态绑定的用武之地了: 我们可以定义一个虚函数 (print) 来指明应当如何打印各种节点。动态绑定将会负责在运行时基于打印节点的实际类型调用正确的函数。
|
||||
首先,我们抽象出"节点"的概念,抽象类的名字定为 Expr_node. 它提供了打印的抽象接口,所有的实际节点类型均从它派生:
|
||||
|
||||
```C
|
||||
ABS_CLASS(Expr_node)
|
||||
@ -319,7 +320,7 @@ ABS_CLASS(Expr_node)
|
||||
};
|
||||
```
|
||||
|
||||
具体类的情形怎样? 这些具体类型中最简单的一类是包含一个整数, 没有子节点的节点:
|
||||
具体类的情形怎样?这些具体类型中最简单的一类是包含一个整数,没有子节点的节点:
|
||||
|
||||
```C
|
||||
CLASS(Int_node)
|
||||
@ -331,13 +332,13 @@ CLASS(Int_node)
|
||||
};
|
||||
```
|
||||
|
||||
其他类型又如何呢? 每个类中都必须存储一个操作符(这倒简单, 本文中假定操作符最长不超过2个字符, 所以, 可以用长度为3的字符数组来保存), 但是如何存储子节点呢? 在运行时之前, 我们并不知道子节点的类型会是什么, 所以我们不能按值存储子节点, 必须存储指针. 这样, 一元和二元节点类如下所示:
|
||||
其他类型又如何呢?每个类中都必须存储一个操作符 (这倒简单,本文中假定操作符最长不超过 2 个字符,所以,可以用长度为 3 的字符数组来保存),但是如何存储子节点呢?在运行时之前,我们并不知道子节点的类型会是什么,所以我们不能按值存储子节点,必须存储指针。这样,一元和二元节点类如下所示:
|
||||
|
||||
```C
|
||||
CLASS(Unary_node)
|
||||
{
|
||||
EXTENDS(Expr_node);
|
||||
char op[3]; // 假设操作符最长不超过2个字符
|
||||
char op[3]; // 假设操作符最长不超过 2 个字符
|
||||
Expr_node* opnd;
|
||||
|
||||
void (*init)(Unary_node* t, const char* a, Expr_node* b);
|
||||
@ -346,7 +347,7 @@ CLASS(Unary_node)
|
||||
CLASS(Binary_node)
|
||||
{
|
||||
EXTENDS(Expr_node);
|
||||
char op[3]; // 假设操作符最长不超过2个字符
|
||||
char op[3]; // 假设操作符最长不超过 2 个字符
|
||||
Expr_node* left;
|
||||
Expr_node* right;
|
||||
|
||||
@ -355,7 +356,7 @@ Expr_node * c);
|
||||
};
|
||||
```
|
||||
|
||||
这个设计方案可以用, 不过有一个问题. 用户要处理的不是值, 而是指针, 所以必须记住分配和释放对象. 例如, 我们需要这么创建表达式树:
|
||||
这个设计方案可以用,不过有一个问题。用户要处理的不是值,而是指针,所以必须记住分配和释放对象。例如,我们需要这么创建表达式树:
|
||||
|
||||
```C
|
||||
Int_node* int_node1 = Int_node_new();
|
||||
@ -377,13 +378,13 @@ lw_oopc_delete(int_node1);
|
||||
|
||||
```
|
||||
|
||||
也就是说, 我们需要去关心每一个节点的创建和释放. 我们不仅把内存管理这类烦心事推给了用户, 而且对用户来说也没有什么方便的办法来处理这些事情. 我们得好好想想办法了.
|
||||
也就是说,我们需要去关心每一个节点的创建和释放。我们不仅把内存管理这类烦心事推给了用户,而且对用户来说也没有什么方便的办法来处理这些事情。我们得好好想想办法了。
|
||||
|
||||
这里, 提供一种解决内存管理问题的思路: **引用计数**, 这里是针对指针, 对指针的状况进行计数, 对象创建的时候, 引用计数为1, 凡是指针被赋值了, 该指针所指对象的引用计数就自增一, 每次指针要释放, 都先检查对象的引用计数, 让引用计数自减一, 如果引用计数为0, 则释放该对象.
|
||||
这里,提供一种解决内存管理问题的思路:**引用计数**,这里是针对指针,对指针的状况进行计数,对象创建的时候,引用计数为 1,凡是指针被赋值了,该指针所指对象的引用计数就自增一,每次指针要释放,都先检查对象的引用计数,让引用计数自减一,如果引用计数为 0,则释放该对象。
|
||||
|
||||
另外, 原先的设计不够高层, 用户只能直接针对节点进行操作, 没有提供操作子树的概念(这也是用户代码之所以复杂的原因之一), 我们发现, 通过提供子树的概念, 我们不但能够隐藏Expr_node继承层次, 而且, 对于每一个节点, 我们具备了操纵左子树和右子树的能力(原来只能操作左子节点和右子节点). 而这种功能增强完全是建立在面向对象的机制之上, 我们并没有引入耦合. 在非常自然和轻松的情形下, 我们获得了更好的软件组件之间协作的能力, 这正是面向对象的魅力所在.
|
||||
另外,原先的设计不够高层,用户只能直接针对节点进行操作,没有提供操作子树的概念 (这也是用户代码之所以复杂的原因之一),我们发现,通过提供子树的概念,我们不但能够隐藏 Expr_node 继承层次,而且,对于每一个节点,我们具备了操纵左子树和右子树的能力 (原来只能操作左子节点和右子节点). 而这种功能增强完全是建立在面向对象的机制之上,我们并没有引入耦合。在非常自然和轻松的情形下,我们获得了更好的软件组件之间协作的能力,这正是面向对象的魅力所在。
|
||||
|
||||
这里, 我们把子树的概念用类Expr来表示, 由于子树此时成了Expr_node具体类的成员, 同样, 左右子树在Expr_node中同样是以指针的方式保存, 所以, 对Expr也需要进行引用计数, 代码直接贴上来, 细节解说参见注释:
|
||||
这里,我们把子树的概念用类 Expr 来表示,由于子树此时成了 Expr_node 具体类的成员,同样,左右子树在 Expr_node 中同样是以指针的方式保存,所以,对 Expr 也需要进行引用计数,代码直接贴上来,细节解说参见注释:
|
||||
|
||||
```C
|
||||
// expr.h
|
||||
@ -398,26 +399,26 @@ ABS_CLASS(Expr_node)
|
||||
int use; // 引用计数
|
||||
|
||||
void (*print)(Expr_node* t); // 打印表达式节点
|
||||
void (*finalize)(Expr_node* t); // 子类通过覆写finalize方法,实现对资源清理行为的定制
|
||||
void (*finalize)(Expr_node* t); // 子类通过覆写 finalize 方法,实现对资源清理行为的定制
|
||||
};
|
||||
|
||||
// 表达式(子树的概念), 其中, init*方法族提供了构建子树的高层API. 方便用户使用
|
||||
// 表达式 (子树的概念),其中,init*方法族提供了构建子树的高层 API. 方便用户使用
|
||||
CLASS(Expr)
|
||||
{
|
||||
int use; // 引用计数
|
||||
Expr_node* p; // 子树的根节点
|
||||
|
||||
// 构建整数表达式(包含一个整数值,无子表达式)
|
||||
// 构建整数表达式 (包含一个整数值,无子表达式)
|
||||
void (*initInt)(Expr* t, int);
|
||||
|
||||
// 构建一元表达式(包含一个操作符,一个子表达式)
|
||||
// 构建一元表达式 (包含一个操作符,一个子表达式)
|
||||
void (*initUnary)(Expr* t, const char*, Expr*);
|
||||
// 构建一元表达式的重载形式(通过传入个整型值参数,构造一个子表达式为整数表达式的一元表达式)
|
||||
// 构建一元表达式的重载形式 (通过传入个整型值参数,构造一个子表达式为整数表达式的一元表达式)
|
||||
void (*initUnaryX)(Expr* t, const char*, int);
|
||||
|
||||
// 构建二元表达式(包含一个操作符,二个子表达式)
|
||||
// 构建二元表达式 (包含一个操作符,二个子表达式)
|
||||
void (*initBinary)(Expr* t, const char*, Expr*, Expr*);
|
||||
// 构建二元表达式的重载形式(通过传入个整型值参数,构造两个子表达式均为整数表达式的二元表达式)
|
||||
// 构建二元表达式的重载形式 (通过传入个整型值参数,构造两个子表达式均为整数表达式的二元表达式)
|
||||
void (*initBinaryX)(Expr* t, const char*, int, int);
|
||||
|
||||
void (*print)(Expr* t); // 打印子树
|
||||
@ -426,36 +427,36 @@ CLASS(Expr)
|
||||
// 整数表达式节点
|
||||
CLASS(Int_node)
|
||||
{
|
||||
EXTENDS(Expr_node); // 继承Expr_node
|
||||
EXTENDS(Expr_node); // 继承 Expr_node
|
||||
|
||||
int n; // 整数值
|
||||
|
||||
// 初始化整数表达式节点(传入整数值)
|
||||
// 初始化整数表达式节点 (传入整数值)
|
||||
void (*init)(Int_node* t, int k);
|
||||
};
|
||||
|
||||
// 一元表达式节点
|
||||
CLASS(Unary_node)
|
||||
{
|
||||
EXTENDS(Expr_node); // 继承Expr_node
|
||||
EXTENDS(Expr_node); // 继承 Expr_node
|
||||
|
||||
char op[3]; // 假设操作符最长不超过2个字符
|
||||
char op[3]; // 假设操作符最长不超过 2 个字符
|
||||
Expr* opnd; // 子表达式
|
||||
|
||||
// 初始化一元表达式节点(传入一个操作符和一个子表达式)
|
||||
// 初始化一元表达式节点 (传入一个操作符和一个子表达式)
|
||||
void (*init)(Unary_node* t, const char* a, Expr* b);
|
||||
};
|
||||
|
||||
// 二元表达式节点
|
||||
CLASS(Binary_node)
|
||||
{
|
||||
EXTENDS(Expr_node); // 继承Expr_node
|
||||
EXTENDS(Expr_node); // 继承 Expr_node
|
||||
|
||||
char op[3]; // 假设操作符最长不超过2个字符
|
||||
char op[3]; // 假设操作符最长不超过 2 个字符
|
||||
Expr* left; // 左子表达式
|
||||
Expr* right; // 右子表达式
|
||||
|
||||
// 初始化二元表达式节点(传入一个操作符和两个子表达式)
|
||||
// 初始化二元表达式节点 (传入一个操作符和两个子表达式)
|
||||
void (*init)(Binary_node* t, const char* a, Expr* b, Expr* c);
|
||||
};
|
||||
|
||||
@ -468,17 +469,17 @@ ABS_CTOR(Expr_node)
|
||||
cthis->use = 1; // 构造函数中,将引用计数初始化为
|
||||
END_ABS_CTOR
|
||||
|
||||
// Expr_node的析构函数(DTOR/END_DTOR用于实现析构函数语义)
|
||||
// Expr_node 的析构函数 (DTOR/END_DTOR 用于实现析构函数语义)
|
||||
DTOR(Expr_node)
|
||||
if (--cthis->use == 0) // 递减引用计数,如果计数为,释放自己
|
||||
{
|
||||
cthis->finalize(cthis); // 释放内存之前先清理资源(其他需要释放的对象)
|
||||
return lw_oopc_true; // 返回true,表示析构成功,可以释放内存
|
||||
cthis->finalize(cthis); // 释放内存之前先清理资源 (其他需要释放的对象)
|
||||
return lw_oopc_true; // 返回 true,表示析构成功,可以释放内存
|
||||
}
|
||||
return lw_oopc_false; // 返回false,表示析构失败,不能释放内存
|
||||
return lw_oopc_false; // 返回 false,表示析构失败,不能释放内存
|
||||
END_DTOR
|
||||
|
||||
// 构建整数表达式(包含一个整数值,无子表达式),n为整数值
|
||||
// 构建整数表达式 (包含一个整数值,无子表达式),n 为整数值
|
||||
void Expr_initInt(Expr* expr, int n)
|
||||
{
|
||||
Int_node* intNode = Int_node_new(lw_oopc_file_line);
|
||||
@ -489,7 +490,7 @@ void Expr_initInt(Expr* expr, int n)
|
||||
|
||||
…… // 因篇幅所限,构建一元表达式、二元表达式以及对应的重载形式的函数实现代码省略
|
||||
|
||||
// 打印表达式(子树)
|
||||
// 打印表达式 (子树)
|
||||
void Expr_print(Expr* t)
|
||||
{
|
||||
Expr_node* p = t->p;
|
||||
@ -506,7 +507,7 @@ FUNCTION_SETTING(print, Expr_print);
|
||||
cthis->use = 1; // 构造函数中,将引用计数初始化为
|
||||
END_CTOR
|
||||
|
||||
// Expr的析构函数(DTOR/END_DTOR用于实现析构函数语义)
|
||||
// Expr 的析构函数 (DTOR/END_DTOR 用于实现析构函数语义)
|
||||
DTOR(Expr)
|
||||
if (--cthis->use == 0) // 递减引用计数,如果计数为,释放自己
|
||||
{
|
||||
@ -542,7 +543,7 @@ FUNCTION_SETTING(Expr_node.print, Int_node_print);
|
||||
FUNCTION_SETTING(Expr_node.finalize, Int_node_finalize);
|
||||
END_CTOR
|
||||
|
||||
…… // 因篇幅所限,一(二)元表达式节点的初始化, 打印, 资源清理, 构造等函数的实现代码省略
|
||||
…… // 因篇幅所限,一 (二) 元表达式节点的初始化,打印,资源清理,构造等函数的实现代码省略
|
||||
|
||||
//main.c
|
||||
#include "stdio.h"
|
||||
@ -551,7 +552,7 @@ END_CTOR
|
||||
int main()
|
||||
{
|
||||
Expr* expr = Expr_new();
|
||||
…… // 创建expr1, expr2, expr3的代码
|
||||
…… // 创建 expr1, expr2, expr3 的代码
|
||||
|
||||
expr1->initUnaryX(expr1, "-", 5);
|
||||
expr2->initBinaryX(expr2, "+", 3, 4);
|
||||
@ -564,13 +565,13 @@ int main()
|
||||
printf("\n");
|
||||
|
||||
Expr_delete(expr);
|
||||
…… // 删除expr3、expr2、expr1的代码
|
||||
…… // 删除 expr3、expr2、expr1 的代码
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
程序运行效果:
|
||||
程序运行效果:
|
||||
|
||||
```bash
|
||||
liuboyf1@ipc:~/data/custom/aklw_oopc/demo/expr$ ./a.out
|
||||
@ -578,12 +579,12 @@ liuboyf1@ipc:~/data/custom/aklw_oopc/demo/expr$ ./a.out
|
||||
(((-5)*(3+4))*((-5)*(3+4)))
|
||||
```
|
||||
|
||||
怎么样? 效果还不错吧, 最重要的是, 我们的C语言代码现在已经完全是面向对象的.
|
||||
怎么样?效果还不错吧,最重要的是,我们的 C 语言代码现在已经完全是面向对象的。
|
||||
|
||||
### 方案的可扩展性如何?
|
||||
### 方案的可扩展性如何?
|
||||
|
||||
假设我们希望添加一种Ternary_node类型来表示三元操作符, 如?: (也就是if-then-else操作符), 看看, 难度有多大?
|
||||
事实上, 正是因为前面的设计是面向对象的, 要增加一种节点类型易如反掌:
|
||||
假设我们希望添加一种 Ternary_node 类型来表示三元操作符,如?: (也就是 if-then-else 操作符),看看,难度有多大?
|
||||
事实上,正是因为前面的设计是面向对象的,要增加一种节点类型易如反掌:
|
||||
|
||||
```C
|
||||
// 三元表达式节点
|
||||
@ -591,37 +592,37 @@ CLASS(Ternary_node)
|
||||
{
|
||||
EXTENDS(Expr_node);
|
||||
|
||||
char op[3]; // 假设操作符最长不超过2个字符
|
||||
char op[3]; // 假设操作符最长不超过 2 个字符
|
||||
Expr* left;
|
||||
Expr* middle;
|
||||
Expr* right;
|
||||
|
||||
// 初始化三元表达式节点(传入一个操作符和三个子表达式)
|
||||
// 初始化三元表达式节点 (传入一个操作符和三个子表达式)
|
||||
void (*init)(Ternary_node* t, const char* op, Expr* left, Expr* middle, Expr* right);
|
||||
};
|
||||
```
|
||||
|
||||
在Expr中添加创建三元表达式的方法:
|
||||
在 Expr 中添加创建三元表达式的方法:
|
||||
|
||||
```C
|
||||
// 表达式(子树的概念), 其中, init*方法族提供了构建子树的高层API, 方便用户使用
|
||||
// 表达式 (子树的概念),其中,init*方法族提供了构建子树的高层 API,方便用户使用
|
||||
CLASS(Expr)
|
||||
{
|
||||
int use; // 引用计数
|
||||
Expr_node* p; // 子树的根节点
|
||||
…… // 既有实现
|
||||
// 构建三元表达式(包含一个操作符,三个子表达式)
|
||||
// 构建三元表达式 (包含一个操作符,三个子表达式)
|
||||
void (*initTernary)(Expr* t, const char*, Expr*, Expr*, Expr*);
|
||||
// 构建三元表达式的重载形式(通过传入一个整型值参数,构造三个子表达式均为整数表达式的三元表达式)
|
||||
// 构建三元表达式的重载形式 (通过传入一个整型值参数,构造三个子表达式均为整数表达式的三元表达式)
|
||||
void (*initTernaryX)(Expr* t, const char*, int, int, int);
|
||||
…… // 既有实现
|
||||
};
|
||||
```
|
||||
|
||||
请读者参照Binary_node的现有实现, 实现出Ternary_node, 这里不再赘述. 一旦实现出Ternary_node, 我们就可以这样创建表达式树并打印:
|
||||
请读者参照 Binary_node 的现有实现,实现出 Ternary_node,这里不再赘述。一旦实现出 Ternary_node,我们就可以这样创建表达式树并打印:
|
||||
|
||||
```C
|
||||
…… // 创建expr1, expr2, expr3, expr对象(指针)
|
||||
…… // 创建 expr1, expr2, expr3, expr 对象 (指针)
|
||||
|
||||
expr1->initUnaryX(expr1, "-", 0);
|
||||
expr2->initUnaryX(expr2, "-", 5);
|
||||
@ -632,48 +633,63 @@ expr->print(expr);
|
||||
printf("\n");
|
||||
```
|
||||
|
||||
为了支持新的节点类型, 对原有代码的更动很少(仅对Expr类有增加方法), 而且只有新增操作(新增类,新增方法), 但没有修改操作(指修改原有方法), 面向对象的设计赋予了系统极大的弹性, 让程序在应对变化时, 更加从容. 在这个例子中, LW_OOPC帮助我们在C语言的世界里营造出OO的天地, 带领我们再一次领略了面向对象的风采.
|
||||
为了支持新的节点类型,对原有代码的更动很少 (仅对 Expr 类有增加方法),而且只有新增操作 (新增类,新增方法),但没有修改操作 (指修改原有方法),面向对象的设计赋予了系统极大的弹性,让程序在应对变化时,更加从容。在这个例子中,LW_OOPC 帮助我们在 C 语言的世界里营造出 OO 的天地,带领我们再一次领略了面向对象的风采。
|
||||
|
||||
### LW_OOPC最佳实践
|
||||
### LW_OOPC 最佳实践
|
||||
|
||||
说得简单一点, 要想使用好LW_OOPC这套宏, 还得首先懂面向对象, 要遵循面向对象设计的那些大原则, 比如开闭原则等. 在C语言中使用面向对象, 根据实际使用的情况, 给出如下建议:
|
||||
说得简单一点,要想使用好 LW_OOPC 这套宏,还得首先懂面向对象,要遵循面向对象设计的那些大原则,比如开闭原则等。在 C 语言中使用面向对象,根据实际使用的情况,给出如下建议:
|
||||
|
||||
1. 继承层次不宜过深, 建议最多三层(接口, 抽象类, 具体类, 参见图1和图2)
|
||||
继承层次过深, 在Java/C#/C++中均不推崇, 在C语言中实践面向对象的时候, 尤其要遵循这一点, 只有这样, 代码才能简单清爽.
|
||||
1. 继承层次不宜过深,建议最多三层 (接口,抽象类,具体类,参见图 1 和图 2)
|
||||
继承层次过深,在 Java/C#/C++ 中均不推崇,在 C 语言中实践面向对象的时候,尤其要遵循这一点,只有这样,代码才能简单清爽。
|
||||
|
||||
2. 尽量避免多重继承
|
||||
尽可能使用单线继承, 但可实现多个接口(与Java中的单根继承类似).
|
||||
尽可能使用单线继承,但可实现多个接口 (与 Java 中的单根继承类似).
|
||||
|
||||
3. 尽量避免具体类继承具体类
|
||||
具体类继承具体类, 不符合抽象的原则, 要尽量避免.
|
||||
具体类继承具体类,不符合抽象的原则,要尽量避免。
|
||||
|
||||
4. 各继承层次分别维护好自己的数据
|
||||
子类尽量不要直接访问祖先类的数据, 如果确实需要访问, 应当通过祖先类提供的函数, 以函数调用的方式间接访问.
|
||||
子类尽量不要直接访问祖先类的数据,如果确实需要访问,应当通过祖先类提供的函数,以函数调用的方式间接访问。
|
||||
|
||||
### LW_OOPC的优点
|
||||
![class-interface](http://akagi201.qiniudn.com/class-interface.png)
|
||||
|
||||
1. 轻量级.
|
||||
### LW_OOPC 的优点
|
||||
|
||||
2. 广泛的适应性, 能够适应各种平台, 各种编译器(能支持C的地方, 基本上都能支持).
|
||||
1. 轻量级。
|
||||
|
||||
3. 帮助懂OO的Java/C++程序员写出面向对象的C程序.
|
||||
2. 广泛的适应性,能够适应各种平台,各种编译器 (能支持 C 的地方,基本上都能支持).
|
||||
|
||||
4. 使用C, 也能引入OO的设计思想和方法, 在团队的C/C++分歧严重时可能非常有用.
|
||||
3. 帮助懂 OO 的 Java/C++ 程序员写出面向对象的 C 程序。
|
||||
|
||||
### LW_OOPC的缺点
|
||||
4. 使用 C,也能引入 OO 的设计思想和方法,在团队的 C/C++ 分歧严重时可能非常有用。
|
||||
|
||||
1. 无法支持重载(C语言不支持所致)
|
||||
2. 不完全的封装(无法区分私有, 保护和公有)
|
||||
LW_OOPC的INTERFACE/ABS_CLASS/CLASS三个宏展开后都是C语言的struct, 其成员全是公有的, 宏本身并无能力提供良好地封装层次的支持, 所以, 只能从编程规范和编程风格上进行引导.
|
||||
3. 不支持RTTI
|
||||
既然不支持RTTI, 那么显然也无法支持安全的向下转型(C++中的dynamic_cast的转型功能)
|
||||
### LW_OOPC 的缺点
|
||||
|
||||
1. 无法支持重载 (C 语言不支持所致)
|
||||
2. 不完全的封装 (无法区分私有,保护和公有)
|
||||
LW_OOPC 的 INTERFACE/ABS_CLASS/CLASS 三个宏展开后都是 C 语言的 struct,其成员全是公有的,宏本身并无能力提供良好地封装层次的支持,所以,只能从编程规范和编程风格上进行引导。
|
||||
3. 不支持 RTTI
|
||||
既然不支持 RTTI,那么显然也无法支持安全的向下转型 (C++ 中的 dynamic_cast 的转型功能)
|
||||
4. 不支持拷贝构造以及赋值语义
|
||||
5. 转换成接口的表述有点麻烦, 表达形式相比C++要啰嗦很多.
|
||||
6. 有学习成本, 需要用户学习并习惯这套宏.
|
||||
5. 转换成接口的表述有点麻烦,表达形式相比 C++ 要啰嗦很多。
|
||||
6. 有学习成本,需要用户学习并习惯这套宏。
|
||||
|
||||
前四条缺点, 实质上并非是LW_OOPC的缺点, 而是C相对C++而言的缺点, 在这里, 之所以也一并列上, 是希望用户不要对LW_OOPC抱太高的期望, 毕竟它也只是一套C语言的宏而已, C语言有的缺点, LW_OOPC并不能够解决.
|
||||
前四条缺点,实质上并非是 LW_OOPC 的缺点,而是 C 相对 C++ 而言的缺点,在这里,之所以也一并列上,是希望用户不要对 LW_OOPC 抱太高的期望,毕竟它也只是一套 C 语言的宏而已,C 语言有的缺点,LW_OOPC 并不能够解决。
|
||||
|
||||
### 总结
|
||||
|
||||
尽管如此,在使用 C 语言编程的时候,在某些情形下,你可能想要通过面向对象来更好的组织代码。偶尔,你也想要用用某个设计模式,此时,这套宏能够帮得上忙,使用它,有助于写出相对易于理解和维护的面向对象的代码。因篇幅所限,本文中没有介绍 LW_OOPC 的高级特性,譬如对内存泄漏检测的支持。示例的完整代码,LW_OOPC 的最新版本以及关于这套宏的更加详细的使用指南,请读者访问<https://github.com/Akagi201/aklw_oopc>获取。最后,期望有兴趣的读者,发挥聪明才智,提出改进建议,让 LW_OOPC 变得越来越好!
|
||||
|
||||
### 幕后花絮
|
||||
|
||||
在完善 LW_OOPC 宏的过程中,我也认真研究了参考资料中列出的材料。最初 V1.0 版本有将近 25 个宏,后来,收到一些同事的反馈,认为少量宏晦涩难记,而且不易理解,经过认真考虑,删除掉 5 个宏,形成现在的 20 个宏。相比其他用 C 实现面向对象的方案,LW_OOPC 简洁优雅,每个宏的命名都经过仔细地推敲,尽可能做到简单易懂,便于记忆。
|
||||
|
||||
但愿 LW_OOPC 真的能够帮助到奋斗在一线的 C 程序员们。
|
||||
|
||||
### 参考资料
|
||||
|
||||
1. 高焕堂,\<UML+OOPC 嵌入式 C 语言开发精讲\>,电子工业出版社,2008 年 9 月。
|
||||
2. \<Object-oriented Programming with ANSI-C\>,下载地址: <http://www.planetpdf.com/codecuts/pdfs/ooc.pdf> .
|
||||
3. C 实现面向对象的方法,<http://www.eventhelix.com/RealtimeMantra/basics/object_oriented_programming_in_c.htm> .
|
||||
|
||||
|
||||
|
16
lw_oopc.h
16
lw_oopc.h
@ -1,6 +1,6 @@
|
||||
// Copyright (C) 2008,2009,2010 by Tom Kao & MISOO Team & Yonghua Jin. All rights reserved.
|
||||
// Copyright (C) 2008,2009,2010 by Tom Kao & MISOO Team & Yonghua Jin. All rights reserved.
|
||||
// Released under the terms of the GNU Library or Lesser General Public License (LGPL).
|
||||
// Author: Tom Kao(中文名:高焕堂),MISOO团队,Yonghua Jin(中文名:金永华)
|
||||
// Author: Tom Kao(中文名:高焕堂),MISOO 团队,Yonghua Jin(中文名:金永华)
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
@ -29,14 +29,14 @@
|
||||
|
||||
#include <malloc.h>
|
||||
|
||||
// 配置宏(两种配置选其一):
|
||||
#define LW_OOPC_USE_STDDEF_OFFSETOF // 表示使用C标准定义的offsetof
|
||||
// #define LW_OOPC_USE_USER_DEFINED_OFFSETOF // 表示使用用户自定义的lw_oopc_offsetof宏
|
||||
// 配置宏 (两种配置选其一):
|
||||
#define LW_OOPC_USE_STDDEF_OFFSETOF // 表示使用 C 标准定义的 offsetof
|
||||
// #define LW_OOPC_USE_USER_DEFINED_OFFSETOF // 表示使用用户自定义的 lw_oopc_offsetof 宏
|
||||
|
||||
// 是否支持内存泄露检测,缺省不支持
|
||||
// #define LW_OOPC_SUPPORT_MEMORY_LEAK_DETECTOR
|
||||
|
||||
// 是否支持调试信息打印(内存分配和释放的详细信息),缺省关闭打印
|
||||
// 是否支持调试信息打印 (内存分配和释放的详细信息),缺省关闭打印
|
||||
// #define LW_OOPC_PRINT_DEBUG_INFO
|
||||
|
||||
#ifdef LW_OOPC_USE_STDDEF_OFFSETOF
|
||||
@ -151,7 +151,7 @@ void type##_ctor(type* cthis) {
|
||||
|
||||
#define EXTENDS(type) struct type type
|
||||
|
||||
#define SUPER_PTR(cthis, father) ((father*)(&(cthis->father)))
|
||||
#define SUPER_PTR(cthis, father) ((father*)(&((cthis)->father)))
|
||||
|
||||
#define SUPER_PTR_2(cthis, father, grandfather) \
|
||||
SUPER_PTR(SUPER_PTR(cthis, father), grandfather)
|
||||
@ -171,6 +171,6 @@ void type##_ctor(type* cthis) {
|
||||
#define SUB_PTR_3(selfptr, self, child, grandchild, greatgrandchild) \
|
||||
SUB_PTR(SUB_PTR_2(selfptr, self, child, grandchild), grandchild, greatgrandchild)
|
||||
|
||||
#define INHERIT_FROM(father, cthis, field) cthis->father.field
|
||||
#define INHERIT_FROM(father, cthis, field) (cthis)->father.field
|
||||
|
||||
#endif
|
||||
|
Loading…
x
Reference in New Issue
Block a user