【C进阶】C程序是怎么运作的呢?-- 程序环境和预处理(下)

前言:
这是程序环境和预处理的下半篇文章。至此,关于c语言知识点:从编译到运行的过程已讲解完毕。传送🚪,上半篇: http://t.csdnimg.cn/hvxmr
本章涉及的知识点: 宏和函数对比、命名约定、#undef、命令行定义、条件编译、文件包含以及其他预处理指令。

目录

3. 预处理详解

3.2.6 宏和函数对比

3.2.7 命名约定

3.3 #undef

3.4 命令行定义

3.5 条件编译

1.常量表达式 

2.多个分支的条件编译

3.判断是否被定义

4.嵌套指令

 3.6 文件包含

3.6.1 头文件被包含的方式:

3.6.2 嵌套文件包含

4. 其他预处理指令


3. 预处理详解

3.2.6 宏和函数对比

        下面两种方式求两个数的较大值,谁优,谁劣?

#include<stdio.h>
//函数的实现
int Max(int x, int y)
{return x > y ? x : y;
}//宏的实现
#define MAX(x,y) ((x)>(y)?(x):(y))int main()
{int a = 0;int b = 0;//输入scanf("%d %d",&a,&b);//1.函数返回较大值int m1 = Max(a, b);printf("%d\n",m1);//2.使用宏 int m2 = MAX(a, b);//等价于 ((a)>(b)?(a):(b));printf("%d\n",m2);return 0;
}

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

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

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

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

原因有二:

1️⃣用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。
所以宏比函数在程序的规模和速度方面更胜一筹
📚怎么理解呢:
从函数返回
📃函数调用的时间花费:
1.函数调用前准备( 传参、函数栈帧空间的维护)
2.主要运算 
3.函数返回,返回值的处理,函数栈帧的销毁
涉及到函数栈帧的内容,传送门👉: http://t.csdnimg.cn/DtDhX
使用宏定义
📃宏定义的时间花费:
2.主要运算(写成宏就把1,3步骤省略掉了) 不用建立函数栈帧,也就没有它的销毁

2️⃣更为重要的是函数的参数必须声明为特定的类型

        所以函数 只能在类型合适的表达式 上使用。反之 这个宏怎可以适用于整形、长整型、浮点型等可以用于>来比较的类型 宏是类型无关的

        如何理解:

由上面的两个原因,求两个数的较大值这个例子中,宏更有优势一些,使用宏定义可以省掉不必要去损耗的时间,那么宏是不是比函数更有优势呢?
宏的缺点: 当然和函数相比宏也有劣势的地方
1️⃣每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度

2️⃣宏是没法调试的 

3️⃣ 宏由于类型无关,也就不够严谨
        只要能够参与运算,那么传入任何参数都能适用 ,这是一把双刃剑。
4️⃣宏可能会带来运算符优先级的问题,导致程容易出现错
宏的劣势:当参数里面有表达式的时候,表达式传参传到宏的体内的时候,宏体内如果有相邻的操作符,这时候操作符优先级可能引起一些问题,导致程序错误。
函数不会有这个问题,即使传入一个表达式,也会把它的值算出一个结果,再传进去.

        宏有时候可以做函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到(因为类型是不可能作为参数给函数传参的,函数传参传的是变量、数组、指针等)

 例子: 

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

3.2.7 命名约定

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

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

1.把宏名全部大写

2.函数名不要全部大写

3.3 #undef

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

#undef NAME //如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除

演示:

3.4 命令行定义

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

例如:当我们根据同一个源文件要编译出一个程序的不同版本的时候,这个特性有点用处。(假定某个 程序中声明了一个某个长度的数组,如果机器内存有限,我们需要一个很小的数组,但是另外一个机器内存大些,我们需要一个数组能够大些。)

演示代码:

按ctrl+~键,看下图:按照下图先按住①再按②

把终端调出来

指定SZ(宏的大小)为10,即数组大小为10,那么依次打印1~10

指定SZ(宏的大小)为100,即数组大小为100,那么依次打印1~100

编译指令:

//linux 环境演示

gcc -D ARRAY_SIZE=10 programe.c

3.5 条件编译

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

比如说:

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

