C语言预处理及宏和函数的区别与各自优劣点的详解(上)

简介: C语言预处理及宏和函数的区别与各自优劣点的详解(上)

一:#define

1:#define定义标识符

1.1:语法形式

#define name stuff

1.2:实例

#define MAX 1000
#define reg register          //为 register(寄存器)这个关键字,创建一个简短的名字
// 如果定义的 stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。
#define DEBUG_PRINT printf("file:%s\tline:%d\t \
                          date:%s\ttime:%s\n" ,\
                          __FILE__,__LINE__ , \
                          __DATE__,__TIME__ )
在#define定义标识符之后后面不能加;
例如:
#define M 100;
int main()
{
  int a = 0;
  int b = 0;
  if (a < 5)
    b = M;
  else
    b = -1;
  return 0;
}

此时会报错

当替换后:
int main()
{
  int a = 0;
  int b = 0;
  if (a < 5)
    b = 100;;
  else
    b = -1;
  return 0;
}
//也就是说else语句前不是if语句,而是空语句即;
所以报错中有:没有匹配if的非法else

那么,怎么书写宏才能够避免这种情况呢?

#define M(x) do{x=100;}while(0)
int main()
{
  int a = 0;
  int b = 0;
  if (a < 5)
    M(b);
  else
    b = -1;
  return 0;
}

此时,代码成功执行

宏替换后:

#define M(x) do{x=100;}while(0)
//这里while(0)后面不要加;
int main()
{
  int a = 0;
  int b = 0;
  if (a < 5)
    do
    {
       b=100;
    }while(0);
  else
    b = -1;
  return 0;
}

我们可以再看一个例子

//如果不加换行符会报错
#define INIT_VAL(a,b) do{\
      a = 0;\
      b = 0; \
      }while(0)
//这里while(0)后面不要加;
int main()
{
  int x = 10;
  int y = 20;
  printf("before: x = %d, y = %d\n", x, y);
  if (1)
  {
    INIT_VAL(x, y);
  }
  else
  {
    x = 100;
    y = 100;
  }
  printf("after: x = %d, y = %d\n", x, y);
  return 0;
}

不加换行符会报错

宏替换后:

#define INIT_VAL(a,b) do{\
      a = 0;\
      b = 0; \
      }while(0)
//这里while(0)后面不要加;
int main()
{
  int x = 10;
  int y = 20;
  printf("before: x = %d, y = %d\n", x, y);
  if (1)
  {
    do
    {
       x=0;
       y=0;
    }while(0);
  }
  else
  {
    x = 100;
    y = 100;
  }
  printf("after: x = %d, y = %d\n", x, y);
  return 0;
}

所以,如果我们想用一个宏向目标代码处插入多条数据时,可以将这多条语句封装到do…while(0)语句中,此时这个语句中可以任意添加语句去成批替换

此处do…while(0)循环的作用:

(1)do…while(0)循环具有花括号语句:可以让宏在替换的时候能够替换多条语句

(2):while(0):让这个循环只执行一次,实现替换

这种结构被称为:do-while-zero结构

2:#define定义宏

2.1:宏定义的介绍

#define机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义
宏(define macro)。
下面是宏的声明方式:
#define name( parament-list ) stuff
其中的 parament - list 是一个由逗号隔开的符号表,它们可能出现在stuff中。
注意:
参数列表的左括号必须与name紧邻。
如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分

2.2:宏定义的替换规则引例

//例如:
#define MAX(x,y) ((x)>(y)?(x):(y))
//反例:
#define SQUARE(x) x*x
int main()
{
  int a = 3;
  int r = SQUARE(a);
  printf("r=%d\n", r);
  r = SQUARE(a+2);
  printf("r=%d\n", r);
  return 0;
}

我们推测答案应该是9跟25,但是:

这说明宏定义跟函数有所不同,函数是先计算a+2得出5后再传递给函数的形参,但是宏却不一样

那么这么结果到底是怎么得出的呢?

#define SQUARE(x) x*x
int main()
{
  int a = 3;
  int r = 3*3;
  printf("r=%d\n", r);//3*3=9
  r = 3+2*3+2;
  printf("r=%d\n", r);//3+2*3+2=11
  return 0;
}

这样我们就能解释11是怎么得出的了

事实上,宏定义就是这种替换方式,宏定义在编译阶段就已经替换进了代码中

也就是说编译后的代码中是没有类似于这种代码的int r = SQUARE(a);

而是替换成了int r = 3*3;

而:#define SQUARE(x) x*x就直接被删除掉了

那么我们到底应该怎么改呢?

2.3:宏定义的替换规则

所以用于对数值表达式进行求值的宏定义都应该加上括号

避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用。

改为:

#define SQUARE(x) ((x)*(x))
int main()
{
  int a = 3;
  int r = SQUARE(a);
  printf("r=%d\n", r);
  r = SQUARE(a + 2);
  printf("r=%d\n", r);
  return 0;
}

可见,此时准确得出了我们想要的结果

那么此时替换为了什么呢?

#define SQUARE(x) ((x)*(x))
int main()
{
  int a = 3;
  int r = ((3)*(3));
  printf("r=%d\n", r);//9
  r = ((3+2)*(3+2));
  printf("r=%d\n", r);//25
  return 0;
}

