闲得无聊,挑战一下c的极限

目录

挨打

开扯

引言

开搞

正题

提升

结尾


挨打

  咳咳,大家不要讲话啊,先听我说,我是说挑战一下我的极限,标题纯属忽略您进来的。

先别着急喷,来都来了,看看再走呗,有好东西哦!

开扯

  我们都知道,c语言是一门底层语言,拥有着赫赫荣光,然而它的另一位兄弟c艹却广受诟病,更有linus事件引得各方激争。啊,扯远了,我今天不是来说这事的哈,c语言,简洁,以及出色的底层能力使得其广受好评,然而c++就得提它那令人脑袋打结的泛型和stl容器。

  传说中c可以实现上层的面向对象的语言,然而小渣渣们也没见过啊,就让大佬们争去吧。抱着这个心态的童鞋们看好了,在下今天就给各位表演一个节目------使用c语言实现c++的泛型,并实现一个简易vector向量容器!

引言

    写过面向对象的童鞋回过来再写c的都知道,这c啊,好归好,但限制也多,最简单的一个例子

    cl_int errNum;
    cl_uint numPlatforms;
    cl_platform_id firstPlatformId;
    cl_context context = NULL;
    errNum = clGetPlatformIDs(1, &firstPlatformId, &numPlatforms);
    if (errNum != CL_SUCCESS || numPlatforms <= 0)
    {
        printf("Failed to find any OpenCL platforms.");
        return NULL;
    }
    errNum = clGetDeviceIDs(firstPlatformId, CL_DEVICE_TYPE_GPU, 1,
        device, NULL);
    if (errNum != CL_SUCCESS)
    {
        printf("There is no GPU, trying CPU...");
        errNum = clGetDeviceIDs(firstPlatformId,
            CL_DEVICE_TYPE_CPU, 1, device, NULL);
    }

这是一段使用opencl接口的代码,很容易发现,c接口获取信息很麻烦,总是需要申请内存,然后用缓冲区接收信息。每次我在写的时候就会想,你就不能给我一个数组么。。。啰嗦得要死,这就是没有向量容器的痛苦吗?(大佬们轻喷,引题,引题)。

好了,它来了,还记得c传说中的宏吗,linux内核源码里面最难的宏,击退了多少想研究内核源码的梦。还记得传说中的侵入式链表吗?哈哈哈,我想说其实我也怕。

不过今天,我们要用宏实现一个数组模板,没错,就是哪种支持任何数据类型的数组模板,有了这个东西,你就可以像java一样带着长度到处跑了,传参不要你指定长度,也不会出现那种奇奇怪怪的scanf_s(),strcpy_s()的接口。

开搞

数组么,不就是一个带着长度到处跑的结构嘛,这有什么稀奇的,但是,每单开一个项目,重复写一个数组会不会恶心你,写了int的,明天要用float,后天要用struct,哎,我为什么不是写c++呢,我一个template秒杀!行,今天咱就用c写一个template。

原理:template不就是根据类型展开模板嘛,既然c不展开,我自己展开不行么,所以关键点就是解决两个问题:函数重命名和展开。

先解决第一个问题,重命名,怎么搞勒,c++怎么搞?不就是往函数名字后门加字符串嘛,把Arrary改成Araray_int不就可以了嘛。好了,问题来了,怎么让编译器自己给我加一个尾巴?

端端端的一阵乱翻,思考,哎,还记得这个东西吗?

#define add(p1,p2) "#p1 is not #p2"

没见过的可以先去查了,比较不常见。

那么这个可以实现吗?显然不行,替换要单串识别啊,有空格才能用,比如

#define add(p1,p2)  #p1#p2 可以不?

可以去试一下哈,应该是不行的。

还有没有办法,当然有,一个不行,那就加俩##

//函数命名重定义解决
#define RENAME(name,param) Template_##name##_##param

##可以连接字串,这个操作就是把指定两个参数替换成Template_XXX_XXX字串了。那为什么可以解决重命名呢?如果name是Array,param是int,如果我使用这个去替换类型名,是不就得到了Array_int了,如果是float,那就是Array_float,是不是很神奇?比如

struct RENAME(ARRAY,type){ };

第一个问题解决了,下面解决第二个问题,怎么展开?

请看vcr

#define add(type) \
++type; 

注意这个\,这个很重要,有这个代码才能读,不然代码要直通天际了。

简单组合一下

#define Template_Array(type) \
struct RENAME(Array,type){ };

