C语言之函数式宏

目录

函数和数据类型

函数式宏

函数和函数式宏

函数式宏和对象式宏

不带参数的函数式宏

函数式宏和逗号运算符


函数式宏和函数类似并且比函数更加灵活,下面我们就来学习函数式宏的相关内容。

函数和数据类型

我们来编写一个程序,它能计算出所读取数值的平方,并将结果显示出来,我们先来编写适用于int类型和double类型的函数。

/*整数和浮点数的平方*/
#include<stdio.h>

/*计算int型数的平方值*/
int sqr_int(int x)
{
	return x * x;
}
/*计算double型数的平方值*/
double sqr_double(double x)
{
	return x * x;
}
int mian()
{
	int x;
	double n;
	
	printf("请输入一个整数:");
	scanf("%d", &x);
	printf("该整数的平方是%d\n", sqr_int(x));
	
	printf("请输入一个整数:");
	scanf("%lf", &n);
	printf("该整数的平方是%f\n", sqr_double(n));
	
	return 0;
}

如果我们又想计算其他数据类型的平方呢?比如计算long型数的平方,就得创建出一个sqr_long的函数,如果接二连三的写出这种功能相近,名称相似的函数,程序就会充斥着这种似是而非的函数

下面我们来学习解决办法:函数式宏


函数式宏

函数式宏(function—like macro)较之对象式宏可以进行更为复杂的代换。

#include<stdio.h>

#define sqr(x) ((x) * (x))//计算x平方的函数式宏

int main()
{
	int x;
	double n;
	
	printf("请输入一个整数:");
	scanf("%d", &x);
	printf("该整数的平方是%d\n", sqr(x));
	
	printf("请输入一个整数:");
	scanf("%lf", &n);
	printf("该整数的平方是%f\n", sqr(n));
	
	return 0;
}
//#define 给出的命令如下:

下文中若出现sqr(☺)形式的表达式就将其展开为:

((☺)* (☺))

因此,在调用printf函数时就可以像下面一样展开并执行:

printf("该数的平方是%d\n", ((x) * (x)));

函数和函数式宏

函数和函数式宏的调用看上去相同,但也有以下几个区别:

■函数式宏sqr是在编译时展开并填入程序的,因此只要能使用双目运算符 * 进行乘法运算的数据类型,都能使用函数式宏。

■而函数定义则需要每个形参都定义各自的数据类型,返回值类型也都只有一种,就这点而言,函数较为严格。


 ■函数为我们默默无闻的进行一系列的复杂处理:

☞参数传递(将实参复制给形参)

☞函数调用和函数的返回操作(函数流程的控制)

☞返回值的传递

而函数式宏所做的工作只是宏展开和填入程序,并不执行上述步骤。


 ■根据以上特征,函数式宏或许能使程序的运行速度稍微提高,但是程序自身可能会变得臃肿(如果宏展开式极为复杂,那么在使用到它的所有地方都会填入这些复杂的表达式)。


 ■函数式宏在使用时必须小心,比如sqr((a++)* (a++)),每次展开a的值都会递增两次。在不经意间表达式被执行了两次,导致程序出现了意料之外的结果,我们称这种情况为宏的副作用

注意:在定义和使用函数式宏的时候,要仔细考虑是否会使用产生副作用。

☞将函数版的sqr_int作为sqr_int(a++)调用时,a的值不会调用两次,如果是宏版,则要将sqr(a)和a++分开。


函数式宏和对象式宏

