C++预处理

李双双 | Jan 26, 2024 min read

一 宏定义

1.1 无参数的宏定义(宏常量)

如果在程序中大量使用到了100这个值,那么为了方便管理,我们可以将其定义为: const int num = 100; 但是如果我们使用num定义一个数组,在不支持c99标准的编译器上是不支持的,因为num不是一个编译器常量,如果想得到了一个编译器常量,那么可以使用: #define num 100

在编译预处理时,将程序中在该语句以后出现的所有的num都用100代替。这种方法使用户能以一个简单的名字代替一个长的字符串,在预编译时将宏名替换成字符串的过程称为“宏展开”。

宏定义,只在宏定义的文件中起作用。

说明:
    1) 宏名一般用大写,以便于与变量区别;
    2) 宏定义可以是常数、表达式等;
    3) 宏定义不作语法检查,只有在编译被宏展开后的源程序才会报错;
    4) 宏定义不是C语言,不在行末加分号;
    5) 宏名有效范围为从定义到本源文件结束;
    6) 可以用#undef命令终止宏定义的作用域;
    7) 在宏定义中,可以引用已定义的宏名;
Show Code
#define PI 3.1415
void test(){
	double r = 10.0;
	double s = PI * r * r;
	printf("s = %lf\n", s);
}

1.2 带参数的宏定义(宏函数)

在项目中,经常把一些短小而又频繁使用的函数写成宏函数,这是由于宏函数没有普通函数参数压栈、跳转、返回等的开销,可以调高程序的效率。

宏通过使用参数,可以创建外形和作用都与函数类似地类函数宏(function-like macro). 宏的参数也用圆括号括起来。

很显然,我们不会选择用函数来完成这个任务,原因有两个:首先,函数调用会带来额外的开销,它需要开辟一片栈空间,记录返回地址,将形参压栈,从函数返回还要释放堆栈。这种开销不仅会降低代码效率,而且代码量也会大大增加,而使用宏定义则在代码规模和速度方面都比函数更胜一筹;其次,函数的参数必须被声明为一种特定的类型,所以它只能在类型合适的表达式上使用,我们如果要比较两个浮点型的大小,就不得不再写一个专门针对浮点型的比较函数。反之,上面的那个宏定义可以用于整形、长整形、单浮点型、双浮点型以及其他任何可以用“>”操作符比较值大小的类型,也就是说,宏是与类型无关的。

注意:
    1) 宏的名字中不能有空格,但是在替换的字符串中可以有空格。ANSI C允许在参数列表中使用空格;
    2) 用括号括住每一个参数,并括住宏的整体定义。
    3) 用大写字母表示宏的函数名。
    4) 如果打算宏代替函数来加快程序运行速度。假如在程序中只使用一次宏对程序的运行时间没有太大提高。
Show Code
#define SUM(x,y) (( x )+( y ))
void test(){
	
	//仅仅只是做文本替换 下例替换为 int ret = ((10)+(20));
	//不进行计算
	int ret = SUM(10, 20);
	printf("ret:%d\n",ret);
}

#define SUM(x,y) (( x )+( y ))
void test(){
	
	//仅仅只是做文本替换 下例替换为 int ret = ((10)+(20));
	//不进行计算
	int ret = SUM(10, 20);
	printf("ret:%d\n",ret);
}

1.3 一些特殊的预定宏

C编译器,提供了几个特殊形式的预定义宏,在实际编程中可以直接使用,很方便。

Show Code
//	__FILE__			宏所在文件的源文件名
//	__LINE__			宏所在行的行号
//	__DATE__			代码编译的日期
//	__TIME__			代码编译的时间
//   #line 行号 [“文件名”]       将行号和文件名更改为指定的行号和文件名;
//  __func__和__FUNCTION__  代表当前函数的函数名,类型为字符串常量;
void test()
{
	printf("%s\n", __FILE__);
	printf("%d\n", __LINE__);
	printf("%s\n", __DATE__);
	printf("%s\n", __TIME__);
}

1.4 宏函数的高级写法

宏定义中允许包含两行以上命令的情形,此时必须在最右边加上”\”且该行”\”后不能再有任何字符,连注释部分都不能有,下面的每行最后的一定要是”\”,”\”后面加一个空格都会报错,更不能跟注释。

  • define的单行定义
#define maxi(a,b) (a>;b?a:b)
  • define的多行定义
// 第一种写法
#define   MACRO(arg1,   arg2)   do   {   
    stmt1;    
    stmt2;    
}while(0)    

// 第二种写法
#define  SORT( a,  n)
{
    int i, j;
    int *t = MALLOC(1,int);
    for(i=0; i<n-1; i++)
    {
        for(j=0; j<n-1-i; j++)
        {
            if(*(a+j) > *(a+j+1))
            {
                *t = *(a+j);
                *(a+j) = *(a+j+1);
                *(a+j+1) = *t;
            }
        }
    }
}

// 第三种写法
// 宏定义写出swap(x,y)交换函数
#define swap(x, y)
x = x + y;
y = x - y;
x = x - y;

二 条件编译

2.1 基本概念

一般情况下,源程序中所有的行都参加编译。但有时希望对部分源程序行只在满足一定条件时才编译,即对这部分源程序行指定编译条件。

2.2 条件编译

防止头文件被重复包含引用;

Show Code
#ifndef _SOMEFILE_H
#define _SOMEFILE_H

//需要声明的变量、函数
//宏定义
//结构体

#endif

三 文件包含指令(#include)

3.1 文件包含处理

“文件包含处理”是指一个源文件可以将另外一个文件的全部内容包含进来。C语言提供了#include命令用来实现“文件包含”的操作。 Minion

3.2 #incude<>和#include"“区别

  • " " 表示系统先在file1.c所在的当前目录找file1.h,如果找不到,再按系统指定的目录检索。
  • < > 表示系统直接按系统指定的目录检索。
    注意:
    1. #include <>常用于包含库函数的头文件;
    2. #include " “常用于包含自定义的头文件;
    3. 理论上#include可以包含任意格式的文件(.c .h等) ,但一般用于头文件的包含;