不同类型指针的筛选功能是如何工作的?

huangapple go评论77阅读模式
英文:

How is filter function working for different types of pointers?

问题

Here's the translated content you requested:

编写一个能处理不同类型数组的过滤函数在C中

我尝试编写一个过滤函数,它接受一个整数数组和一个评估为真或假的回调函数。根据回调函数的结果,将一个元素添加到过滤后的数组中,并返回过滤后的数组。我们可以泛化它吗?比如,我们可以让它适用于任何类型的数组吗?我不知道如何使它适用于字符串(char *'s)。

这是代码片段,请问有人能解释一下它是如何工作的吗?(流程以及对整数数组和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;
}

I've provided the translated code portion as requested.

英文:

Writing a filter function that can handle different types of arrays in C!

I tried to write a filter function that takes an integer array and a callback function that evaluates to true or false. Based on the result of the callback function, an element is added to the filtered array, and the function returns the filtered array. Can we generalize it? Like, can we make it work for any type of array? I am not able to figure out how to make it work for strings (char *'s).

Here's is the code snippet, could anyone please explain how's working? (flow and what's being passed for both integer array and char * arrays).
What's memcpy copying in case of char * arrays?

#include &lt;stdio.h&gt;
#include &lt;conio.h&gt;
#include &lt;string.h&gt;
#include &lt;stdlib.h&gt;

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] = {&quot;Boy&quot;, &quot;Apple&quot;, &quot;Cat&quot;, &quot;Zebra&quot;, &quot;Aeroplane&quot;};
	char **fStrs;
	int n, i;
	evens = (int *)filter(arr, 8, sizeof(int), isEven, &amp;n);
	puts(&quot;Evens&quot;);
	for (i = 0; i &lt; n; i++)
	{
		printf(&quot;%d\t&quot;, evens[i]);
	}
	printf(&quot;\n&quot;);
	free(evens);

	fStrs = (char **)filter(strs, 5, sizeof(char *), startsWith, &amp;n);
	for (i = 0; i &lt; n; i++)
		printf(&quot;%s  &quot;, fStrs[i]);
	printf(&quot;\n&quot;);
	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 &lt; 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 &amp; 1);
}

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

答案1

得分: 0

如果您指的是字符串数组(而不是字符数组),那么您的“elementSize”是sizeof(char *)。您的字符串数组是指向字符串的指针列表。在这种情况下,“filter”将完全接受或拒绝每个字符串。

英文:

If you mean an array of strings (as opposed to an array of chars), then your "elementSize" is sizeof(char *). Your array of strings is a list of pointers to the strings. A "filter" in this case would accept or reject each string in its entirety.

答案2

得分: 0

Here is the translated content:

你有没有使用过 qsort 的经验?qsort回调函数具有签名 int (*comp)(const void *, const void *)。几乎所有与 qsort 相关的原则都适用于你的 filter 函数。

在这两种情况下,回调函数都会提供数组的元素的指针。这些元素本身可能是指针。

记住:指针是值。将一个 int 的值复制到另一个 int,与将一个(例如)float * 的值复制到另一个 float * 之间没有根本区别。

int a = 5;
int b = a;
float some_data = 1.23f;
float *a_pointer = &amp;some_data;
float *b_pointer = a_pointer;

赋值操作 b = aa_pointer = b_pointer 都执行了右边对象的值的逐字复制到左边对象。

扩展一下,memcpy 也是以相同的方式工作,但通过一层间接性

int a = 5;
int b;

memcpy(&amp;b, &amp;a, sizeof (int));
float some_data = 1.23f;
float *a_pointer = &amp;some_data;
float *b_pointer;

memcpy(&amp;b_pointer, &amp;a_pointer, sizeof (float *));

在这两个示例中,memcpy 的第三个参数是被复制的对象的大小。在第一个示例中是一个 int,在第二个示例中是一个 float *

memcpy 要求您指定这些对象在内存中的位置,因此它接受两个指针,但它不关心对象是什么,只关心它们占用多少内存。

使用以下代码:

char *first_array[2] = {
    &quot;Hello&quot;,         
    &quot;World&quot; 
};
char *second_array[2];   
                         
memcpy(&amp;second_array[0], &amp;first_array[1], sizeof (char *));

