C语言预处理详解(上)(30)

文章目录

  • 前言
  • 一、预定义符号
  • 二、#define定义标识符
  • 三、#define定义宏
  • 四、#define的替换规则
  • 五、带有副作用的宏
  • 六、宏和函数的对比
  • 七、#undef的作用
  • 八、# 和
    • #的作用
    • ##的作用
  • 总结


前言

  C语言的入门学习差不多要到尾声了,感觉如何呢~
  前文说编译的第一步就是预编译(即预处理),那具体这个阶段会做哪些工作呢?

正文开始!


一、预定义符号

  在C语言中,有一些有意思的预定义符号,这些预定义符号都是语言内置的,即以及定义好的,我们可以直接使用。预定义符号主要有以下几个:

// 实际项目可能会有用
__FILE__        //进行编译的源文件
__LINE__        //文件当前的行号
__DATE__        //文件被编译的日期
__TIME__        //文件被编译的时间
__FUNCTION__    //进行编译的函数
__STDC__        //如果编译器遵循ANSI C,其值为1,否则未定义

这些预定义符号是已经用#define定义好的,在代码运行后的预处理阶段会被替换为相应的内容

其使用就是直接打印即可,只要注意占位符是 %s 还是 %d 即可:

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

运行结果如下:
在这里插入图片描述

二、#define定义标识符

用#define定义标识符的格式如下:

#define MAX 100
#define reg register //懒人觉得register太长了

这些被#define定义的标识符都将在预处理阶段被编译器替换成对应的内容

