C语言 预处理详解(#include、#define、#if,特殊预定宏)

简介: C语言 预处理详解(#include、#define、#if,特殊预定宏)

预处理


基本概念


C语言对源程序处理的四个步骤:预处理、编译、汇编、链接。


预处理是在程序源代码被编译之前,由预处理器(Preprocessor)对程序源代码进行的处理。


这个过程并不对程序的源代码语法进行解析,但它会把源代码分割或处理成为特定的符号为下一步的编译做准备工作。



文件包含指令(#include)


文件包含处理


“文件包含处理”是指一个源文件可以将另外一个文件的全部内容包含进来。C语言提供了#include命令用来实现“文件包含”的操作。


例如下图简单示例:



#incude<>和#include""区别


区别:


  • “” 表示系统先在file1.c所在的当前目录找file1.h,如果找不到,再按系统指定的目录检索。


  • < > 表示系统直接按系统指定的目录检索


注意:


  1. #include <>常用于包含库函数的头文件;


  1. #include ""常用于包含自定义的头文件;


  1. 理论上#include可以包含任意格式的文件(.c .h等) ,但一般用于头文件的包含(.h);


宏定义(#define)


无参数的宏定义(宏常量)


如果在程序中大量使用到了100这个值,那么为了方便管理,我们可以将其定义为:


const int num = 100;


但是如果我们使用num定义一个数组,在不支持c99标准的编译器上是不支持的,因为num不是一个编译器常量,如果想得到了一个编译器常量,那么可以使用:


#define num 100


在编译预处理时,将程序中在该语句以后出现的所有的num都用100代替。这种方法使用户能以一个简单的名字代替一个长的字符串,在预编译时将宏名替换成字符串的过程称为“宏展开”。


宏定义,只在宏定义的文件中起作用。


#define PI 3.1415
void test(){
  double r = 10.0;
  double s = PI * r * r;
  printf("s = %lf\n", s);
}


带参数的宏定义(宏函数)


在项目中,经常把一些短小而又频繁使用的函数写成宏函数,这是由于宏函数没有普通函数参数压栈、跳转、返回等的开销,可以调高程序的效率。


宏通过使用参数,可以创建外形和作用都与函数类似地类函数宏(function-like macro)。宏的参数也用圆括号括起来。


#define SUM(x,y) (( x )+( y ))
void test(){
  //仅仅只是做文本替换 下例替换为 int ret = ((10)+(20));
  //不进行计算
  int ret = SUM(10, 20);
  printf("ret:%d\n",ret);
}


注意:


  1. 宏的名字中不能有空格,但是在替换的字符串中可以有空格。ANSI C允许在参数列表中使用空格;


  1. 用括号括住每一个参数,并括住宏的整体定义。


  1. 用大写字母表示宏的函数名。


  1. 如果打算宏代替函数来加快程序运行速度,需要用多次才能得以体现。假如在程序中只使用一次宏对程序的运行时间没有太大提高。


宏定义几点说明:


  1. 宏名一般用大写,以便于与变量区别;


  1. 宏定义可以是常数、表达式等;


  1. 宏定义不作语法检查,只有在编译被宏展开后的源程序才会报错;


  1. 宏定义不是C语言,不在行末加分号;


  1. 宏名有效范围为从定义到本源文件结束;


  1. 可以用#undef命令终止宏定义的作用域;


  1. 在宏定义中,可以引用已定义的宏名;


条件编译(#if)


基本概念


一般情况下,C语言源程序中的每一行代码都要参加编译。但有时候出于对程序代码优化的考虑,希望只对其中一部分内容进行编译,此时就需要在程序中加上条件,让编译器只对满足条件的代码进行编译,将不满足条件的代码舍弃,这就是条件编译。



条件编译格式


在C语言中,若要对程序中的代码段有条件地进行编译,就要用到条件编译命令,条件编译主要有如下几种格式:


if格式


#if 表达式
     语句序列①
[#else
     语句序列②]
#endif


功能:当表达式的值为真时,编译语句序列①,否则编译语句序列②。其中,#else和语句序列②可有可无。


ifdef格式


#ifdef     标识符
     语句序列①
[#else
     语句序列②]
#endif


功能:当标识符已被定义时(用#define定义),编译语句序列①,否则编译语句序列②。其中#else和语句序列②可有可无。


ifndef格式


#ifndef     标识符
     语句序列①
[#else
     语句序列②]
#endif


功能:该格式功能与ifdef相反。


用法


防止头文件被重复包含引用:


#ifndef _SOMEFILE_H
#define _SOMEFILE_H
//需要声明的变量、函数
//宏定义
//结构体
#endif


注意:这里的_SOMEFILE_H不是固定的,下面我们举个栗子。


栗子


设有3个源文件如下图所示,其中存在着对同一个源文件重复包含的问题。请修改程序,不要删除代码,利用条件编译避免重复包含,使得每个源文件都能通过编译。



分析:


源文件a.c中的main()函数要调用b.c中的subl()函数,也要调用c.c中的sub2()函数,所以,文件a.c包含了b.c和c.c。源文件b.c中的sub3()函数要调用c.c中的 sub2()函数,所以b.c也包含c.c。这样一来就造成了重复包含的问题,即文件a.c包含了两次c.c,a.c的内容相当于是这样的:



其中c.c的内容出现了两遍,就有了两次sub2()函数的定义,导致编译错误。


要解决这个问题,需要用条件编译,将c.c的内容放在条件编译控制之下,即将c.c的内容修改为


//c.c
#ifndef FILE_C
#define FILE_C
void sub()
{
  ...
}
#endif


这样就可以避免c.c的内容被重复包含。


一些特殊的预定宏


C编译器,提供了几个特殊形式的预定义宏,在实际编程中可以直接使用,很方便。


//  __FILE__      宏所在文件的源文件名 
//  __LINE__      宏所在行的行号
//  __DATE__      代码编译的日期
//  __TIME__      代码编译的时间
void test()
{
  printf("%s\n", __FILE__);
  printf("%d\n", __LINE__);
  printf("%s\n", __DATE__);
  printf("%s\n", __TIME__);
}


运行结果为:


相关文章
|
3月前
|
编译器 C语言
C语言--预处理详解(1)
【10月更文挑战第3天】
|
3月前
|
编译器 Linux C语言
C语言--预处理详解(3)
【10月更文挑战第3天】
|
1月前
|
C语言
【C语言】全局搜索变量却找不到定义?原来是因为宏!
使用条件编译和 `extern` 来管理全局变量的定义和声明是一种有效的技术,但应谨慎使用。在可能的情况下,应该优先考虑使用局部变量、函数参数和返回值、静态变量或者更高级的封装技术(如结构体和类)来减少全局变量的使用。
45 5
|
2月前
|
程序员 编译器 C语言
C语言中的预处理器指令,涵盖其基本概念、常见指令(如`#define`、`#include`、条件编译指令等)、使用技巧及注意事项
本文深入解析C语言中的预处理器指令,涵盖其基本概念、常见指令(如`#define`、`#include`、条件编译指令等)、使用技巧及注意事项,并通过实际案例分析,展示预处理器指令在代码编写与处理中的重要性和灵活性。
82 2
|
2月前
|
C语言
【c语言】你绝对没见过的预处理技巧
本文介绍了C语言中预处理(预编译)的相关知识和指令,包括预定义符号、`#define`定义常量和宏、宏与函数的对比、`#`和`##`操作符、`#undef`撤销宏定义、条件编译以及头文件的包含方式。通过具体示例详细解释了各指令的使用方法和注意事项,帮助读者更好地理解和应用预处理技术。
35 2
|
3月前
|
编译器 C语言
C语言:typedef 和 define 有什么区别
在C语言中,`typedef`和`#define`都是用来创建标识符以简化复杂数据类型或常量的使用,但它们之间存在本质的区别。`typedef`用于定义新的数据类型别名,它保留了数据类型的特性但不分配内存。而`#define`是预处理器指令,用于定义宏替换,既可用于定义常量,也可用于简单的文本替换,但在编译前进行,过度使用可能导致代码可读性下降。正确选择使用`typedef`或`#define`可以提高代码质量和可维护性。
|
3月前
|
C语言
C语言--预处理详解(2)
【10月更文挑战第3天】
|
C语言 关系型数据库 PostgreSQL
|
10天前
|
存储 算法 C语言
【C语言程序设计——函数】素数判定(头歌实践教学平台习题)【合集】
本内容介绍了编写一个判断素数的子函数的任务,涵盖循环控制与跳转语句、算术运算符(%)、以及素数的概念。任务要求在主函数中输入整数并输出是否为素数的信息。相关知识包括 `for` 和 `while` 循环、`break` 和 `continue` 语句、取余运算符 `%` 的使用及素数定义、分布规律和应用场景。编程要求根据提示补充代码,测试说明提供了输入输出示例,最后给出通关代码和测试结果。 任务核心:编写判断素数的子函数并在主函数中调用,涉及循环结构和条件判断。
47 23
|
10天前
|
算法 C语言
【C语言程序设计——函数】利用函数求解最大公约数和最小公倍数(头歌实践教学平台习题)【合集】
本文档介绍了如何编写两个子函数,分别求任意两个整数的最大公约数和最小公倍数。内容涵盖循环控制与跳转语句的使用、最大公约数的求法(包括辗转相除法和更相减损术),以及基于最大公约数求最小公倍数的方法。通过示例代码和测试说明,帮助读者理解和实现相关算法。最终提供了完整的通关代码及测试结果,确保编程任务的成功完成。
38 15

热门文章

最新文章