qsort 结构的多种类型字段的自定义比较函数

Custom compare function for qsort multiple types of fields of a struct

我正在尝试使用 qsort 根据字段对结构数组进行排序。我目前停留在如何创建 qsort 中使用的比较函数上。

结构定义:

struct data{
  unsigned char feet;
  double lock;
  short stall;
  short gain;
  char show;
  char country;
  int king;
  unsigned int bunch;
  short strange;
  long int tiger;
  char win[9];
  float ramp;
  char sea;
  unsigned int test;
  short cave;
  char pound;
  float sofa;
};

排序顺序(按优先顺序):

win in descending order. -
stall in ascending order. -
sea in ascending order. -
pound in ascending order. -
ramp in ascending order. -
test in descending order. -
feet in ascending order. -
show in ascending order. -
sofa in descending order. -
cave in ascending order. -
king in ascending order. -
gain in descending order. -
strange in ascending order. -
country in descending order. -
lock in descending order. -
bunch in ascending order. 
tiger in ascending order. 

这是我试过的比较函数,但我似乎无法得到正确的结果,请根据优先级对所有字段进行排序:

int cmpStructs(const void *a1, const void *a2){
  struct data *a = (struct data *)a1;
  struct data *b = (struct data *)a2;

  if(strcmp(a->win,b->win)>0){
    return -1;
  } else if (strcmp(a->wine,b->wine)<0){
    return 1;
  } else if(a->stall>b->stall){
    return 1;
  } else if(a->stall<b->stall){
    return -1;
  } 
   ///continued if else statements for the rest of the fields

 return 0;
};

好吧,首先,您将所有这些 else if 位过于复杂化了:

int cmpStructs(const void *a1, const void *a2){
    struct data *a = (struct data *)a1;
    struct data *b = (struct data *)a2;

    // win, descending.
    if (strcmp(a->win, b->win) > 0) return -1;
    if (strcmp(a->win, b->win) < 0) return  1;

    // stall, ascending.
    if (a->stall > b->stall) return 1;
    if (a->stall < b->stall) return -1;

    // And so on, for each field.

    return 0;
};

这样,您就可以轻松地注释掉比较“套件”的大块,从而依次测试每个字段。


您还可以考虑使用宏 meta-programming 来使其更容易和更易读,同时减少 error-prone 因为复杂性局限在宏中,例如(未经测试,但你应该明白了):

#define CMP_STR_ASC(s1, s2) \
    if (strcmp(s1, s2) > 0) return -1; \
    if (strcmp(s1, s2) < 0) return  1;
#define CMP_STR_DESC(s1, s2) CMP_STR_ASC(s2, s1)

#define CMP_VAL_ASC(v1, v2) \
    if (v1 > v2) return -1; \
    if (v1 < v2) return 1;
#define CMP_VAL_DESC(v1, v2) CMP_VAL_ASC(v2, v1)

int cmpStructs(const void *a1, const void *a2){
    struct data *a = (struct data *)a1;
    struct data *b = (struct data *)a2;

    // Very simple rule set, based on the macros.

    CMP_STR_DESC ( a->win,     b->win     )
    CMP_VAL_ASC  ( a->stall,   b->stall   )
    CMP_VAL_ASC  ( a->sea,     b->sea     )
    CMP_VAL_ASC  ( a->pound,   b->pound   )
    CMP_VAL_ASC  ( a->ramp,    b->ramp    )
    CMP_VAL_DESC ( a->test,    b->test    )
    CMP_VAL_ASC  ( a->feet,    b->feet    )
    CMP_VAL_ASC  ( a->show,    b->show    )
    CMP_VAL_DESC ( a->sofa,    b->sofa    )
    CMP_VAL_ASC  ( a->cave,    b->cave    )
    CMP_VAL_ASC  ( a->king,    b->king    )
    CMP_VAL_DESC ( a->gain,    b->gain    )
    CMP_VAL_ASC  ( a->strange, b->strange )
    CMP_VAL_DESC ( a->country, b->country )
    CMP_VAL_DESC ( a->lock,    b->lock    )
    CMP_VAL_ASC  ( a->bunch,   b->bunch   )
    CMP_VAL_ASC  ( a->tiger,   b->tiger   )

    return 0;
};

只要你不做一些愚蠢的事情,比如将这些宏包装在没有大括号的 if/while 语句中,或者传入有副作用的参数,它们应该没问题(function-like 宏通常需要如果您希望能够在任何地方使用它们,则可以保护它们,但是,如果您只是将它们用作“独立”语句,则可以使用更简单的语句。


事实上,正是这种对宏的复杂性的隔离使得针对问题进行调整变得容易,例如 DevSolar 在评论中提出的问题(运行 strcmp两次)。

由于隔离,您只需将宏更改为类似以下内容即可轻松解决此问题:

#define CMP_STR_ASC(s1, s2) { \
    int cmpVal = strcmp(s1, s2); \
    if (cmpVal > 0) return -1; \
    if (cmpVal < 0) return  1; \
}