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

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

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

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

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

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

Часть Вторая: Почему Указатели?

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

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

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

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

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

4 ответов


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

вы вызываете неопределенное поведение в C. см. C99 6.3.2.3 указатели / 8:

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

в C++, эта программа является некорректным: http://ideone.com/9zRYSj

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

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

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

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

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

  1. int foo(int *a, int *b);
  2. int foo(int a[], int b[])

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

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

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

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


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

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


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

   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];
}

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

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

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

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

здесь comp_qsort_funct_t - Это тип указателя на функцию

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

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

Так что вы не должны использовать другой прототип.


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

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

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

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

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