Вы можете написать свою функцию сравнения одним из четырех способов (два формально правильных, но все эквивалентные 1 ). Страшный qsort
прототип int compare (const void *a, const void *b)
- это просто способ, которым C передает указатель на элементы сортируемого массива. Ваша задача - просто привести обратно к правильному типу перед сравнением в функции.
Реальный вопрос: "Какой указатель типа передается?
Когда вы отвечаете, что это легко. Вы сортируете двумерный массив по строки. 2D-массив на самом деле является массивом одномерных массивов. Каждый указатель, переданный для сравнения, будет указателем на одномерный массив (строки)
Так что каждый a
и b
будет указатель на массив из int [3]
. В вашей функции сравнения вам нужно будет привести к указателю на массив int [3]
, а затем разыменовать один раз, чтобы оставить тип как int [3]
, а затем сравнить на основе третьего элемент. (но когда вы получаете доступ к вашему текущему типу int [3]
преобразование указателя массива применяется также и к этому массиву, поэтому вам просто нужно добавить 2
к значению вашего указателя и разыменование для сравнения третьим элементом).
Вы можете использовать результаты двух условных тестов, чтобы избежать потенциального переполнения, которое может возникнуть в результате простого возврата b - a
, который, если b
- большое отрицательное число и a
- большое (или b
- большое положительное число и a
(большое отрицательное число) произойдет, если результат превысит объем памяти для int
. Так что вместо этого для сортировки по возрастанию вы можете использовать:
return (a > b) - (a < b);
(для сортировки по убыванию просто переверните сравнения, например, (a < b) - (a > b)
. Попробуйте. Выберите любые два значения для a
и b
и вычислите результат. В возрастающем случае a
меньше b
, вы return 0 - 1;
(или -1
), например, означает a
сортирует до b
)
Так как же выглядит функция сравнения? Поскольку формальный тип для указателя на массив из int [3]
равен int (*)[3]
, вы можете сделать:
int comp (const void *a, const void *b)
{
int *pa2 = *(int (*)[3])a + 2,
*pb2 = *(int (*)[3])b + 2;
return (*pa2 > *pb2) - (*pa2 < *pb2);
}
или если вы хотите просто сделать int pa2
и не беспокоиться о При необходимости разыменования указателя при сравнении, для второго формального приведения вы можете заключить полное приведение a
в круглые скобки, а затем просто использовать (stuff a)[2]
для непосредственного доступа к третьему целочисленному значению - это просто выглядит более запутанным приведение, например,
int comp (const void *a, const void *b)
{
int pa2 = (*(int (*)[3])a)[2],
pb2 = (*(int (*)[3])b)[2];
return (pa2 > pb2) - (pa2 < pb2);
}
(что имеет для вас наибольшее значение)
Тогда сортировка по третьему элементу каждой строки в вашем двумерном массиве действительно тривиальна, например,
#include <stdio.h>
#include <stdlib.h>
int comp (const void *a, const void *b)
{
int *pa2 = *(int (*)[3])a + 2,
*pb2 = *(int (*)[3])b + 2;
return (*pa2 > *pb2) - (*pa2 < *pb2);
}
int main() {
int abc[6][3] ={{0,0,4},
{1,0,3},
{0,1,4},
{1,1,5},
{0,2,3},
{1,2,4}};
qsort (abc, 6, sizeof *abc, comp);
for (int i = 0; i < 6; ++i)
printf(" %2d %2d %2d\n", abc[i][0], abc[i][1], abc[i][2]);
}
Пример использования / вывода
$ ./bin/qsort2d+2
1 0 3
0 2 3
0 0 4
0 1 4
1 2 4
1 1 5
Теперь два последних эквивалентных способа написания функций сравнения основаны на последовательном применении преобразования массива / указателя и понимании того, что указатель на массив и сам массив будут указывать на один и тот же адрес (но формально это разные типы) Формально у вас есть int (*)[3]
, но, поскольку вы знаете, что просто хотите второй int После этого адреса вы можете привести указатель сравнения a
и b
к int *
и добавить два. Это эквивалентно, но не отражает формальный состав. (вы также можете заключить весь состав в скобки и также использовать [2]
).
В этом случае приведение выглядит намного проще, но может быть несколько менее очевидно, что происходит на самом деле. Я оставлю это вам, чтобы попробовать заменить *(int (*)[3])
в полной программе на (int *)
. (это позволит избежать затянувшегося обсуждения, которое будет происходить в комментариях)
Просмотрите все и продумайте оба «Какой указатель типа передается?» и зачем использовать результат двух Сравнение, а не только вычитание в качестве результата в вашей функции сравнения, чтобы избежать переполнения. Сообщите мне, если у вас возникнут дополнительные вопросы.
Сноски:
1.) C11 Стандарт - 6.3.2. 1 Другие операнды - L-значения, массивы и обозначения функций (p3) За исключением случаев, когда это операнд оператора sizeof
, оператор _Alignof
или унарный оператор '&'
или строковый литерал , используемый для инициализации массива, выражение с типом «массив типа» преобразуется в выражение с типом «указатель на тип» , которое указывает на начальный элемент массива объекта и не является lvalue.