宏初识

336 阅读6分钟

在C语言中,宏(Macro)是一种预处理指令,用于在编译之前对代码进行文本替换。宏通常用于定义常量、简化代码、减少重复代码等。以#开头的均为预处理命令。宏的定义使用 #define 指令。

宏的种类

1.宏定义

2.文件包含

3.条件编译

1.宏定义(#define)

宏定义的效果是:当编译器遇到宏名是,都会自动用宏名中的宏体去替换掉宏名(宏替换/宏展开)

优点:

1.一种是方便程序的修改,可以用带不参宏体以代替一个在程序中经常使用的常量,这样在将该常量改变时,应对指挥程序进行修改,只修改宏定义中宏体即可。而且当常量比较长时,我们可以用较短的有意义标识符来写程序,大大的提高了代码的开发效率和可读性。

2.另一方面提高程序的运行效率,这体现在程序运行阶段,而不是编译阶段。使用带参宏,可以完成函数调用的功能,而且与函数相比,宏定义能减少信用开销,只略分配内存步骤一次来提高运行效率。

1.无参宏替换

只用宏体替换宏名,并不涉及运算

#define price 30

2.带参宏定义

带有一定的逻辑运算,自动调用后面的表达式.

它的机制有点类似于函数,但它比函数更加简练。通常我们会把某一段短小精悍而一运算重复率用非常高的代码变成带参数宏,这样就减少了代码的重复率,也提高了代码的可观赏性。

#define price(y) y+30

price(10)=10+30

int M(int price)

{

return y+30;

}

#define price(y) (y + 30) 定义了一个宏 price,它接受一个参数 y,并返回 y + 30

可以注意到,带参宏定义与函数非常类似。

但正如C语言中所讲,函数的使用可以使程序更加模块化,便于组织,而且可重复利用。

发生函数调用时,需要保留调用函数的现场,以便被调函数执行结束后能返回继续执行。

同样,在被调函数执行完后,能恢复调用函数的现场,这都需要一定的时间。

如果被调函数执行的操作比较多,从转换时间开效可以忽略。如果此函数完成的功能比较少,甚至于只完成一点操作,如一个乘法语句的操作,则这部分转换开销就相对较大了。而使用代参数的宏定义,就不会出现这个问题。 尽管宏定义可以完成简单的操作,但是复杂的操作还是要由函数来完成,而且宏定义所占用的目标代码空间相对较大,所以使用需要依据具体情况来决定

3.系统宏

为方便开发者编码,在c和c++语言中定义了少量系统宏

例:

1._FILE_

包含当前文件程序名和路径的字符串

2._DATE_

代表当前日期的字符串

2.文件包含(#include)

指定文件的全部内容替换程序中的命令行,从而是指定的文件与单元文件连成一个源文件,有如下两种使用形式。

这两种形式都可以使用,但是有以下几种区别

#include "文件名"————用户自定义

使用双引号的,表示编译系统首先,在当前的原文件目录中查找。若未找到,再根据系统头文件存放的目录路径去搜索系统头文件。

#include <文件名>————系统定义

使用尖括号的表示,编译系统根据系统头文件存放的目录路径去搜索系统头文件,而不是在源文件目录查找

简而言之,就是系统定义的头文件通常使用尖括号,用户自定义的头文件。

文件包含命令可以出现在文件的任何位置,但通常放置在文件的开头处。一条include命令只能指定一个被包含的文件。

同时文件包含也允许嵌套,即在一个被包含的文件中,又可以包含另一个文件。

在实际开发时,把一个程序继续分散在若干文件中时,可以将多个文件公用的符号,常量定义和宏定义等单独写成一个文件,然后在其他需要这些定义和说明的源文件中,用文件包含命令包含该头文件,这样可以避免在每个文件开头都去重附属险的险共用代码,也可以避免因输入或修改的失误造成的不一致性

3.条件编译

一般情况下,原文件的所有代码行都会参加编译,以生成目标代码,

但在某些特殊情况中,也许只希望对部分满足条件的代码行进行编译,

这就是条件变异常见的条件编译命令。有以下两种格式

1**.#ifdef <标识符>**

程序段1

#else

程序段2

#endif

功能:如果在程序中定义了指定的标识符,就用程序段1参与编译,否则程序段1参与编译。

2.**#ifndef <标识符>****

程序段1

#else

程序段2

#endif

功能:当程序中定义指令标识符时,程序段二参与编译,否则程序段一参与编译。

都可省略else分支,写为

#ifdef<标识符>

程序段

#ifndef<标识符>

程序段

宏的基本语法

#define 宏名 替换文

常见的宏用法

  1. 定义常量

    • 使用宏定义常量可以使代码更具可读性,并且便于修改。

    • 示例:

      #define PI 3.14159

屏幕截图 2024-11-01 132758.png

  1. 定义简单的函数

    • 宏可以用来定义简单的函数,避免函数调用的开销。

    • 示例:

      #define MAX(a, b)

      ((a) > (b) ? (a) :

      (b))

屏幕截图 2024-11-01 131427.png

  1. 条件编译

    • 使用宏进行条件编译,可以根据不同的条件编译不同的代码块。

    • 示例:

      #define DEBUG

      #ifdef DEBUG

      printf("Debug mode is enabled. \n");

      #endif

屏幕截图 2024-11-01 131451.png

  1. 字符串化

    • 使用 # 可以将宏参数转换为字符串。

    • 示例:

      #define STRINGIFY(x)

      #x

      printf("%s\n",STRINGIFY(Hello)); // 输出 "Hello“

屏幕截图 2024-11-01 131515.png

  1. 连接操作符

    • 使用 ## 可以将两个符号连接在一起。

    • 示例:

      #define CONCAT(a, b) a##b

      int xy = 10;

      printf("%d\n", CONCAT(x, y)); // 输出 10

屏幕截图 2024-11-01 132934.png

宏的注意事项

  1. 括号的使用

    • 在定义宏时,尽量使用括号来确保运算顺序正确。例如:

      #define SQUARE(x) ((x) * (x))

屏幕截图 2024-11-01 132948.png

  1. 副作用

    • 宏展开时可能会产生副作用,特别是当宏参数是表 达式时。例如:

      #define DOUBLE(x) ((x) + (x))

      int a = 5;

      int b = DOUBLE(a++); // a 会被增加两次

屏幕截图 2024-11-01 133003.png

  1. 调试困难

    • 宏展开后的代码在调试时可能难以理解,因为宏展开后的代码与源代码不同。

示例代码

以下是一个使用宏的简单示例:

#include <stdio.h>

// 定义常量

#define PI 3.14159

// 定义简单的函数

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

// 字符串化

#define STRINGIFY(x) #x

// 连接操作符

#define CONCAT(a, b) a##b

int main() {

printf("PI = %f\n", PI);

printf("MAX(3, 5) = %d\n", MAX(3, 5));

printf("%s\n", STRINGIFY(Hello));

int xy = 10;

printf("%d\n", CONCAT(x, y));

return 0;

}

代码展示

屏幕截图 2024-11-01 133211.png