C语言程序与设计——预处理命令

在C语言中宏有三种形式:

  1. 定义符号常量
  2. 定义傻瓜表达式
  3. 定义代码段
    在使用宏的过程中需要注意的是,宏的作用仅仅是在预处理阶段对代码进行替换,而非进行运算,所以在使用时,如果出现了我们预期之外的结果,很有可能是宏没有处理好。下面就挨个演示一下。

定义符号常量

着一种形式比较简单只是单纯的替换,这里对该种情况不做过多介绍

#include<stdio.h>
#define PI 3.1415926535int main(){int r = 5;printf("[r] = %d [S] = %f", r, PI * r * r);
}

定义傻瓜表达式

可以通过宏封装一个简易的表达式,但是为什么叫傻瓜表达式呢,正如之前所说,宏只是进行替换,而非进行计算,所以在没有考虑周全的情况下,很容易出错。下面我们以封装一个比较函数为例,返回大的值。
分别用到下面五组示例

#include<stdio.h>
#define MAX(a, b) a > b ? a : b
#define P(func) printf("%s = %d\n", #func, func);int main(){int a = 7;P(MAX(2, 3));P(5 + MAX(2, 3));P(MAX(2 ,MAX(2, 3)));P(MAX(2, 3 > 4 ? 3 : 4));P(MAX(a++, 3));
}

先解释一下代码,主函数部分是我们的测试用例不需要赘述。封装的MAX(a,b)表达式就是我们想要实现的功能。下面的P宏是我为了方便实现的打印的宏,其中#func这个参数的意义就是字符串话,可以使得运行结果更容易观察。

运行结果:
在这里插入图片描述
可以看到除了第一个测试用例基本上这些都不是正确答案。为了探究原因我们就需要看到把宏展开,也就是预处理之后的样子。使用gcc -E filename命令即可。
在这里插入图片描述
可以看到他们宏仅仅是做了替换,实际的运算顺序与我们预期的不符合,所以我们就需要使用小括号来使得运算顺序满足我们的预期。

#include<stdio.h>
#define MAX(a, b) ((a) > (b) ? a : b)
#define P(func) printf("%s = %d\n", #func, func);int main(){int a = 7;P(MAX(2, 3));P(5 + MAX(2, 3));P(MAX(2 ,MAX(2, 3)));P(MAX(2, 3 > 4 ? 3 : 4));P(MAX(a++, 3));
}

在这里插入图片描述
可以看到前四个结果已经达到我们预期的结果了。但是第五个结果与预期不符合。可以从上面预处理之后的代码中可以看出,它的错误并不是计算顺序的原因,而是我们对a++访问之后自增1,而后进行返回的原因,若想修复这个bug我们需要一个中间变量来记住增1前的a值

定义代码段

#include<stdio.h>//#define MAX(a, b)(((a) > (b) ? a : b))#define MAX(a, b)({\__typeof(a) _a = a;\__typeof(b) _b = b;\_a > _b ? _a : _b;\
})#define P(func) printf("%s = %d\n", #func, func);int main(){int a = 7;P(MAX(2,3));P(5 + MAX(2,3));P(MAX(2,MAX(2,3)));P(MAX(2,3 > 4 ? 3 : 4));P(MAX(a++,6));
}

运行结果:
在这里插入图片描述
这里解释一下新出现的t__typeof,功能就是提取变量类型的关键字,然后进行定义,也就是说 __typeof(a) _a = a;改行代码与 int _a = a;相同。当宏有多行的时候,需要使用\来链接。

内置的宏

说明
__ DATE __日期Mmm dd yyy
__ FILE __文件名
__ LINE __行号
__ TIME __时间:hh:mm:ss
__ func __函数名(非标准)
__ Func __函数名(非标准)
__ PRETTY_FUNCTION __更详细的的函数信息(非标准)

其中非标准的含义是指不同的环境下可能会出现不同的结果,或者是不存在该宏。比如我是在ubantu系统下运行的,没有__Func__的宏
在这里插入图片描述

#include<stdio.h>
#define P(a) printf("[%s] = %s\n", #a, a);int main(){P(__DATE__);P(__FILE__);printf("[__LINE__] = %d", __LINE__);P(__TIME__);P(__func__);P(__PRETTY_FUNCTION__);
}

在这里插入图片描述

条件式编译

在宏当中也可以实现条件分支的功能实现代码剪裁

函数说明
#ifdef DEBUG是否定义了DEBUG
#ifndef DEBUG是否没定义了DEBUG
#if MAX_N ==5宏MAX_N 是否等于5
#ieif MAX_N ==4否则宏MAX_N 是否等于4
#else
#endif
打印日志信息

