C/C++ 宏字符串连接
Posted
技术标签:
【中文标题】C/C++ 宏字符串连接【英文标题】:C/C++ macro string concatenation 【发布时间】:2011-07-12 11:53:24 【问题描述】:#define STR1 "s"
#define STR2 "1"
#define STR3 STR1 ## STR2
是否可以将STR1
和STR2
连接到"s1"
?
您可以通过将 args 传递给另一个宏函数来做到这一点。但是有直接的方法吗?
【问题讨论】:
不应该是#define STR3 STR1 ## STR2 它也不应该是,因为这将 STR3 定义为预处理令牌 STR1STR2。并且将 args 传递给另一个宏函数也无济于事,因为字符串文字不能粘贴在一起——“s”“1”不是有效的标记。 【参考方案1】:如果它们都是字符串,你可以这样做:
#define STR3 STR1 STR2
然后扩展为:
#define STR3 "s" "1"
在 C 语言中,用空格分隔两个字符串,如 "s" "1"
中一样,完全等同于有一个字符串 "s1"
。
【讨论】:
从技术上讲,字符串连接是在语言级别完成的。 预处理器不做这样的事情。正是 C 语言本身将相邻的字符串文字视为单个字符串文字。 这不仅仅是一个技术问题——你不能连接L"a"
和"b"
来得到L"ab"
,但是你可以连接L"a"
和L"b"
获取L"ab"
。
如果你尝试#include STR3
并且STR3
是一个有效的头文件,这将不起作用。有人知道怎么做吗?
@Zythos - 您可能想发布一个单独的问题,详细说明您正在尝试做什么、您期望发生什么以及实际发生的事情。【参考方案2】:
对于字符串文字,您不需要这种解决方案,因为它们是在语言级别连接的,而且无论如何它都不会工作,因为“s”“1”不是有效的预处理器标记。
[编辑:为了回应下面错误的“仅供记录”评论,不幸收到了几个赞成票,我将重申上面的声明并观察程序片段
#define PPCAT_NX(A, B) A ## B
PPCAT_NX("s", "1")
从 gcc 的预处理阶段产生此错误消息:错误:粘贴“s”和“1”不提供有效的预处理令牌
]
但是,对于一般的令牌粘贴,试试这个:
/*
* Concatenate preprocessor tokens A and B without expanding macro definitions
* (however, if invoked from a macro, macro arguments are expanded).
*/
#define PPCAT_NX(A, B) A ## B
/*
* Concatenate preprocessor tokens A and B after macro-expanding them.
*/
#define PPCAT(A, B) PPCAT_NX(A, B)
然后,例如,PPCAT_NX(s, 1)
和 PPCAT(s, 1)
都生成标识符 s1
,除非 s
被定义为宏,在这种情况下 PPCAT(s, 1)
生成 <macro value of s>1
。
继续主题是这些宏:
/*
* Turn A into a string literal without expanding macro definitions
* (however, if invoked from a macro, macro arguments are expanded).
*/
#define STRINGIZE_NX(A) #A
/*
* Turn A into a string literal after macro-expanding it.
*/
#define STRINGIZE(A) STRINGIZE_NX(A)
那么,
#define T1 s
#define T2 1
STRINGIZE(PPCAT(T1, T2)) // produces "s1"
相比之下,
STRINGIZE(PPCAT_NX(T1, T2)) // produces "T1T2"
STRINGIZE_NX(PPCAT_NX(T1, T2)) // produces "PPCAT_NX(T1, T2)"
#define T1T2 visit the zoo
STRINGIZE(PPCAT_NX(T1, T2)) // produces "visit the zoo"
STRINGIZE_NX(PPCAT(T1, T2)) // produces "PPCAT(T1, T2)"
【讨论】:
仅作记录,"s""1"
在 C(和 C++)中有效。它们是两个标记(字符串文字),编译器会将自身连接起来并作为一个标记进行威胁。
你误解了我的评论和 C 语言。我说"s""1" isn't a valid token
——没错;正如您所说,它是 两个 标记。但是将它们与 ## 一起使用会使它们成为 single 预处理标记,而不是两个标记,因此编译器不会进行连接,而是词法分析器会拒绝它们(语言需要诊断)。
@mr5 仔细阅读 cmets。作为宏参数传递的宏名称在传递之前不会扩展。但是,它们在宏的主体中进行了扩展。因此,如果 A 定义为 FRED,则 STRINGIZE_NX(A) 扩展为“A”,但 STRINGIZE(A) 扩展为 STRINGIZE_NX(FRED),后者扩展为“FRED”。
@bharath 结果字符串为“PPCAT(T1,T2)”——符合预期。 而不是预期的“s1”——根本不是预期的。 为什么我们需要额外的间接/嵌套? -- 阅读代码 cmets,以及我在上面的 6 个赞成票的评论。只有宏的主体被扩展;在宏体之外,括号之间的宏参数在传递给宏之前不扩展。所以STRINGIZE_NX(whatever occurs here)
扩展为“这里发生的任何事情”,而不管发生的任何事情或这里发生的任何宏定义。
@bharath 当然它不会打印“Name A”——A 是参数名称,而不是宏的参数,即 ALEX。您声称if A is defined as FRED then STRINGIZE_NX(A) still expands to "FRED"
- 这是错误的,与您的测试完全不同。您正在努力不理解或正确理解这一点,我不会进一步回复您。【参考方案3】:
提示:上面的STRINGIZE
宏很酷,但是如果你犯了一个错误并且它的参数不是宏——你的名字有错别字,或者忘记了#include
头文件——那么编译器会很高兴地将声称的宏名称放入字符串中而不会出错。
如果您希望 STRINGIZE
的参数始终是具有正常 C 值的宏,那么
#define STRINGIZE(A) ((A),STRINGIZE_NX(A))
将展开它一次并检查它的有效性,丢弃它,然后将它再次展开成一个字符串。
我花了一段时间才弄明白为什么 STRINGIZE(ENOENT)
最终会变成 "ENOENT"
而不是 "2"
...我没有包括 errno.h
。
【讨论】:
重要的观察和 +1 以正确使用,
运算符。 :)
字符串的内容应该是一个有效的 C 表达式并没有什么特别的原因。如果你想这样做,我建议给它一个不同的名字,比如 STRINGIZE_EXPR。
这个技巧可能是孤立的。但它会阻止编译器看到它将连接的字符串序列。 (导致像((1),"1") "." ((2),"2")
这样的序列,而不仅仅是“1”“。”“2”)
只是为了澄清自守的意思:使用原始的 STRINGIZE
定义,"The value of ENOENT is " STRINGIZE(ENOENT)
有效,而 "The value of ENOENT is" STRINGIZE_EXPR(X)
产生错误。以上是关于C/C++ 宏字符串连接的主要内容,如果未能解决你的问题,请参考以下文章
如何定义宏以将连接的char字符串转换为C中的wchar_t字符串