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

news2024/9/9 0:51:21
前言:
        这是程序环境和预处理的下半篇文章。至此,关于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.coloradmin.cn/o/1305518.html

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

相关文章

【算法每日一练]-结构优化(保姆级教程 篇5 树状数组)POJ3067日本 #POJ3321苹果树 #POJ2352星星 #快排变形

目录 今天知识点 求交点转化求逆序对&#xff0c;每次操作都维护一个y点的前缀和 树的变动转化成一维数组的变动&#xff0c;利用时间戳将节点转化成区间 离散化数组来求逆序对数 先将y排序&#xff0c;然后每加入一个就点更新求一次前缀和 POJ3067&#xff1a;日本 思路&…

关于学习计算机的心得与体会

也是隔了一周没有发文了&#xff0c;最近一直在准备期末考试&#xff0c;后来想了很久&#xff0c;学了这么久的计算机&#xff0c;这当中有些收获和失去想和各位正在和我一样在学习计算机的路上的老铁分享一下&#xff0c;希望可以作为你们碰到困难时的良药。先叠个甲&#xf…

scala编码

1、Scala高级语言 Scala简介 Scala是一门类Java的多范式语言&#xff0c;它整合了面向对象编程和函数式编程的最佳特性。具体来讲Scala运行于Java虚拟机&#xff08;JVM)之上&#xff0c;井且兼容现有的Java程序&#xff0c;同样具有跨平台、可移植性好、方便的垃圾回收等特性…

大数据技术6:大数据技术栈

前言&#xff1a;大数据相关的技术名词特别多&#xff0c;这些技术栈之间的关系是什么&#xff0c;对初学者来说很难找到抓手。我一开始从后端转大数据的时候有点懵逼&#xff0c;整体接触了一遍之后才把大数据技术栈给弄明白了。 一、大数据技术栈 做大数据开发&#xff0c;无…

12.12作业

头文件 #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QTimerEvent> #include <QTime> #include <QtTextToSpeech>QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass Widget : public QWidget {Q_OBJECT…

Linux:gdb的简单使用

个人主页 &#xff1a; 个人主页 个人专栏 &#xff1a; 《数据结构》 《C语言》《C》《Linux》 文章目录 前言一、前置理解二、使用总结 前言 gdb是Linux中的调试代码的工具 一、前置理解 我们都知道要调试一份代码&#xff0c;这份代码的发布模式必须是debug。那你知道在li…

揭秘高效大型语言模型:技术、方法与应用展望

近年来&#xff0c;大型语言模型&#xff08;LLMs&#xff09;在自然语言处理领域取得了显著的进展&#xff0c;如GPT-series(GPT-3, GPT-4)、Google-series(Gemini, PaLM), Meta-series(LLAMA1&2), BLOOM, GLM等模型在各种任务中展现出惊人的能力。然而&#xff0c;随着模…

2-Spring

2-Spring 文章目录 2-Spring项目源码地址Spring概述Spring特点&#xff08;优点&#xff09;Spring相关学习网站基于Maven的Spring框架导入Spring的组成及拓展 Spring-IOC--原型理解IOC-原型--示例开发示例-常规开发示例-Set函数&#xff08;IOC原型&#xff09;开发示例-对比思…

【热】如何实现el-table列宽随内容长度自适应最小宽度

非常火急火燎的来写这篇博客&#xff01;&#xff01;因为自己一开始想实现这个效果时在网上查了很久查了很多资料和博客都没有找到能有效达到效果的方法&#xff0c;要么就是别人说有效但是我这里会报错而且难以解决。最后终于被我自己给摸索出来了&#xff01; 应用场景 很…

AI+无代码助力企业供应链优化

内容来自演讲&#xff1a;潘峰 | 预见明日科技&#xff08;北京&#xff09;有限公司 | CEO 摘要 本文介绍了企业供应链中的挑战和解决方案。文章指出&#xff0c;供应链成本占企业经营成本的大部分&#xff0c;且存在供给端和需求端的高度不确定性。为应对这种不确定性&…

