使用void指针和type参数对C中的多个类型进行排序的函数

时间:2019-03-02 03:56:32

标签: c pointers

在此问题中,使用void指向数组的指针,元素数量和指示元素类型的整数的函数应该对数组进行排序。有什么技巧可以避免像下面的解决方案那样重复编写4次相同的代码?

//type 1:short, 2:int, 3:float, 4:double
void Sort(void *values, int nValues, int type) {
    int i, j, temp;

    switch(type) {
    case 1: //short
    {
        short *ptr = (short *)values;
        for (i = 0; i < nValues - 1; i++)
            for (j = i; j < nValues; j++)
                if (ptr[i] > ptr[j]) {
                    temp = ptr[i];
                    ptr[i] = ptr[j];
                    ptr[j] = temp;
                }
        break;
    }
    case 2: // int
    {
        int *ptr = (int *)values;
        for (i = 0; i < nValues - 1; i++)
            for (j = i; j < nValues; j++)
                if (ptr[i] > ptr[j]) {
                    temp = ptr[i];
                    ptr[i] = ptr[j];
                    ptr[j] = temp;
                }
        break;
    }
    case 3: // float
    {
        float *ptr = (float *)values;
        for (i = 0; i < nValues - 1; i++)
            for (j = i; j < nValues; j++)
                if (ptr[i] > ptr[j]) {
                    temp = ptr[i];
                    ptr[i] = ptr[j];
                    ptr[j] = temp;
                }
        break;
    }
    case 4: // double
    {
        double *ptr = (double *)values;
        for (i = 0; i < nValues - 1; i++)
            for (j = i; j < nValues; j++)
                if (ptr[i] > ptr[j]) {
                    temp = ptr[i];
                    ptr[i] = ptr[j];
                    ptr[j] = temp;
                }
    }
    }
}

2 个答案:

答案 0 :(得分:6)

是的,还有另一种选择。在名为qsort的{​​{3}}中(这可能是快速排序的缩写,但也可以是其他任何排序算法)。要使用该函数,请向其传递数组,数组中元素的数量,每个元素的大小以及比较函数。

您只需要为每种数据类型编写比较功能,如该页面上的示例所示。

如果要将4个比较功能合并为一个功能,则必须将一些上下文信息传递给比较功能。在这种情况下,您不能再使用qsort,而必须使用qsort_s。然后您的比较函数可能如下所示:

#define compare(a, b) (((a) > (b)) - ((b) > (a)))
#define compare_ptr(type) (compare(*(type *)(p1), *(type *)(p2)))

static int compare_by_type(const void *p1, const void *p2, void *ctx) {
    int type = *(int *) ctx;
    switch (type) {
    case 1: return compare_ptr(short);
    case 2: return compare_ptr(int);
    case 3: return compare_ptr(float);
    case 4: return compare_ptr(double);
    default: return 0;
    }
}

#undef compare
#undef compare_ptr

int main(void) {
    int iarray[] = {1, 6, 4, 9, 55, 999, -33333};
    int sort_type = 1;

    qsort_s(iarray, 7, sizeof(int), compare_by_type, &type);
}

这是一些相当高级的内容:

  • 传递函数指针
  • 使用指向任意类型的指针做指针的东西
  • 使用接受类型名称作为宏参数的宏
  • 将布尔运算与整数运算混合

但是最后,只要它们支持<运算符,就可以在列表中添加更多类型。

请注意,floatdouble甚至都不属于此类别,因为一旦其中一个数字为{{1},它们的运算符<就会返回false。 },表示“非数字”,它来自NaN之类的表达式。一旦在数组中具有这样的值,该行为就变得不确定。排序功能甚至可能陷入无限循环。要解决此问题,请更改0.0 / 0.0宏的定义:

compare

现在看起来更加复杂,但适用于#define compare(a, b) (((a) > (b)) - !((b) <= (a))) 。例如:

NaN

这意味着NaN将被排序到数组的前面。

compare(NaN, 5)
= (NaN > 5) - !(5 <= NaN)
= false - !(5 <= NaN)
= false - !(false)
= false - true
= 0 - 1
= -1