哎,好像可以替换是吧,如果type=int,那么就展开成struct Template_Array_int{},嘿嘿,编译器这回不会认错了吧,尾巴都不一样了。

正题

好,来实现。

//函数命名重定义解决
#define RENAME(name,param) Template_##name##_##param

//泛型数组
#define Template_Array(type) \
struct RENAME(Array,type){ \
const size_t length;\
type* const head;\
};            

为啥安全,长度是常量,只要我不捉死改它类型,它就很安全。还有一个是把指针定义成常量指针(名字不一定对啊,大家知道就行),嘻嘻,数组内容你随便改,指针指向你不准动,你敢动它就报错,我都提醒你了还要动?泄露了那是你命有此结!

来一个例子试试哈

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#pragma warning( disable : 4996 )

typedef struct A {
	char* name;
}A;
//手动展开
Template_Array(A)
typedef struct Template_Array_A A_Array;
//正常情况下,如果指针域数组成员存在堆资源,那么释放时需要指定数组长度以供遍历释放 
void free_function(A_Array* array) {
	int i = 0;
	for (i = 0; i < array->length; i++) {
		free(array->head[i].name);
	}
	free(array->head);
}
int main() {
	char* p = NULL;
	int i = 0;
	A* A_ptr = (A*)malloc(sizeof(A) * 3);
	for (i = 0; i < 3; i++) {
		p = (char*)malloc(5);
		strcpy(p, "abc");
		p[3] = i + '0';
		p[4] = '\0';
		A_ptr[i].name = p;
	}
	A_Array array = { 3,A_ptr };
	for (i = 0; i < 3; i++) {
		printf("%d: %s\n", i, array.head[i].name);
	}
	free_function(&array);
	return 0;
}

跑一跑

哦,没有问题!

为什么要用这个例子呢?主要还是有代表性,像这种需要遍历来释放元素资源的处理函数,很容易造成内存泄露。如果在开始分配时就记得先写释放还好,如果只是记着,代码写长了,申请释放组写多了,哎,我是哪里还申请内存来着?当然,还有一个优点是写释放或者子处理过程方便多了,再也没必要写这种scanf_s()这种啰嗦东西。子过程也安心,这个长度很放心,没作死就不会错,安全检查这个环节就可以省下来了。

提升

还记得开题前说的向量容器吗?既然数组可以实现,那么向量肯定没问题,说干就干,开整!

//泛型类声明,以供成员函数引用
#define Vector_statement(type) \
struct RENAME(Vector,type);


//成员函数声明,以供类定义引用
#define Vector_function_statement(type) \
typedef void ( *RENAME(void_param, type) )(struct RENAME(Vector,type)* ); \
typedef void ( *RENAME(type_param_function,type) )(struct RENAME(Vector,type)* ,const type *); \
typedef void ( *RENAME(Move_function,type) )(struct RENAME(Vector,type)* ,struct RENAME(Vector,type)*); \
typedef void ( *RENAME(Copy_function,type) )(struct RENAME(Vector,type)* ,const struct RENAME(Vector,type)*);

注意,面向对象思想就是在结构体力面加几个成员函数的指针,不过c语言不支持静态成员,也就是说实例得多占几个指针空间了。这里声明的意义是解决嵌套引用问题,第二部分定义函数指针以供结构体定义成员。

向量声明

//泛型类定义
//泛型向量
#define Vector_define(type) \
        Vector_statement(type) \
        Vector_function_statement(type) \
     struct RENAME(Vector,type) {\
	    size_t length;\
        size_t capacity;\
        type* value;\
        RENAME(void_param,type) destructor; \
        RENAME(type_param_function,type) push_back; \
        RENAME(Move_function,type) move; \
        RENAME(Copy_function,type) copy; \
	};

可以看见我们简单提供了复制函数和移动函数,这会使得相互赋值简单许多,同时给了一个添加函数,这使得我们的向量可以扩展。至于【】运算c里面不支持,但是struct是public,利用指针就可以直接访问了。注意:我们不提供构造函数,原因是我们需要给实例的成员函数赋值,那么构造函数就单独提出来,也就是说除了构造函数,其他的就可以像类一样直接调用了,利用代码提示,这效率就嘎嘎上来了。注意到我们在定义向量前调用了声明宏,这使得在向量声明前相关函数声明以及展开,不会出现未定义符号问题。

成员函数实现

