C中如何实现封装

发布于 2024-11-07 07:21:07 字数 1584 浏览 3 评论 0原文

我不确定我想做的是否称为封装,但它是一个 OOP 概念。我正在实现一个二叉树,特别是插入函数:

typedef struct __node* tree;
typedef struct __node { void* data; tree l,r; } node;

typedef struct {int (*cmp)(void* a,void* b); tree root;} avl_tree;

....

void tree_insert(tree node, tree* root, int (*cmp)(void* a,void* b))
{
  if (*root==NULL) { *root=node; return; }
  int c1 = cmp(node->data, (*root)->data);

  if (c1==-1) tree_insert(node, &((*root)->l), cmp);
}

tree tree_new_node(void*data){ tree a = malloc(...); ... return a; }

void avl_insert(void* data, avl_tree* a)
{
  tree_insert(tree_new_node(data), &(a->root), a->cmp);
  ....
}

该模块将通过avl_insert函数来使用,该函数被赋予一个指向相关平衡树avl_tree的指针,其中包含指向原始树的指针以及指向比较器的指针。现在,它显然应该调用 tree insert ,并且 tree_insert 应该能够访问比较器以及我当前插入的节点。该函数在二叉树上运行,因此它是自然递归的。但是,如果我将比较器和当前节点作为参数,它们将在每次递归调用时传递,这是不必要的,因为它们始终是相同的。

我想避免这样做。我一直无法想出一个干净又好的解决方案。这些是我能想到的选项:

  1. 使用 C++ 类,并让 tree_insert 函数成为 avl_tree 类的方法。然后它可以通过 this 指针访问比较器。这个解决方案的问题是我想使用 C 而不是 C++。此外,它不会消除当前节点参数的传递。

  2. 在函数内使用静态成员(或全局数据)。我不确定我是否可以在每次 avl_insert 调用时干净地初始化它们。此外,该解决方案不是线程安全的。

现在我想起来,用函数式编程语言来实现似乎很容易。我想知道,这是 C 的基本问题还是只是我不知道该怎么做。实现这一目标的最干净的方法是什么?

谢谢你!


在我思考了 Victor Sorokin 的答案之后,我读到了有关 this 指针的内容,结果发现它是每个成员函数调用中的隐式参数。现在想来,这似乎是唯一合乎逻辑的解决方案。每次调用 tree_insert 函数都需要知道它所操作的结构的地址。即使在函数式语言中,你也无法避免那个额外的指针......

一个可能的解决方案是在每个节点中保留一个指向主树结构的指针......

所以这是一个基本的“问题”。

I am not sure that what I am trying to do is called encapsulation, but it's an OOP concept. I am implementing a binary tree and in particular the insert function:

typedef struct __node* tree;
typedef struct __node { void* data; tree l,r; } node;

typedef struct {int (*cmp)(void* a,void* b); tree root;} avl_tree;

....

void tree_insert(tree node, tree* root, int (*cmp)(void* a,void* b))
{
  if (*root==NULL) { *root=node; return; }
  int c1 = cmp(node->data, (*root)->data);

  if (c1==-1) tree_insert(node, &((*root)->l), cmp);
}

tree tree_new_node(void*data){ tree a = malloc(...); ... return a; }

void avl_insert(void* data, avl_tree* a)
{
  tree_insert(tree_new_node(data), &(a->root), a->cmp);
  ....
}

The module is to be used through the avl_insert function which is given a pointer to the relevant balanced tree avl_tree which contains the pointer to the raw tree as well as a pointer to comparator. Now, it should obviously call tree insert and tree_insert should have access to the comparator as well as to the node I am currently inserting. The function walks on a binary tree so it's naturally recursive. However, if I give it the comparator and the current node as parameters they will be passed with each recursive invocation which is not necessary since they will always be the same.

I would like to avoid having to do so. I have not been able to come up with a clean and nice solution. These are the options that I could think of:

  1. Use a C++ class and have the tree_insert function be a method of avl_tree class. Then it would have access to the comparator through the this pointer. The problem with this solution is that I want to use C not C++. Besides, it won't eliminate the passing of the current node parameter.

  2. Use static members inside the function (or global data). I am not sure I can cleanly initialize them at each avl_insert call. Besides, this solution is not thread safe.

Now that I think about it this seems very easy to implement in a functional programming language. I wonder, is this a fundamental problem with C or is it just me not knowing how to do it. What would be the cleanest way to achieve this?

Thank you!


