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.rhkb.cn/news/159648.html

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

相关文章

回归预测 | MATLAB实现CNN-LSSVM基于卷积神经网络-最小二乘支持向量机的数据回归预测(多指标,多图)

回归预测 | MATLAB实现CNN-LSSVM基于卷积神经网络-最小二乘支持向量机的数据回归预测&#xff08;多指标&#xff0c;多图&#xff09; 目录 回归预测 | MATLAB实现CNN-LSSVM基于卷积神经网络-最小二乘支持向量机的数据回归预测&#xff08;多指标&#xff0c;多图&#xff09;…

小程序框架语法详解以及页面生命周期的代码预演

目录 一、框架简介 二、视图层 2.1 简介 2.2 WXML语法演示 2.2.1 数据绑定 2.2.2 列表渲染 2.2.3 条件渲染 2.2.4 模板 2.3 事件系统 2.4 页面一级菜单展示及切换 2.5 a页面跳b页面界面内部按钮演示 2.6 a页面跳c页面&#xff08;不在一级菜单内的页面&#xff09;…

Webpack和JShaman相比有什么不同?

Webpack和JShaman相比有什么不同&#xff1f; Webpack的功能是打包&#xff0c;可以将多个JS文件打包成一个JS文件。 JShaman专门用于对JS代码混淆加密&#xff0c;目的是让JavaScript代码变的不可读、混淆功能逻辑、加密代码中的隐秘数据或字符&#xff0c;是用于代码保护的…

想要精通算法和SQL的成长之路 - 滑动窗口和大小根堆

想要精通算法和SQL的成长之路 - 滑动窗口和大小根堆 前言一. 大小根堆二. 数据流的中位数1.1 初始化1.2 插入操作1.3 完整代码 三. 滑动窗口中位数3.1 在第一题的基础上改造3.2 栈的remove操作 前言 想要精通算法和SQL的成长之路 - 系列导航 一. 大小根堆 先来说下大小根堆是什…

NPM 常用命令(十二)

目录 1、npm unpublish 1.1 使用语法 1.2 描述 2、npm unstar 2.1 使用语法 3、npm update 3.1 使用语法 3.2 描述 3.3 示例 插入符号依赖 波浪号依赖 低于 1.0.0 的插入符号依赖 子依赖 更新全局安装的包 4、npm version 4.1 使用语法 5、npm view 5.1 使用语…

LLMs的终局是通用人工智能AGI总结 生成式AI和大语言模型 Generative AI LLMs

终于学完了 生成式AI和大语言模型 Generative AI & LLMs. LLMs 解决了如下问题&#xff1a; 对NLP的不能够理解长句子&#xff0c;解决方案 自注意力机制Transformers architecture Attention is all you need大模型算力不够&#xff0c;解决方案 LLMs 缩放法则和计算最…

电商爬虫API快速入门指南

​电子商务爬虫API​是一个公共数据爬虫API&#xff0c;旨在通过大多数电子商务网站收集大量实时本地化数据并搜索信息。这个数据收集工具作为一个值得信赖的解决方案&#xff0c;实现通过最复杂的电子商务网站收集公共信息。电子商务爬虫API适用于商业用例&#xff0c;诸如价格…

数据结构 - 2(顺序表10000字详解)

一&#xff1a;List 1.1 什么是List 在集合框架中&#xff0c;List是一个接口&#xff0c;继承自Collection。 Collection也是一个接口&#xff0c;该接口中规范了后序容器中常用的一些方法&#xff0c;具体如下所示&#xff1a; Iterable也是一个接口&#xff0c;Iterabl…

加入鲲鹏HPC训练营,一起引领高性能计算新潮流

随着科学技术的迅猛发展&#xff0c;高性能计算&#xff08;HPC&#xff09;已经成为各行各业的核心竞争力之一。在这个数字化时代&#xff0c;高性能计算对于解决大数据分析、人工智能、模拟计算等领域的复杂问题至关重要。 所谓HPC&#xff0c;就是一个计算机集群系统&#x…

安全典型配置(三)使用ACL禁止特定用户上网案例