//泛型函数实现
//构造函数实现
//析构函数实现
//插入函数实现
//移动函数实现
//复制函数实现
#define Vector_function_implement(type) \
void RENAME(destructor_impl,type)(struct RENAME(Vector,type)* pthis){ \
     if(pthis->value!=NULL ) \
         free(pthis->value); \
     pthis->value=NULL; \
} \
void RENAME(push_back_impl,type)(struct RENAME(Vector,type)* pthis,const type* value){\
    if(pthis->length<pthis->capacity){ \
       pthis->value[pthis->length]=*value;\
    }\
    else{ \
        size_t i = 1; \
        while (i<=pthis->capacity){ \
            i*=2; \
        } \
        type* newh=(type*)malloc(sizeof(type)*i); \
        memcpy(newh,pthis->value,sizeof(type) * (pthis->length)); \
        free(pthis->value);  \
        pthis->value = newh; \
        pthis->value[pthis->length]=*value; \
        pthis->capacity=i; \
    } \
    ++pthis->length; \
}; \
void RENAME(move_impl,type)(struct RENAME(Vector,type)* pthis,struct RENAME(Vector,type)* src) { \
        if (pthis->value != NULL) {   \
            free(pthis->value); \
        }\
        pthis->length=src->length; \
        pthis->capacity=src->capacity; \
        pthis->value = src->value; \
        src->value = NULL; \
        src->length=0;  \
        src->capacity=0;  \
};  \
void RENAME(copy_impl,type)(struct RENAME(Vector,type)* pthis,const struct RENAME(Vector,type)* src) { \
    if (pthis->value != NULL) {   \
         free(pthis->value); \
     }\
    pthis->value=(type*)malloc(src->capacity);\
    memcpy(pthis->value, src->value, sizeof(type) *src->length); \
    pthis->length=src->length; \
    pthis->capacity=src->capacity; \
}; \
void RENAME(constructor_impl,type)(struct RENAME(Vector,type)* pthis) {\
        pthis->length = 0;\
        pthis->capacity = 0; \
        pthis->value = NULL; \
        pthis->destructor = RENAME(destructor_impl,type); \
        pthis->push_back = RENAME(push_back_impl,type); \
        pthis->move = RENAME(move_impl,type); \
        pthis->copy = RENAME(copy_impl,type); \
} 

利用 \ ,我们可以把实现定义成模板,这样就可以根据指定类型进行展开,得到相应版本的成员函数,同时利用重命名宏,我们还解决了多个用例存在时命名冲突问题。

最后,我们利用

#define Template_Vector(type)  \
   Vector_define(type) \
   Vector_function_implement(type)\

来定义展开顺序,这样就可以像之前一样手动展开了,统一风格又简洁!

来跑一个例子看看

Template_Vector(int)
typedef struct Template_Vector_int Int_Vector;

#include<stdio.h>


int main(){
	Int_Vector vector,vector2;
	Template_constructor_impl_int(&vector);
	Template_constructor_impl_int(&vector2);
	int i = 5;
	vector.push_back(&vector, &i);
	++i;
	vector.push_back(&vector, &i);
	printf("v1[0]: %d\nv1[1]: %d\n",vector.value[0],vector.value[1]);

	vector2.move(&vector2, &vector);
	printf("v1[0]: %d\nv1[1]: %d\n", vector2.value[0], vector2.value[1]);

	vector.destructor(&vector);
	vector2.destructor(&vector);
	return 0;
}

结果:

完美运行!(我私下改的时候可狼狈了,哈哈)

结尾

这是全部头文件代码

#pragma once
#include<stdlib.h>
#include<string.h>

//函数命名重定义解决
#define RENAME(name,param) Template_##name##_##param

//泛型数组
#define Template_Array(type) \
struct RENAME(Array,type){ \
const size_t length;\
type* const head;\
};            //不能带;以供typedef定义展开


//泛型类声明,以供成员函数引用
#define Vector_statement(type) \
struct RENAME(Vector,type);


//成员函数声明,以供类定义引用
#define Vector_function_statement(type) \
typedef void ( *RENAME(void_param, type) )(struct RENAME(Vector,type)* ); \
typedef void ( *RENAME(type_param_function,type) )(struct RENAME(Vector,type)* ,const type *); \
typedef void ( *RENAME(Move_function,type) )(struct RENAME(Vector,type)* ,struct RENAME(Vector,type)*); \
typedef void ( *RENAME(Copy_function,type) )(struct RENAME(Vector,type)* ,const struct RENAME(Vector,type)*);


