赞
踩
C语言中有一些预定义符号,这些符号是语言内置的,可以直接使用
例如:FILE LINE TIME DATE STDC(如果编译器支持ANSI C就返回1
,否则未定义)
int main()
{
printf("%s\n", __FILE__);
printf("%d\n", __LINE__);
printf("%s\n", __TIME__);
printf("%s\n", __DATE__);
//printf("%d\n", __STDC__);
return 0;
}
可以看到我的这个编译器是不支持ANSI C的
语法:#define name stuff
#define定义的标识符,可以代表很多东西
例如:
#define M 10
#define Q "hello world"
#define PRINT printf("hello world")
int main()
{
printf("%d\n", M);
printf("%s\n", Q);
PRINT;
return 0;
}
上面那些都是正常的使用情况:
下面介绍两种特殊的情况:
1,定义一个死循环
2,如果定义的stuff过长,可以分几行写,除最后一行外,每行最后加上\作为分行符
#define DO_FOEVER for(;;)
#define DEBUG_PRINT printf("file:%s\tline:%d\t \
date:%s\ttime:%s\n" ,\
__ FILE__,__LINE__ , \
__DATE__,__TIME__ )
#define 机制包括了一个规定,允许把参数替换到文本中,
这种实现通常称为宏(macro)或定义 宏(define macro)
语法:#define M(参数) stuff
无论在定义宏还是普通标识符的时候,最终都会在编译预处理阶段完成替换
并且在定义宏的时候,应注意带括号,防止运算符的优先级问题
下面举几个例子,自己体会一下:
#define DOUBLE(x) x*x
int main()
{
printf("%d\n", DOUBLE(6));
printf("%d\n", DOUBLE(6+1));
printf("%d\n", 2*DOUBLE(6));
return 0;
}
看到这个结果明显不对劲,这就是上述所说的应该给宏定义的文本中的各项加上括号,避免出错
下面看下修改过后:
#define DOUBLE(x) ((x)*(x))
注意:无论在定义标识符还是在定义宏的时候,最好不要在结尾加上分号,
这样很容易在使用上出错,例如:
#define DOUBLE(x) ((x)*(x));
if(1)
printf("%d\n", DOUBLE(6));
else
;
实质上,编译预处理完后,DOUBLE(6)被替换成下面的样子
if(1)
printf("%d\n", ((6) * (6)));;
else
;
上面这种情况是会报错的,一条if语句跟了两条语句(没有大括号前提下)。
所以,我们在定义标识符,或者宏的时候 末尾不要加分号,避免使用时的错误。
讲述这个之前,首先介绍一下C语言的一种机制:
字符串相邻在一起时,会合并成一个字符串
例如:
int main()
{
printf("%s\n", "hello ""world");
return 0;
}
而,#的作用是,当宏参数前面加上#的时候,会将这个参数转化为字符串。
例如:#define(x) printf(“…”#x"…“);
最终会被替换成:printf(”…““x””…");
下面举一个业务场景做例子:
int main()
{
int a = 10;
printf("the value of a is %d\n", a);
int b = 20;
printf("the value of b is %d\n", b);
float c = 30.0f;
printf("the value of c is %f\n", c);
return 0;
}
每一个数据后面都跟着一条输出语句,而且每条输出语句的内容极为相似,
我们能不能将它封装为一个函数呢?其实是不可以的,因为我们想让它以何种方式打印,
需要传给函数"%d" "%f"这样的参数,但是函数会误以为这些是字符串。
所以要借助宏来实现
#define PRINT(x,format) printf("the value of "#x" is "format"\n",x)
int main()
{
int a = 10;
PRINT(a, "%d");
int b = 20;
PRINT(b, "%d");
float c = 30.0f;
PRINT(c, "%f");
return 0;
}
##的作用非常奇怪,是将##两边的字符合成一个字符
例如:
#define A(x,y) printf("%d\n",x##y)
int main()
{
int ab = 10;
A(a, b);
return 0;
}
当宏的参数在宏定义的文本中出现了不止一次的时候,就可能会出现副作用。
例如:我们定义一个宏来输出两个数中的较大值
#define MAX(x,y) ((x)>(y)?(x):(y))
int main()
{
int a = 2;
int b = 3;
//想输出++a 与++b中的较大值
printf("%d\n", MAX(++a, ++b));
return 0;
}
我们本想输出的是4,但是你看结果,这带副作用的参数
比如,我们正常用malloc来开辟内存空间
int* a=(int*) malloc(sizeof(int)*10);
写起来较为麻烦,可以用宏来封装一层
#define MALLOC(type,num) (type*)malloc(sizeof(type)*num)
这样开辟内存空间就可以这样写了,
int*a=MALLOC(int,10);
比上面的代码就简洁了许多
我们在结构体那片博客中介绍过offsetof()的功能:是结构体变量的成员地址
相对于结构体变量的地址的偏移量
今天,我们就来模拟实现一下offsetof()
struct stu
{
int a;
char c;
int b;
};
#define OFFSETOF(type,x) (int)&(((type*)0)->x)
int main()
{
printf("%d\n", OFFSETOF(struct stu, a));
printf("%d\n", OFFSETOF(struct stu, c));
printf("%d\n", OFFSETOF(struct stu, b));
return 0;
}
所以,可以看到函数与宏各有千秋,函数不能实现的宏可以实现,宏不能实现的但是函数可以实现,
所以下面我们来总结一下,函数与宏的区别
#define PRINT(x,format) printf("the value of "#x" is "format"\n",x)
int main()
{
int a = 10;
PRINT(a, "%d");
int b = 20;
PRINT(b, "%d");
float c = 30.0f;
PRINT(c, "%f");
return 0;
}
宏:每次使用时,在编译预处理阶段定义的宏的代码就会插入到程序中,如果定义的宏代码量很大
,这样的话就大大增加了代码量。
函数:函数的代码只出现在一个地方,每次调用函数的时候都只会调用这一份代码
宏:当实现的功能比较简单是,宏的速度是很快的
函数:当实现的功能比较简单时,调用函数和释放函数栈帧的时间,比真正实现函数功能还长
举个简单例子:输出较大值
int main()
{
int a = 2;
int b = 3;
max(a, b);
MAX(a, b);
return 0;
}
可以看到,如果实现简单的功能,宏要比函数运行速度快不少
宏:如果在定义宏的时候没有适当加上括号,可能会在周围环境的表达式中,与临近运算符出现
未曾预料的结果
函数:函数参数只在函数调用的时候计算一次,它的运算结果传递给函数。表达式的结果更容易预测。
例如:
#define DOUBLE(x) x+x
int main()
{
int a = 3;
printf("%d\n", 2 * DOUBLE(3));
}
我们预料的结果应该是12,但结果:
宏:宏参数可能被替换到宏体中的多个位置,所以带有副作用的参数会造成不可预料的结果。
函数:函数只在传参的时候求值一次,结果更容易预料。
宏:参数与类型无关,只要对参数的操作是合法的,他就可以运用到各种类型。
函数:函数参数是与类型有关的,参数的类型不同那么就需要不同的函数。
例如:比较大小用宏来实现,就可以比较多种数据类型的大小
#define MAX(x,y) ((x)>(y)?(x):(y))
宏:由于宏的实现是替换,所以宏不能够调试。
函数:函数是可以调试的。我们在调试的时候按F11进入到函数内部。
void print(int* arr, int len)
{
int i = 0;
for (i = 0; i < len; i++)
{
printf("%d ", arr[i]);
}
}
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr) / sizeof(arr[0]);
print(arr, sz);
return 0;
}
宏:宏是不能够递归的。
函数:函数可以实现递归。
例如我们写一个递归函数实现求前N项和
int sumN(int n) { if (n == 1) { return 1; } else { return n + sumN(n - 1); } } int main() { int n = 10; printf("%d\n", sumN(n)); return 0; }
综上,介绍了七点函数与宏的区别。
选择性的编译,当满足某种条件时,才进行编译,比如说我们用来调试的代码,删了比较可惜,
就可以用条件编译的方式。
#include <stdio.h>
#define __DEBUG__
int main()
{
int i = 0;
int arr[10] = {0};
for(i=0; i<10; i++)
{
arr[i] = i;
#ifdef __DEBUG__
printf("%d\n", arr[i]);//为了观察数组是否赋值成功。
#endif //__DEBUG__
}
return 0;
}
常见的条件编译指令:
1. #if 常量表达式 //... #endif //常量表达式由预处理器求值。 如: #define __DEBUG__ 1 #if __DEBUG__ //.. #endif 2.多个分支的条件编译 #if 常量表达式 //... #elif 常量表达式 //... #else //... #endif 3.判断是否被定义 #if defined(symbol) #ifdef symbol #if !defined(symbol) #ifndef symbol 4.嵌套指令 #if defined(OS_UNIX) #ifdef OPTION1 unix_version_option1(); #endif #ifdef OPTION2 unix_version_option2(); #endif #elif defined(OS_MSDOS) #ifdef OPTION2 msdos_version_option2(); #endif #endif
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。