在学习C语言时,我遇到过预处理器指令 #define
,后来在阅读一些代码时也遇到了它。但除了用它来定义常量的替换和宏的定义之外,我并不真正理解它在没有"body"或令牌字符串的特殊情况下使用的意义。
例如,考虑这行代码:
#define OCSTR(X)
就是这样!那么这有什么用,或者更好的说,何时需要使用#define
呢?
在学习C语言时,我遇到过预处理器指令 #define
,后来在阅读一些代码时也遇到了它。但除了用它来定义常量的替换和宏的定义之外,我并不真正理解它在没有"body"或令牌字符串的特殊情况下使用的意义。
例如,考虑这行代码:
#define OCSTR(X)
就是这样!那么这有什么用,或者更好的说,何时需要使用#define
呢?
这个用法有两种情况。第一种也是最常见的情况涉及条件编译:
#ifndef XYZ
#define XYZ
// ...
#endif
您肯定已经为包含保护使用了这个,但它也可以用于诸如系统依赖项之类的东西:
#ifdef WIN32
// Windows specific code here...
#endif
#ifdef DEBUG
#define TEST(X) text(X)
#else
#define TEST(X)
#endif
TEST(X);
DEBUG
,则会调用该函数,否则不会执行任何操作。这种宏通常成对出现,在条件#ifdef
内部,例如:
#ifdef _DEBUG
#define OCSTR(X)
#else
#define OCSTR(X) SOME_TOKENS_HERE
#endif
另一个例子,
#ifdef __cplusplus
#define NAMESPACE_BEGIN(X) namespace X {
#define NAMESPACE_END }
#else
#define NAMESPACE_BEGIN(X)
#define NAMESPACE_END
#endif
最近我挖掘出一个奇怪的案例来回答一个问题,结果发现它只是一种评论性质的内容。这个问题中的代码看起来像:
void CLASS functionName(){
//
//
//
}
#define
,作者选择记录函数访问项目中全局变量的方式:C++语法:void CLASS functionName()?。因此,与/* CLASS */
没有太大区别,除了不允许拼写错误,如/* CLAAS */
...或许还有其他一些小的好处?我同意每一个答案,但我想指出一件小事。
作为一个C语言纯粹主义者,我一直坚信每一个#define
都应该是一个表达式,所以,即使使用以下常见做法:
#define WHATEVER
并用它进行测试
#ifdef WHATEVER
我认为写作时最好的方式是:
#define WHATEVER (1)
还有#debug
宏应该是表达式:
#define DEBUG (xxx) (whatever you want for debugging, value)
通过这种方式,您可以完全避免 #宏
的误用,并防止出现不良问题(特别是在一个拥有1000万行C代码的项目中)。
#if
和#ifdef
的辩论可能会引起您的兴趣,个人而言我更倾向于使用#if
。https://dev59.com/MnVC5IYBdhLWcg3w9GLM - HostileFork says dont trust SE#define TEST
而没有表达式时,在clang中,#elif TEST
宏会抛出一个错误,说“错误:表达式中需要值”。 - rosshjb#define
宏在预处理期间被简单地替换为其替换文本。如果没有替换文本,那么它们将被替换为空!因此,这段源代码:
#define FOO(x)
print(FOO(hello world));
将被预处理成这样:
print();
这可以用于摆脱你不想要的东西,比如说assert()
。它主要在条件情况下有用,在某些条件下有非空的主体。
正如您在上面的回复中所看到的,它在调试代码时非常有用。
#ifdef DEBUG
#define debug(msg) fputs(__FILE__ ":" (__LINE__) " - " msg, stderr)
#else
#define debug(msg)
#endif
所以,在调试时,该函数将打印行号和文件名,以便您知道是否存在错误。如果您不进行调试,则不会产生任何输出。
这样的东西有很多用途。
例如,其中一个用途是让宏在不同的构建中具有不同的行为。例如,如果您想要调试消息,可以像这样:
#ifdef _DEBUG
#define DEBUG_LOG(X, ...) however_you_want_to_print_it
#else
#define DEBUG_LOG(X, ...) // nothing
#endif
另一个用途可能是根据您的系统自定义头文件。这是来自我在Linux中实现的Mesa OpenGL头文件:
#if !defined(OPENSTEP) && (defined(__WIN32__) && !defined(__CYGWIN__))
# if defined(__MINGW32__) && defined(GL_NO_STDCALL) || defined(UNDER_CE) /* The generated DLLs by MingW with STDCALL are not compatible with the ones done by Microsoft's compilers */
# define GLAPIENTRY
# else
# define GLAPIENTRY __stdcall
# endif
#elif defined(__CYGWIN__) && defined(USE_OPENGL32) /* use native windows opengl32 */
# define GLAPIENTRY __stdcall
#elif defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 303
# define GLAPIENTRY
#endif /* WIN32 && !CYGWIN */
#ifndef GLAPIENTRY
#define GLAPIENTRY
#endif
并且在头文件声明中使用,例如:
GLAPI void GLAPIENTRY glClearIndex( GLfloat c );
GLAPI void GLAPIENTRY glClearColor( GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha );
GLAPI void GLAPIENTRY glClear( GLbitfield mask );
...
我删除了GLAPI
的部分。
你可以想象一下,一个宏在某些情况下使用,在其他情况下不使用,可以在这些情况下定义为某个东西,而在其他情况下则不定义。
其他情况可能如下:
如果该宏不带参数,则可以仅用于声明某些情况。一个著名的例子是保护头文件。另一个例子可能是这样的:
#define USING_SOME_LIB
然后可以像这样使用:
#ifdef USING_SOME_LIB
...
#else
...
#endif
可能在某个阶段使用了宏来执行某些操作(例如日志记录),但是在发布时,所有者决定该日志不再有用,因此只需删除宏的内容,使其变为空。虽然这样做不被推荐,最好使用我在答案开头提到的方法。
最后,它可能只是为了更好的解释,例如你可以说
#define DONT_CALL_IF_LIB_NOT_INITIALIZED
你可以编写如下的函数:
void init(void);
void do_something(int x) DONT_CALL_IF_LIB_NOT_INITIALIZED;
虽然这最后一种情况有点荒谬,但在这种情况下是有道理的:
#define IN
#define OUT
void function(IN char *a, OUT char *b);
这可以用于当您希望静默某些函数时。例如,在调试模式下,您想要打印一些调试语句,在生产代码中,您想要省略它们:
#ifdef DEBUG
#define PRINT(X) printf("%s", X)
#else
#define PRINT(X) // <----- silently removed
#endif
使用方法:
void foo ()
{
PRINT("foo() starts\n");
...
}