#define宏的妙用!实现你以为的函数offsetof等

news2024/12/19 7:50:35

目录

一.#define

1.1#define的使用

1.2在define定义标识符的时候,要不要在最后加上 ;?

二.#define定义宏

2.1好代码的写法:

2.2#define 替换规则

 2.3#和##

三.带有副作用的宏参数

四.宏和函数的对比

五.实现offetof

 思路解析过程:

 代码实现:

六.写一个宏,可以将一个整数的二进制位的奇数位和偶数位交换

思路分析:

代码实现: 

一.#define

1.1#define的使用

语法:

#include<stdio.h>
#define MAX 1000

int main()
{
	printf("%d \n", MAX);

	return 0;
}

结果:

 格式:

#define MAX 1000
#define reg register          //为 register这个关键字,创建一个简短的名字
#define do_forever for(;;)     //用更形象的符号来替换一种实现
#define CASE break;case        //在写case语句的时候自动把 break写上。

建议#define 定义的名字 全大写来区分

举个例子:

#define MAX 1000
#define reg register          //为 register这个关键字,创建一个简短的名字
#define do_forever for(;;)     //用更形象的符号来替换一种实现
#define CASE break;case        //在写case语句的时候自动把 break写上。

1.2在define定义标识符的时候,要不要在最后加上 ;?

建议不用加!!!

例子:

#define MAX 1000;

替换过来就是这样的:

#define的本质是替换!!!

二.#define定义宏

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

2.1好代码的写法:

例子:

这个代码有个问题

#include<stdio.h>
#define SQUARE(x) x * x

int main()
{
	printf("%d \n", SQUARE(5));
	return 0;
}

结果:

结果没有问题呀,那如果是这样的代码呢?

#include<stdio.h>
#define SQUARE(x) x * x

int main()
{
	printf("%d \n", SQUARE(5+1));
	return 0;
}

我们想要的是: 6 * 6 = 36

结果:

为什么他是11,不是36?

因为本质是替换:

5 + 1 * 5 + 1 = 11 

优化一下:

#define SQUARE(x) (x) * (x)

结果:

那又如果是:

#define SQUARE(x) (x) + (x)

int main()
{
	int a = 5;
	printf("%d\n", 10 * SQUARE(a));
	return 0;
}

我们想要的 : 10 * 10 = 100 

但结果是:55

因为替换下来的结果是 : 10 * (5) + (5)

最终优化:

#include<stdio.h>
#define SQUARE(x) ((x) + (x))

int main()
{
	int a = 5;
	printf("%d\n", 10 * SQUARE(a));
	return 0;
}

结果:

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

2.2#define 替换规则

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

1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先 被替换。

2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。

3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上 述处理过程。

注意:

1. 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。 2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。

 2.3#和##

这两个都要配合#define使用

这个稍微的有一点偏题但是,又结合宏可以实现一个功能:

#的作用就是,加了#的变量,变成一个字符串使用:

铺垫:

我们发现C语言字符串有直接拼接的功能

 例子:

我们发现这样的代码有点麻烦,那我们能不能写个函数实现一下打印的功能?

发现不同的地方其实就这几个: 

 而且我们发现函数没法搞,这个时候宏就该上场了:

#include<stdio.h>

#define PRINT(val,format) printf("the value of "#val" is "format"\n",val)
//解析拿a举例:           printf("the value of" "a" " is " "%d" "\n",val );
int main()
{
	int a = 9;
	PRINT(a, "%d");

	float b = 5.5f;
	PRINT(b, "%f");

	char c = 'c';
	PRINT(c, "%c");
	return 0;
}

结果:

## 的作用:

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

例子:

#include <stdio.h>

#define CAT(A,B) A##B
int main()
{
	int ABCDEF = 1000;
	printf("%d\n",CAT(ABC,DEF));

	return 0;
}

结果:

 就只是把变量名拼接了起来使用

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

 三.带有副作用的宏参数

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

例如: 

x+1;//不带副作用
x++;//带有副作用

例子:

#include<stdio.h>
#define MAX(a, b) ( (a) > (b) ? (a) : (b) )

int main()
{
	int x = 3;
	int y = 4;
	int z = MAX(x++, y++);
	printf("x=%d y=%d z=%d\n", x, y, z);//输出的结果是什么?

	return 0;
}

分析:

 这样的代码会引起一系列的不良反应

结果:

四.宏和函数的对比

宏通常被应用于执行简单的运算。

比如在两个数中找出较大的一个。

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

那为什么不用函数来完成这个任务?

1. 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。 所以宏比函数在程序的规模和速度方面更胜一筹

2. 更为重要的是函数的参数必须声明为特定的类型。 所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点型等可以 用于>来比较的类型。 宏是类型无关的

 宏的缺点:

1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。

2. 宏是没法调试的。

3. 宏由于类型无关,也就不够严谨。

4. 宏可能会带来运算符优先级的问题,导致程容易出现错。

 宏有时候可以做函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到。

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

五.实现offetof