memcpy 复制了第一个数组的第二个元素的值(恰好是一个指针值,一个 char *)到第二个数组的第一个元素中。


那么回到 filter。与 qsort 共享一个警告,以及一个是你的函数独有的问题。

qsort 一样,你可以操作数组的数组。在这些情况下,回调函数必须小心地正确指定通过 void * 传递的类型。

所以如果你有一个像这样的数组数组:

char data[][32] = {
    &quot;foo&quot;,
    &quot;bar&quot;
};

那么回调实际上会接收到一个 char (*)[32] - 一个数组的指针。这意味着回调函数不能被制作成在所有字符串容器上“通用”工作。char **char (*)[N] 不是等同的。一个“字符串数组”并不总是一个 char * 数组。

你的 filter 函数的一个独特问题是创建一个可能与以前的数组共享数据的新数组引发了所有权的问题,并引发了很多问题:

  • 如果两个数组都包含由 malloc(或类似的函数)返回的指针值 - 哪个数组的所有者负责将这些指针值传递给 free

    • 如果是新数组的所有者,那么旧数组的所有者如何知道不将哪些值传递给 free
  • 新数组是否应被视为对旧数组的视图

    • 旧数组的所有者如何知道新数组何时完成使用?

我无法回答这些问题,因为这是一种设计问题。在原地过滤数组并提供处理被拒绝数据地址的功能解决了一些问题,但这是一种妥协。

以下是一个简单的示例:

#define _POSIX_C_SOURCE 200809L
#include &lt;ctype.h&gt;
#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;string.h&gt;

static size_t filter(
	void *base,
	size_t nel,
	size_t width,
	int (*fn)(void *),
	void (*on_reject)(void *))
{
	size_t len = 0;

	for (size_t i = 0; i &lt; nel; i++) {
		void *element = (char *) base + i * width;

		if (fn(element))
			memcpy(((char *) base + len++ * width), element, width);
		else if (on_reject)
			on_reject(element);
	}

	return len;
}

static int is_even(void *el)
{
	int *val = el;

	return *val % 2 == 0;
}

static int is_uppercase(void *el)
{
	const char *val = *((char (*)[32]) el);

	if (!*val)
		return 0;

	while (*val)
		if (!isupper((unsigned char) *val++))
			return 0;

	return 1;
}

static int starts_with_ba(void *el)
{
	return 0 == strncmp(*(char **) el, &quot;ba&quot;, 2);
}

static void reject_ba(void *el)
{
	free(*(char **) el);
}

