C语言---预处理详解

1.预定义符号

在C语言中有一些内置的预定义符号

__FILE__
__LINE__
__DATE__
__TIME__
__STDC__
//进行编译的源文件
//文件当前的行号
//文件被编译的日期
//文件被编译的时间
//如果编译器遵循ANSI C,其值为1,否则未定义

 编译器在__STDC__报错,说明,vs编译器是没有遵循ANSI C的


2.#define

        2.1定义标识符

语法:
#define name stuff

我们通常会在一条语句写完后带上分号";",但是在#define定义的时候不要加分号!!!

你看出问题所在了吗?

        #define定义的符号是替换的,那么M就会被替换成100;所以加上分号是一件危险的事情.

       2.2定义宏

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

下面是宏的申明方式:
#define name( parament-list ) stuff
其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中

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

 但是宏有个特点,我们先来看段代码.

我们想要的结果是8*8=64,结果输出的却是23,这与我们的预期不符,是为什么呢?

        因为宏是替换进去的,此时n = 3+5*3+5,结果自然就是23,所以我们在定义宏的时候需要加括号以保证正确性,不要吝啬括号.

结论:对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用.

        2.3#define 替换规则

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

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

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

        2.4#和##

下面我们来探讨如何把参数插入到字符串中!

字符串是有自动拼接的效果的

因此我们可以这样写代码

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

还有一个方法是使用 # ,把一个宏参数变成对应的字符串.

此时使用#,就会把参数代入到字符串中

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

        2.5带副作用的宏参数

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

x+1;//不带副作用
x++;//带有副作用

下面我们写一个宏来证明具有副作用的参数所引起的问题。

        2.6宏和函数对比

宏通常被应用于执行简单的运算。比如在两个数中找出较大的一个。

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

那为什么不用函数来完成这个任务?
原因有二:

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

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

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

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

宏与函数对比图:

属 性#define定义宏函数
代 码 长 度每次使用时,宏代码都会被插入到程序中。除了非常
小的宏之外,程序的长度会大幅度增长
函数代码只出现于一个地方;每
次使用这个函数时,都调用那个
地方的同一份代码
执 行 速 度更快存在函数的调用和返回的额外开
销,所以相对慢一些
操 作 符 优 先 级宏参数的求值是在所有周围表达式的上下文环境里,
除非加上括号,否则邻近操作符的优先级可能会产生
不可预料的后果,所以建议宏在书写的时候多些括
号。
函数参数只在函数调用的时候求
值一次,它的结果值传递给函
数。表达式的求值结果更容易预
测。
带 有 副 作 用 的 参 数参数可能被替换到宏体中的多个位置,所以带有副作
用的参数求值可能会产生不可预料的结果。
函数参数只在传参的时候求值一
次,结果更容易控制。
参 数 类 型宏的参数与类型无关,只要对参数的操作是合法的,
它就可以使用于任何参数类型。
函数的参数是与类型有关的,如
果参数的类型不同,就需要不同
的函数,即使他们执行的任务是
相同的。
调 试宏是不方便调试的函数是可以逐语句调试的
递 归宏是不能递归的函数是可以递归的

        2.7命名约定

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

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

3.#undef

在C语言中,#undef是一个预处理指令,用于取消定义一个已经定义的宏。

宏定义是一种在编译时用特定的值或代码片段替换标识符的方式。通过#define指令可以定义一个宏,而#undef指令可以取消定义宏。


4.命令行定义

许多C 的编译器提供了一种能力,允许在命令行中定义符号。用于启动编译过程。
例如:当我们根据同一个源文件要编译出一个程序的不同版本的时候,这个特性有点用处。(假定某个程序中声明了一个某个长度的数组,如果机器内存有限,我们需要一个很小的数组,但是另外一个机器内存大些,我们需要一个数组能够大些。)

Linux环境下演示:

指令:

gcc test.c -D ARRAY_SIZE=100 -o test


