You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C语言通用filter函数对不同类型指针的工作原理解析

编写可处理C语言中不同类型数组的filter函数

我原本写了只处理整数数组的filter函数,现在想把它泛化,让它支持任意类型的数组,尤其是字符串(char*)数组的处理一直没搞定。下面是实现好的泛化版代码,我需要搞懂它的工作原理,包括整数数组和char数组的处理流程、传递的参数,还有处理char数组时memcpy到底复制了什么。

代码实现

#include <stdio.h>
#include <conio.h>
#include <string.h>
#include <stdlib.h>

void *filter(void *array, int size, int elementSize, int (*fn)(void *), int *filteredSize);
int isEven(void *);
int startsWith(void *);

void main()
{
    int arr[8] = {1,2,3,4,5,6,7,8}, *evens;
    char *strs[5] = {"Boy", "Apple", "Cat", "Zebra", "Aeroplane"};
    char **fStrs;
    int n, i;
    evens = (int *)filter(arr, 8, sizeof(int), isEven, &n);
    puts("Evens");
    for (i = 0; i < n; i++)
    {
        printf("%d\t", evens[i]);
    }
    printf("\n");
    free(evens);

    fStrs = (char **)filter(strs, 5, sizeof(char *), startsWith, &n);
    for (i = 0; i < n; i++)
        printf("%s  ", fStrs[i]);
    printf("\n");
    free(fStrs);
}

void *filter(void *array, int size, int elementSize, int (*fn)(void *), int *filteredSize)
{
    int i;
    void *filteredArray;
    *filteredSize = 0;
    filteredArray = malloc(size * elementSize);
    if (filteredArray == NULL)
        return NULL;
    for (i = 0; i < size; i++)
    {
        void *currentElementLoc = (char *)array + (i * elementSize);
        if ((*fn)(currentElementLoc))
        {
            void *filteredElementLoc = (char *)filteredArray + (*filteredSize * elementSize);
            memcpy(filteredElementLoc, currentElementLoc, elementSize);
            (*filteredSize)++;

        }
    }
    filteredArray = realloc(filteredArray, *filteredSize * elementSize);
    if (filteredArray == NULL)
    {
        free(filteredArray);
        return NULL;
    }

    return filteredArray;
}

int isEven(void *p)
{
    int num = *((int *)p);
    return !(num & 1);
}

int startsWith(void *ptrToStr)
{
    static int hasRun = 0;
    static char ch;
    char *str = *((char **)ptrToStr);
    if (!hasRun)
    {
        puts("Starts with");
        scanf(" %c", &ch);
        hasRun = 1;
    }
    return *str == ch;
}

核心泛化思路

这个filter函数通过**void*通用指针**、元素大小elementSize和回调函数实现了泛型处理:

  • void* array:接收任意类型的数组首地址
  • int elementSize:告诉函数单个元素的字节大小,用来计算每个元素的内存偏移
  • int (*fn)(void*):回调函数,判断当前元素是否符合过滤条件,返回非0表示保留

整数数组处理流程

以arr数组({1,2,3,4,5,6,7,8})为例:

  1. 调用filter(arr, 8, sizeof(int), isEven, &n):传递数组首地址、元素个数8、单个int的大小4、判断偶数的回调isEven、存储过滤后元素个数的指针&n
  2. filter函数内部:
    • 先malloc一块能装8个int的内存(8*4=32字节)
    • 循环遍历每个元素:计算当前元素地址(char*)array + i*4(把void转成char是为了按字节偏移),传给isEven
    • isEven把void*转成int*,解引用拿到整数,通过!(num & 1)判断是否为偶数(二进制最后一位为0则是偶数)
    • 符合条件的元素,用memcpy把当前int的4字节数据复制到过滤数组的对应位置,filteredSize自增
  3. 循环结束后用realloc把过滤数组缩容到实际需要的大小(filteredSize*4),返回数组首地址
  4. 主函数里把返回的void*转成int*,遍历输出后free释放内存

char*数组处理流程

以strs数组({"Boy", "Apple", "Cat", "Zebra", "Aeroplane"})为例:

  1. 调用filter(strs, 5, sizeof(char*), startsWith, &n):传递数组首地址、元素个数5、单个char*的大小(32位系统是4,64位是8)、判断首字符的回调startsWith、&n
  2. filter函数内部:
    • malloc一块能装5个char的内存(58=40字节,64位系统)
    • 循环遍历每个元素:计算当前元素地址(char*)array + i*8,传给startsWith
    • startsWith把void*转成char**,解引用拿到字符串的首地址(比如第一个元素是"Boy"的地址)
    • 第一次调用时会让用户输入一个字符,之后判断字符串首字符是否等于输入的字符,返回非0表示保留
    • 符合条件的元素,用memcpy把当前char*的8字节数据(也就是字符串的地址)复制到过滤数组的对应位置,filteredSize自增
  3. 缩容后返回void*,主函数转成char**,遍历输出字符串后free释放内存

处理char*数组时memcpy复制的内容

strs是指针数组,每个元素是char*类型的指针,指向字符串常量的首地址。memcpy复制的是这个指针本身的字节数据,而不是字符串的内容。比如"Apple"的地址是0x1234,memcpy就把0x1234这8个字节(64位系统)复制到过滤数组里,所以过滤后的数组里存的还是指向原字符串的指针,并没有复制字符串内容。

内容的提问来源于stack exchange,提问作者Aqib Javid Bhat

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 05:27:19