精华内容
下载资源
问答
  • 宏定义优缺点

    2020-04-13 19:40:51
    米哈游三面的一个问题,问的很深,从宏定义是什么,干什么用,优缺点,到别的语言为什么没有,层层深入,很好的问题。结果我也很好的挂掉了。所以来讨论一下上述几个问题正确的答案到底是什么。 1.首先什么是宏定义...

      米哈游三面的一个问题,问的很深,从宏定义是什么干什么用优缺点到别的语言为什么没有,层层深入,很好的问题。结果我也很好的挂掉了。所以来讨论一下上述几个问题正确的答案到底是什么。

    1.首先什么是宏定义,在C++中只有#define算是宏定义,其余的#操作都叫预处理,所以宏定义就是#define,#define就是宏定义。

    2.宏定义的应用就是使用#define将指定的标识符用指定的字符串代替。宏定义也常常用来当作变量名字使用。

    #define N int*
    
    N a, b;
    
    // 这里的a是int*类型,但是b是int类型

    3.优点:1.程序修改的方便,对源程序中的部分代码可能存在需要替换的情况,这个时候将需要替换的代码块声明成宏定义,那么修改程序只需要修改宏定义即可,这样代码块中的目标代码块在预处理的过程中展开即可。

                  2.部分情况下增加程序效率。宏定义是替换了代码块,如果不用宏定义这部分代码块可能是使用函数来代替,但是使用成员函数可以代替函数的调用,这样函数调用的开销就省去了。如果这部分代码块比较小,在函数运行中占据了大量开销,这样就能增加运行效率。但是如果这部分代码块非常大,函数调用在其中只占很小一部分开销,这样一来几乎没有增加效率。

      缺点:1.#define预处理阶段对内容直接展开,导致没有代码的检查,如果展开的代码块存在问题,不会被检测到,是一个很大的隐患。不过这个还是要看编译器,C++ std11的编译器倒是会提前展开处理问题。

      2.优先级问题,宏定义后的代码块里的优先级如果没有特别注意到,可能会不是自己想的样子,产生莫名其妙的问题。有两种情况,一是括号产生的,二是变量声明产生的(如上面这个例子,声明的变量前者是指针类型,后者是常数类型)

    展开全文
  • C++宏定义优缺点

    千次阅读 2014-09-01 08:50:11
     #define是C语言中提供的宏定义命令,其主要目的是为程序员在编程时提供一定的方便,并能在一定程度上提高程序的运行效率,但学生在学习时往往不能 理解该命令的本质,总是在此处产生一些困惑,在编程时误用该命令...
    一、#define的基本用法

        #define是C语言中提供的宏定义命令,其主要目的是为程序员在编程时提供一定的方便,并能在一定程度上提高程序的运行效率,但学生在学习时往往不能 理解该命令的本质,总是在此处产生一些困惑,在编程时误用该命令,使得程序的运行与预期的目的不一致,或者在读别人写的程序时,把运行结果理解错误,这对 C语言的学习很不利。

    1 #define命令剖析

    1.1   #define的概念

        #define命令是C语言中的一个宏定义命令,它用来将一个标识符定义为一个字符串,该标识符被称为宏名,被定义的字符串称为替换文本。
    该命令有两种格式:一种是简单的宏定义,另一种是带参数的宏定义。

    (1)简单的宏定义:
    1. #define <宏名>  <字符串>
    2. 例: #define PI 3.1415926
    (2) 带参数的宏定义
     
    1. #define <宏名> (<参数表>) <宏体>
    2. 例: #define A(x) x
        一个标识符被宏定义后,该标识符便是一个宏名。这时,在程序中出现的是宏名,在该程序被编译前,先将宏名用被定义的字符串替换,这称为宏替换,替换后才进行编译, 宏替换是简单的替换

    1.2 宏替换发生的时机

        为了能够真正理解#define的作用,让我们来了解一下对C语言源程序的处理过程。当我们在一个集成的开发环境如Turbo C中将编写好的源程序进行编译时,实际经过了预处理、编译、汇编和连接几个过程。其中预处理器产生编译器的输出,它实现以下的功能:
    (1) 文件包含
        可以把源程序中的#include 扩展为文件正文,即把包含的.h文件找到并展开到#include 所在处。
    (2)条件编译
        预处理器根据#if和#ifdef等编译命令及其后的条件,将源程序中的某部分包含进来或排除在外,通常把排除在外的语句转换成空行。
    (3)宏展开
        预处理器将源程序文件中出现的对宏的引用展开成相应的宏 定义,即本文所说的#define的功能,由预处理器来完成。
        经过预处理器处理的源程序与之前的源程序有所有不同, 在这个阶段所进行的工作只是纯粹的替换与展开,没有任何计算功能,所以在学习#define命令时只要能真正理解这一点,这样才不会对此命令引起误解并误用。

    2 #define使用中的常见问题解析

    2.1 简单宏定义使用中出现的问题

        在简单宏定义的使用中,当替换文本所表示的字符串为一个表达式时,容易引起误解和误用。如下例:
       
    1. 例1 #define N 2+2
    2. void main()
    3. {
    4.    int a=N*N;
    5.    printf(%d”,a);
    6. }
         (1) 出现问题:
     
        在此程序中存在着宏定义命令,宏N代表的字符串是2+2,在程序中有对宏N的使用,一般同学在读该程序时,容易产生的问题是先求解N为2+2=4,然后在程序中计算a时使用乘法,即N*N=4*4=16, 其实该题的结果为8,为什么结果有这么大的偏差?

         (2) 问题解析:
     
        如1节所述, 宏展开是在预处理阶段完成的,这个阶段把替换文本只是看作一个字符串,并不会有任何的计算发生,在展开时是在宏N出现的地方 只是简单地使用串2+2来代替N,并不会增添任何的符号,所以对该程序展开后的结果是a=2+2*2+2,计算后=8,这就是宏替换的实质,如何写程序才能完成结果为16的运算呢?

         (3)解决办法:
     
    1. /*将宏定义写成如下形式*/
    2. #define N (2+2)
    3. /*这样就可替换成(2+2)*(2+2)=16*/

    2.2 带参数的宏定义出现的问题

        在带参数的宏定义的使用中,极易引起误解。例如我们需要做个宏替换能求任何数的平方,这就需要使用参数,以便在程序中用实际参数来替换宏定义中的参数。一般学生容易写成如下形式:
    1. #define area(x) x*x
    2. /*这在使用中是很容易出现问题的,看如下的程序*/

    3. void main()
    4. {
    5.     int y = area(2+2);
    6.     printf(%d”,y);
    7. }
        按理说给的参数是2+2,所得的结果应该为4*4=16,但是错了,因为该程序的实际结果为8,仍然是没能遵循纯粹的简单替换的规则,又是先计算再替换 了, 在这道程序里,2+2即为area宏中的参数,应该由它来替换宏定义中的x,即替换成2+2*2+2=8了。那如果遵循(1)中的解决办法,把2+2 括起来,即把宏体中的x括起来,是否可以呢?#define area(x) (x)*(x),对于area(2+2),替换为(2+2)*(2+2)=16,可以解决, 但是对于area(2+2)/area(2+2)又会怎么样呢,有的学生一看到这道题马上给出结果,因为分子分母一样,又错了,还是忘了遵循先替换再计算的规则了,这道题替换后会变为 (2+2)*(2+2)/(2+2)*(2+2)即4*4/4*4按照乘除运算规则,结果为16/4*4=4*4=16,那应该怎么呢?解决方法是在整个宏体上再加一个括号,即 #define   area(x) ((x)*(x)), 不要觉得这没必要,没有它,是不行的。
        要想能够真正使用好宏定义,那么在读别人的程序时, 一定要记住先将程序中对宏的使用全部替换成它所代表的字符串,不要自作主张地添加任何其他符号,完全展开后再进行相应的计算,就不会写错运行结果。
         如果是自己编程使用宏替换,则在使用简单宏定义时,当字符串中不只一个符号时,加上括号表现出优先级,如果是带参数的宏定义,则要给宏体中的每个参数加上括号,并在整个宏体上再加一个括号。看到这里,不禁要问,用宏定义这么麻烦,这么容易出错,可不可以摒弃它, 那让我们来看一下在C语言中用宏定义的好处吧。
    如:
    1. #include <iostream.h>
    2. #define product(x)    x*x
    3. int main()
    4. {
    5.     int i=3;
    6.     int j,k;
    7.     j = product(i++);
    8.     cout<<"j="<<j<<endl;
    9.     cout<<"i="<<i<<endl;
    10.     k = product(++i);
    11.     cout<<"k="<<k<<endl;
    12.     cout<<"i="<<i<<endl;
    13.     return 0;
    14. }
    依次输出结果:
    j=9;i=5;k=49;i=7


    3 宏定义的优点

    (1)   方便程序的修改

        使用简单宏定义可用宏代替一个在程序中经常使用的常量,这样在将该常量改变时,不用对整个程序进行修改,只修改宏定义的字符串即可,而且当常量比较长时, 我们可以用较短的有意义的标识符来写程序,这样更方便一些。我们所说的常量改变不是在程序运行期间改变,而是在编程期间的修改,举一个大家比较熟悉的例子,圆周率π是在数学上常用的一个值,有时我们会用3.14来表示,有时也会用3.1415926等,这要看计算所需要的精度,如果我们编制的一个程序中 要多次使用它,那么需要确定一个数值,在本次运行中不改变,但也许后来发现程序所表现的精度有变化,需要改变它的值, 这就需要修改程序中所有的相关数值,这会给我们带来一定的不便,但如果使用宏定义,使用一个标识符来代替,则在修改时只修改宏定义即可,还可以减少输入 3.1415926这样长的数值多次的情况,我们可以如此定义 #define   pi   3.1415926,既减少了输入又便于修改,何乐而不为呢?

    (2) 提高程序的运行效率

        使用带参数的宏定义可完成函数调用的功能,又能减少 系统开销,提高运行效率。正如C语言中所讲,函数的使用可以使程序更加模块化,便于组织,而且可重复利用,但在发生函数调用时,需要保留调用函数的现场,以便子 函数执行结束后能返回继续执行,同样在子函数执行完后要恢复调用函数的现场,这都需要一定的时间,如果子函数执行的操作比较多,这种转换时间开销可以忽 略,但如果子函数完成的功能比较少,甚至于只完成一点操作,如一个乘法语句的操作,则这部分转换开销就相对较大了,但使用带参数的宏定义就不会出现这个问 题,因为它是在预处理阶段即进行了宏展开,在执行时不需要转换,即在当地执行。宏定义可完成简单的操作,但复杂的操作还是要由函数调用来完成,而且宏定义所占用的目标代码空间相对较大。所以在使用时要依据具体情况来决定是否使用宏定义。

    4 结语

        本文对C语言中宏定义#define在使用时容易出现的问题进行了解析,并从C源程序处理过程的角度对#define的处理进行了分析,也对它的优点进行 了阐述。只要能够理解宏展开的规则,掌握使用宏定义时,是在预处理阶段对源程序进行替换,只是用对应的字符串替换程序中出现的宏名,这样就可在正确使用的 基础上充分享受使用宏定义带来的方便和效率了

    二、define中的三个特殊符号:#,##,#@

    1. #define Conn(x,y) x##y
    2. #define ToChar(x) #@x
    3. #define ToString(x) #x
    (1)x##y表示什么?表示x连接y,举例说:
    1. int n = Conn(123,456); /* 结果就是n=123456;*/
    2. char* str = Conn("asdf", "adf"); /*结果就是 str = "asdfadf";*/
    (2)再来看 #@x ,其实就是给x加上单引号,结果返回是一个const char。举例说:
    char a = ToChar(1);结果就是a='1';
    做个越界试验char a = ToChar(123);结果就错了;
    但是如果你的参数超过四个字符,编译器就给给你报错了!
    error C2015: too many characters in constant   :P
    (3)最后看看#x,估计你也明白了,他是给x加双引号
    char* str = ToString(123132);就成了str="123132";
    三、常用的一些宏定义

    1 防止一个头文件被重复包含 
    1. #ifndef BODYDEF_H 
    2. #define BODYDEF_H 
    3.  //头文件内容 

    4. #endif
     
    2 得到指定地址上的一个字节或字

    1. #define MEM_B( x ) ( *( (byte *) (x) ) ) 
    2. #define MEM_W( x ) ( *( (word *) (x) ) )
    用法如下:
    1. #include <iostream>
    2. #include <windows.h>

    3. #define MEM_B(x) (*((byte*)(x)))
    4. #define MEM_W(x) (*((WORD*)(x)))

    5. int main()
    6. {
    7.     int bTest = 0x123456;

    8.     byte m = MEM_B((&bTest));/*m=0x56*/
    9.     int n = MEM_W((&bTest));/*n=0x3456*/

    10.     return 0;
    11. }

    3 得到一个field在结构体(struct)中的偏移量

     
    1. #define OFFSETOF( type, field ) ( (size_t) &(( type *) 0)-> field )
         请参考文章:详解写宏定义:得到一个field在结构体(struct type)中的偏移量

    4 得到一个结构体中field所占用的字节数 
    1. #define FSIZ( type, field ) sizeof( ((type *) 0)->field )

    5 得到一个变量的地址(word宽度) 
    1. #define B_PTR( var ) ( (byte *) (void *) &(var) ) 
    2. #define W_PTR( var ) ( (word *) (void *) &(var) )
    6 将一个字母转换为大写

    1. #define UPCASE( c ) ( ((c) >= ''a'' && (c) <= ''z'') ? ((c) - 0x20) : (c) )
    7 判断字符是不是10进值的数字

    1. #define DECCHK( c ) ((c) >= ''0'' && (c) <= ''9'')
    8 判断字符是不是16进值的数字 
    1. #define HEXCHK( c ) ( ((c) >= ''0'' && (c) <= ''9'') ||((c) >= ''A'' && (c) <= ''F'') ||((c) >= ''a'' && (c) <= ''f'') )
    9 防止溢出的一个方法

    1. #define INC_SAT( val ) (val = ((val)+> (val)) ? (val)+: (val))
    10 返回数组元素的个数 
    1. #define ARR_SIZE( a ) ( sizeof( (a) ) / sizeof( (a[0]) ) )
    11 使用一些宏跟踪调试

    ANSI标准说明了五个预定义的宏名。它们是: 
    1. _LINE_ /*(两个下划线),对应%d*/
    2. _FILE_ /*对应%s*/
    3. _DATE_ /*对应%s*/
    4. _TIME_ /*对应%s*/



    宏定义

      宏定义是C提供的三种预处理功能的其中一种,这三种预处理包括:宏定义、文件包含、条件编译。

    1.不带参数的宏定义:

      宏定义又称为宏代换、宏替换,简称“宏”。

        格式:
          #define标识符 字符串
         其中的标识符就是所谓的符号常量,也称为“宏名”。
         预处理( 预编译)工作也叫做宏展开:将宏名替换为字符串。
         掌握"宏"概念的关键是“换”。一切以换为前提、做任何事情之前先要换,准确理解之前就要“换”。
           即在对相关命令或语句的含义和功能作具体分析之前就要换:
      例:
       #define PI  3.1415926
       把程序中出现的PI全部换成3.1415926
       说明:
       (1)宏名一般用大写。
       (2) 使用宏可提高程序的通用性和易读性,减少不一致性,减少输入错误和便于修改。例如:数组大小常用宏定义。
       (3)预处理是在编译之前的处理,而编译工作的任务之一就是语法检查,预处理不做语法检查。
       (4)宏定义末尾不加分号。
       (5)宏定义写在函数的花括号外边,作用域为其后的程序,通常在文件的最开头。
       (6)可以用 #undef命令终止宏定义的作用域。
       (7)宏定义可以嵌套。
       (8)字符串" "中永远不包含宏。
       (9)宏定义不分配内存,变量定义分配内存。
       (10)宏定义不存在类型问题,它的参数也是无类型的。

    2.带参数的宏定义:

      除了一般的字符串替换,还要做参数代换
      格式:
        #define 宏名(参数表) 字符串
        例如:#define S(a,b) a*b
      area=S(3,2);第一步被换为area=a*b; ,第二步被换为area=3*2;
      类似于函数调用,有一个哑实结合的过程:
       (1)实参如果是表达式容易出问题
        #define S(r) r*r
        area=S(a+b);第一步换为area=r*r;第二步被换为area=a+b*a+b;
        正确的宏定义是#define S(r) ((r)*(r))
      (2)宏名和参数的括号间不能有空格。
       (3)宏替换只作替换,不做计算,不做表达式求解。
       (4)函数调用在编译后程序运行时进行,并且分配内存。宏替换在编译前进行,不分配内存。
       (5)宏的哑实结合不存在类型,也没有类型转换。
      (6)函数只有一个返回值,利用宏则可以设法得到多个值。
       (7)宏展开使源程序变长,函数调用不会。
        (8)宏展开不占运行时间,只占编译时间,函数调用占运行时间(分配内存、保留现场、值传递、返回值)。

    展开全文
  • (二)优缺点 优点: 1.提高程序的可读性,方便修改 2.避免函数入栈,出栈的操作,提高运行效率,减少系统开销 3.是由预处理器处理的,通过字符串的替换操作可以完成很多编译器无法实现的功能,如连接字符等 4....

    一、内存结构

    内存大致可以分为四个部分:代码段,静态存储区,堆,栈。

    具体划分如下图所示:

    f33f77c4d28e1ad2c1ed292e1ac7fc2c.png

    栈:在执行函数时,函数内部局部变量的存储单元都可以在栈上创建,函数执行结束后会自动释放内存。栈内存的分配运算内置于处理器的指令中。效率高,但分配的内存容量有限,程序发生错误时,很有可能出现栈溢出。

    堆:又称为动态内存分配区,程序在执行的时候用malloc或new申请指定大小的内存,程序员自己负责在任何时候用free或delete释放内存。若在申请后未释放,在程序结束时有可能会有OS自动回收,或者造成内存泄露,因此,使用时一定要注意。

    静态存储区:包括程序中明确被初始化的全局变量、静态变量、常量以及未被初始化的数据。内存在程序编译时已经分配好,这块内存在整个运行期间一直存在。

    代码区:存放程序代码,代码区是可以共享的(其他的执行程序可以调用它),整个内存中只有一份。

    栈和堆的区别

    1.管理方式不同

    栈由编译器自动管理,堆由程序员控制。

    2.空间大小不同

    栈是向低地址扩展的结构,是连续的内存区域,因此栈顶和栈底是规定好的,容量有限,若用户使用过多的栈空间,会产生溢出。堆是向高地址扩展,不是连续的内存区域,堆获得的空间较大,分配时也比较灵活。

    3.是否产生碎片

    对于堆来说,频繁的malloc/free,new/delete会是空间不连续,造成大量碎片,使得程序效率降低。而栈不存在这样的问题。

    4.增长方向不同

    堆和栈相对生长

    5.分配方式

    堆由程序员通过使用malloc、free、new、delete来进行管理。栈由编译器申请释放,栈的动态内存分配通过alloca函数完成。

    6.分配效率不同

    栈是机器系统提供的数据结构,计算机在底层对栈提供支持。堆是C库函数提供的,堆的效率比栈低得多。

    二、函数的调用过程(栈帧)

    每一次的函数调用都是一个过程,我们将这个过程称为函数的调用过程。在这个过程中,会开辟栈空间用来保存本次函数调用过程中临时变量以及现场保护等工作,这块栈空间我们称之为函数栈帧。栈帧的维护需要两个寄存器也叫帧指针,即ebp和esp,esp中存放栈顶地址,ebp存放栈底地址。esp和ebp一次只能存放一个地址。

    以下是函数调用的详细过程:

    6d97009001a756dd45cb51c0f3ac11c7.png如图所示,

    假设函数a调用了函数b,函数b调用了函数c,函数b有三个参数,从左至右依次为参数1,参数2,参数3。

    1.函数a在调用函数b的时候,首先将函数b的参数以相反的顺序依次压入栈中,即,从最后一个参数开始压栈。

    2.函数a使用call指令调用函数b,并将call指令下的一条指令的地址当做返回地址压入栈中。(汇编call命令的两个功能:1.保存当前指令的下一个指令的地址。2.pc指针跳转到调用函数的入口地址。)

    3.在函数b的栈帧中,首先保存函数a的栈底地址,再将函数a的栈顶地址当做函数b的栈底地址,即图中所示的push ebp和mov ebp,esp这两条指令。

    4.然后,从ebp的位置开始存放函数b的局部变量,将这些变量的地址依次存放在栈中,先定义的先入栈,后定义的后入栈。

    注意:在不同的编译器上函数的调用过程可能会有所不同,但大致思想相同。

    三、宏和函数

    (一)宏和函数的区别:

    1.宏只是做简单的字符串替换,与类型无关;而函数是参数的传递,参数有类型。

    2.宏参数在进行替换时是直接替换,不进行任何计算,函数在调用时会计算形参的值。

    3.宏替换发生在编译之前,而函数调用发生在编译之后。

    4.宏替换不占内存空间,函数在调用时有栈帧,会占用栈上的空间。

    5.使用宏的执行速度更快,函数调用有跳转、现场保护、返回过程等操作,因此有额外的时间开销。

    6.每次使用宏都会进行替换,因此如果有大量的宏或者宏较长,大量的宏替换会使得代码长度大幅度增加;函数只有一个,每次使用都会调用同一个地方的函数,因此代码长度较短。

    7.宏不能递归,函数可以。

    (二)宏的优缺点

    优点:

    1.提高程序的可读性,方便修改

    2.避免函数入栈,出栈的操作,提高运行效率,减少系统开销

    3.宏是由预处理器处理的,通过字符串的替换操作可以完成很多编译器无法实现的功能,如连接字符等

    4.宏与类型无关,更通用

    缺点:

    1.宏不能进行调试(预处理时直接进行文本替换,不检查类型,语法等)

    2.宏与类型无关,不够严谨

    3.宏可能会带来运算符优先级问题

    4.造成代码长度大幅度增加

    5.宏不能递归,函数式宏定义会导致代码执行效率降低

    (三)# 和 ## 的使用

    # :用来将宏变成字符串

    ## :拼接宏的名字或字符串

    #include #define A(a) (#a)

    #define B(b,c) b##c

    int main()

    {

    printf(A(hahaha));

    //这里是将hahaha转换成字符串

    printf("\n");

    printf("%d",B(33,44));

    //这里的33和44是两个int型数字,组合后仍是一个int型数字3344,而不是字符串

    printf("\n");

    printf("%s\n",B("hhhh","aaaa"));

    return 0;

    }

    运行结果:

    hahaha

    3344

    hhhhaaaa

    C语言重要知识点总结(一)–编译链接过程、数据类型、操作符

    C语言重要知识点总结(三)–const、volatile、extern、static、restrict、register关键字解析

    C语言重要知识点总结(四)–结构体、结构体内存对齐、位段、枚举、联合

    以上总结如有不足之处,希望指出,谢谢!

    展开全文
  • 1、宏定义的优点: (1) 方便程序的修改  使用简单宏定义可用宏代替一个在程序中经常使用的常量,这样在将该常量改变时,不用对整个程序进行修改,只修改宏定义的字符串即可,而且当常量比较长时,我们可以用较短...

    1、宏定义的优点:
    (1)   方便程序的修改
          使用简单宏定义可用宏代替一个在程序中经常使用的常量,这样在将该常量改变时,不用对整个程序进行修改,只修改宏定义的字符串即可,而且当常量比较长时,我们可以用较短的有意义的标识符来写程序,这样更方便一些。
    (2) 提高程序的运行效率
          使用带参数的宏定义可完成函数调用的功能,又能减少系统开销,提高运行效率。正如C语言中所讲,函数的使用可以使程序更加模块化,便于组织,而且可重复利用,但在发生函数调用时,需要保留调用函数的现场,以便子函数执行结束后能返回继续执行,同样在子函数执行完后要恢复调用函数的现场,这都需要一定的时间,如果子函数执行的操作比较多,这种转换时间开销可以忽略,但如果子函数完成的功能比较少,甚至于只完成一点操作,如一个乘法语句的操作,则这部分转换开销就相对较大了,但使用带参数的宏定义就不会出现这个问题,因为它是在预处理阶段即进行了宏展开,在执行时不需要转换,即在当地执行。宏定义可完成简单的操作,但复杂的操作还是要由函数调用来完成,而且宏定义所占用的目标代码空间相对较大。所以在使用时要依据具体情况来决定是否使用宏定义。
    2、宏定义的缺点:

    宏定义有一些缺点:
    (1) 无法对宏定义中的变量进行类型检查
    此缺点,是相对于const变量来说的
    [define与const的区别的简单总结]
    define定义的变量,是Compile-Time时期的变量,系统在编译时候,就将其全部替换,而不会对其变量进行类型等属性检查,相对不是很安全,可能存在潜在的问题,而没有发现.
    正因为其仅仅是编译时期替换,所以其定义的变量,是不会在运行时候分配内存的,不占用内存空间.
    const定义的变量,是 Run-Time时期的变量,如果类型不匹配,系统在运行时候,就会发现并提示或报错,对应的,const变量在运行时期,也是一种变量,系统会为其分配内存.
     
    (2) 边界效应
    A.    未加括号带来的边界效应
    由于宏定义的时候,其各个分量未加括号,而在使用宏定义的时候,传递的参数是变量的表达式,然后经过系统展开后,由于优先级的原因,导致其结果不是你所希望的.
    [例子]
    #define MUL(A,B) A*B
    而在使用的时候,这样的调用:
    int a=1,b=2,c=3,d=0;
    d=MUL(a+b,c)
    经过编译时候展开,就变成了
    d=a+b*c
    而不是我们所希望的
    d=(a+b)*c
    [解决办法]
    其解决办法也很简单,就是给每个分量,都加上括号,就可以避免此类问题
    即,在宏定义的时候,如此定义:
    #define MUL(A,B) ((A)*(B))
     
    B.    在define数据类型的时候, 未加括号带来的问题
    在用define进行新的数据类型定义的时候,由于未加括号,会出现你所未预料到的结果.
    此点其实就是上面说的边界效应,之所以将此点单独说一下,是由于此点不是普通计算结果的问题,而是数据类型的问题,问题相对更严重.
    也是笔者在看《想成为嵌入式程序员应知道的0×10个基本问题》的时候,看到其作者提到的这个问题,此处就用其例子解释如下:
    [例子]
    #define dPS struct s *   //注意末尾无分号
    当使用的时候,遇到:
    dPS p1,p2;
    的时候,经过编译时候替换扩展,就变成了
    struct s* p1,p2;
    而p2就不是我们所希望的s的指针类型了,而是一个普通的s数据结构类型的了.产生了边界效应.
    [解决办法]
    对应的解决办法也很简单,就是,遇到此类新数据类型定义的时候,还是用typedef
    将上述宏定义改为:
    typedef struct s * tPS; // 注意末尾有分号
    而后的使用:
    tPS p1,p2;
    就正常了.
     
    C.   特殊情况时候,加了括号也无法避免错误
    在宏定义中出现++或—之类的操作符的时候,即使加括号,也无法避免其中的问题
    [例子]
    #define MIN(A,B) ((A)<(B)?(A):(B))
    如果进行如此调用
    int a=1,b=3,min=0;
    min=MIN(a++,b);
     经过编译替换展开后,就成了
    max=((a++)< (b)?(a++):(b))
    计算出来的结果,就是
    min=3,而不是我们所要的min=1了.
     
    此类问题无法避免,除非程序员在调用宏的时候,自己多加注意,尽量避免此类调用.

    ***函数调用和宏函数的区别:

           函数和宏函数的区别就在于,宏函数占用了大量的空间(以空间换时间),而函数占用了时间。大家要知道的是,函数调用是要使用系统的栈来保存数据的,如果编译器里有栈检查选项,一般在函数的头会嵌入一些汇编语句对当前栈进行检查;同时,CPU也要在函数调用时保存和恢复当前的现场,进行压栈和弹栈操作,所以,函数调用需要一些CPU时间。而宏函数不存在这个问
    题(预处理阶段已经完成宏替换)。宏函数仅仅作为预先写好的代码嵌入到当前程序,不会产生函数调用,所以仅仅是占用了空间,在频繁调用同一个宏函数的时候,该现象尤其突出。 

    展开全文
  • *宏定义缺点* : 由于是直接嵌入的,所以代码可能相对多一点 嵌套定义过多可能会影响程序的可读性,而且很容易出错; 对带参的宏而言,由于是直接替换,并不会检查参数是否合法,存在安全隐患。 补充:...
  • 优缺点

    2012-12-18 22:51:09
    2. 提高程序的运行效率:使用带参的宏定义既可完成函数调用的功能,又能避免函数的出栈与入栈操作,减少系统开销,提高运行效率; 3.宏是由预处理器处理的,通过字符串操作可以完成很多编译器无法实现的功能。比如#...
  • 缺点:在预处理阶段进行替换,不会进行类型检测,安全性低(如果写错因为在预处理阶段不会在文件定义宏处报错,而会在使用处报错); 建议:尽量使用const修饰的常量替换常量 函数: 优点: 1.不是函数,少了...
  • C\C++优缺点

    2019-04-15 02:33:33
    定义宏(define macro) 声明方式:#define name( parament-list ) stuff 其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中,参数列表的左括号必须与name紧邻。如果两者之间有任何空白存在...
  • 8.缺点: 9.define与const的区别的简单总结: 1.格式: 每个#define行由三部分组成:第一部分是指令 #define 自身,“#”表示这是一条预处理命令,“define”为命令。 第二部分为,一般为缩略语,...
  • 宏的定义,宏与函数的区别以及优缺点比较 1.宏定义 c程序提供的预处理功能之一。包括带参数的宏定义和不带参数的宏定义。具体是指用一个指定的标志符来进行简单的字符串替换或者进行阐述替换。 宏书写形式: #define...
  • 函数优缺点

    千次阅读 2009-11-09 11:25:00
    老的C语言程序员中有一种倾向,就是把很短的执行频繁的计算写成,而不是定义为函数。完成I / O的g e t c h a r,做字符测试的i s d i g i t都是得到官方认可的例子。人们这样做最根本的理由就是执行效率:可以...
  • 函数重载,引用,优缺点 1.函数重载 1.1重载的条件 1.2为什么C++可以进行函数重载 1.3函数重载的缺点 2.引用 2.1引用的概念 2.2引用的特性 2.3传值,传引用,传地址的区别 2.4指针和引用 3.优缺点 3.1常量...
  • #define宏定义的优点和缺点

    万次阅读 2014-03-31 15:54:40
    1、宏定义的优点: (1) 方便程序的修改  使用简单宏定义可用宏代替一个在程序中经常使用的常量,这样在将该常量改变时,不用对整个程序进行修改,只修改宏定义的字符串即可,而且当常量比较长时,我们可以用较短...
  • C/C++——浅谈函数应用优缺点

    千次阅读 2013-08-26 22:16:29
    老的C语言程序员中有一种倾向,就是把很短的执行频繁的计算写成,而不是定义为函数。完成I / O的g e t c h a r,做字符测试的i s d i g i t都是得到官方认可的例子。人们这样做最根本的理由就是执行效率:可以...
  • 尽管函数式宏定义和普通函数相比有很多缺点,但只要小心使用还是会显著提高代码的执行效率,毕竟省去了分配和释放栈帧、传参、传返回值等一系列工作,因此那些简短并且被频繁调用的函数经常用函数式宏定义来代替实现...
  • 缺省参数是声明或定义函数时为函数的参数指定一个默认值。在调用该函数时,如果没有指定实参则采用该默认值,否则使用指定的实参。 void TestFunc(int a = 0) { cout<<a<<endl; } int main() { ...
  • 宏定义

    2019-09-24 09:35:11
    宏定义 #define命令是C语言中的一个宏定义命令,它用来将一个标识符定义为一个字符串,该标识符被称为宏名,被定义的字符串称为替换文本。 该命令有两种格式:一种是简单的宏定义,另一种是带参数的宏定义。 简单的...
  • C++中宏定义优缺点

    2018-07-30 10:42:22
    先来看一看宏定义: define 标识符 字符串 1#define命令属于“预处理命令”中的一种。它是由C++统一规定的,但非C++语言本身的组成部分,由于编译器无法识别他们,不能对其直接进行编译。预处理过程必须在对程序...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 11,122
精华内容 4,448
关键字:

宏定义的优缺点