C语言define高级用法大全

article/2025/9/24 21:50:51

今天在看代码时,突然发现很多define的用法看不懂,故在此总结一下,顺便吐槽一下,C语言的宏复杂起来真的很难看懂。

不信的去看下这个的源码:【C语言开源库】lw_oopc:轻量级的C语言面向对象编程框架

一、宏的定义与撤销

640?wx_fmt=png

需要注意的是:

(1)宏定义应注意添加括号,这样语义会比较清晰。

(2)使用#undef可以撤销宏定义。

(3)引号中的宏定义不会被替换。

(4)宏定义的宏名必须是合法的标识符。

(5)宏定义中单、双引号必须成对出现。

二、带有参数的宏定义

640?wx_fmt=png

需要注意的是:

(1)宏调用时参数的个数要与定义时相同。

三、跨行的宏定义,使用反斜杠 分隔

640?wx_fmt=png

这种跨行的代码就是函数宏的感觉了。

#include "stdio.h"#define test(x,y) do \{               \int a = x;   \int b = y;   \printf("%d",a); \}while(0);       \int a = x + y;int main() {test(1,2);printf("%d",a);
}

四、三个特殊符号:****#,##,#@

640?wx_fmt=png

五、常见的宏定义

1、防止头文件被重复包含

640?wx_fmt=png

2、得到指定地址上的一个字节值或字值

640?wx_fmt=png

3、得到一个field在结构体(struct)中的偏移量

#define OFFSETOF( type, field ) ( (size_t) &(( type *) 0)-> field )

4、*得到一个结构体中field所占用的字节数

#define FSIZ( type, field ) sizeof( ((type *) 0)->field )

5、得到一个变量的地址(word宽度)

#define B_PTR(var) ( (byte *) (void *) &(var) ) 
#define W_PTR(var) ( (word *) (void *) &(var) )

6、将一个字母转换为大写

#define UPCASE(c) ( ((c) >= 'a' && (c) <= 'z') ? ((c) - 0x20) : (c) )

7、判断字符是不是10进制的数字

#define DECCHK(c) ((c) >= '0' && (c) <= '9')

8、判断字符是不是16进制的数字

#define HEXCHK(c) (((c) >= '0' && (c) <= '9') ||((c) >= 'A' && (c) <= 'F') ||((c) >= 'a' && (c) <= 'f'))

9、防止溢出的一个方法

#define INC_SAT(val) (val = ((val)+1 > (val)) ? (val)+1 : (val))

10、返回数组元素的个数

#define ARR_SIZE(a) ( sizeof((a)) / sizeof((a[0])) )

关于嵌套宏的使用

--------------短小结论----------------------
涉及到宏定义展开顺序的知识,如果宏替换以# ##为前缀 ,则由外向内展开

 #define f(x) #x //结果将被扩展为由实际参数替换该参数的带引号的字符串#define b(x) a##x //连接实际参数#define ac helloint main(void){f(b(c))//display "b(c)"}
1234567

如果最外层p(x)宏替换不以# ##为前缀,则由内向外展开

#define f(x) #x
#define p(x) f(x)
#define b(x) a##x
#define ac hello
int main(void)
{p(b(c))// display "hello"return 0;
}
123456789

问题:下面通过宏定义实现一个可以指定前缀的字符串。
PREFIX+".%d"

方法1:使用#运算符。出现在宏定义中的#运算符把跟在其后的参数转换成一个字符串。有时把这种用法的#称为字符串化运算符。

#include<cstdio>
#define PREX 1.3.6
#define FORMAT(n) #n".%d\n"int main()
{int ival = 246;printf(FORMAT(PREX), ival);// PREX.246	return 0;
}
12345678910

但是输出结果是:PREX.246,和预期的结果不一样,宏PREX作为宏FORMAT的参数并没有替换。那么如何让FORMAT宏的参数可以替换呢?
首先,C语言的宏是允许嵌套的,其嵌套后,一般的展开规律像函数的参数一样:先展开参数,再分析函数,即由内向外展开。但是,注意:
(1) 当宏中有#运算符时,参数不再被展开;
(2) 当宏中有##运算符时,则先展开函数,再展开里面的参数;

PS: ##运算符用于把参数连接到一起。预处理程序把出现在##两侧的参数合并成一个符号(非字符串)。

方法2:修改宏定义的格式,再添加一个中间宏TMP(x)实现对参数的替换,然后再替换为最终想要的字符串。

#define PREX 1.3.6
#define FORMAT(x) TMP(x)
#define TMP(x) #x".%d\n"int main()
{int ival = 246;printf(FORMAT(PREX), ival);// 1.3.6.246	return 0;
}
12345678910

