C 的假装 OOP 写法

之前微博上说 C 的一个好处就是没有什么是一个 void* 解决不了的,然后因为自己用 C 写一个小程序,遇到了一个问题,就是因为 C 是强类型,但是如果需要写一个相对通用的数据结构。这个稍微有点麻烦。

比如说,hashmap,我们常用的都是 <string, string> 的一个 map,于是,相对来说的话,hash 函数比较容易。但是,如果是需要实现 Java 那样的通用数据结构呢,是不是需要对每个特定的数据对象写一个?

所以这边就扯到了这么个比较好玩的技巧。

先看下效果:

  • main.c
#include "value.h"
#include "value_a.h"
#include "value_b.h"

int main(void)
{
	struct value v1;
	value_a_instance(&v1);
	v1.set(&v1, (void*)"hello");
	v1.display(&v1);
	printf("%lld", v1.hash(&v1));

	struct value v2;
	value_b_instance(&v2);
	double a = 2.0f;
	v2.set(&v2, (void*)&a);
	v2.display(&v2);
	printf("%lld", v2.hash(&v2));
	printf("%lf", *(double*)v2.data);

	return 0;
} 
  • value.h
#ifndef VALUE_H
#define VALUE_H 1

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

struct value {
	void (*display)(struct value*);
	uint64_t (*hash)(struct value*);
	void (*set)(struct value*, void*);
	void (*instance)(struct value*);

	void *data;
};

#endif /* ifndef VALUE_H */ 

我们可以看到,通过对 value 调用 value_x_instance 函数可以拿到属于新的类型的对象指针。

这个新的类型主要包含了几个函数指针,主要用途就是通过注入的不同指针,对其进行 value 操作。

  • value_a.h
#ifndef VALUE_A_H
#define VALUE_A_H 1

#include "value.h"

void value_a_display(struct value*);
uint64_t value_a_hash(struct value*);
void value_a_set(struct value*, void*);
void value_a_instance(struct value*);

#endif /* ifndef VALUE_A_H */ 
  • value_a.c
#include "value_a.h"

void value_a_display(struct value* v)
{
	puts((char*)v->data);
}

uint64_t value_a_hash(struct value* v)
{
	return 2;
}

void value_a_set(struct value* v, void* data)
{
	assert((char*)data);
	v->data = data;
}

void value_a_instance(struct value* v)
{
	v->data = (void*)malloc(sizeof(char));
	v->display = value_a_display;
	v->hash = value_a_hash;
	v->set = value_a_set;
} 

这两个文件就是对此函数的诠释,只不过这边比较简单,所以很多的细节都是简化的。

这样一来,如果我们需要进行存储数据结构的编写,就觉得很舒服了。比如 list 的定义就可以如下:

struct list {
    list *pre;
    list *next;

    struct value *node;
}; 

然后通过 insert 的参数,对其 value 进行注入。不过有个不好的地方,这边在调用内部方法的时候,需要指定 self 参数。这个实属无奈之举。本来还想通过栈帧找到父结构体,但这个代码就太依赖于系统,编译器。所以只能这样。

不过,这就刚刚好了。其实还有一种方法,通通 void* 然后再加一个类型模版,不过这就是要理一下 C++ 的模版思路了。

不得不说,返璞归真,还是喜欢上 pure C 的代码。相较于高层的太多限制,C 上可以通过指针玩的很舒服。最近在进行 linux 内核的程序编写,深深地感觉到,手写 makefile 的好处。可以和 shell 完美的兼容。同时可以对依赖进行文件级别的控制。

唯一不好的一点就是 test 不是特别好写,只能自己进行 test.c + assert 的方式。之后再看看 C 的单元测试工具。开始从空格党变成 tab 党了。空格用来对其,tab 用来缩进的策略确实可以。而且可以很方便的进行 2,4,8,12 的缩进调整。

是在不知道该起什么名,就这样吧。

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.