Функция сравнения 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 *. Но тогда компилятор С++ не позволяет вам это делать.