关键在于模块化。这样更容易设计、实现、编译和维护。
如果您有时间学习,请看看Ada应用程序如何结构化,以及其强制性的package
(模块接口)和package body
(模块实现)。
这是关于编码的。
对于维护(请记住,您只编写一次代码,但需要多次维护),我建议记录您的代码;Doxygen对我来说是一个不错的选择。我还建议构建一个强大的回归测试套件,以便您进行重构。
常见误解认为面向对象技术无法应用于 C 语言。其实大部分技术是可以的,只不过相对于那些专门为此设计语法的语言来说,会稍微麻烦一些。
健壮系统设计的基础之一是将实现封装在接口后面。FILE* 和与其一起工作的函数(如 fopen()、fread() 等)是在 C 语言中将封装应用于接口的好例子。(当然,由于 C 缺乏访问限定符,你无法强制执行不要窥视文件结构体 struct FILE 内部的规定,但只有受虐狂才会这样做。)
必要时,可以使用函数指针表在 C 语言中实现多态行为。是的,语法很丑陋,但效果与虚函数相同:
struct IAnimal {
int (*eat)(int food);
int (*sleep)(int secs);
};
/* "Subclass"/"implement" IAnimal, relying on C's guaranteed equivalence
* of memory layouts */
struct Cat {
struct IAnimal _base;
int (*meow)(void);
};
int cat_eat(int food) { ... }
int cat_sleep(int secs) { ... }
int cat_meow(void) { ... }
/* "Constructor" */
struct Cat* CreateACat(void) {
struct Cat* x = (struct Cat*) malloc(sizeof (struct Cat));
x->_base.eat = cat_eat;
x->_base.sleep = cat_sleep;
x->meow = cat_meow;
return x;
}
struct IAnimal* pa = CreateACat();
pa->eat(42); /* Calls cat_eat() */
((struct Cat*) pa)->meow(); /* "Downcast" */
所有都是好的答案。
我只想补充一点“尽量减少数据结构”。这在C语言中甚至可能更容易,因为如果说C++是“带有类的C语言”,面向对象编程则试图鼓励你把脑海中的每个名词/动词都变成类/方法。这可能非常浪费资源。
例如,假设您有一个时间序列温度读数数组,并且希望将它们显示为Windows中的折线图。Windows有一个PAINT消息,当您收到它时,可以循环遍历数组并进行LineTo函数操作,同时缩放数据以将其转换为像素坐标。
但是我已经看到过太多次的情况是,由于图表由点和线组成,人们会构建一个包含点对象和线对象的数据结构,每个对象都可以DrawMyself,并使其持久化,理论上认为这样会“更有效率”,或者他们可能需要能够用鼠标悬停在图表的某些部分并数值地显示数据,因此他们在对象中构建了用于处理此类问题的方法,这当然涉及创建和删除更多的对象。
因此,您最终得到了大量的代码,这些代码看起来非常清晰易读,但实际上只花费了90%的时间来管理对象。
所有这些都是为了“良好的编程实践”和“效率”而进行的。
至少在C语言中,简单有效的方法将更加明显,构建金字塔的诱惑也会更小。
GNU编码标准已经发展了几十年。即使您不完全遵循它们,阅读它们仍然是一个好主意。思考其中提出的观点可以为您构建自己的代码结构提供更坚实的基础。
封装无论使用什么开发语言都是成功开发的关键。
我在C语言中用过的一个技巧是,在".h"文件中不包含"私有"方法的原型。