C³ Cubism C 语言扩展






3.05/5 (4投票s)
使程序对人类更加可理解和熟悉
引言
这个项目似乎是在用一种更有意义的方式来实现 C 语言的面向对象编程原则。复杂的部分被转移到了自动化领域。这样,程序文本就可以根据程序员的描述自动生成。我知道 C 语言已经发展出了 C++ 或 C# 等许多语言。我决定将这个新改进命名为 C³,即 C 的立方。
背景
有一天,一群年轻的艺术家问一位经验丰富的工匠——“如何画画?”。他回答说,你需要仔细分析你想画什么,把它分解成最简单的几何形状,然后再重新组合起来。当然,他指的是完全不同的东西。但艺术家们以自己的方式理解了他的回答。立体主义是 20 世纪初的艺术运动。
我邀请您潜入编程领域令人惊叹的立方世界。您将接触到令人惊叹的事物。我向您保证,这将是一次堪比参观画展的体验。
Using the Code
该架构由可供人类使用的独立层组成。
第一层
第一层是一个三维数据结构系统,用于处理它们的函数,以及最新的数据传输路由。
结构体用于存储一起使用的最少数据。传输路由是负责在严格定义的、经过特定环境的数据传输的特殊模块。
将结构体指针传递给函数,然后在函数中即可访问所有数据。当我觉得缺少某些数据时,就需要将它们添加到结构体中。否则,如果数据未被使用,则有必要考虑将其移至另一个结构体。
我将用一个简单的 `string` 的 `chars` 模块的示例来解释结构体和函数的使用。
struct chars
{
char *buf;
size_t size;
union
{
unsigned int flags;
struct
{
unsigned int is_init:1;
unsigned int is_alloc:1;
};
};
};
int chars_init(struct chars *p, size_t size)
{
int is_set = 0;
if (p)
{
p->buf = malloc(size * sizeof(char));
if (p->buf)
{
p->size = size;
p->is_init = 1;
p->is_alloc = 1;
is_set = 1;
}
}
return is_set;
}
我使用了匿名结构体和联合体,因此最好使用 C89 编译器标志,或者如果您想使用其他标准,则需要为结构体添加名称。
函数名以结构体名开头,以便清楚该函数是为哪个结构体准备的。返回值通常在成功时为非零,反之为零。可以在条件中进行检查。
chars *p = chars_create(1);
if (chars_init(p, 1 KB))
{
printf("Success");
}
函数 `chars_create(size_t count)` 创建一个对象或对象数组,并返回分配内存中第一个对象的指针。函数 `chars_init(T *p)` 可以接受额外的参数来初始化结构体的成员。在这种情况下,这就是缓冲区的大小。
这里 `T` 是结构体的名称。例如,`chars_init(T *p)` 将被预处理器替换为 `chars_init(chars *p)`。
还有一个函数 `chars_free(T *p)` 用于释放资源。我懒得每次都调用这个函数。在某些情况下,此函数会自动调用。
int chars_free(struct chars *p)
{
if (p)
{
if (p->buf && p->is_alloc)
{
free(p->buf);
p->buf = NULL;
p->is_alloc = 0;
}
}
return 1;
}
我尝试创建最简单的 `struct`,每个文件一个 `struct` 和一组函数。当我制作了几个这样的文件后,我不得不在每个文件中重复 `create`、`init` 和 `free` 函数。然后我想,我如何才能自动创建这些函数呢?
第二层
第二层是一个最简单的模块系统——立方体,它积极利用 C 预处理器。
这是按照第二层实现的 `chars` 模块。
/* chars.h */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "common_def.h"
#define MODULE_NAME chars
#define MODULE_STRUCT chars_struct.h
#define MODULE_INIT chars_init.h
#define MODULE_INIT_VAR chars_init_var.h
#define MODULE_FREE chars_free.h
#include "module.h"
/* chars_struct.h */
VAR_POINTER(char,buf)
VAR(size_t,size)
VAR(size_t,rpos)
VAR(size_t,wpos)
VAR(float,factor)
FLAGS(
FLAG(init)
FLAG(alloc)
)
/* chars_init.h */
if (!size)
{
size = CHARS_DEFAULT_BUFFER_SIZE;
}
p->buf = malloc(size * sizeof(char));
if (p->buf)
{
p->size = size;
p->is_init = 1;
p->is_alloc = 1;
}
else
{
p->is_alloc = 0;
}
p->factor = CHARS_DEFAULT_GROWTH_FACTOR;
/* chars_init_var.h */
PARAMETER(size_t,size)
/* chars_free.h */
if (p->buf)
{
free(p->buf);
p->buf = NULL;
}
模块的不同部分包含在单独的文件中。有时这可能很不方便。所以我写了一个程序来为我制作这些文件。现在我不用处理那么多文件了。而且,只需要制作一个类似 `*.mod.h` 的文件,然后运行 `mod_to_h` 程序就可以生成像 `*.h` 这样的头文件。
`mod_to_h` 程序的参数是 `.mod.h` 文件的名称和 `destination` 文件夹。
mod_to_h file.mod.h include
`mod_to_h` 程序读取 `file.mod.h`,并在 `include` 文件夹中创建 `file.h` 和 `*others*` 文件。
第三层
第三层是一个程序——分析器,用于根据程序员的描述创建模块。
这是第三层 `chars` 模块在 `chars.mod.h` 文件中的实现。
#module chars
#struct
char *buf;
size_t size;
init;
alloc;
#init (size_t size)
if (!size)
{
size = CHARS_DEFAULT_BUFFER_SIZE;
}
p->buf = malloc(size * sizeof(char));
if (p->buf)
{
p->size = size;
p->is_init = 1;
p->is_alloc = 1;
}
else
{
p->is_alloc = 0;
}
#free
if (p->buf)
{
free(p->buf);
p->buf = NULL;
}
#include "module.h"
这里是预处理器的定义关键字 `#module`、`#struct`、`#init`、`#free`。这些元素是可选的,它们只是为构造函数添加必要的行。定义在 `module.h` 文件中。`module.txt` 文件包含一个新模块的模板文本。
在编译过程中,会调用 `mod_to_h` 程序,它会根据给定的模块描述,在扩展名为 `.h` 的头文件中创建必要的 C 语言结构。
要查找所有 `.mod.h` 文件并将其转换为 `include` 文件夹中的头文件,您可以使用以下控制台命令。
适用于 Windows 用户
FOR /R %i IN (*.mod.h) DO mod_to_h %i include
在脚本、Visual Studio 预构建事件中使用时,需要将百分号加倍。
FOR /R %%i IN (*.mod.h) DO mod_to_h %%i include
警告!`mod_to_h` 程序会覆盖 `destination` 文件夹中的头文件,因此请谨慎使用此程序。
Linux 用户可以使用 `make` 来编译他们的测试程序,因为 `Makefile` 包含自动生成头文件的行。
可以使用各种修饰符进行自动操作。这些修饰符是:`create`、`init`、`free`。修饰符可以指定在变量类型之前。
#include "chars.h"
#module chars_test
#struct
create chars *s1;
init chars *s2;
free chars *s3;
chars *s4;
这里,变量 `s1` 被创建,变量 `s2` 被创建并用默认值初始化,变量 `s3` 将在调用 `free()` 函数时被释放,`s1` 和 `s2` 也是如此。而变量 `s4` 只是一个指向 `chars` 类型的指针,我应该手动调用构造函数。
`function.txt` 文件包含一个新函数的模板文本。
FUNCTION_INLINE int FUNC(name)(T *p)
{
int is_set = 0;
if (p)
{
/* Write code here */
}
return is_set;
}
在这里,我将用函数名替换 `name`。然后完整的函数名将是 `module_name _ function_name`。例如,`chars_read_pchar` 是一个 `chars` 模块和函数名 `read_pchar`。这里,`pchar` 是参数的类型,它可以是文件等。在模块内部,我可以使用 `FUNC(read_pchar)` 调用函数,而在程序的其他部分则使用 `chars_read_pchar`。
最好将不同类型参数的函数放在单独的文件中。然后使用 `include` 连接。这使我能够创建用于处理特定数据类型的通用函数,并将它们连接到不同的模块。
例如
#include STR(T_NAME(utils.h)) /* chars_utils.h */
#include STR(T_NAME(char.h)) /* chars_char.h */
#include STR(T_NAME(pchar.h)) /* chars_pchar.h */
#include STR(T_NAME(mark.h)) /* chars_mark.h */
将用于处理 `pchar`、`char` 和实用工具的函数连接到 `chars` 模块。
当然,我可以将这些函数连接到另一个模块,例如 `file`,只要它在结构体中具有相应的成员。
例如,我将创建一个用于处理 `chars` 类型 `string` 的程序。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* Include chars module */
#include "chars.h"
int main(int argc, const char *argv[])
{
/* Define variable s with chars type */
chars *s = NULL;
/* Create chars */
s = chars_create(1);
/* Initialize chars */
if (chars_init(s, 10 KB))
{
/* Copy string "Hello" to s */
chars_read_pchar(s, "Hello", 0, CHARS_FLAG_RESET);
/* Copy string ", World !" to s next to "Hello" */
chars_read_pchar(s, ", World !", 0, 0);
chars_print(s, CHARS_FLAG_PRINT_NEWLINE);
chars_print_info(s, CHARS_FLAG_PRINT_NEWLINE);
}
return 0;
}
`chars_read_pchar` 函数将字符串 `Hello` 读取到变量 `s`。
第四层
当然,使用缩写表示法会更方便,例如
s = "Hello"
或者类似
s << "Hello"
甚至
s = "Hello" + ", World !"
我制定了规则,使得 `<<` 运算符相对于左侧变量表示 *读取*,而 `>>` 运算符表示写入。
那么 `chars_read_pchar` 函数应该从 `pchar` *读取* 并复制到 `chars`。
我决定颠倒“写入”和“读取”函数的含义,以便更贴近自然语言。这篇文章中已经修正了这一点。
编译
适用于 Windows 用户
您可以使用 Visual Studio C/C++ 编译器或 Digital Mars C/C++ 编译器 (DMC)。
在 `mod_to_h` 文件夹中有一个用于 Visual Studio Express 2005 的项目,用于构建 `mod_to_h` 程序。
要使用 Digital Mars C/C++ 编译器,您可以运行控制台命令
dmc -Iinclude -Icommon -Imodule -Ichars -Iparse -Iutopia test\mod_to_h.c
我在 Windows XP 上测试了这个命令。
Linux 用户可以使用命令
make test/mod_to_h.c
在这种情况下,`Makefile` 中的行应该被注释掉。
#include $(OBJECTS_H)
编译 `mod_to_h` 后,可以取消注释此行。
include $(OBJECTS_H)
建议将 `destination` 文件夹移至
HEADERS_DIR=$(HOME)/tmp
内存 `/tmpfs`,以便在编译过程中频繁覆盖文件。
已知限制
目前不支持使用双指针类型、地址或数组作为结构体的成员,或作为构造函数的参数。我只是不使用它们,也许将来我会添加它们。
C89 不允许空结构体,因此在结构体中没有数据,只有没有模块结构体类型参数 `(T *p)` 的库函数集合的模块中,不推荐使用 `#struct`,尽管有些编译器允许空结构体,但这种解决方案将不具备跨平台性。仅在结构体中有数据时使用 `#struct`。
待办事项
我将在第四层中执行此操作,即行处理器,它使得对 C 语言的添加成为可能,从而更容易处理各种类型的数据,例如 `string`。
第五层是将人类自然语言描述转换为 C。
我将在未来的文章中这样做。
关注点
我花了多年时间来创建这项技术。结果,我得出了一个类似于 OOP 的技术。但仍有显著差异。它在这里完全编译,因此保证了程序的高速运行。
此外,程序的一部分是自动创建的,这简化了程序员的任务。我希望使编程更容易。
历史
- 2023 年 7 月 31 日:初始版本
已完成三层技术的描述。
- 2023 年 9 月 19 日:更新源代码和文章
添加编译说明。
为 Visual Studio 的源代码添加项目文件。
修复源代码中的错误:空结构体,跨平台支持。