int main(void)
{
	int nums[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	size_t nums_l = filter(nums, sizeof nums / sizeof *nums, sizeof *nums,
			is_even, NULL);

	for (size_t i = 0; i &lt; nums_l; i++)
		printf(&quot;%d\n&quot;, nums[i]);

	char strs[][32] = {
		&quot;hello&quot;,
		&quot;World&quot;,
		&quot;UPPER&quot;,
		&quot;CASE&quot;,
		&quot;no&quot;,
		&quot;lower&quot;,
		&quot;OKAY&quot;
	};

	size_t strs_l = filter(strs, sizeof strs / sizeof *strs, sizeof *

<details>
<summary>英文:</summary>

Do you have any experience using [`qsort`][1]? The *callback* function for `qsort` has the signature `int (*comp)(const void *, const void *)`. Almost all of the same principles of `qsort` apply to your `filter` function.

In both cases, the callback function is being provided pointers to *elements* of the array. These elements may themselves be pointers.

Remember: pointers are values. There is no fundamental difference between copying the value of one `int` to another `int`, and copying the value of one (e.g.) `float *` to another `float *`.

```C
int a = 5;
int b = a;
float some_data = 1.23f;
float *a_pointer = &amp;some_data;
float *b_pointer = a_pointer;

Both the assignments b = a and a_pointer = b_pointer perform a byte-wise copy of the value of the right to the object on the left.

By extension, memcpy works the same way, but through one level of indirection.

int a = 5;
int b;

memcpy(&amp;b, &amp;a, sizeof (int));
float some_data = 1.23f;
float *a_pointer = &amp;some_data;
float *b_pointer;

memcpy(&amp;b_pointer, &amp;a_pointer, sizeof (float *));

In both examples, the third argument to memcpy is the size of the object being copied. In the first example, an int, in the second example, a float *.

memcpy requires you specify where in memory these objects exist, hence it accepts two pointers, but it does not care what the objects are - just how much memory they occupy.

With

char *first_array[2] = {
    &quot;Hello&quot;,         
    &quot;World&quot; 
};
char *second_array[2];   
                         
memcpy(&amp;second_array[0], &amp;first_array[1], sizeof (char *));

memcpy copies the value (just happens to be a pointer value, a char *) of the second element of the first array to the first element of the second array.


So back to filter. There is one caveat shared with qsort, and one problem unique to your function.

Like qsort, you can operate on arrays of arrays. In these instances, care must be taken by the callback function to correctly specify the type passed via void *.

So if you have an array of arrays like

char data[][32] = {
    &quot;foo&quot;,
    &quot;bar&quot;
};

then the callback is actually receiving a char (*)[32] - a pointer to an array. This means a callback function cannot be made to work "generically" on all string containers. char ** and char (*)[N] are not equivalent. An "array of strings" is not always an array of char *.

A unique problem with your filter function is that creating a new array that potentially shares data with the previous array opens up a concern of ownership, and raises a lot of questions:

  • If both arrays contain pointer values returned by malloc (or similar) - which array owner is responsible for passing those pointer values to free?

    • If its the new array owner, how does the the owner of old array know which values not to pass to free?
  • Should the new array be considered a view into the old array?

    • How does the owner of the old array know the new array is finished being used?

I cannot answer these questions, as it is a point of design. Filtering the array in-place, and providing a facility to handle rejected data addresses some problems, but it is a compromise.

Here is a cursory example:

#define _POSIX_C_SOURCE 200809L
#include &lt;ctype.h&gt;
#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;string.h&gt;

static size_t filter(
	void *base,
	size_t nel,
	size_t width,
	int (*fn)(void *),
	void (*on_reject)(void *))
{
	size_t len = 0;

	for (size_t i = 0; i &lt; nel; i++) {
		void *element = (char *) base + i * width;

		if (fn(element))
			memcpy(((char *) base + len++ * width), element, width);
		else if (on_reject)
			on_reject(element);
	}

	return len;
}

static int is_even(void *el)
{
	int *val = el;

	return *val % 2 == 0;
}

static int is_uppercase(void *el)
{
	const char *val = *((char (*)[32]) el);

	if (!*val)
		return 0;

	while (*val)
		if (!isupper((unsigned char) *val++))
			return 0;

	return 1;
}

static int starts_with_ba(void *el)
{
	return 0 == strncmp(*(char **) el, &quot;ba&quot;, 2);
}

static void reject_ba(void *el)
{
	free(*(char **) el);
}

int main(void)
{
	int nums[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	size_t nums_l = filter(nums, sizeof nums / sizeof *nums, sizeof *nums,
			is_even, NULL);

	for (size_t i = 0; i &lt; nums_l; i++)
		printf(&quot;%d\n&quot;, nums[i]);

	char strs[][32] = {
		&quot;hello&quot;,
		&quot;World&quot;,
		&quot;UPPER&quot;,
		&quot;CASE&quot;,
		&quot;no&quot;,
		&quot;lower&quot;,
		&quot;OKAY&quot;
	};

	size_t strs_l = filter(strs, sizeof strs / sizeof *strs, sizeof *strs,
			is_uppercase, NULL);

	for (size_t i = 0; i &lt; strs_l; i++)
		printf(&quot;%s\n&quot;, strs[i]);

	char *data[] = {
		strdup(&quot;foo&quot;),
		strdup(&quot;bar&quot;),
		strdup(&quot;baz&quot;),
		strdup(&quot;qux&quot;)
	};

	size_t data_l = filter(data, sizeof data / sizeof *data, sizeof *data,
			starts_with_ba, reject_ba);

	for (size_t i = 0; i &lt; data_l; i++) {
		puts(data[i]);
		free(data[i]);
	}
}

The output of this program is:

2
4
6
8
UPPER
CASE
OKAY
bar
baz

huangapple
  • 本文由 发表于 2023年5月21日 11:06:43
  • 转载请务必保留本文链接:https://go.coder-hub.com/76298127.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定