C语言-预处理详解

1.预处理符号

C语言中设置了一些预定义符号,可以直接使用,预定义符号是在预处理期间处理的。

__FILE__//代表当前进行编译的源文件
__LINE__//文件当前行号
__DATE__//文件当前日期
__TIME__//文件当前时间
__STDC__//如果编译器遵循ANSIC,其值为1,否则未定义

接下来,我们可以试验一下:

#include<stdio.h>int main()
{printf("%s\n",__FILE__);printf("%s\n",__DATE__);printf("%s\n",__TIME__);printf("%d\n",__LINE__);return 0;   
}

经过预处理之后,代码变成了:

int main()
{printf("%s\n","test.c");//目前在编译的文件名printf("%s\n","July 19 2024");printf("%s\n","18:17:54");printf("%d\n",8);return 0;
}

2.#define定义字符常量

基本语法:

                #define 名字 内容

即 #define name stuff

例子:

#define MAX 1000
#define reg register//为register这个关键字创了一个简短的名字
#define do_forever for(;;)//变成死循环了
#define CASE break;case//在写case语句时自动把break写上
//如果定义的语句过长,可以分成几行写,除最后一行外,每行最后都要加上一个反斜杠'\'(续行符)。注意:其后不能有空格,应直接回车,否则,续的就不是原来的语句了。
#define DEBUG_PRINT printf("file:%s\tline:%d\t\date:%s\ttime:%s\n",\__FILE__,__LINE__,    \__DATE__,__TIME__)

            

思考:

在define定义标识符的时候,如果在末尾加上‘;’怎样?

比如:

#define MAX 1000
#define MAX 1000;

建议不要加‘;’,因为可能会导致一些问题。

例:

当用了:

printf("%d\n",MAX);

就会出问题。

如果是这个:

if(condition)max=MAX;
elsemax=0;

那么等替换之后,if和else之间就是两条语句,而没有大括号时if和else后面只能接一条语句,else就不知道是和哪个else匹配的,就会出现语法错误。

3.#define定义宏

#define机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或者定义宏(define macro)。

下面是其

定义方式:

#define name(parament-list) stuff

其中的parament-list是一个由逗号隔开的符号表,带表参数,它可能出现在stuff中。

注意:

        参数列表的左括号必须与name紧邻,如果二者之间有任何的空白存在,参数列表就会解释为stuff的一部分。

举例:

#define f(x) x*x

4.带有副作用的宏参数

所谓的副作用就是在实现预想的结果的同时,影响了其他的结果。

当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预料的后果。

例如:

#define MAX(a,b) ((a)>(b)?(a):(b))
.....
int x=5;
int y=8;
int z=MAX(x++,y++);

5.宏替换的规则

在程序中扩展#define定义符号和宏时,需要涉及几个步骤。

1.在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,他们首先被替换。

2.替换的文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值替换。

3.最后,再对结果文本进行扫描,看看它是否包含任何由#define定义的符号。如果是,则重复上述处理过程。

注意:

1.宏参数和#define定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。

2.当预处理器搜索#define定义的符号时,字符串常量的内容并不被搜索。

例:

#define M 10

但是“MARY"中的M是不会被替换的。

6.宏与函数的对比

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

比如在两个数中找到较大数。

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

而如果用函数来完成,有一下缺点:

1.用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间要多。所以在小型计算中,宏比函数在程序的规模和速度上要更胜一筹。

2.更为重要的是,函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用,反之,宏可以适用于整型,长整型,浮点型等可以用>来比较大小的类型。宏的参数是类型无关的。

宏有些时候可以做到函数做不到的事。比如:宏的参数可以出现类型,而函数不可以:

#define MALLOC(num,type) (type*)malloc(num*sizeof(type))

和函数相比,宏的劣势:

1.每次使用宏时,一份宏定义的代码将插入到程序中。除非这段代码较短,否则可能大幅度增加程序的长度。

2.宏没法调试

3.宏由于是类型无关的,不够严谨。

4.宏可能带来运算符优先级的问题,导致程序容易出错。

至于第四点,有个例子:

#define MAX(a,b) a*b
//当a=3+1 b=1+2时

7.#和##

一.#