5.条件编译

条件编译是一种预处理指令,它允许根据条件来选择性地编译代码。条件编译指令在编译阶段进行处理,可以根据条件的真假来决定是否编译代码。

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

条件编译使用#if#ifdef#ifndef#elif#else#endif等预处理指令来实现。下面列举条件编译指令:

  1. #if#endif:用于指定一个条件,如果条件为真,则编译条件之间的代码。
    #if CONDITION// 代码块
    #endif
    
  2. #ifdef#endif:用于检查一个标识符是否已经定义,如果已定义,则编译条件之间的代码。
    #ifdef IDENTIFIER// 代码块
    #endif
    
  3. #ifndef#endif:与#ifdef相反,用于检查一个标识符是否未定义,如果未定义,则编译条件之间的代码。
    #ifndef IDENTIFIER// 代码块
    #endif
  4. #elif:用于指定一个新的条件,如果前面的条件为假,且当前条件为真,则编译条件之间的代码。
    #if CONDITION1// 代码块1
    #elif CONDITION2// 代码块2
    #else// 代码块3
    #endif
    

条件可以是任何可以求值为非零或零的表达式,通常使用预定义的宏来表示条件。例如,#ifdef指令通常用于检查是否定义了某个宏,如下所示:

#include <stdio.h>#define DEBUGint main() {#ifdef DEBUGprintf("Debug mode\n");#elseprintf("Release mode\n");#endifreturn 0;
}

如果定义了宏DEBUG,则会编译输出"Debug mode";否则,会编译输出"Release mode"。

条件编译可以用于在不同的编译环境下编译不同的代码,或者根据不同的条件来选择性地包含或排除代码。它在处理平台特定代码、调试代码和配置选项等方面非常有用。


6.文件包含

        我们已经知道, #include 指令可以使另外一个文件被编译。就像它实际出现于 #include 指令的地方一样。
这种替换的方式很简单:

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

6.1头文件被包含的方式:

本地文件包含:

#include "test.h"

查找策略:

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

Linux环境的标准头文件的路径:

/usr/include

VS环境的标准头文件的路径:

你如果是默安装路径的话:C:\Program Files (x86)\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.30.30705\include

注意 : 按照自己的安装路径去找每个人的机器各有差异

库文件包含:

#include <stdio.h>

查找头文件直接去标准路径下去查找,如果找不到就提示编译错误。
这样是不是可以说,对于库文件也可以使用 “” 的形式包含?
答案是肯定的,可以。
但是这样做查找的效率就低些,当然这样也不容易区分是库文件还是本地文件了

6.2嵌套文件包含

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

通过前面的学习我们应该都知道了,有不明白的友友可以看这篇程序环境

头文件在预处理的时候会直接在原地展开,如果一个一个头文件按800行代码计算,如果你重复包含多个的话,那么就会造成冗余,降低程序的运行效率.

那有没有什么办法可以避免呢?

1.条件编译

每个头文件的开头写:

#ifndef __TEST_H__   // 如果未定义标识符__TEST_H__
#define __TEST_H__   // 定义标识符__TEST_H__
// 头文件的内容
#endif //__TEST_H__

通过这种方式,当多个源文件需要包含同一个头文件时,只有第一次包含会生效,后续的包含会被忽略,避免了头文件的重复包含问题。

2.#pragma once

#pragma once是一种用于防止头文件多重包含的预处理指令。与传统的条件编译指令相比,#pragma once更简洁和直观。

使用#pragma once可以确保头文件只被包含一次。当编译器遇到#pragma once指令时,它会检查当前的头文件是否已经被包含过,如果是,则直接跳过该头文件的包含,否则继续包含该头文件。

要使用#pragma once,只需要在头文件的开头添加一行#pragma once即可

#pragma once// 头文件的内容

