C语言---预处理详解

news2025/2/23 16:29:30

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 DEBUG

int main() {
    #ifdef DEBUG
        printf("Debug mode\n");
    #else
        printf("Release mode\n");
    #endif

    return 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.coloradmin.cn/o/1098469.html

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

相关文章

基于IPSec VPN隧道技术的国密加密网关保障电力工控数据安全

IPSec VPN&#xff08;Internet Protocol Security Virtual Private Network&#xff09;隧道技术为电力工控系统提供了重要的数据安全传输手段。该技术能实现身份鉴别和数据加密传输&#xff0c;为系统的防护工作增添了有力的支持。 电力工控系统对数据传输的可靠性要求较高。…

String的intern()方法详解

文章目录 前言一、new String&#xff08;&#xff09;创建了几个对象&#xff1f;二、Stting anew String("ab")new String("c")创建了几个对象三、String的intern()方法四&#xff1a;面试题五&#xff1a;总结 前言 在开发过程中很多朋友&#xff0c;由…

vue.config.js配置proxy代理产生404错误的原因

在使用vue做开发时&#xff0c;请求api接口时为了解决跨域问题&#xff0c;一般会设置proxy代理&#xff0c; 但有时候会莫名其妙的出现404错误&#xff0c;这里总结一下vue设置proxy代理产生404错误的几种原因&#xff1a; 原因1&#xff1a;没有注意vue proxy代理优先级的规…

SQL Server向表中插入数据

SQL Server向表中插入数据 切换到对应的数据库 use DBTEST插入数据 方式1 insert into 表名&#xff08;列名1,列名2) values&#xff08;数据1&#xff0c;数据2&#xff09;注意&#xff1a; 列名就算是字符类型也不用加引号&#xff0c;数据如果对应的字段是字符串类型&…

48.排列问题求解

思路分析&#xff1a;通过为每一队分配一个id&#xff0c;join条件要求t1.num < t2.num实现相同两队只比一次 代码实现&#xff1a; with t as (SELECT team_name,caseteam_nameWHEN 勇士 then 1WHEN 湖人 then 2WHEN 灰熊 then 3else 4end numFROM team )SELECT t1.team_…

零经验想跳槽转行网络安全,需要准备什么?

最近在后台看到很多私信都是有关转行网络安全的问题&#xff0c;目前咨询最多的都是&#xff1a;觉得现在的工作没有发展空间&#xff0c;替代性强&#xff0c;工资低&#xff0c;想跳槽转行网络安全。其中&#xff0c;他们主要关心的是&#xff1a;没有经验怎么学习&#xff1…

模糊测试面面观 | 电动汽车充电桩安全漏洞案例分享

在上一期我们讲了针对车载以太网DOIP协议详细阐释在实际过程的漏洞发掘过程&#xff0c;本期我们将继续延展&#xff0c;探讨电动车充电系统的安全漏洞。开源网安在基于 GB/T 27930-2015通信标准的电动汽车充电桩中&#xff0c;采用渗透测试、模糊测试和数据流分析等多种安全漏…

易天光通信推出100G BIDI ER光模块最新解决方案

随着数字信息时代的快速发展&#xff0c;网络通信技术的迅猛进步成为推动科技创新和产业升级的重要引擎之一。作为光通信行业的新秀&#xff0c;近期易天光通信推出了全新的100G BIDI ER1 Lite光模块和100G BIDI LR1 Lite光模块&#xff0c;助力崭新的未来网络建设。 易天光通…

45.复购率问题求解

思路分析&#xff1a; &#xff08;1&#xff09;近xx天&#xff0c;最大日期肯定就是最新的一天&#xff0c;故先用max(order_date) over() today计算当天日期 &#xff08;2&#xff09;过滤出最近90天的订单并且按照user_id,product_id分组求购买次数&#xff1b; &#xff…

逐字稿 | 视频理解论文串讲(上)【论文精读】

大家好&#xff0c;前两期我们讲了视频理解领域里的两篇经典的论文&#xff0c;一个是双流网络&#xff0c;第一个是 I3D 网络&#xff0c;所以说对视频理解这个问题有了个基本的了解。 那今天我们就从 2014 年开始&#xff0c;一直到最近 2021 年的工作&#xff0c;我们一起来…

攻防世界题目练习——Web引导模式(三)(持续更新)

题目目录 1. mfw2.3.4.5. 1. mfw 进去看到网页和页面内容如下&#xff1a; 看到url的参数 ?pageabout &#xff0c;我以为是文件包含什么的&#xff0c;反复试了几次&#xff0c;想用 …/…/…/…/etc/passwd &#xff0c;但是发现.似乎被过滤了&#xff0c;实在不知道怎么做…

转行学网络安全,月薪6k到30k,给兄弟们一些个人建议

前言&#xff1a; 网络安全是指网络系统的硬件、软件及其系统中的数据受到保护&#xff0c;不因偶然或恶意原因而遭受破坏、更改、泄露&#xff0c;系统连续可靠正常地运行&#xff0c;网络服务不中断。 截至2018年8月&#xff0c;我国网民规模达8.02亿人&#xff0c;互联网高…

PHP 生成微信小程序码,并存储图片

背景 当前鄙人接手的项目&#xff0c;需要在设备机器上展示 小程序二维码 为了记录扫码用户从哪台机器注册的 那么&#xff0c;实现方案就是&#xff1a;在小程序码中&#xff0c;绑定设备编号参数 在此&#xff0c;记录一番实现步骤 … 小程序开发文档 - 【 获取不限制的小程…

数据结构-----红黑树简介

目录 前言 1.什么是红黑树&#xff1f; 2.为什么需要红黑树&#xff1f;&#xff08;与AVL树对比&#xff09; 3.红黑树的特性 前言 在此之前我们学习过了二叉排序树和平衡二叉树&#xff08;AVL树&#xff09;&#xff0c;这两种树都是属于搜索树的一种&#xff0c;那么今天…

计算机毕业设计 无人智慧超市管理系统的设计与实现 Javaweb项目 Java实战项目 前后端分离 文档报告 代码讲解 安装调试

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

FSM人物动画状态机

人物动画状态机 介绍FSM角色模型的设置角色动作的设置角色动画控制器的设置书写角色动画的具体状态&#xff0c;实现缓动起步的FSM总结 介绍 摇杆我就不介绍了&#xff0c;之前我在这里面讲过怎么用摇杆&#xff0c;摇杆连接。 这里我先说下什么是FSM人物动画状态机&#xff0…

文心一言 VS 讯飞星火 VS chatgpt (114)-- 算法导论10.2 7题

七、用go语言&#xff0c;给出一个 O(n)时间的非递归过程&#xff0c;实现对一个含 n个元素的单链表的逆转。要求除存储链表本身所需的空间外&#xff0c;该过程只能使用固定大小的存储空间。 文心一言&#xff1a; 在Go语言中&#xff0c;你可以使用迭代方法来反转一个单链表…

【C++】:string用法详解

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

算法通过村第十四关-堆|黄金笔记|中位数

文章目录 前言数据流中的中位数的问题总结 前言 提示&#xff1a;我独自度过了太多的时光&#xff0c;沉默已成一种习惯。 帕瑞尔马卡姆《夜航西飞》 这个是一个比较难的题目&#xff0c;要不尝试一下看看。 数据流中的中位数的问题 参考题目地址&#xff1a;295. 数据流的中位…

身为程序员,你很有必要了解一下JNPF

一、JNPF是什么&#xff1f; JNPF是一个“低代码”的可视化编程平台&#xff0c;采用J2EE技术体系&#xff0c;基于Spring Boot框架封装的快速开发平台&#xff0c;包含多种开箱即用的功能&#xff0c;具备RBAC、自动生成代码、SaaS多租户等多个功能和模块。 JNPF&#xff0c;重…