详解预处理(1)

news2024/11/23 17:08:12

目录

预定义符号

预处理指令#define

#define定义符号

#define定义宏

#define替换规则

#和##(C语言预处理操作符)

#

##

带副作用的宏参数

宏和函数的对比

命名约定


在之前我们学习了一个文本文件.c生成一个可执行程序。今天我们详细讲解其中的编译下的预处理这个步骤的我们需要掌握的知识点。

预定义符号

标准C语言预定义的符号,在预处理阶段可以直接使用。 这些预定义符号都是语言内置的。

  • __FILE__    进行编译的源文件
  • __LINE__    文件当前的行号
  • __DATE__   文件被编译的日期
  • __TIME__    文件被编译的时间
  • __STDC__   如果编译器遵循ANSI C,其值为1,否则未定义
  • __FUNCTION__ 文件当前的函数名字
#include<stdio.h>
int main()
{
	printf("%s\n", __FILE__);
	printf("%d\n", __LINE__);
	printf("%s\n", __DATE__);
	printf("%s\n", __TIME__);
	//printf("%s\n", __STDC__);//当前使用的是VS2022不遵循ANSI C
	printf("%s\n", __FUNCTION__);
	return 0;
}

  • 换到gcc的底下就可以使用。printf("%s\n", __STDC__); 

预处理指令#define

接着我们来介绍#define。#define是一种预处理指令

#define定义符号

#define定义常量就是定义标识符。

语法:#define  name  stuff

  • name是名字
  • stuff是内容
  • #define MAX 100
  • #define M 3+5
  • #define 仅仅只是替换符号,不进行计算。
  • #defien在定义标识符的时候,不要在最后加上
#define MAX 100
#define STR "abcdef"
#define INT int
#define M 3+5
int main()
{
	int a = MAX;
	INT b = 0;
	printf("%s", STR);
    int c = M;
	return 0;
}

除了上面示例以外还有特殊的写法。

#define MAX 100
#define STR "abcdef"
#define INT int
#define forever for(;;)
int main()
{
	int a = MAX;
	INT b = 0;
	printf("%s", STR);
	forever;
	//替换之后
	for (;;)
		;
	//死循环
	return 0;
}
#define CASE break;case
int main()
{
	int n = 0;
	scanf("%d", &n);
	switch (n)
	{
	case 1:
	CASE 2:
	CASE 3:
	}
	return 0;
}
//替换之后
#define CASE break;case
int main()
{
	int n = 0;
	scanf("%d", &n);
	switch (n)
	{
	case 1:
	     break; 
	case 2:
	     break; 
	case 3:
	}
	return 0;
}
#define MAX 1000
#define reg register      //为 register这个关键字,创建一个简短的名字
#define do_forever for(;;)   //用更形象的符号来替换一种实现
#define CASE break;case     //在写case语句的时候自动把 break写上。

// 如果定义的 stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。
#define DEBUG_PRINT printf("file:%s\tline:%d\t \
             date:%s\ttime:%s\n" ,\
             __FILE__,__LINE__ ,    \
             __DATE__,__TIME__ )

特别注意的是

#define MAX 1000;
#include<stdio.h>
int main()
{
	int a = MAX;
	return 0;
}

#define MAX 1000;
#include<stdio.h>
int main()
{
	int a = 1000;
	;//空语句
	return 0;
}

像上面这种情况可能没有问题,但是建议不要加上 ; ,这样容易导致问题。 比如下面的场景,会出现语法错误:

#define MAX 1000;
#include<stdio.h>
int main()
{
	int a = 10;
	int b = 0;
	if (a>5)
		b = MAX;
	else
		max = 0;
	return 0;
}

#define MAX 1000;
#include<stdio.h>
int main()
{
	int a = 10;
	int b = 0;
	if (a > 5)
		b = 1000;
	;//空语句这里就有问题了
	else
		max = 0;
	return 0;
}
#define MAX 1000;
#include<stdio.h>
int main()
{
	printf("%d", MAX);
	return 0;
}

#define MAX 1000;
#include<stdio.h>
int main()
{
	printf("%d", 1000;);//语法错误
	return 0;
}

#define定义宏

宏是什么?

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

