一、c/c++宏定义的来源
宏定义,就是用一个标识符来表示一个字符串,如果在后面的代码中出现了该标识符,那么就全部替换成指定的字符串。通常c/c++宏定义这几处出处:
1)最常见的就是来自于开发者编码过程中采用宏定义命令“#define”来定义,它是一种C语言预处理命令。
2) ANSI标准也自行提供了多个预定义的宏名,例如__DATE__,__TIME__,__FILE__, __LINE__, __FUNCTION__等,实际上也是采用“#define”来定义,只是标准库已经定义并占据了这些命名,开发者直接调用即可。
3)另外编译器也会自带一些宏定义,类似于WIN32 __LINUX__等,另外开发者在编译过程中也可以通过配置文件、命令语句来传递宏定义参与编译。例如在makefile文件加入"MYID:=100",或在CMakeLists.txt 中加入“add_definitions( -DMYID)”,又或在gcc指令中直接加入“ -DMYID”,其实都可以看做是宏传递参与编译的一种。
二、宏定义应用
宏定义命令“#define”是c/c++编程宏使用最多情况,具体展开有很多细节内容,但归根结底就两种用法,带参数宏定义和不带参数宏定义。
不带参数宏定义格式:# define 标识符 字符序列
例如:
#define PI (3.1415926) //常量宏定义
又例如:
//#define PYFREE 1
#define PYFREE //没给出字符序列也是可以的
又例如,我们遇到最多的就头文件防止重复编译进行的宏定义
#ifndef _TEST__H_ //条件编译
#define _TEST__H_ //防止一个头文件被重复包含
//头文件内容
#endif //_TEST__H_
带参数宏定义格式:# define 标识符(参数表) 字符序列
注意,宏调用时是以实参代换形参,而不是“值传送”,因此对于参数不要吝惜括号吧,例如:
#define CIRCLE_S(R) PI*(R)*(R) //带参宏定义
又例如,参数还可以进行多重嵌套:
#define SET_VAL(VARIABLE,VAL) ((VARIABLE) = (VAL)) //设值
#define SET_CLASS_VAL(INSTANCE, SUB_VARIABLE,VAL) SET_VAL(INSTANCE->SUB_VARIABLE, VAL) //多重嵌套,类设值
另外,通过"#"可以实现参数转字符串操作,通过“##”可以实现宏参数粘合在一起
#define STR_TRAN(arg) #arg //#把宏参数arg变为一个字符串
//STR_TRAN(100)等同于"100"
#define CONS(a,b) STR_TRAN(a##b) //##把两个宏参数贴合在一起
//CONS(12,34) 等同于 "1234"
ANSI标准库预定义的宏名,例如__DATE__,__TIME__,__FILE__, __LINE__, __FUNCTION__等,可以在程序直接调用。例如结合“##”,可以构造出类似printf函数类似的宏定义:
//ANSI标准库自带的宏定义:__DATE__,__TIME__,__FILE__, __LINE__, __FUNCTION__等
#define Print_INFO(log_fmt,...) \
do{ \
printf("[%s %s][%s:%d][%s] \n"log_fmt"\n",\
__DATE__, __TIME__, __FILE__, __LINE__, __FUNCTION__, ##__VA_ARGS__); \
}while (0)
//在main函数直接调用如下:
Print_INFO("hi");
//输出示例如下:
//[Feb 10 2023 14:29:05][test.c:47][main]
//hi
通过编译指令或工程配置文件传递进来参与编译的宏,其使用和“#define”命令得到的宏是一致的,例如:
//gcc test.c -o test.exe -DGCC_CMD_DEF //传递宏定义GCC_CMD_DEF
#ifdef GCC_CMD_DEF //条件编译
printf("GCC_CMD_DEF is define!\n");
#endif
又或
//gcc test.c -o test.exe -DGCC_CMD_DEF=1 //传递宏定义GCC_CMD_DEF并指定字符序列
#if GCC_CMD_DEF //条件编译
printf("GCC_CMD_DEF is define!\n");
#endif
三、宏定义应用测试案例
由于宏调用就是将宏名替换为字符串, 掌握"宏"概念的关键是“换”,并且这个“换”是在预处理(预编译)完成的,因此准确理解宏调用语句之前就先要“换”,再去阅读理解。
按上述涉及到宏应用知识点,创建test.h/c源文件:
test.h
#ifndef _TEST__H_ //条件编译
#define _TEST__H_ //防止一个头文件被重复包含
#define PI (3.1415926) //常量宏定义
#define CIRCLE_S(R) PI*(R)*(R) //带参宏定义
#define SET_VAL(VARIABLE,VAL) ((VARIABLE) = (VAL)) //设值
#define SET_CLASS_VAL(INSTANCE, SUB_VARIABLE,VAL) SET_VAL(INSTANCE->SUB_VARIABLE, VAL) //多重嵌套,类设值
#define STR_TRAN(arg) #arg //#把宏参数arg变为一个字符串
#define CONS(a,b) STR_TRAN(a##b) //##把两个宏参数贴合在一起
#define PYFREE //宏定义,用于条件编译
//ANSI标准库自带的宏定义:__DATE__,__TIME__,__FILE__, __LINE__, __FUNCTION__等
#define Print_INFO(log_fmt,...) \
do{ \
printf("[%s %s][%s:%d][%s] \n"log_fmt"\n",__DATE__, __TIME__, __FILE__, __LINE__, __FUNCTION__, ##__VA_ARGS__); \
}while (0)
//
#ifdef WIN32 //条件编译
#pragma message("this is window platform")
#else
#pragma message("this is not window platform")
#endif
#endif //_TEST__H_
test.c
#include "test.h"
//gcc test.c -o test.exe
typedef struct Data_Test
{
char cVal;
int iVal;
}*pData,Data;
int main(int argc, char* argv[])
{
printf("hello,def test!\n");
float r = 2.0;
printf("c=2*PI*r=%0.4f!\n",2*PI*r);
printf("CIRCLE_S(r)=%0.4f!\n",CIRCLE_S(r));
pData pd;
SET_CLASS_VAL(pd,iVal,10);
printf("pd->iVal=%d\n",pd->iVal);
printf("STR_TRAN(val) is \"%s\" \n",STR_TRAN(val));
printf("CONS(a,b) is \"%s\" \n",CONS(a,b));
#ifdef PYFREE //已定义编译条件
printf("PYFREE is be defined!\n"); //执行
#endif
#if defined(PYFREE) //条件编译
//想想"#if PYFREE"呢,为何其不能编译通过,如果“#defined PYFREE 1”呢,有如何
printf("PYFREE is really be defined!\n"); //执行
#endif
#undef PYFREE //取消宏定义
#ifdef PYFREE
printf("PYFREE is be defined!\n"); //则不执行
#endif
#ifndef PYFREE //没定义编译条件,,#ifndef与#ifdef相反
printf("PYFREE is not be defined!\n"); //执行
#endif
#ifdef GCC_CMD_DEF //编译命令指定宏定义,gcc test.c -o test.exe -DGCC_CMD_DEF
printf("GCC_CMD_DEF is define!\n");
#else
printf("GCC_CMD_DEF is not define!\n");
#endif
Print_INFO("hi,ANSI define!\n");
return 0;
}
通过gcc指令编译执行(本文是win 系统下执行的gcc指令):
或者建立Makefile文件,添加如下内容:
CX = g++
GCC_CMD_DEF := 1 #宏定义
BIN := .
TARGET := test.exe
FLAGS := -static
Include := .
source := test.c
$(TARGET) :
$(CX) $(FLAGS) $(source) -I$(Include) -o $(BIN)/$(TARGET)
clean:
rm $(BIN)/$(TARGET)
编译及运行如下: