【问题标题】:Code organization style for C? [closed]C 的代码组织风格? [关闭]
【发布时间】:2024-01-03 07:19:01
【问题描述】:

我知道一些更高级的语言,都是基于网络的(PHP、javascript、一些 python)。我终于决定学习一门较低级别的语言,并决定使用 C。问题是我使用的所有语言都严重基于 OOP。看到(基于我所做的研究)C 没有类或继承。因此,我问你这个问题:我应该如何以像 OOP 那样有条理的方式组织我的 C 代码,而不必切换语言或只拥有包含无穷无尽功能的文件?

从未来编辑:回想起来这个问题非常愚蠢。我 15 岁,在学校没有 CS……

【问题讨论】:

标签: c oop structure


【解决方案1】:

C 并没有提供太多的代码组织方式。有函数和文件。

当您想要使用隐藏实现或私有函数来实现接口时,请创建两个文件,例如“foo.h”和“foo.c”。

接口和/或公共函数是“foo.h”中的函数原型,函数写在“foo.c”中。此外,任何隐藏的实现或其他私有函数都在“foo.c”中,但标记为static,如static int foo_internal_function(int i);。标记为static 的函数只能在它们所在的文件中引用,不能在其他任何文件中引用。

没有正式的命名空间,尽管您可以使用前缀函数名称获得大致相同的效果,例如Foo_discombobulate_developers()。没有类,尽管您至少可以使用文件获得一些封装效果。没有可扩展的层次结构,只有文件和函数。

C 没有任何地方可以组织后来的语言,但是已经用它编写了许多非常好的程序。小心,注释任何令人困惑的东西(在 C 中可能会有令人困惑的东西),并做好笔记。

【讨论】:

  • +1 了解公共和私有 API 的正确方式。使用static 隐藏所有未导出的函数。为模块创建自己的命名空间(所有导出符号的通用前缀)是您应该做的事情。
【解决方案2】:

“功能无穷的文件”

您真正想要的是具有定义明确且功能有限的文件。这称为modular programming。这个想法是您将基于功能的函数分组到单个编译单元中,并在标头中定义函数原型:

Foo.h:

int GetFoo();

Foo.c:

int GetFoo()
{
   ...
}

这有点类似于将一组方法分组到一个类中的方式。主要区别在于,在任何给定时间,您可能正在或可能没有“这个”事情。也就是说,您仍然可以在 C 中进行本质上“面向对象”的编程。但是,您的对象成为模块的参数。这是一种方法:

Bar.h:

typedef int BAR_OBJ

BAR_OBJ MakeBar();

int GetBarSize(BAR_OBJ);

void DoFooToBar(BAR_OBJ, ...)

Bar.c

   struct BarDetails
   {
      int isFree;
      int size;
      ...
      // other info about bar
   };

   static BarDetails arrayOfBars[...]; // 

   BAR_OBJ MakeBar()
   {
       // search BarDetails array, find free Bar    
   }

   int GetBarSize(BAR_OBJ obj)
   {
       return arrayOfBars[obj];
   }

   void DoFooToBar(BAR_OBJ, ...)
   {
      // lookup bar obj and do something
   }

【讨论】:

  • 关于您的代码的风格问题:“#define BAR_OBJ int”应该是“typedef int BAR_OBJ”,因为它更清楚您的意图是什么(创建一个等效于 int 的新类型)并且可能有一些前者具有意外副作用的极端情况。此外,您通常不需要在函数声明中使用 extern,但我想明确表示并没有什么坏处。
  • 是的,你是对的。更新代码。
  • extern int GetFoo(); 是假的。
  • extern 不是假的,但对于函数声明来说是多余的。不过,这并不妨碍我使用它。
  • 另外,“BAR_OBJ MakeBar();”应该是“BAR_OBJ MakeBar(void);”。在 C++ 中没有区别,但在 C 中,第一个意味着您在声明中没有提供有关参数的信息,第二个意味着函数不接受任何参数。
【解决方案3】:

好吧,如果你仍然想做一些 OO,就使用 C++。

如果您真的只想使用 C 语言而不使用任何 OO,则将您的逻辑划分为多个文件,并将每个源文件视为一个对象。因此,每个文件只会包含与该单一结构密切相关的结构和功能。

【讨论】:

  • 完全正确——对象只是数据加上对该数据执行明确定义的职责的操作。
【解决方案4】:

这是一个在 C 中频繁出现的模仿 OOP 的模式:

考虑一个名为 MyClass 的类。

/* MyClass.h or myclass.h */

#ifndef MYCLASS_H
#define MYCLASS_H

struct myclass_s;
typedef struct myclass_s myclass_t;

myclass_t * myclass_new();
void delete_myclass(myclass_t *);

    // Method int doMyStuff(int arg1,int arg2)
int myclass_doMyStuff(myclass_t *, int arg1, int arg2);

#endif //MYCLASS_H

头文件定义了类型myclass_t,但隐藏了实际的实现myclass_s。具有两个名称的这种有点公认的要求源于 C 在单独的命名空间中具有结构,而在 C++ 中,结构与所有其他类型位于相同的命名空间中。该代码旨在在 C 和 C++ 中工作。这是对应的.c文件:

/* MyClass.c or myclass.c */

#include "myclass.h" // Or MyClass.h

struct myclass_s {
   int exampleField;
};

myclass_t * myclass_new()
{
   myclass_t * o=(myclass_t*)malloc(sizeof(myclass_t));
   // Initialize o here.
   return o;
}

void myclass_delete(myclass_t * o)
{
   // Do any cleanup needed on o here.
   free(o);
}

int myclass_doMyStuff(myclass_t * o,int arg1,int arg2)
{
   // ...
}

在 C 中也可以进行继承和动态绑定,但它们的涉及程度更高。上述模式,或任何模拟 OOP 的模式,并不总是在 C 中做事的最佳方式,所以尽量不要沉迷于以类为中心的思维方式。不过,这种模式偶尔还是有用的。例如,libpng 使用了与此类似的东西(他们还使用 setjmp/longjmp 执行“异常”,我建议不要这样做)。

【讨论】:

    【解决方案5】:

    GObject 用于 OOP-ish C,但设计良好的结构化程序不应该比同等的​​ OOP 程序更难阅读/维护。

    【讨论】:

      最近更新 更多