//泛型类定义
//泛型向量
#define Vector_define(type) \
        Vector_statement(type) \
        Vector_function_statement(type) \
     struct RENAME(Vector,type) {\
	    size_t length;\
        size_t capacity;\
        type* value;\
        RENAME(void_param,type) destructor; \
        RENAME(type_param_function,type) push_back; \
        RENAME(Move_function,type) move; \
        RENAME(Copy_function,type) copy; \
	};


//泛型函数实现
//构造函数实现
//析构函数实现
//插入函数实现
//移动函数实现
//复制函数实现
#define Vector_function_implement(type) \
void RENAME(destructor_impl,type)(struct RENAME(Vector,type)* pthis){ \
     if(pthis->value!=NULL ) \
         free(pthis->value); \
     pthis->value=NULL; \
} \
void RENAME(push_back_impl,type)(struct RENAME(Vector,type)* pthis,const type* value){\
    if(pthis->length<pthis->capacity){ \
       pthis->value[pthis->length]=*value;\
    }\
    else{ \
        size_t i = 1; \
        while (i<=pthis->capacity){ \
            i*=2; \
        } \
        type* newh=(type*)malloc(sizeof(type)*i); \
        memcpy(newh,pthis->value,sizeof(type) * (pthis->length)); \
        free(pthis->value);  \
        pthis->value = newh; \
        pthis->value[pthis->length]=*value; \
        pthis->capacity=i; \
    } \
    ++pthis->length; \
}; \
void RENAME(move_impl,type)(struct RENAME(Vector,type)* pthis,struct RENAME(Vector,type)* src) { \
        if (pthis->value != NULL) {   \
            free(pthis->value); \
        }\
        pthis->length=src->length; \
        pthis->capacity=src->capacity; \
        pthis->value = src->value; \
        src->value = NULL; \
        src->length=0;  \
        src->capacity=0;  \
};  \
void RENAME(copy_impl,type)(struct RENAME(Vector,type)* pthis,const struct RENAME(Vector,type)* src) { \
    if (pthis->value != NULL) {   \
         free(pthis->value); \
     }\
    pthis->value=(type*)malloc(src->capacity);\
    memcpy(pthis->value, src->value, sizeof(type) *src->length); \
    pthis->length=src->length; \
    pthis->capacity=src->capacity; \
}; \
void RENAME(constructor_impl,type)(struct RENAME(Vector,type)* pthis) {\
        pthis->length = 0;\
        pthis->capacity = 0; \
        pthis->value = NULL; \
        pthis->destructor = RENAME(destructor_impl,type); \
        pthis->push_back = RENAME(push_back_impl,type); \
        pthis->move = RENAME(move_impl,type); \
        pthis->copy = RENAME(copy_impl,type); \
} 

#define Template_Vector(type)  \
   Vector_define(type) \
   Vector_function_implement(type)
   

更新:

   这样的方式使得c语言也能使用模板来缩减工作量,参考stl实现,我们还可以实现set,map,list等相应的容器。例如可以这样定义键值对

//泛型键值对
#define Template_Pair(Key,Value) \
   struct RENAME(Pair,Key##_##Value){ \
        Key key; \
        Value value; \
    };

通过键值对,想来实现一个map应该不是难事。 细心的小伙伴会发现其实并不一定要RENAME宏来处理重命名,或许直接拼接可能更直观,比如上面的key##_##value,当然,这也是本文的bug之一,或许代码就是这样修修补补的过程。

    虽然这样的实现有点那种炫技的嫌疑,不过不确定是否有人会有这样的需要。当然了,也可能有人会说都这样了,我干嘛不去写c++呢?要我说:您说对,确实没必要这么麻烦。原因一是这种写法并不像c++那么简洁,自定义模板很麻烦,由于c语言宏定义的问题,这样的代码没办法调试。展开后的源码没有办法断点调式,其一会导致定义模版很复杂,其二是源代码难调式。当然,对于模板定义复杂问题也可以这样解决,先实现一个int版本,调式无碍后再将其改成对应模板。不过,这也是一定要这么做的人需要了。其二的原因是本身c++是c的超集,实在麻烦,大可使用g++等c++编译器去写c代码也行,按照c语法,同时只添加template功能,也会使得这个过程简便更多,不过需要注意extern “C”导出c函数命名接口。可以这么实现的另外一点是江湖上流传着使用c++编译器编译c代码能提升性能的传说,当然,这点我没去求证过,感兴趣的小伙伴可以去试试。

  • 21
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值