【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语言深度解剖》学习

本章完。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux 系统 SSH 和 SCP 服务器搭建、配置、访问以及出现的问题

SSH是Secure Shell的缩写&#xff0c;是一种网络协议&#xff0c;用于通过本地或远程网络在计算机上进行远程登录和命令操作。SSH 是 Telnet 协议的演变&#xff1a;正如其名称所描述的&#xff0c;SSH 是安全的&#xff0c;并对通过网络传输的数据进行加密。 SSH 是目前较为可…

分布式-分布式事务理论、模型、方案、框架

一、分布式事务理论模型 分布式事务问题也叫分布式数据一致性问题&#xff0c;简单来说就是如何在分布式场景中保证多个节点数据的一致性。分布式事务产生的核心原因在于存储资源的分布性&#xff0c;比如多个数据库&#xff0c;或者MySQL和Redis两种不同存储设备的数据一致性…

5. PyTorch——数据处理模块

1.数据加载 在PyTorch中&#xff0c;数据加载可通过自定义的数据集对象。数据集对象被抽象为Dataset类&#xff0c;实现自定义的数据集需要继承Dataset&#xff0c;并实现两个Python魔法方法&#xff1a; __getitem__&#xff1a;返回一条数据&#xff0c;或一个样本。obj[in…

鸿蒙开发之状态管理@Prop和@Link

一、用法 在父子组件需要进行数据同步的时候&#xff0c;可以通过Prop和Link装饰器来做到。在父组件中用State装饰&#xff0c;在自组件中用Prop或Link装饰。 结论&#xff1a;Prop用于子组件只监听父组件的数据改变而改变&#xff0c;自己不对数据改变 Link用于子组件与父组…

Skype的介绍及使用

介绍及使用说明 Windows自带的Skype是一款全球通用的即时通讯软件&#xff0c;它可以让用户通过文字、语音和视频进行免费的在线沟通。下面是Skype的使用说明&#xff1a; 1.安装和登录&#xff1a;如果你的Windows系统中没有预装Skype&#xff0c;你可以在Microsoft官…

锁定屏幕与挂起

概要&#xff1a; 本篇主要讲述Ubuntu22.04中的锁定屏幕和挂起 锁定屏幕就是大家通常所说的息屏、锁屏&#xff0c;英文单词是lock 挂起一般也被称为休眠、睡眠&#xff0c;英文单词是suspend 一、锁定屏幕 1、CtrlL 按下键盘上的CtrlL键&#xff0c;即可锁定屏幕&#x…

【华为鸿蒙系统学习】- HarmonyOS4.0开发工具和环境配置问题总结|自学篇

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 &#x1f4ab;个人格言:"没有罗马,那就自己创造罗马~" 目录 官方链接 HUAWEI DevEco Studio和SDK下载和升级 | HarmonyOS开发者 安装教程 &#xff08;…

【PTA刷题+代码+详解】求二叉树度为1的结点个数(递归法)

文章目录 题目C代码详解 题目 在二叉树T中&#xff0c;其度为1的结点是指某结点只有左孩子或只有右孩子。利用递归方法求二叉树T的度为1的结点个数。 1&#xff09;如果TNULL&#xff0c;则是空树&#xff0c;度为1的结点个数为0&#xff0c;返回值为0&#xff1b; 2&#xff0…

Python爬虫实战 | 爬取拼多多商品的详情价格SKU数据

本案例将为大家演示如何爬取拼多多商品的详情数据。目的是爬取大量的商品以及商品的评论&#xff0c;所以在程序设计上要考虑到该爬虫的高并发以及持久化存储。爬虫工具选用了Scrapy框架&#xff0c;以满足爬虫的高并发请求任务&#xff1b;持久化存储用了MongoDB&#xff0c;对…

python:五种算法(SSA、WOA、GWO、PSO、GA)求解23个测试函数(python代码)