#include <stdio.h>
#define __DEBUG__
int main()
{int i = 0;int arr[10] = {0};for(i=0; i<10; i++){arr[i] = i;#ifdef __DEBUG__printf("%d\n", arr[i]);//为了观察数组是否赋值成功。 #endif //__DEBUG__}return 0;
}

常见的条件编译指令:

1.常量表达式 

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

注意:预处理期间,其实处理都是文本呀,代码处理的过程中,编译指令是有的,不需要编译的,就把它删了,需要后面编译的代码会留着。

所以右图中int a=2;不需要删除的原因在这里。

2.多个分支的条件编译

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

该编译放到这个代码里头,不该编译就删掉了 

3.判断是否被定义

if defined和ifdef是相同的,都是用于检查某个标识符是否已经定义的预处理指令

它们在C和C++中是等效的。

        使用 ifdef 或 if defined 可以根据某个标识符是否已经定义来进行条件编译。如果标识符已经通过 #define 或其他方式定义过,则执行 ifdef 或 if defined 后面的代码块;否则,忽略该代码块。

#define DEBUG_MODE#ifdef DEBUG_MODE// 调试模式下的代码printf("执行调试代码\n");// ...
#endif

        在上述示例中,#define DEBUG_MODE 定义了一个名为 DEBUG_MODE 的宏。在 #ifdef DEBUG_MODE 的代码块中,可以放置调试模式下需要执行的代码。如果 DEBUG_MODE 宏已经被定义,那么代码块中的代码将会被执行;否则,代码块将被忽略。

        请注意,ifdef 和 if defined 仅用于在编译时进行条件判断,而不是在运行时。它们用于根据不同的编译配置或条件选择性地包含或排除代码块,从而实现更灵活的程序控制。

图解:

if defined(MAX)

#ifdef MAX 

把宏注释掉,用ifdef

同理可得!define和#ifndef:

#define

先来看没有用#define定义的时候,define(MAX)条件判断为假,!define(MAX)判断为真。

下面是已经定义的情况

 #ifndef

下面是 "#ifndef" 指令的基本语法:

#ifndef 宏名称// 如果宏名称未定义,则执行的代码
#endif

        如果名为 "宏名称" 的宏未定义,那么在预处理阶段将包含 "#ifndef" 块中的代码。如果该宏已定义,则会跳过块中的代码。

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来结束。

 3.6 文件包含

        我们已经知道, #include 指令可以使另外一个文件被编译。就像它实际出现于 #include 指令的地方一样。
这种替换的方式很简单:
        预处理器先删除这条指令,并用包含文件的内容替换。
        这样一个源文件被包含10 次,那就实际被编译 10 次。
 

3.6.1 头文件被包含的方式:

  • 本地文件包含

#include "filename"

查找策略:先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标 准位置查找头文件。 如果找不到就提示编译错误。
路径:自己工程当前的目录查找

Linux 环境的标准头文件的路径:
/ usr / include
VS 环境的标准头文件的路径:
C : \Program Files ( x86 ) \Microsoft Visual Studio 12.0 \VC\include
// 这是 VS2013 的默认路径
注意按照自己的安装路径去找。
  • 库文件包含

 #include <filename.h>

查找头文件直接去标准路径下去查找,如果找不到就提示编译错误。

这样是不是可以说,对于库文件也可以使用 “” 的形式包含?
答案是肯定的, 可以。
但是这样做查找的效率就低些,当然这样也不容易区分是库文件还是本地文件了。

3.6.2 嵌套文件包含

comm.h和comm.c是公共模块。

test1.h和test1.c使用了公共模块。

test2.h和test2.c使用了公共模块。

test.h和test.c使用了test1模块和test2模块。

这样最终程序中就会出现两份comm.h的内容。这样就造成了文件内容的重复

如何解决这个问题? 答案:条件编译。

解决思路:

①使用#ifndef条件编译

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

②使用pragma once防止头文件被反复多次的包含 

#pragma once

vscode编译器:

以上①②两者写法均可防止文件重复包含。

注: 推荐《高质量C/C++编程指南》中附录的考试试卷(很重要)。

笔试题:

1. 头文件中的 ifndef/define/endif是干什么用的?

        头文件中的ifndef/define/endif是用于防止头文件被重复包含,以避免编译错误。ifndef用于判断某个标识符是否已经被定义,如果未被定义,则继续执行define指令,定义该标识符,并执行后续的代码;如果已经被定义,则跳过后续的代码,直接执行endif指令。这样可以确保头文件只被包含一次。

2. #include 和 #include "filename.h"有什么区别?

        #include <filename.h>是用于包含系统头文件,编译器会先在系统目录中查找该头文件;而#include "filename.h"是用于包含用户自定义的头文件,编译器会先在当前目录中查找该头文件,如果未找到,则会在系统目录中查找。

4. 其他预处理指令

#error
#pragma
#line
...
不做介绍,自己去了解。
#pragma pack()在结构体部分介绍。

参考《C语言深度解剖》学习

本章完。

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

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

相关文章

C语言—每日选择题—Day46

第一题 1. 下列程序段的输出结果是&#xff08;&#xff09; #include <stdio.h> int main() {int x 1,a 0,b 0;switch(x) {case 0: b;case 1: a;case 2: a;b;}printf("a%d,b%d\n", a, b);return 0; } A&#xff1a;a2,b1 B&#xff1a;a1,b1 C&#xf…

JVM面试

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 1.JVM 的整体结构2.类加载做了哪些事情?类加载器有哪些&#xff1f;双亲委派和沙箱安全 3.Java虚拟机栈是什么4.方法区的理解HotSpot 中方法区的演进方法区的内部结…

【Spring教程26】Spring框架实战:从零开始学习SpringMVC 之 bean加载控制

目录 1 问题分析2 思路分析3 环境准备4 设置bean加载控制5 知识点1&#xff1a;ComponentScan 欢迎大家回到《Java教程之Spring30天快速入门》&#xff0c;本教程所有示例均基于Maven实现&#xff0c;如果您对Maven还很陌生&#xff0c;请移步本人的博文《如何在windows11下安装…

文件搜索项目演示

演示功能搜索功能1&#xff1a;根据文件名搜索2&#xff1a;根据文件路径搜索3&#xff1a;根据文件名拼音(全拼、首拼)搜索 选择更新目录功能自动初始化和定时更新功能程序文件项目知识介绍 演示功能 搜索功能 1&#xff1a;根据文件名搜索 2&#xff1a;根据文件路径搜索 3…

Linux--操作系统

1. 常见的操作系统 Windowsmac OSLinuxiOSAndroid 2. 操作系统的定义 操作系统直接运行在计算机上的系统软件&#xff0c; 它是控制硬件和支持软件运行的计算机程序。 3. 操作系统的作用 向下控制硬件向上支持软件的运行&#xff0c;具有承上启下的作用。 4.总结 操作系统…

Win10的SVN Adapter V1.0 中黄色感叹号 -- 解决

大部分都问题都可以通过&#xff1a; 关闭 SVN Adapter V1.0 在下载最新的 SVNDrv.sys替换 C:\Windows\System32\drivers 中的同名文件启动 SVN Adapter V1.0 就能成功 但是部分人的电脑 SVN Adapter V1.0 是有感叹号的&#xff0c;说明注册表有问题 先用 CCleaner 修复注册表…

vue实现移动端适配

目录 1. 使用vw单位&#xff1a;vw是视窗宽度的百分比&#xff0c;可以根据不同设备的屏幕宽度来进行自适应。在Vue中可以通过设置全局CSS样式&#xff0c;将所有的尺寸单位改为vw。 2. 使用Flexible.js&#xff1a;Flexible.js是一个用于淘宝移动端适配的库&#xff0c;可以…

正则表达式(8):基本正则表达式小结

正则表达式&#xff08;8&#xff09;&#xff1a;基本正则表达式小结 本博文转载自 写这篇文章的目的就是总结前文中所介绍的”基本正则表达式”&#xff0c;并且结合一些实例进行练习&#xff0c;以便我们能够在练习中完全掌握它们。 首先&#xff0c;我们对前文中提到的符…

【rabbitMQ】声明队列和交换机

上一篇&#xff1a;springboot整合rabbitMQ模拟简单收发消息 https://blog.csdn.net/m0_67930426/article/details/134904766?spm1001.2014.3001.5501 相关配置环境参考上篇 springAMQP提供了几个类用来声明声明队列&#xff0c;交换机及其绑定关系 声明队列&#xff0c;…

Rational rose 安装教程(图文)

Rational Rose是Rational公司出品的一种面向对象的统一建模语言的可视化建模工具。用于可视化建模和公司级水平软件应用的组件构造。 就像一个戏剧导演设计一个剧本一样&#xff0c;一个软件设计师使用Rational Rose&#xff0c;以演员&#xff08;数字&#xff09;、使用拖放式…

第三届iEnglish全国ETP大赛16强落位 诠释教育游戏价值

10日,与北方骤降的温度形成鲜明对比,以“玩转英语,用iEnglish”为主题的国内首个教育游戏活动第三届iEnglish全国ETP(English Through Pictures)大赛总决赛小组赛热火朝天的进行。随着“云帆沧海队”搭上末班车,本届活动16强全部产生,接下来的三个周末他们将向年度总冠军发起最…

C# WPF上位机开发(增强版绘图软件)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 前面我们写过一个绘图软件&#xff0c;不过那个比较简单&#xff0c;主要就是用鼠标模拟pen进行绘图。实际应用中&#xff0c;另外一种使用比较多的…

Leetcode—509.斐波那契数【简单】

2023每日刷题&#xff08;五十七&#xff09; Leetcode—509.斐波那契数 实现代码 int fib(int n){if(n 0) {return 0;}if(n 1) {return 1;}return fib(n-1) fib(n-2); }运行结果 之后我会持续更新&#xff0c;如果喜欢我的文章&#xff0c;请记得一键三连哦&#xff0c;点…

网络安全公司梳理,看F5如何实现安全基因扩增

应用无处不在的当下&#xff0c;从传统应用到现代应用再到边缘、多云、多中心的安全防护&#xff0c;安全已成为企业数字化转型中的首要挑战。根据IDC2023年《全球网络安全支出指南》&#xff0c;2022年度中国网络安全支出规模137.6亿美元&#xff0c;增速位列全球第一。有专家…

磁力计LIS2MDL开发(1)----轮询获取磁力计数据

磁力计LIS2MDL开发.1--轮询获取磁力计数据 概述视频教学样品申请源码下载通信模式速率生成STM32CUBEMX串口配置IIC配置CS设置串口重定向参考程序初始换管脚获取ID复位操作BDU设置设置速率启用偏移消除开启温度补偿设置为连续模式轮询读取数据主程序演示 概述 本文将介绍如何使…

Knowledge Graph知识图谱—9. Data Quality and Linking

9. Data Quality and Linking 9.1 How well are the linked open data in practice? Linked Open Vocabularies(LOV) project – analyze usage of vocabularies 9.2 Quality Linked Data Conformance vs. Quality Conformance: – i.e., following standards and best prac…

【git push ERROR: commit id: missing Change-Id in message footer】

使用 gerrit 后&#xff0c;提交代码会出现如下截图问题&#xff1a; 临时解决&#xff1a; step1: 把上面红色的那条gitidir复制下来执行下&#xff1a; step2:执行下面的命令会添加change_id git commit --amendstep3: 然后推送代码到服务器上 git push origin HEAD:refs/fo…

事件驱动架构 vs. RESTful架构:通信模式对比与选择

1. 通信风格 事件驱动架构&#xff08;EDA&#xff09; 是一种异步通信风格&#xff0c;组件之间通过产生和消费事件进行通信。 事件是表示系统中重大变化或事件的消息&#xff0c;并分发给感兴趣的组件。这种通信模型允许系统的不同部分之间进行解耦和动态交互。 组件充当事件…

新手上路:盘点「性能测试」必须掌握的技术点

前段时间&#xff0c;有一些小伙伴提出希望我们推送点性能测试的技术干货。所以&#xff0c;小编今天通过上网查资料&#xff0c;结合项目实操过程中的一些问题&#xff0c;总结了一些关于性能测试的内容&#xff0c;希望是大家想要了解的内容哈。 1、性能测试的目的 首先&am…

.Net中的集合

所有的集合都是继承自IEnumerable。集合总体可以分为以下几类&#xff1a;关联/非关联型集合&#xff0c;顺序/随机访问集合&#xff0c;顺序/无序集合&#xff0c;泛型/非泛型集合&#xff0c;线程集合。 各集合类底层接口关系图 泛型与非泛型集合类的分析 泛型集合是类型安…