offsetof是计数结构体类型成员在内存上的偏移量,并且他并不是函数,而是由宏实现的,因为他的实参传入了结构体类型,但是函数是不能传入类型的

如果你还不会结构体偏移量可以学习我这一篇文章:https://blog.csdn.net/Lizhihao_/article/details/126903868?spm=1001.2014.3001.5502

 演示:

 思路解析过程:

1.

 2.

 3.

 代码实现:

1.

2.

 3.

4.

 我这里并没有去访问0地址,只是拿出来玩了一下,真正拿出的是m_name的地址

 结果:

代码:

#include<stdio.h>
#include<stddef.h>

#define OFFSTROF(s_type,m_name)          (size_t)&(((s_type*)0)->m_name)

struct S
{
	char name;
	int age;
	char sz;
};

int main()
{
	printf("%zu\n", OFFSTROF(struct S, name));
	printf("%zu\n", OFFSTROF(struct S, age));
	printf("%zu\n", OFFSTROF(struct S, sz));

	/*printf("%zu\n", offsetof(struct S, name));
	printf("%zu\n", offsetof(struct S, age));
	printf("%zu\n", offsetof(struct S, sz));*/

	return 0;
}

 六.写一个宏,可以将一个整数的二进制位的奇数位和偶数位交换

 思路分析:

1.

 2.

3.

代码实现: 

 1.

 2.

3. 结果:

 代码:

#include<stdio.h>

#define SWAP_BIT(n)    n = (((n & 0x55555555) << 1) + ((n & 0xaaaaaaaa)>>1))

int main()
{
	int n = 10;
	//             n = 00000000 00000000 00000000 00001010 
	//要实现的结果:n = 00000000 00000000 00000000 00000101(5)
	SWAP_BIT(n);
	printf("%d\n", n);
	return 0;
}

 写作不易,你们的支持是作者最大的动力!!!

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

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

相关文章

破解系统密码

一、利用5次shift漏洞破解win7密码 1.1 漏洞 1. 在未登录时&#xff0c;连续按5次shift键&#xff0c;弹出程序C:\Windows\System32\sethc.exe 2. 部分win7及win10系统在未进入系统时&#xff0c;可以通过系统修复漏洞篡改系统文件名&#xff01; 注意&#xff1a;如win7或win…

SpringBoot/Spring AOP默认动态代理方式

Spring 5.x中AOP默认依旧使用JDK动态代理SpringBoot 2.x开始&#xff0c;AOP为了解决使用JDK动态代理可能导致的类型转换异常&#xff0c;而使用CGLIB。在SpringBoot 2.x中&#xff0c;AOP如果需要替换使用JDK动态代理可以通过配置项spring.aop.proxy-target-classfalse来进行修…

【Linux】7.0 信号

文章目录信号的基本概念kill -l 查看信号列表信号的处理方式signal( ) 自定义处理信号信号的产生方式键盘产生进程异常&#xff08;core dump&#xff09;系统调用软件条件信号的发送&#xff08;OS&#xff09;信号常见相关名词解释进程接收处理信号原理信号集函数的使用打印p…

【Redis】3.详解分布式锁

文章目录1. 什么是分布式锁2. 分布式锁的特点3. 常见的分布式锁4. 实现分布式锁5.解决分布式锁中的原子性问题5.1 Lua脚本5.2 使用Java代码调用Lua脚本实现原子性1. 什么是分布式锁 分布式锁是指分布式系统或者不同系统之间共同访问共享资源的一种锁实现&#xff0c;其是互斥的…

【Django框架】——20 Django视图 02 路由命名和反向解析

文章目录一、 路由命名二、reverse反向解析三、通过URL模板页面进行传参四、namespace1.reverse反向解析2.url模板标签在 Django 项⽬中&#xff0c;⼀个常⻅需求是获取最终形式的 URL&#xff0c;⽐如⽤于嵌⼊⽣成的内容中&#xff08;视图和资源⽹址&#xff0c;给⽤户展示⽹…

《网络安全笔记》第七章:注册表基础

一、注册表基础 1、概述 注册表是windows操作系统、硬件设备以及客户应用程序得以正常运行和保存设置的核心“数据库”&#xff0c;也可以说是一个非常巨大的树桩分层结构的数据库系统注册表记录了用户安装在计算机上的软件和每个程序的相互关联信息&#xff0c;它包括了计算…

【UDS】ISO14229之0x2F服务

文章目录前言一、理论描述二、使用步骤1.请求2.响应总结->返回总目录<- 前言 简称&#xff1a; “InputOutputControlByIdentifier”&#xff0c;根据标识符控制输入输出 功能&#xff1a; 根据标识符控制输入输出服务用于替换输入信号的值、电控单元内部参数或控制电子…

Telnet连接

❤️人生没有白走的路&#xff0c;每一步都算数❤️ 你是否安装Telnet没毛病&#xff0c;但登录总报错&#xff1f; 巧了&#xff0c; 我也遇到了。 于是我打开浏览器尝试搜索&#xff0c;有许多说的并不详细。 所以呢就有了这篇文章&#xff01; 首先我们准备实验环境&#xf…