一、五种算法简介 1、麻雀搜索算法SSA 2、鲸鱼优化算法WOA 3、灰狼优化算法GWO 4、粒子群优化算法PSO 5、遗传算法GA 二、5种算法求解23个函数 &#xff08;1&#xff09;23个函数简介 参考文献&#xff1a; [1] Yao X, Liu Y, Lin G M. Evolutionary programming made…

vue 集成行政区域选择插件region和数据回显

故事&#xff1a;最近&#xff0c;项目需要进行行政区域围栏的绘制&#xff0c;由于老旧项目是利用js保存全国行政区域地址和编码&#xff0c;在选择器select进行匹配显示&#xff0c;但此方法复杂&#xff0c;因此选择集成区域插件region 步骤一&#xff1a;用命令安装region…

Vue3-09-条件渲染-v-show 的基本使用

v-show 的作用 v-show 可以根据条件表达式的值【展示】或【隐藏】html 元素。v-show 的特点 v-show 的实现方式是 控制 dom 元素的 css的 display的属性&#xff0c; 因此&#xff0c;无论该元素是否展示&#xff0c;该元素都会正常渲染在页面上&#xff0c; 当v-show 的 条件…

如何通过 SSH 访问 VirtualBox 的虚机

VirtualBox 是一款免费虚机软件。在用户使用它安装了 linux 以后&#xff0c;它默认只提供了控制台的管理画面。 直接使用控制台管理 Linux 没有使用诸如 putty 或者 vscode 这样的 ssh 远程管理工具方便。那么可不可以直接使用 ssh 访问 VirtualBox 上的 Linux 呢&#xff1f…

GNN 学习笔记

稍微看一下之后备用。 【图神经网络综述】GNN原理&#xff0b;落地应用实现框架全解_gnn实现-CSDN博客 GNN相比CNN最大的区别在于数据结构&#xff0c;CNN一般作用在二维、三维数据里&#xff0c;如图像、表格数据等&#xff0c;可以进行卷积操作。而GNN作用在一个由节点和边…

模拟目录管理 - 华为OD统一考试(C卷)

OD统一考试(C卷) 分值: 200分 题解: Java / Python / C++ 题目描述 实现一个模拟目录管理功能的软件,输入一个命令序列,输出最后一条命令运行结果。 支持命令: 1)创建目录命令: mkdir 目录名称,如mkdir abc为在当前目录创建abc目录,如果已存在同名目录则不执行任何操作…

案例055:基于微信小程序的四六级词汇

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;SSM JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder X 小程序…

超简单的新手重装Win10系统教程图解

如果我们的电脑系统出现问题了&#xff0c;那么就可以选择重装安装系统&#xff0c;轻轻松松解决系统问题&#xff0c;从而恢复对电脑的正常使用。但是&#xff0c;作为新手用户不懂很多的装机专业知识&#xff0c;所以重装系统的难度比较大&#xff0c;接下来小编给大家介绍超…

pytest-fixtured自动化测试详解

fixture的作用 1.同unittest的setup和teardown,作为测试前后的初始化设置。 fixture的使用 1.作为前置条件使用 2.fixture的的作用范围 1.作为前置条件使用 pytest.fixture() def a():return 3def test_b(a):assert a3 2.fixture的作用范围 首先实例化更高范围的fixture…

Javascript高频面试题

系列文章目录 文章目录 系列文章目录前言1.JavaScript常见数据类型null 和 undefind区别symbol&#xff08;ES6新增&#xff09;、bigInt&#xff08;ES10新增&#xff09; 2.JavaScript判断数据类型的方式3. 和 区别&#xff0c;分别在什么情况使用&#xff1f;4.变量声明 va…

Unity检测AssetBundle是否循环依赖

原理&#xff1a;bundle的依赖关系构建一个二维的矩阵图&#xff0c;如果对角线相互依赖&#xff08;用1标记&#xff09;则表示循环依赖。 using PlasticGui; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEditor; public cl…