冒牌程序员-毛哥 C语言入门教程(第四章:程序流 第六节 宏)

阅读 23

02-15 21:00

第四章:程序流

第六节:宏

宏简单的说,就是用一段文字替代另一段文字。有人说,这不是吃饱撑的吗?不是的,你学了以后,就知道这个东西多有用了。最直观的,如果有一大段文字,总是在我们程序中使用,那么我们每次使用是不是就要把这段文字重新再写一遍,是不是很烦,那么我们给这一段文字取个名字,只要我们把这个名字写在我们的程序中,编译器在翻译我们程序的时候,自动将这个名字替换成对应的文字,是不是很爽的感觉。其实好处还不止如此,如果我们要对这段文字修改呢?是不是修改一处就可以了。如果我们每个地方不适用名字,而是直接使用文字,要修改的话,很容易漏掉一个或者几个地方,是不是就出错了。


宏的关键字#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__就是一个整数,其值就是当前行号。
*/








精彩评论(0)

0 0 举报