宏当中的可变参数

前言

使用示例:

#ifndef COLOR_H
#define COLOR_H

#define NONE		"\033[0m"
#define BLACK	 	"\033[0;30m"
#define L_BLACK	 	"\033[1;30m"
#define RED      	"\033[0;31m"
#define L_RED    	"\033[1;31m"
#define GREEN    	"\033[0;32m"
#define L_GREEN  	"\033[1;32m"
#define YELLOW   	"\033[0;33m"
#define L_YELLOW 	"\033[1;33m"
#define BLUE     	"\033[0;34m"
#define L_BLUE	 	"\033[1;34m"
#define PURPLE   	"\033[0;35m"
#define L_PURPLE	"\033[1;35m"
#define CYAN     	"\033[0;36m"
#define L_CYAN   	"\033[1;36m"
#define WHITE    	"\033[0;37m"
#define L_WHITE    	"\033[1;37m"

//#define BOLD          "\033[1m"
//#define UNDERLINE     "\033[4m"

#define printr(format, ...) do{printf(RED		format NONE, ##__VA_ARGS__);}while(0)
#define printg(format, ...) do{printf(GREEN		format NONE, ##__VA_ARGS__);}while(0)
#define printy(format, ...) do{printf(YELLOW	format NONE, ##__VA_ARGS__);}while(0)
#define printb(format, ...) do{printf(BLUE		format NONE, ##__VA_ARGS__);}while(0)
#define printk(format, ...) do{printf(BLACK		format NONE, ##__VA_ARGS__);}while(0)
#define printw(format, ...) do{printf(WHITE		format NONE, ##__VA_ARGS__);}while(0)
#define printc(format, ...) do{printf(CYAN		format NONE, ##__VA_ARGS__);}while(0)
#define printp(format, ...) do{printf(PURPLE	format NONE, ##__VA_ARGS__);}while(0)

#define printlr(format, ...) do{printf(L_RED	format NONE, ##__VA_ARGS__);}while(0)
#define printlg(format, ...) do{printf(L_GREEN	format NONE, ##__VA_ARGS__);}while(0)
#define printly(format, ...) do{printf(L_YELLOW	format NONE, ##__VA_ARGS__);}while(0)
#define printlb(format, ...) do{printf(L_BLUE 	format NONE, ##__VA_ARGS__);}while(0)
#define printlk(format, ...) do{printf(L_BLACK	format NONE, ##__VA_ARGS__);}while(0)
#define printlw(format, ...) do{printf(L_WHITE	format NONE, ##__VA_ARGS__);}while(0)
#define printlc(format, ...) do{printf(L_CYAN	format NONE, ##__VA_ARGS__);}while(0)
#define printlp(format, ...) do{printf(L_PURPLE	format NONE, ##__VA_ARGS__);}while(0)

#define fprintr(stream, format, ...) do{fprintf(stream, RED		format NONE, ##__VA_ARGS__);}while(0)
#define fprintg(stream, format, ...) do{fprintf(stream, GREEN	format NONE, ##__VA_ARGS__);}while(0)
#define fprinty(stream, format, ...) do{fprintf(stream, YELLOW	format NONE, ##__VA_ARGS__);}while(0)
#define fprintb(stream, format, ...) do{fprintf(stream, BLUE	format NONE, ##__VA_ARGS__);}while(0)
#define fprintk(stream, format, ...) do{fprintf(stream, BLACK	format NONE, ##__VA_ARGS__);}while(0)
#define fprintw(stream, format, ...) do{fprintf(stream, WHITE	format NONE, ##__VA_ARGS__);}while(0)
#define fprintc(stream, format, ...) do{fprintf(stream, CYAN 	format NONE, ##__VA_ARGS__);}while(0)
#define fprintp(stream, format, ...) do{fprintf(stream, PURPLE	format NONE, ##__VA_ARGS__);}while(0)

#define fprintlr(stream, format, ...) do{fprintf(stream, L_RED 		format NONE, ##__VA_ARGS__);}while(0)
#define fprintlg(stream, format, ...) do{fprintf(stream, L_GREEN 	format NONE, ##__VA_ARGS__);}while(0)
#define fprintly(stream, format, ...) do{fprintf(stream, L_YELLOW 	format NONE, ##__VA_ARGS__);}while(0)
#define fprintlb(stream, format, ...) do{fprintf(stream, L_BLUE 	format NONE, ##__VA_ARGS__);}while(0)
#define fprintlk(stream, format, ...) do{fprintf(stream, L_BLACK	format NONE, ##__VA_ARGS__);}while(0)
#define fprintlw(stream, format, ...) do{fprintf(stream, L_WHITE	format NONE, ##__VA_ARGS__);}while(0)
#define fprintlc(stream, format, ...) do{fprintf(stream, L_CYAN		format NONE, ##__VA_ARGS__);}while(0)
#define fprintlp(stream, format, ...) do{fprintf(stream, L_PURPLE	format NONE, ##__VA_ARGS__);}while(0)