【微|信|公|众|号&#xff1a;厦门微思网络】 安全典型配置&#xff08;一&#xff09;使用ACL限制FTP访问权限案例_厦门微思网络的博客-CSDN博客本例中配置的本地用户登录密码方式为irreversible-cipher&#xff0c;表示对用户密码采用不可逆算法进行加密&#xff0c;非法用…

android studio检测不到真机

我的情况是&#xff1a; 以前能检测到&#xff0c;有一天我使用无线调试&#xff0c;发现调试有问题&#xff0c;想改为USB调试&#xff0c;但是半天没反应&#xff0c;我就点了手机上的撤销USB调试授权&#xff0c;然后就G了。 解决办法&#xff1a; 我这个情况比较简单&…

女性用品经营商城小程序的作用是什么

女性悦己消费增强&#xff0c;围绕女性产生的商品&#xff0c;品牌多且样式足&#xff0c;消费者可以随时购买到&#xff0c;但随着线上互联网深入人们生活&#xff0c;电商近些年发展迅速&#xff0c;传统女性用品线下经销商或品牌在实际经营中面临着痛点。 线上卖货是各商家…

【C++】:string用法详解

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;本期来给大家解读一下有关Linux的基础知识点&#xff0c;如果看完之后对你有一定的启发&#xff0c;那么请留下你的三连&#xff0c;祝大家心想事成&#xff01; C 语 言 专 栏&#xff1a;C语言&#xff1a;从入门到精通 数…

TCP/IP(十)TCP的连接管理(七)CLOSE_WAIT和TCP保活机制

一 CLOSE_WAIT探究 CLOSE_WAIT 状态出现在被动关闭方,当收到对端FIN以后回复ACK,但是自身没有发送FIN包之前 ① 服务器出现大量 CLOSE_WAIT 状态的原因有哪些? 1、通常来讲,CLOSE_WAIT状态的持续时间应该很短,正如SYN_RCVD状态2、但是在一些特殊情况下,就会出现大量连接长…

word误删除的文件怎么恢复?恢复办法分享

在日常工作和学习中&#xff0c;我们常常会使用到Word来撰写文章、毕业论文、方案等。然而&#xff0c;我们可能会遇到Word误删文件的情况&#xff0c;令我们陷入恐慌&#xff0c;特别是这个文件很重要时。幸运的是&#xff0c;有办法找回。下面一起来看下word误删除的文件怎么…

CEC2013(MATLAB):猎豹优化算法(The Cheetah Optimizer,CO)求解CEC2013

一、猎豹优化算法CO 猎豹优化算法&#xff08;The Cheetah Optimizer&#xff0c;CO&#xff09;由MohammadAminAkbari等人于2022年提出&#xff0c;该算法性能高效&#xff0c;思路新颖。 参考文献&#xff1a; Akbari, M.A., Zare, M., Azizipanah-abarghooee, R. et al. Th…

spring boot自定义配置时在yml文件输入有提示

自定义一个配置类&#xff0c;然后在yml文件具体配置值时&#xff0c;一般不会有提示&#xff0c;这个解决这个问题 依赖 <!--自定义配置类&#xff0c;在yml文件写的时候会有提示--><dependency><groupId>org.springframework.boot</groupId><arti…

【git篇】git的使用

文章目录 1. Git介绍与安装1. Git简介2. 下载安装程序3. 设置用户名和邮箱 2. Git的基本使用1. 创建版本库2. 文件管理1. 提交文件2. 查看状态3. 查看提交日志4. 版本回退 3. 原理解析1. Git区的划分2. 撤销修改3. 删除文件 4. 分支管理1. 基本原理2. 创建分支3. 合并分支4. 删…

网页游戏的开发框架

网页游戏开发通常使用不同的开发框架和技术栈&#xff0c;以创建各种类型的游戏&#xff0c;从简单的HTML5游戏到复杂的多人在线游戏&#xff08;MMO&#xff09;等。以下是一些常见的网页游戏开发框架和它们的特点&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&a…

Android Studio SDK manager加载packages不全

打开Android Studio里的SDK manager&#xff0c;发现除了已安装的&#xff0c;其他的都不显示。 解决方法&#xff1a; 设置代理&#xff1a; 方便复制> http://mirrors.neusoft.edu.cn/ 重启Android Studio