? 1.带副作用的宏参数
? ?如果宏的参数中带有改变传过来的参数,形式比如说自增、自减,它可能带有副作用exp:
#define MAX(x,y) ((x>y)?(x):(y))
int main()
{
int a = 10;
int b = 20;
printf("%d\n", MAX(a++, b++));//21
//printf("%d",(a++>b++)?(a++):(b++));
//这里比较完之后a,b都自增了,输出的较大值b会是21,在输出之后b又自增
printf("%d\n", a);//11
//a在上面的式子中只自增了一次变成11
printf("%d\n", b);//22
//而b自增了两次,一次在比较之后,一次在输出之后,所以b现在等于22
return 0;
}
?2.宏和函数的对比
函数的参数需要特定的参数,而宏在传参的时候不需要考虑数据类型,它只是进行替换;但在程序序调试的时候,宏出现的错误不容易被找出;函数在调用和传参还有返回需要大量的汇编代码,花费的时间可能更长,而宏在预处理阶段就发生了替换;但是在每次宏的替换都会将一段代码插入到代码中,而函数仅有一段代码,每次使用的时候仅需调用;函数参数是不能传数据类型的,而宏可以;
#define MALLOC(num,type) (type*)malloc(num*sizeof(type))
#define SIZEOF(type) sizeof(type)
void put_data(int* pr)
{
int i = 0;
//动态连续空间放入数据
for (i = 0; i < 10; i++)
{
pr[i] = i;
}
//打印
for (i=0; i <10; i++)
{
printf("%d ", pr[i]);
}
printf("\n");
}
int main()
{
//宏传类型参数,可行
printf("%d\n", SIZEOF(int));//4
//动态开辟空间
int* ptr1 = (int*)malloc(10 * sizeof(int));
put_data(ptr1);//0 1 2 3 4 5 6 7 8 9
int* ptr = MALLOC(10, int);
put_data(ptr);//0 1 2 3 4 5 6 7 8 9
//释放空间
free(ptr1);
ptr1 = NULL;
free(ptr);
ptr = NULL;
return 0;
}
?#undef用于取消符号定义的
#define MAX(x,y) ((x>y)?(x):(y))
int main()
{
int a = 20;
int b = 30;
printf("%d", MAX(a, b));
#undef MAX(x,y)
printf("%d", MAX(a, b));//error 未定义标识符
return 0;
}
? 3.命令行定义
gcc test.c -D SZ=10在程序运行之前就定义好,
4.条件编译
在一段程序中,也许可以遇见我们只在特定情况下需要的代码,或者只需要使用一次的代码,我们可以通过条件编译,进行判断选择这段代码需不需要编译
#define PRINT_S
int main()
{
int a = 20;
int b = 0;
#ifdef PRINT//这里的就是说之前定义过PRINT这个符号,程序就会编译这段代码,反之就会跳过
printf("%d", a);//注意如果一段代码不会进行编译,就是灰色的(vs2017下)
#endif//这里的endif是条件编译的结束语句,与ifdef搭配
#ifdef PRINT_S
printf("%d", a);//20
#endif
//1.#if 常量表达式
#if 1 //为真进行编译
printf("%d", a);//20
#endif
//2.多分支判断
#if 20>30 //为假不进行编译
printf("%d", a);
#elif 20==30
printf("%d", a);
#else
printf("%d", a);//20
#endif
//3.#if defined(符号)
#if defined(PRINT_S)//如果定义过
printf("%d", a);//20
#endif
#if !defined(PRINT_S)//如果没有定义过(但定义过)
printf("%d", a);//20
#endif
//4.
return 0;
}
5.文件包含
在头文件包含的过程,是将头文件的内容copy一份到现在.c程序中进行编译,在我们实际写工程的过程中,其实会有意无意的将一个头文件包含多次,那么整个项目当中的就会有很多的代码冗余,在我们的库函数有解决办法
//假设这是我们要包含的头文件,我们一般会采取以下方式,防止一个头文件被多次包含
#ifndef __PASS__
#define __PASS__
//这里面是头文件的内容
//.......
#endif
//这样头文件的内容只能被包含一次(编译)
//当然还有其他的解决办法(现在多采用)
#pragma once
//这里面是头文件的内容
//.......
? 其他的预处理指令:#error(如果在编译程序的过程中遇到,会编译一个错误提示信息,并停止编译)、#pragma(指定一个跟踪选择)、#line(改变当前命令行数和文件名称)等? ? ? ? ? ? ? ? ? ?
6.宏offsetof可以查偏移? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
#include <stddef.h>
struct S
{
//默认对齐数为8
int a;//4 占用了偏移量为0 1 2 3的地址共四个字节
char b;//1 占用了偏移量为4的地址 共一个字节
double c;//8 现在接下来使用的的空间地址的偏移量为5,但5不是八的倍数,所以要将指针移到偏移量为8的地址继续接下来的的存储,
//前面浪费了3个字节的空间,再将c存进去,占8个字节
}s;
//这个结构体的大小为16 为最大对齐数的倍数
#define OFFSETOF(struct_typename,member_name) (int)&(((struct_typename*)0)->member_name)
//解释:我们将结构体的开始放成员的地址为0(结
//构体类型的地址或者指针)当首地址为0,
//那对应偏移量的成员的地址就等于偏移量,exp:
//上面b的地址就是偏移量4,最后的类型强转
//的强制类型转化,将地址的转换为整型,地址是十
//六进制数字,而如果指针变量为整型常量,指针的
//存放的地址将会被赋值为这个整型常量(((struct
//S*)0)->a)的地址为 0X00000000,在这个地址偏
//移4之后放下b,而此时b的地址为0x00000004,对
//这些地址强转成整型,就会得到0,4,8等成员的
//相较首地址的偏移量
int main()
{
//printf("%d\n", sizeof(s));//16
//使用c库里定义的宏
//printf("a在结构体中的偏移量%d\n", offsetof(struct S,a));//0
//printf("b在结构体中的偏移量%d\n", offsetof(struct S, b));//4
//printf("c在结构体中的偏移量%d\n", offsetof(struct S, c));//8
//使用上面自己定义的宏
printf("a在结构体中的偏移量%d\n", OFFSETOF(struct S, a));//0
printf("b在结构体中的偏移量%d\n", OFFSETOF(struct S, b));//4
printf("c在结构体中的偏移量%d\n", OFFSETOF(struct S, c));//8
return 0;
}
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
|