关于C中函数指针的一个问题

时间:2009-04-16 15:01:27

标签: c pointers casting function-pointers

有以下声明:

void qsort(void *lineptr[], int left, int right, int (*comp)(void *, void *));
int numcmp(char *, char *);
int strcmp(char *s, char *t);

然后,在程序的某个地方有以下调用:

  qsort((void**) lineptr, 0, nlines-1, 
                    (int (*)(void*,void*))(numeric ? numcmp : strcmp));

(忽略前三个参数和numeric)。

我问这是什么:

(int (*)(void*,void*))(numeric ? numcmp : strcmp)

我理解qsort期待一个“指向函数的指针获得两个void指针并返回一个int”,因为它是第四个参数,但上面的内容是如何满足的? 在我看来,它似乎是某种类型的演员,因为它是由两个括号组成的,但那将是一个非常奇怪的演员。因为它需要一个函数并使该函数成为“指向函数的指针,该指针获得两个void指针并返回int”。这没有意义。
(我在这里遵循的规则是,在变量将变量提升为该类型之前,括号中的 type 类型。)

所以我想我错了,也许有人可以告诉我怎么读这个,订单是什么?

9 个答案:

答案 0 :(得分:5)

你错过了这里的诀窍 - 部分

(numeric ? numcmp : strcmp)

使用三元运算符选择在qsort内调用哪个函数。如果数据是数字,则使用numcmp。如果没有,它使用strcmp。更具可读性的实现如下所示:

int (*comparison_function)(void*,void*) = 
    (int (*)(void*,void*))(numeric ? numcmp : strcmp);
qsort((void**) lineptr, 0, nlines-1, comparison_function);

答案 1 :(得分:4)

这里发生的事情确实是演员阵容。让我们忽略三元并假装总是使用numcmp。出于这个问题的目的,函数可以充当C中的函数指针。因此,如果你看一下数字的类型,它实际上是

(int (*)(int*,int*))

为了在qsort中正确使用它,它需要有void参数。因为这里的类型在参数和返回类型方面都具有相同的大小,所以可以替换另一个类型。所需要的只是一个使编译器满意的演员表。

(int (*)(void*,void*))(numcmp )

答案 2 :(得分:4)

您可以在不使用函数指针的情况下执行此操作。这是how。根据我的经验,在大多数地方,如果你使用的是演员,那你就错了。

答案 3 :(得分:3)

请注意qsort()的标准定义包括const

void qsort(void *base, size_t nmemb, size_t size,
           int (*compar)(const void *, const void *));

请注意,字符串比较器有两个“char **”值,而不是“char *”值。

我编写比较器,以便在调用代码中不需要强制转换:

#include <stdlib.h>    /* qsort() */
#include <string.h>    /* strcmp() */

int num_cmp(const void *v1, const void *v2)
{
    int i1 = *(const int *)v1;
    int i2 = *(const int *)v2;
    if (i1 < i2)
        return -1;
    else if (i1 > i2)
        return +1;
    else
        return 0;
}

int str_cmp(const void *v1, const void *v2)
{
    const char *s1 = *(const char **)v1;
    const char *s2 = *(const char **)v2;
    return(strcmp(s1, s2));
}

强迫人们使用你的函数在代码中编写强制转换是很难看的。不。

我写的两个函数匹配标准qsort()所需的函数原型。未跟随括号的函数名称相当于指向函数的指针。

您可以在较旧的代码中找到代码,或者由那些在较旧的编译器上编写的代码编写的代码,使用符号来使用指向函数的指针:

result = (*pointer_to_function)(arg1, arg2, ...);

以现代风格写成:

result = pointer_to_function(arg1, arg2, ...);

就个人而言,我发现明确的解除引用更清楚,但不是每个人都同意。

答案 4 :(得分:3)

正如其他人所指出的那样,

(int (*)(void*,void*))(numeric ? numcmp : strcmp)

然后以下是类型转换

(int (*)(void*,void*))

,表达式为

(numeric ? numcmp : strcmp)

C声明可能很难阅读,但可以学习。方法是从内部开始,然后向右一步,然后向左一步,向右,向左,向右,向左等,直到完成。在评估内部的所有内容之前,不要在括号外穿过。例如,对于上面的类型转换,(*)表示这是一个指针。指针是括号内唯一的东西,所以我们在它之外的右侧进行评估。 (void*,void*)表示这是一个指向具有两个指针参数的函数的指针。最后int表示函数的返回类型。外括号使其成为类型转换。 更新:两篇更详细的文章:The Clockwise/Spiral RuleReading C Declarations: A Guide for the Mystified

然而,好消息是虽然以上内容非常有用,但有一种非常简单的欺骗方式 cdecl 程序可以从C转换为英文描述,反之亦然:

cdecl> explain (int (*)(void*,void*))
cast unknown_name into pointer to function (pointer to void, pointer to void) returning int
cdecl> declare my_var as array 5 of pointer to int
int *my_var[5]
cdecl>

练习:i是什么类型的变量?

int *(*(*i)[])(int *)

如果您的计算机上没有安装cdecl,请在rot13中回答(但您真的应该这样做):

pqrpy> rkcynva vag *(*(*v)[])(vag *)
qrpyner v nf cbvagre gb neenl bs cbvagre gb shapgvba (cbvagre gb vag) ergheavat cbvagre gb vag
pqrpy>

答案 5 :(得分:3)

编写该代码段的人试图过于聪明。在他看来,他可能认为自己是一个优秀的程序员,通过制作一个聪明的“一线”。 实际上,他制作的代码可读性较差且长期使用是令人讨厌的,应该以类似于Harper Shelby代码的更明显的形式进行重写。

记住Brian Kernighan的格言:

  

调试是写入的两倍   代码首先。   因此,如果您将代码编写为   尽可能巧妙地,你是   定义,不够智能调试   它


我在硬实时期限内做了很多性能关键编码......而且我还没有看到一个密集的单线程适合的地方。

我甚至搞砸了编译和检查asm以查看单行程序是否有更好的编译asm实现,但从未发现单行程值得它。

答案 6 :(得分:1)

我可能会这样读:

typedef int (*PFNCMP)(void *, void *);

PFNCMP comparison_function;

if (numeric)
{
    comparison_function =  numcmp;
}
else
{
    comparison_function = strcmp;
}

qsort((void**) lineptr, 0, nlines-1, comparison_function);

问题中的示例有一个明确的案例。

答案 7 :(得分:0)

我认为你的逻辑是正确的。它实际上是“指向函数的指针,它获取两个void指针并返回一个int”,这是方法签名所需的类型。

答案 8 :(得分:0)

numcmpstrcmp都是指向函数的指针,这些函数将两个char*作为参数并返回intqsort例程需要一个指向函数的指针,该函数将两个void*作为参数并返回int。因此演员。这是安全的,因为void*充当通用指针。现在,继续阅读声明:让我们来你的 strcmp的声明:

 int strcmp(char *, char *);

编译器将其读取为strcmp实际上是:

 int (strcmp)(char *, char *)

一个函数(在大多数情况下衰减到指向函数的指针),它带有两个char *个参数。因此指针strcmp的类型为:

 int (*)(char *, char *)

因此,当您需要转换另一个与strcmp兼容的函数时,您可以使用上面的类型来转换为。

同样,由于qsort的比较器参数需要两个void * s,因而是奇数演员!