'#'运算符将宏的一个参数转换为字符串常量。它仅允许出现在带参数的宏的替换列表中。

‘#'运算符所执行的操作可以理解为“字符串量化”。

适用场景:

当我们想要打印出“The value of x is %d"的情况

例如:

The value of b is 12
The value of a is 1

而以上的语句大致相同,需要改变的是某个数据的名字以及数据的具体大小。

为了方便后面的讲解,我们需要补充一个知识点:

在c语言中,printf("Hello world!")与printf("Hello""world!”)的结果是一样的。

回到原来的问题:我们把这个语句定义为宏

即:

#define PRINT(n) printf("The value of"#x"is%d\n",x);

当我们使用这个宏时:

int a=1;
PRINTF(a);

就会变成:

The value of a is 1

而#x就是将x转化为"x"这个字符串。

如果还想在字符串中加一些内容,可以直接#内容。

注意:我们需要使用这个符号的一个原因是:宏在替换时,不会替换字符串内的内容。

二.##

'##'字符用在宏定义里可以把两个符号粘连在一起,合成一个符号。被称为记号粘合。

这样的连接产生的标识符必须是合法的,否则就是未定义的。

它有如下应用场景:

当我们想要找出两个数中的较小数时,不同的数据类型得写不同的函数,但是,这样的函数结构是类似的,于是,我们就想到用宏,然而,这些不同函数的名称也要随着参数改变,同时要是合法的,而宏的参数只有一个,那就是类型,所以,我们想到用记号粘合。

int int_max(int x,int y)
{return x>y?x:y;
}
float float_max(float x,float y)
{return x>y?x:y;
}

改法:

#define TYPE_MAX(type)    \
type type##_max(type x,type y)\
{                            \return (x>y?x:y);        \
}

具体使用:

TYPE_MAX(int)
//在预编译中替换为:
//int_max(int x,int y)
//{
//   return x>y?x:y;
}
TYPE_MAX(float)
//在预编译中替换为:
//float float_max(float x,float y)
//{
//    return x>y?x:y;
//}int main()
{int m=int_max(2,3);float n=float_max(3.5f,4.5f);return 0;
}

8.命名约定

由于宏与函数的使用语法相似,所以语言上无法帮我们区分二者,所以我们通常用一下命名习惯区分:

1.宏的全名全部大写。

2.函数名不要全部大写。

当然,这个规定不一定要遵守,c语言本身就有一些没有遵守,例如:

offsetof———宏:计算结构体成员相较于结构体起始位置的偏移量。

9.#undef

这个指令用于移除一个宏定义。是可以直接写入主函数的,不过需要注意的是:它与#define相同,不用单独加';'来表示语句的结束。

例如:

#define NAME 10int main()
{#undef NAMEreturn 0;
}

10.命令行定义

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

例如:当我们根据同一个源文件要编译出一个程序的不同版本时,这个能力就能起到作用。

假如某个程序中声明了一个某长度的数组,如果机器的内存有限,我们需要一个很小的数组,但是另一个机器的内存大些,我们需要一个更大的数组时,就可以利用这个能力。

好处:代码可以根据需求快速调整。

11.条件编译

利用条件编译指令我们可以很方便的编译或放弃一条或一组语句。

例如:某些语句起到的是调试作用,我们希望在某些条件下使用。

#define _DEBUG_//当不想要ifdef后面的语句时,就注释掉这句int main()
{#ifdef _DEBUG_printf("6\n");#endifreturn 0;
}

常见的条件编译指令:

#if 常量表达式 //如果为假,后面的语句就不参与编译//...
#endif
//注意:一定要以#endif结尾//多分支条件编译#if 常量表达式//...
#elif 常量表达式//...
#else//...
#endif//判断是否被定义
#if defined(symbol)
#ifdef symbol#if !definede(symbol)
#ifndef symbol

注意:这些指令是可以相互嵌套的

12.头文件的包含

一.本地文件的包含:

#include"filename"

查找方式:先在源文件下的目录里找,如果该文件未被找到,编译器就会像找库函数一样在标准位置查找头文件。

如果找不到就显示编译错误。

二.库文件包含

#include<filename>

查找方式:直接在标准文件下查找,如果找不到就提示编译错误。