语法

 #define  name( parament-list )  stuff

  • name 是名字
  • parament-list 是一个由逗号隔开的符号表,可能出现在stuff中,可以没有/一个/两个均可。
  • parament-list 的参数可能会出现在  stuff 文本内容中。
  • 参数列表的左括号必须与name紧邻。
  • 如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。
  • 所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用
#define ADD(x,y) x+y                                                
#include<stdio.h>
int main()
{
    int a = 10;
    int b = 20;
    int c = ADD(a, b);
    printf("%d\n", c);
    return 0;
}

看了上面#define定义宏的使用,相信你很快就上手了。


但是这个简单的宏却存在重要错误隐患。

#define ADD(x,y) x+y                                                
#include<stdio.h>
int main()
{
    int a = 10;
    int b = 20;
    int c = 4*ADD(a, b);
    printf("%d\n", c);
    return 0;
}

我们原本想计算4(a+b)的值,应该是90,但是结果确是60。因为替换之后的代码:

#define ADD(x,y) x+y                                                
#include<stdio.h>
int main()
{
    int a = 10;
    int b = 20;
    int c = 4* a + b;//60
    printf("%d\n", c);
    return 0;
}

 有人说将x+y加上括号(x+y)即可,但是这样真的就安全了吗?

#define ADD(x,y) (x*y)                                                
#include<stdio.h>
int main()
{
    int a = 10;
    int b = 20;
    int c = 4*ADD(a+1, b+1);
//  int c=4*(11*21)=4*(32)
    printf("%d\n", c);
    return 0;
}

 我们原本想计算的是4((a+1)*(b+1))结果,但是没有达到我们想要的结果。为什么呢?

#define ADD(x,y) (x*y)                                                
#include<stdio.h>
int main()
{
    int a = 10;
    int b = 20;
    int c = 4*(a+1*b+1);
//int c=4*(10+20+1)=4*(31)
    printf("%d\n", c);
    return 0;
}

综上所诉:宏在书写的时候尽量带上足够多的有效的括号

所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用。导致计算的顺序发生变化。 

#define替换规则

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

  • 首先,在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换
  • 其次,替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值替换
  • 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程

特别提醒:

  • 宏参数和#define 定义中可以出现其他#define定义的变量。但是对于宏,不能出现递归
  • 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索
#define M 100
#define ADD(x,y)  ((x)*(y))//这里的文本内容是不能出现宏的:类似函数递归的这种情况的
#include<stdio.h>
int main()
{
	int a = 10;
	int b = 20;
	int c = 4 * ADD(M, b+10);//M首先会被替换
	printf("%d\n", c);
	printf("Master\n");//这里的M是不会被替换
	return 0;
	return 0;
}

#和##(C语言预处理操作符)

#

提问:如何把参数插入到字符串中?这里我们引入: 

# 就是把宏的参数插入到字符串中


首先,我们需要知道:字符串是有自动连接的特点的。

合并打印和分开打印都能达成一样的效果。

#include<stdio.h>
int main()
{
	printf("hello word\n");
	printf("hello"" word\n");
	return 0;
}

 


 看下面这个代码,我们发现有非常多重复的地方,我们能简化代码,把重复的地方封装成函数。

#include<stdio.h>
int  main()
{
	int a = 10;
	printf("the value of a is %d\n", a);
	int b = 20;
	printf("the value of b is %d\n", b);
	float f = 3.14f;
	printf("the value of f is %f\n", f);
	return 0;
}

我们发现并不能封装成一个函数,因为类型不同,打印的符号也不同。那能不能写一个宏呢? 

宏当然可以解决,宏没有类型的限制。宏只负责替换,不管类型的。

