摇一下向量:
该程序有效,但是:
我试图使用相同的功能进行气泡上升和气泡下降进行摇动排序(气泡上升将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, ¤t_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
。您的建议是错误的。在代码中,您会得到语法错误的“运算符”,例如(-)
和(<)
。只需使用sign
和oper
。
即使这样,sign sign
仍将解析为- -
或+ +
,这不是您想要的。您可以将i++
重写为同样有效的i = i + 1
,也可以使用令牌粘贴运算符sign##sign
,该运算符将生成--
或++
。
宏不是功能。您可能要在函数内调用宏。调用宏时作用域内的所有局部变量也都在宏作用域内。这意味着可能不需要定义所有这些指针。
为什么要传递数组元素类型var_t
?我认为SetVector
和GetVector
不是宏,因此类型独立性下降了。
如果var_t
是数组元素的类型,则索引不一定是同一类型;它应该是整数类型。 (您的元素必须与<
运算符具有可比性,因此它是算术类型之一,但是如果您有一个char
数组长于256个元素,会发生什么情况?)
如果元素是算术类型,则可能不需要GetValue
和SetValue
调用。您可以使用=
运算符分配值。
所有这些使我认为您真的不知道自己在做什么。加上已知的宏的陷阱和缺点是在此处不使用任何宏的充分理由。
附录中,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; ) ...
在此,条件发生递减,并且更新部分为空。优点是,它逐字使用完全相同的边界
0
和n
,但是通过在进入循环主体之前递减,可以访问相同的有效范围,从0
到n - 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/