首页 > 其他分享 >#define#include 定义 预处理入门详解(C语言)

#define#include 定义 预处理入门详解(C语言)

时间:2024-09-25 20:47:53浏览次数:10  
标签:定义 ++ C语言 编译 include 替换 define

本篇博客会解答如下问题:


1.#include <>与""的区别

2.头文件中ifdef/ ifndef/define/endif分别的作用是什么

3.defined 定义需要加 ';' 吗

4.#pragme once是干什么用的

5.define定义常量

6.define定义宏

本篇博客共为 2800 余字,问题都在博客当中做得回答,目录有部分问题快捷键。


前言:根据ANSI C的任何一种实现,为翻译环境+运行环境。

翻译环境由编译和链接两个大类组成,编译可以拆解为预编译、编译、汇编三个过程。

         {预编译->编译->汇编}->链接   最后成为可执行程序

本文讲述的是.c文件->.s文件的预处理阶段。预处理阶段主要处理源文件中#开始的预编译命令,如#include/#define。

在此过程中,1.将展开所有define,并展开所有宏定义

                      2.删除所有注释

                      3.处理 #include 预编译指令,头文件的内容展开。整个过程是递归进行的,被包含的头文件也可能包含其他文件。

                      4.添加行号和文件名标识,方便后续编译器生成调试信息

                      5.处理所有的条件编译指令,如:#ifndef / #endif等,保留#pragma的编译器指令

例:                                                                test.c

test.i

我们可以看到处理了#include预编译命令(.i图片上半部分),将define定义的常数全部替换,增加了行号和文件名标识。

基本语法: #define name stuff

重要:define的作用就是纯替换,在进行预编译时,将name->stuff全部替换

问题1:define后面需要加 ';' 吗?

#define MAX 1000;

...

if(条件)

max = MAX;

else

...

建议不要 ';' ,如遇见上述代码,在if没有大括号的前提下,只能执行一条语句,define替换后,为max=1000;;,出现语法错误。


当我们知道了define就是纯替换,我们可以做很多事情

define宏定义

如果我们这样定义:

#define MUL(a)  a*a

当我们使用这个定义时:

int c=5;

int b;

b=MUL(c);

上述代码变为:(.i文件)

int c=5;

int b;

b=c*c;

#define机制包括了⼀个规定,允许把参数替换到文本中,这种实现通常称为宏

在我们使用宏中:要及时添加括号,保证替换后进行运算时运算优先级不发生改变。

如:让上述代码变为

b=MUL(c+1);   --> 本意想变为(c+1)*(c+1)-->但替换后就变为了 c + 1 * c + 1

在定义时加上括号:#define MUL(a)  (a)*(a) 就不会出现上述问题了。

那么      #define ADD(x)  (x)+(x)     会有问题么?

在我们要使用这样的代码就会出现问题:

a=10;

printf("%d",5*ADD(a));     如果本意是(10)+(10)后再×5,那么就出现错误了 

                                         替换后就为5*(10)+(10)=50+10=60;

当我们将#define  ADD(x)   ((x)+(x)); 就可以避免这个错误了。

#define MAX(a, b) ( (a) > (b) ? (a) : (b) )

MAX(x++, y++)

因为是替换,所以不是像函数一样传参,在进行比较时会产生多余的++赋值,再次改变了数值本身的大小( (x++) > (y++) ? (x++) : (y++) )。这样叫带有副作⽤的宏参数。

上面这个示例MAX(x++,y++)如果是函数也会改变数值,不过是define定义的在( (x++) > (y++) ? (x++) : (y++) )时会给其中一个变量多加一次。

所以,在我们定义时,一定要清楚替换后的运算是如何进行的。

在宏替换时,1.参数有define定义的符号优先替换

                      2.替换文本插入到程序原来位置。对于宏,参数名被他们的值所替换

                      3.最后在查看是否包含任何由#define定义的符号,如果有重复上述过程

至此,我们可以有很多种写代码的方式。如:

当要比较大小时,可以直接用宏定义

#define MAX(a,b)  ((a)>(b)?(a):(b))

相较于函数,这样就剪掉了调用函数和从函数返回时间,并且不用设置函数返回和比较类型。(宏可以直接比较整形或浮点型等)

#运算符将宏的⼀个参数转换为字符串字面量。它仅允许出现在带参数的宏的替换列表中。#运算符所执行的操作可以理解为”字符串化“。

当我想在printf中当字符串打印,并做为变量时:

#define PT(a) printf("shuchu:"    #a    "=%d",a);

替换为 printf("shuchu:"     "a"    "=%d",a);

#a替换为“a”   使其可以以字符串的形式输出

##运算符可以把位于它两边的符号合成⼀个符号,它允许宏定义从分离的文本片段创建标识符。 例如:#define type  a    替换时,  type-->a   type_add 就不能替换为a了,但加上##,type##_add-->a_add

#undef  用于取消定义,在此之后命令不会再替换


#ifdef/#endif  等条件编译

#ifdef MAN

printf("man");

#endif

ifdef条件编译,如果上面进行了 #define MAN 声明,那么执行里面的printf语句。在此条件编译中,只关心有没有进行声明,不关心声明值为多少 #define MAN 0 也是可以的。

#ifndef 如果没有被定义则执行

常见的有:

1.常量表达式由预处理器求值

#if 常量表达式

...

#endif

例:

#define  MAN 1

#if MAN

执行...

#endif

2.多个分支的条件编译

#if 常量表达式

...

#elif 常量表达式

