跳至主要內容

08-预处理器

AI悦创原创C教程C教程大约 9 分钟...约 2687 字

1. 预处理指令

1.1 预处理指令

C 预处理程序在编译之前使用 # 指令在程序源代码中进行替换。

例如,在编译程序之前,行 #include <stdio.h>stdio.h 头文件的内容替换。

预处理程序指令及其用法:

  • #include 包括头文件。
  • #define#undef 定义和取消定义宏。
  • #ifdef#ifndef#if#else#elif#endif 条件编译。
  • #pragma 用于指示编译器完成一些特定的动作
  • #error#warning 输出错误或警告消息错误停止编译。

不要在 # 指令的末尾放一个分号 ; 字符。

以下哪个预处理指令允许在源代码中包含头文件?

A. #including

B. #define

C. #ifdef

D. #include

1.2 #include 指令

#include 指令是用来在程序中包含头文件的。一个头文件声明了一个库函数和宏的集合.

一些有用的 C 语言库:

  • stdio: 输入/输出函数,包括 printf 和文件操作。
  • stdlib: 内存管理和其他实用工具
  • string: 处理字符串的函数
  • errno: errno 全局变量和错误代码宏
  • math: 常用的数学函数
  • time: 时间/日期实用程序

对应的库的头文件按惯例以 .h 结尾。如果头文件要在编译器的包含路径中搜索,#include指令希望头文件名周围有括号 <>

一个用户定义的头文件也被赋予 .h 的扩展名,但要用引号 " 来表示,如 "myutil.h"。当使用引号时,该文件将在源代码目录中被搜索到。

例如:

#include <stdio.h>
#include “myutil.h” 

一些开发者使用 .hpp 扩展名来表示头文件。

填空,包括 stdio.h 头文件。

___ <stdio.h>

#include

1.3 #define 指令

C 语言中,可以用 #define 定义一个标识符来表示一个常量。

其特点是:定义的标识符不占内存,只是一个临时的符号,预编译后这个符号就不存在了。

预编译又叫预处理。预编译不是编译,而是编译前的处理。这个操作是在正式编译之前由系统自动完成的。

#define 定义标识符的一般形式为:

#define  标识符  常量   //注意, 最后没有分号

#define 又称宏定义,标识符为所定义的宏名,简称宏。标识符的命名规则与前面讲的变量的命名规则是一样的。#define 的功能是将标识符定义为其后的常量。一经定义,程序中就可以直接用标识符来表示这个常量。

例如:

Code1
#include <stdio.h>
#define PI 3.14
#define AREA(r) (PI*r*r)

int main() {
  float radius = 2;
  printf("%3.2f\n", PI);
  printf("Area is %5.2f\n", AREA(radius));
  printf("Area with radius + 1: %5.2f\n", AREA(radius+1));
  return 0;
} 

在编译之前,预处理程序会对每个宏标识符进行扩展。在这种情况下,PI 的每一次出现都被替换为 3.14,AREA(arg) 被替换为PI*arg*arg 的表达。发送给编译器的最终代码将已经有了常量值。

这不是我们可能期望的! 然而,如果你考虑到 #define 严格通过替换文本来工作,你会看到 AREA(radius+1) 变成 PI*radius+1*radius+1 ,也就是 3.14*2+1*2+1

解决这个问题的办法是将每个参数用圆括号 () 括起来,以获得正确的运算顺序。

例如:

Code1
#define AREA(r) (PI*(r)*(r)) 

该代码产生的输出结果: "Area with radius + 1: 28.26"

输入代码,定义一个用于计算参数平方的预处理程序宏:

___ SQR(___) ((x)*(x))

#define

x

1.4 格式化预处理指令

使用预处理程序指令时,# 必须是一行中的第一个字符。但是,在 # 之前以及 # 与指令之间可以有任意数量的空格。

如果 # 指令很长,则可以使用 \ 字符将定义扩展到多行。

例如:

Code1
#include <stdio.h>
#define VERY_LONG_CONSTANT \
23.678901

#define MAX 100
#define MIN 0
#    define SQUARE(x) \
    x*x
int main() {
  printf("%f\n", VERY_LONG_CONSTANT * SQUARE(2));
  return 0;
}

# 之前和 # 与指令之间可以有任意数量的空格。

输入字符,将宏的定义扩展到多行:

#define A_LONG_MACRO ___
42.4256789

\

1.5 预定义的宏定义

除了定义你自己的宏之外,还有几个标准的预定义宏,它们在 C 程序中总是可用的,不需要 #define 指令,如:

  • __DATE__ 当前日期是一个字符串,格式为Mm dd yyyy
  • __TIME__ 当前的时间是一个字符串,格式为hh:mm:ss
  • __FILE__ 当前的文件名,字符串。
  • __LINE__ 当前的行号,是一个int值。
  • __STDC__ 1

例如:

Code1
char curr_time[10];
char curr_date[12];
int std_c;

strcpy(curr_time, __TIME__);
strcpy(curr_date, __DATE__);
printf("%s %s\n", curr_time, curr_date);
printf("This is line %d\n", __LINE__);    
std_c = __STDC__;
printf("STDC is %d", std_c); 

以下哪项是代表当前日期的预定义宏?

A. __FILE__

B. __TIME__

C. __DATE__

2. 条件编译指令

