C中的预处理,宏

简介: 🐰宏的缺点🐰用宏实现动态开辟的技巧🐰命名约定🐰#undef🐰条件编译🌸#ifdef🌸#ifndef🌸#if defined(symbol)🌸#if !defined(symbol)🐰常见的条件编译指令🌸#if 🌸多分支的条件编译🐰文件包含🐰atoi🐰offsetof🐰模拟实现offsetof🐰有关宏的习题🌸写一个宏,可以将一个整数的二进制位的奇数位和偶数位交换。

🚀🚀🚀大家觉不错的话,就恳求大家点点关注,点点小爱心,指点指点🚀🚀🚀

目录

🐰宏的缺点

🐰用宏实现动态开辟的技巧

🐰命名约定

🐰#undef

🐰条件编译

🌸#ifdef

🌸#ifndef

🌸#if defined(symbol)

🌸#if !defined(symbol)

🐰常见的条件编译指令

🌸#if

         🌸多分支的条件编译

🐰文件包含

🐰atoi

🐰offsetof

🐰模拟实现offsetof

🐰有关宏的习题

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


🐰宏的缺点

1.每次使用宏的时候,会将宏定义的代码插入到程序当中,增加了程序的长度

2.宏没有办法调式

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

4.宏可能会带来运算符优先级的问题,导致程序容易出错。(可以适当添加圆括号来解决)

1. #include<stdio.h>
2. #define MAX(x,y) ((x)>(y)?(x):(y))
3. int Max(int x,int y)
4. {
5. return x>y?x:y;
6. }
7. int main()
8. {
9. int a=2,b=3;
10. //函数
11. printf("%d\n",Max(a, b));
12. //宏
13. printf("%d\n",MAX(a, b));
14. return 0;
15. }

🐰用宏实现动态开辟的技巧

1. #include<stdio.h>
2. #include<stdlib.h>
3. #define MALLOC(x,type) (type*)malloc(x*sizeof(type))
4. int main()
5. {
6. int *p=(int*)malloc(10*sizeof(int));
7. //宏
8. //这样使得动态开辟空间更加方便
9. int* p2=MALLOC(10, int);
10. //只需传递开辟空间的大小,和类型
11. return 0;
12. }

🐰命名约定

标准并没有规定宏名必须全部大写,函数名不能全部大写,只是一种俗称的习惯,这样更容易区分宏和函数

把宏名全部大写

函数名不全部大写

🐰#undef

#undef用于移除一个宏定义

例如:

1. #undef用于移除一个宏定义
2. #include<stdio.h>
3. #define M 100
4. int main()
5. {
6. printf("%d\n",M);
7. #undef M//M就被移除了
8. printf("%d\n",M);
9. return 0;
10. }

🐰条件编译

在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的。因为我们有条件编译指令

调试性的代码,删除可惜,保留碍事,我们选择性编译

🌸#ifdef

1. #ifdef symbol//如果symbol定义,则执行#ifdef symbol和endif的语句
2. 
3. ...
4. 
5. endif

例如:

1. #include<stdio.h>
2. #define __DEBUG__ 10
3. int main()
4. {
5. int arr[10];
6. for(int i=0;i<10;i++)
7.     {
8.         arr[i]=i;
9. #ifdef __DEBUG__//如果没有定义__DEBUG__,则后面#endif之前的语句不编译
10. printf("%d ",arr[i]);
11. #endif
12.     }
13. return 0;
14. }
15. 结果:
16. 0 1 2 3 4 5 6 7 8 9
17. 因为这里的__DEBUG__ 被定义了,所以会执行#ifdef和#endif之间的语句

🌸#ifndef

1. #ifndef symbol //如果symbol没定义,则执行#ifdef symbol和endif的语句
2. 
3. ...
4. 
5. #endif
1. #endif
2. #include<stdio.h>
3. #define __DEBUG__ 10
4. int main()
5. {
6. int arr[10];
7. for(int i=0;i<10;i++)
8.     {
9.         arr[i]=i;
10. #ifndef __DEBUG__
11. printf("%d ",arr[i]);
12. #endif
13.     }
14. return 0;
15. }
16. 没有输出结果,因为__DEBUG__ 定义了,所以不会执行#ifndef和#endif之间的语句。这个和#ifdef和#endif执行的情况相反

🌸#if defined(symbol)