嵌套宏在某些情况下还是有一定的用处,但是我可能更愿意定义一个函数宏来完成上面这个工作:

#include <cstdio>
#define FORMAT_FUN(szPrex, szFormat) do { \char szTmp[128] = {0}; \_snprintf(szTmp, sizeof(szTmp)-1, "%s", szFormat); \_snprintf(szFormat, sizeof(szFormat)-1, "%s%s", szPrex, szTmp); \
} while(0); \
const char *szPrex = "1.3.6";int main()
{int ival = 246;char szFormat[128] = ".%d\n";FORMAT_FUN(szPrex, szFormat);	//printf("%s\n", szFormat);printf(szFormat, ival);// 1.3.6.246	 return 0;
}
1234567891011121314151617

举几个关于宏嵌套用法的例子:

Ex. 1#include <cstdio>
#define TO_STRING2(x) #x
#define TO_STRING(x) TO_STRING1(x)
#define TO_STRING1(x) #x
#define PARAM(x) #x
#define ADDPARAM(x) INT_##xint main()
{const char *str = TO_STRING(PARAM(ADDPARAM(1)));printf("%s\n",str);str = TO_STRING2(PARAM(ADDPARAM(1)));printf("%s\n",str);return 0;
}
/*
output:
"ADDPARAM(1)"
PARAM(ADDPARAM(1))
*/Ex. 2
#include <stdio.h>
#define TO_STRING2(x) a_##x
#define TO_STRING(x) TO_STRING1(x)
#define TO_STRING1(x) #x
#define PARAM(x) #x
#define ADDPARAM(x) INT_##xint main()
{const char *str = TO_STRING(TO_STRING2(PARAM(ADDPARAM(1))));printf("%s\n",str);return 0;
}
/*
VS2010 output:
a_PARAM(ADDPARAM(1))
GCC 4.3.2 output:
a_PARAM(INT_1)
*/
1234567891011121314151617181920212223242526272829303132333435363738394041424344

注意:例子2的代码分别在不同的编译器上输出结果不相同。这是为什么呢?

C99_TC3
6.10.3.1 Argument substitution
After the arguments for the invocation of a function-like macro have been identified, argument substitution takes place. Aparameter in the replacement list, unless preceded by a # or ## preprocessing token or followed by a ## preprocessing token (see below), is replaced by the corresponding argument after all macros contained therein have been expanded. Before being substituted, each argument’s preprocessing tokens are completely macro replaced as if they formed the rest of the preprocessing file; no other preprocessing tokens are available.
除非替换序列中的形式参数的前面有一个#符号,或者其前面或后面有一个##符号,否则,在插入前要对宏调用的实际参数记号进行检查,并在必要时进行扩展。

改为:

#include <stdio.h>
#define TO_STRING2(x) a_##x
#define TO_STRING(x) TO_STRING1(x)
#define TO_STRING1(x) T(x)
#define T(x) #x
#define PARAM(x) #x
#define ADDPARAM(x) INT_##xint main()
{const char *str = TO_STRING(TO_STRING2(PARAM(ADDPARAM(1))));printf("%s\n",str);return 0;
}
/*
VS2010 output:
a_PARAM(INT_1)
GCC 4.3.2 output:
a_PARAM(INT_1)
(1) 对TO_STRING的参数TO_STRING2(...)进行检查替换,生成标记a_PARAM(ADDPARAM(1))
(2) 对TO_STRING1的参数a_PARAM(ADDPARAM(1))进行检查替换,生成标记a_PARAM(INT_1)
(3) 对T的参数a_PARAM(INT_1)进行检查替换,生成字符串"a_PARAM(INT_1)"
*/Ex. 3
#include <stdio.h>int ival = 0;
#define A(x) printf("%d\n", ival+=1);
#define B(x) printf("%d\n", ival+=2);
#define C() printf("%d\n", ival+=3);int main()
{A(B(C()));printf("%d\n", ival);// ?, 1return 0;
}
1234567891011121314151617181920212223242526272829303132333435363738394041

补充知识:
The C Programming Language, Second Edition
P.205 预处理

(1) 预处理器执行宏替换、条件编译以及包含指定的文件。
(2) 以#开头的命令行("#"前可以有空格),就是预处理器处理的对象。
(3) 这些命令行的语法独立于语言的其他部分,它们可以出现在任何地方,其作用可延续到所在翻译单元的末尾(与作用域无关)。
(4) 行边界是有实际意义的。每一行都将单独进行分析。


关于#和##在C语言的宏中,#的功能是将其后面的宏参数进行字符串化操作(Stringfication),简单说就是在对它所引用的宏变量 通过替换后在其左右各加上一个双引号。比如下面代码中的宏:

#define WARN_IF(EXP)     /do{ if (EXP)     /fprintf(stderr, "Warning: " #EXP "/n"); }    /while(0)

那么实际使用中会出现下面所示的替换过程:

WARN_IF (divider == 0);被替换为do {if (divider == 0)
fprintf(stderr, "Warning" "divider == 0" "/n");
} while(0);

这样每次divider(除数)为0的时候便会在标准错误流上输出一个提示信息。

而##被称为连接符(concatenator),用来将两个Token连接为一个Token。注意这里连接的对象是Token就行,而不一定 是宏的变量。比如你要做一个菜单项命令名和函数指针组成的结构体的数组,并且希望在函数名和菜单项命令名之间有直观的、名字上的关系。那么下面的代码就非常实用:

struct command
{char * name;
void (*function) (void);
};#define COMMAND(NAME) { NAME, NAME ## _command }// 然后你就用一些预先定义好的命令来方便的初始化一个command结构的数组了:struct command commands[] = {COMMAND(quit),
COMMAND(help),
...
}

COMMAND宏在这里充当一个代码生成器的作用,这样可以在一定程度上减少代码密度,间接地也可以减少不留心所造成的错误。我们还可以n个##符号连接 n+1个Token,这个特性也是#符号所不具备的。比如:

#define LINK_MULTIPLE(a,b,c,d) a##_##b##_##c##_##dtypedef struct _record_type LINK_MULTIPLE(name,company,position,salary);
// 这里这个语句将展开为:
// typedef struct _record_type name_company_position_salary;

关于…的使用

…在C宏中称为Variadic Macro,也就是变参宏。比如:

#define myprintf(templt,...) fprintf(stderr,templt,__VA_ARGS__)// 或者#define myprintf(templt,args...) fprintf(stderr,templt,args)

第一个宏中由于没有对变参起名,我们用默认的宏__VA_ARGS__来替代它。第二个宏中,我们显式地命名变参为args,那么我们在宏定义中就可以用 args来代指变参了。同C语言的stdcall一样,变参必须作为参数表的最有一项出现。当上面的宏中我们只能提供第一个参数templt时,C标准要 求我们必须写成:

myprintf(templt,);

的形式。这时的替换过程为:

myprintf("Error!/n",);替换为:fprintf(stderr,"Error!/n",);

这是一个语法错误,不能正常编译。这个问题一般有两个解决方法。首先,GNU CPP提供的解决方法允许上面的宏调用写成:

myprintf(templt);

而它将会被通过替换变成:

fprintf(stderr,"Error!/n",);

很明显,这里仍然会产生编译错误(非本例的某些情况下不会产生编译错误)。除了这种方式外,c99和GNU CPP都支持下面的宏定义方式:

#define myprintf(templt, ...) fprintf(stderr,templt, ##__VAR_ARGS__)

这时,##这个连接符号充当的作用就是当__VAR_ARGS__为空的时候,消除前面的那个逗号。那么此时的翻译过程如下:

myprintf(templt);被转化为:fprintf(stderr,templt);

这样如果templt合法,将不会产生编译错误。

错误的嵌套-Misnesting

宏的定义不一定要有完整的、配对的括号,但是为了避免出错并且提高可读性,最好避免这样使用。

由操作符优先级引起的问题-Operator Precedence Problem

由于宏只是简单的替换,宏的参数如果是复合结构,那么通过替换之后可能由于各个参数之间的操作符优先级高于单个参数内部各部分之间相互作用的操作符优先级,如果我们不用括号保护各个宏参数,可能会产生预想不到的情形。比如:

#define ceil_div(x, y) (x + y - 1) / y

那么

a = ceil_div( b & c, sizeof(int) );

将被转化为:

a = ( b & c   + sizeof(int) - 1) / sizeof(int);
// 由于+/-的优先级高于&的优先级,那么上面式子等同于:
a = ( b & (c + sizeof(int) - 1)) / sizeof(int);

这显然不是调用者的初衷。为了避免这种情况发生,应当多写几个括号:

#define ceil_div(x, y) (((x) + (y) - 1) / (y))

消除多余的分号-Semicolon Swallowing

通常情况下,为了使函数模样的宏在表面上看起来像一个通常的C语言调用一样,通常情况下我们在宏的后面加上一个分号,比如下面的带参宏:

MY_MACRO(x);

但是如果是下面的情况:

#define MY_MACRO(x) {/
/* line 1 *//
/* line 2 *//
/* line 3 */ }//...if (condition())
MY_MACRO(a);
else
{...}

