我觉得喝咖啡和编程类似。当然作为资深穷屌我并没有钱对咖啡进行深入的研究,只是从经常在科技园附近星巴克里面装作写代码看能不能忽悠到投资人或者志同道合之人的有限经历出发,用咖啡来类比。对于大部分科班的人来说,接触到的第一语言是C语言,然后C/C++->Java->python/php/scala/lua等等,就像一开始我们喝美式,苦,酸涩,后来加了一点炼乳牛奶之后发现了新大陆,于是尝试拿铁。尝试卡布奇诺,尝试在咖啡中加入各种能让苦酸涩消失甚至变的齁甜的东西。俗话说借酒消愁愁更愁,借甜浇咖咖不咖,终于有一天意识到自己已经很久没有喝过纯粹的咖啡了,干了一杯美式,发现原来这才是咖啡的味道呀。
对于C语言本身也是类似的感受,它虽然语法简单,简单到简陋,简陋到没有太多基础设施,但是每次从其他语言的沼泽漩涡中脱身回到C语言的怀抱中来的时候又总是有新的感悟。很多人对c保持一种敬畏的态度以至于敬而远之,总是在担心自己写出来的代码会不会太幼稚太low或者不酷炫而迟迟动不了手。这就很没有必要。用文章来比喻,高手写的文章不见得遣词造句很华丽,而引人入胜的是作者独到的见解和思路,就像韩寒的作品。而很多网文,纵然有华丽的遣词,气吞河山的长短句,喘不过气的排比,仍然拯救不了那糟糕的剧情和空洞的想象力,反而给人一种很尴尬,矫揉造作的感觉。就像大型Java CRUD项目——过度设计,过度封装,多态,设计模式,哈里发塔一样高的继承树以及 500斤重的配置文件堆砌出来的怪物。
俗话说千里之外之行始于足下,万里之行始于轮子。很多人即便学完了C的基本语法,却依旧感觉无从下笔。书本知识最终要转换成工程实践,工程代码和纯学术算法代码又不太一样,比如你可能会写排序算法。冒泡,堆排,快排溜得飞起,却总是对着一堆int数组排来排去?工程化面临的场景可不太一样,你必须更加关注数据如何存储如何查询,如何做一个可扩展的可实施并且隐藏实现细节的轮子出来,轮子是服务于别的程序员的,写出有服务意识的代码,这是纯书本代码不太关注的细节,也是把old school代码转换成工程可用的很好的方式。
如果你学习了一段时间的C语言,还是有一种无从下笔的无力感,那么不妨从造轮子开始。造轮子的起手式就是这种无力感的“破壁人”。
首先我推荐Linux平台,如果你还没有安装Linux,请关注我另一个专栏弓箭维修指北。
正式进入开发阶段,最头疼的问题就是给自己的项目起个名字,一定要威武雄壮霸气听到就腿软的那种horrible thing。所以我打算起——“姥姥家的锅铲”(grandma's turner aka GT)作为项目名。先建立目录,在项目的根目录下再建两个目录分别用来存放我们的头文件和源码目录。然后让我们先尝试造个简单的轮子吧——栈。
// gttypes.h // Created by Rowland@Sjet on 2018/1/28. // #ifndef GTLIB_GTTYPES_H #define GTLIB_GTTYPES_H #ifdef __cplusplus extern "C" { #endif #define GT_API extern #define GT_OK (0) #define GT_ERROR_OUTMEM (-1) #define GT_ERROR_FULL (-2) #define GT_ERROR_EMPTY (-3) #ifdef __cplusplus } #endif #endif
如果要说构造轮子的起手式,这个骨架就是了。最外层的宏ifndef define endif是用来告诉编译器不要重复include我,里面的extern c {}层次是通知c++编译器用c的方式处理我,再里面一层就是我们的代码了。宏应该是服务于可读性越直观越好。
// gtstack.h // Created by Rowland@Sjet on 2018/1/28. // #ifndef GTLIB_GTSTACK_H #define GTLIB_GTSTACK_H #ifdef __cplusplus extern "C" { #endif #include "gttypes.h" typedef struct GtStack GtStack; GT_API GtStack* gt_stack_create(size_t); GT_API int gt_stack_push(GtStack*, void*); GT_API int gt_stack_pop(GtStack*, void**); GT_API void gt_stack_destroy(GtStack**); #ifdef __cplusplus } #endif #endif //
如非必要,在头文件中只暴露api,任何和实现相关的具体函数,变量和结构体尽量不要在头文件中暴露。未来如果你想闭源,实现只提供so库,操作灵活性更大。
// gtstack.c // Created by Rowland@Sjet on 2018/1/28. // #include <stdlib.h> #include "../include/gtstack.h" struct GtStack{ size_t max; int index; void** elems; }; GtStack* gt_stack_create(size_t max){ GtStack* out = (GtStack*)malloc(sizeof(GtStack)); if(!out) exit(GT_ERROR_OUTMEM); if(max<=0) max = 16; out->elems = (void**)calloc(max, sizeof(void*)); if(!out->elems) exit(GT_ERROR_OUTMEM); out->max = max; out->index = 0; return out; } int gt_stack_push(GtStack* in, void* data){ if(in->index>=in->max) return GT_ERROR_FULL; in->elems[in->index++] = data; return GT_OK; } int gt_stack_pop(GtStack* in, void** data){ if(in->index<=0) return GT_ERROR_EMPTY; *data = in->elems[--in->index]; return GT_OK; } void gt_stack_destroy(GtStack** in){ if(*in){ GtStack* stack = *in; free(stack->elems); free(stack); *in = NULL; } }
我写的代码比较随意,没有推敲,但是我不会写“先申明,再使用”的代码,肯定是一步到位,如果你拥有一个现代编译器,也最好不要写类似int a; a=5;这种先申明后初始化的代码,大多数这么写的代码是为了兼容老的编译器或者维护一个历史包袱沉重的项目,在实际工程当中,有可能会发生脏读。
接下来,在项目根目录写一个main.c来测试一下吧
// main.c // Created by Rowland@Sjet on 2018/1/28. // #include <stdio.h> #include <stdlib.h> #include "include/gtstack.h" int main(){ GtStack* stack = gt_stack_create(10); gt_stack_push(stack, "顺丰"); gt_stack_push(stack, "韵达"); gt_stack_push(stack, "申通"); gt_stack_push(stack, "圆通"); char* p; int err; while((err=gt_stack_pop(stack, (void**)&p))==GT_OK){ printf("pop:%s\n", p); } gt_stack_destroy(&stack); return EXIT_SUCCESS; }
编译、执行
Sjet/> clang main.c include/gtstack.h src/gtstack.c Sjet/> ./a.out pop:圆通 pop:申通 pop:韵达 pop:顺丰 Sjet/> _
验证通过就可以把main.c删掉了,这样第一个轮子就造好了。接下来的篇幅里会再介绍如何使用自动构建工具,如何测试,如何检测内存泄漏,再介绍几个常用数据结构和算法,我们就可以撸一个实际的项目出来了。尽量不使用开源类库而手动实现所需要的各种边边角角。感兴趣的不妨点个赞再关注一下!
本期代码地址: nikoloss