C语言--预处理详解(2)

简介: 【10月更文挑战第3天】

【10月更文挑战第3天】

5.宏替代的规则

在程序中扩展#define定义符号和宏时,需要涉及⼏个步骤。

  1. 在调⽤宏时,⾸先对参数进⾏检查,看看是否包含任何由#define定义的符号。如果是,它们⾸先被替换。

  2. 替换⽂本随后被插⼊到程序中原来⽂本的位置。对于宏,参数名被他们的值所替换。

  3. 最后,再次对结果⽂件进⾏扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。

注意:

  1. 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。

  2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索

宏是不能出现递归的,不能在宏内自己调用自己

#define M 15
#define max(x,y) ((x)>(y)?(x):(y))
int main()
{
    int m = MAX(M, 15);
    //根据替换规则,这里的M是宏定义的符号,所以M先被替换
    // 
    // 替换过程,首先对参数进行检查,再对本身进行检查
    //int m = MAX(100, 15);
    //int m = ((100)>(15)?(100):(15))
    printf("%d", m);
    printf("MAX(M,15)");//字符串内的宏并不会进行运算
    return 0;
}

6.宏函数的对比

//宏
#define max(x,y) ((x)>(y)?(x):(y))

//函数
int Max(int x, int y)
{
    return x > y ? x : y;
}
int main()
{
    int m = MAX(10, 20);
    printf("%d\n", m);


    m = Max(10, 20);
    return 0;
}
//函数是有函数体的,函数具有数据类型,但是宏没有

宏通常被应⽤于执⾏简单的运算。⽐如在两个数中找出较⼤的⼀个时,写成下⾯的宏,更有优势⼀些。

那为什么不⽤函数来完成这个任务?

原因有⼆:

  1. ⽤于调⽤函数和从函数返回的代码可能⽐实际执⾏这个⼩型计算⼯作所需要的时间更多。所以宏⽐函数在程序的规模和速度⽅⾯更胜⼀筹。

  2. 更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使⽤。反之这个宏怎可以适⽤于整形、⻓整型、浮点型等可以⽤于 > 来⽐较的类型。宏的参数是类型⽆的。

和函数相⽐宏的劣势:

  1. 每次使⽤宏的时候,⼀份宏定义的代码将插⼊到程序中。除⾮宏⽐较短,否则可能⼤幅度增加程序的⻓度。

  2. 宏是没法调试的。

  3. 宏由于类型⽆关,也就不够严谨。

  4. 宏可能会带来运算符优先级的问题,导致程容易出现错。

宏的参数是直接带进去不进行计算的

但是函数的参数是计算好之后再带进去的

宏有时候可以做函数做不到的事情。⽐如:宏的参数可以出现类型,但是函数做不到

#define MALLOC(n,type) (type*)malloc(n*sizeof(type))
int main()
{
    //开辟10个整型字节大小的空间
    //int *p=(int *)malloc(10 * sizeof(int));

    int *p=MALLOC(10, int);//函数是不能传类型的,那么我们写一个宏

    return 0;
}

宏和函数的一个对比

属性 #define定义宏 函数
代码长度 每次使用时,宏代码都会被插入到程序中。除了非常小的宏之外,程序的长度会大幅度增长 函数代码只出现于一个地方;每次使用这个函数时,都调用那个地方的同一份代码
执行速度 更快 存在函数的调用和返回的额外开销,所以相对慢一些
操作符优先级 宏参数的求值是在所有周围表达式的上下文环境里,除非加上括号,否则邻近操作符的优先级可能会产生不可预料的后果,所以建议宏在书写的时候多写括号 函数参数只在函数调用的时候求值一次,它的结果值传递给函数。表达式的求值结果更容易预测
带有副作用的参数 参数可能被替换到宏体中的多个位置,如果宏的参数被多次计算,带有副作用的参数求值可能会产生不可预料的结果 函数参数只在传参的时候求值一次,结果更容易控制
参数类型 宏的参数与类型无关,只要对参数的操作是合法的,他就可以使用任何参数类型 函数的参数是与类型有关的,如果参数的类型不同,就需要不同的函数,即使 他们执行的任务是不同的
调试 宏是不方便调试的 函数是可以逐语句进行调试的
递归 宏是不能递归的 函数是可以递归的

7.#和

#运算符

运算符将宏的⼀个参数转换为字符串字⾯量。它仅允许出现在带参数的宏的替换列表中。 #运算符所执⾏的操作可以理解为”字符串化“