编写一个程序,通过宏实现一个log()功能打印,打印所在文件,行号,函数,以及内容。当需要DEBUG时log()可以使用,不需要DEBUG时,则log()函数无任何作用

#include<stdio.h>#define DEBUG
#ifdef DEBUG
#define log(frm, args...){\printf("[FILE]:%s [LINE]:%d [func]:%s [content]:", __FILE__, __LINE__,__func__);\printf(#frm, ##args);\printf("\n");\
}
#else
#define log(frm, args...)
#endifint add(int a, int b){return a + b;
}int main(){log(add(1 , 5));
}输出结果:
>> [FILE]:log.c [LINE]:24 [func]:main [content]:add(1 , 5)

"#"和”##“的作用:

  • ”#“是将后面的内容字符串化,保证输出
  • ”##“是连接作用,可以连接两个变量名,在这里是保证当log传入参数只有一个时,不会报错(int a, b, ab; a##b 是与ab等价的)

泛型宏(Generic macro)

#include<stdio.h>
#define TYPE(a) _Generic((a), \int : "%d\n",\double: "%lf\n", \char * : "%s\n"\
)int main(){int a = 1;double b = 4.0;char string[] = "hello";printf(TYPE(a),a);printf(TYPE(b), b);printf(TYPE(string), string);
}运行结果:
>> 1
4.000000
hello

在这里插入图片描述

从上图可以看到除了泛型宏以外,自定义的函数在主函数之外也可以运行,而且会先于主函数运行。这是因为我们在函数上面为其添加了属性使其可以能够单独运行。尽管函数可以独立运行,但是一个程序是不可以没有主函数的,否则会有编译错误。

补充:字符串

在预处理阶段,不只是宏,还有头文件也会被展开,在这里简单介绍一下一个比较重要的头文件<string.h>,该头文件包含对于字符串的操作比较方便。

函数说明
strlen(str)计算字符串长度,以\0作为结束符
strcmp(str1, str2)字符串比较
strcpy(dest, src)字符串拷贝
strcmp(str1, str2, n)安全字符串比较
strcpy(dest, src, n)安全字符串拷贝
memcpy(str1, str2, n)内存拷贝
memcmp(str1, str2, n)内存比较
memset(str,c, n)内存设置

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

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

相关文章

Java代码基础算法练习-搬砖问题-2024.03.25

任务描述&#xff1a; m块砖&#xff0c;n人搬&#xff0c;男搬4&#xff0c;女搬3&#xff0c;两个小孩抬一砖&#xff0c;要求一次全搬完&#xff0c;问男、 女、小孩各若干&#xff1f; 任务要求&#xff1a; 代码示例&#xff1a; package M0317_0331;import java.util.S…

【Android】图解View事件分发机制

文章目录 View事件分发机制dispartchTouchEvent()dispatchTouchEvent() 方法主要负责什么&#xff1f; onTouchEvent(event) 点击事件分发的传递规则自上而下自下而上 View事件分发机制 View的事件分发机制是Android中非常核心的一个概念&#xff0c;它负责处理触摸事件&#…

SpringMVC | Spring MVC中的“拦截器”

目录: 一、拦截器 &#xff1a;1. 拦截器的 “概述”2. 拦截器的 “定义” (创建“拦截器”对象)3. 拦截器的 “配置” (让“拦截器”对象生效)4. 拦截器的 “执行流程”“单个拦截器”的执行流程“多个拦截器”的执行流程 二、应用案例一实现用户登录权限验证 作者简介 &#…

nav仿真(2)

开启仿真和建图 打开第一个窗口启动仿真&#xff1a; source devel/setup.bash export TURTLEBOT3_MODELburger roslaunch turtlebot3_gazebo turtlebot3_world.launch # 启动仿真打开第二个窗口&#xff0c;开始建图&#xff1a; source devel/setup.bash export TURTLEBOT3_…

举4例说明Python如何使用正则表达式分割字符串

在Python中&#xff0c;你可以使用re模块的split()函数来根据正则表达式分割字符串。这个函数的工作原理类似于Python内置的str.split()方法&#xff0c;但它允许你使用正则表达式作为分隔符。 示例 1: 使用单个字符作为分隔符 假设你有一个由逗号分隔的字符串&#xff0c;你可…

Redis入门到实战-第三弹

Redis入门到实战 Redis数据类型官网地址Redis概述Redis数据类型介绍更新计划 Redis数据类型 官网地址 声明: 由于操作系统, 版本更新等原因, 文章所列内容不一定100%复现, 还要以官方信息为准 https://redis.io/Redis概述 Redis是一个开源的&#xff08;采用BSD许可证&#…

用大语言模型控制交通信号灯,有效缓解拥堵!