2.1 #ifdef,#ifndef 和 #undef 指令

#ifdef#ifndef#undef 指令对使用 #define 创建的宏起作用。

例如,如果两次定义相同的宏,将存在编译问题,因此你可以使用 #ifdef 指令进行检查。

或者,如果你想重新定义宏,则可以先使用 #undef

下面的程序演示了这些指令:

Code1
#include <stdio.h>

#define RATE 0.08
#ifndef TERM
  #define TERM 24
#endif

int main() {
  #ifdef RATE  /* this branch will be compiled */
    #undef RATE  
    printf("Redefining RATE\n");
    #define RATE 0.068
  #else  /* this branch will not be compiled */
    #define RATE 0.068
  #endif

  printf("%f  %d\n", RATE, TERM);

  return 0;
}

因为 RATE 是在顶部定义的,所以只有 #ifdef 子句会被编译。

在预处理过程中,当 #ifdef RATE 为假时,可选的 #else 分支会被编译。需要一个 #endif 来关闭该代码块。

一个 #elif 指令就像一个 else if,可以用来在 #else 之后提供额外的选择。

填空,定义TERM宏(如果未定义)。

#___TERM
  #define TERM 24
#___

ifndef

endif

2.2 条件编译指令

代码段的条件编译由一组指令控制:#if#else#elif#endif

例如:

Code1
#define LEVEL 4

int main() {
  #if LEVEL > 6
    /* do something */
  #elif LEVEL > 5
    /* else if branch */
  #elif LEVEL > 4
    /* another else if */
  #else
    /* last option here */
  #endif

  return 0;
} 

在有些情况下,这种条件性编译是有用的,但这种类型的代码应该少用。

defined() 预处理器操作符可以和 #if 一起使用,如:

Code1
#if !defined(LEVEL)
  /* statements */
#endif

#ifif 语句是不能互换的。#if 使用预处理器可用的数据进行评估,然后只发送真正的分支代码进行编译。

一个 if 语句使用在运行时提供数据,并有可能分支到任何 else 子句。

如果没有定义 LEVEL 宏,请填入空白处以包括 printf 语句:

#if ___(LEVEL)
  printf("hello");
#___

!defined

endif

3. 预处理预算符

3.1 预处理运算符

C 预处理器提供以下运算符。

# 运算符

# 宏运算符 称为字符串化或字符串化运算符,它告诉预处理器将参数转换为字符串常量。

参数两侧的空白将被忽略,转义序列将被识别。

例如:

Code1
#define TO_STR(x) #x

printf("%s\n", TO_STR( 123\\12 ));

下面代码输出是?

#define STR(x) #x
#define STRLEN(x) strlen(x)
printf("%d", STRLEN(STR(12345)));

5

3.2 ## 运算符

#运算符 类似,##运算符 可用于类函数宏(带参宏)的替换部分。##运算符 可以把两个记号组合成一个记号。

例如:

Code1
#define VAR(name, num) name##num

int x1 = 125;
int x2 = 250;
int x3 = 500;  

printf("%d\n", VAR(x, 3)); 

下面代码输出是?

#define CONCAT(x, y) x##y
int x = 4;
int y = 5;
int CONCAT(x,y) = x + y;
printf("%d", xy);

9

4. 小测验

4.1 练习-1

定义一个将其参数增加三倍的宏。然后使用 num 变量作为其参数并输出结果。

___ TRIPLE(x) (x) * 3

int num = 42;
printf("%d", TRIPLE(___));

#define

num

4.2 练习-2

如果定义了 TRIPLE 宏,则定义宏 SQR,否则定义 TRIPLE。

#___ TRIPLE
#define SQR(x) (x) * (x)
#___#define TRIPLE(x) (x) * 3
#___

ifdef

else

endif

4.3 练习-3

预处理器的哪个阶段起作用?

A. 编译前✅

B. 编译后

C. 执行时

4.4 练习-4

下面代码的输出是哪一项?

#include <stdio.h>
#define T 42
int main()
{
   int T = 8;
   printf("%d ", T);
   return 0;
}

A. 8

B. 编译错误 Compile Error✅

C. 42

D. 0

4.5 练习-5

下面代码的输出是哪一项?

#define sqr(x) x*x
int x = 16/sqr(4);
printf("%d", x);

A. 16

B. 0

C. 1✅

D. 4

公众号:AI悦创【二维码】

AI悦创·编程一对一

AI悦创·推出辅导班啦,包括「Python 语言辅导班、C++ 辅导班、java 辅导班、算法/数据结构辅导班、少儿编程、pygame 游戏开发、Linux、Web、Sql」,全部都是一对一教学:一对一辅导 + 一对一答疑 + 布置作业 + 项目实践等。当然,还有线下线上摄影课程、Photoshop、Premiere 一对一教学、QQ、微信在线,随时响应!微信:Jiabcdefh

C++ 信息奥赛题解,长期更新!长期招收一对一中小学信息奥赛集训,莆田、厦门地区有机会线下上门,其他地区线上。微信:Jiabcdefh

方法一:QQopen in new window

方法二:微信:Jiabcdefh

上次编辑于:
贡献者: AndersonHJB
你认为这篇文章怎么样?
  • 0
  • 0
  • 0
  • 0
  • 0
  • 0
评论
  • 按正序
  • 按倒序
  • 按热度