第四章:程序流
第六节:宏
宏简单的说,就是用一段文字替代另一段文字。有人说,这不是吃饱撑的吗?不是的,你学了以后,就知道这个东西多有用了。最直观的,如果有一大段文字,总是在我们程序中使用,那么我们每次使用是不是就要把这段文字重新再写一遍,是不是很烦,那么我们给这一段文字取个名字,只要我们把这个名字写在我们的程序中,编译器在翻译我们程序的时候,自动将这个名字替换成对应的文字,是不是很爽的感觉。其实好处还不止如此,如果我们要对这段文字修改呢?是不是修改一处就可以了。如果我们每个地方不适用名字,而是直接使用文字,要修改的话,很容易漏掉一个或者几个地方,是不是就出错了。
宏的关键字#define
最简单的,就是给一个常用的常量,或者字符串取一个宏名字。例如:
/*********冒牌程序员-毛哥 define.c***********/
#include<stdio.h>
#define pi 3.14
int main()
{
double f;
printf("请输入球的半径:");
scanf("%lf",&f);
printf("球的体积是:%f\n",3/4*pi*f*f*f);//为什么这个结果为0?
printf("球的体积是:%f\n",pi*f*f*f*3/4);
printf("球的面积是:%f\n",4*pi*f*f);
return 0;
/*
可以看出,我们使用pi替代了3.14,如果我们想提高精度,就要将3.14改成3.1415926
这时,我们可以只修改#define 语句就可以了,方便且不出错。
宏的作用就是,在编译器第一次扫描源文件(就是读源文件内容的时候),他会把pi用3.14进行替换。
还记得#include吗。
*/
}
下面我们介绍复杂的函数式宏定义,看程序:
/*********冒牌程序员-毛哥 define.c***********/
#include<stdio.h>
#define MAX(a,b) ((a)>(b)?(a):(b))
/*
可以观察,这个函数类的宏,MAX是宏的名字,a和b是宏的参数。和函数非常相似,甚至
有时候,你自己用的是一个函数,还是一个宏,你都分不清楚,我也分不清楚。
a,b是宏的参数。
*/
//我们可以把我们前面,用scanf取出除回车外的其他字符的代码用宏表示:
#define __get(c) \
do \
{ \
scanf("%c",&c); \
} while (c==10);
//定义一个结构体 ydm
typedef struct ydm
{
int x;
char c;
float z;
char *s;
} ydm_t;
#define offsetof(TYPE,MEMBER) ((long)&((TYPE*)0)->MEMBER)
#define get_ydm(ptr,type,member_name) ({ \
typeof(((type*)0)->member_name) *__mptr = (ptr); \
(type*)((char*)__mptr - offsetof(type,member_name)); \
})
/*
offsetof宏:
假设一个TYPE类型的结构体,其地址是0,那么变量MEMBER的地址,就是从结构体
开始,到成员变量MEMBER的偏移量,这个偏移量后面会经常说。一个结构体成员变量
的偏移量,指的就是结构体成员变量的地址值,减去结构体开始的地址值,就是这个
成员变量的偏移量。有了这个偏移量,就可以有下面的宏
*/
/*
这个宏,我在这里取名叫get_ydm,其实应该叫container_of,这个宏在linux内核中
经常被用到。
这个宏的作用就是,知道一个结构体中某一个成员变量的地址,我们来获取结构体的地址。
typeof
这个在这里必须解释一下,如果有一个变量,float f; 那么 typeof(f)就代表了 float。
如果有 short sh; 那么typeof(sh)就代表short。这样知道了变量的名字,我们就可以用
typeof来获得变量类型。
typeof(((type*)0)->member_name) *__mptr = (ptr);
这一句就定义了一个指着变量__mptr,指针类型就是我们所知道的结构体成员变量类型指针。
(type*)((char*)__mptr - offsetof(type,member_name))
成员变量地址,减去结构体中,这个成员变量的偏移量就是结构体的地址。
这里说一下,为什么要用({...}),把语句括起来。这是因为()把一个语句块{}变成了
一个表达式,表达式的结果就是{}中最后一句中表达式的值。
*/
int main()
{
int x=MAX(2,3);
/*
在这里,MAX(2,3)会被替换成
((2)>(3)?(a):(b))
看看是不是字符替换。注意,宏里面大量使用小括号,为什么?确保我们宏不被拆开。
*/
char c;
__get(c);
printf("%c\n",c);
__get(c); //有了这个宏,我们就不用担心scanf函数中,回车符号的捣乱了。
printf("%c\n",c);
ydm_t st;
ydm_t *pst;
float *fp = &st.z;
pst=get_ydm(fp,ydm_t,z);
printf("&st = %p pst = %p\n",&st,pst);
printf("%d\n",({int x = 100; x+3;})); //注意这个古怪的写法,一般在宏里面用。
return 0;
}
#,##符号:
#会把#符号后面的东西转换成一个字符串,也就是加上双引号。而##会把符号前面和后面的字符合并,形成一个新的标识或者变量名。举例如下:
/*********冒牌程序员-毛哥 o-string.c***********/
#include<stdio.h>
#define STR(x) (#x) //#会自动给x两边添加引号,使之变成字符串。
#define DEFINE_VARIABLE(TYPE,name,value) TYPE __##name=value;
//##会将__和宏变量name进行合并,形成一个新的变量名字。
int main()
{
char *str = STR(yangdamao); //STR宏将yangdamao变成字符串。
DEFINE_VARIABLE(float,float,10.3); //定义了变量__float
printf("%s = %f\n",str,__float);
return 0;
}
变参宏:
/*********冒牌程序员-毛哥 o-string.c***********/
#include<stdio.h>
#define p(format,...) printf(format,##__VA_ARGS__);
#define pp(format,...) printf(format,__VA_ARGS__);
/*
这个__VA_ARGS__就代表...
而##这俩玩意好像没啥用,可以去掉试试。
*/
int main()
{
p("%s\n","yangdamao");
p("ydm\n");
pp("%s\n","ceshi");
//pp("ceshi\n"); 由于pp宏没有##,所以,只有format参数的pp就会报错。
return 0;
}
__FILE__和__LINE__以及__FUN__:(内置宏)
/*********冒牌程序员-毛哥 o-string.c***********/
#include<stdio.h>
int main()
{
printf("当前的源文件是:%s\n", __FILE__);
printf("当前行号是:%d\n",__LINE__);
printf("当前函数名:%d\n",__func__);
return 0;
}
/*
可以看出来,__FILE__是一个文件名字符串,就是当前源文件名
__LINE__就是一个整数,其值就是当前行号。
*/