C 语言中的宏定义

  • #define 是预处理器处理的单元实体之一
  • #define 宏定义可以出现在程序的任意为止
  • #define 定义之后的代码都可以使用这个宏(无作用域的概念)

定义常量宏

  • #define 定义的宏常量可以直接使用
  • #define 定义的宏常量本质为字面量【不占用内存】

实例分析: 宏表达式分析

Test.c
#define ERROR -1
#define PATH1 "D:\test\test.c"
#define PATH2 D:\test\test.c
#define PATH3 D:\test\        // \ 成为接续符
test.c

int main()
{
    int err = ERROR;
    char* p1 = PATH1;
    char* p2 = PATH2;
    char* p3 = PATH3;
}
Test.i
# 1 "test.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "test.c"

int main()
{
 int err = -1;
 char* p1 = "D:\test\test.c";
 char* p2 = D:\test\test.c;
 char* p3 = D:\testtest.c;
}
分析: 预处理不会报错,只是进行的宏文本替换,编译阶段报错

宏定义表达式

  • #define 表达式的使用类似函数调用
  • #define 表达式比函数更强大
  • #define 表达式比函数更容易出错,同时增加调试难度

实例分析: 宏表达式分析

#include <stdio.h>

#define _SUM(a, b) (a) + (b)
#define _MIN(a, b) ((a) < (b) ? (a) : (b))
#define _DIM(a) sizeof(a)/sizeof(*a)

int main()
{
    int a = 1;
    int b = 2;
    int c[4] = {0};
    
    int s1 = _SUM(a, b);
    int s2 = _SUM(a, b) * _SUM(a, b);
    int m = _MIN(a++, b);
    int d = _DIM(c);
    
    printf("s1 = %d\n", s1);
    printf("s2 = %d\n", s2);
    printf("m = %d\n", m);
    printf("d = %d\n", d);
}
输出:【部分输出不是我们期望的】
s1 = 3
s2 = 5  【非期望输出】
m = 2   【非期望输出】
d = 4   【宏表达式的强大】

分析:
 int s1 = (a) + (b);
 int s2 = (a) + (b) * (a) + (b);        // 预处理后语义发生改变
 int m = ((a++) < (b) ? (a++) : (b));   // 预处理后语义发生改变
 int d = sizeof(c)/sizeof(*c);

在使用宏时,多使用 () 是好的习惯

#define _SUM(a, b) (a) + (b)

_SUM(a, b) * _SUM(a, b) ==> ((a) + (b)) * ((a) + (b))   

宏表达式与函数的对比

  • 宏表达式被预处理器处理,编译器不知道宏表达式的存在【因此没有作用域的概念】
  • 宏表达式用 “实参” 完全替代形参,不进行任何运算
  • 宏表达式没有任何的“调用”开销
  • 宏表达式中不能出现递归定义
#define _SUM_(n)((n>0) ? (_SUM_(n-1)+n) : 0)

.c ==> .i

((10>0) ? (_SUM_(10 -1)+10) : 0);

宏定义的常量或表达式是否有作用域限制?

实例分析: 宏的作用域分析

#include <stdio.h>

void def()
{
    #define PI 3.1415926
    #define AREA(r) r * r * PI
}

double area(double r)
{
    return AREA(r);
}

int main()
{
    double r = area(5);
    
    printf("PI = %f\n", PI);
    printf("d = 5; a = %f\n", r);
    
    return 0;
}
输出:
PI = 3.141593
d = 5; a = 78.539815

分析:
定义宏之后的代码可以直接使用当前宏。
宏编译器被预处理器处理,编译器不知道宏标识符的存在,因此编译器无法将作用域的概念用于标识符。

强大的内置宏

含义 示例
_FILE_ 被编译的文件名 file1.c
_LINE_ 当前行号 25
_DATE_ 编译时的日期 Jan 31 2012
_TIME_ 编译时的时间 17:01:01
_STDC_ 编译器是否遵循标准C规范 1

示例分析: 宏使用综合示例

#include <stdio.h>
#include <malloc.h>

#define MALLOC(type, x) (type*)malloc(sizeof(type)*x)    // 宏的强大

#define FREE(p) (free(p), p=NULL)                        // 宏的强大

#define LOG(s) printf("[%s] {%s:%d} %s \n", __DATE__, __FILE__, __LINE__, s)    // 宏的强大

#define FOREACH(i, m) for(i=0; i<m; i++)
#define BEGIN {
#define END   }

int main()
{
    int x = 0;
    int* p = MALLOC(int, 5);
    
    LOG("Begin to run main code ...");
    
    FOREACH(x, 5)
    BEGIN
        p[x] = x;
    END
    
    FOREACH(x, 5)
    BEGIN
        printf("%d \n", p[x]);
    END
    
    FREE(p);
    
    LOG("END");
}
输出:
[Dec  3 2018] {test.c:19} Begin to run main code ... 
0 
1 
2 
3 
4 
[Dec  3 2018] {test.c:33} END 

小结

  • 预处理器直接对宏进行文本替换
  • 宏使用时的参数不会进行求值和运算
  • 预处理器不会对宏定义进行语法检查
  • 宏定义时出现的语法错误只能被编译器检测
  • 宏定义的效率高于函数调用
  • 宏的使用会带来一定的副作用
  • 如果使用得当,宏会是很好的助手。多加 () 是个好习惯

以上内容参考狄泰软件学院系列课程,请大家保护原创!


TianSong
734 声望138 粉丝

阿里山神木的种子在3000年前已经埋下,今天不过是看到当年注定的结果,为了未来的自己,今天就埋下一颗好种子吧