本章内容已完:
        如果您喜欢这篇文章,可以点赞、评论和分享给您的同学,这将对我提供巨大的鼓励和支持。另外,我会在未来的更新中持续探讨与c/c++相关的内容。我会为您带来更多关于编程技术问题的深入解析、应用案例和趣味玩法等。感兴趣的话给博主点个关注,获取最新的内容消息!

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

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

相关文章

【TES720D】青翼科技基于复旦微的FMQL20S400全国产化ARM核心模块

板卡概述 TES720D是一款基于上海复旦微电子FMQL20S400的全国产化核心模块。该核心模块将复旦微的FMQL20S400&#xff08;兼容FMQL10S400&#xff09;的最小系统集成在了一个50*70mm的核心板上&#xff0c;可以作为一个核心模块&#xff0c;进行功能性扩展&#xff0c;特别是用…

【数组的使用】

文章目录 前言数组的格式有两种数组是引用数据类型遍历数组获取数组的长度&#xff1a;数组名.length数组之间的引用数组中的null关于引用的注意事项总结 前言 数组的格式有两种 int[] array{1,2,3,4};int[] array2new int[10];//默认将数组进行初始化&#xff0c;里面的值都为…

问:TCP/IP协议栈在内核态的好还是用户态的好

“TCP/IP协议栈到底是内核态的好还是用户态的好&#xff1f;” 问题的根源在于&#xff0c;干嘛非要这么刻意地去区分什么内核态和用户态。 引子 为了不让本文成为干巴巴的说教&#xff0c;在文章开头&#xff0c;我以一个实例分析开始。 最近一段时间&#xff0c;我几乎每…

idea使用debug无法启动,使用run可以启动

1、将调试断点清除 使用快捷键ctrl shift F8&#xff0c;将勾选的选项去除即可 2、Error running SampleApplication: Command line is too long. Shorten command line for SampleApplication or also for Spring Boot default configuration&#xff0c;报这种错误&#x…

tcp/ip协议2实现的插图,数据结构2 (9 - 章)

&#xff08;20&#xff09; 20 九章1 IP选项处理 ip_dooptions &#xff08;21&#xff09; 21 九章2 IP选项处理 ip_rtaddr,save_rte,ip_srcroute与结构体 &#xff08;22&#xff09;九章3 IP选项处理 ip_pcbopts, ip_insertoptions , iptime 与结构 &#xff08;23&#xf…

过滤器(Filter)和拦截器(Interceptor)有什么不同?

过滤器&#xff08;Filter&#xff09;和拦截器&#xff08;Interceptor&#xff09;是用于处理请求和响应的中间件组件&#xff0c;但它们在实现方式和应用场景上有一些不同。 实现方式: 过滤器是Servlet规范中定义的一种组件&#xff0c;通常以Java类的形式实现。过滤器通过在…

大数据Flink(九十八):SQL函数的归类和引用方式

文章目录 SQL函数的归类和引用方式 一、SQL 函数的归类

STM32如何使用PWM?

一&#xff1a;PWM介绍 PWM 是 Pulse Width Modulation 的缩写&#xff0c;中文意思就是脉冲宽度调制&#xff0c;简 称脉宽调制。它是利用微处理器的数字输出来对模拟电路进行控制的一种非常有 效的技术&#xff0c;其控制简单、灵活和动态响应好等优点而成为电力电子技术最广…

Vue之Vue的介绍安装开发实例生命周期钩子

博主心得&#xff1a; keyup必须与change一起使用v-on.click可以直接写成clickclick“setVal”里的setVal换成数字之后有惊喜VS Code是真的狗&#xff0c;一些报错根本不会直接显示总结&#xff1a;VS code太狗了 1.vue介绍 1.1 什么是vue vue是一个构建用户界面UI的渐进式jav…

【配置环境】SQLite数据库安装和编译以及VS下C++访问SQLite数据库