城市交通拥堵是一个全球性的问题&#xff0c;在众多缓解交通拥堵的策略中&#xff0c;提高路口交通信号控制的效率至关重要。传统的基于规则的交通信号控制&#xff08;TSC&#xff09;方法&#xff0c;由于其静态的、基于规则的算法&#xff0c;无法完全适应城市交通不断变化的…

Unity 学习日记 8.2D物理引擎

1.2D刚体的属性和方法 2.碰撞器

MySQL -- 开窗函数 row_number 之 先根据名字分组,然后再根据分数排序

目录 开窗函数 row_number需求&#xff1a; 先根据名字分组&#xff0c;然后再根据分数排序 开窗函数 row_number 需求&#xff1a; 先根据名字分组&#xff0c;然后再根据分数排序 sql写法及解释&#xff1a; 可以再加子查询进行条件判断 函数用法解释&#xff1a;

登录注册界面

T1、编程设计理工超市功能菜单并完成注册和登录功能的实现。 显示完菜单后&#xff0c;提示用户输入菜单项序号。当用户输入<注册>和<登录>菜单序号时模拟完成注册和登录功能&#xff0c;最后提示注册/登录成功并显示注册信息/欢迎XXX登录。当用户输入其他菜…

Redis - 高并发场景下的Redis最佳实践_翻过6座大山

文章目录 概述6座大山之_缓存雪崩 &#xff08;缓存全部失效&#xff09;缓存雪崩的两种常见场景如何应对缓存雪崩&#xff1f; 6座大山之_缓存穿透&#xff08;查询不存在的 key&#xff09;缓存穿透的原因解决方案1. 数据校验2. 缓存空值3. 频控4. 使用布隆过滤器 6座大山之_…

K8s的Pod出现Init:ImagePullBackOff问题的解决,(以calico网络插件为例)

问题描述&#xff1a; 对于这类问题的解决思路应该都差不多&#xff0c;本文以calico插件安装为例&#xff0c;发现有个Pod的镜像没有pull成功 第一步&#xff1a;查看这个pod的描述信息 kubectl describe pod calico-node-t9rql -n kube-system从上图发现是docker拉取"…

H3C技术大全复现之高级路由交换技术 1

华子目录 VLAN 基本技术VLANIEEE 802.1Q交换机端口类型MVRP协议实验测试 VLAN扩展技术Super VLAN产生背景Super vlan&#xff08;相当于vlanif接口&#xff0c;也属于虚拟接口&#xff0c;可以充当网关&#xff09;Sub vlan&#xff08;普通vlan&#xff09;关于代理ARP普通代理…

了解和使用无操作系统和平台驱动程序

快速发展的技术需要软件支持&#xff08;固件驱动程序和示例代码&#xff09;来简化设计过程。本文介绍了如何使用 no-OS&#xff08;无操作系统&#xff09;驱动程序和平台驱动程序来构建具有 Analog Devices 模数转换器和数模转换器的应用固件&#xff0c;这些转换器在以下方…

Docker之docker compose!!!!

一、概述 是 Docker 官方提供的一款开源工具&#xff0c;主要用于简化在单个主机上定义和运行多容器 Docker 应用的过程。它的核心作用是容器编排&#xff0c;使得开发者能够在一个统一的环境中以声明式的方式管理多容器应用的服务及其依赖关系。 也就是说Docker Compose是一个…

docker安装WireGuard服务

启动 WireGuard 如下异常 则是linux内核需要升级 $ wg-quick down wg0 $ wg-quick up wg0 Error: WireGuard exited with the error: Cannot find device "wg0" This usually means that your hosts kernel does not support WireGuard!at /app/lib/WireGuard.js:65…

unicloud 云函数 介绍及使用

普通云函数 callFunction方式云函数&#xff0c;也称之为普通云函数。 uni-app的前端代码&#xff0c;不再执行uni.request联网&#xff0c;而是通过uniCloud.callFunction调用云函数。 callFunction方式避免了服务器提供域名&#xff0c;不暴露固定ip&#xff0c;减少被攻击…

机器学习(一)

经典定义: 利用经验改善系统自身的性能。 经典的机器学习过程: 基本术语: 数据集:训练集、测试集 示例、样例、样本 属性、特征:属性值 属性空间、样本空间、输入空间 特征向量 标记空间、输出空间 归纳偏好(偏置): 任何一个有效的机器学习算法必有其偏好 学习算法的…

数据库系统概论-练手题集合【期末复习|考研复习】

前言 总结整理不易&#xff0c;希望大家点赞收藏。 给大家整理了一下数据库系统概论中的练手题&#xff0c;以供大家期末复习和考研复习的时候使用。 数据库系统概论系列文章传送门&#xff1a; 第一章 绪论 第二/三章 关系数据库和标准语言SQL 第四/五章 数据库安全性和完整性…