当然,库文件也可以用""包含,不过,这样的话效率就会低一些,也会没那么容易区分本地文件和库文件。

三.嵌套文件的包含

如果一个同文件被多次包含,编译器在预处理时就会多次替换头文件的内容,这样的重复包含对编译的压力很大。所以,我们可以使用一些方法避免这样的情况出现。

利用条件编译防止重复包含。

方法一:

在每个头文件里加上:

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

方法二:

在每个头文件中加上:

#pragma once

13.其他预处理指令

目前不想写了,可参考《C语言深度剖析》这本书。

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

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

相关文章

重复图片查找:巧用Python和OpenCV进行图像哈希与汉明距离检测以从海量图片中找出重复图片

重复图片查找&#xff1a;巧用Python和OpenCV进行图像哈希与汉明距离检测以从海量图片中找出重复图片 1. 导言2. 环境准备3. 图像哈希&#xff08;pHash&#xff09;原理4. 汉明距离原理5. 代码实现导入必要的库图像哈希计算函数汉明距离计算函数查找重复图片函数示例使用 在处…

【ELK+Kafka+filebeat分布式日志收集】kibana基本使用教程

基本功能 查询数据 在Discover中查看数据: 进入Discover视图。 选择之前创建的索引模式。 使用搜索框和过滤器来查询和过滤数据。 使用KQL(Kibana Query Language): KQL是一种简单的查询语法,用于过滤Elasticsearch中的数据。 支持Terms Query、Boolean Queries、Range …

分布式锁的实现:Redis和Zookeeper

在分布式系统中&#xff0c;确保数据的一致性和避免并发问题是非常重要的。分布式锁是解决这些问题的关键技术之一。本文将详细介绍如何使用Redis和Zookeeper实现分布式锁&#xff0c;并通过Java代码示例帮助读者理解其实现原理。 1. 分布式锁的基本概念 分布式锁是一种用于协…

乐鑫AWS IoT ExpressLink方案,简化物联网设备连接AWS IoT服务

在现代科技迅速发展的今天&#xff0c;物联网&#xff08;IoT&#xff09;已经成为连接物理世界与数字世界的重要桥梁&#xff0c;越来越多的设备开始接入网络&#xff0c;实现智能化控制。 在这个大背景下&#xff0c;乐鑫携手亚马逊&#xff0c;推出了AWS IoT ExpressLink方…

Linux:Linux发展史

大家好&#xff01;此篇文章并非技术博文&#xff0c;而是简单了解Linux的时代背景和发展史&#xff0c;只有知其所以然才能让我们更好地让走进Liunx的世界&#xff01; 一、计算机的发展历史背景 首先我们要知道&#xff0c;早期大多数科技的进步都是以国家的对抗为历史背景的…

Java语言程序设计基础篇_编程练习题**15.17 (几何问题:寻找边界矩形)

**15.17 (几何问題:寻找边界矩形) 请编写一个程序&#xff0c;让用户可以在一个二维面板上动态地增加和移除点&#xff0c;如图15-29a所示。当点加入和移除的时候&#xff0c;一个最小的边界矩形更新显示。假设每个点的半径是 10 像素 解题思路&#xff1a; 这道题可以从编程…

数学建模(4)——支持向量机算法

一、代码示例 import numpy as np import matplotlib.pyplot as plt from sklearn import datasets from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler from sklearn.svm import SVC from sklearn.metrics import clas…

1-如何挑选Android编译服务器

前几天&#xff0c;我在我的星球发了一条动态&#xff1a;入手洋垃圾、重操老本行。没错&#xff0c;利用业余时间&#xff0c;我又重新捣鼓捣鼓代码了。在接下来一段时间&#xff0c;我会分享我从服务器的搭建到完成Android产品开发的整个过程。这些东西之前都是折腾过的&…

JVM类加载机制详解

Java在运行期才对类进行加载到内存、连接、初始化过程。这使得Java应用具有极高的灵活性和拓展性&#xff0c;可以依赖运行期进行动态加载和动态连接。 主要加载哪些&#xff1f;Java中的数据类型分为基本数据类型和引用数据类型&#xff0c;基本数据类型由虚拟机预先定义&…

Flask: URL 视图函数 路由