该死。比较两个NaN应该得出0,表示它们相等。因此,在这种特殊情况下,需要进行更正:

compare(NaN, NaN)
= (NaN > NaN) - !(NaN <= NaN)
= false - true
= -1

由于#define compare(a, b) (((a) > (b)) - ((b) > (a)) - ((a) != (a) || (b) != (b))) 是唯一一个不等于其自身的值,因此此附加代码不会影响整数算术,应由编译器对其进行优化。

答案 1 :(得分:0)

这里有两种不同的方法可以避免代码重复出现:

如果您不能使用库函数或想保留算法,则可以使用预处理器宏来解决此问题:

#define SORT_TYPE(values, nValues, type) do {\
        type *ptr = (type *)(values);        \
        int i, j, n = (nValues);             \
        for (i = 0; i < n - 1; i++) {        \
            for (j = i + 1; j < n; j++) {    \
                if (ptr[i] > ptr[j]) {       \
                    type temp = ptr[i];      \
                    ptr[i] = ptr[j];         \
                    ptr[j] = temp;           \
                }                            \
            }                                \
        }                                    \
    } while (0)

//type 1:short, 2:int, 3:float, 4:double
void Sort(void *values, int nValues, int type) {
    switch (type) {
      case 1: //short
        SORT_TYPE(values, nValues, short);
        break;
      case 2: // int
        SORT_TYPE(values, nValues, int);
        break;
      case 3: // float
        SORT_TYPE(values, nValues, float);
        break;
      case 4: // double
        SORT_TYPE(values, nValues, double);
        break;
    }
}

注意:

  • SORT_TYPE可以被仅具有一次评估的副作用的参数调用,但是它仍然是脆弱的:如果以变量名的形式表示参数,生成的代码将会中断ptrijn。可以通过命名块变量ptr__或其他一些扭曲的方式来尝试减少冲突,但是并不能完全解决问题。编写宏时必须格外注意并小心使用。

  • NaNfloat数组中的
  • double值可能无法正确处理,因为如果一个或两个参数均为NaN,则比较将返回false。使用朴素的冒泡排序算法,它们将保留在原处,这可能是不合适的,也可能是不合适的。在使用其他排序算法或对此算法进行细微改动后,其行为可能会有所不同,甚至可能是不确定的。

  • 您的冒泡排序实现应使用i = j + 1来获得更好的性能。

  • 气泡排序算法对于时间复杂度为 O(N 2 的大型阵列而言效率非常低。

这是一种更有效的方法,其中将排序算法留给C库函数qsort,并为每种类型编写特定的比较函数:

int shortCmp(const void *aa, const void *bb) {
    short a = *(const short *)aa;
    short b = *(const short *)bb;
    return (b < a) - (a < b);
}

int intCmp(const void *aa, const void *bb) {
    int a = *(const int *)aa;
    int b = *(const int *)bb;
    return (b < a) - (a < b);
}

int floatCmp(const void *aa, const void *bb) {
    float a = *(const float *)aa;
    float b = *(const float *)bb;
    if (a != a || b != b) {
        /* sort NaN values to the end of the array */
        return (a != a) - (b != b);
    }
    return (b < a) - (a < b);
}

int doubleCmp(const void *aa, const void *bb) {
    double a = *(const double *)aa;
    double b = *(const double *)bb;
    if (a != a || b != b) {
        /* sort NaN values to the end of the array */
        return (a != a) - (b != b);
    }
    return (b < a) - (a < b);
}

//type 1:short, 2:int, 3:float, 4:double
void Sort(void *values, int nValues, int type) {
    switch (type) {
      case 1: //short
        qsort(values, nValues, sizeof(short), shortCmp);
        break;
      case 2: // int
        qsort(values, nValues, sizeof(int), intCmp);
        break;
      case 3: // float
        qsort(values, nValues, sizeof(float), floatCmp);
        break;
      case 4: // double
        qsort(values, nValues, sizeof(double), doubleCmp);
        break;
    }
}