在C语言中实现一个通用的“映射”函数来处理数组

13

我在实现一个可以用于数组的通用“map”函数时遇到了困难。 我从下面的草稿开始:

void MapArray(void * src, void * dest, void * (f)(void *), size_t n, size_t elem)
{
   unsigned int i = 0, j = 0;
   void * temp = malloc(elem);

   for(i = 0; i<n, i++)
   {
      temp = (f)((char *) src) + i));
      for(j = 0; j < elem; j++)
      {
         *(((char *) dest) + i) = *(((char *) temp) + i);
      }
   }
   free(temp);
}

我明白为什么这不正确——在将其传递给“f”之前,我将其强制转换为(char*),但现在我感到失落,想不出解决办法。(我正在学习C语言)

我的想法是获取“f”的结果,并逐字节将其复制到dest[i]中。

你能给我一些提示吗?


你需要这个指针函数做什么,想要实现哪种映射,我认为你不想要例如 SGI 的 map 容器吧? - Svisstack
这是一个典型的“映射”应用程序,你可以在几乎所有功能性语言中找到它。你发送一个列表、一个函数,它将返回由此组成的列表:(f(l[1]), ..., f(l[n]))。 - Lasirc
3个回答

22

你的第一个问题是在几个表达式中处理了太多的事情。你需要把它拆分开来。

void MapArray(void * src, void * dest, void * (f)(void *), size_t n, size_t elem)
{
   unsigned int i = 0, j = 0;
   void * temp = malloc(elem);

   char* csrc = (char*)src;
   char* cdest = (char*)dest;
   char* ctemp = (char*)temp;
   for(i = 0; i<n; i++)
   {
       csrc++;
       cdest++;
       ctemp++;
       temp = f(csrc);
       for(j = 0; j < elem; j++)
       {
           cdest[i] = ctemp[i];
       }
   }
   free(temp);
}

现在是你的第二个问题。你分配了一个缓冲区,然后你把指针赋值给它?反复这样做?然后只释放最后一个 f 调用的结果吗?这是完全不必要的。

void MapArray(void * src, void * dest, void * (f)(void *), size_t n, size_t elem)
{
   unsigned int i = 0, j = 0;

   char* csrc = (char*)src;
   char* cdest = (char*)dest;
   for(i = 0; i<n; i++)
   {
       csrc++;
       cdest++;
       char* ctemp = (char*)f(csrc);
       for(j = 0; j < elem; j++)
       {
           cdest[i] = ctemp[i];
       }
   }
}

现在是你的第三个问题。你传递了一个指向char类型的指针,但没有传递void*类型的指针。这意味着你的函数不能是通用的,即f不能应用于任何东西。我们需要一个void*类型的数组,这样该函数就可以接受任何类型的参数。我们还需要将类型的大小作为参数传递进来,以便我们知道要移动dest指针多远。

void MapArray(void ** src, void * dest, void * (f)(void *), size_t n, size_t sizeofT)
{
    for(unsigned int i = 0; i < n; i++) {
        void* temp = f(src[n]);
        memcpy(dest, temp, sizeofT);
        dest = (char*)dest + sizeofT;
    }
}

我们仍然有另一个问题 - temp的内存。我们没有释放它。我们也没有传递用户数据参数到f中,这样它就可以返回我们不需要释放的堆分配的内存。f能够工作的唯一方式是它返回一个静态缓冲区。

void MapArray(void ** src, void * dest, void * (f)(void *, void*), void* userdata, size_t n, size_t sizeofT)
{
    for(unsigned int i = 0; i < n; i++) {
        void* temp = f(src[n], userdata);
        memcpy(dest, temp, sizeofT);
        dest = (char*)dest + sizeofT;
    }
}

现在f可以对任何内容进行操作并保留所需的状态。 但我们仍未释放缓冲区。 现在,f返回一个简单的结构体,告诉我们是否需要释放缓冲区。 这还允许我们在不同的f调用中释放或不释放缓冲区。

typedef struct {
    void* data;
    int free;
} freturn;

void MapArray(void ** src, void * dest, freturn (f)(void *, void*), void* userdata, size_t n, size_t sizeofT)
{
    for(unsigned int i = 0; i < n; i++) {
        freturn thisreturn = f(src[n], userdata);
        void* temp = thisreturn.data;
        memcpy(dest, temp, sizeofT);
        dest = (char*)dest + sizeofT;
        if (thisreturn.free)
            free(temp);
    }
}

然而,我仍然不明白这个函数的作用。所有这些代码只是为了替换一个简单的for循环吗?你试图替换的那段代码比调用你的函数的代码更简单,可能更有效率,绝对更强大(例如可以使用continue/break)。

此外,C对于这种工作真的很糟糕。 C ++要好得多。例如,在C++中,将函数应用于数组的每个成员非常简单。


好多有用的信息!非常感谢。您真的不知道我从您的回复中学到了多少! - Lasirc

2
没有太多像这样的东西在C标准库中,这是因为在C中很难做到这一点。你不能将结果“逐字节”复制到dest[i]上,因为你已经将dest转换为char *,它只指向一个char(字节)。
假设elem是f返回类型的大小,n是src和dest中元素的数量。在这种情况下,你的代码不算太远,但是(正如你已经猜测的那样),你操作指针的方式(尤其是转换为char *)行不通。
即使你解决了这个问题,你还有另一个问题:在不知道类型的情况下分配从f返回的类型将非常(非常)困难。实际上,我所能想到的唯一方法就是将这段代码封装成宏。
#define MapArray(s, d, f, n) \
do {                         \
   unsigned i;               \
   for (i = 0; i<n; i++)     \
      d[i] = f(s[i]);        \
} while (0)

您可以像这样使用它:
int f(int a) { return a + 100; }

#define elements(array) (sizeof(array)/sizeof(array[0]))

int main() { 
    unsigned i;
    int x[] = { 0, 1, 2, 3};
    int y[elements(x)];

    MapArray(x, y, f, elements(x));

    for (i=0; i<elements(x); i++)
        printf("%d\n", y[i]);
    return 0;
}

注意:我并不推荐这样做。这只是一种完成所需的方式,但正如我一开始所说的,这在C语言中几乎是不可能做得很好的。这段代码在某种程度上可以工作,但在我看来,它并不能胜任这项工作。


1
  • 检查函数 f 是否返回指向本地变量的指针
  • 我不确定循环遍历 j 应该做什么

循环j是我尝试逐字节复制应用程序“f”的输出到“dest”的条目“i”的方法。我这样做是因为我希望我的“f”返回可变大小的数据。 - Lasirc
你正在循环中使用 i - srean
@Lasirc: 但此时该循环只是将相同的字节复制到相同的位置 elem 次。您需要在内部使用 *(((char *) dest) + i * elem + j) = *(((char *) temp) + i * elem + j); - j_random_hacker
@Lasirc 我本来想说你应该先单独索引,然后在调用 f 之前将其转换为 void*,而不是使用 f( ((char *)src) + i )。但是 @DeadMg 的答案使这一切都变得无关紧要了。@j_random_hacker 感谢您更明确地传达了我的想法。 - srean

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接