6

有以下声明:

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变量前括号中的类型将变量提升为该类型)。

所以我想我弄错了,也许有人可以告诉我如何阅读这个,顺序是什么?

4

9 回答 9

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);
于 2009-04-16T15:09:10.810 回答
5

这里发生的事情确实是一个演员表。让我们暂时忽略三进制并假设始终使用 numcmp。出于这个问题的目的,函数可以充当 C 中的函数指针。因此,如果您查看 numeric 的类型,它实际上是

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

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

(int (*)(void*,void*))(numcmp )
于 2009-04-16T15:09:21.497 回答
5

正如其他人指出的那样,对于

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

那么以下是类型转换

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

表达式是

(numeric ? numcmp : strcmp)

C 声明可能很难阅读,但可以学习。方法是从内部开始,然后向右走一步,然后向左走一步,向外继续向右,向左,向右,向左等直到完成。在对括号内的所有内容进行评估之前,您不能越过括号外。例如对于上面的类型转换,(*)表示这是一个指针。指针是括号内唯一的东西,所以我们评估它外面的右侧。(void*,void*)表示它是一个指向具有两个指针参数的函数的指针。最后int表示函数的返回类型。外括号使其成为类型转换。更新:两篇更详细的文章:顺时针/螺旋规则阅读 C 声明:迷惑者指南.

然而,好消息是,虽然上面的知识非常有用,但有一个非常简单的作弊方法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>
于 2009-04-16T16:54:54.303 回答
4

您可以在没有函数指针转换的情况下执行此操作。方法如下。以我的经验,在大多数地方,如果你使用演员表,你就做错了。

于 2009-04-16T15:17:13.947 回答
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, ...);

就个人而言,我发现显式取消引用更清晰,但并非所有人都同意。

于 2009-04-16T16:02:47.363 回答
3

编写该代码片段的人试图过于聪明。在他的脑海中,他可能认为通过制作一个聪明的“单线”,他正在成为一名优秀的程序员。实际上,他正在编写的代码可读性较差,并且长期使用起来令人讨厌,应该以更明显的形式重写,类似于 Harper Shelby 的代码。

记住 Brian Kernighan 的格言:

首先,调试的难度是编写代码的两倍。因此,如果您尽可能巧妙地编写代码,那么根据定义,您还不够聪明,无法对其进行调试。


我在硬实时截止日期的情况下进行了大量性能关键编码......我仍然没有看到适合密集单线的地方。

我什至弄乱了编译和检查 asm 以查看 one-liner 是否具有更好的编译 asm 实现,但从未发现 one-liner 值得。

于 2009-04-16T16:56:49.137 回答
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);

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

于 2009-04-16T15:56:16.350 回答
0

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

于 2009-04-16T15:07:59.517 回答
0

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

 int strcmp(char *, char *);

编译器strcmp实际上按原样读取它:

 int (strcmp)(char *, char *)

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

 int (*)(char *, char *)

因此,当您需要转换另一个函数以与您兼容时,strcmp您将使用上述作为转换为的类型

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

于 2009-04-16T15:13:55.057 回答