#endif

GNU原文链接

翻译

宏定义可以接受可变参数(和变参函数一样多)。
语法和变参函数很像,例如:

#define eprintf(...) fprintf(stderr, __VA_ARGS__)

这种宏叫做可变参数宏variadic)。

  • 当这样的宏被使用时,最后一个被命名的参数(上面的例子没有命名的参数)后所有的序列,包括逗号,都会成为可变参数。
  • 这个参数序列会替换宏当中的标识符__VA_ARGS__
宏展开的例子:
eprintf("%s:%d: ", input_file, lineno)
 	-> fprintf(stderr, "%s:%d:", input_file, lineno);

可变参数会完全展开后再插入到该宏中,看起来就和普通参数一样。
你可以使用#或者##来字符串化(stringize)参数、连接它的开头或尾随序列。(但是下面会讲##的一种重要的特殊用法。)

C++扩展

如果你的宏很复杂,你可能想要有更多的名字(而不是仅仅有__VA_ARGS__C++ 允许这样的扩展。你可以在...前紧跟一个参数名,这个名字会被用来作为可变参数。所以上面的例子可以修改成这样:

#define eprintf(args...) fprintf(stderr, args)

如果使用这个扩展,你就不能在同一个宏内再使用__VA_ARGS__

不使用扩展

不过,就算不用扩展,你也可以在一个可变参数宏varidic)中
使用命名参数,例如:

#define eprintf(format, ...) fprintf(stderr, format, __VA_ARGS__)
  • 这样的写法看起来更具描述性,但是由于历史原因它不太灵活:你必须在格式化字符串之后提供至少一个参数。(译者注:也就是说,比如你在调用printf()时,除了格式化字符串,还要有别的参数)
  • 在标准C中,你不能省略分隔参数和省略号...的逗号。(不过,此限制已经在C++20中取消,并且GNU C从不存在这个限制。)(译者注:总之都加上逗号肯定不错)

另外,如果你的可变参数为空,会有语法错误,因为格式化字符串后会多一个逗号:

eprintf("success!\n",);
	-> fprintf(stderr, "success!\n", );

这在C++20中已经修复,GNU CPP也有处理该问题的扩展。

首先,在GNU CPP,以及C++20中,你可以完全省略可变参数:

eprintf("success!\n")
	->fprintf(stderr, "success!\n", );

下面这一段,我看不太懂,可能翻译有误,建议原文。。。
其次,C++20引入了__VA_OPT__函数宏。这个宏只能出现在可变参数宏的定义中。如果可变参数序列有任何参数(包括逗号),则__VA_OPT__扩展为它的参数。但是如果可变参数序列没有任何参数,__VA_OPT__就不扩展。
截止。。。。。。。。。。。。。。。。

#define eprintf(format, ...)\
	fprintf(stderr, format __VA_OPT__(,) __VA_ARGS__)

__VA_OPT__GNU C和*GNU C++*可用。

GNU CPP扩展

由于历史原因,GNU CPP也提供另一种扩展来处理多余的尾随逗号:
##会有特殊含义,当它放在逗号和可变参数之间时。尽管引入了__VA_OPT__,但是为了向后兼容,GNU CPP仍然支持此扩展。
如果你这样写:

#define eprintf(format, ...) fprintf(stderr, format, ##__VA_ARGS__)

##前为逗号,或者传递的可变参数为空时,可变参数会被丢弃,然后##前的逗号会被删除。

上面的解释对于宏参数唯一时的可变参数情况 具有二义性,因为试图区分是空参数还是缺少参数是没有意义的。当符合特定的C标准时,CPP保留逗号。否则逗号作为标准的扩展被删除。

补充
  • C标准要求__VA_ARGS__只能出现在可变参数宏的替换列表中。它不能用作宏名称、宏参数名或在不同类型的宏中使用。也可能在公开文本中被禁止,标准具有二义性。除非用于其所定义的目的,否则,我们建议您不要使用它。

  • 同样,C++ 禁止在可变参数宏的替换列表之外使用__VA_OPT__

  • 可变参数宏随着C99成为C语言的标准部分。

  • GNU CPP以前使用命名可变参数(args...,不是...__VA_ARGS__;)来支持它们,这仍然支持向后兼容。

原文

3.6 Variadic Macros
A macro can be declared to accept a variable number of arguments much as a function can. The syntax for defining the macro is similar to that of a function. Here is an example:

#define eprintf(...) fprintf (stderr, __VA_ARGS__)

This kind of macro is called variadic. When the macro is invoked, all the tokens in its argument list after the last named argument (this macro has none), including any commas, become the variable argument. This sequence of tokens replaces the identifier VA_ARGS in the macro body wherever it appears. Thus, we have this expansion:

