C语言 预处理详解

目录

1.预定义符号

2.#define

2.1#define 定义标识符

2.2#define 定义宏

2.3#define 替换规则

2.4#和##

2.4.1# 的作用

2.4.2## 的作用 

2.5 带有副作用的宏参数

2.6宏和函数的对比 

对比

**2.7内联函数 

2.8命名约定 

3.#undef

**4.命令行定义 

5.条件编译

常见的条件编译指令

6.头文件包含

6.1头文件被包含的方式

6.2嵌套文件的包含


1.预定义符号

__FILE__          //进行编译的源文件

__LINE__         //文件当前的行号

__DATE__       //文件被编译的日期

__TIME__        //文件被编译的时间

__STDC__       //如果编译器遵循ANSI C,其值为1,否则未定义

这些预定义符号都是语言内置的

举个例子:

2.#define

#define是一种预处理指令,他有两种用法:

  1. #define 定义常量(标识符)
  2. #define 定义宏 

2.1#define 定义标识符

语法:

        #define  name  stuff

举个例子:

#define 是完全替换,比如

所以在定义的时候,为了强调他是一个整体,需要自己带上括号:

注意:由于是完全替换,在define定义标识符的时候,不要在最后加   ;   否则替换的时候会将  也替换过去,会导致语法错误

2.2#define 定义宏

#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常会被解释为宏(macro)或定义宏(define macro)

下面是宏的声明方式:

#define name( parament-list ) stuff

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

注意:

参数列表的左括号必须与name紧邻

如果两者之间有任何空白存在,参数列表就会被释解释为stuff的一部分

如:

#define定义宏也是完全替换,比如:

为了防止出现失误,我们在声明的时候需要加上括号:

我们在写宏的时候,如果逻辑需要,我们可以加上足够多的括号来使宏变得完整

2.3#define 替换规则

在程序中扩展#define定义符号和宏时,需要涉及几个步骤

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

注意:

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

2.4#和##

2.4.1# 的作用

如何把参数插入到字符串中?、

我们发现字符串是有自动连接的特点的

假设有这样的代码:

我们如何用宏来实现printf的功能呢,这里我们使用#

他的替换是周怎么完成的呢 

这里只有当字符串作为宏参数的时候才可以把字符串放在字符串中

使用#,把一个宏参数变成对应的字符串 

比如:代码中的#N会被预处理器处理为:“N”

所以“#N”即被处理为““N””

2.4.2## 的作用 

##可以把位于他两边的符号合成一个符号

他允许宏定义从分离的文本片段创建标识符

注意:这样的连接必须产生一个合法的标识符,否则其结果就是未定义的

2.5 带有副作用的宏参数

当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么在使用这个宏的时候就可能出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的有永久性效果

x+1;//不带副作用

x++;//带副作用

MAX宏可以证明具有副作用的参数所引起的问题 

这段代码输出的结果是什么?

这里我们得知道预处理之后的结果是什么: 

这段代码是证明执行的呢?

2.6宏和函数的对比 

宏通常被应用于执行简单的运算

比如在两个数中找出较大的一个

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

那为什么不用函数来完成这个任务?

原因有二:

  1. 用于调用函数和从函数返回的代码可能实际执行这个小型计算工作所需要的时间更多
    所以宏比函数在程序的规模和速度方面更胜一筹
  2. 更为重要的是函数的参数必须声明为特定的类型
    所以函数只能在类型合适的表达式上使用
    宏是类型无关的

 宏的缺点:当然和函数相比,宏也有劣势的地方:

  1. 每次使用宏的时候,一份宏定义的代码将插入到程序中,除非宏比较短,否则可能大幅度增加程序的长度
  2. 宏是没法调试的
  3. 宏由于类型无关,也就不够严谨
  4. 宏可能会带来运算符优先级的问题,导致过程容易出现错误

 宏有时候可以做函数做不到的事情,比如:宏的参数可以出现类型,但是函数做不到

对比

建议:

如果逻辑比较简单,可以使用宏来实现

如果计算逻辑比较负责,那么就使用函数实现 

**2.7内联函数 

C99之后,C++引入了内联函数的概念  inline关键字

内联函数具有函数和宏的双重优点:

  1. 内联函数是函数
  2. 内联函数又像宏一样,在调用的地方展开

2.8命名约定 

一般来讲函数的宏的使用语法很相似。所以语言本身没法帮我们区分二者

那我们平时的一个习惯是:

  • 把宏名全部大写               //MAX
  • 函数名不要全部大写        //Max

3.#undef

这条指令用于移除一个宏定义

**4.命令行定义 

许多C的编译器提供了一种能力,允许在命令行中定义符号,用于启动编译过程

例如:当我们根据同一个源文件要编译出一个程序的不同版本的时候,这个特性有点用处

(假定某个程序中声明了一个某个长度的数组,如果机器内存有限,我们需要一个很小的数组,但是另外一 个机器内存大写,我们需要一个数组能够大写。)

5.条件编译

在编译一个程序序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的,因为我们有条件编译指令

条件编译就是:满足条件就编译,不满足条件就不编译

比如说:

调试性的代码,删除可惜,保留又碍事,所以我们可以选择性的编译

常见的条件编译指令

1.
#if   常量表达式
        //...
#endif
//常量表达式由预处理器求值
如:
#define __DEBUG__ 1
#if __DEBUG__
        //..
#endif

表达式为真则编译,为假则不编译

2.多个分支的条件编译
#if 常量表达式
        //...
#elif 常量表达式
        //...
#else
        //...
#endif

只会选择以一个#if或者#elif执行 

3.判断是否被定义
#if defined(symbol)
#ifdef symbol

#if !defined(symbol)
#ifndef symbol

判断(symbol)是否被定义过,如果被定义过则执行代码

4.嵌套指令
#if defined(OS_UNIX)
        #ifdef OPTION1
                unix_version_option1();
        #endif
        #ifdef OPTION2
                unix_version_option2();
        #endif
#elif defined(OS_MSDOS)        
        #ifdef OPTION2
                msdos_version_option2();
        #endif
#endif

注意:#if 与 #endif 是配套使用的,同时出现,同时消失

6.头文件包含

我们已经知道,#include 指令可以使另外一个文件被编译,就像它实际出现于 #include 指令的地方一样

这种替换的方式很简单:

预处理器先删除这条指令,并用包含文件的内容替换
这样一个源文件被包含10次,那就实际被编译10次

6.1头文件被包含的方式

头文件的包含一般有两种方式:

1.包含本地文件(自己的.h文件)
#include "xxx.h"(用双引号)

2.包含标准库中的文件
#include <xxx.h> (用尖括号)

查找策略:

先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件

#include包括""和<>这两种情况

""是在用户工作目录下寻找(用户的工作目录是通过编译器指定的
<>是找系统标准库函数,通过系统环境变量指定系统库目录

如果找不到就提示编译错误

6.2嵌套文件的包含

如果出现这样的场景

comm.h和comm.c是公共模块
test1.h和test1.c使用了公共模块
test2.h和test2.c使用了公共模块
test.h和test.c使用了test1模块和test2模块。
这样最终程序中就会出现两份comm.h的内容
这样就造成了文件内容的重复

我们可以用条件编译解决这个问题

每个头文件的开头写:

#ifndef __TEST_H__
#define __TEST_H__
//头文件的内容
#endif  //__TEST_H__

或者 

#pragma once

就可以避免头文件的重复引入

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/189113.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ElasticSearch中常见的分词器介绍

文章目录 ElasticSearch中常见的分词器介绍前言分词器的作用如何指定分词器分词器的组成分词器的类型标准分词器空格分词器简单分词器关键词分词器停用词分词器IK分词器NGram分词器正则匹配分词器语言分词器自定义分词器 ElasticSearch中常见的分词器介绍 前言 ElasticSearch是…

【C++】stack,queue和deque

stack的介绍 stack是一种容器适配器&#xff0c;专门用在具有后进先出操作的上下文环境中&#xff0c;其删除只能从容器的一端进行元素的插入与提取操作。stack是作为容器适配器被实现的&#xff0c;容器适配器即是对特定类封装作为其底层的容器&#xff0c;并提供一组特定 的成…

jupyter notebook中markdown改变图像大小

文章目录 &#x1f56e;原始图像&#x1f56e;改变图像大小&#x1f56e;使图像靠左 在 jupyter notebook中&#xff0c;导入的图片过大&#xff0c;想要改变图像的大小 &#x1f56e;原始图像 &#x1f56e;改变图像大小 复制小括号里面的内容到src后面&#xff0c;满足<…

Django的ORM操作

文章目录 1.ORM操作1.1 表结构1.1.1 常见字段和参数1.1.2 表关系 2.ORM2.1 基本操作2.2 连接数据库2.3 基础增删改查2.3.1 增加2.3.2 查找2.3.4 删除2.3.4 修改 1.ORM操作 orm&#xff0c;关系对象映射&#xff0c;本质翻译的。 1.1 表结构 实现&#xff1a;创建表、修改表、…

【ElasticSearch】学习使用DSL和RestClient编写查询语句

文章目录 DSL和RestClient的学习前言1、DSL查询文档1.1 查询分类1.2 全文检索查询1.21 全文检索概述1.2.2 基本使用 1.3 精确查询1.3.1 term查询1.3.2 range查询 1.4 地理坐标查询1.4.1 geo_bounding_box查询1.4.2 geo_distance查询 1.5 复合查询1.5.1 常见相关性算法1.5.2 算分…

WebRTC简介及使用

文章目录 前言一、WebRTC 简介1、webrtc 是什么2、webrtc 可以做什么3、数据传输需要些什么4、SDP 协议5、STUN6、TURN7、ICE 二、WebRTC 整体框架三、WebRTC 功能模块1、视频相关①、视频采集---video_capture②、视频编解码---video_coding③、视频加密---video_engine_encry…

SpringBoot系列-2 自动装配

背景&#xff1a; Spring提供了IOC机制&#xff0c;基于此我们可以通过XML或者注解配置&#xff0c;将三方件注册到IOC中。问题是每个三方件都需要经过手动导入依赖、配置属性、注册IOC&#xff0c;比较繁琐。 基于"约定优于配置"原则的自动装配机制为该问题提供了一…

Unity中全局光照GI的总结

文章目录 前言一、在编写Shader时&#xff0c;有一些隐蔽的Bug不会直接报错&#xff0c;我们需要编译一下让它显示出来&#xff0c;方便修改我们选择我们的Shader&#xff0c;点击编译并且展示编译后的Shader后的内容&#xff0c;隐蔽的Bug就会暴露出来了。 二、我们大概回顾一…

Python 潮流周刊#26:requests3 的现状

△点击上方“Python猫”关注 &#xff0c;回复“1”领取电子书 你好&#xff0c;我是猫哥。这里每周分享优质的 Python、AI 及通用技术内容&#xff0c;大部分为英文。本周刊开源&#xff0c;欢迎投稿[1]。另有电报频道[2]作为副刊&#xff0c;补充发布更加丰富的资讯。 &#…

每天一点python——day66

#每天一点Python——66 #字符串的分隔 #如图&#xff1a; #方法①split()从左开始分隔&#xff0c;默认空格为分割字符&#xff0c;返回值是一个列表 shello world jisuanji#首先创建一个字符串 list1s.split() print(list1)#输出结果是&#xff1a;[hello, world, jisuanji]注…

No179.精选前端面试题,享受每天的挑战和学习

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云课上架的前后端实战课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入…

python实现炒股自动化,个人账户无门槛量化交易的开始

本篇作为系列教程的引子&#xff0c;对股票量化程序化自动交易感兴趣的朋友可以关注我&#xff0c;现在只是个粗略计划&#xff0c;后续会根据需要重新调整&#xff0c;并陆续添加内容。 股票量化程序化自动交易接口 很多人在找股票个人账户实现程序化自动交易的接口&#xff0…

【笔记】结合P02项目——maven继承与聚合

maven的继承关系 P02项目大概是这个样子&#xff0c;下图展示的是其父工程 父工程配置了parent依赖springb-boot-starter-parent&#xff0c;子工程配置其parant为父工程 子工程引用common子工程 maven的版本锁定 管理子工程的版本号问题 父工程添加dependencyManageMent…

【Docker】Docker 网络

引言 Docker是一个开源的应用容器引擎&#xff0c;它允许开发者将应用及其依赖打包到一个可移植的容器中&#xff0c;然后发布到任何流行的Linux机器或Windows机器上&#xff0c;也可以实现虚拟化。Docker的主要优势之一是其网络功能&#xff0c;而网络功能的核心就是网络驱动…

剖析WPF模板机制的内部实现

剖析WPF模板机制的内部实现 众所周知&#xff0c;在WPF框架中&#xff0c;Visual类是可以提供渲染&#xff08;render&#xff09;支持的最顶层的类&#xff0c;所有可视化元素&#xff08;包括UIElement、FrameworkElment、Control等&#xff09;都直接或间接继承自Visual类。…

Maya 2024 for Mac(3D建模软件)

Maya 2024是一款三维计算机图形软件&#xff0c;具有强大的建模、动画、渲染、特效等功能&#xff0c;广泛应用于影视、游戏、广告等行业。以下是Maya 2024软件的主要功能介绍&#xff1a; 建模&#xff1a;Maya 2024具有强大的建模工具&#xff0c;包括多边形建模、曲面建模、…

Leetcode100120. 找出强数对的最大异或值 I

Every day a Leetcode 题目来源&#xff1a;100120. 找出强数对的最大异或值 I 解法1&#xff1a;模拟 枚举 2 遍数组 nums 的元素&#xff0c;更新最大异或值。 代码&#xff1a; /** lc appleetcode.cn id100120 langcpp** [100120] 找出强数对的最大异或值 I*/// lc c…

【大模型】大语言模型语料下载

文章目录 概述Hugging Faceobs操作git-lfs例子RedPajama-Data-1TSlimPajama-627B/git clone续传 数据格式参考资料 概述 大模型训练中语料是非常重要的&#xff0c;目前公网上有各种各样的语料可以供下载&#xff0c;但是不可能每个用户、每次训练任务都通过公网去拉取语料&am…

MYSQL操作详解

一)计算机的基本结构 但是实际上&#xff0c;更多的是这种情况: 二)MYSQL中的数据类型: 一)数值类型: 数据类型内存大小(字节)说明bit(M)M指定位数,默认为1单个二进制位值&#xff0c;或者为0或者为1&#xff0c;主要用于开/关标志tinyint1字节1个字节的整数值&#xff0c;支持…