一&#xff0c;环境 Windows 11 家庭中文版&#xff0c;64 位操作系统, 基于 x64 的处理器SQLite - 3.43.2Microsoft Visual Studio Community 2022 (64 位) - Current 版本 17.5.3 二&#xff0c;SQLite简介 简要介绍 SQLite&#xff08;Structured Query Language for Lite&a…

Babel 在Powershell 上无法查看版本

ES6 模块语法不能应用在ES5环境中 (ES6模块化语法不能在node.js中执行)&#xff0c;此时需要Babel进行转码 通过npm install -g babel-cli 安装好后&#xff0c;想通过 babel --version产看版本。但是无法查看 首先&#xff0c;我们要以管理员方式运行PowerShell&#xff0c;&…

9-k8s-亲和力与反亲和力

文章目录 一、概念二、实操节点亲和力1三、实操pod亲和力2 一、概念 节点亲和力概念&#xff08;反亲和力相反&#xff09; ps&#xff1a;官方文档http://kubernetes.p2hp.com/docs/concepts/scheduling-eviction/assign-pod-node.html 节点亲和力&#xff08;Node Affinity&a…

手撕Vue-数据驱动界面改变下

经过上一篇的介绍&#xff0c;已经实现了观察者模式的基本内容&#xff0c;接下来要完成的就是将上一篇的发布订阅模式运用到 Nue 中&#xff0c;实现数据驱动界面改变。 在监听数据变化的章节当中&#xff0c;根据指定的区域和数据去编译渲染界面 这个步骤处&#xff0c;我写了…

Docker逃逸---授权 SYS_ADMIN Capability逃逸原理浅析

目录 一、产生原因 二、利用条件 三、复现过程 1、容器内挂载宿主机cgroup 2、设置notify_no_release并寻找容器在宿主机上的存储路径 3、将恶意脚本写入release_agent 一、产生原因 给容器额外授权了SYS_ADMIN Cap&#xff0c;并且容器以root权限运行&#xff0c;攻击者…

数据结构----算法--五大基本算法

数据结构----算法–五大基本算法 一.贪心算法 1.什么是贪心算法 在有多个选择的时候不考虑长远的情况&#xff0c;只考虑眼前的这一步&#xff0c;在眼前这一步选择当前的最好的方案 二.分治法 1.分治的概念 分治法&#xff1a;分而治之 将一个问题拆解成若干个解决方式…

计算机毕业设计--基于SSM+Vue的物流管理系统的设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…

基于寄生捕食优化的BP神经网络(分类应用) - 附代码

基于寄生捕食优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码 文章目录 基于寄生捕食优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码1.鸢尾花iris数据介绍2.数据集整理3.寄生捕食优化BP神经网络3.1 BP神经网络参数设置3.2 寄生捕食算法应用 4.测试结果…

【JVM】JVM类加载机制

JVM类加载机制 加载双亲委派模型 验证准备解析初始化 JVM的类加载机制,就是把类,从硬盘加载到内存中 Java程序,最开始是一个Java文件,编译成.class文件,运行Java程序,JVM就会读取.class文件,把文件的内容,放到内存中,并且构造成.class类对象 加载 这里的加载是整个类加载的一…

阿里云2023年双十一优惠活动整理

随着双十一的临近&#xff0c;阿里云也为大家准备了一系列优惠活动。作为国内知名的云服务提供商&#xff0c;阿里云在双十一期间推出了多种优惠政策和福利&#xff0c;让用户在享受优质云服务的同时&#xff0c;也能节省一些费用。本文将对阿里云双十一优惠活动进行详细整理&a…

25.0 MySQL 数据库概述

1. 数据库介绍 1.1 简介 数据库是用于存储, 管理和组织数据的一种技术.使用数据库有以下几个重要的原因: * 1. 数据的持久化存储: 数据库可以将数据持久地保存在磁盘上, 确保数据在计算机系统关闭或发生故障时不会丢失.这样可以保证数据的安全性和可靠性.* 2. 数据共享和协作…