Openwrt源码下载出现“The remote end hung up unexpected”

最近项目原因需要下载openwrt21.02版本源码&#xff0c;花费了很多时间&#xff0c;找到正确方法后&#xff0c;发现可以节省很多时间&#xff0c;记录下过程&#xff0c;方便自己&#xff0c;可能方便他人。 一.问题阐述 openwrt21.02下载链接如下&#xff1a; git clone -…

Uncaught ReferenceError: jQuery is not defined解决方法

当我在写java的Maven项目时&#xff0c;出现了这样的一个报错信息&#xff1a; 我一直找代码&#xff0c;抓包&#xff0c;调试&#xff0c;比对代码 jQuery未定义就是指JS的导包没有导进来&#xff01;&#xff01;&#xff01;&#xff01; 导进来就运行正常啦

12.字符串拼接【2023.12.4】

1.问题描述 我们在编程过程中经常会遇到把不同字符串拼接在一起的情况&#xff0c;从而更直观地展示给用户我们所要表达的信息。本题将给出两个字符串&#xff0c;请依次将这两个字符串拼接在一起。 2.解决思路 用字符串拼接符 进行连接两个字符串 3.代码实现 str1input(…

SpringBoot中MyBatis-Flex的集成和使用

一、MyBatis-Flex 是什么​ MyBatis-Flex是一个基于MyBatis的数据访问框架&#xff0c;专门为Flex应用程序而设计的。它提供了一种灵活而高效的方式来处理Flex应用程序中的数据访问&#xff0c;可以轻松地连接到各种数据源&#xff0c;并提供了一些方便的工具和功能&#xff0c…

2023.12.6-12.11 黑马知行教育项目实战,访问咨询意向线索主题

目录 简单介绍: 一.项目背景介绍 二.项目架构介绍 三.项目内容 3.1访问和咨询分析主题: 3.1.1 表与表之间的关联 3.1.2访问咨询主题需求汇总:最终需在ADS层制作六张表 3.1.3 访问咨询DWS大宽表建表与导入数据 3.2意向线索主题需求分析 3.2.1意向线索主题需求汇总:最终需在…

最新科研成果:在钻石中存储多比特数据,实现25GB数据密度

近日&#xff0c;纽约城市大学&#xff08;CUNY&#xff09;的研究人员已经成功地利用钻石原子结构中的小型氮缺陷作为“颜色中心”来写入数据进行存储&#xff08;然后是检索&#xff09;。这项发表在《自然纳米技术》上的技术允许通过将数据编码为多个光频率&#xff08;即颜…

[GFCTF 2021]文件查看器

文章目录 前置知识可调用对象数组对方法的调用GC回收机制phar修改签名 解题步骤 前置知识 可调用对象数组对方法的调用 我们先来看下面源码 <?phperror_reporting(0);class User{public $username;public $password;public function check(){if($this->username"…

架构LAMP

目录 1.什么是LAMP 2.LAMP组成及作用 3.搭建Apache httpd服务 4.编译安装mysqld 服务 5.编译安装PHP 解析环境 6.安装论坛 1.什么是LAMP LAMP架构是目前成熟的企业网站应用模式之一&#xff0c;指的是协同工作的一整套系统和相关软件&#xff0c;能够提供动态Web站点服务…

Threejs项目实战之一:汽车外观换肤效果三维展示

目录 最终效果1 创建项目2 安装插件3 编写代码3.1 准备工作3.2 代码编写3.2.1 在template标签中构建html页面3.2.2 在style标签中构建页面样式文件3.2.3 在script标签中编写js代码 最终效果 先看下最终实现的效果 接下来&#xff0c;我们就从创建项目开始&#xff0c;一步一步…

三天精通Selenium Web 自动化 - 测试框架(一)

1 框架结构雏形 返回 新建的一个java project&#xff0c;项目名为autotest,创建如下结构 图1 框架结构雏形 base&#xff1a;里面有个基类 &#xff08;BaseParpare.java&#xff09;&#xff0c;这个类的主要作用是启动&#xff08;启动浏览器使用了TetsNG的BeforeClass&am…