当前位置: 首页 > news >正文

C语言——预处理详解

目录

1 预定义符号

2 #define定义常量

3 #define定义宏

4 带有副作用的宏参数

5 宏替换的规则 

6 宏函数的对比

7 #和##

8 命名约定 

9 #undef 

10 条件编译 

11 头文件的包含 

11.1 本地文件包含

11.2 库文件包含 

11.3 嵌套文件包含 


1 预定义符号

C语言设置了一些预定义符号,可以直接使用,预定义符号是在预处理期间处理的。

1 _ _FILE_ _//进行编译的源文件

2 _ _LINE_ _//文件当前的行号

3 _ _DATE_ _//文件被编译的日期

4 _ _TIME_ _//文件被编译的时间

5 _ _ STDC_ _//如果编译器遵循ANSIC,其值为1,否则未定义

举个例子:

#include <stdio.h>
int main()
{printf("%s\n", __DATE__);printf("%s\n", __TIME__);printf("%d\n", __LINE__);return 0;
}

结果显示日期,时间和行号:

2 #define定义常量

基本语法:

#define name stuff

 一些使用例子:

#define MAX 1000;
#define reg register //为register关键字创建一个名字reg
#define do_forever for(;;)//for(;;)是死循环语句,可以用do_forever这种更形象的符号替代
#define CASE break;case//在写case语句的时候自动把break加上
//如果定义的stuff过长,可以分开几行写,除了最后一行外,每行的后面都加一个\(续航符)
#define DEBUG_PRINT printf("line:%d\t \date:%s\t \time:%s\n",\__LINE__,\__DATE__,\__TIME__)

在define定义标识符,要不要加上分号(;)?

例如:

#define MAX 1000
#define MAX 1000;

 在define定义标识符时,最好不要加;,容易产生问题。例如:

#include <stdio.h>
#define MAX 1000;
int main()
{int max = 0;if (1)max = MAX;elsemax = 1;return 0;
}

如果加了分号,替换后为:max=1000;; 这就是两条语句,没有大括号时,if后只能有一条语句,会出现语法错误,所以define定义标识符时,最好不要加分号。

3 #define定义宏

#define机制允许把参数替换到文本中,这种实现方式通常称为宏(macro) 或者定义宏(define macro)。下面是宏的申明方式:

#define name(parament-list) stuff 

其中的parament-list是一个由逗号分开的符号表,它们可能出现在stuff中。

注意:参数列表的左括号必须与name紧邻,如果有任何空白存在,参数列表就会被解释为stuff的一部分。

举例:

#define SQUARE (x) x*x

这个宏接受一个参数x,假设x=5,将SQUARE(5)用在程序中,预处理器就会用5*5替换SQUARE(5)。

这个宏在使用时可能会存在一个问题,如果代码这么写:

#include <stdio.h>
#define SQURE(x) x*x
int main()
{int a = 5;printf("%d\n", SQURE(a + 1));return 0;
}

乍一看这个代码输出结果时36,但是实际是11,这是因为参数x被替换成a+1,预处理时会将SQURE(a+1)替换成a+1*a+1,根据算数优先级,结果就为11。所以如果我们在宏定义上加两个括号,这个问题就解决了:

#define SQUARE(x) (x)*(x)

再举一个例子:

#define DOUBLE(x) (x)+(x)

这回加了括号,想避免之前的问题,但是这个宏在使用时可能还会出现错误,举个例子:

#include <stdio.h>
#define DOUBLE(x) (x)+(x)
int main()
{int a = 5;printf("%d\n", 10*DOUBLE(a));return 0;
}

结果好像是打印100,但是实际结果是55。这是因为10*DOUBLE(a)在预处理时替换成10*(5)+(5),结果是55,解决办法就是在宏定义表达式两边加上一对括号就可以了。

#define DOUBLE(x) ((x)+(x))

所以对于数值表达式进行求值的宏定义都应该用这种方式加上括号。避免临近操作符之间的不可预料的作用。

4 带有副作用的宏参数