1. #if defined(symbol)//如果symbol定义,则执行#ifdef symbol和endif的语句
2. 
3. ...
4. 
5. endif
1. #include<stdio.h>
2. #define __DEBUG__ 10
3. int main()
4. {
5. int arr[10];
6. for(int i=0;i<10;i++)
7.     {
8.         arr[i]=i;
9. #if defined(__DEBUG__)
10. printf("%d ",arr[i]);
11. #endif
12.     }
13. return 0;
14. }
15. 结果:0 1 2 3 4 5 6 7 8 9
16. 因为定义了__DEBUG__,所以会执行#if defined(__DEBUG__)和#endif之间的代码,这个和#ifdef和endif使用情况一样

🌸#if !defined(symbol)

1. #if !defined(symbol)//如果symbol没定义,则执行#ifdef symbol和endif的语句
2. 
3. ...
4. 
5. endif
1. #include<stdio.h>
2. #define __DEBUG__ 10
3. int main()
4. {
5. int arr[10];
6. for(int i=0;i<10;i++)
7.     {
8.         arr[i]=i;
9. #if !defined(__DEBUG__)
10. printf("%d ",arr[i]);
11. #endif
12.     }
13. return 0;
14. }

🐰常见的条件编译指令

🌸#if

1. #if 常量表达式
2. 
3. ...
4. 
5. #endif

如果常量表达式的值为真,则执行#if和#endif之间的代码

1. #include<stdio.h>
2. int main()
3. {
4. #if 1==1//如果常量表达式的值为真,则执行#if和#endif之间的代码
5.     printf("hehe\n");
6. #endif
7.     return 0;
8. }
9. 结果:
10. hehe
11. 因为1==1表达式的值为真

🌸多分支的条件编译

1. #if 常量表达式
2. 
3. ...
4. 
5. #elif 常量表达式
6. 
7. ...
8. 
9. #elif 常量表达式
10. 
11. ...
12. 
13. endif
1. #include<stdio.h>
2. int main()
3. {
4. #if 1==1//这里可以与if 和else if的联系,只会执行一个(一组)语句
5.     printf("hehe1\n");
6. #elif 1==1//
7.     printf("hehe2\n");
8. #elif 1==1//
9.     printf("hehe3\n");
10. #endif
11.     return 0;
12. }
13. 结果:hehe1
14. 因为第一个常量表达式1==1的值为真,所以执行printf("hehe2\n");,不会执行以下的分支语句了,这里与if和else if执行情况差不多

🐰文件包含

头文件的包含的方式

1.本地文件的包含

#include"test.h"

查找策略:先找在源文件所在的目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件

如果找不到就提示编译错误

2.库文件的包含

#include<stdio.h>

查找策略:直接去标准位置查找头文件,如果找不到就提示编译错

如果找不到就提示编译错误

注意:虽然库函数的头文件可以用双引号查找,但是进行了两次查找,降低了代码的速率

头文件多重包含(C++和类的多重继承类造成成员数据的重复)

解决方法:

4875988C-2347-4FB6-B470-4D974D47EA80.png添加#pragma once,可以防止头文件多刺被包含

 

🐰atoi

用于将字符串转换成整形

atoi的原型:

int atoi (const char * str);

const char * str:字符串的首地址

1. #include<stdio.h>
2. #include<stdlib.h>
3. int my_atoi(char* ptr)
4. {
5. int sum=0;
6. int flag=1;
7. if(*ptr=='+')
8.     {
9.         flag=1;
10.         ptr++;
11.     }
12. if(*ptr=='-')
13.     {
14.         flag=-1;
15.         ptr++;
16.     }
17. while(*ptr)
18.     {
19.         sum=sum*10+(*ptr-'0');
20.         ptr++;
21.     }
22.     sum*=flag;
23. return sum;
24. }
25. int main()
26. {
27. int ret=atoi("-1234");
28. int rat=my_atoi("-1234");//模拟实现atoi
29. printf("%d\n",ret);
30. printf("%d\n",rat);
31. return 0;
32. }
33. 结果:
34. -1234
35. -1234

🐰offsetof

offsetof宏用于与求偏移量(结构体,联合体...)

offsetof的原型:

offsetof (type,member)

type:类型(结构体,联合体...)

member:成员(结构体成员...)

1. #include<stdio.h>
2. #include<stddef.h>
3. typedef struct S
4. {
5. int a;//0-3
6. int b;//4-7
7. char c;//8
8. double d;//16-23
9. }S;
10. int main()
11. {
12. printf("%d\n",offsetof(S, a));
13. printf("%d\n",offsetof(S, b));
14. printf("%d\n",offsetof(S, c));
15. printf("%d\n",offsetof(S, d));
16. return 0;
17. }
18. 结果:
19. 0
20. 4
21. 8
22. 16
23. 分别对应着a,b,c,d的偏移量