如果在宏名称sqr和紧邻其后的( 之间插入空格,进行如下宏定义:

#define sqr (x) ((x) * (x))

 则sqr会被编译器当做对象式宏,程序中的sqr都会被替换为(x) ((x) * (x))

因此我们在定义函数式宏时,不要在宏名称和(之间插入空格。

 以下是计算二值之和的函数式宏:

#define sum_of(x,y) x + y

我们使用以下语句来调用这个函数式宏

z = sum_of(a, b) * sum_of(c, d);

让我们来看看宏展开式是否符合我们的意愿呢?

z = a + b * c + d;

很显然结果不尽人意,保险起见我们在宏定义时将每个参数以及整个表达式都用括号括起来

#define sum_of(x,y) ((x) + (y))

这样表达式就能正确展开了:

z = (a + b) * (c + d);

不带参数的函数式宏

函数式宏也可以像函数那样进行不带参数的定义,例如下面这个响铃的宏alert()

#define alert() (putchar('\n'))

函数式宏和逗号运算符

下面我们来介绍函数式宏的一个重要使用方法,我们先来看下错误示范:

#include<stdio.h>

#define puts_alert(str) {putchar('\a');  puts(str)}

int main()
{
	int n;
	
	printf("请输入一个整数:");
	scanf("%d", &n);
	
	if(n)
		puts_alert("这个整数不是0");
	else
		puts_alert("这个整数是0");
	
	return 0;
}//本程序在编译时报错,因此不能运行

让我们来分析下原因:

函数式宏put_alert的定义是在puts函数显示字符串str时响铃,只不过这个程序在编译时出错,不能运行。

main函数的if语句展开后如下图所示,if语句会在第一个复合语句{ }处结束,这时因为末尾的 ;会被视为空语句,因此编译器会认为“没有if,为何出现了else”(即使这样,也不能去掉{}否则会出现别的错误)

下面就需要讲到逗号运算符了:

逗号运算符
a,b                                  按顺序判断a和b,整个表达式最终生成b的判断结果
#include<stdio.h>

#define puts_alert(str)  (putchar('\a'),  puts(str))

int main()
{
	int n;
	
	printf("请输入一个整数:");
	scanf("%d", &n);
	
	if(n)
		puts_alert("这个整数不是0");
	else
		puts_alert("这个整数是0");
	
	return 0;
}

一般由逗号运算符连接的两个表达式“a, b”在语法上可以视为一个表达式(其实不仅限于逗号运算符,只要是由运算符连接的多个表达式,例如“a + b”,都可以视为一个表达式),因此在本程序中if语句在语法上就是正确的。

 如果宏定义中要代换两个以上的表达式,则使用逗号运算符连接,使其在语法上构成一个表达式。

 我们对逗号运算符是怎么执行的来具体说明下:

对于逗号运算符“a, b”,会按顺序判断表达式a和b。对左侧的a仅进行判断,判断结果会被省略去,对于右侧的表达式b进行判断所得到的类型和值,就是逗号表达式“a, b”的类型和值。

例如:i=1,j=5

运行x = (++i, ++j),则i和j的值都会递增,但是递增后j的值会被赋值给x。


感觉基本数据类型中的整型和字符型、浮点型的知识点好复杂啊!!!

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

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

相关文章

【网络安全】网络防护之旅 - 点燃网络安全战场的数字签名烟火

​ &#x1f308;个人主页&#xff1a;Sarapines Programmer&#x1f525; 系列专栏&#xff1a;《网络安全之道 | 数字征程》⏰墨香寄清辞&#xff1a;千里传信如电光&#xff0c;密码奥妙似仙方。 挑战黑暗剑拔弩张&#xff0c;网络战场誓守长。 ​ 目录 &#x1f608;1. 初识…

计算机网络:DNS域名解析系统

我最近开了几个专栏&#xff0c;诚信互三&#xff01; > |||《算法专栏》&#xff1a;&#xff1a;刷题教程来自网站《代码随想录》。||| > |||《C专栏》&#xff1a;&#xff1a;记录我学习C的经历&#xff0c;看完你一定会有收获。||| > |||《Linux专栏》&#xff1…

Java 第13章 异常 本章作业

1 编程 两数相除的异常处理 各自属于哪些异常&#xff1a; 数据格式不正确 NumberformatException 缺少命令行参数 ArrayIndexOutOfBoundsException 除0异常处理 ArithmeticException ArrayIndexOutOfBoundsException 为数组下标越界时会抛出的异常&#xff0c;可以在检测到命…

swing快速入门(十三)

注释很详细&#xff0c;直接上代码 上一篇 新增内容 1.Dialog&#xff08;模式对话框和非模式对话框&#xff09; 模式对话框&#xff1a;在对话框没有消失之前无法操作父窗口 非模式对话框&#xff1a;对话框的出现不会影响对父窗口的操作 2.setButton&#xff08;对话框设置…

Chromadb词向量数据库总结

简介 Chroma 词向量数据库是一个用于自然语言处理&#xff08;NLP&#xff09;和机器学习的工具&#xff0c;它主要用于词嵌入&#xff08;word embeddings&#xff09;。词向量是将单词转换为向量表示的技术&#xff0c;可以捕获单词之间的语义和语法关系&#xff0c;使得计算…

arthas 线上排查问题基本使用

一、下载 [arthas下载地址]: 下载完成 解压即可使用 二、启动 java -Dfile.encodingUTF-8 -jar arthas-boot.jar 如果直接使用java -jar启动 可能会出现乱码 三、使用 启动成功之后 arthas会自动扫描当前服务器上的jvm进程 选择需要挂载的jvm进程 假如需要挂在坐标【1】的…

机器视觉工程师为什么如何找一个高薪的企业上班

竟然那么辛苦&#xff0c;为什么不找一个工资高的地方被压榨呢&#xff0c;在现在的公司学到的技术&#xff0c;给不到你应有的薪资&#xff0c;那你就只能跳槽来获得&#xff0c;各行各业都有牛人和大佬&#xff0c;各行各业都有高薪的人&#xff0c;只不过看你能不能到那个高…

nginx_rtmp_module 之 ngx_rtmp_live_module模块

模块作用 直播模块代码 ngx_rtmp_live_module.c&#xff0c;主要作用是&#xff1a;当客户端推流或者拉流的时候&#xff0c;创建的rtmp session会加入到 live 模块的存储链表中。 模块配置命令 static ngx_command_t ngx_rtmp_live_commands[] {{ ngx_string("live&…

【Linux】键盘高级操作技巧

命令行最为珍视的目标之一就是懒惰&#xff1a;用最少的击键次数来完成最多的工作。另一个目标是你的手指永 远不必离开键盘&#xff0c;永不触摸鼠标。因此&#xff0c;我们有必要了解一些键盘操作&#xff0c;使我们用起来更加的迅速和高效。 移动光标 在前面介绍过上下左右…

JVM-2-对象

对象创建 当Java虚拟机遇到一条字节码new指令时&#xff0c;首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用&#xff0c;并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有&#xff0c;那必须先执行相应的类加载过程。 为对象分配空间…

visual Studio MFC 平台实现图片的傅里叶变换

图片的傅里叶变换 本文主要讲解傅里叶变换的基本数学概念与物理概念&#xff0c;并本文使用visual Studio MFC 平台实现对傅里叶变换在图片上进行了应用。 一、傅里叶变换的原理 在这里推荐一篇讲得非常形象的文章通俗讲解&#xff1a;图像傅里叶变换 1.1 傅里叶变换原理的说明…

C++ 二叉搜索树(BST)的实现(非递归版本与递归版本)与应用

C 二叉搜索树的实现与应用 一.二叉搜索树的特点二.我们要实现的大致框架三.Insert四.InOrder和Find1.InOrder2.Find 五.Erase六.Find,Insert,Erase的递归版本1.FindR2.InsertR3.EraseR 七.析构,拷贝构造,赋值运算符重载1.析构2.拷贝构造3.赋值运算重载 八.Key模型完整代码九.二…

Prometheus 监控笔记(1):你真的会玩监控吗?

认识Prometheus Prometheus 是一种开源的系统和服务监控工具&#xff0c;最初由 SoundCloud 开发&#xff0c;后来成为继 Kubernetes 之后云原生生态系统中的一部分。在 Kubernetes 容器管理系统中&#xff0c;通常会搭配 Prometheus 进行监控&#xff0c;同时也支持多种 Expo…

股票价格预测 | Python实现基于ARIMA和LSTM的股票预测模型(含XGBoost特征重要性衡量)

文章目录 效果一览文章概述模型描述源码设计效果一览 文章概述 Python实现基于ARIMA和LSTM的股票预测模型(Stock-Prediction) Data ExtractionFormatting data for time seriesFeature engineering(Feature Importance using X

2023NEFU实习项目解析 - 中俄贸易供需服务平台

文章目录 项目概述项目初始化搭建项目初始框架配置Tomcat建立项目数据库编写统一返回类及其工具类编写数据库工具类通过Filter解决Response返回中文乱码问题使用Filter解决权限校验问题 项目主干开发用户登录企业管理&#xff08;分页查询原生实现&#xff09;上传VIP申请书模板…

【ArkTS】生命周期

页面生命周期 通常Entry修饰的组件称为页面&#xff0c;其拥有页面生命周期 onPageShow&#xff1a;页面每次显示时触发。onPageHide&#xff1a;页面每次隐藏时触发&#xff08;通常是路由跳转到其他页面了&#xff09;。onBackPress&#xff1a;当用户点击返回按钮时时触发…

【LeetCode刷题-哈希表】--187.重复的DNA序列

187.重复的DNA序列 本题就是找到长度为10的字符出现次数大于2的 子串序列 方法&#xff1a;使用哈希表 class Solution {public List<String> findRepeatedDnaSequences(String s) {List<String> ans new ArrayList<String>();HashMap<String,Integer&g…

GitLab下载地址是127.0.0.1如何修改ip

问题&#xff1a; 在下图位置之前我的ip是127.0.0.1&#xff0c;那我是如何修改的呢&#xff1f;请看下文 解决方案&#xff1a; 配置 GitLab站点 Url和端口号 GitLab 默认的配置文件路径是 /etc/gitlab/gitlab.rb # 修改配置文件 $ sudo vi /etc/gitlab/gitlab.rb 默认的站…

【lesson14】MySQL表的基本查询retrieve(读取)1

文章目录 表的基本操作介绍retrieveselect列建表基本测试 where子句建表基本测试 表的基本操作介绍 CRUD : Create(创建), Retrieve(读取)&#xff0c;Update(更新)&#xff0c;Delete&#xff08;删除&#xff09; retrieve select列 建表 基本测试 插入数据 全列查询 …

【人工智能】实验三 A*算法求解八/十五数码问题实验与基础知识

实验三 A*算法求解八数码问题实验 实验目的 熟悉和掌握启发式搜索的定义、估价函数和算法过程&#xff0c;并利用A*算法求解N数码难题&#xff0c;理解求解流程和搜索顺序。 实验内容 以8数码问题和15数码问题为例实现A*算法的求解程序&#xff08;编程语言不限&#xff09…