eprintf ("%s:%d: ", input_file, lineno)fprintf (stderr, "%s:%d: ", input_file, lineno)

The variable argument is completely macro-expanded before it is inserted into the macro expansion, just like an ordinary argument. You may use the ‘#’ and ‘##’ operators to stringize the variable argument or to paste its leading or trailing token with another token. (But see below for an important special case for ‘##’.)

If your macro is complicated, you may want a more descriptive name for the variable argument than VA_ARGS. CPP permits this, as an extension. You may write an argument name immediately before the ‘…’; that name is used for the variable argument. The eprintf macro above could be written

#define eprintf(args...) fprintf (stderr, args)

using this extension. You cannot use VA_ARGS and this extension in the same macro.

You can have named arguments as well as variable arguments in a variadic macro. We could define eprintf like this, instead:

#define eprintf(format, …) fprintf (stderr, format, VA_ARGS)
This formulation looks more descriptive, but historically it was less flexible: you had to supply at least one argument after the format string. In standard C, you could not omit the comma separating the named argument from the variable arguments. (Note that this restriction has been lifted in C++20, and never existed in GNU C; see below.)

Furthermore, if you left the variable argument empty, you would have gotten a syntax error, because there would have been an extra comma after the format string.

eprintf("success!\n", );fprintf(stderr, "success!\n", );

This has been fixed in C++20, and GNU CPP also has a pair of extensions which deal with this problem.

First, in GNU CPP, and in C++ beginning in C++20, you are allowed to leave the variable argument out entirely:

eprintf ("success!\n")fprintf(stderr, "success!\n", );

Second, C++20 introduces the VA_OPT function macro. This macro may only appear in the definition of a variadic macro. If the variable argument has any tokens, then a VA_OPT invocation expands to its argument; but if the variable argument does not have any tokens, the VA_OPT expands to nothing:

#define eprintf(format, ...) \
  fprintf (stderr, format __VA_OPT__(,) __VA_ARGS__)

VA_OPT is also available in GNU C and GNU C++.

Historically, GNU CPP has also had another extension to handle the trailing comma: the ‘##’ token paste operator has a special meaning when placed between a comma and a variable argument. Despite the introduction of VA_OPT, this extension remains supported in GNU CPP, for backward compatibility. If you write

#define eprintf(format, ...) fprintf (stderr, format, ##__VA_ARGS__)

and the variable argument is left out when the eprintf macro is used, then the comma before the ‘##’ will be deleted. This does not happen if you pass an empty argument, nor does it happen if the token preceding ‘##’ is anything other than a comma.

eprintf ("success!\n")fprintf(stderr, "success!\n");

The above explanation is ambiguous about the case where the only macro parameter is a variable arguments parameter, as it is meaningless to try to distinguish whether no argument at all is an empty argument or a missing argument. CPP retains the comma when conforming to a specific C standard. Otherwise the comma is dropped as an extension to the standard.

The C standard mandates that the only place the identifier VA_ARGS can appear is in the replacement list of a variadic macro. It may not be used as a macro name, macro argument name, or within a different type of macro. It may also be forbidden in open text; the standard is ambiguous. We recommend you avoid using it except for its defined purpose.

Likewise, C++ forbids VA_OPT anywhere outside the replacement list of a variadic macro.

Variadic macros became a standard part of the C language with C99. GNU CPP previously supported them with a named variable argument (‘args…’, not ‘…’ and VA_ARGS), which is still supported for backward compatibility.

  • 2
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
c语言中的函数是一种定义的形式,可以在编译预处理阶段通过替换来实现类似函数调用的功能。它不同于普通的函数调用,其主要特点是在定义时使用一些特殊的形参,这些形参可以包含可变参数可变参数是指函数在调用时可以传入不定数量的参数。在定义中,我们可以使用"..."来表示可变参数,且该参数必须放置在参数列表的最后。 通过定义中的可变参数,我们可以通过函数实现一些功能较为复杂的操作。比如,我们可以编写一个求和的函数,可以传入任意数量的参数,并对它们进行求和计算。例如: ``` #define SUM(...) sum(__VA_ARGS__) int sum(int count, ...) { int result = 0; va_list arg; va_start(arg, count); for(int i = 0; i < count; i++) { result += va_arg(arg, int); } va_end(arg); return result; } ``` 在使用可变参数函数时,我们需要使用定义中的特殊形参,包括`va_list`、`va_start`、`va_arg`和`va_end`。`va_list`是一个用于存储可变参数的类型,`va_start`用于初始化可变参数的访问,`va_arg`用于按顺序获取可变参数的值,`va_end`用于结束对可变参数的访问。 总之,函数和可变参数C语言中非常有用的功能,可以通过它们来实现一些复杂的操作和功能。但需要注意的是,在使用可变参数时要小心,保证参数的正确性和合法性,以免出现错误。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值