Warning" "divider == 0" "/n");
} while(0);
这样每次divider(除数)为0的时候便会在标准错误流上输出一个提示信息。
而##被称为连接符(concatenator),用来将两个Token连接为一个Token。注意这里连接的对象是Token就行,而不一定是宏的变量。比如你要做一个菜单项命令名和函数指针组成的结构体的数组,并且希望在函数名和菜单项命令名之间有直观的、名字上的关系。那么下面的代码就非常实用:
struct command
{
char * name;
void (*function) (void);
};
#define COMMAND(NAME) { NAME, NAME ## _command }
//然后你就用一些预先定义好的命令来方便的初始化一个command结构的数组了:
struct command commands[] = {
COMMAND(quit),
COMMAND(help),
...
}
COMMAND宏在这里充当一个代码生成器的作用,这样可以在一定程度上减少代码密度,间接地也可以减少不留心所造成的错误。我们还可以n个##符号连接 n+1个Token,这个特性也是#符号所不具备的。比如:
#define LINK_MULTIPLE(a,b,c,d) a##_##b##_##c##_##d
typedef struct _record_type LINK_MULTIPLE(name,company,position,salary);
//这里这个语句将展开为:
// typedef struct _record_type name_company_position_salary;
关于...的使用
...在C宏中称为Variadic Macro,也就是变参宏。比如:
#define myprintf(templt,...) fprintf(stderr,templt,__VA_ARGS__)
//或者
#define myprintf(templt,args...) fprintf(stderr,templt,args)
第一个宏中由于没有对变参起名,我们用默认的宏__VA_ARGS__来替代它。第二个宏中,我们显式地命名变参为args,那么我们在宏定义中就可以用 args来代指变参了。同C语言的stdcall一样,变参必须作为参数表的最有一项出现。当上面的宏中我们只能提供第一个参数templt时,C标准要求我们必须写成:
myprintf(templt,);
的形式。这时的替换过程为:
myprintf("Error!/n",);
替换为:
fprintf(stderr,"Error!/n",);
这是一个语法错误,不能正常编译。这个问题一般有两个解决方法。首先,GNU CPP提供的解决方法允许上面的宏调用写成:
myprintf(templt);
而它将会被通过替换变成:
fprintf(stderr,"Error!/n",);
很明显,这里仍然会产生编译错误(非本例的某些情况下不会产生编译错误)。除了这种方式外,c99和GNU CPP都支持下面的宏定义方式:
#define myprintf(templt, ...) fprintf(stderr,templt, ##__VAR_ARGS__)
这时,##这个连接符号充当的作用就是当__VAR_ARGS__为空的时候,消除前面的那个逗号。那么此时的翻译过程如下:
myprintf(templt);
被转化为:
fprintf(stderr,templt);
这样如果templt合法,将不会产生编译错误。 这里列出了一些宏使用中容易出错的地方,以及合适的使用方式。
分享到:
相关推荐
- **操作符优先级**:在宏定义中使用某些操作符时需要注意优先级问题,以免造成误解或编译错误。 - **副作用的重复**:如果宏定义中的表达式有副作用,则多次展开可能会导致这些副作用被重复执行,从而产生意料之外...
总结来说,C语言中的`#`和`##`符号是宏定义中的重要工具,它们分别用于字符串化和拼接操作,使得宏定义能处理更复杂的文本替换,包括生成唯一的变量名和结构体填充等高级应用场景。理解和熟练运用这两个符号,能够...
* 如果宏定义中使用了"#"或"##"符号,那么宏参数将不会再展开。 * 如果宏参数是一个宏,那么需要添加一个中间转换宏,以确保宏参数的正确展开。 五、应用实例 以下是一些使用"#"和"##"符号的应用实例: * 合并...
需要注意的是,如果在宏定义中使用了 `##` 运算符,那么参数在替换过程中不会被扩展。这意味着嵌套调用可能会导致意外的结果。例如: ```c #define CONCAT(x, y) x##y ``` 对于 `CONCAT(a, CONCAT(b, c))` 的调用...
在C语言中,“#”和“##”是预处理器的一部分,它们分别具有将宏参数转换为字符串以及连接宏参数的功能。正确理解并掌握这两种符号的使用方法对于深入理解和编写复杂的C语言程序至关重要。 #### 二、“#”的使用...
这些宏定义在`stdarg.h`头文件中,因此编写可变参数函数时需要包含此头文件。 #### 宏定义解析 1. **`va_start`**: 初始化可变参数列表的迭代器。 - `void va_start(va_list arg_ptr, prev_param);` - 参数: -...
在C语言中,可变参数是一种非常有用的特性,它允许函数接受不同数量的参数。这种特性使得编写如`printf`函数那样灵活的函数成为可能,这些函数可以根据传入的参数动态地处理数据。本文将深入探讨C语言中的可变参数...
例如,在宏表达式中使用副作用强烈的操作(如自增或自减)可能导致问题。下面的示例演示了这种情况: ```c #define ADD(x, y) (x + y) int main() { int a = 5, b = 3; printf("Result: %d\n", ADD(a++, b++)); ...
在C语言中,可变参数是一种非常有用的特性,它允许函数接受不同数量的参数。这种功能在编写一些需要灵活处理参数的函数时非常方便,比如常见的`printf`函数。本篇将详细介绍C语言中可变参数的使用及其背后的机制。 ...
`GET_FILE_NAME`宏用于将`__FILE__`的值展开,以便能够在代码中使用当前文件名。 ### 总结 C语言中的“#”和“##”预处理器指令提供了强大的功能,用于宏定义和宏展开。它们能够帮助开发者编写更为灵活和模块化的...
### C语言带参数的宏定义详解 #### 一、引言 C语言作为一种广泛使用的编程语言,提供了多种机制来提高代码的复用性和可维护性。宏定义是C语言中的一个重要特性,它允许程序员创建自定义的文本替换规则。本文将深入...
### C 语言中的宏、井号与可变参数详解 #### 宏的概述与使用 在C语言中,宏(Macro)是一种预处理指令,它允许开发者定义一系列文本替换规则,这些规则会在编译之前由预处理器进行处理。宏不是在程序运行时执行的...
ANSI标准形式要求至少有一个命名的参数,并且函数原型中使用省略号表示可变参数的存在。而与UNIX System V兼容的形式则不需要提供任何命名参数,而是直接使用va_list类型作为参数列表的开始。 va_start宏在函数内...
- 在宏定义中使用等号(`=`)是错误的做法,例如: ```c #define N=100 /* 错误 */ ``` 这样定义会导致预处理器在遇到宏时将其替换为`=100`,而非期望的数值`100`。 - 在宏定义末尾添加分号也是错误的,例如: ...
如果在表达式中使用这个宏,可能会导致预期之外的结果,因为宏展开不考虑语境。例如: ```c int i = 1; printf("%d\n", INC(i) + 1); // 可能输出2而不是3 ``` 这是因为`INC(i)`先被展开,然后才进行加法操作,而...
C语言system()函数:执行shell命令 头文件: #include 定义函数: int system(const char * string); 函数说明:system()会调用fork()产生子进程, 由子进程来调用/bin/sh-c string 来...
3. **外设驱动开发**:针对PIC32提供的各种外设,如ADC、DAC、UART等,书中详细讲解了如何使用C语言来控制这些外设。 4. **高级应用开发**:如实现通信协议(如I2C、SPI)、图形用户界面等,这些都是实际项目中经常...
C语言sin()函数:正弦函数 头文件: #include <math.h> sin() 函数用来求给定值的正弦值,其原型为: double sin(double x); 【参数】给定的值(弧度)。 【返回值】返回-1 至1 之间的计算结果。 弧度...