这样会由于多出的那个分号产生编译错误。为了避免这种情况出现同时保持MY_MACRO(x);的这种写法,我们需要把宏定义为这种形式:

#define MY_MACRO(x) do {/* line 1 *//
/* line 2 *//
/* line 3 */ } while(0)

这样只要保证总是使用分号,就不会有任何问题。

Duplication of Side Effects

这里的Side Effect是指宏在展开的时候对其参数可能进行多次Evaluation(也就是取值),但是如果这个宏参数是一个函数,那么就有可能被调用多次从而达到不一致的结果,甚至会发生更严重的错误。比如:

#define min(X,Y) ((X) > (Y) ? (Y) : (X))//...c = min(a,foo(b));

这时foo()函数就被调用了两次。为了解决这个潜在的问题,我们应当这样写min(X,Y)这个宏:

#define min(X,Y) ({/
typeof (X) x_ = (X);/
typeof (Y) y_ = (Y);/
(x_ < y_) ? x_ : y_; })

({…})的作用是将内部的几条语句中最后一条的值返回,它也允许在内部声明变量(因为它通过大括号组成了一个局部Scope)


http://chatgpt.dhexx.cn/article/dGtbHsAn.shtml

相关文章

Oracle基础之define用法简介教程

继上一篇博客Oracle绑定变量学习笔记&#xff0c;再写一篇define变量的简单教程 文章目录 1、define常量用法2、&和&&符号用法 1、define常量用法 注意&#xff1a;define常被误解为定义变量&#xff0c;其实不然&#xff0c;define定义的是字符常量&#xff0c;d…

#define的使用

目录 前言 1. #define定义标识符 2. #define定义宏 3. #define 替换规则 4. 宏和函数对比 前言 在很多C程序中&#xff0c;我们会在一个源文件的开头看到 #define 的字眼&#xff0c;和define的英文解释相同——可以理解为给xxx下定义&#xff0c;这里可以是标识符甚至是…

#define 的使用

#define 多数情况下我们是用来定义宏的&#xff0c;但是实际上&#xff0c;#define 作为预处理指令&#xff0c;也可以用来定义标识符&#xff0c;类似于 typedef&#xff0c;但是其本质和 typedef 是不同的。 目录 一、#define 定义标识符 1、定义和使用 2、#define 定义标…

C语言之#define用法入门详解

一、#define的基本语法 在C语言中&#xff0c;常量是使用频率很高的一个量。常量是指在程序运行过程中&#xff0c;其值不能被改变的量。常量常使用#define来定义。 使用#define定义的常量也称为符号常量&#xff0c;可以提高程序的运行效率。 其常见的用法包括两种&#xff0…

python微信聊天机器人_Python搭建一个微信聊天机器人

前言 因为班群里总是挺静的&#xff0c;所以想着要不放个机器人试试能不能活跃活跃气氛&#xff0c;然后就begin网上查阅资料试着建立个机器人。 语言环境开发环境&#xff1a;Ubuntu16.04 编程语言&#xff1a;Python2.7 步骤第一步&#xff1a;首先安装python2.7语言 su…

Python大佬用20行代码带你打造一个微信聊天机器人(附代码)

近来&#xff0c;打开微信群发消息&#xff0c;就会秒收到一些活跃分子的回复&#xff0c;有的时候感觉对方回答很在理&#xff0c;但是有的时候发现对方的回答其实是驴唇不对马嘴&#xff0c;仔细深究发现&#xff0c;原来对方是机器人。今天&#xff0c;小编就带大家用20行代…

16行代码实现微信聊天机器人,自动智能回复,打团了让它来陪老婆聊天

在我们的生活和工作当中,很多时候我们并不能及时地回复消息,尤其是业务比较多的人,客户给我们发消息我们不回又不好,但又没有那么多精力时时回复,这个时候智能机器人就能帮助我们解决很多问题。 像电商类的客服,像大的QQ群/微信群管理员,以及我们打游戏的时候,挂着脚本…

利用python实现微信聊天机器人(需自己设置关键字及回复内容)

本人为python小白写此帖纪念用python写的第一个脚本 微信聊天机器人 1、通过查找截图进行鼠标点击操作 2、通过直接输入屏幕中的坐标进行鼠标点击操作&#xff08;在复制他人最新信息时采用指定坐标双击&#xff09; # 双击并复制信息 pyautogui.doubleClick(394,…

php个人微信聊天机器人,wxpy将个人微信号变成微信聊天机器人

