c - 使用MACRO摇动排序

标签 c sorting vector macros shake

摇一下向量:
该程序有效,但是:

我试图使用相同的功能进行气泡上升和气泡下降进行摇动排序(气泡上升将MAX值移至右侧,气泡下降将min值移至左侧)。为了做到这一点,我试图使用以下不编译的宏:

符号是“ +”,而操作符是“>”表示气泡

符号为“-”,操作符为“ <”表示冒泡

冒泡-

start是迭代器i(迭代到Vector索引)

末端是n-1-i;

减少气泡-
交换开始和结束值

#define bubble_up_down(var_t, pVector, _Is_swp, start, end, sign, oper)\
{\
    var_t current_index;\
    var_t current_val;\
    var_t next_val;\              
    for (current_index = *(start) ; current_index (oper) *(end) ; (sign)(sign)current_index){\
    {\
        VectorGet((pVector), current_index, &current_val);\
        VectorGet((pVector), current_index(sign)1, &next_val);\
        if(current_val (oper) next_val)\
        {\
            VectorSet((pVector), current_index, next_val);\
            VectorSet((pVector), current_index(sign)1, current_val);\
            *(_Is_swp) = 1;\
        }\
    }\
}


需要您的建议来修复此宏。

最佳答案

现在还不清楚为什么要在这里使用宏。您要避免重复代码吗?还是要使您的排序例程类型独立?

无论如何,您的宏有几个错误:


您可能已经读过,应该用括号来保护宏参数。通常这是一个很好的建议,因为宏是文本的替换;例如,臭名昭著的SQ(x + 1)将解析为x + 1*x + 1。您的建议是错误的。在代码中,您会得到语法错误的“运算符”,例如(-)(<)。只需使用signoper
即使这样,sign sign仍将解析为- -+ +,这不是您想要的。您可以将i++重写为同样有效的i = i + 1,也可以使用令牌粘贴运算符sign##sign,该运算符将生成--++
宏不是功能。您可能要在函数内调用宏。调用宏时作用域内的所有局部变量也都在宏作用域内。这意味着可能不需要定义所有这些指针。
为什么要传递数组元素类型var_t?我认为SetVectorGetVector不是宏,因此类型独立性下降了。
如果var_t是数组元素的类型,则索引不一定是同一类型;它应该是整数类型。 (您的元素必须与<运算符具有可比性,因此它是算术类型之一,但是如果您有一个char数组长于256个元素,会发生什么情况?)
如果元素是算术类型,则可能不需要GetValueSetValue调用。您可以使用=运算符分配值。


所有这些使我认为您真的不知道自己在做什么。加上已知的宏的陷阱和缺点是在此处不使用任何宏的充分理由。



附录中,PO表示,宏应该实现两件事:应避免重复代码,并且应使排序与数组元素的类型无关。这是两件事。

编写简短的局部宏以避免重复代码可能是一种有用的技术,特别是在代码需要在多个位置使变量保持同步的情况下。在您的情况下有用吗?

因此,您有了向上冒泡的代码:

int done = 0;

while (!done) {
    done = 1;

    for (int i = 1; i < n; i++) {
        if (a[i - 1] > a[i]) {
            swap(a, i - 1, i);
            done = 0;
        }
    }
}


(这使用一个swap函数交换两个数组元素。它比您的版本更直接,因为它不使用get / set访问器函数。)现在,您编写向下冒泡的副本:

while (!done) {
    done = 1;

    for (int i = n - 1; i > 0; i--) {
        if (a[i - 1] > a[i]) {
            swap(a, i - 1, i);
            done = 0;
        }
    }
}


这两个片段仅在循环控制方面有所不同。两者都访问从1到n - 1的所有索引。因此,您的宏需要传递起始值和结束值。但是它还需要知道比较的方向(小于或大于)以及是增加还是减少索引。这是一个简单循环的四个数据。

您可以尝试摆脱比较,并在两个方向上都使用!=。但是,如果数组为空,则循环将失败。

当您使用无符号整数作为索引时,上述向后循环在空数组上将已经失败。向前和向后lop不对称,因为下限和上限也不对称:下限始终是包含的,上限始终是排除的。这个正向循环:

for (unsigned int i = 0; i < n; i++) ...


具有以下向后等效项:

for (unsigned int i = n; i-- > 0; ) ...


在此,条件发生递减,并且更新部分为空。优点是,它逐字使用完全相同的边界0n,但是通过在进入循环主体之前递减,可以访问相同的有效范围,从0n - 1。它与unsigned int一起使用,这是循环变量的自然选择。

简而言之:C语言中的前向和后向循环是不对称的,因此为它们编写宏并不容易。 C的语法比for i = 1 to n更为冗长,但是事实就是如此。拥抱它并通过选择适当的索引名来减轻打字的麻烦:它是i,而不是current_index

如果没有宏,是否可以减少代码的冗余性?当然:您可以编写两个用于向上和向下冒泡的函数:

static int bubble_up(int a[], int n)
{
    int done = 1;

    for (int i = 1; i < n; i++) {
        if (a[i - 1] > a[i]) {
            swap(a, i - 1, i);
            done = 0;
        }
    }

    return done;
}

static int bubble_down(int a[], int n)
{
    int done = 1;

    for (int i = n; i-- > 1; ) {
        if (a[i - 1] > a[i]) {
            swap(a, i - 1, i);
            done = 0;
        }
    }

    return done;
}


(这些函数是static,即当前编译单元的私有函数。)现在,您实际的排序函数如下所示:

void sort_bubble_up(int a[], int n)
{
    int done = 0;

    while (!done) {
        done = bubble_down(a, n);
    }
}

void sort_bubble_down(int a[], int n)
{
    int done = 0;

    while (!done) {
        done = bubble_down(a, n);
    }
}

void sort_shaker(int a[], int n)
{
    int done = 0;

    while (!done) {
        done = bubble_up(a, n) || bubble_down(a, n);
    }
}


如果您不害怕空循环的主体,甚至可以将它们简化为:

void sort_bubble_up(int a[], int n)
{
    while (bubble_down(a, n)) { }
}

void sort_bubble_down(int a[], int n)
{
    while (bubble_down(a, n)) { }
}

void sort_shaker(int a[], int n)
{
    while (bubble_up(a, n) || bubble_down(a, n)) { }
}


但是,所有这些代码仅适用于int数组。标准库实现类型独立性的方法是通过void *指针和用户定义的比较函数在字节级别上工作。例如,排序功能qsort执行此操作。

C ++和其他语言都有模板,您可以在其中编写几种类型的算法。当“实例化”模板时,编译器仅为此类型创建一个函数,然后将其调用。

您可以使用宏来模拟。如果只想在函数主体中调用宏,则可以定义:

#define BUBBLE_SORT(ARRAY, N, TYPE) do {         \
        int done = 0;                            \
        int i;                                   \
                                                 \
        while (!done) {                          \
            done = 1;                            \
                                                 \
            for (i = 1; i < N; i++) {            \
                if (ARRAY[i - 1] > ARRAY[i]) {   \
                    TYPE sawp = ARRAY[i];        \
                                                 \
                    ARRAY[i] = ARRAY[i - 1];     \
                    ARRAY[i - 1] = swap;         \
                    done = 0;                    \
                }                                \
            }                                    \
        }                                        \
    } while (0)


