C语言——预处理指令

一、预定义符号

在C语言中,有一些预定义的符号(也称为预定义宏),它们提供了关于编译器和编译过程的信息。这些符号在编译时由编译器自动定义,可以在程序中使用。以下是一些常见的预定义符号(这里的下划线都是两个连续的下划线):

  1. __FILE__:当前源文件的名称(字符串字面量)。

  2. __LINE__:当前源代码行的行号(整数常量)。

  3. __DATE__:编译日期,格式为 "Mmm dd yyyy"(字符串字面量)。

  4. __TIME__:编译时间,格式为 "hh:mm:ss"(字符串字面量)。

  5. __STDC__:如果编译器遵循ISO C标准,则定义为1。

  6. __STDC_VERSION__:编译器遵循的ISO C标准的版本号,例如199901L表示C99,201112L表示C11。

  7. __STDC_HOSTED__:如果编译器运行在宿主环境中(即操作系统之上),则定义为1;如果编译器运行在独立环境中(没有操作系统),则未定义。

  8. __func__(C99及以后):当前函数的名称(字符串字面量)。

这些预定义符号可以在程序中用于调试、日志记录或其他需要编译时信息的场合。例如,你可以使用它们来打印出错信息的位置。

例子:

#include <stdio.h>

void some_function() {
    printf("Function: %s\n", __func__);
    printf("File: %s, Line: %d\n", __FILE__, __LINE__);
}

int main() {
    printf("Compiled on: %s %s\n", __DATE__, __TIME__);
    some_function();
    return 0;
}

运行结果:

在这个例子中,__func__ 用于打印当前函数的名称,__FILE__ 和 __LINE__ 用于打印当前文件名和行号,__DATE__ 和 __TIME__ 用于打印编译日期和时间。

可以通过__STDC__测试编译器是否遵循ANSI C标准(ANSI C标准和ISO C标准指的是同一个标准,只是在不同的时间段由不同的组织发布)。

可以通过下面的程序测试是否遵循ANSI C标准:

#include <stdio.h>

int main() {
#ifdef __STDC__
    printf("followed");
#else
    printf("not followed");
#endif
    return 0;
}

在Visual Studio 2022中运行得到:

Visual Studio 2022 默认情况下可能不会完全遵守 ANSI 标准。

我们可以通过在项目属性中设置预处理器定义来定义__STDC__宏。在Visual Studio中,你可以通过以下步骤来定义这个宏:

  1. 打开你的项目。

  2. 在菜单栏中选择“项目” > “属性”。

  3. 在属性页中,选择“配置属性” > “C/C++” > “预处理器”。

  4. 在“预处理器定义”中添加__STDC__

  5. 点击“应用”然后“确定”。

完成这些步骤后,重新编译你的程序,就会看到输出"followed"。

设置后的运行结果:

在linux环境下,使用gcc编译这段程序:

然后运行,可以得到:

说明gcc是遵循ANSI C标准的。

二、#define

在C语言中,#define 是一种预处理指令,用于定义宏。它告诉编译器在实际编译之前进行文本替换。

注意:宏定义后面没有分号

1、对象式宏(Object-like Macros)

对象宏是最简单的宏,通常用来定义常量或替换文本。它们没有参数,并在预处理器运行时简单地替换代码中出现的所有实例。例如:

#define BUFFER_SIZE 1024

在这个例子中,BUFFER_SIZE 是一个对象宏,它在所有后续代码中将被替换为 1024

对象式宏也可以用来定义字符串:

#define SITE_NAME "example.com"

或者进行更复杂的文本替换:

#define LONG_LINE "This is a very, very long " \
                   "line of code that we wrap " \
                   "across several lines for " \
                   "readability"

这里要使用续行符 \ ,在C语言中,\ 符号被称为续行符(Line Continuation Character)。它的作用是告诉编译器,当前这一行代码在逻辑上并未结束,而是延续到下一行。这在编写长表达式或宏定义时非常有用,因为它允许开发者将代码分成多行,以提高代码的可读性。

或者甚至可以进行一段代码的替换:

#define PRINT for(int i = 0;i < 10;i++) {\
    printf("*");\
}

对象式宏甚至可以定义为空,以便在编译时有选择地启用或禁用代码部分:

#define DEBUG 
// 定义为空宏

2、函数式宏(Function-like Macros)

函数式宏看起来像函数调用,但它们不会生成函数调用的开销。它们在预处理时像模板一样展开,可以带参数。例如:

#define MAX(a, b) ((a) > (b) ? (a) : (b))

这个例子定义了一个函数式宏 MAX,用来计算两个值的最大值。注意,宏参数周围的括号非常重要,它们确保了宏参数作为整体参与计算,避免了运算符优先级的问题,或者说防止实参是表达式的问题。

函数式宏也可以实现更复杂的代码结构,例如:

#define FOREACH(item, array) \
  for(int keep = 1, \
          count = 0,\
          size = sizeof (array) / sizeof *(array); \
      keep && count != size; \
      keep = !keep, count++) \
    for(item = (array) + count; keep; keep = !keep)

这个宏 FOREACH 可以用来遍历数组,它通过一系列的循环和计数变量实现。

#include <stdio.h>

#define FOREACH(item, array) \
  for(int keep = 1, \
		  count = 0,\
		  size = sizeof (array) / sizeof *(array); \
	  keep && count != size; \
	  keep = !keep, count++) \
	for(item = (array) + count; keep; keep = !keep)

int main() {
	int arr[5] = { 1,2,3,4,5 };
	int* item = arr;
	FOREACH(item, arr) {
		printf("%d ", *item);
	}
	return 0;
}

运行结果:

这个FOREACH宏是为C语言提供一种类似于其他高级语言中的 foreach 循环的机制。这个宏使用两层 for 循环来遍历数组中的每个元素。

详细介绍这个函数式宏:

#define FOREACH(item, array) \

这一行开始定义了一个名为 FOREACH 的宏,它需要两个参数:itemarrayitem 将成为指向当前数组元素的指针,array 是要迭代的数组。

  for(int keep = 1, \
          count = 0,\
          size = sizeof (array) / sizeof *(array); \

这里初始化了三个变量:keepcountsize

  • keep 用来控制循环的继续或终止。开始时被设为 1(true),表示循环应该执行。
  • count 用来在数组中迭代,一开始设为 0。
  • size 用来记录数组中元素的数量,通过 sizeof (array) / sizeof *(array) 计算得出。sizeof (array) 给出整个数组所占的字节数,sizeof *(array) 给出数组中单个元素所占的字节数。二者相除就得到了数组中元素的数量。
      keep && count != size; \

这里是外层 for 循环的条件部分,它表明只要 keeptruecount 小于 size,循环就应该继续执行。

      keep = !keep, count++) \

这是外层 for 循环的迭代部分。在每次迭代的最后,keep 的值会被取反(这里的作用主要是在内层循环中使用),并将 count 加 1 以指向下一个数组元素。

    for(item = (array) + count; keep; keep = !keep)

这是内层 for 循环,它实际上只执行一次。item 被赋值为指向当前数组元素的指针,其偏移是 count。内层循环的条件是 keep 仍然为 true。由于内层循环内部没有语句,因此它实际上只用来赋值 item。在内层循环的迭代部分,keep 再次被取反,这会导致内层循环结束。

对于内层循环,实际上的作用就是给 item 赋值,这里的 keep 就是保证内层循环在每次外层循环中只进行一次。为什么不只用一个单独的语句例如这样:

    item = (array) + count;

而是使用一个只循环一次的循环呢?

实际上就是为了使用形式是这样:

int my_array[] = {1, 2, 3, 4, 5};
int *item;
FOREACH(item, my_array) {
    // 这里可以使用 *item 来访问数组的当前元素
    printf("%d\n", *item);
}

如果使用上面的一个单独语句的形式,就无法像这样的形式使用了。

3、注意事项

1.多加圆括号

宏在编译阶段就被替换,而且这种替换就只是单纯简单的替换。就是因为这里是简单的替换,所以会导致一些小问题。例如下面:


                
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值