【程序环境与预处理玩转指南】(中)

简介: 【程序环境与预处理玩转指南】

【程序环境与预处理玩转指南】(上):https://developer.aliyun.com/article/1424845


再来看看下面的情况:


#include<stdio.h>
#define DOUBLE(x) (x) + (x)     //参数列表的左括号必须与DOUBLE紧邻
int main()
{
  int a = 5;
  printf("%d\n", 10 * DOUBLE(a));
  return 0;
}


运行结果:



  • 这将打印什么值呢?
  • 看上去,好像打印100,但事实上打印的是55. 我们发现替换之后:
printf ("%d\n",10 * (5) + (5));


  • 乘法运算先于宏定义的加法,所以出现了 55
  • 这个问题,的解决办法是在宏定义表达式两边加上一对括号就可以了


  • 总结:所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用。


3.2.3 #define 替换规则


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

  1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
  2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。
  3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。

注意:

  1. 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。
  2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索,会把宏当作字符串的内容处理。


3.2.4 #和##


字符串是有自动连接的特点的。

#include<stdio.h>
int main()
{
  int a = 10;
  printf("The value of a is %d\n", a);
  int b = 20;
  printf("The value of b is %d\n", b);
  return 0;
}



我们发现上面的代码除了变量和变量的值不同之外,输出模式都是一样,那我们是否可以将打印封装成一个函数呢?


#include<stdio.h>
void Print(int value)//假如传入的类型为浮点型呢?
{
    //如何把参数插入到字符串中?
  printf("The value of ? is %d\n", value);//这里?的地方要怎么写呢?
  //%d - 这样写是不是写死了呢?万一传入是其他类型呢?
}
int main()
{
  int a = 10;
  //printf("The value of a is %d\n", a);
  Print(a);
  int b = 20;
  //printf("The value of b is %d\n", b);
  Print(b);
  return 0;
}


我们发现函数无法实现,那宏呢?如何把参数插入到字符串中?如何传入的类型为浮点型呢? -  使用 # ,把一个宏参数变成对应的字符串。


#include<stdio.h>
#define PRINT(n,format) printf("The value of "#n" is "#format"\n", n)
int main()
{
  int a = 10;
  //printf("The value of a is %d\n", a);
  PRINT(a, "%d");
  int b = 20;
  //printf("The value of b is %d\n", b);
  PRINT(b, "%d");
  float f = 3.14f;
  //printf("The value of f is %f\n", f);
  PRINT(f, "%f");
  return 0;
}


运行结果:



## 的作用


##可以把位于它两边的符号合成一个符号。 它允许宏定义从分离的文本片段创建标识符。


注意:这样的连接必须产生一个合法的标识符。否则其结果就是未定义的。


3.2.5 带副作用的宏参数


当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。

x+1;//不带副作用
x++;//带有副作用
#include<stdio.h>
#define MAX(a, b) ( (a) > (b) ? (a) : (b) )
int main()
{
  int x = 5;
  int y = 8;
  int z = MAX(x++, y++);//使用超过一次带有副作用的参数
  printf("x=%d y=%d z=%d\n", x, y, z);//输出的结果是什么?
}


这里我们得知道预处理器处理之后的结果是什么:


z = ( (x++) > (y++) ? (x++) : (y++));
//     5       8      不执行   执行
//     x = 6   9               z = 9
//                             y = 10



3.2.6 宏和函数对比


宏通常被应用于执行简单的运算。 比如在两个数中找出较大的一个。

#define MAX(a, b) ((a)>(b)?(a):(b))


函数找最大值


int Max(int a, int b)
{
    return x > y ? x : y;
}


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


原因有二:

  • 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多,调用函数会形成栈帧和释放栈帧。 所以宏比函数在程序的规模和速度方面更胜一筹。
  • 更为重要的是函数的参数必须声明为特定的类型。 所以函数只能在类型合适的表达式上使用。反之这个宏可以适用于整形、长整型、浮点型等可以 用于>来比较的类型。 宏是类型无关的。


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


#include<stdio.h>
//宏的参数可以是数据类型
#define MALLOC(num,type) (type*)malloc(num * sizeof(type))
int main()
{
  //开辟40个字节的空间
  int* pa = (int*)malloc(10 * sizeof(int));
  int* pb = MALLOC(10, int);
  return 0;
}


宏的缺点:当然和函数相比宏也有劣势的地方:


  1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
  2. 宏是没法调试的。
  3. 宏由于类型无关,也就不够严谨。
  4. 宏可能会带来运算符优先级的问题,导致程容易出现错。


宏和函数的一个对比


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


3.2.7 命名约定


一般来讲函数的宏的使用语法很相似。所以语言本身没法帮我们区分二者。 那我们平时的一个习惯是:

  • 把宏名全部大写

函数名不要全部大写


【程序环境与预处理玩转指南】(下):https://developer.aliyun.com/article/1424853

相关文章
|
9天前
|
编译器 Linux C语言
程序环境和预处理(2)
程序环境和预处理(2)
22 0
|
9天前
|
存储 自然语言处理 程序员
程序环境和预处理(1)
程序环境和预处理(1)
24 0
|
1月前
|
存储 编译器 程序员
程序环境和预处理
程序环境和预处理
|
1月前
|
存储 编译器 程序员
零基础也能学会的『程序环境和预处理』
零基础也能学会的『程序环境和预处理』
|
1月前
|
存储 自然语言处理 编译器
程序环境和预处理(详解)
程序环境和预处理(详解)
|
10月前
|
存储 编译器 程序员
【C】程序环境和预处理
在ANSI C的任何一种实现中,存在两个不同的环境。
|
10月前
|
编译器 Linux C++
【程序环境与预处理】(二)
【程序环境与预处理】(二)
63 0
|
10月前
|
存储 自然语言处理 程序员
【程序环境与预处理】(一)
【程序环境与预处理】(一)
63 0
|
1月前
|
编译器 Linux C++
【程序环境与预处理玩转指南】(下)
【程序环境与预处理玩转指南】
|
1月前
|
存储 自然语言处理 编译器
程序环境+预处理
程序环境+预处理
58 0