在编译和链接之前,还需要对源文件进行一些文本方面的操作,比如文本替换、文件包含、删除部分代码等,这个过程叫做预处理,由预处理程序完成。

#include的用法详解

#include叫做文件包含命令,用来引入对应的头文件(.h文件)。#include也是C语言预处理命令的一种。

#include的处理过程很简单,就是将头文件的内容插入到该命令所在的位置,从而把头文件和当前源文件连接成一个源文件,这与复制粘贴的效果相同。

#include 的用法(两种):

1
2
#include <stdHeader.h>
#include "myHeader.h"

使用尖括号< >和双引号” “的区别在于头文件的搜索路径不同:

  • 使用尖括号< >,编译器会到系统路径下查找头文件;
  • 而使用双引号” “,编译器首先在当前目录下查找头文件,如果没有找到,再到系统路径下查找。

使用双引号比使用尖括号多了一个查找路径,它的功能更为强大。

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

宏定义

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

1
#define  宏名  字符串

关于宏(macro)的翻译

计算机中宏又称为宏命令,是通过特殊的控制语,将一系列动作简便化。即:一种批处理的程序。

汉语汉字中的宏是雄大,大的意思。开始觉得macro是也大的意思,造成每次看宏就特别别扭,不能叫大指令吗?后来了解到macro实际是指巨大的,大量的意思,那么macro指令就有指令集合之意,那么中文叫大指令就不贴切了,就被翻译成宏了,但是翻译成宏就必须对宏的定义做成调整,可以直接把宏做动词时的意思为“把一个物体由小变大,即膨胀”,作形容词时“物体由小变大的过程,即膨胀的过程”。被宏定义的指令,可以理解为对指令进行压缩,并在预处理时进行解压。

宏定义只是简单的字符串替换,由预处理器来处理;而 typedef 是在编译阶段由编译器处理的,它并不是简单的字符串替换,而给原有的数据类型起一个新的名字,将它作为一种新的数据类型。

带参数的宏定义

C语言允许宏带有参数。在宏定义中的参数称为“形式参数”,在宏调用中的参数称为“实际参数”,这点和函数有些类似。

1
#define 宏名(形参列表) 字符串

带参宏调用的一般形式为

1
宏名(实参列表);

例如:

1
2
3
#define M(y) y*y+3*y  //宏定义
// TODO:
k=M(5); //宏调用

对带参宏定义的说明

  • 带参宏定义中,形参之间可以出现空格,但是宏名和形参列表之间不能有空格出现
  • 在带参宏定义中,不会为形式参数分配内存,因此不必指明数据类型。
  • 在宏定义中,字符串内的形参通常要用括号括起来以避免出错。

带参宏定义和函数的区别

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

宏参数的字符串化和宏参数的连接

在宏定义中,有时还会用到###两个符号,它们能够对宏参数进行操作。

# 的用法

#用来将宏参数转换为字符串,也就是在宏参数的开头和末尾添加引号。例如有如下宏定义:

1
#define STR(s) #s

那么:

1
2
printf("%s", STR(abc));
printf("%s", STR("abc"));

分别被展开为:

1
2
printf("%s", "abc");
printf("%s", "\"abc\"");

可以发现,即使给宏参数“传递”的数据中包含引号,使用#仍然会在两头添加新的引号,而原来的引号会被转义。

##的用法

##称为连接符,用来将宏参数或其他的串连接起来。例如有如下的宏定义:

1
2
#define CON1(a, b) a##e##b
#define CON2(a, b) a##b##00

那么:

1
2
printf("%f\n", CON1(8.5, 2));
printf("%d\n", CON2(12, 34));

将被展开为:

1
2
printf("%f\n", 8.5e2);
printf("%d\n", 123400);

C语言中几个预定义宏

预定义宏就是已经预先定义好的宏。

ANSI C 规定了以下几个预定义宏,它们在各个编译器下都可以使用:

  • **LINE**:表示当前源代码的行号;
  • **FILE**:表示当前源文件的名称;
  • **DATE**:表示当前的编译日期;
  • **TIME**:表示当前的编译时间;
  • **STDC**:当要求程序严格遵循ANSI C标准时该标识被赋值为1;
  • __cplusplus:当编写C++程序时该标识符被定义。

条件编译详解 #if、##ifdef、#ifndef

这种能够根据不同情况编译不同代码、产生不同目标文件的机制,称为条件编译。条件编译是预处理程序的功能,不是编译器的功能。

#if 的用法

1
2
3
4
5
6
7
8
9
#if 整型常量表达式1
程序段1
#elif 整型常量表达式2
程序段2
#elif 整型常量表达式3
程序段3
#else
程序段4
#endif

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

#ifdef 的用法

#ifdef 用法的一般格式为:

1
2
3
4
5
#ifdef  宏名
程序段1
#else
程序段2
#endif

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

也可以省略 #else:

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

#ifndef 的用法

#ifndef 用法的一般格式为:

1
2
3
4
5
#ifndef 宏名
程序段1
#else
程序段2
#endif

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

三者之间的区别

最后需要注意的是,#if 后面跟的是“整型常量表达式”,而 #ifdef 和 #ifndef 后面跟的只能是一个宏名,不能是其他的。

#error命令,阻止程序编译

#error 指令用于在编译期间产生错误信息,并阻止程序的编译,其形式如下:

1
#error error_message

例如:

当我们希望以 C++ 的方式来编译程序时,可以这样做:

1
2
3
#ifndef __cplusplus
#error 当前程序必须以C++方式编译
#endif

#pragma使用方法

预处理命令总结

预处理指令是以#号开头的代码行,# 号必须是该行除了任何空白字符外的第一个字符。# 后是指令关键字,在关键字和 # 号之间允许存在任意个数的空白字符,整行语句构成了一条预处理指令,该指令将在编译器进行编译之前对源代码做某些转换。

指令 说明
# 空指令,无任何效果。
#include 包含一个源代码文件。
#define 定义宏。
#undef 取消已定义的宏。
#if 如果给定条件为真,则编译下面代码。  endif结尾
#ifdef 如果宏已经编译,则编译下面代码。endif结尾
#ifndef 如果宏没有定义,则编译下面代码。endif结尾
#elif 如果前面的#if给定条件不为真,当前条件为真,则编译下面代码。
#endif 结束一个#if…#elif…#else…条件编译块。
#error 用于在编译期间产生错误信息,并阻止程序的编译
#pragma 使用标准化方法,向编译器发布特殊的命令到编译器中。