🐰模拟实现offsetof

1. #include<stdio.h>
2. #include<stddef.h>
3. typedef struct S
4. {
5. int a;//0-3
6. int b;//4-7
7. char c;//8
8. double d;//16-23
9. }S;
10. #define OFFSETOF(t,m) (int)(&((S*)0)->m)//把0地址处强制转化为结构体指针,然后取出成员的地址,在以整形的形式输出,这样就可以得到偏移量了,(换句话说,就是结构体的成员假设从0地址处进行存储)
11. //注意:强制类型转化,不会真的原来的类型转化了,而是把原来的类型进行临时拷贝,然后再转化为自己想要的类型,所以没有真正把系统0地址转化为结构体指针,而是把0地址的拷贝转化为了结构体指针类型。
12. int main()
13. {
14. printf("%d\n",OFFSETOF(S,a));
15. printf("%d\n",OFFSETOF(S,b));
16. printf("%d\n",OFFSETOF(S,c));
17. printf("%d\n",OFFSETOF(S,d));
18. return 0;
19. }
20. 结果:
21. 0
22. 4
23. 8
24. 16

🐰有关宏的习题

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

1. 解题思路:
2. 
3. 首先保存奇数位
4. 
5. 保存的方法:例如:10(00000000000000000000000000001010)
6. 
7. 10:            00000000000000000000000000001010
8. 
9. 0x55555555     01010101010101010101010101010101
10. 
11. 只要让10按位与上0x55555555就能保留奇数位(相同为相同的,不同则为0)
12. 
13. 奇数位结果:00000000000000000000000000000000
14. 
15. 保存偶数位
16. 
17. 保存的方法:例如:10(00000000000000000000000000001010)
18. 
19. 10:            00000000000000000000000000001010
20. 
21. 0xaaaaaaaa     10101010101010101010101010101010
22. 
23. 只要让10按位与上0xaaaaaaaa就能保留奇数位(相同为相同的,不同则为0)
24. 
25. 偶数位结果:00000000000000000000000000001010
26. 
27. 让奇数位左移一位
28. 
29. 00000000000000000000000000000000<<1得到00000000000000000000000000000000
30. 
31. 
32. 让偶数位右一位
33. 
34. 00000000000000000000000000001010>>1得到00000000000000000000000000000101
35. 
36. 然后移位后的奇数位和偶数位相加00000000000000000000000000000000+00000000000000000000000000000101
37. 
38. 最后的到00000000000000000000000000000101为5
1. #include<stdio.h>
2. #define SWAP(n) n=(((n&0x55555555)<<1)+((n&0xaaaaaaaa)>>1))
3. int main()
4. {
5. int a=10;
6. SWAP(a);
7. printf("%d\n",a);
8. return 0;
9. }
10. 结果:
11. 5
12. 10的二进制补码:00000000000000000000000000001010
13. 转化后的补码:00000000000000000000000000000101
14. 所以为5

🌸🌸🌸如果大家还有不懂或者建议都可以发在评论区,我们共同探讨,共同学习,共同进步。谢谢大家! 🌸🌸🌸  



相关文章
|
7月前
|
编译器 程序员 C语言
【文件包含的本质、预处理符号、# vs ##】
【文件包含的本质、预处理符号、# vs ##】
|
7月前
|
编译器 Linux C语言
预处理详解(#和##运算符、命名约定、#undef​​、命令行定义​、条件编译、头文件的包含​)
预处理详解(#和##运算符、命名约定、#undef​​、命令行定义​、条件编译、头文件的包含​)
|
7月前
|
安全 编译器 程序员
C语言(16)----预处理中的宏以及预处理指令
C语言(16)----预处理中的宏以及预处理指令
67 2
|
7月前
|
安全 C语言
程序预处理:全解版-1
程序预处理:全解版
44 0
|
7月前
|
编译器 C语言
程序预处理:全解版-2
程序预处理:全解版
41 0
|
自然语言处理 编译器 程序员
文件编译和预处理
文件编译和预处理
106 0
|
存储 自然语言处理 编译器
C/C++:程序环境和预处理/宏
简单讲解了宏定义、预处理、条件编译等等
C/C++:程序环境和预处理/宏
|
安全 编译器 Linux
【C/C++】你了解预处理吗?带你深度剖析#define定义宏
【C/C++】你了解预处理吗?带你深度剖析#define定义宏
232 0
|
存储 C语言
c语言程序的预处理
c语言程序的预处理
c语言程序的预处理