After I thought about Victor Sorokin's answer I read about the this pointer and it turns out it is an implicit parameter in every member function call. Now that I think about it it seems the only logical solution. Each invocation of the tree_insert function needs to know the address of the structure it's operating on. Not even in a functional language could you avoid that extra pointer...

A possible solution would be to keep a pointer to the main tree structure in each node..

So it's a fundamental "problem".

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

兰花执着 2024-11-14 07:21:07

一种可用于实现封装的有趣方法是研究 C++ 编译器发出的汇编代码,然后将其转换为适当的 C 代码。

另一种更传统的方法是使用一些 C 对象库,例如 GLib。

不过,我认为这两种方法会给出类似的结果:)

顺便说一句,您提到的第一个选项与第二个选项一样容易受到线程问题的影响。 C++ 中没有隐式线程安全。

我在 Linux 内核(文件系统层)中看到的“OOP”C 代码主要关注多态性,而不是封装。多态性是通过引入枚举可能操作的结构(作为函数指针)来实现的。然后创建各种“子类”,每个子类都使用自己的一组实现方法来初始化该结构。

One fun approach that could be used to achieve encapsulation is looking into assembly code emitted by C++ compiler and then translating it into appropriate C code.

Another, more conventional, approach would be to use some C object library, like GLib.

I think, though, these two methods will give similar results :)

By the way, first option you mentioned is just as vulnerable to threading issues as second. There's no implicit thread-safety in C++.

"OOP" C code I have seen in Linux kernel (file-system layer) is mostly concerned with polymorphism, not with encapsulation. Polymorphism is achieved by introducing structure enumerating possible operations (as pointers to functions). Various "subclasses" then created, each initializing this structure with it's own set of implementation methods.

旧城烟雨 2024-11-14 07:21:07

您应该能够将尾递归转换为迭代,并完全避免函数调用。像这样的东西

void tree_insert(tree node,tree*root,int (*cmp)(void*a,void*b))
{
   tree* current = root;
   while (*current != NULL)
   {
     int c1=cmp(node->data,(*current)->data);
     if(c1==-1)current = &((*current)->l);
     else current = &((*current)->r);
   }
   *current=node;
 }  

You should be able to convert that tail recursion to iteration, and avoid the function calls altogether. Something like

void tree_insert(tree node,tree*root,int (*cmp)(void*a,void*b))
{
   tree* current = root;
   while (*current != NULL)
   {
     int c1=cmp(node->data,(*current)->data);
     if(c1==-1)current = &((*current)->l);
     else current = &((*current)->r);
   }
   *current=node;
 }  
看海 2024-11-14 07:21:07

已经有一个问题涵盖了我的答案 -C 程序中的“静态”是什么意思?


您可以粗略地将 C 源文件视为一个类。关键字static使变量或函数只有内部链接,这类似于经典OOP中的private

foo.h

#ifndef FOO_H
#define FOO_H

double publicStuff;

double getter (void);
void setter (double);
int publicFunction (void);

#endif

foo.c

#include "foo.h"

static double privateStuff;

static int privateFunction (void)
{
    return privateStuff;
}

int publicFunction (void)
{
    return privateFunction();
}

double getter (void)
{
    return privateStuff;
}

void setter (double foo)
{
    privateStuff = foo;
}

main.c

#include "foo.h"
#include <stdio.h>

static double privateStuff = 42;

static int privateFunction (void)
{
    return privateStuff;
}

int main (void)
{
    publicStuff = 3.14;

    setter(publicStuff);

    printf("%g %d %d\n", getter(), publicFunction(), privateFunction());

    return 0;
}

There is already a question covering my answer—What does “static” mean in a C program?


You can roughly take a C source file as a class. The keyword static makes the variable or function have only internal linkage, which is similar to private in classical OOP.

foo.h

#ifndef FOO_H
#define FOO_H

double publicStuff;

double getter (void);
void setter (double);
int publicFunction (void);

#endif

foo.c

#include "foo.h"

static double privateStuff;

static int privateFunction (void)
{
    return privateStuff;
}

int publicFunction (void)
{
    return privateFunction();
}

double getter (void)
{
    return privateStuff;
}

void setter (double foo)
{
    privateStuff = foo;
}

main.c

#include "foo.h"
#include <stdio.h>

static double privateStuff = 42;

static int privateFunction (void)
{
    return privateStuff;
}

int main (void)
{
    publicStuff = 3.14;

    setter(publicStuff);

    printf("%g %d %d\n", getter(), publicFunction(), privateFunction());

    return 0;
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文