当宏参数在宏定义中出现超过一次的时候,如果参数带有副作用,使用宏时就有可能造成不可预料的结果。副作用就是表达式求值时出现的永久性效果。

例如:

x+1;//不带副作用

x++;//带有副作用

#include <stdio.h>
#define MAX(a,b) ((a)>(b)?(a):(b))
int main()
{int x = 5;int y = 8;int z = MAX(x++, y++);printf("x=%d y=%d z=%d\n",x,y,z);return 0;
}

 

所以结果为x=6 y=10 z=9

5 宏替换的规则 

在程序中#define定义符号和宏时,需要有如下几个步骤:

  • 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换
  • 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。
  • 最后,再次对结果文件进行扫描,看是否有任何#define定义的符号,如果是,重复上述流程。

 注意:

  • 宏参数和#define定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。
  • 当预处理器搜索#define定义的符号时,字符串常量的内容并不被搜索。

举个例子:

#include <stdio.h>
#define MAX(a,b) ((a)>(b)?(a):(b))
#define M 10
int main()
{int x = 5;int z = MAX(x, MAX(2,3));printf("x=%d M=%d z=%d\n", x, M, z);return 0;
}

 输出结果:

其中“M=%d"中的M不会被替换。

6 宏函数的对比

宏常常被应用于简单的运算。

比如求两个数较大的一个数,写成宏更有优势一些:

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

 举个例子:

#include <stdio.h>
#define MAX(a,b) ((a)>(b)?(a):(b))
int main()
{int a = 10;int b = 11;printf("%d\n", MAX(a, b));//输出11return 0;
}

为什么不用函数来完成呢?有两个原因:

  • 函数在调用和返回值时需要时间,所以宏比函数在程序的简洁性和速度更胜一筹
  • 再者函数参数需要特定的类型,只能在符合这个类型的数上使用,宏参数是类型无关的,整型,长整型,短整型,浮点型等同时适用。

宏的参数可以出现类型,函数做不到:

#include <stdio.h>
#include <stdlib.h>
#define MALLOC(num,type) (type*)malloc(num*sizeof(type))
int main()
{int* ptr=MALLOC(5,int);if (ptr == NULL){perror("malloc");return 1;}free(ptr);ptr = NULL;return 0;
}

和函数相比,宏的劣势:

  • 每次使用宏时,一份宏定义的代码将插入到程序中。可能会大幅度增加宏的长度
  • 宏是没办法调试的
  • 宏由于类型无关,所以不够严谨
  • 宏可能会带来运算符优先级的问题

总结一下宏和函数的对比:

属性#define定义宏函数
代码长度除了非常小的宏外,程序长度大幅度增长函数代码只出现在那一个地方
执行速度更快比宏慢一些
操作符优先级可能会因为操作符优先级造成不可预料的后果表达式的结果更容易预测
带有副作用的宏参数如果宏被多次计算,带有副作用参数求值可能会造成不可预料的结果函数只在传参时求值一次,结果更容易控制
参数类型可以使用任何参数类型参数类型不同,就要使用不同的函数
调试不方便调试可以逐语句调试
递归不能递归可以递归

7 #和##

##可以把位于它两边的符号合成一个符号,它允许宏定义从分离的文本片段创建标识符。

写一个函数求两个数的最大值时,不同的数据类型就得写不同的函数。例如:

int int_max(int x, int y)
{return x > y ? x : y;
}
float float_max(float x, float y)
{return x > y ? x : y;
}

这样写起来比较繁琐,如果用宏定义:

#include <stdio.h>
#define NUM_MAX(type)      \
type type##_max (type x,type y)\
{                              \return (x>y)?x:y;\
}NUM_MAX(int)
NUM_MAX(float)int main()
{int m = int_max(2, 3);printf("%d\n", m);float n = float_max(3.4f, 5.4f);printf("%f\n", n);return 0;
}

这样使用宏内部的int##_max和float##_max生成int_max和float_max做函数名。

8 命名约定 

