Вопрос

Есть следующие объявления:

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 в скобках перед тем, как переменная преобразует переменную в этот тип).

Думаю, я просто ошибаюсь, может быть, кто-нибудь подскажет мне, как это читать, в каком порядке?

Это было полезно?

Решение

То, что здесь происходит, действительно является актерским составом.Давайте на секунду проигнорируем троичный код и представим, что numcmp используется всегда.Для целей этого вопроса функции могут выступать в качестве указателей на функции в C.Итак, если вы посмотрите на тип числового значения, на самом деле это

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

Чтобы это можно было правильно использовать в qsort, у него должны быть параметры void.Поскольку все типы здесь имеют одинаковый размер относительно параметров и возвращаемых типов, можно заменить на другой.Все, что нужно, — это приведение типов, чтобы компилятор был доволен.

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

Другие советы

Здесь вы упустили трюк - порцию

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

Вы можете сделать это без приведения указателя функции.Вот как.По моему опыту, в большинстве случаев, если вы используете приведение, вы делаете это неправильно.

Обратите внимание, что стандартное определение 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, ...);

Лично я считаю явное разыменование более понятным, но не все с этим согласны.

Тот, кто написал этот фрагмент кода, пытался быть слишком умным..По его мнению, он, вероятно, думает, что становится хорошим программистом, создавая умную «остроту». На самом деле он создает код, который менее читаем и с ним неудобно работать в долгосрочной перспективе. и его следует переписать в более очевидной форме, аналогичной коду Харпера Шелби.

Помните пословицу Брайана Кернигана:

Отладка в два раза тяжелее, чем написание кода в первую очередь.Поэтому, если вы пишете код как можно более умно, вы, по определению, недостаточно умны, чтобы отлаживать его.


Я пишу много критически важного для производительности кода с жесткими сроками в реальном времени...и я до сих пор не видел места, где уместна плотная однострочница.

Я даже возился с компиляцией и проверкой ассемблера, чтобы увидеть, имеет ли однострочник лучшую реализацию скомпилированного ассемблера, но никогда не считал, что однострочный вариант того стоит.

Как отмечали другие, для

(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 *)

Ответить в гниль13 в случае, если на вашем компьютере не установлен cdecl (но вам действительно следует это сделать!):

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

Я бы, наверное, прочитал это так:

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

Пример в вопросе имеет явный случай.

Ваша логика, я думаю, верна.Это действительно приведение к «указателю на функцию, которая получает два указателя void и возвращает int», который является требуемым типом по сигнатуре метода.

Оба numcmp и strcmp являются указателями на функции, которые принимают два char* в качестве параметров и возвращает intqsort подпрограмма ожидает указатель на функцию, которая принимает два void* в качестве параметров и возвращает int.Отсюда и актерский состав.Это безопасно, поскольку void* действует как общий указатель.Теперь перейдем к чтению декларации:Давайте твой strcmpобъявление:

 int strcmp(char *, char *);

Компилятор читает это как strcmp на самом деле:

 int (strcmp)(char *, char *)

функция (в большинстве случаев распадающаяся до указателя на функцию), которая занимает два char * аргументы.Тип указателя strcmp следовательно является:

 int (*)(char *, char *)

Следовательно, когда вам нужно привести другую функцию, совместимую с strcmp вы бы использовали приведенное выше в качестве тип для приведения.

Аналогично, поскольку qsortаргумент компаратора занимает два void *и, следовательно, странный состав!

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top