宏(Macro)本质上就是代码片段,通过别名来使用。可简单地将宏编程理解为:按一定规则将文本定义为一个标志符,在编程时直接引用标志符替代这些文本进行编程,在程序编译时,再将标志符替换回预定义的文本的过程。C语言预编译器主要实现宏替换功能。Keil C51与标准C语言大同小异,也有宏系统。
宏定义格式如下:
#define 标志符[(参数表)] 字符串
注意字符串结尾处不可有分号。宏定义标准符后面可以带小括号,括号里面也可以带参数表,也可以不带参数。带小括号的宏定义类似于函数定义,我们可以把这种宏叫做宏函数。
宏的主要应用如下:
1. 定义别名,如下面代码:
#ifndef BYTE
#define BYTE unsigned char
#endif
#ifndef byte
#define byte unsigned char
#endif
#ifndef ui8
#define ui8 unsigned char
#endif
#ifndef UI8
#define UI8 unsigned char
#endif
#ifndef U8
#define U8 unsigned char
#endif
#ifndef u8
#define u8 unsigned char
#endif
#ifndef uchar
#define uchar unsigned char
#endif
#ifndef INT8
#define INT8 signed char
#endif
#ifndef int8
#define int8 signed char
#endif
#ifndef I8
#define I8 signed char
#endif
#ifndef i8
#define i8 signed char
#endif
2. 定义常数,如下面代码:
#define FSCLK 30000000L
3. 避免头文件重复包含的宏定义,如下面代码:
/*config.h
Designed by Bill Liu
Version 0.0
Modified last by Bill Liu on 06/22/2022
*/
#ifndef __CONFIG_H__
#define __CONFIG_H__
#define FOSC 30000000UL
//********************************************************
void SysInit(); //init System speed fastest
#endif
4. 条件编译的宏定义,如下面代码:
/*******************************************************************************
* 函 数 名 : LcdInit()
* 函数功能 : 初始化LCD屏
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
#define LCD1602_4PINS
#ifndef LCD1602_4PINS
void Lcd1602_Init() //LCD初始化子程序
{
LCDWriteCom(0x38); //开显示
LCDWriteCom(0x0c); //开显示不显示光标
LCDWriteCom(0x06); //写一个指针加1
LCDWriteCom(0x01); //清屏
LCDWriteCom(0x80); //设置数据指针起点
}
#else
void LCDInit() //LCD初始化子程序
{
LCDWriteCom(0x32); //将8位总线转为4位总线
LCDWriteCom(0x28); //在四位线下的初始化
LCDWriteCom(0x0c); //开显示不显示光标
LCDWriteCom(0x06); //写一个指针加1
LCDWriteCom(0x01); //清屏
LCDWriteCom(0x80); //设置数据指针起点
}
#endif
//*************************************************************************
5. 取代函数定义,宏函数定义与函数定义有些相像,但是其编译执行过程是完全不一样的,宏函数在编译工程中是完全的代码替换,而函数则是编译成机器码仿真特定的地址空间内,调用时需要来回跳转,效率不如宏函数高,但是使用宏函数定义编译后的程序可能会占用更大的程序存储空间。
宏函数定义及应用
1. 无参数表宏函数定义 宏函数名实际就是后面宏函数体的别名,因此定义宏函数在宏函数名前不能有返回值类型,宏函数体实际上是以宏函数名的连续文本,在换行前必须加反斜杠(\),如下面代码示:
/*main.c
Designed by Bill Liu
Version 0.0
Modified last by Bill Liu on 12/08/2021
*/
#include "main.h"
#include "stcint.h"
#define InitP0_PPOut() {\
P0M1 = 0x00;\
P0M0 = 0xFF;\
}
void main()
{
STCIO_InitPortsBits(P0|P1|P2|P3|P4, 0xFF, BI_IO);
P3 = 0xFF;
UartS1_Init(VBAUD_8BITS,G1,T2,9600);
STCIO_InitP3Bit(SCT_BIT1, PP_OUT);
InitP0_PPOut();
SundBuzzerx10ms(50);
Delay10xms(50, FSCLK);
SundBuzzerx10ms(50);
while(1)
{
UartS1_SendString("Uart S1 Test!");
Delay10xms(200, FSCLK);
}
}
//End of main()
//*********************************************
void SundBuzzerx10ms(ui8 x)
{
BUZZER = 0;
Delay10xms(x, FSCLK);
BUZZER = 1;
}
编译以上代码 ,结果如下:
下面修改一下代码,多次引用宏函数,修改后的代码如下:
/*main.c
Designed by Bill Liu
Version 0.0
Modified last by Bill Liu on 12/08/2021
*/
#include "main.h"
#include "stcint.h"
#define InitP0_PPOut() {\
P0M1 = 0x00;\
P0M0 = 0xFF;\
}
void main()
{
STCIO_InitPortsBits(P0|P1|P2|P3|P4, 0xFF, BI_IO);
P3 = 0xFF;
UartS1_Init(VBAUD_8BITS,G1,T2,9600);
STCIO_InitP3Bit(SCT_BIT1, PP_OUT);
InitP0_PPOut();
InitP0_PPOut();
InitP0_PPOut();
InitP0_PPOut();
InitP0_PPOut();
SundBuzzerx10ms(50);
Delay10xms(50, FSCLK);
SundBuzzerx10ms(50);
while(1)
{
UartS1_SendString("Uart S1 Test!");
Delay10xms(200, FSCLK);
}
}
//End of main()
//*********************************************
void SundBuzzerx10ms(ui8 x)
{
BUZZER = 0;
Delay10xms(x, FSCLK);
BUZZER = 1;
}
再编译,看下编译结果怎么样,结果如下:
可以看出code由2533变成了2553, 现在把宏函数改为函数,并调用一次,改好的代码如下:
/*main.c
Designed by Bill Liu
Version 0.0
Modified last by Bill Liu on 12/08/2021
*/
#include "main.h"
#include "stcint.h"
void InitP0_PPOut() {
P0M1 = 0x00;
P0M0 = 0xFF;
}
void main()
{
STCIO_InitPortsBits(P0|P1|P2|P3|P4, 0xFF, BI_IO);
P3 = 0xFF;
UartS1_Init(VBAUD_8BITS,G1,T2,9600);
STCIO_InitP3Bit(SCT_BIT1, PP_OUT);
InitP0_PPOut();
//InitP0_PPOut();
//InitP0_PPOut();
//InitP0_PPOut();
//InitP0_PPOut();
SundBuzzerx10ms(50);
Delay10xms(50, FSCLK);
SundBuzzerx10ms(50);
while(1)
{
UartS1_SendString("Uart S1 Test!");
Delay10xms(200, FSCLK);
}
}
//End of main()
//*********************************************
void SundBuzzerx10ms(ui8 x)
{
BUZZER = 0;
Delay10xms(x, FSCLK);
BUZZER = 1;
}
编译结果如下:
现在去掉多次调用前的注释,多次调用函数,修改后的代码如下:
/*main.c
Designed by Bill Liu
Version 0.0
Modified last by Bill Liu on 12/08/2021
*/
#include "main.h"
#include "stcint.h"
void InitP0_PPOut() {
P0M1 = 0x00;
P0M0 = 0xFF;
}
void main()
{
STCIO_InitPortsBits(P0|P1|P2|P3|P4, 0xFF, BI_IO);
P3 = 0xFF;
UartS1_Init(VBAUD_8BITS,G1,T2,9600);
STCIO_InitP3Bit(SCT_BIT1, PP_OUT);
InitP0_PPOut();
InitP0_PPOut();
InitP0_PPOut();
InitP0_PPOut();
InitP0_PPOut();
SundBuzzerx10ms(50);
Delay10xms(50, FSCLK);
SundBuzzerx10ms(50);
while(1)
{
UartS1_SendString("Uart S1 Test!");
Delay10xms(200, FSCLK);
}
}
//End of main()
//*********************************************
void SundBuzzerx10ms(ui8 x)
{
BUZZER = 0;
Delay10xms(x, FSCLK);
BUZZER = 1;
}
通过以上测试可以看出,如果调用一次,使用宏函数比使用函数编译后的代码所占空间更小,如果需要多次调用,函数调用编译后的代码更小。使用宏函数运行速度会更快,因为它没有了,函数间的跳转,这一点前面已经讲过。这样我们就明白了在何时使用宏函数,何时使用函数。
2. 带参数表的宏函数定义 由于宏函数在编译时先由预处理器进行文本替换然后再编译,因此参数列表中的参数不需要,也不能有类型约束,另外规则规定参数展开需要加小括号,否则会得到不可预测的结果。下面是一个正确的宏函数定义:
#define Sum(x, y) {\
return (x) + (y);\
}
现将上面的宏函数放入程序中,看能否编译通过,程序代码如下:
/*main.c
Designed by Bill Liu
Version 0.0
Modified last by Bill Liu on 12/08/2021
*/
#include "main.h"
#include "stcint.h"
#define Sum(x, y) {\
return (x) + (y);\
}
int main()
{
STCIO_InitPortsBits(P0|P1|P2|P3|P4, 0xFF, BI_IO);
P3 = 0xFF;
UartS1_Init(VBAUD_8BITS,G1,T2,9600);
STCIO_InitP3Bit(SCT_BIT1, PP_OUT);
InitP0_PPOut();
InitP0_PPOut();
InitP0_PPOut();
InitP0_PPOut();
InitP0_PPOut();
SundBuzzerx10ms(50);
Delay10xms(50, FSCLK);
SundBuzzerx10ms(50);
/*
while(1)
{
UartS1_SendString("Uart S1 Test!");
Delay10xms(200, FSCLK);
}
*/
Sum(8, 7);
}
//End of main()
//*********************************************
void SundBuzzerx10ms(ui8 x)
{
BUZZER = 0;
Delay10xms(x, FSCLK);
BUZZER = 1;
}
编译结果如下:
编译通过。下面我们来试一下能否像函数那样调用,修改程序代码如下:
/*main.c
Designed by Bill Liu
Version 0.0
Modified last by Bill Liu on 12/08/2021
*/
#include "main.h"
#include "stcint.h"
#define Sum(x, y) {\
return (x) + (y);\
}
int a;
int main()
{
STCIO_InitPortsBits(P0|P1|P2|P3|P4, 0xFF, BI_IO);
P3 = 0xFF;
UartS1_Init(VBAUD_8BITS,G1,T2,9600);
STCIO_InitP3Bit(SCT_BIT1, PP_OUT);
SundBuzzerx10ms(50);
Delay10xms(50, FSCLK);
SundBuzzerx10ms(50);
/*
while(1)
{
UartS1_SendString("Uart S1 Test!");
Delay10xms(200, FSCLK);
}
*/
a = Sum(8, 7);
return a;
}
//End of main()
//*********************************************
void SundBuzzerx10ms(ui8 x)
{
BUZZER = 0;
Delay10xms(x, FSCLK);
BUZZER = 1;
编译结果如下:
编译不能通过。现在再修改一下宏函数定义及程序代码,修改后的程序如下:
/*main.c
Designed by Bill Liu
Version 0.0
Modified last by Bill Liu on 12/08/2021
*/
#include "main.h"
#include "stcint.h"
#define Sum(x, y,z) {\
z = (x) + (y);\
}
int a;
int main()
{
STCIO_InitPortsBits(P0|P1|P2|P3|P4, 0xFF, BI_IO);
P3 = 0xFF;
UartS1_Init(VBAUD_8BITS,G1,T2,9600);
STCIO_InitP3Bit(SCT_BIT1, PP_OUT);
SundBuzzerx10ms(50);
Delay10xms(50, FSCLK);
SundBuzzerx10ms(50);
/*
while(1)
{
UartS1_SendString("Uart S1 Test!");
Delay10xms(200, FSCLK);
}
*/
Sum(8, 7, a);
return a;
}
//End of main()
//*********************************************
void SundBuzzerx10ms(ui8 x)
{
BUZZER = 0;
Delay10xms(x, FSCLK);
BUZZER = 1;
}
编译结果如下:
编译通过。上面宏函数得到的结果是否是正确的呢?下面来做下测试,先修改程序代码如下:
/*main.c
Designed by Bill Liu
Version 0.0
Modified last by Bill Liu on 12/08/2021
*/
#include "main.h"
#include "stcint.h"
#define Sum(x, y,z) {\
z = (x) + (y);\
}
ui8 mstr[20] = {0};
int a = 0;
void main()
{
STCIO_InitPortsBits(P0|P1|P2|P3|P4, 0xFF, BI_IO);
P3 = 0xFF;
UartS1_Init(VBAUD_8BITS,G1,T2,9600);
STCIO_InitP3Bit(SCT_BIT1, PP_OUT);
SundBuzzerx10ms(50);
Delay10xms(50, FSCLK);
SundBuzzerx10ms(50);
Sum(8, 7, a);
while(1)
{
memset(mstr,0,strlen(mstr));
sprintf(mstr,"a = %d\n",a);
UartS1_SendString(mstr);
Delay10xms(200, FSCLK);
}
}
//End of main()
//*********************************************
void SundBuzzerx10ms(ui8 x)
{
BUZZER = 0;
Delay10xms(x, FSCLK);
BUZZER = 1;
}
编译结果如下:
编译通过,现在把它下载到单片机,看下结果如何
上面是在串口助手中看到的结果,可以看出 a=15,结果是正确的。注意宏函数中z在展开时没有加括号。先修改一下程序代码,修改后如下:
/*main.c
Designed by Bill Liu
Version 0.0
Modified last by Bill Liu on 12/08/2021
*/
#include "main.h"
#include "stcint.h"
#define Sum(x, y,z) {\
z = (x) + (y);\
}
ui8 mstr[20] = {0};
int a = 0;
void main()
{
STCIO_InitPortsBits(P0|P1|P2|P3|P4, 0xFF, BI_IO);
P3 = 0xFF;
UartS1_Init(VBAUD_8BITS,G1,T2,9600);
STCIO_InitP3Bit(SCT_BIT1, PP_OUT);
SundBuzzerx10ms(50);
Delay10xms(50, FSCLK);
SundBuzzerx10ms(50);
while(1)
{
Sum(10, 15, a);
memset(mstr,0,strlen(mstr));
sprintf(mstr,"a = %d\n",a);
UartS1_SendString(mstr);
Delay10xms(200, FSCLK);
}
}
//End of main()
//*********************************************
void SundBuzzerx10ms(ui8 x)
{
BUZZER = 0;
Delay10xms(x, FSCLK);
BUZZER = 1;
}
编译下载到单片机,结果如下:
如果将程序修改如下,又会如何呢?修改后的程序代码如下:
结果也是对的。尽管如此,在编程时还是建议在展开时将参数加上小括号,现将宏函数代码修改如下:
/*main.c
Designed by Bill Liu
Version 0.0
Modified last by Bill Liu on 12/08/2021
*/
#include "main.h"
#include "stcint.h"
#define Sum(x, y,z) {\
(x) = (x) + 1;
(z) = (x) + (y);\
}
ui8 mstr[20] = {0};
int a = 0;
void main()
{
STCIO_InitPortsBits(P0|P1|P2|P3|P4, 0xFF, BI_IO);
P3 = 0xFF;
UartS1_Init(VBAUD_8BITS,G1,T2,9600);
STCIO_InitP3Bit(SCT_BIT1, PP_OUT);
SundBuzzerx10ms(50);
Delay10xms(50, FSCLK);
SundBuzzerx10ms(50);
while(1)
{
Sum(10, 1, a);
memset(mstr,0,strlen(mstr));
sprintf(mstr,"a = %d\n",a);
UartS1_SendString(mstr);
Delay10xms(200, FSCLK);
}
}
//End of main()
//*********************************************
void SundBuzzerx10ms(ui8 x)
{
BUZZER = 0;
Delay10xms(x, FSCLK);
BUZZER = 1;
}
编译结果如下:
不能通过编译。修改代码如下:
/*main.c
Designed by Bill Liu
Version 0.0
Modified last by Bill Liu on 12/08/2021
*/
#include "main.h"
#include "stcint.h"
#define Sum(x, y,z) {\
(z) = (z) + 1;\
(z) = (x) + (y);\
}
ui8 mstr[20] = {0};
int a = 0;
void main()
{
STCIO_InitPortsBits(P0|P1|P2|P3|P4, 0xFF, BI_IO);
P3 = 0xFF;
UartS1_Init(VBAUD_8BITS,G1,T2,9600);
STCIO_InitP3Bit(SCT_BIT1, PP_OUT);
SundBuzzerx10ms(50);
Delay10xms(50, FSCLK);
SundBuzzerx10ms(50);
while(1)
{
Sum(10, 1, a);
memset(mstr,0,strlen(mstr));
sprintf(mstr,"a = %d\n",a);
UartS1_SendString(mstr);
Delay10xms(200, FSCLK);
}
}
//End of main()
//*********************************************
void SundBuzzerx10ms(ui8 x)
{
BUZZER = 0;
Delay10xms(x, FSCLK);
BUZZER = 1;
}
编译结果如下:
再修改代码,修改后的代码如下:
/*main.c
Designed by Bill Liu
Version 0.0
Modified last by Bill Liu on 12/08/2021
*/
#include "main.h"
#include "stcint.h"
#define Sum(z, y,x) {\
(x) = (x) + 1;\
(x) = (z) + (y);\
}
ui8 mstr[20] = {0};
int a = 0;
void main()
{
STCIO_InitPortsBits(P0|P1|P2|P3|P4, 0xFF, BI_IO);
P3 = 0xFF;
UartS1_Init(VBAUD_8BITS,G1,T2,9600);
STCIO_InitP3Bit(SCT_BIT1, PP_OUT);
SundBuzzerx10ms(50);
Delay10xms(50, FSCLK);
SundBuzzerx10ms(50);
while(1)
{
Sum(10, 1, a);
memset(mstr,0,strlen(mstr));
sprintf(mstr,"a = %d\n",a);
UartS1_SendString(mstr);
Delay10xms(200, FSCLK);
}
}
//End of main()
//*********************************************
void SundBuzzerx10ms(ui8 x)
{
BUZZER = 0;
Delay10xms(x, FSCLK);
BUZZER = 1;
}
再编译,看能否通过,编译结果如下:
编译通过,能看出门道不?如果列表中有更多参数,有会怎样?参与的定义是否与后面的引用相关联?如果你对宏有正确认识,心里一定会有正确答案 。我们知道下面函数不能实现数据交换,该函数代码如下:
void swap(int x, int y)
{
int tem = x;
x = y;
y = tem;
}
如果是宏函数呢?下面我们来试一下。程序代码如下:
/*main.c
Designed by Bill Liu
Version 0.0
Modified last by Bill Liu on 12/08/2021
*/
#include "main.h"
#include "stcint.h"
ui8 mstr[20] = {0};
int t1 = 10;
int t2 = 15;
int tem = 0;
#define swap(x,y) {\
tem =(x);\
(x) = (y);\
(y) = tem;\
}
void main()
{
STCIO_InitPortsBits(P0|P1|P2|P3|P4, 0xFF, BI_IO);
P3 = 0xFF;
UartS1_Init(VBAUD_8BITS,G1,T2,9600);
STCIO_InitP3Bit(SCT_BIT1, PP_OUT);
SundBuzzerx10ms(50);
Delay10xms(50, FSCLK);
SundBuzzerx10ms(50);
while(1)
{
swap(t1,t2);
memset(mstr,0,strlen(mstr));
sprintf(mstr,"t1 = %d\r\n",t1);
UartS1_SendString(mstr);
memset(mstr,0,strlen(mstr));
sprintf(mstr,"t2 = %d\r\n",t2);
UartS1_SendString(mstr);
Delay10xms(200, FSCLK);
}
}
//End of main()
//*********************************************
void SundBuzzerx10ms(ui8 x)
{
BUZZER = 0;
Delay10xms(x, FSCLK);
BUZZER = 1;
}
编译,下载到单片机,在串口助手的中看到的结果如下:
从结果可以看出,已经实现了互换。