预处理
什么是预处理
在编译之前对源文件进行简单加工的过程,就称为预处理(即预先处理、提前处理)。以#开头的命令为预处理命令。
预处理是C语言的一个重要功能,由预处理程序完成。当对一个源文件进行编译时,系统将自动调用预处理程序对源程序中的预处理部分作处理,处理完毕自动进入对源程序的编译。编译器会将预处理的结果保存到和源文件同名的.i文件中,例如 main.c 的预处理结果在 main.i 中。和.c一样,.i也是文本文件,可以用编辑器打开直接查看内容。
#include
功能特点
#include 为文件包含命令,用来引入对应的头文件。#include 的处理过程就是将头文件的内容插入到该命令所在的位置,从而把头文件和当前源文件连接成一个源文件,这与复制粘贴的效果相同。
<> 与“”的区别
使用尖括号< >和双引号" “的区别在于头文件的搜索路径不同:
使用尖括号< >,编译器会到系统路径下查找头文件;比如标准库文件等
使用双引号” ",编译器首先在当前目录下查找头文件,如果没有找到,再到系统路径下查找。一般情况我们自定义的头文件用双引号引用。
注意
同一个头文件可以被多次引入,但是多次引入的效果和一次引入的效果相同,因为头文件在代码层面有防止重复引入的机制
文件包含允许嵌套,即在一个被包含的文件中又可以包含另一个文件。
不管是标准头文件,还是自定义头文件,都只能包含变量和函数的声明,不能包含定义。在头文件中定义定义函数和全局变量这种认知是原则性的错误!否则在多次引入时会引起重复定义错误。
#define
功能特点
#define 叫做宏定义命令。即用一个标识符来表示一个字符串,如果在后面的代码中出现了该标识符,那么就全部替换成指定的字符串。
宏定义的本质是在编译之前进行简单的替换,不对表达式进行任何的计算。
宏定义中的宏名是标识符的一种,命名规则和变量相同。字符串可以是数字、表达式、if 语句、函数
宏定义不是说明或语句,在行末不必加分号,如加上分号则连分号也一起替换
宏定义必须写在函数之外,其作用域为宏定义命令起到源程序结束。如要终止其作用域可使用#undef命令
带参数的宏定义
带参宏定义的一般形式:#define 宏名(形参列表) 字符串
带参宏调用的一般形式为:宏名(实参列表);
特点:
a、对带参数的宏,在展开过程中不仅要进行字符串替换,还要用实参去替换形参(先替换字符串、再替换参数)
b、在带参宏定义中,不会为形式参数分配内存,因此不必指明数据类型.
带参宏定义和函数的区别
区别:
a、宏展开仅仅是字符串的替换,不会对表达式进行计算。且宏在编译之前就被处理掉了,它没有机会参与编译,也不会占用内存。
b、函数是一段可以重复使用的代码,会被编译,会给它分配内存,每次调用函数,就是执行这块内存中的代码。
宏参数的字符串化和宏参数的连接(#和##)
#的用法和特点
#用来将宏参数转换为字符串,即在宏参数的开头和末尾添加引号
实例:
#include <stdio.h>
#define S(string) #string
int main()
{
printf("%s\n", S(abcdefg)); // 打印输出 "abcdefg"
printf("%s\n", S("abcdefg")); // 打印输出 "\"abcdefg\""
return 0;
}
##的用法和特点
##称为连接符,用来将宏参数或其它的字符串连接起来
#include <stdion.h>
#define CONNECT_1(a, b) a##e##b##
#define CONNECT_2(a, b) a##b##56
int main()
{
printf("%f\n", CONNECT_1(1.234, 3)); // 被展开为1.234e3 打印输出 1234.000000
printf("%d\n", CONNECT_2(12, 34)); // 被展开为123456 打印输出 123456
return 0;
}
条件编译
什么是条件编译
能够根据不同情况编译不同代码、产生不同目标文件的机制,称为条件编译。条件编译是预处理程序的功能,不是编译器的功能
实例
a、输出红色的文字,并且要求跨平台,在 Windows 和 Linux 下都能运行,这个程序的难点在于,不同平台下控制文字颜色的代码不一样,我们必须要能够识别出不同的平台。Windows 有专有的宏_WIN32,Linux 有专有的宏__linux__
#include <stdio.h>
int main(){
#if _WIN32
system("color 0c");
printf("http://c.biancheng.net\n");
#elif __linux__
printf("\033[22;31mhttp://c.biancheng.net\n\033[22;30m");
#else
printf("http://c.biancheng.net\n");
#endif
return 0;
}
b、VS/VC 有两种编译模式,Debug 和 Release。在学习过程中,我们通常使用 Debug 模式,这样便于程序的调试;而最终发布的程序,要使用 Release 模式,这样编译器会进行很多优化,提高程序运行效率,删除冗余信息。
#include <stdio.h>
#include <stdlib.h>
int main(){
#ifdef _DEBUG
printf("正在使用 Debug 模式编译程序...\n");
#else
printf("正在使用 Release 模式编译程序...\n");
#endif
system("pause");
return 0;
}
当以 Debug 模式编译程序时,宏 _DEBUG 会被定义,预处器会保留第 5 行代码,删除第 7 行代码。反之会删除第 5 行,保留第 7 行。
#if 用法的一般格式
#if 整型常量表达式1
程序段1
#elif 整型常量表达式2
程序段2
#elif 整型常量表达式3
程序段3
#else
程序段4
#endif
#ifdef 用法的一般格式为:
#ifdef 宏名
程序段1
#else
程序段2
#endif
#ifndef 用法的一般格式为:
#ifndef 宏名
程序段1
#else
程序段2
#endif
注意:#if 后面跟的是“整型常量表达式”,而 #ifdef 和 #ifndef 后面跟的只能是一个宏名,不能是其他的
#error命令
功能特点
#error 指令用于在编译期间产生错误信息,并阻止程序的编译。形式为
#error error_message
实例
a、我们的程序针对Linux编写,不保证兼容Windows
#ifdef WIN32
#error This programme cannot compile at Windows Platform
#endif
WIN32 是Windows下的预定义宏。当用户在Windows下编译该程序时,由于定义了WIN32这个宏,所以会执行 #error 命令,提示用户发生了编译错误,错误信息是:
This programme cannot compile at Windows Platform
b、当我们希望以C++的方式来编译程序
#ifndef __cplusplus
#error 当前程序必须以C++方式编译
#endif
注意:报错信息不需要加引号 " " ,如果加上,引号会被一起输出
C语言中几个常用的预定义宏
__ LINE__: 表示当前源代码的行号;
__ FILE__: 表示当前源文件的名称;
__ DATE__: 表示当前的编译日期;
__ TIME__: 表示当前的编译时间;
__ STDC__: 当要求程序严格遵循ANSI C标准时该标识被赋值为1;
__ cplusplus: 当编写C++程序时该标识符被定义。
__ FUNCTION__(或者__func__):表示当前的调用的函数名称