可见,加括号后的确可以防止操作符优先级的干扰,那么到底要加多少括号才能万无一失呢?

建议是能加的地方都加上括号.

接下来我们再来看一个例子:

#define DOUBLE(x) (x) + (x)
定义中我们使用了括号,想避免之前的问题,但是这个宏可能会出现新的错误。
int a = 5;
printf("%d\n", 10 * DOUBLE(a));
这将打印什么值呢?
看上去,好像打印100,但事实上打印的是55.
我们发现替换之后:
printf("%d\n", 10 * (5) + (5));
由于:乘法运算先于宏定义的加法,所以出现了55
这个问题的解决办法是在宏定义表达式两边加上一对括号就可以了。

即这样

#define DOUBLE(x) ((x) + (x))
我们发现替换之后:
printf("%d\n", 10 * ((5) + (5)));
即50

3:#define替换规则

#define 替换规则
在程序中扩展#define定义符号和宏时,需要涉及几个步骤。
1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先
被替换。
2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。
3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上
述处理过程。
注意:
1. 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。
2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。
相关文章
|
22天前
|
程序员 C语言 开发者
pymalloc 和系统的 malloc 有什么区别
pymalloc 和系统的 malloc 有什么区别
|
14天前
|
C语言
c语言调用的函数的声明
被调用的函数的声明: 一个函数调用另一个函数需具备的条件: 首先被调用的函数必须是已经存在的函数,即头文件中存在或已经定义过; 如果使用库函数,一般应该在本文件开头用#include命令将调用有关库函数时在所需要用到的信息“包含”到本文件中。.h文件是头文件所用的后缀。 如果使用用户自己定义的函数,而且该函数与使用它的函数在同一个文件中,一般还应该在主调函数中对被调用的函数做声明。 如果被调用的函数定义出现在主调函数之前可以不必声明。 如果已在所有函数定义之前,在函数的外部已做了函数声明,则在各个主调函数中不必多所调用的函数在做声明
29 6
|
18天前
|
程序员 C语言 开发者
pymalloc 和系统的 malloc 有什么区别?
pymalloc 和系统的 malloc 有什么区别?
|
25天前
|
C语言
【c语言】你绝对没见过的预处理技巧
本文介绍了C语言中预处理(预编译)的相关知识和指令,包括预定义符号、`#define`定义常量和宏、宏与函数的对比、`#`和`##`操作符、`#undef`撤销宏定义、条件编译以及头文件的包含方式。通过具体示例详细解释了各指令的使用方法和注意事项,帮助读者更好地理解和应用预处理技术。
24 2
|
27天前
|
存储 算法 程序员
C语言:库函数
C语言的库函数是预定义的函数,用于执行常见的编程任务,如输入输出、字符串处理、数学运算等。使用库函数可以简化编程工作,提高开发效率。C标准库提供了丰富的函数,满足各种需求。
|
30天前
|
存储 C语言
【c语言】字符串函数和内存函数
本文介绍了C语言中常用的字符串函数和内存函数,包括`strlen`、`strcpy`、`strcat`、`strcmp`、`strstr`、`strncpy`、`strncat`、`strncmp`、`strtok`、`memcpy`、`memmove`和`memset`等函数的使用方法及模拟实现。文章详细讲解了每个函数的功能、参数、返回值,并提供了具体的代码示例,帮助读者更好地理解和掌握这些函数的应用。
25 0
|
1月前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
35 3
|
1月前
|
存储 缓存 C语言
【c语言】简单的算术操作符、输入输出函数
本文介绍了C语言中的算术操作符、赋值操作符、单目操作符以及输入输出函数 `printf` 和 `scanf` 的基本用法。算术操作符包括加、减、乘、除和求余,其中除法和求余运算有特殊规则。赋值操作符用于给变量赋值,并支持复合赋值。单目操作符包括自增自减、正负号和强制类型转换。输入输出函数 `printf` 和 `scanf` 用于格式化输入和输出,支持多种占位符和格式控制。通过示例代码详细解释了这些操作符和函数的使用方法。
39 10
|
1月前
|
机器学习/深度学习 C语言
【c语言】一篇文章搞懂函数递归
本文详细介绍了函数递归的概念、思想及其限制条件,并通过求阶乘、打印整数每一位和求斐波那契数等实例,展示了递归的应用。递归的核心在于将大问题分解为小问题,但需注意递归可能导致效率低下和栈溢出的问题。文章最后总结了递归的优缺点,提醒读者在实际编程中合理使用递归。
61 7
|
1月前
|
存储 编译器 程序员
【c语言】函数
本文介绍了C语言中函数的基本概念,包括库函数和自定义函数的定义、使用及示例。库函数如`printf`和`scanf`,通过包含相应的头文件即可使用。自定义函数需指定返回类型、函数名、形式参数等。文中还探讨了函数的调用、形参与实参的区别、return语句的用法、函数嵌套调用、链式访问以及static关键字对变量和函数的影响,强调了static如何改变变量的生命周期和作用域,以及函数的可见性。
30 4