C语言-预处理命令

#include
#define
#if
#ifdef
#ifndef
typedef

我在以前的章节中经常使用#include命令。用它来调用库函数,这种#号开头的命令称为预处理命令

在编译之前对源程序进行简单加工的过程就称为预处理(即预先处理,提前处理)

预处理是C语言的一个重要功能,由预处理程序完成。当对一个源文件进行编译时,系统将自动调用预处理程序对源程序中的预处理部分作处理,处理完毕自动进入对源程序的编译。

编译器会将预处理的结果保存到和源文件同名的.i文件中,例如 main.c 的预处理结果在 main.i 中。和.c一样,.i也是文本文件,可以用编辑器打开直接查看内容。

#include

#include是文件包含命令,用来引用头文件,引入对应文件的用法有两种:

  • 使用尖括号<>,编译器回到系统路径下查找头文件。
  • 使用"",编译器先在当前目录找头文件,然后再回到系统路径下查找头文件

使用#include的注意事项:

  • 一个#include命令只能包含一个头文件,多个头文件要写多次#include
  • 同一个文件可以被多次引入,但多次引入和一次引用的效果一样,C语言中的头文件在代码层面有防止重复引用的机制。
  • 文件包含允许嵌套,也就是说在一个被包含的文件中又可以包含另一个文件。

「在头文件中定义定义函数和全局变量」这种认知是原则性的错误!不管是标准头文件,还是自定义头文件,都只能包含变量和函数的声明,不能包含定义,否则在多次引入时会引起重复定义错误。

define 宏定义

#define 叫做宏定义命令,它也是C语言预处理命令的一种。所谓宏定义,就是用一个标识符来表示一个字符串,如果在后面的代码中出现了该标识符,那么就全部替换成指定的字符串。

#define 宏名 字符串
  1. 宏定义是用宏名来表示的一个字符串,在宏展开时又以该字符串取代宏名,这只是一种简单粗暴的替换。
  2. 宏定义不是说明或语句在行末不必加分号,如加上分号则连分号一起替换。
  3. 宏定义必须写在函数外面,其作用域为宏定义命令起到源程序结束。如要终止其作用域可使用#undef命令。
  4. 代码中的宏名如果被引导包围,那么预处理程序不对其做宏代替。
  5. 宏定义允许嵌套,在宏定义的字符串中可以使用已经定义的宏名,在宏展开时由预处理程序层层代替。
  6. 宏命名可以用大写也可以用小写。
  7. 可用宏定义表示数据类型 例如:
#define UINT unsigned int

带参宏定义

  1. 带参宏定义中形参之间可以出现空格,但是宏名和形参列表之间不能有空格出现
  2. 在带参宏定义中,不会为形式参数分配内存,因此不必指明数据类型。而在宏调用中,实参包含了具体数据,要用它们去代替形参,因此实参必须指明数据类型,和函数的不同在于,它是直接替换,不存在数值间的转换的问题。

带参数的宏定义和函数很相似,但有本质上的区别:宏展开只是字符串的替换,不会对表达式计算,宏在编译之前就处理掉了,他没有机会参与编译,也不会占用内存,而函数是一段可以重复使用的代码,会被编译,会分配内存,每次调用函数,只是在另一块内存中执行函数中的代码。

if

#if 整型常量表达式1
    程序段1
#elif 整型常量表达式2
    程序段2
#elif 整型常量表达式3
    程序段3
#else
    程序段4

它的意思是:如常“表达式1”的值为真(非0),就对“程序段1”进行编译,否则就计算“表达式2”,结果为真的话就对“程序段2”进行编译,为假的话就继续往下匹配,直到遇到值为真的表达式,或者遇到 #else。这一点和 if else 非常类似。

需要注意的是:#if命令要求判断条件为“整型常量表达式”,也就是说,表达式中不能包含变量,而且结果必须是整数;而if后面的表达式没有限制,只要符合语法即可。

ifdef

#ifdef  宏名
    程序段1
#else
    程序段2
#endif

它的意思是,如果当前的宏已被定义过,则对“程序段1”进行编译,否则对“程序段2”进行编译。

ifndef

#ifndef 宏名
    程序段1 
#else 
    程序段2 
#endif

与 #ifdef 相比,仅仅是将 #ifdef 改为了 #ifndef。它的意思是,如果当前的宏未被定义,则对“程序段1”进行编译,否则对“程序段2”进行编译,这与 #ifdef 的功能正好相反。

指令 说明
# 空指令,无任何效果
#include 包含一个源代码文件
#define 定义宏
#undef 取消宏
#if 如果给定条件为真则编译下面代码
#ifdef 如果宏已经定义,则编译以下代码
#ifndef 如果宏没有定义则编译以下代码
#elif 如果前面的#if给定的条件不为真,当前条件为真,则编译下面代码
#endif 结束一个#if……#else条件编译块

typedef

C语言允许为一个数据类型起一个新的别名。

typedef oldName newName

oldName 表示数据类型原来的名字 newName 表示数据类型新的名字。

typedef int INTEGER;
INTEGER a, b;
a = 1;
b = 2;

INTEGER a, b;等效于int a, b;。

它还可以给数组、指针、结构体等类型定义别名。

typedef char ARRAY20[20];

表示 ARRAY20 是类型char [20]的别名。它是一个长度为 20 的数组类型。接着可以 用 ARRAY20 定义数组:

ARRAY20 a1, a2, s1, s2;

它等价于:

char a1[20], a2[20], s1[20], s2[20];

指针

typedef int (*PTR_TO_ARR)[4];

表示 PTR_TO_ARR 是类型int * [4]的别名,它是一个二维数组指针类型。接着可以使用 PTR_TO_ARR 定义二维数组指针:

PTR_TO_ARR p1, p2;

按照类似的写法,还可以为函数指针类型定义别名:

typedef int (*PTR_TO_FUNC)(int, int);
PTR_TO_FUNC pfunc;

typedef与#define的区别。 typedef的表现上有些类似于#define,但它和宏替换之间存在一个关键性的区别。正确思考这个问题的方法就是把 typedef 看成一种彻底的“封装”类型,声明之后不能再往里面增加别的东西。他们俩之间有很大的区别:

  1. 可以使用其他类型说明符对宏类型名进行扩展,但对typedef所定义的类型名却不能这样做
  2. 在连续定义几个变量的时候,typedef能够保证定义的所有变量均为同一类型,而#define则无法保证

Posts in this Series