一、实验环境&#xff1a;win10 python3 wxpy 微信版本&#xff1a;6.6.5 1.1、从官网下载python最新版本并进行安装。 1.2、进入python安装目录Scripts/文件夹下&#xff0c;使用easy_install.exe pip 命令安装pip 1.3、使用pip install wxpy 命令安装wxpy 二、源码文件demo…

一个15分钟的视频,教你用Python创建自己的微信聊天机器人文中赠送全部源代码

你好&#xff0c;我是程序员晚枫。 在社区交流群&#xff08;&#x1f449;传送门&#xff09;里的朋友都知道&#xff0c;社区有一个智能聊天机器人&#xff1a; 1、24小时在线的智能客服&#xff0c; 2、根据大家的提问&#xff0c;自动回复对应的学习资料&#xff0c; 3、对…

微信聊天机器人

做一个微信聊天自动回复的机器人 # -*- coding: utf-8 -*- """ Created on Tue Jun 4 18:30:39 2019author: yyp """#-*- coding:utf-8 -*- import itchat import requestsdef get_response(msg):apiurl http://i.itpk.cn/api.php #//moli机器…

Python Wechaty 微信聊天机器人 padlocal协议搭建指南

python版本推荐3.9 微信机器人框架&#xff1a;Wechaty Python-Wechaty中文手册 开发前需要准备&#xff1a; Linux服务器&#xff08;CentOS 8.0&#xff0c;Python环境&#xff0c;Docker&#xff09;本地Python开发环境&#xff08;我个人使用VSCode&#xff09;申请一个…

用Python写了一个微信聊天机器人

在我们的生活和工作当中&#xff0c;很多时候我们并不能及时地回复消息&#xff0c;尤其是业务比较多的人&#xff0c;客户给我们发消息我们不回又不好&#xff0c;但又没有那么多精力时时回复&#xff0c;这个时候智能机器人就能帮助我们解决很多问题。 像电商类的客服&#…

Python创建微信聊天机器人

最近想着做一个微信机器人&#xff0c;主要想要实现能够每天定时推送天气预报或励志语录&#xff0c;励志语录要每天有自动更新&#xff0c;定时或当有好友回复时&#xff0c;能够随机推送不同的内容。于是开始了分析思路。博主是采用了多线程群发&#xff0c;因为微信对频繁发…

三步轻松打造微信聊天机器人(附源码)

最近微信公众平台开发是热门&#xff0c;我也跟风做了一个陪聊的公众号。 其实类似的自动回话程序早就有了&#xff0c;比如前一阵很火的小黄鸡&#xff08;还是小黄鸭来着&#xff1f;&#xff09;。但尽管是跟风&#xff0c;也要体现一些不同。别人做的都是中文陪聊&#xff…

实现微信聊天机器人-中级篇

一、ChatterBot工作原理 Chatbot Engine的技术框架采用了开源项目ChatterBot (https://github.com/gunthercox/ChatterBot)。 ChatterBot是一个python库,利用它可以轻松的创建对话软件。 一个未经训练的ChatterBot实例开始不知道如何沟通,每次用户输入一个语句,ChatterBo…

微信聊天机器人,不使用iChat,可以群聊

目录 1. 微信聊天界面截图 2. 图片文字识别 3. 获取最新消息 3.1 独聊 3.2 群聊 4. 机器人聊天系统 5. 成果展示 6. 全部代码 本文参考大神【喵王叭】的文章&#xff1a;python实现微信、QQ聊天自动回复【纯物理】_喵王叭的博客-CSDN博客_python自动回复纯物理方式实现微…

tomcat的开发模式和生产模式

tomcat这两种模式对大家的影响主要是在jsp的开发当中。大家都知道jsp需要先编译成servlet源文件并编译为字节码&#xff0c;即生成.class文件才可以执行&#xff0c;所以在jsp文件首次运行时先要进行编译导致运行较慢&#xff0c;之后的访问都会直接进入执行阶段。 在开发模式下…

chrome开发模式下清除缓存问题

介绍&#xff1a; 在开发的时候经常会遇到这样的问题&#xff0c;后台js修改&#xff0c;前台怎么也清除不了缓存的问题&#xff0c;包括使用ctrlf5都没有办法处理,今天教大家一种方式&#xff0c;在chrome中采用开发模式下彻底清除缓存问题 一、打开chrome&#xff0c;随便访…

微信小程序-云开发模式pk传统开发模式【详细】

👨‍🎓作者:bug菌 ✏️博客:CSDN、掘金等 💌公众号:猿圈奇妙屋 🚫特别声明:原创不易,转载请附上原文出处链接和本文声明,谢谢配合。 🙏版权声明:文章里可能部分文字或者图片来源于互联网或者百度百科,如有侵权请联系bug菌处理。 小伙伴们,说起开发微…