一般来说函数的宏的使用语法相似,语言本身没法区分二者,所以尽可能:

  • 宏名全部大写
  • 函数名不要全大写

9 #undef 

#undef用来移除一个宏定义

语法:

#undef NAME

一个名字被重新定义,旧名字就要移除

10 条件编译 

在编译一个程序的时候我们要将一条语句或一组语句编译或者放弃是很方便的,因为可以用条件编译指令。

常见的条件编译指令

#if 常量表达式

语句

#endif

//表达式为真,执行语句,否则不执行

类似的,多分支条件编译:

#if 常量表达式

//

#elif 常量表达式

//

#else 

//

#endif 

判断是否被定义:

#ifdef symbol 如果宏定义了symbol ,执行语句

#ifndef symbol 如果没有宏定义symbol ,执行语句

11 头文件的包含 

11.1 本地文件包含

#include “filename”

查找策略:先在源文件所在根目录下查找,如果找不到,就像查找库函数头文件一样在标准位置查找头文件。

11.2 库文件包含 

#include <filename.h>

查找头文件直接去标准位置查找,如果找不到就提示错误。

11.3 嵌套文件包含 

一个头文件包含几次就编译几次,如果重复包含,编译的压力就比较大。

例如:

#include "test.h"
#include "test.h"
#include "test.h"
#include "test.h"
int main()
{return 0;
}

如果直接这样写,test.h里的文件就会拷贝5次放在源文件中,如果工程比较大,会造成严重后果。所以引入条件编译来避免头文件重复引用:


#ifndef __TEST_H__
#define __TEST_H__
//头文件内容
#endif//或者
#pragma once

 以上就是有关预处理的常见知识了,如果这篇文章对你有用,可以点点赞哦,你的支持就是我写下去的动力,后续会不断地分享知识。

http://www.dtcms.com/a/272280.html

相关文章:

  • C#中异步任务取消:CancellationToken
  • 【C++详解】STL-list模拟实现(深度剖析list迭代器,类模板未实例化取嵌套类型问题)
  • 【TCP/IP】10. 引导协议与动态主机配置协议
  • prometheus+grafana接入nginx实战
  • 零成本实现商品图换背景
  • 静态路由实验(2)
  • Vue3 深度解析:渲染器与渲染函数的奥秘
  • 【PTA数据结构 | C语言版】链式栈的3个操作
  • linux 4.14 kernel屏蔽arm arch timer的方法
  • 网络编程与自动化
  • 高亚科技签约奕源金属,助力打造高效智能化采购管理体系
  • Flask 入门教程:用 Python 快速搭建你的第一个 Web 应用
  • 在 Ubuntu 上安装和配置 Kafka
  • 下一代防火墙-终端安全防护
  • 普林斯顿大学DPPO机器人学习突破:Diffusion Policy Policy Optimization 全新优化扩散策略
  • Eigen 几何模块深拆:Isometry3d vs Affine3d + 变换矩阵本质详解
  • OSPF协议:核心概念与配置要点解析
  • 虚拟项目[3D物体测量]
  • 从真人到数字分身:3D人脸扫描设备在高校数字人建模教学中的应用
  • 强化学习 MDP
  • Selenium 4 教程:自动化 WebDriver 管理与 Cookie 提取 || 用于解决chromedriver版本不匹配问题
  • 《PyQt6-3D:开启Python 3D开发新世界》
  • Windows Edge 播放 H.265 视频指南
  • OpenAI正准备推出一款搭载人工智能功能的网络浏览器,试图直接挑战Alphabet旗下
  • 前端面试专栏-算法篇:21. 链表、栈、队列的实现与应用
  • NAT技术(网络地址转换)
  • 【实战】使用 ELK 搭建 Spring Boot Docker 容器日志监控系统
  • OSPF实验以及核心原理全解
  • 【SkyWalking】配置告警规则并通过 Webhook 推送钉钉通知
  • HP EVA SAN 数据恢复利器:Data recovery plugin for HP StorageWorks EVA