C语言define高级用法大全_c define(1),阿里高级算法专家公开10份资料

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Golang全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024b (备注go)
img

正文

#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 hello
 int 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\_##x

int 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\_##x

int 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\_##x

int 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);// ?, 1

	return 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##\_##d

typedef 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);

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

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Go)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
myprintf(“Error!/n”,);

替换为:

fprintf(stderr,“Error!/n”,);


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



myprintf(templt);


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




**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Go)**
[外链图片转存中...(img-JOBVND82-1713709414860)]

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值