...

#else 

...

#endif

3.判断是否被定义

#if  defined(symbol)     如果定义了,便执行。if  defined后定义带括号!!

#ifdef  symbol       如果定义了,便执行。  无括号

#if  !defined(symbol)    反之

#ifndef  symbol

色块相同的两个语句效果相同

4.嵌套指令

#if  常量表达式

                           #ifdef

                           ...

                           #endif

#elif  常量表达式

                           #ifdef

                           ...

                           #endif

#endif


问题2:#include <>与""的区别,可以直接用""吗?

""先在源文件目录下查找,找不到再在标准位置找。都找不到报错。

<>直接在标准位置找。

可以都用"",不过<>头文件会现在源文件目录下找一遍。


#pragme once是干什么用的

#include引用时,如果写三遍,#include "test.h"。

#include "test.h"

#include "test.h"

#include "test.h"

test.c文件中将test.h包含3次,那么test.h文件的内容将会被拷贝3份在test.c中。

在前面加上#pragme once就可以避免重复引入

同样用条件编译也可以避免,在每个头文件开头写:

#ifdef   _TEST_

define  _TEST_

头文件内容...

#endif

当定义过一次后,不满足条件,就不会再次引入了。


本篇关于 预处理 到此就结束了。

如果你觉得本文还不错,请你给我一个赞吧!感谢~~

标签:定义,++,C语言,编译,include,替换,define
From: https://blog.csdn.net/m0_73640343/article/details/142344704

相关文章

  • C语言-循环结构
    0.引入   例子:求100以内所有整数之和     intsum=1+2+3+...+100;     intsum=0;    inti=1;    sum=sum+i;    i++;      //上述的两个语句重复100次   循环的本质就是重复,......
  • 梳理一下C语言中的格式说明符
    整数类型%d:带符号的十进制整数(适用于int类型)。%hd或%hi:带符号的短整数(适用于shortint类型)。%ld:带符号的长整数(适用于longint类型)。%lld:带符号的长长整数(适用于longlongint类型)。%i:同%d,可以自动识别有符号整数大小。%u:无符号的十进制整数(适用于unsignedint)。%hu:无符号的短......
  • c语言中字符串输入的相关知识点
    (1)scanf只能接收非空格字符串遇到空格或者换行就算结束。代码如下:#include<stdio.h>#include<stdlib.h>intmain(){chararr[10];scanf("%s",&arr);printf("%s",arr);}(2)gets函数-能够接收空格,但是不能接收回车#include<stdio.h>#include&l......
  • c语言实现最小堆和最大堆
    第一部分:最大堆和最小堆的基本性质(1)基本定义①最大堆根是这颗树最大的值,每个根节点都比  左右子节点的值大,对左右子树仍然成立;②最小堆根是这颗树的最小的值,每个根节点都比左右子节点的值小,同样对左右子树成立;(2)性质(数组下标关系)由堆构建的树的背后原理是基于完全......
  • C语言课程设计题目(24个选题)
    C语言课程设计题目题目一:职工信息管理系统设计题目二:图书信息管理系统设计题目三:图书管理系统设计题目四:实验设备管理系统设计题目五:西文下拉菜单的设计题目六:学生信息管理系统设计题目七:学生成绩管理系统设计题目八:学生选修课程系统设计题目九:学生成绩记录簿设计题目十:......
  • QT5程序部署提示缺少Qt5系统库问题的解决方法 symbol lookup error /libQt5XcbQpa.so.
    https://blog.csdn.net/qq_29852231/article/details/128853681 QT5程序部署提示缺少Qt5系统库问题的解决方法问题:在用QT5.12开发程序后,部署至现场(Ubuntu18/20)发现提示缺少QT5的平台库(platform)或者系统提供的QT5平台库无法正常支撑程序运行解析:经过研究发现,即时将Platform文件......
  • C语言数组探秘:数据操控的艺术【上】
    在C语言中数组是非常重要的,应用也是非常广泛的,它可以帮助我们更好的写代码,来解决问题。欧克,开始今天的数组的章节。一.数组的概念数组是一组相同类型元素的集合;从这个概念中我们就可以发现2个有价值的信息:数组中存放的是1个或者多个数据,但是数组元素个数不能为0。数组......
  • 理解C语言之深入理解指针(三)
    目录1.字符指针变量2.数组指针变量2.1数组指针变量是什么?2.2数组指针变量怎么初始化3.⼆维数组传参的本质4.函数指针变量4.1函数指针变量的创建4.2函数指针变量的使⽤4.3两段有趣的代码4.3.1typedef关键字5.函数指针数组6.转移表1.字符指针变......
  • 嵌入式C语言自我修养:C语言的面向对象编程思想
    ⭐关联知识点:C和C++的区别代码复用与分层思想什么是代码复用呢?(1)函数级代码复用:定义一个函数实现某个功能,所有的程序都可以调用这个函数,不用自己再单独实现一遍,函数级的代码复用。(2)将一些通用的函数打包封装成库,并引出API供程序调用,实现了库级的代码复用;(3)将一些类似的应用程序抽象成......
  • 嵌入式C语言自我修养:C语言的模块化的编程思想
    不同模块如何集成到系统中去?模块的编译和链接一个C语言项目划分成不同的模块,通常由多个文件来实现。在项目编译过程中,编译器是以C源文件为单位进行编译的,每一个C源文件都会被编译器翻译成对应的一个目标文件。链接器对每一个目标文件进行解析,将文件中的代码段、数据段分别组装,生成......