Функция сравнения Qsort

Я начинаю с C, и я пытаюсь понять функцию сравнения, необходимую для функции qsort.

Часть первая: синтаксис

Простым предлагаемым использованием является это (я также включил некоторый код main() для печати результатов):

#include <stdio.h>
#include <stdlib.h>

int values[] = { 40, 10, 100, 90, 20, 25, 12, 13, 10, 40 };

int compare (const void *a, const void *b)
{
    const int *ia = (const int *)a; // casting pointer types 
    const int *ib = (const int *)b;
    return *ia  - *ib; 
}

int main ()
{
    int n;
    for (n=0; n<10; n++)
    {
        printf("%d ",values[n]);
    }
    printf("\n");
    qsort (values, 10, sizeof(int), compare);
    for (n=0; n<10; n++)
    {
        printf ("%d ",values[n]);
    }
    printf("\n");
    system("pause");
    return 0;
}

Я не понимаю, зачем вам нужны все лишние вещи в функции сравнения, поэтому я упростил это:

int compare (int *a, int *b)
    {
        return *a-*b; 
    }

Это все еще работает и дает те же результаты. Может ли кто-нибудь объяснить мне, что я удалил, и почему он все еще работает?

ЧАСТЬ 2: Почему указатели?

Кроме того, действительно ли нужно использовать указатели? Почему я не могу просто сравнить "a" и "b" прямо так (это НЕ работает):

int compare (int a, int b)
        {
            return a-b; 
        }

По какой-то причине, с многомерным массивом, я смог уйти с НЕ с помощью указателей, и по какой-то причине он сработает! что происходит! (Пример кода сортировки многомерного массива по 2-му элементу в каждом вспомогательном массиве):

#include <stdio.h>
#include <stdlib.h>

int values[7][3] = { {40,55}, {10,52}, {100,8}, {90,90}, {20,91}, {25,24} };

int compare (int a[2], int b[2])
{
    return a[1] - b[1];
}

int main ()
{
    int n;
    for (n=0; n<6; n++)
    {
        printf ("%d,",values[n][0]);
        printf ("%d ",values[n][1]);
    }
    printf("\n");
    qsort (values, 6, sizeof(int)*3, compare);
    for (n=0; n<6; n++)
    {
        printf ("%d,",values[n][0]);
        printf ("%d ",values[n][1]);
    }
    printf("\n");
    system("pause");
    return 0;
}

Я очень рад, что многомерная сортировка массивов работает, так как это моя конечная цель , но я не знаю, как мне удалось заставить ее работать (кроме немой удачи и измельчения код), поэтому мне действительно понравилось бы объяснение того, почему некоторые из примеров, которые я предоставил, работают, и почему некоторые не делают этого! Спасибо!

Ответ 1

Это все еще работает и дает те же результаты. Может ли кто-нибудь объяснить мне, что я удалил, и почему он все еще работает?

Вы вызываете поведение undefined в C. См. C99. 6.3.2.3 Указатели /8:

Указатель на функцию одного типа может быть преобразован в указатель на функцию другого тип и обратно; результат сравнивается с исходным указателем. Если преобразованный указатель используется для вызова функции, тип которой несовместим с указанным типом, поведение undefined.

В С++ эта программа плотно сформирована: http://ideone.com/9zRYSj

Он по-прежнему "работает", потому что функция compare ожидает пару указателей; и на вашей конкретной платформе sizeof(void*) совпадает с sizeof(int*), поэтому вызов указателя функции типа int(void *, void *), который на самом деле содержит указатель на функцию типа int(int *, int *), фактически тот же, что и тип указателя вашей конкретной платформы в этот конкретный момент времени.

Кроме того, действительно ли нужно использовать указатели? Почему я не могу просто сравнить "a" и "b" прямо так (это НЕ работает):

Потому что qsort берет общую функцию сравнения для любых двух типов; а не только int. Таким образом, он не знает, к какому типу, по которому указатель разыменован.

По какой-то причине, с многомерным массивом, я смог уйти с НЕ с помощью указателей, и по какой-то причине он сработает! что происходит!

Это связано с тем, что следующие прототипы одинаковы:

  • int foo(int *a, int *b);
  • int foo(int a[], int b[])

То есть, массив переходит в указатель при передаче функции. Явное указание длины массива, как вы это делали:

int foo(int a[2], int b[2])

заставляет компилятор делать sizeof и другие биты времени компиляции для обработки элемента как массива из двух элементов; но функция по-прежнему принимает пару указателей, когда она переходит на уровень машины.

В любом из этих случаев передача функции сравнения, которая не принимает пару void *, приводит к поведению undefined. Один из действительных результатов "undefined поведения" - это "похоже, что это работает". Другим допустимым результатом будет "работает по вторникам" или "форматирует жесткий диск". Не полагайтесь на это поведение.

Ответ 2

Я не понимаю, зачем вам все лишние вещи в сравнении, поэтому я упростил это для этого

Используете ли вы const квалификатор для вас. Потому что вы не должны изменять значения в компараторе. Но можно отбросить const и сломать обещание, которое вы приложите к компилятору.


qsort ожидает указатель на функцию, который принимает два const void * в качестве параметров, поэтому передаются указатели для функции компаратора:

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

Таким образом, передача a и b приведет к интерпретации значений в качестве указателей, что очевидно неверно.


Он работает без указания указателей на многомерные массивы, потому что, когда вы передаете массивы, они распадаются на указатели. Итак, следующий компаратор у вас в порядке.

int compare (int a[2], int b[2])
{
    return a[1] - b[1];
}

Ответ 3

Ответ очень прост: из руководства qsort http://pubs.opengroup.org/onlinepubs/009695399/functions/qsort.html прототип указателя функции:

int comp(const void *a, const void *b)

Типед, связанный с этим прототипом, может быть объявлен таким образом

typedef int (*comp_qsort_funct_t) ( const void *, const void * )

Где comp_qsort_funct_t - тип указателя функции

Прототипом функции сравнения qsort является использование переменной const, поскольку это хорошая практика/дизайн, поскольку данные не будут изменены.

Использование другого прототипа может привести к неожиданному результату в зависимости от компилятора/платформы. Или просто не скомпилировать, пример приведен в комментарии: http://ideone.com/9zRYSj

Поэтому вы не должны использовать другой прототип.

Ответ 4

Я хотел бы подтвердить наблюдения Длинета, который задал первоначальный вопрос.

Компилятор C действительно примет все формы функции сравнения, даже без const, и даже используя int * вместо void *. Однако мой компилятор С++ отклоняет варианты и принимает форму const void *.

Компилятор C даже принимает форму int, не используя указатели. Я проверил и обнаружил, что int form функции сравнения принимает значения указателя, а не ints массива. И результат состоит в том, что массив не отсортирован, а остается в том же порядке. И я думаю, что все это то, что вы ожидаете.

Таким образом, кажется, что вы можете использовать int * вместо void * в определении функции, а затем не нужно вводить внутри функции.

Является ли это хорошая практика программирования является спорным, с некоторыми программисты говорят да, некоторые говорят нет. Кажется мне, хорошее программирование или нет, сокращение беспорядка было бы точкой в ​​пользу использования int * вместо void *. Но тогда компилятор С++ не позволяет вам это делать.