这不是很清楚为什么你想在这里使用宏。你想避免重复代码?还是你想让你的排序例程类型独立?
总之,您的宏有几个误区:
- 你可能阅读,你应该警惕宏参数用括号。这通常是很好的建议,因为宏是文本替换;例如臭名昭着的
SQ(x + 1)
将解析为x + 1*x + 1
。就你而言,建议是错误的。您的代码中会出现句法错误的“运算符”,例如(-)
和(<)
。只需使用sign
和oper
即可。
- 即便如此,
sign sign
将解析为- -
或+ +
,这不是你想要的。您可以将i++
重写为同样有效的i = i + 1
,或者您可以使用令牌粘贴操作符sign##sign
,这会生成--
或++
。
- 宏不是函数。你可能会在一个函数内调用你的宏。你调用宏的范围内的所有局部变量也在宏的范围内。这意味着可能不需要定义所有这些指针。
- 为什么要传递数组元素类型
var_t
?我认为SetVector
和GetVector
不是宏,所以类型独立性下降。
- 如果
var_t
是数组元素的类型,那么您的索引不一定是相同的类型;它应该是一个整数类型。 (您的元素必须与<
运算符相媲美,所以它是算术类型之一,但是如果您有一个长度超过256个元素的char
阵列,会发生什么情况?)
- 如果您的元素是算术类型,可能不需要
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
的所有指数。所以你的宏需要传递开始和结束值。但是它也需要知道比较结果–小于或大于–以及是否递增或递减索引。这是四个简单循环的数据。
您可以尝试摆脱比较并在两个方向上使用!=
。但是如果数组为空,那么你的循环将失败。
当您使用无符号整数作为索引时,上面的向后循环将在空数组上失败。正向和反向的lops与C不对称,因为下限和上限也是不对称的:下限总是包含性的,上限总是排他性的。这种正向循环:
for (unsigned int i = 0; i < n; i++) ...
有以下落后相当于:
for (unsigned int i = n; i-- > 0;) ...
这里,减量发生在条件和更新的部分是空的。优点是,它使用完全相同的边界,即逐字符号0
和n
,但通过在进入循环体之前递减,访问相同的有效数字范围0
到n - 1
。它适用于无符号整数,这是循环变量的自然选择。要长话短说:正向和反向循环在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);
(即do { ... } while (0)
事情周围thze宏使得宏的行为就像函数调用一样,循环体的新范围允许局部变量)
这里的问题是这样的多行宏很难调试。当主体出现错误时,您只需在错误消息中获取调用宏的行的编号。 (但是你可以使用-E
与大多数编译器看到预处理器如何解决了宏)。
结论:
- 宏是有用的,但你要知道自己在做什么。一般来说,尽量避免使用它们,因为它们很难调试,而且往往难以理解。 (另外这个人可能会在半年后。)
- 如果你必须使用宏,尽量让它看起来尽可能的自然。通过运营商如
>
或+
应该让你保持警惕。
- 对通用代码使用函数而不是宏。
- 拥抱C处理不同类型的方法。如果学习
qsort
的工作方式,而不是为泡泡排序实现提供宏,它将会更有用(如果不那么有趣)。
- 如果你真的需要写很多类型无关的代码,你可能不应该使用C.
有什么错误? – leyanpan
开发更长的代码,因为宏是邪恶的。顺便说一下,你的思维方式在C++模板中有非常好的视角 –
在这里使用宏有什么意义?在执行向上/向下的气泡排序和摇动排序时,您是否只想替换冗余代码?如果'Get' /'SetValue'显然不是类型无关的,那类型是什么?你如何调用你的宏? –