1

为了学习纯 C(来自 C++),我决定使用结构和宏编写一个简单的数学库。

到目前为止,我将此作为测试宏:

#define MulVec2(dest,src) ((dest.x) = (dest.x) * (src.x); (dest.y) = (dest.y) * (src.y); return dest;)

typedef struct vec2f_s
{
    float x, y;
}
vec2f_t;

在我的调用代码中,我有这个:

int main(void)
{

    vec2f_t v, w;

    v.x = 5.0f;
    v.y = 2.0f;

    w.x = 3.0f;
    w.y = 3.0f;

    v = MulVec2(v, w);

    printf( "x => %f; y => %f \n", v.x, v.y );

    return 0;
}

我的问题如下:

1)我是否需要编写一个单独的宏来将对象的指针/地址传递给宏?如果是这样,怎么做?例如,请注意MulVec2(dest,src)宏假定传入的对象不是动态分配的,但我也希望支持这一点。

2)当我编译代码时,我得到这个错误:

../main.c: In function 'main':
../main.c:15:9: error: expected ')' before ';' token
../main.c:15:7: error: incompatible types when assigning to type 'vec2f_t' from type 'float'

我能做些什么来解决这个问题?

编辑

我应该澄清一下,我不打算为此使用宏,但编写宏的原因是我不必为向量的双精度和浮点变量编写单独的函数。我希望通过遵循 DRY(不要重复自己)原则,尽可能地重复使用它。

4

5 回答 5

2

宏不是函数!

当您调用 时MulVec2(v, w),它只是将宏替换为其定义的值。

预处理后的代码将如下所示:

int main(void)
{

    vec2f_t v, w;

    v.x = 5.0f;
    v.y = 2.0f;

    w.x = 3.0f;
    w.y = 3.0f;

 v=MulVec2(dest,src) ((dest.x) = (dest.x) * (src.x); (dest.y) = (dest.y) * (src.y); 
    return dest;)


    printf( "x => %f; y => %f \n", v.x, v.y );

    return 0;
}

这就是编译错误的原因。只需将 #define 替换为MulVec2函数即可。

于 2012-10-07T18:17:27.807 回答
2

正如其他人指出的那样,宏不是函数,这将是函数的用例。宏只是将 Mul2Vec(...) 文本扩展为您在宏中编写的内容,这对编译器没有任何意义,因为无法return脱离表达式。

如果您想在 C 中实现 C++ 模板的效果,请重新考虑您的策略,也许只是选择其中一个floatordouble并坚持下去。但是如果你绝对必须在不重复代码的情况下使用泛型,你可以像这样定义函数定义宏:

/* vec-impl.h */

#define PASTE(a, b) a ## b
#define NAME(prefix, type) PASTE(prefix, type)

#define VEC_T NAME(vec_, T)

typedef struct {
  T x;
  T y;
} VEC_T;

void NAME(MulVec2_, VEC_T)(VEC_T *dest, VEC_T *src) {
 dest->x = dest->x * src->x;
 dest->y = dest->y * src->y; 
}

定义实际函数的编译单元如下所示:

/* vec-double.c */

#define T double
#include "vec-impl.h"

/* vec-float.c */

#define T float    
#include "vec-impl.h"

此设置为您提供了 C++ 模板的穷人近似,没有类型推断和任何花哨的元编程功能。同样,除非您绝对必须这样做,否则不要走那条路,即使那样,也要尽量减少执行此操作的代码。这不是惯用的 C 代码,并且不会被有能力的 C 程序员所接受——虽然 C 比 C++ 更容易受到预处理器黑客攻击,但这已经超出了界限。

关于您发布的代码:要了解宏是什么,请购买一本关于 C 的好书来解释该主题,例如 Kernighan 和 Ritchie 的“The C Programming language”。在探索宏时,请记住以下几点:

  • 除非你真的知道自己在做什么,否则永远不要return脱离宏。如果您仍然这样做,请将 RETURN 一词作为宏名称的一部分。其他流控制语句也是如此,例如breakcontinuegoto。在你之后维护该代码的人会很感激它。

  • 按照惯例,宏总是用全大写字母拼写,所以它是 MUL_VEC2,而不是 MulVec2。这会提醒读者他正在处理的是宏,而不是函数。

  • 宏定义中的括号点是为了使宏扩展即使传递了复合表达式也能工作。因此dest,必须用括号括起来,而不是dest.x. 即,而不是(dest.x),一个人会写(dest).x

  • 使用-E编译器的开关向您显示预处理器输出,这样您就可以看到编译器看到的内容并弄清楚发生了什么。

于 2012-10-07T18:24:06.830 回答
1

这是一个糟糕的设计。多次评估其参数的宏很容易出错,因为作为参数传递的表达式的副作用可能会破坏整个代码。使用inline与在 C++ 中使用函数几乎相同的函数。

作为一般规则,不要将宏用于可以通过函数完成的事情。

现在,如果您有这样的函数,请创建另一个通过指针接收参数的函数,将其命名为MulVec2p。然后使用 C11 的一个新功能,称为类型泛型宏,您可以执行类似于 C++ 中的函数重载的操作

#define MULVEC2(X, Y)                \
(_Generic((X),                       \
  struct vec2f_s: MulVec2,           \
  struct vec2f_s*: MulVec2p)         \
   ((X), (Y)))

(这个只测试第一个参数的类型,但我希望你能明白。)

编译器尚未完全支持 C11,但例如 clang 已经有_Generic.

于 2012-10-07T18:21:29.990 回答
1

如果您有 C99 编译器,则此版本的宏将执行您希望它执行的操作:

  #define DOT2(a, b) ((vec2f_t){(a).x * (b).x, (a).y * (b).y})

按照惯例,但特别是如果他们两次评估他们的参数,就像这里的情况一样,宏必须有一个大写的 UGLY_NAME。我喜欢宏,但我从痛苦的经历中知道,如果你不遵守这条规则,你会后悔的。所以我已经用你的替换MulVec2DOT2; MULVEC2也可以。

同样按照惯例,宏参数总是用括号括起来,例如(a).x. 参照。_ 此规则的例外情况向读者表明您正在做一些不寻常和/或错误的事情。但fabs((a))可能是不必要的。

使用此宏,您甚至可以获取地址(使用 gcc 测试):

   extern void foo(vec2f_t *);
   // ...
   foo(&DOT2(ying, yang));
于 2012-11-17T02:55:24.790 回答
0

对于第二个问题 - 删除 return desc;从宏中,您不能将此宏分配给任何人

于 2012-10-07T18:17:07.203 回答