oracle中替换字符串的不同写法

replace函数 replace(原字段&#xff0c;“原字段旧内容“,“原字段新内容“) 例如将DEPTNO字段值中的0替换为1&#xff1a; TRANSLATE TRANSLATE(expr, from_string, to_string) from_string 与 to_string 以字符为单位&#xff0c;对应字符一一替换。 用法示例&#xf…

详解数据结构——二叉排序树

目录 二叉排序树 二叉排序树的查找 二叉排序树的插入 二叉排序树的删除 查找时间效率分析 二叉排序树 二叉排序树&#xff0c;又称二叉查找树&#xff08;BST&#xff0c;Binary Search Tree)一棵二叉树或者是空二叉树&#xff0c;或者是具有如下性质的二叉树: 左子树上所有结…

SpringBoot - SpringBoot整合i18n实现消息国际化

文章目录1. MessageSource源码2. 项目环境搭建1. 创建项目服务auth2. 工具类 I18nUtils3. 自定义异常 CommonException4. 统一异常处理 GlobalExceptionHandler3. 业务实现1. 实体类 UserEntity2. 请求实体 UserQo3. 控制层 UserController4. 业务逻辑层 UserService5. 将异常信…

144. 授人以渔 - 如何查找 SAP UI5 官网上没有提到的控件属性的使用明细

本教程第 113 步骤, SAP UI5 应用开发教程之一百一十三 - 授人以渔 - 如何自行查询任意 SAP UI5 控件属性的文档和技术实现细节我用一整篇文章的篇幅,解答了一位学习者这个疑问: 想请教一下 sap.m.Input 控件中,value里设置的内容,比如path,type,constraints,在哪里可以查…

C++ 多态

目录 一、多态的定义和实现 1.1 多态的构成条件&#xff1a; 1.2 虚函数的重写&#xff08;覆盖&#xff09;&#xff1a; 1.3 多态的两个特殊点&#xff1a; 1.4 析构函数的重写&#xff1a; 1.5 override和final 1.6 重载&#xff0c;重定义&#xff08;隐藏&#xff…

Linux【进程地址空间】

进程地址空间&#x1f4d6;1. 地址空间概念&#x1f4d6;2. 写时拷贝&#x1f4d6;3. 虚拟地址空间的优点&#x1f4d6;1. 地址空间概念 在学习C/C内存管理时&#xff0c;我们可能见过这样一幅图&#xff1a; 但是我们可能不是很理解它&#xff0c;首先有一个问题&#xff1a;…

OpenTCS客户端开发之Web客户端(一)

越来越多人私信我关于OpenTCS的问题。可以感觉到很多人对OpenTCS的研究的人多了很多&#xff0c;很好。这些问题很多是关于算法方面的&#xff0c;也有一部分是关于UI方面的&#xff0c;毕竟OpenTCS本质上是一个算法项目&#xff0c;但是如果希望把它进行商业化&#xff0c;那免…

【微服务】服务拆分和远程调用

2.1 服务拆分原则 这里总结了微服务拆分时的几个原则&#xff1a; 不同微服务&#xff0c;不要重复开发相同业务微服务数据独立&#xff0c;不要访问其它微服务的数据库微服务可以将自己的业务暴露为接口&#xff0c;供其它微服务调用 2.2 服务拆分示例 以微服务cloud-demo为…

第三节:运算符【java】

目录 &#x1f392;运算符 &#x1f4c3;1. 什么是运算符 &#x1f4d7;2. 算术运算符 2.1 基本四则运算符&#xff1a;加减乘除模( - * / %) 2.2 增量运算符 - * % 2.3 自增/自减运算符 -- &#x1f4d9;3. 关系运算符 &#x1f4d5;4.逻辑运算符(重点) 4.1 逻辑与…

隔离出来的“陋室铭”

被隔离了 日常锻炼身体就是去公司旁边的酒店游泳&#xff0c;结果酒店里除了小阳人&#xff0c;我就喜提次密称号&#xff0c;7天隔离走起&#xff1b;又因为不想耽误家里孩子上学&#xff0c;老人外出&#xff0c;就选择了单独隔离&#xff0c;结果就拉到了单独的隔离点&…

精通Git(三)——Git分支机制

文章目录前言分支机制简述创建分支切换分支基本的分支与合并操作基本的分支操作基本的合并操作基本的合并冲突处理分支管理与分支有关的工作流长期分支主题分支远程分支推送跟踪分支拉取删除远程分支变基基本的变基操作变基操作的潜在危害只在需要的时候执行变基操作变基操作与…

C++——vector容器的基本使用和模拟实现

1、vector的介绍 vector是表示可变大小数组的序列容器。 就像数组一样&#xff0c;vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素 进行访问&#xff0c;和数组一样高效。但是又不像数组&#xff0c;它的大小是可以动态改变的&#xff0c;而且…