然后像这样使用宏:

char c[] = "Mississippi";

BUBBLE_SORT(c, strlen(c), char);


(围绕thze宏的do { ... } while (0)事物使宏的行为类似于函数调用。循环体的新作用域允许使用局部变量。)

这里的问题是这样的多行宏很难调试。当正文中有错误时,您只需获取错误消息中调用宏的行号即可。 (但是您可以在大多数编译器中使用-E来查看预处理器如何解析该宏。)

结论:


宏可能很有用,但是您必须知道自己在做什么。通常,请尽量避免使用它们,因为它们很难调试,并且通常很难被他人理解。 (另一个人可能半年后才是您。)
如果必须使用宏,请尝试使其看起来尽可能自然。像>+这样的传递运算符应该使您警惕。
对于通用代码,请使用函数而非宏。
拥抱C处理不同类型的方法。学习qsort的工作方式(而不是花很多时间)比摆弄泡沫实现排序的宏更有用。
如果您确实需要编写很多与类型无关的代码,则可能不应该使用C。

关于c - 使用MACRO摇动排序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46011141/

相关文章:

C++ 对象的快速排序 vector

c++ - 顺时针旋转M*N矩阵90度,C++

c - C 中结构的实例化?

c - 如何使c程序在Windows 7中可执行

python - 使用 python 冒泡排序进行稳定排序

r - 将 'where' 条件从 SAS 翻译成 R

c++ - std::map::erase 无限循环

c++ - Arduino 字符串比较不起作用

c - 这段代码的参数是什么

mysql - 如何从 MySQL 数据库中获取最后一批更新