zoukankan      html  css  js  c++  java
  • 宏——基础

    编译4个过程:预处理,编译,汇编,连接。宏就是在预处理阶段发挥作用。

    宏结尾没有;,因为凡是;结尾的东西,都是由第二阶段“编译”来处理的(a.i->a.s),而所有预编译的代码都是在预编译阶段处理的,为了以示区分,所以所有预编译的代码都不需要分号结尾。

    宏有两种,一种是有宏体宏,另一种是无宏体宏。

    无宏体宏

    什么是无宏体宏

    只有宏名、没有宏体。

    定义形式

    #define 宏名 

    举例

    #define X86

    预编译完后,由于这个宏没有宏体,所以宏直接替换为空,空就是啥也没有。

    有宏体宏

    有宏体宏分为两种,一种是无参宏,另一种是有参宏。

    无参宏

    定义形式

    #define 宏名 宏体

    举例

    #define YES 1
    #define NO 0
    #define PI 3.1415926
    #define OUT printf(“Hello,World”);
    View Code

    预处理后,宏名会被替换为宏体。

    带参宏

    定义形式

    #define 宏名(参数表) 宏体

    举例

    #define  S(a,b)  a*b*10
                            
    int main(void)
    {
        int va;                    
        va = S(3,2); //3对应a,2对应b                    
        printf("va = %d
    ", va);                    
        return 0;
    }
    View Code

    预编译处理时,将宏体中的a和b,使用参数中的3和2来替换。va = S(a, b) —> va = 3*2*10

     带参宏需要注意之处

    ①宏名和参数列表之间不能有空格

    #define  S  (a,b)  a*b*10  
    View Code

    由于S和(a, b)之间有空格,宏名变为了S,宏体变为了(a,b) a*b*10,含义发生了变化。

    ②写带参宏的时,不要吝啬括号

    #define  S(a,b)  a*b*10
    View Code

    其实这个带参宏是有缺点的,如果参数写成如下形式的话,替换后结果可能就完全背离了你的本意。

    S(x+1, y+2) —> x+1*y+2*10

    对于预编译器来说,它再处理宏定义时,它并不知道你的使用意图是什么,它只会忠实的进行替换工作,但是替换之后是否能够达到你要的效果,这个就不一定了。怎么解决?

    为了避免这种情况,大家在定义带参宏时不要吝啬括号。

    #define  S(a,b)  ((a)*(b)*10)   //为了保险起见,对整个宏体最好也加一个()。
    View Code

    S(x+1, y+2) ——> ((x+1)*(y+2)*10)

    带参宏 与 函数

    这两个玩意儿长得很像,但实际上是两个完全不同的东西。

    例子

    #include <stdio.h>
    #define  S(a,b)  a*b*10
    void s(int a, int b)
    {
        return a*b*10;
    }
                                
    int main(void)
    {
        int va1, va2;
        va1 = S(3, 2); //引用带参宏
        va2 = s(3, 2); //调用函数
        printf("va1 = %d, va2 = %d
    ", va1, va2);
        return 0;
    }
    View Code

    仅仅从调用来看,这两个东西确实长得很像,如果将宏也定义为小写的话,仅看调用的话,很难看出这个到底谁是函数谁是宏定义。为了能够让大家快速的区分带参宏和函数,大家在定义宏的时候,宏名一定要大写,否则在阅读代码时,很容易与函数搞混,非常不利于代码的阅读和理解。

    二者的区别

    二者是有着本质区别的:

    带参宏

    处理阶段:预编译

    宏只是一个供我们程序员识别的一个符号,一旦预编译之后带参宏就会消失了,取而代之的是宏体。

    参数列表

    带参宏的形参是没有类型的,我们使用int 、float等类型只有一个目的,就是使用类型来开辟一个变量空间,变量空间的字节数和存储格式是由类型来决定的,所以定义变量时必须要有类型说明。而带参宏的参数仅仅只起到替换说明的作用,不需要开辟空间来存放实参的值,既然不需要开辟空间,那就不需要类型的说明。

    函数

    处理阶段:由编译、汇编、链接阶段处理

    在“预处理阶段”是不会处理函数这个东西的,在预处理前后,函数没有任何变化。

    函数是一个独立体,有调用的过程

    运行函数时涉及调用的过程:

    调用时:从当前函数跳转到被调用的函数,开辟形参和自动局部变量时,涉及压栈操作。

    调用结束:返回到调用函数,释放函数的形参和自动局部变量的空间时,涉及弹栈操作

    函数的参数列表

    函数的形参是需要被开辟空间的,所以必须要要有类型说明。

    宏的一些值得强调的地方

    预处理完之后,宏定义和宏引用都会消失

    #define NUM  100  //宏定义,预处理后消失
    int main
    {
        int a;            
        a = NUM;  //宏引用,预处理后被替换为宏体,宏引用消失
        return 0;
    }
    View Code

    宏名的字母一般习惯大写,以便与变量名、函数名相区别

    如果宏名小写的话,会很容易和正常的变量和函数混淆。

    疑问:难道真的没有小写的宏吗?

    其实也不是,在少数某些特殊情况下,还真有定义为小写的,但是这种情况比较少见。标准IO函数,有三个宏(stdio.h):

    stdin:标准输入(从键盘输入数据)

    stdout:标准输出

    stderr:标注出错输出

    这三个宏其实就是小写的,之所以写成小写,应该是历史遗留问题。

    所有预编译的代码都是独占一行的(不能多行)

    #define STUDENT struct student{int a; int b;};

    为了独占一行,我把结构体写在了一行中,但是这样子不方便理解,我们往往会把它改成如下形式

    #define STUDENT struct student{
        int a; 
        int b;
    };
    View Code

    加了(连行符)后,其实这几行在同一行中。

    宏的作用域 与 #undef

    正常情况下的宏作用域为从定义为位置开始,一直到文件的末尾。如果你希望结束宏的作用域的话,可以使用#undef这个预编译关键字。

    #define NUM 100 
    int fun();
                    
    int main(void)
    {
        int a = NUM;
        return 0;
    }
                    
    #undef NUM
                    
    int fun()
    {
        int a = NUM;//这里将无法使用这个宏
    }
    View Code

    定义宏时可以嵌套引用其它的宏,但是不能嵌套引用自己

    嵌套其它宏

    #define   WIDTH       80
    #define   LENGTH       (WIDTH)+40
    #define   AREA        WIDTH*(LENGTH)
                        
    int main(void)
    {
        int a = AREA;
        return 0;
    }
    View Code

    这种形式很显然是正确的。如下写法也是正确的

    #define   AREA    WIDTH*(LENGTH)
    #define   WIDTH       80
    #define   LENGTH       (WIDTH)+40
                        
    int main(void)
    {
        int a = AREA;  //WIDTH*(LENGTH) —>80*(LENGTH) —>>80*40
        return 0;
    }
    View Code

    这个写法是正确的,只要宏引用的位置在定义位置的作用域范围内就行。显然AREA的引用都在AREA、WIDTH、LENGTH作用域内,所以AREA的引用在替换时,完全不存在任何问题。如下代码AREA的引用不再LENGTH作用域内,预处理没问题,但是编译时回报未定义符号

    #define   AREA     WIDTH*(LENGTH)
    #define   WIDTH       80
    
    int main(void)
    {
        int a = AREA;  WIDTH*(LENGTH) —>80*(LENGTH) —>>80*40
        return 0;
    }
    #define   LENGTH       (WIDTH)+40
    View Code

    为什么不能嵌套自己

    #define  AREA      AREA*10
    int main(void)
    {
        int a = AREA;
        return 0;
    }
    View Code

    嵌套自己时在预编译器做完替换后,最后还剩一个宏名,这个宏名无法再被替换,最后留给第二阶段编译时,将变成一个无法识别的符号,从而报错。所以宏不能嵌套自己,这个和函数不一样,函数嵌套调用自己是递归,宏嵌套引用自己就是犯错。

    只作字符替换,不做正确性检查

    预编译器处理宏时,预编译器只关心替换的事情,至于替换的宏体的写法是否正确,预编译器本身并不做检查,因为判断写法是否正确的这件事情是由第二阶段的编译来做的。

    #define NUM 100WEE
    int main(void)
    {        
        int a = NUM;
        return 0;
    }    
    View Code

    整形数100WEE的写法完全是错的,但是在预编译时根本不会提示任何错误,预编译器会忠实的将NUM换为100WEE,但是后续编译时就会报无法识别100WEE的错误。

    预定义宏

    什么是预定义宏

    预定义宏,也可以称为编译器内置宏,这个宏并没有定义在哪个.h文件中,所以不能再哪个.h中找到这些玩意。进行预编译时,当预编译器看到这些玩意时,会自动处理这些预定义宏。其实将这些预定义宏称为预编译关键字,可能更好些。

    作用

    __DATE__:代表预处理的日期

    当预处理器检测到__DATE__后,会将其替换为"月 日 年"的字符串形式的时间,时间格式是西方人习惯的格式。

    __FILE__:代表当前预编译正在处理的那个源文件的文件名

    当预处理器检测到__FILE__后,会将其替换为"***.c"的文件名。

    __LINE__:代表__LINE__当前所在行的行号

    当预处理器检测到__LINE__后,会将其替换为__LINE__当前所在行的行号(整形)。

    __TIME__:代表对源文件进行预编译时的时间

    当预处理器检测到__TIME__后,会将其替换为“hh:mm:ss”格式的时间。

    __func__:当前__func__所在函数的函数名 

    不过这个在预编译阶段不会被处理,而是留到编译阶段处理。

    预定义宏的意义 与 调试宏

    意义

    常常用于调试打印、跟踪代码用。当一个程序写大了后,在调试程序的功能性错误时,往往需要打印信息来跟踪代码,看看程序是运行到什么位置时才出现了功能性错误,以方便我们调试。

    printf("%s %d %s
    ", __FILE__, __LINE__, __func__);
    View Code

    调试宏

    在每个需要打印的地方都写printf会非常的麻烦,因此我们可以把它写成调试宏。

    #include <stdio.h>
                                
    //调试宏,DEBUG的名字可以自己随便起
    #define DEBUG printf("%s %d %s
    ", __FILE__, __LINE__, __func__);
                                
    void exchange(int *p1, int *p2)
    {      
        DEBUG
        int tmp = 0;
        DEBUG                        
        tmp = *p1;
        DEBUG
        *p1 = *p2;
        DEBUG
        *p2 = tmp;
        DEBUG
    }
                                                                                
    int main(void)
    {       
        int a = 10; 
        int b = 30; 
                                    
        DEBUG                            
        exchange(&a, &b);
        DEBUG
                                    
        printf("a=%d, b=%d
    ", a, b);
        DEBUG
        return 0;
    }
    View Code

    通过打印信息来跟踪程序,其实有些时候比“单步运行调试”更好用,因为单步运行调试在某些情况其实很麻烦,不如打印信息来的好使。

    如果你想打印自定义信息的话,我们还可以将调试宏定义为带参宏

    #define DEBUG(s1, s2) printf(s1, s2);

    疑问:感觉这么写,也不比直接写printf("%d ", va)方便多少呀?

    不直接使用printf,而是写成DEBUG(s1, s2)带参宏的形式,可以方便我们使用“条件编译”来快速打开和关闭调试宏,后面将再介绍这个问题。

  • 相关阅读:
    Roads in the Kingdom CodeForces
    Vasya and Shifts CodeForces
    SOS dp
    Singer House CodeForces
    Codeforces Round #419 (Div. 1) (ABCD)
    在踏踏实实的生活里让自己坚持去做梦
    Replace Delegation with Inheritance
    Replace Inheritance with Delegation
    Replace Parameter with Methods
    Preserve Whole Object
  • 原文地址:https://www.cnblogs.com/kelamoyujuzhen/p/9417469.html
Copyright © 2011-2022 走看看