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})为例:
- 调用
filter(arr, 8, sizeof(int), isEven, &n):传递数组首地址、元素个数8、单个int的大小4、判断偶数的回调isEven、存储过滤后元素个数的指针&n - filter函数内部:
- 先malloc一块能装8个int的内存(8*4=32字节)
- 循环遍历每个元素:计算当前元素地址
(char*)array + i*4(把void转成char是为了按字节偏移),传给isEven isEven把void*转成int*,解引用拿到整数,通过!(num & 1)判断是否为偶数(二进制最后一位为0则是偶数)- 符合条件的元素,用memcpy把当前int的4字节数据复制到过滤数组的对应位置,
filteredSize自增
- 循环结束后用realloc把过滤数组缩容到实际需要的大小(
filteredSize*4),返回数组首地址 - 主函数里把返回的
void*转成int*,遍历输出后free释放内存
char*数组处理流程
以strs数组({"Boy", "Apple", "Cat", "Zebra", "Aeroplane"})为例:
- 调用
filter(strs, 5, sizeof(char*), startsWith, &n):传递数组首地址、元素个数5、单个char*的大小(32位系统是4,64位是8)、判断首字符的回调startsWith、&n - filter函数内部:
- malloc一块能装5个char的内存(58=40字节,64位系统)
- 循环遍历每个元素:计算当前元素地址
(char*)array + i*8,传给startsWith startsWith把void*转成char**,解引用拿到字符串的首地址(比如第一个元素是"Boy"的地址)- 第一次调用时会让用户输入一个字符,之后判断字符串首字符是否等于输入的字符,返回非0表示保留
- 符合条件的元素,用memcpy把当前char*的8字节数据(也就是字符串的地址)复制到过滤数组的对应位置,
filteredSize自增
- 缩容后返回
void*,主函数转成char**,遍历输出字符串后free释放内存
处理char*数组时memcpy复制的内容
strs是指针数组,每个元素是char*类型的指针,指向字符串常量的首地址。memcpy复制的是这个指针本身的字节数据,而不是字符串的内容。比如"Apple"的地址是0x1234,memcpy就把0x1234这8个字节(64位系统)复制到过滤数组里,所以过滤后的数组里存的还是指向原字符串的指针,并没有复制字符串内容。
内容的提问来源于stack exchange,提问作者Aqib Javid Bhat
相关产品推荐
相关产品推荐