(#n  == "n"  替换到文本内容里面去,把一个宏参数变成对应的字符串)

                           //printf("the value of " "n" " is " "%d" "\n", n);
#define PRINT(n,format) 	printf("the value of "#n" is "format"\n", n);
#include<stdio.h>
int  main()
{
	int a = 10;
	PRINT(a, "%d");//写一个宏,把需要打印的变量和类型传过去
	int b = 20;
	PRINT(b, "%d");
	float f = 3.14f;
	PRINT(f, "%f");
	return 0;
}
//在宏参数面前加# 不是让宏参数完整的替换,而是以字符串的形式替换过去

 

##

##可以把位于它两边的符号合成一个符号。 它允许宏定义从分离的文本片段创建标识符。

: 这样的连接必须产生一个合法的标识符。否则其结果就是未定义的。 

#define CAT(v,n)  v##n
#include<stdio.h>
int main()
{
	int value10 = 100;
	printf("%d\n", CAT(value, 10));
	return 0;
}

 

带副作用的宏参数

宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么在使用这个宏的时候就可能出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。 

#include<stdio.h>
#define MAX(x,y)  ((x)>(y)?(x):(y))
int main()
{
	int a = 3;
	int b = 5;
	int m = MAX(a++, b++);
	printf("%d\n", m);//6
	printf("%d\n", a);//4
	printf("%d\n", b);//6
	return 0;
}

 为什么和我们预期的不一样??因为宏的参数是直接替换进去的,不做任何其他处理。

#include<stdio.h>
#define MAX(x,y)  ((x)>(y)?(x):(y))
int main()
{
	int a = 3;
	int b = 5;
	int m = MAX(a++, b++);
	//int m=((a++)>(b++)?(a++):(b++));
	//        3  >  5
	//        4     6
	//                 no       6
	//                          7
	printf("%d\n", m);//6
	printf("%d\n", a);//4
	printf("%d\n", b);//7
	return 0;
}

 

宏和函数的对比

宏:通常被应用于执行简单的运算。比如在两个数中找出较大的一个。

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

宏的优势:

  • 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹
  • 更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点型等可以用于>来比较的类型。宏是类型无关
  • 宏有时候可以做函数做不到的事情。
  1. 宏的参数可以出现类型,但是函数做不到
  2. 宏的操作符# 和 ##  ,在函数里面是没有的
#define MALLOC(num,type)   (type*)malloc(num*sizeof(type))
int main()
{
	int* p = MALLOC(10, int);
	//
	//>....
	return 0;
}

宏的劣势

  • 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
  • 宏是没法调试的。
  • 宏由于类型无关,也就不够严谨。
  • 宏可能会带来运算符优先级的问题,导致程容易出现错。
属性#define函数
代码长度每次使用时,宏代码都会被插入到程序中。除了非常小的宏之外,程序的长度会大幅度增长函数代码只出现于一个地方;每次使用这个函数时,都调用那个地方的同一份代码
执行速度更快存在函数的调用和返回的额外开销,所以相对慢一些
操作符优先级宏参数的求值是在所有周围表达式的上下文环境里,除非加上括号,否则邻近操作符的优先级可能会产生不可预料的后果,所以建议宏在书写的时候多些括号。函数参数只在函数调用的时候求值一次,它的结果值传递给函数。表达式的求值结果更容易预测。
带有副作用的参数参数可能被替换到宏体中多个位置,所以带有副作用参数求值可能会产生不可预料的结果。函数参数只在传参的时候求值一次,结果更容易控制。
参数类型宏的参数与类型无关,只要对参数的操作是合法的,它就可以使用于任何参数类型。函数的参数是与类型有关的,如果参数的类型不同,就需要不同的函数,即使他们执行的任务是不同的
调试宏是不方便调试的函数是逐语句逐过程调试的
递归宏是不能递归的函数是可以递归的

建议:如果逻辑比较简单,可以使用宏来实现。但是如果计算逻辑比较复杂,就使用函数。

C99之后,C++引入了内联函数的概念,后期我们学习了C++,inline内联函数具有函数和宏的双重特点,内联函数是函数,却又像宏一样,在调用的地方展开。

命名约定

一般来讲函数的宏的使用语法很相似。

所以语言本身没法帮我们区分二者。 那我们平时的一个习惯是:

把宏名全部大写 函数名不要全部大写

✔✔✔✔✔最后,感谢大家的阅读,若有错误和不足,欢迎指正!

代码------→【gitee:唐棣棣 (TSQXG) - Gitee.com】

联系------→【邮箱:2784139418@qq.com】 

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

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

相关文章

BUUCTF 基础破解 1

BUUCTF:https://buuoj.cn/challenges 题目描述&#xff1a; 给你一个压缩包&#xff0c;你并不能获得什么&#xff0c;因为他是四位数字加密的哈哈哈哈哈哈哈。。。不对 我说了什么了不得的东西。。 密文&#xff1a; 下载附件解压&#xff0c;发现一个rar压缩包。 解题思…

C++项目:网络版五子棋对战(收官总结篇)

文章目录 一、项目背景&#xff08;一&#xff09;用户管理&#xff08;二&#xff09;匹配对战&#xff08;三&#xff09;聊天功能 二、开发环境三、核心技术四、项目大流程五、项目模块介绍&#xff08;一&#xff09;实用工具类模块1.意义2.设计 &#xff08;二&#xff09…

FLStudio2024最新破解版注册机

水果音乐制作软件FLStudio是一款功能强大的音乐创作软件,全名:Fruity Loops Studio。水果音乐制作软件FLStudio内含教程、软件、素材,是一个完整的软件音乐制作环境或数字音频工作站... FL Studio21简称FL 21&#xff0c;全称 Fruity Loops Studio 21&#xff0c;因此国人习惯叫…

当vCenter的证书过期、Root密码过期、Root密码遗忘同时发生时的解决方法与步骤

文章目录 当vCenter的MACHINE证书过期、Root密码过期、权限SSO User密码与Root密码遗忘同时发生时的解决方法与步骤1. 强制修改Root密码2. 强制重新生成权限SSO User的密码3、解决证书过期的问题 当vCenter的MACHINE证书过期、Root密码过期、权限SSO User密码与Root密码遗忘同时…

用别人的网站多不舒服,自己手撸一个密码批量生成器网站

自己手撸一个密码批量生成器网站 自己手撸一个密码生成器网站 小编可以这样给你说&#xff0c;这个是最简单的拉&#xff0c;没有任何的装饰&#xff0c;简单容易上手&#xff0c;还是经过小编测试过的哈 python版本django版本python3.8.6Django3.0.5 声明 这个代码也就是小编…

应用程序架构是如何演变的

【squids.cn】 全网zui低价RDS&#xff0c;免费的迁移工具DBMotion、数据库备份工具DBTwin、SQL开发工具等 如果您一直在开发或以某种方式参与应用程序架构&#xff0c;那么在过去的几年中您肯定看到了许多变化。有很多不同类型的架构和技术陆续出现然后消失&#xff0c;以至于…

Windows端口封禁图文教程

文章目录 方式一&#xff1a;打开secpol.msc方式二&#xff1a;Microsoft 管理控制台参考文档 方式一&#xff1a;打开secpol.msc WIN键R输入secpol.msc 在本地安全策略窗口中&#xff0c;选中“IP安全策略&#xff0c;在本地计算机”&#xff0c;右键右侧空白处&#xff0c;选…

ubuntu2004上安装openjdk6

今天因为工作需要要在Ubuntu2004上安装openjdk6&#xff0c;还是有点麻烦的. 这里记录一下过程。 Step 1&#xff1a; openjdk的下载地址在这里&#xff0c;选择对应的架构并将openjdk开头的包全部下载回来。 Step 2&#xff1a; 安装的时候系统缺少以下依赖&#xff1a; …

3d模型轻量化方法以及工具平台

3D模型轻量化是指减少3D模型的文件大小&#xff0c;以便在需要更快的数据传输或更快的渲染速度时使用。 一、以下是几种常见的3D模型轻量化方法&#xff1a; 1、移除不必要的细节&#xff1a;模型中可能存在一些细节&#xff0c;但这些细节对于渲染或使用模型并不重要。通过移…

基于springboot实现乐校园二手书交易管理系统【项目源码+论文说明】

基于springboot实现乐校园二手书交易管理系统演示 摘要 在Internet高速发展的今天&#xff0c;我们生活的各个领域都涉及到计算机的应用&#xff0c;其中包括乐校园二手书交易管理系统的网络应用&#xff0c;在外国二手书交易管理系统已经是很普遍的方式&#xff0c;不过国内的…

如何实现两栏布局?这篇文章告诉你所有的细节!

&#x1f3ac; 江城开朗的豌豆&#xff1a;个人主页 &#x1f525; 个人专栏 :《 VUE 》 《 javaScript 》 &#x1f4dd; 个人网站 :《 江城开朗的豌豆&#x1fadb; 》 ⛺️ 生活的理想&#xff0c;就是为了理想的生活 ! 目录 ⭐ 专栏简介 &#x1f4d8; 文章引言 一、背…

031-从零搭建微服务-监控中心(一)

写在最前 如果这个项目让你有所收获&#xff0c;记得 Star 关注哦&#xff0c;这对我是非常不错的鼓励与支持。 源码地址&#xff08;后端&#xff09;&#xff1a;mingyue: &#x1f389; 基于 Spring Boot、Spring Cloud & Alibaba 的分布式微服务架构基础服务中心 源…

正点原子嵌入式linux驱动开发——外置RTC芯片PCF8563

上一章学习了STM32MP1内置RTC外设&#xff0c;了解了Linux系统下RTC驱动框架。一般的应用场合使用SOC内置的RTC就可以了&#xff0c;而且成本也低&#xff0c;但是在一些对于时间精度要求比较高的场合&#xff0c;SOC内置的RTC就不适用了。这个时候需要根据自己的应用要求选择合…

Halcon 常用通道Scale灰度元操作整理

一、说明 我们将常见的,基于图层信号幅度的操作集中展现出来,以便以后见到相关的操作不会产生唐突。至于这些算子在项目中的灵活应用,我们将在项目中具体指定。 二、基于数量(Scale)的操作 2.1 亮度(Scale)调整 scale_image_max(Image:ImageScaleMax::)

微信批量添加好友,让你的人脉迅速增长

在这个数字化时代&#xff0c;微信作为中国最流行的社交平台之一&#xff0c;已经成为了人们生活中不可或缺的一部分。它的广泛使用为我们提供了无限的社交可能性。你是否曾为了扩大人脉圈子而犯愁&#xff1f;今天&#xff0c;我将向你揭示一个高效添加微信好友的秘密武器&…

Camtasia2024破解版百度云网盘下载

真的要被录屏软件给搞疯了&#xff0c;本来公司说要给新人做个培训视频&#xff0c;想着把视频录屏一下&#xff0c;然后简单的剪辑一下就可以了。可谁知道录屏软件坑这么多&#xff0c;弄来弄去头都秃了&#xff0c;不过在头秃了几天之后&#xff0c;终于让我发现了一个值得“…

居舍系列再续“异国的相遇2023”艺术项目

跨越四城感知艺术声浪&#xff0c;以科技与艺术探索旅行中的情绪共鸣 太古酒店集团旗下居舍系列再度开启两年一度的艺术项目“异国的相遇 2023”&#xff08;Encounters Across Cultures&#xff09;新篇章 — 当艺术与科技同频 &#xff0c;以艺术为媒介&#xff0c;将科技赋予…

16 用于NOMA IoT网络上行链路安全速率最大化的HAP和UAV协作框架

文章目录 摘要相关模型仿真实验仿真结果 摘要 优化无人机到HAP的信道分配、用户功率和无人机三维位置来研究上行安全传输解决非凸问题&#xff0c;采用K-means聚类算法&#xff0c;将成对的用户划分成不同的组&#xff0c;每个簇可以有相应的无人机服务&#xff0c;然后将构造…

NAT技术与代理服务器

目录 一、NAT与NAPT技术 1.NAT技术 2.NAPT技术 &#xff08;1&#xff09;四元组的唯一性 &#xff08;2&#xff09;数据的传输过程 &#xff08;3&#xff09;NAPT的缺陷 二、代理服务器 1.正向代理和反向代理 2.代理服务器的应用 &#xff08;1&#xff09;游戏加…

16、Python --案例实操:控制台打印【 菱形 】和 【 圆 】

目录 控制台打印菱形控制台打印圆 控制台打印菱形 # 控制台打印菱形# 层数 num 8 # 打印上半部分 for i in range(num):# 第一行if i 0:print( * (num - 1 - i) *)else:print( * (num - 1 - i) * (i * 2 - 1) * *)# 打印下半部分 for i in range(num - 1):if i num …