Flask: URL 视图函数 路由 from flask import Flask, request app Flask(__name__) # URL: / # 视图函数: hello_world # 路由: 将根URL映射到hello_world函数 app.route(/) def hello_world(): return Hello, World! # URL: /user/<int:user_id> # 视图…

站在资本投资领域如何看待分布式光纤传感行业?

近年来&#xff0c;资本投资领域对于分布式光纤传感行业并不十分敏感。这主要是由于分布式光纤传感技术是一个专业且小众的领域&#xff0c;其生命周期相对较长&#xff0c;缺乏爆发性&#xff0c;与消费品或商业模式创新产业有所不同。此外&#xff0c;国内的投资环境也是影响…

双向长短期记忆网络(BiLSTM)及其Python和MATLAB实现

长短期记忆网络&#xff08;Long Short-Term Memory&#xff0c;LSTM&#xff09;是一种特殊的循环神经网络&#xff08;RNN&#xff09;&#xff0c;旨在克服传统RNN在长序列数据中的梯度消失和梯度爆炸问题&#xff0c;并能更有效地捕捉长期依赖关系。除了基本的LSTM结构外&a…

谷粒商城实战笔记-42-前端基础-Vue-生命周期和钩子函数

下面是Vue官网关于生命周期及不同阶段的钩子函数的图示。 Vue 实例生命周期是指从一个组件被创建到最终被销毁的整个过程。 在这一过程中&#xff0c;Vue 提供了一系列的“钩子”函数&#xff0c;在生命周期的不同阶段执行自定义的代码。 以下是 Vue 对象生命周期的主要阶段…

Ubuntu 20.04搭建OpenCV 4.5.0 C++环境

Ubuntu 20.04搭建OpenCV 4.5.0 & C环境_ubuntu opencv4.5.0-CSDN博客

图片转换之heic转jpg(使用ImageMagick)

缘由&#xff1a;iphone的图库&#xff0c;用jpg拍照保存后内存占比较大&#xff0c;heic格式会微缩不少。问题来了&#xff0c;电脑不能直接小图预览heic。 分析&#xff1a;现在就是解决小图预览的问题&#xff08;大图用wps可以看&#xff09; 解决&#xff1a;查找了一些…

Vue中的diff算法

文章目录 diff算法是什么比较方式源码分析patchpatchVnodeupdateChildren小结Vue3中diff算法优化diff算法是什么 diff算法是一种通过同层的树节点进行比较的高效算法 其有两个特点: 比较只会在同层级进行,不会跨层级比较在dff比较的过程中,循环从两边向中间比较(首位交叉…

基于神经网络的聚类分析

神经网络是一种非常有用的机器学习模型&#xff0c;具有无数的应用。今天&#xff0c;我们将分析一个数据集&#xff0c;看看我们是否可以通过应用无监督聚类技术来查找数据中的模式和隐藏分组&#xff0c;从而获得新的见解。 我们的目标是对复杂数据进行降维&#xff0c;以便…

基于深度学习的草莓成熟度实时检测系统(UI界面+YOLOv8/v7/v6/v5模型+完整代码与数据集)

1. 引言 在农业领域&#xff0c;草莓的成熟度检测是保证果实品质的重要环节。传统的方法依赖于人工经验&#xff0c;不仅耗时费力&#xff0c;还容易出错。本文介绍如何使用YOLO&#xff08;You Only Look Once&#xff09;系列模型&#xff08;YOLOv8/v7/v6/v5&#xff09;构…

mysql面试(一)

前言 从今天开始&#xff0c;更新一些mysql的基础知识&#xff0c;面试会遇到的知识点之类的内容。比如四个隔离级别&#xff0c;mvcc机制&#xff0c;三大日志&#xff0c;索引&#xff0c;B树的形成等等&#xff0c;从数据库的底层来剖析索引和树是怎么形成的&#xff0c;以…

接口自动化测试框架实战-0-项目功能概览

熟悉我CSDN的朋友们应该知道&#xff0c;之前已经更新了requests、pytest、allure2、yaml、jenkins、postman等基础知识的合集。相信大家对接口测试已经有了全面的认识&#xff0c;现在应该迫不及待地想要一个实战项目了。接下来的文章中&#xff0c;我们将把这些知识点串联起来…