Pergunta

Há as seguintes declarações:

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

Então, em algum lugar no programa, há a seguinte chamada:

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

(Ignorar os três primeiros argumentos e numeric).

Eu pergunto o que é isso:

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

Eu entendo que qsort está esperando um "ponteiro para função que recebe dois ponteiros void e retorna um int" como é quarto argumento, mas como o que está escrito acima satisfaz isso? Parece-me como uma espécie de elenco porque é feito de dois parênteses, mas isso seria um elenco muito estranho. Porque é preciso uma função e faz esta função um "ponteiro para função que recebe dois ponteiros void e retorna um int". Que é sem sentido.
(I seguido aqui a regra de que um tipo type entre parênteses antes de uma variável promove a variável a que tipo).

Então eu acho que eu só entendi errado, talvez alguém pode me dizer como ler isso, qual é a ordem?

Foi útil?

Solução

O que está acontecendo aqui é realmente um elenco. Permite ignorar o ternário por um segundo e fingir que numcmp é sempre usado. Para efeitos desta questão, as funções podem atuar como ponteiros de função em C. Então, se você olhar para o tipo de numérico é realmente

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

Para que isso seja adequadamente utilizado em qsort ele precisa ter parâmetros vazios. Porque os tipos aqui todos têm o mesmo tamanho com relação a parâmetros e tipos de retorno, é possível substituir por outro. Tudo o que é necessário é um elenco para tornar o feliz compilador.

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

Outras dicas

Você perdeu o truque aqui - a parte

(numeric ? numcmp : strcmp)

está usando o operador ternário para escolher que função está sendo chamado dentro de qsort. Se os dados forem numéricos, ele usa numcmp. Se não, ele usa strcmp. Uma implementação mais legível ficaria assim:

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

Você pode fazê-lo sem o elenco ponteiro de função. Aqui está como . Na minha experiência, na maioria dos lugares, se você estiver usando um elenco, você está fazendo errado.

Note que a definição padrão de qsort() inclui const:

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

Note que o comparador corda é dada dois valores 'char **', e não os valores dos char * '.

Eu escrevo meus comparadores de modo que os moldes são desnecessárias no código de chamada:

#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));
}

forçando as pessoas a moldes de escrita no código usando suas funções é feio. não.

As duas funções que eu escrevi coincidir com o protótipo da função exigida pelo qsort() padrão. O nome de uma função quando não seguido por parênteses é equivalente a um ponteiro para a função.

Você vai encontrar no código antigo, ou código escrito por aqueles que foram trazidos em compiladores mais antigos, que ponteiros para funções são utilizados usando a notação:

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

Em estilo moderno, o que está escrito:

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

Pessoalmente, acho que o dereference explícita mais clara, mas nem todos concordam.

Quem escreveu que trecho de código estava tentando ser muito inteligente . Em sua mente, ele provavelmente pensa que ele está sendo um bom programador, fazendo um inteligente "one-liner". Na realidade, ele está fazendo um código que é menos legível e é desagradável para trabalhar com a longo prazo e deve ser reescrito de uma forma mais óbvia semelhante ao código de Harper Shelby.

Lembre-se do ditado de Brian Kernighan:

A depuração é duas vezes mais difícil como a escrita o código em primeiro lugar. Portanto, se você escrever o código como inteligentemente quanto possível, você é, por definição, não é inteligente o suficiente para depurar -lo.


Eu faço um monte de desempenho crítico codificação com prazos tempo real hard ... e eu ainda não vi um lugar onde um one-liner densa é apropriado.

Eu até brinquei com compilação e verificação da asm para ver se o one-liner tem uma implementação asm melhor compilado mas nunca ter encontrado o one-liner para valer a pena.

Como os outros têm para fora pontas, para

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

, em seguida, o seguinte é um tipo cast

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

e a expressão é

(numeric ? numcmp : strcmp)

declarações C pode ser bastante difícil de ler, mas é possível aprender. O método é começar na parte interior e, em seguida, ir passo um direito, em seguida, deixado um passo, continua para fora para a direita, esquerda, direita, esquerda, etc até terminar. Você não atravessar fora um parêntese antes de tudo dentro foi avaliada. Por exemplo, para o tipo fundido acima, (*) indicar que isto é um apontador. Ponteiro foi a única coisa dentro do parêntese assim, então nós avaliamos para o lado direito fora dela. (void*,void*) indica que é um ponteiro para uma função com dois argumentos de ponteiro. Finalmente int indica o tipo de retorno da função. O parêntesis exterior faz com que este tipo de fundido. Update: dois artigos mais detalhados: Regra no sentido horário / espiral e Leitura C declarações:. Um Guia para o Mystified

No entanto, a boa notícia é que, embora o acima é extremamente útil saber, há uma maneira extremamente simples de fraude : cdecl programa pode converter de C para Inglês descrição e vice-versa:

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>

Exercício:? Que tipo de variável é i

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

Resposta em rot13 no caso de você não ter cdecl instalado em sua máquina (mas você realmente deveria!):

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

Eu provavelmente iria lê-lo como este:

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);

O exemplo a questão tem um caso explícito.

A sua lógica é correta que eu penso. Na verdade, é a projeção para "ponteiro para função que recebe dois ponteiros void e retorna um int", que é o tipo exigido pela assinatura do método.

Ambos numcmp e strcmp são ponteiros para funções que levam dois char* como parâmetros e retorna um int. A rotina qsort espera um ponteiro para uma função que leva dois void* como parâmetros e retorna um int. Daí o elenco. Isto é seguro, desde void* atua como um ponteiro genérico. Agora, sobre a leitura da declaração: take Vamos o A declaração de strcmp:

 int strcmp(char *, char *);

O compilador lê-lo como strcmp é realmente:

 int (strcmp)(char *, char *)

uma função (decaindo para um ponteiro para uma função na maioria dos casos), que leva dois argumentos char *. O tipo de strcmp ponteiro é, portanto:

 int (*)(char *, char *)

Assim, quando você precisa lançar uma outra função para ser compatível com strcmp você usaria o acima como o tipo para lançar a.

Da mesma forma, uma vez que argumento comparador de qsort leva dois void *s e, assim, o elenco estranho!

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top