【问题标题】:How to not repeat myself in this situation? C functions that are the same but have different arguments在这种情况下如何不重复自己?相同但具有不同参数的 C 函数
【发布时间】:2016-08-25 20:01:53
【问题描述】:

现在我知道你在想什么了——我在标题中描述的事情听起来就像超载一样。我知道这不是 C 语言中的事情,无论如何我都不会这样做。我有这 2 个函数 - 它们的主体完全一样,但参数是 2 个不同的结构。基本上它是一个二叉搜索树结构和一个红黑树结构。您可能知道这些结构只有一个区别——红黑树结构包含一个多字段,即颜色字段。此外,函数搜索、最小值、最大值、前任、继任者......这些函数将具有完全相同的主体,但可惜它们采用 2 种不同类型的结构。当然插入和删除方法也会不同。

所以我在想如何避免打破编程中的第一条规则而不重复自己?我想了很多解决方案,但当我试图找到一种方法来实现它时,都没有奏效。我想过只为两者使用一个函数,但我不能这样做,因为结构不同。我考虑过使用宏,但老实说,我不知道如何使这些工作,我什至不确定它们是否可以避免我有 2 个不同结构的问题。我考虑过制作一个通用结构并让 rb 结构包含它和一个颜色变量,但这直接用几个字符改变了代码,因为我必须更深入地进入结构以获得值并且我不再有重复代码。

只是一个问题的例子:

bst_search(bstTree t, char *k)
{
    // Searching in tree
}

rb_search(rbTree t, char *k)
{
    // SAME code for searching in tree
}

如果我在 java 中编码,我可能会使用抽象超类来解决这个问题,但 C 没有这样的花哨的东西。

一些额外的信息:两个实现都有自己的头文件和类文件,我想保持这种方式。现在我在这 2 个类中都有重复的代码,唯一不同的是函数和结构的名称(c 的插入和删除函数除外)。

很抱歉,如果这有一个明显的解决方案,我只是在不复制代码的情况下找不到解决方法。

【问题讨论】:

  • 如果您不想通过使用通用结构来更改所有其余代码,请在两个调用函数中的每一个中执行此操作(每种类型一个)。将相关成员复制到您的公共结构中,并调用搜索功能。
  • 第一条规则肯定不是 DRY。我会把KISS放在第一位。但是,当然,您会得到不同的答案

标签: c struct


【解决方案1】:

作用于通用输入树的宏会成功,但我发现该解决方案有点脏。

另一种方法是拥有一个通用结构,其中包含两棵树的所有成员,没有嵌套结构。例如:

struct genericTree {
  // common members for both trees 
 ... 
  // members for rb trees 
 ... 
  // members for bst
 ... 
} 

那么你有一个函数:

search(genericTree* t, char* k) 

要保持语义,请使用 typedef:

typedef genericTree bstTree;
typedef genericTree rbTree;

因此,当它们只期望这些类型时,您仍然可以拥有获得 bstTree 或 rbTree 的函数。

这种方法的缺点是你会为一棵树占用更多内存,因为你保留了另一棵树的成员。你可能会通过一些工会来缓解它。

【讨论】:

    【解决方案2】:

    如果您使用bstTree 作为第一个成员创建rbTree,则:

    typedef struct
    {
       bstTree common ;
       int color ;
    } rbTree
    

    然后您可以安全地将rbTree 转换为bstTree,因此rb_search() 可以实现为一个简单的宏:

    #define rb_search(t, k) bst_search( (bstTree*)(t). k )
    

    一个问题是,现在对于rbTree 独有的任何代码,您必须通过common 成员访问大多数成员。然而,这并不是完全必要的;如果您没有使用bstTree 成员定义rbTree,而只是确保首先以相同的顺序和类型与公共成员相同地定义两者,您将能够将一个转换为另一个并访问成员只要将相同的打包和对齐选项应用于使用这些结构的所有模块 - 但是这样做的安全性和维护性要差得多。一种有点丑陋但更安全的方法是将公共成员放在包含文件中,#include 将成员放在每个结构定义中。

    【讨论】:

    • 最佳答案。这就是 Windows API 处理旧结构的新版本的方式。
    • 我总是会像这样定义那个宏:#define rb_search(t, k) bst_search(&(t)->common, k) 这样你就可以保留对其调用的类型检查,如果你用rb_search(hashTablePointer, "foo") 调用它,编译器就会尖叫。
    • 我喜欢这个版本,但是当我开始实现它时,我记得 - 通用 bstTree 有 3 个指针 - 父、右和左,它们都是 bstTree 指针。因此,如果我有一个包含指向其他 bstTree 的指针的 bstTree 的 rbTree,则 rbTree 节点之间没有连接。这有点超出了树的目的。是否有任何不涉及更多代码重复的解决方法?我应该继续为每棵不同的树执行相同的代码吗?
    • @user3212138 :最初的前提是只有一个成员的结构不同 - 现在有四个!如果原始问题不准确,我将无能为力。也许您应该包含定义和代码,以免依赖千里眼!
    • @user3212138 :没问题。在这一点上,我会对自己说——“啊!这就是 C++ 的用途!”——灯泡时刻。 ;-)
    【解决方案3】:

    在 C 中没有很好的方法来做到这一点(与 C++ 相比,模板正是为此目的而存在的)。

    丑陋的方式#1。通过使用宏:

    #define MAKE_SEARCH_FUNCTION(FN_NAME, VAR_TYPE) \
    FN_NAME(VAR_TYPE t, char *k) \
    { \
        /* Searching in tree */ \
    }
    
    struct bstTree {
    };
    MAKE_SEARCH_FUNCTION(bst_search, struct bstTree*)
    
    struct rbTree {
    };
    MAKE_SEARCH_FUNCTION(rb_search, struct rbTree*)
    

    丑陋的方式#2。通过将正文移动到单独的包含文件中。更多的工作,但如果函数非常大或者如果您需要一次生成整个函数系列(例如bst_search()/bst_add()/ bst_remove()),则有意义。

    // header.h

    FN_NAME(VAR_TYPE t, char *k) 
    {
        // Searching in tree
    }
    

    // source.c

    struct bstTree {
    };
    #define VAR_TYPE struct bstTree*
    #define FN_NAME bst_search
    #include "header.h"
    #undef VAR_TYPE
    #undef FN_NAME
    
    struct rbTree {
    };
    #define VAR_TYPE struct rbTree*
    #define FN_NAME rb_search
    #include "header.h"
    #undef VAR_TYPE
    #undef FN_NAME
    

    【讨论】:

      猜你喜欢
      • 2022-01-09
      • 1970-01-01
      • 2015-06-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-07-24
      相关资源
      最近更新 更多