我产生了一些联想,事实上每个初学者经常会犯以下错误:
main->mian;,->,;(->( ;true->ture

于是我们将错就错,有了以下解决方案

#define mian main
#define ,
#define (
#define )
#define ture true
#define ;

开玩笑的,我们还是要从源头上解决,多敲多练,尽可能避免这种一般性的低级错误

三、#define定义宏

#define机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(define macro)
其声明方式为:#define name( parament-list ) stuff,其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中。
注意:参数列表的左括号必须与name紧邻。如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分

#include <stdio.h>
#define SQUARE(x) x*x //求x的平方int main()
{int ret = SQUARE(5);//相当于int ret = 5*5,在预处理阶段就被展开了;printf("%d\n", ret); //结果为25return 0;
}

意思是这么个意思,但是我们要注意这个声明是不太正确的,我们不能在宏的使用里吝啬括号,不然可能会发生以下错误:

printf(“%d\n” ,SQUARE( 5 + 1) );
展开为:printf(“%d\n” ,5 + 1 * 5 + 1);
结果是打印11,与我们想要的25相悖
正确声明是:#define SQUARE(x) ((x)*(x))

也就是说用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用

四、#define的替换规则

  以下面代码为例,现在我们来讲解下#define的替换规则

#include <stdio.h>
#define MAX 100
#define SQUARE(x) ((x)*(x)*MAX)int main()
{int ret = SQUARE(5);printf("%d\n", ret);return 0;
}
  1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换

#define定义的宏中含有#define定义的符号MAX,则调用该宏时,首先将MAX替换

#include <stdio.h>
#define SQUARE(x) ((x)*(x)*100)
int main()
{int ret = SQUARE(5);printf("%d\n", ret);return 0;
}
  1. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值替换

此时代码等价于:

#include <stdio.h>
int main()
{int ret = ((5)*(5)*100);printf("%d\n", ret);return 0;
}
  1. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程

上例不再包含任何由#define定义的符号

我们也要注意以下几点:

  1. 宏参数和#define 定义中可以出现其他#define定义的变量。但是对于宏,不能出现递归
#define FAC(x) (x)*FAC(x-1) //error
  1. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索
#include <stdio.h>
#define MAX 100
int main()
{// 以下代码字符串中的MAX不会被替换为100,而字符串外的MAX会被替换printf("MAX = %d\n", MAX); // 结果为MAX = 100return 0;
}

五、带有副作用的宏

  代码执行后,除了达到我们想要的结果之外,还导致了其他问题的发生,我们就说该条语句带有副作用

例如,我们现在想比较a和b的大小,并将其较大值赋值给c,之后再将a和b同时加1:

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

这段代码看似没有问题,但是结果却是不正确的,因为该宏经过替换后,等价于以下代码:

#include <stdio.h>
int main()
{int a = 10;int b = 20;int c = ((a++)>(b++)?(a++):(b++));printf("%d\n", c);return 0;
}

所以,当我们使用宏的时候,应该避免传入带有副作用的宏参数

六、宏和函数的对比

  可能通过前面的学习,你也发现了一个问题,就是宏和函数的感觉特别像,那两者可以等同吗?
  事实上是不行的,两者有以下区别:

  1. 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹(因为函数要开栈帧,宏不用,这个我们下篇会介绍
  2. 更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。但是宏可以适用于整形、长整型、浮点型等可以用于来比较的类型。宏是类型无关的
  3. 宏有时候可以做到函数做不到的事情。例如,宏的参数可以出现类型,但是函数却不可以
#include <stdio.h>
#include <stdlib.h>
#define MALLOC(num,type) (type*)malloc(num*sizeof(type))int main()
{int* p2 = MALLOC(10, int);if (p2 == NULL){printf("p2开辟失败\n");return 1;}free(p2);p2 = NULL;return 0;
}

可是,宏也有自己的劣势,在于:

  1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度
  2. 宏是没法调试的(我认为这点非常不友好!)
  3. 宏由于类型无关,也就不够严谨
  4. 宏可能会带来运算符优先级的问题,导致程容易出现错

倘若用一张图来表明宏和函数的区别,那么可以用下图:
在这里插入图片描述

七、#undef的作用

  #undef可以移除一个#define定义的标识符或宏

#include <stdio.h>
#define MAX 100
int main()
{printf("%d\n", MAX);//正常使用
#undef MAX // 此时MAX失效printf("%d\n", MAX); //报错,MAX未定义
}

八、# 和

这个我真感觉没什么用,但是还是讲一下吧:

#的作用

把一个宏参数变成对应的字符串

在介绍#的作用的之前,我先向大家说明一下:字符串是有自动连接的特点的:

	char arr[] = "hello ""world!";//等价于char arr[] = "hello world!";printf("helll ""world!\n");//等价于printf("helll world!\n");

认识到这点后,我们来看代码:

#include <stdio.h>
int main()
{int age = 10;printf("The value of age is %d\n", age);double pi = 3.14;printf("The value of pi is %f\n", pi);int* p = &age;printf("The value of p is %p\n", p);return 0;
}

我们发现,printf要打印的内容大部分是一样的,那么,为了避免代码冗余,我们可不可以将其封装成一个函数或是宏呢?
答案是不行,不信大家可以自行尝试一下,这时候就可以考虑用这个#了:

#include <stdio.h>
#define print(data,format) printf("The value of "#data" is "format"\n",data)
int main()
{int age = 10;print(age, "%d");double pi = 3.14;print(pi, "%f");int* p = &age;print(p, "%p");return 0;
}

这时我们只需将要打印的变量的变量名和打印格式传入即可。该代码经过预处理后等价于以下代码

#include <stdio.h>
int main()
{int age = 10;printf("The value of ""age"" is ""%d""\n", age);double pi = 3.14;printf("The value of ""pi"" is ""%f""\n", pi);int* p = &age;printf("The value of ""p"" is ""%p""\n", p);return 0;
}

又因为字符串有自动连接的特点,所以可以打印出期望的结果

##的作用

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

#include <stdio.h>
#define CAT(x,y) x##y
int main()
{int workhard = 100;printf("%d\n", CAT(work, hard));//打印100return 0;
}

总结

  其实预处理的过程还是蛮复杂的,关于宏和函数的对比那块大家要自行好好掌握

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

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

相关文章

计算机网络:计算机网络概述 —— 描述计算机网络的参数

文章目录 数据量性能指标速率带宽数据传输速率 吞吐量时延分析时延问题 时延带宽积往返时间利用率丢包率丢包的情况 抖动可用性可靠性安全性 计算机网络是现代信息社会的基础设施&#xff0c;其性能和可靠性对各类应用至关重要。为了理解和优化计算机网络&#xff0c;我们需要深…

python习题2

1、输出一个年份&#xff0c;判断其是不是闰年 #输入一个年份&#xff0c;判断其是否是闰年 y eval(input()) if y%4 0 and y%100 ! 0:print("是") elif y%4000:print("是") else:print("不是") 2、模拟智能客服&#xff1a; 按1查询账户余额…

【linux】麒麟v10安装prometheus监控(ARM架构)

Prometheus介绍 Prometheus 是一个开源的系统监控和警报工具包&#xff0c;最初由 SoundCloud 开发&#xff0c;现在是一个独立的开源项目&#xff0c;并且是云原生计算基金会&#xff08;CNCF&#xff09;的一部分。Prometheus 以其强大的数据模型和灵活的查询语言&#xff0…

Jedis多线程环境报错:redis Could not get a resource from the pool 的主要原因及解决办法。

本篇文章主要讲解&#xff0c;Jedis多线程环境报错&#xff1a;redis Could not get a resource from the pool 的主要原因及解决办法。 作者&#xff1a;任聪聪 日期&#xff1a;2024年10月6日01:29:21 报错信息&#xff1a; 报文&#xff1a; redis Could not get a resou…

[SQL] 安装

一 Windows 1.1 下载 进入Mysql的官方网站,点击下载->找到社区版本 选择对应操作系统进行下载。 点击下载 选择直接下载即可 1.2 安装 选择Full安装&#xff1a; MySQL服务器、客户端程序和其他附加工具如果只需要服务端那就选择Server only即可 点击执行,等待组件下载完…

PostgreSQL 任意命令执行漏洞(CVE-2019-9193)

记一次授权攻击通过PostgreSql弱口令拿到服务器权限的事件。 使用靶机复现攻击过程。 过程 在信息收集过程中&#xff0c;获取到在公网服务器上开启了5432端口&#xff0c;尝试进行暴破&#xff0c;获取到数据库名为默认postgres&#xff0c;密码为1 随后连接进PostgreSql …

IDEA里面的长截图插件

1.我的悲惨经历 兄弟们啊&#xff0c;我太惨了&#xff0c;我刚刚在准备这个继承和多态的学习&#xff0c;写博客的时候想要截图代码&#xff0c;因为这个代码比较大&#xff0c;一张图截取不下来&#xff0c;所以需要长截图&#xff0c;之前使用的qq截图突然间拉胯&#xff0…

【RK3588】rknpu驱动流程

画图工具 &#xff1a; https://pixso.cn/

第十四章 Redis之全局唯一ID(分布式集群)

目录 一、概念 ‌二、全局唯一ID的生成方法‌ 三、Redis生成全局ID 3.1. 生成策略 3.2. 代码 一、概念 全局唯一ID是指在分布式系统中&#xff0c;每个实体都有一个唯一的标识符&#xff0c;确保在不同的节点或服务之间能够唯一标识一个实体。这种唯一性对于数据的一致性…

OpenCV库模块解析

1.OpenCV库每个模块解析 2.OpenCV的常用函数 它为计算机视觉应用程序提供了一个通用的基础设施&#xff0c;并加速了在商业产品中使用机器感知。作为BSD许可的产品&#xff0c;OpenCV使企业可以很容易地利用和修改代码。该库拥有超过2500个优化算法&#xff0c;其中包括经典和最…

Android -- [SelfView] 自定义多色渐变背景板

Android – 自定义多色渐变背景板 前言&#xff1a; Android 自带的 xml 文件内 gradient 设置渐变最多只有三种颜色&#xff0c;使用方便但范围受限&#xff0c;不能很好满足各种需求&#xff1b; 本款多色渐变背景板应运而生&#xff1a;* 1. 支持圆角模式&#xff0c;矩形模…

Windows环境下CTRL+C信号处理函数的执行线程

1. 捕获CTRLC 有时候我们希望自己的程序被CTRLC以后&#xff0c;可以先执行一些收尾的工作才结束&#xff0c;比如释放动态内存&#xff0c;关闭网络端口、保存一些状态日志等等&#xff0c;可以用到C的signal的机制。 例程如下&#xff1a; #include <iostream> #inc…

【工具】前端js数字金额转中文大写金额

【工具】前端js数字金额转中文大写金额 代码 <!DOCTYPE html> <html lang"zh"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>金额转…

WIFI网速不够是不是光猫的“路由模式”和“桥接模式”配置错了?

光猫&#xff08;光纤调制解调器&#xff09;是一种用于将光纤信号转换为数字信号的设备&#xff0c;通常用于家庭或企业网络中。光猫可以在不同的工作模式下运行&#xff0c;其中最常见的两种模式是“路由模式”和“桥接模式”。以下是这两种模式的详细解释及其优缺点。 一、路…

『网络游戏』服务器向客户端分发消息【20】

对服务器添加System引用 修改脚本&#xff1a;LoginSys.cs 修改脚本&#xff1a;NetSvc.cs 修改脚本&#xff1a;ServerSession.cs 修改脚本&#xff1a;GameMsg.cs 修改脚本&#xff1a;MsgPack.cs 修改脚本&#xff1a;LoginSys.cs 修改脚本&#xff1a;ServerRoot.cs 修改脚…

GAMES101(19节,相机)

相机 synthesis合成成像&#xff1a;比如光栅化&#xff0c;光线追踪&#xff0c;相机是capture捕捉成像&#xff0c; 但是在合成渲染时&#xff0c;有时也会模拟捕捉成像方式&#xff08;包括一些技术 动态模糊 / 景深等&#xff09;&#xff0c;这时会有涉及很多专有名词&a…

探索新境界,尽在Codigger新版官网!

&#x1f389; 重大更新&#xff01; 我们自豪地宣布 Codigger 官网焕然一新&#xff0c;带来前所未有的体验&#xff01; &#x1f31f; 全新界面&#xff1a;Desktop享受更加直观、现代的视觉盛宴&#xff0c;发现 Codigger 的无限可能。 &#x1f680; 增强功能&#xff1…

ThinkPHP5bootstrapMySQL开发学习平台(包括后台管理功能、PC端网页、移动端网页)手把手运行源码

一、项目预览(全部源码链接在最下面) 功能及页面持续优化中...... 二、本地运行方式 1、下载源码包进行解压(源码在最下面) 2、下载phpstudy_pro,并运行Apache&

【Qt】Qt安装(2024-10,QT6.7.3,Windows,Qt Creator 、Visual Studio、Pycharm 示例)

文章目录 一、Qt 简介二、安装开源版本2.1 Qt 官网 与 版本选择2.2 Qt 安装程序 三、使用示例3.1 Qt Creator3.11 示例程序3.12 新建C项目3.13 新建Python项目 3.2 Visual Studio 附录附录 1&#xff1a;Additional Libraries 说明附录2 &#xff1a;老版本安装附录3&#xff1…

Java数据类型常量

目录 一、数据类型 1.1分类 1.2关键字&内存占用&范围 1.3包装类 1.4说明 1.5类型转换 1.6类型提升 二、常量 2.1java中的常量 2.2定义常量 2.3分类 一、数据类型 1.1分类 1.2关键字&内存占用&范围 数据类型关键字内存占用范围字节型byte1字节-128…