//#define PRINT(format,n)   printf("the value of n is "format"\n",n)
//int main()
//{
//    int a = 10;
//    //创建一个宏
//    PRINT("%d",a);
//    //printf("the value of a is %d\n", a);
//    
//    int b = 20;
//    PRINT("%d", b);
//    //printf("the value of b is %d\n", b);
//    
//    float f = 5.5f;
//    PRINT("%f", f);
//    //printf("the value of f is %d\n", f);
//    return 0;
//}
//写一个宏,既能适用于浮点型又能适用于整型
//#define PRINT(format,n) printf("the value of n is "fromat,n)
/*
这个宏的参数是打印的数据类型和要打印的数--format和n
因为在打印字符串的时候
printf("helloworld");
printf("hello""world");
这两种打印结果是一样的,都是helloworld


那么我们这里就写
printf("the value of n is "fromat,n)
因为我们传的format是"%d"
那么和前面的代码进行组合的话得到的就是
printf("the value of n is ""%d",n)
无异于
printf("the value of n is %d",n)

我们在后面加上"\n"
就是相当于三个字符串进行相加
"the value of n is "
fromat
"\n"
就是这三个字符串
*/
/*
the value of n is 10
the value of n is 20
the value of n is 5.500000
但是还没有达到我们要的结果
我们想将n改变成我们要打印的字母

那么我们应该怎么做呢?
*/

#define PRINT(format,n)   printf("the value of "  #n  " is "format"\n",n)
int main()
{
    int a = 10;
    //创建一个宏
    PRINT("%d", a);
    //printf("the value of a is %d\n", a);

    int b = 20;
    PRINT("%d", b);
    //printf("the value of b is %d\n", b);

    float f = 5.5f;
    PRINT("%f", f);
    //printf("the value of f is %d\n", f);
    return 0;
}

/*
对宏进行修改
printf("the value of" #n "is "format"\n",n)
"the value of"这个是一个字符串
#n  当我们传过来的是n是a的话,那么#a就变成了"a",就间接的变成字符串了

那么替换后的代码就是这样的
printf("the value of" "a" "is ""%d""\n",n)

那么#n的作用就是将n变为对应的字符串
*/

/*
打印结果:
the value of a is 10
the value of b is 20
the value of f is 5.500000
就是我们所想要的结果

*/

对于宏的参数内的元素n,出现#n的地方就会替换为“n”,将n变成字符串

##运算符

可以把位于它两边的符号合成⼀个符号,它允许宏定义从分离的⽂本⽚段创建标识符。 ## 被称为记号粘合这样的连接必须产⽣⼀个合法的标识符。否则其结果就是未定义的。

这⾥我们想想,写⼀个函数求2个数的较⼤值的时候,不同的数据类型就得写不同的函数。

//这个宏是生成函数的模版
#define FUNC(type)\
type type##_max(type x,type y)\
{\
    return x>y?x:y;\
}
//使用模版调用函数,函数名字叫int_max
FUNC(int)
/*
替换步骤:
##就是将左右两边的int 和_max合成一个符号
那么这里的int_max就是函数名字
int int_max(int x,int  y)\
{\
    return x>y?x:y;\
}
*/
//使用模版调用函数,函数名字叫float_max
FUNC(float)
int main()
{
    printf("%d\n", int_max(3, 5));
    return 0;
}

8.命名约定

⼀般来讲函数的宏的使⽤语法很相似。所以语⾔本⾝没法帮我们区分⼆者。那我们平时的⼀个习惯是:

把宏名全部⼤写

函数名不要全部⼤写

相关文章
|
2月前
|
编译器 C语言
C语言--预处理详解(1)
【10月更文挑战第3天】
|
2月前
|
编译器 Linux C语言
C语言--预处理详解(3)
【10月更文挑战第3天】
|
2月前
|
自然语言处理 编译器 Linux
【C语言篇】编译和链接以及预处理介绍(上篇)1
【C语言篇】编译和链接以及预处理介绍(上篇)
43 1
|
1月前
|
C语言
【c语言】你绝对没见过的预处理技巧
本文介绍了C语言中预处理(预编译)的相关知识和指令,包括预定义符号、`#define`定义常量和宏、宏与函数的对比、`#`和`##`操作符、`#undef`撤销宏定义、条件编译以及头文件的包含方式。通过具体示例详细解释了各指令的使用方法和注意事项,帮助读者更好地理解和应用预处理技术。
27 2
|
2月前
|
编译器 Linux C语言
【C语言篇】编译和链接以及预处理介绍(下篇)
【C语言篇】编译和链接以及预处理介绍(下篇)
37 1
【C语言篇】编译和链接以及预处理介绍(下篇)
|
2月前
|
编译器 C语言
C语言预处理详解
C语言预处理详解
|
2月前
|
存储 C语言
【C语言篇】编译和链接以及预处理介绍(上篇)2
【C语言篇】编译和链接以及预处理介绍(上篇)
40 0
|
4月前
|
存储 自然语言处理 程序员
【C语言】文件的编译链接和预处理
【C语言】文件的编译链接和预处理
|
4月前
|
程序员 编译器 C语言
C语言中的预处理指令及其实际应用
C语言中的预处理指令及其实际应用
94 0
|
6月前
|
C语言 编译器 开发者
【C语言基础】:预处理详解(二)
【C语言基础】:预处理详解(二)